mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JS: Add query for resource exhaustion from deep object handling
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Processing user-controlled data with a method that allocates excessive amounts
|
||||
of memory can lead to denial of service.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If the JSON schema validation library <code>ajv</code> is configured with
|
||||
<code>allErrors: true</code> there is no limit to how many error objects
|
||||
will be allocated. An attacker can exploit this by sending an object that
|
||||
deliberately contains a huge number of errors, and in some cases, with
|
||||
longer and longer error messages. This can cause the service to become
|
||||
unresponsive due to the slow error-checking process.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Do not use <code>allErrors: true</code> in production.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the example below, the user-submitted object <code>req.body</code> is
|
||||
validated using <code>ajv</code> and <code>allErrors: true</code>:
|
||||
</p>
|
||||
|
||||
<sample src="examples/DeepObjectResourceExhaustion.js"/>
|
||||
|
||||
<p>
|
||||
Although this ensures that <code>req.body</code> conforms to the schema,
|
||||
the validation itself could be vulnerable to a denial-of-service attack.
|
||||
An attacker could send an object containing so many errors that the server
|
||||
runs out of memory.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A solution is to not pass in <code>allErrors: true</code>, which means
|
||||
<code>ajv</code> will only report the first error, not all of them:
|
||||
</p>
|
||||
|
||||
<sample src="examples/DeepObjectResourceExhaustion_fixed.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Ajv documentation: <a href="https://github.com/ajv-validator/ajv/blob/master/docs/security.md#untrusted-schemas">security considerations</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Resources exhaustion from deep object traversal
|
||||
* @description Processing user-controlled object hierarchies inefficiently can lead to denial of service.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/resource-exhaustion-from-deep-object-traversal
|
||||
* @tags security
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.DeepObjectResourceExhaustion::DeepObjectResourceExhaustion
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node link,
|
||||
string reason
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
sink.getNode().(Sink).hasReason(link, reason)
|
||||
select sink, source, sink, "Denial of service caused by processing user input from $@ with $@.",
|
||||
source.getNode(), "here", link, reason
|
||||
@@ -0,0 +1,14 @@
|
||||
import express from 'express';
|
||||
import Ajv from 'ajv';
|
||||
|
||||
let ajv = new Ajv({ allErrors: true });
|
||||
ajv.addSchema(require('./input-schema'), 'input');
|
||||
|
||||
var app = express();
|
||||
app.get('/user/:id', function(req, res) {
|
||||
if (!ajv.validate('input', req.body)) {
|
||||
res.end(ajv.errorsText());
|
||||
return;
|
||||
}
|
||||
// ...
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import express from 'express';
|
||||
import Ajv from 'ajv';
|
||||
|
||||
let ajv = new Ajv({ allErrors: process.env['REST_DEBUG'] });
|
||||
ajv.addSchema(require('./input-schema'), 'input');
|
||||
|
||||
var app = express();
|
||||
app.get('/user/:id', function(req, res) {
|
||||
if (!ajv.validate('input', req.body)) {
|
||||
res.end(ajv.errorsText());
|
||||
return;
|
||||
}
|
||||
// ...
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about DoS attacks
|
||||
* due to inefficient handling of user-controlled objects.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.TaintedObject
|
||||
|
||||
module DeepObjectResourceExhaustion {
|
||||
import DeepObjectResourceExhaustionCustomizations::DeepObjectResourceExhaustion
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for reasoning about DoS attacks due to inefficient handling
|
||||
* of user-controlled objects.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "DeepObjectResourceExhaustion" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
source instanceof Source and label = TaintedObject::label()
|
||||
or
|
||||
// We currently can't expose the TaintedObject label in the Customizations library
|
||||
// so just add its default sources here.
|
||||
source instanceof TaintedObject::Source and label = TaintedObject::label()
|
||||
or
|
||||
source instanceof RemoteFlowSource and label.isTaint()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
sink instanceof Sink and label = TaintedObject::label()
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof TaintedObject::SanitizerGuard
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
|
||||
) {
|
||||
TaintedObject::step(src, trg, inlbl, outlbl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Provides sources, sinks and sanitizers for reasoning about
|
||||
* DoS attacks due to inefficient handling of user-controlled objects.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Provides sources, sinks and sanitizers for reasoning about
|
||||
* DoS attacks due to inefficient handling of user-controlled objects.
|
||||
*/
|
||||
module DeepObjectResourceExhaustion {
|
||||
/**
|
||||
* A data flow source for slow input validation.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for slow input validation.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `link` and `text` should be included in the message to explain
|
||||
* why the input validation is slow.
|
||||
*/
|
||||
abstract predicate hasReason(DataFlow::Node link, string text);
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for slow input validation.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** Gets a node that may refer to an object with `allErrors` set to `true`. */
|
||||
private DataFlow::SourceNode allErrorsObject(
|
||||
DataFlow::TypeTracker t, DataFlow::PropWrite allErrors
|
||||
) {
|
||||
t.start() and
|
||||
exists(JsonSchema::Ajv::AjvValidationCall call) and // only compute if `ajv` is used
|
||||
allErrors.getPropertyName() = "allErrors" and
|
||||
allErrors.getRhs().mayHaveBooleanValue(true) and
|
||||
result = allErrors.getBase().getALocalSource()
|
||||
or
|
||||
exists(ExtendCall call |
|
||||
allErrorsObject(t.continue(), allErrors).flowsTo(call.getAnOperand()) and
|
||||
(result = call or result = call.getDestinationOperand().getALocalSource())
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ObjectLiteralNode obj |
|
||||
allErrorsObject(t.continue(), allErrors).flowsTo(obj.getASpreadProperty()) and
|
||||
result = obj
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = allErrorsObject(t2, allErrors).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a node that may refer to an object with `allErrors` set to `true`. */
|
||||
private DataFlow::SourceNode allErrorsObject(DataFlow::PropWrite allErrors) {
|
||||
result = allErrorsObject(DataFlow::TypeTracker::end(), allErrors)
|
||||
}
|
||||
|
||||
/** Argument to an `ajv` validation call configured with `allErrors: true`. */
|
||||
private class AjvValidationSink extends Sink {
|
||||
DataFlow::PropWrite allErrors;
|
||||
|
||||
AjvValidationSink() {
|
||||
exists(JsonSchema::Ajv::AjvValidationCall call |
|
||||
this = call.getInput() and
|
||||
allErrorsObject(allErrors).flowsTo(call.getAnOptionsArg())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasReason(DataFlow::Node link, string text) {
|
||||
link = allErrors and
|
||||
text = "allErrors: true"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user