mirror of
https://github.com/github/codeql.git
synced 2025-12-18 09:43:15 +01:00
Merge pull request #1224 from asger-semmle/cheerio
Approved by esben-semmle
This commit is contained in:
@@ -62,6 +62,7 @@ import semmle.javascript.frameworks.AsyncPackage
|
||||
import semmle.javascript.frameworks.AWS
|
||||
import semmle.javascript.frameworks.Azure
|
||||
import semmle.javascript.frameworks.Babel
|
||||
import semmle.javascript.frameworks.Cheerio
|
||||
import semmle.javascript.frameworks.ComposedFunctions
|
||||
import semmle.javascript.frameworks.ClientRequests
|
||||
import semmle.javascript.frameworks.ClosureLibrary
|
||||
|
||||
120
javascript/ql/src/semmle/javascript/frameworks/Cheerio.qll
Normal file
120
javascript/ql/src/semmle/javascript/frameworks/Cheerio.qll
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Provides a model of `cheerio`, a server-side DOM manipulation library with a jQuery-like API.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss as Xss
|
||||
|
||||
module Cheerio {
|
||||
/**
|
||||
* A reference to the `cheerio` function, possibly with a loaded DOM.
|
||||
*/
|
||||
private DataFlow::SourceNode cheerioRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = DataFlow::moduleImport("cheerio")
|
||||
or
|
||||
exists(string name | result = cheerioRef().getAMemberCall(name) |
|
||||
name = "load" or
|
||||
name = "parseHTML"
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = cheerioRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the `cheerio` function, possibly with a loaded DOM.
|
||||
*/
|
||||
DataFlow::SourceNode cheerioRef() { result = cheerioRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Creation of `cheerio` object, a collection of virtual DOM elements
|
||||
* with an interface similar to that of a jQuery object.
|
||||
*/
|
||||
class CheerioObjectCreation extends DataFlow::SourceNode {
|
||||
CheerioObjectCreation::Range range;
|
||||
|
||||
CheerioObjectCreation() { this = range }
|
||||
}
|
||||
|
||||
module CheerioObjectCreation {
|
||||
/**
|
||||
* Creation of a `cheerio` object.
|
||||
*/
|
||||
abstract class Range extends DataFlow::SourceNode { }
|
||||
|
||||
private class DefaultRange extends Range {
|
||||
DefaultRange() {
|
||||
this = cheerioRef().getACall()
|
||||
or
|
||||
this = cheerioRef().getAMethodCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `cheerio` object, a collection of virtual DOM elements
|
||||
* with an interface similar to jQuery objects.
|
||||
*/
|
||||
private DataFlow::SourceNode cheerioObjectRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof CheerioObjectCreation
|
||||
or
|
||||
// Chainable calls.
|
||||
t.start() and
|
||||
exists(DataFlow::MethodCallNode call, string name |
|
||||
call = cheerioObjectRef().getAMethodCall(name) and
|
||||
result = call
|
||||
|
|
||||
if name = "attr" or name = "data" or name = "prop" or name = "css"
|
||||
then call.getNumArgument() = 2
|
||||
else
|
||||
if name = "val" or name = "html" or name = "text"
|
||||
then call.getNumArgument() = 1
|
||||
else (
|
||||
name != "toString" and
|
||||
name != "toArray" and
|
||||
name != "hasClass"
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = cheerioObjectRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `cheerio` object, a collection of virtual DOM elements
|
||||
* with an interface similar to jQuery objects.
|
||||
*/
|
||||
DataFlow::SourceNode cheerioObjectRef() {
|
||||
result = cheerioObjectRef(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a DOM attribute through `cheerio`.
|
||||
*/
|
||||
class AttributeDef extends DOM::AttributeDefinition {
|
||||
DataFlow::CallNode call;
|
||||
|
||||
AttributeDef() {
|
||||
this = call.asExpr() and
|
||||
call = cheerioObjectRef().getAMethodCall("attr") and
|
||||
call.getNumArgument() >= 2
|
||||
}
|
||||
|
||||
override string getName() { call.getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValueNode() { result = call.getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* XSS sink through `cheerio`.
|
||||
*/
|
||||
class XssSink extends Xss::DomBasedXss::Sink {
|
||||
XssSink() {
|
||||
exists(string name | this = cheerioObjectRef().getAMethodCall(name).getAnArgument() |
|
||||
JQuery::isMethodArgumentInterpretedAsHtml(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,21 +66,7 @@ class JQueryMethodCall extends CallExpr {
|
||||
*/
|
||||
predicate interpretsArgumentAsHtml(Expr e) {
|
||||
// some methods interpret all their arguments as (potential) HTML
|
||||
(
|
||||
name = "after" or
|
||||
name = "append" or
|
||||
name = "appendTo" or
|
||||
name = "before" or
|
||||
name = "html" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prepend" or
|
||||
name = "prependTo" or
|
||||
name = "replaceWith" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
) and
|
||||
JQuery::isMethodArgumentInterpretedAsHtml(name) and
|
||||
e = getAnArgument()
|
||||
or
|
||||
// for `$, it's only the first one
|
||||
@@ -97,15 +83,7 @@ class JQueryMethodCall extends CallExpr {
|
||||
*/
|
||||
predicate interpretsArgumentAsSelector(Expr e) {
|
||||
// some methods interpret all their arguments as (potential) selectors
|
||||
(
|
||||
name = "appendTo" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prependTo" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
) and
|
||||
JQuery::isMethodArgumentInterpretedAsSelector(name) and
|
||||
e = getAnArgument()
|
||||
or
|
||||
// for `$, it's only the first one
|
||||
@@ -308,3 +286,39 @@ private class JQueryClientRequest extends CustomClientRequest {
|
||||
|
||||
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
|
||||
}
|
||||
|
||||
module JQuery {
|
||||
/**
|
||||
* Holds if method `name` on a jQuery object may interpret any of its
|
||||
* arguments as HTML.
|
||||
*/
|
||||
predicate isMethodArgumentInterpretedAsHtml(string name) {
|
||||
name = "after" or
|
||||
name = "append" or
|
||||
name = "appendTo" or
|
||||
name = "before" or
|
||||
name = "html" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prepend" or
|
||||
name = "prependTo" or
|
||||
name = "replaceWith" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if method `name` on a jQuery object may interpret any of its
|
||||
* arguments as a selector.
|
||||
*/
|
||||
predicate isMethodArgumentInterpretedAsSelector(string name) {
|
||||
name = "appendTo" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prependTo" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user