JS: Add schema validation as TaintedObject sanitizer

This commit is contained in:
Asger Feldthaus
2021-02-22 20:28:39 +00:00
parent 79839d2304
commit b978359803
6 changed files with 147 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ import semmle.javascript.InclusionTests
import semmle.javascript.JSDoc
import semmle.javascript.JSON
import semmle.javascript.JsonParsers
import semmle.javascript.JsonSchema
import semmle.javascript.JsonStringifiers
import semmle.javascript.JSX
import semmle.javascript.Lines

View File

@@ -0,0 +1,73 @@
/**
* Provides classes and predicates for working with JSON schema libraries.
*/
import javascript
/**
* Provides classes and predicates for working with JSON schema libraries.
*/
module JsonSchema {
/** A call that validates an input against a JSON schema. */
abstract class ValidationCall extends DataFlow::CallNode {
/** Gets the data flow node whose value is being validated. */
abstract DataFlow::Node getInput();
/** Gets the return value that indicates successful validation. */
boolean getPolarity() { result = true }
}
/** Provides a model of the `ajv` library. */
module Ajv {
/** A method on `Ajv` that returns `this`. */
private string chainedMethod() {
result =
["addSchema", "addMetaSchema", "removeSchema", "addFormat", "addKeyword", "removeKeyword"]
}
/** An instance of `ajv`. */
class Instance extends API::InvokeNode {
Instance() { this = API::moduleImport("ajv").getAnInstantiation() }
/** Gets the data flow node holding the options passed to this `Ajv` instance. */
DataFlow::Node getOptionsArg() { result = getArgument(0) }
/** Gets an API node that refers to this object. */
API::Node ref() {
result = getReturn()
or
result = ref().getMember(chainedMethod()).getReturn()
}
}
/** A call to the `validate` method of `ajv`. */
class AjvValidationCall extends ValidationCall {
Instance instance;
int argIndex;
AjvValidationCall() {
this = instance.ref().getMember("validate").getACall() and argIndex = 1
or
this = instance.ref().getMember(["compile", "getSchema"]).getReturn().getACall() and
argIndex = 0
or
this = instance.ref().getMember("compileAsync").getPromised().getACall() and argIndex = 0
}
override DataFlow::Node getInput() { result = getArgument(argIndex) }
/** Gets the argument holding additional options to the call. */
DataFlow::Node getOwnOptionsArg() { result = getArgument(argIndex + 1) }
/** Gets a data flow passed as the extra options to this validation call or to the underlying `Ajv` instance. */
DataFlow::Node getAnOptionsArg() {
result = getOwnOptionsArg()
or
result = instance.getOptionsArg()
}
/** Gets the ajv instance doing the validation. */
Instance getAjvInstance() { result = instance }
}
}
}

View File

@@ -120,4 +120,19 @@ module TaintedObject {
label = label()
}
}
/**
* A sanitizer guard that validates an input against a JSON schema.
*/
private class JsonSchemaValidationGuard extends SanitizerGuard {
JsonSchema::ValidationCall call;
JsonSchemaValidationGuard() { this = call }
override predicate sanitizes(boolean outcome, Expr e, FlowLabel label) {
outcome = call.getPolarity() and
e = call.getInput().asExpr() and
label = label()
}
}
}