Merge pull request #1224 from asger-semmle/cheerio

Approved by esben-semmle
This commit is contained in:
semmle-qlci
2019-04-11 15:21:44 +01:00
committed by GitHub
7 changed files with 199 additions and 24 deletions

View File

@@ -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

View 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)
)
}
}
}

View File

@@ -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"
}
}