JS: Add ajv error as source of ExceptionXss

This commit is contained in:
Asger Feldthaus
2021-02-26 08:35:38 +00:00
parent 24199a5499
commit 7afa755597
7 changed files with 117 additions and 7 deletions

View File

@@ -5,8 +5,8 @@
<overview>
<p>
Directly writing exceptions to a webpage without sanitization allows for a cross-site scripting
vulnerability if the value of the exception can be influenced by a user.
Directly writing error messages to a webpage without sanitization allows for a cross-site scripting
vulnerability if parts of the error message can be influenced by a user.
</p>
</overview>
@@ -27,6 +27,19 @@ leaving the website vulnerable to cross-site scripting.
<sample src="examples/ExceptionXss.js" />
</example>
<example>
<p>
This second example shows an input being validated using the JSON schema validator <code>ajv</code>,
and in case of an error, the error message is sent directly back in the response.
</p>
<sample src="examples/ExceptionXssAjv.js" />
<p>
This is unsafe, because the error message can contain parts of the input.
For example, the input <code>{'&lt;img src=x onerror=alert(1)&gt;': 'foo'}</code> will generate the error
<code>data/&lt;img src=x onerror=alert(1)&gt; should be number</code>, causing reflected XSS.
</p>
</example>
<references>
<li>
OWASP:

View File

@@ -15,8 +15,18 @@ import javascript
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
import DataFlow::PathGraph
/**
* Gets a description of the source.
*/
string getSourceDescription(DataFlow::Node source) {
result = source.(ErrorSource).getDescription()
or
not source instanceof ErrorSource and
result = "Exception text"
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"$@ is reinterpreted as HTML without escaping meta-characters.", source.getNode(),
"Exception text"
getSourceDescription(source.getNode())

View File

@@ -0,0 +1,13 @@
import express from 'express';
import Ajv from 'ajv';
let app = express();
let ajv = new Ajv();
ajv.addSchema({type: 'object', additionalProperties: {type: 'number'}}, 'pollData');
app.post('/polldata', (req, res) => {
if (!ajv.validate('pollData', req.body)) {
res.send(ajv.errorsText());
}
});

View File

@@ -38,6 +38,31 @@ module JsonSchema {
or
result = ref().getMember(chainedMethod()).getReturn()
}
/**
* Gets an API node for a function produced by `new Ajv().compile()` or similar.
*
* Note that this does not include the instance method `new Ajv().validate` as its
* signature is different.
*/
API::Node getAValidationFunction() {
result = ref().getMember(["compile", "getSchema"]).getReturn()
or
result = ref().getMember("compileAsync").getPromised()
}
/**
* Gets an API node that refers to an error produced by this Ajv instance.
*/
API::Node getAValidationError() {
exists(API::Node base |
base = [ref(), getAValidationFunction()]
|
result = base.getMember("errors")
or
result = base.getMember("errorsText").getReturn()
)
}
}
/** A call to the `validate` method of `ajv`. */
@@ -48,10 +73,7 @@ module JsonSchema {
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
this = instance.getAValidationFunction().getACall() and argIndex = 0
}
override DataFlow::Node getInput() { result = getArgument(argIndex) }

View File

@@ -130,6 +130,37 @@ module ExceptionXss {
result = getCallbackErrorParam(pred)
}
/**
* A source of error values that is likely to contain unencoded user input.
*/
abstract class ErrorSource extends DataFlow::Node {
/**
* Gets a human-readable description of what type of error this refers to.
*
* The result should be captialized and usable in the context of a noun.
*/
abstract string getDescription();
}
/**
* An error produced by validating using `ajv`.
*
* Such an error can contain property names from the input if the
* underlying schema uses `additionalProperties` or `propertyPatterns`.
*
* For example, an input of form `{"<img src=x onerror=alert(1)>": 45}` might produce the error
* `data/<img src=x onerror=alert(1)> should be string`.
*/
private class JsonSchemaValidationError extends ErrorSource {
JsonSchemaValidationError() {
this = any(JsonSchema::Ajv::Instance i).getAValidationError().getAnImmediateUse()
}
override string getDescription() {
result = "JSON schema validation error"
}
}
/**
* A taint-tracking configuration for reasoning about XSS with possible exceptional flow.
* Flow labels are used to ensure that we only report taint-flow that has been thrown in
@@ -140,6 +171,9 @@ module ExceptionXss {
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
source instanceof Xss::Shared::Source and label instanceof NotYetThrown
or
source instanceof ErrorSource and
label.isTaint()
}
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {