Merge branch 'js-team-sprint' into js/insecure-http-options

This commit is contained in:
Asger F
2020-06-23 00:16:02 +01:00
committed by GitHub
27 changed files with 13 additions and 1095 deletions

View File

@@ -3,22 +3,6 @@
"qhelp.dtd">
<qhelp>
<overview>
</overview>
<recommendation>
</recommendation>
<example>
</example>
<references>
<li>OWASP Top 10: <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">A1 Injection</a>.</li>
</references>
<include src="IncompleteSanitization.qhelp" />
</qhelp>

View File

@@ -1,22 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
</overview>
<recommendation>
</recommendation>
<example>
</example>
<references>
</references>
</qhelp>

View File

@@ -1,100 +0,0 @@
/**
* @name Server crash
* @description A server that can be forced to crash may be vulnerable to denial-of-service
* attacks.
* @kind problem
* @problem.severity error
* @precision high
* @id js/server-crash
* @tags security
* external/cwe/cwe-730
*/
import javascript
/**
* Gets a function that `caller` invokes.
*/
Function getACallee(Function caller) {
exists(DataFlow::InvokeNode invk |
invk.getEnclosingFunction() = caller and result = invk.getACallee()
)
}
/**
* Gets a function that `caller` invokes, excluding calls guarded in `try`-blocks.
*/
Function getAnUnguardedCallee(Function caller) {
exists(DataFlow::InvokeNode invk |
invk.getEnclosingFunction() = caller and
result = invk.getACallee() and
not exists(invk.asExpr().getEnclosingStmt().getEnclosingTryCatchStmt())
)
}
predicate isHeaderValue(HTTP::ExplicitHeaderDefinition def, DataFlow::Node node) {
def.definesExplicitly(_, node.asExpr())
}
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Configuration" }
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node node) {
// using control characters in a header value will cause an exception
isHeaderValue(_, node)
}
}
predicate isLikelyToThrow(DataFlow::Node crash) {
exists(Configuration cfg, DataFlow::Node sink | cfg.hasFlow(_, sink) | isHeaderValue(crash, sink))
}
/**
* A call that looks like it is asynchronous.
*/
class AsyncCall extends DataFlow::CallNode {
DataFlow::FunctionNode callback;
AsyncCall() {
callback.flowsTo(getLastArgument()) and
callback.getParameter(0).getName() = ["e", "err", "error"] and
callback.getNumParameter() = 2 and
not exists(callback.getAReturn())
}
DataFlow::FunctionNode getCallback() { result = callback }
}
/**
* Gets a function that is invoked by `asyncCallback` without any try-block wrapping, `asyncCallback` is in turn is called indirectly by `routeHandler`.
*
* If the result throws an excection, the server of `routeHandler` will crash.
*/
Function getAPotentialServerCrasher(
HTTP::RouteHandler routeHandler, DataFlow::FunctionNode asyncCallback
) {
exists(AsyncCall asyncCall |
// the route handler transitively calls an async function
asyncCall.getEnclosingFunction() =
getACallee*(routeHandler.(DataFlow::FunctionNode).getFunction()) and
asyncCallback = asyncCall.getCallback() and
// the async function transitively calls a function that may throw an exception out of the the async function
result = getAnUnguardedCallee*(asyncCallback.getFunction())
)
}
/**
* Gets an AST node that is likely to throw an uncaught exception in `fun`.
*/
ExprOrStmt getALikelyExceptionThrower(Function fun) {
result.getContainer() = fun and
not exists([result.(Expr).getEnclosingStmt(), result.(Stmt)].getEnclosingTryCatchStmt()) and
(isLikelyToThrow(result.(Expr).flow()) or result instanceof ThrowStmt)
}
from HTTP::RouteHandler routeHandler, DataFlow::FunctionNode asyncCallback, ExprOrStmt crasher
where crasher = getALikelyExceptionThrower(getAPotentialServerCrasher(routeHandler, asyncCallback))
select crasher, "When an exception is thrown here and later exits $@, the server of $@ will crash.",
asyncCallback, "this asynchronous callback", routeHandler, "this route handler"

View File

@@ -1,113 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Applications are constrained by how many resources they can make use
of. Failing to respect these constraints may cause the application to
be unresponsive or crash. It is therefore problematic if attackers
can control the sizes or lifetimes of allocated objects.
</p>
</overview>
<recommendation>
<p>
Ensure that attackers can not control object sizes and their
lifetimes. If object sizes and lifetimes must be controlled by
external parties, ensure you restrict the object sizes and lifetimes so that
they are within acceptable ranges.
</p>
</recommendation>
<example>
<p>
The following example allocates a buffer with a user-controlled
size.
</p>
<sample src="examples/ResourceExhaustion_buffer.js" />
<p>
This is problematic since an attacker can choose a size
that makes the application run out of memory. Even worse, in older
versions of Node.js, this could leak confidential memory.
To prevent such attacks, limit the buffer size:
</p>
<sample src="examples/ResourceExhaustion_buffer_fixed.js" />
</example>
<example>
<p>
As another example, consider an application that allocates an
array with a user-controlled size, and then fills it with values:
</p>
<sample src="examples/ResourceExhaustion_array.js" />
<p>
The allocation of the array itself is not problematic since arrays are
allocated sparsely, but the subsequent filling of the array will take
a long time, causing the application to be unresponsive, or even run
out of memory.
Again, a limit on the size will prevent the attack:
</p>
<sample src="examples/ResourceExhaustion_array_fixed.js" />
</example>
<example>
<p>
Finally, the following example lets a user choose a delay after
which a function is executed:
</p>
<sample src="examples/ResourceExhaustion_timeout.js" />
<p>
This is problematic because a large delay essentially makes the
application wait indefinitely before executing the function. Repeated
registrations of such delays will therefore use up all of the memory
in the application.
Again, a limit on the delay will prevent the attack:
</p>
<sample src="examples/ResourceExhaustion_timeout_fixed.js" />
</example>
<references>
</references>
</qhelp>

View File

@@ -1,20 +0,0 @@
/**
* @name Resource exhaustion
* @description Allocating objects or timers with user-controlled
* sizes or durations can cause resource exhaustion.
* @kind path-problem
* @problem.severity warning
* @id js/resource-exhaustion
* @precision high
* @tags security
* external/cwe/cwe-770
*/
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.ResourceExhaustion::ResourceExhaustion
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
where dataflow.hasFlowPath(source, sink)
select sink, source, sink, sink.getNode().(Sink).getProblemDescription() + " from $@.", source,
"here"

View File

@@ -1,10 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var size = parseInt(url.parse(req.url, true).query.size);
let dogs = new Array(size).fill(x => "dog"); // BAD
// ... use the dogs
});

View File

@@ -1,16 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var size = parseInt(url.parse(req.url, true).query.size);
if (size > 1024) {
res.statusCode = 400;
res.end("Bad request.");
return;
}
let dogs = new Array(size).fill(x => "dog"); // GOOD
// ... use the dogs
});

View File

@@ -1,10 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var size = parseInt(url.parse(req.url, true).query.size);
let buffer = Buffer.alloc(size); // BAD
// ... use the buffer
});

View File

@@ -1,16 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var size = parseInt(url.parse(req.url, true).query.size);
if (size > 1024) {
res.statusCode = 400;
res.end("Bad request.");
return;
}
let buffer = Buffer.alloc(size); // GOOD
// ... use the buffer
});

View File

@@ -1,9 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var delay = parseInt(url.parse(req.url, true).query.delay);
setTimeout(f, delay); // BAD
});

View File

@@ -1,15 +0,0 @@
var http = require("http"),
url = require("url");
var server = http.createServer(function(req, res) {
var delay = parseInt(url.parse(req.url, true).query.delay);
if (delay > 1000) {
res.statusCode = 400;
res.end("Bad request.");
return;
}
setTimeout(f, delay); // GOOD
});