JavaScript: Add model of JSON parsers

This commit is contained in:
Asger F
2018-07-27 16:07:28 +01:00
parent 3c0f04ac96
commit 156b94e436
11 changed files with 186 additions and 37 deletions

View File

@@ -23,6 +23,7 @@ import semmle.javascript.Functions
import semmle.javascript.HTML
import semmle.javascript.JSDoc
import semmle.javascript.JSON
import semmle.javascript.JsonParsers
import semmle.javascript.JSX
import semmle.javascript.Lines
import semmle.javascript.Locations

View File

@@ -0,0 +1,80 @@
/**
* Provides classes for working with JSON parsers.
*/
import javascript
/**
* A call to a JSON parser such as `JSON.parse`.
*/
abstract class JsonParserCall extends DataFlow::CallNode {
/**
* Gets the data flow node holding the input string to be parsed.
*/
abstract DataFlow::Node getInput();
/**
* Gets the data flow node holding the resulting JSON object.
*/
abstract DataFlow::SourceNode getOutput();
}
/**
* A JSON parser that returns its result.
*/
private class PlainJsonParserCall extends JsonParserCall {
PlainJsonParserCall() {
exists (DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::globalVarRef("JSON").getAPropertyRead("parse") or
callee = DataFlow::moduleImport("parse-json") or
callee = DataFlow::moduleImport("json-parse-better-errors") or
callee = DataFlow::moduleImport("json-safe-parse"))
}
override DataFlow::Node getInput() {
result = getArgument(0)
}
override DataFlow::SourceNode getOutput() {
result = this
}
}
/**
* A JSON parser that returns its result wrapped in a another object.
*/
private class JsonParserCallWithWrapper extends JsonParserCall {
string outputPropName;
JsonParserCallWithWrapper() {
exists (DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::moduleImport("safe-json-parse/tuple") and outputPropName = "1" or
callee = DataFlow::moduleImport("safe-json-parse/result") and outputPropName = "v" or
callee = DataFlow::moduleImport("fast-json-parse") and outputPropName = "value" or
callee = DataFlow::moduleImport("json-parse-safe") and outputPropName = "value")
}
override DataFlow::Node getInput() {
result = getArgument(0)
}
override DataFlow::SourceNode getOutput() {
result = this.getAPropertyRead(outputPropName)
}
}
/**
* A JSON parser that returns its result through a callback argument.
*/
private class JsonParserCallWithCallback extends JsonParserCall {
JsonParserCallWithCallback() {
this = DataFlow::moduleImport("safe-json-parse/callback").getACall()
}
override DataFlow::Node getInput() {
result = getArgument(0)
}
override DataFlow::SourceNode getOutput() {
result = getCallback(1).getParameter(1)
}
}

View File

@@ -44,8 +44,11 @@ class DirectEval extends CallExpr {
}
/**
* DEPRECATED. Use `JsonParserCall` and the data flow API instead.
*
* A call to `JSON.parse`.
*/
deprecated
class JsonParseCall extends MethodCallExpr {
JsonParseCall() {
this = DataFlow::globalVarRef("JSON").getAMemberCall("parse").asExpr()

View File

@@ -400,14 +400,11 @@ module TaintTracking {
}
/**
* A taint propagating data flow edge arising from JSON parsing or unparsing.
* A taint propagating data flow edge arising from JSON unparsing.
*/
private class JsonManipulationTaintStep extends AdditionalTaintStep, DataFlow::MethodCallNode {
JsonManipulationTaintStep() {
exists (string methodName |
methodName = "parse" or methodName = "stringify" |
this = DataFlow::globalVarRef("JSON").getAMemberCall(methodName)
)
private class JsonStringifyTaintStep extends AdditionalTaintStep, DataFlow::MethodCallNode {
JsonStringifyTaintStep() {
this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify")
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
@@ -415,6 +412,20 @@ module TaintTracking {
}
}
/**
* A taint propagating data flow edge arising from JSON parsing.
*/
private class JsonParserTaintStep extends AdditionalTaintStep, DataFlow::CallNode {
JsonParserCall call;
JsonParserTaintStep() { this = call }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = call.getInput() and
succ = call.getOutput()
}
}
/**
* Holds if `params` is a `URLSearchParams` object providing access to
* the parameters encoded in `input`.

View File

@@ -55,36 +55,6 @@ module NosqlInjection {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/**
* A taint tracking configuration for tracking user input that flows
* into a call to `JSON.parse`.
*/
private class RemoteJsonTrackingConfig extends TaintTracking::Configuration {
RemoteJsonTrackingConfig() {
this = "RemoteJsonTrackingConfig"
}
override predicate isSource(DataFlow::Node nd) {
nd instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node nd) {
nd.asExpr() = any(JsonParseCall c).getArgument(0)
}
}
/**
* A call to `JSON.parse` where the argument is user-provided.
*/
class RemoteJson extends Source, DataFlow::ValueNode {
RemoteJson() {
exists (DataFlow::Node parsedArg |
parsedArg.asExpr() = astNode.(JsonParseCall).getArgument(0) and
any(RemoteJsonTrackingConfig cfg).hasFlow(_, parsedArg)
)
}
}
/** An expression interpreted as a NoSQL query, viewed as a sink. */
class NosqlQuerySink extends Sink, DataFlow::ValueNode {
override NoSQL::Query astNode;