mirror of
https://github.com/github/codeql.git
synced 2026-04-28 18:25:24 +02:00
JS: Add ajv error as source of ExceptionXss
This commit is contained in:
@@ -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>{'<img src=x onerror=alert(1)>': 'foo'}</code> will generate the error
|
||||
<code>data/<img src=x onerror=alert(1)> should be number</code>, causing reflected XSS.
|
||||
</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
@@ -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) }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user