QL code and tests for C#/C++/JavaScript.

This commit is contained in:
Pavel Avgustinov
2018-08-02 17:53:23 +01:00
commit b55526aa58
10684 changed files with 581163 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Accessing files using paths constructed from user-controlled data can allow an attacker to access
unexpected resources. This can result in sensitive information being revealed or deleted, or an
attacker being able to influence behavior by modifying unexpected files.
</p>
</overview>
<recommendation>
<p>
Validate user input before using it to construct a file path, either using an off-the-shelf library
like the <code>sanitize-filename</code> npm package, or by performing custom validation.
</p>
<p>
Ideally, follow these rules:
</p>
<ul>
<li>Do not allow more than a single "." character.</li>
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
applying this filter to ".../...//", the resulting string would still be "../".</li>
<li>Use a whitelist of known good patterns.</li>
</ul>
</recommendation>
<example>
<p>
In the first example, a file name is read from an HTTP request and then used to access a file.
However, a malicious user could enter a file name which is an absolute path, such as
<code>"/etc/passwd"</code>.
</p>
<p>
In the second example, it appears that the user is restricted to opening a file within the
<code>"user"</code> home directory. However, a malicious user could enter a file name containing
special characters. For example, the string <code>"../../etc/passwd"</code> will result in the code
reading the file located at <code>"/home/[user]/../../etc/passwd"</code>, which is the system's
password file. This file would then be sent back to the user, giving them access to all the
system's passwords.
</p>
<sample src="examples/TaintedPath.js" />
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Path_traversal">Path Traversal</a>.</li>
<li>npm: <a href="https://www.npmjs.com/package/sanitize-filename">sanitize-filename</a> package.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Uncontrolled data used in path expression
* @description Accessing paths influenced by users can allow an attacker to access
* unexpected resources.
* @kind problem
* @problem.severity error
* @precision high
* @id js/path-injection
* @tags security
* external/cwe/cwe-022
* external/cwe/cwe-023
* external/cwe/cwe-036
* external/cwe/cwe-073
* external/cwe/cwe-099
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.security.dataflow.TaintedPath::TaintedPath
from Configuration cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, "This path depends on $@.", source, "a user-provided value"

View File

@@ -0,0 +1,13 @@
var fs = require('fs'),
http = require('http'),
url = require('url');
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
// BAD: This could read any file on the file system
res.write(fs.readFileSync(path));
// BAD: This could still read any file on the file system
res.write(fs.readFileSync("/home/user/" + path));
});

View File

@@ -0,0 +1,44 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes user input directly to
<code>require('child_process').exec</code>, or some other library
routine that executes a command, allows the user to execute malicious
code.</p>
</overview>
<recommendation>
<p>If possible, use hard-coded string literals to specify the command to run
or library to load. Instead of passing the user input directly to the
process or library function, examine the user input and then choose
among hard-coded string literals.</p>
<p>If the applicable libraries or commands cannot be determined at
compile time, then add code to verify that the user input string is
safe before using it.</p>
</recommendation>
<example>
<p>The following example shows code that takes a shell script that can be changed
maliciously by a user, and passes it straight to <code>child_process.exec</code>
without examining it first.</p>
<sample src="examples/command-injection.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<!-- LocalWords: CWE untrusted unsanitized Runtime
-->
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Uncontrolled command line
* @description Using externally controlled strings in a command line may allow a malicious
* user to change the meaning of the command.
* @kind problem
* @problem.severity error
* @precision high
* @id js/command-line-injection
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import javascript
import semmle.javascript.security.dataflow.CommandInjection::CommandInjection
from Configuration cfg, DataFlow::Node source, DataFlow::Node sink, DataFlow::Node highlight
where cfg.hasFlow(source, sink) and
if cfg.isSink(sink, _) then cfg.isSink(sink, highlight) else highlight = sink
select highlight, "This command depends on $@.", source, "a user-provided value"

View File

@@ -0,0 +1,9 @@
var cp = require("child_process"),
http = require('http'),
url = require('url');
var server = http.createServer(function(req, res) {
let cmd = url.parse(req.url, true).query.path;
cp.exec(cmd); // BAD
});

View File

@@ -0,0 +1,52 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly writing user input (for example, an HTTP request parameter) to an HTTP response
without properly sanitizing the input first, allows for a cross-site scripting vulnerability.
</p>
<p>
This kind of vulnerability is also called <i>reflected</i> cross-site scripting, to distinguish
it from other types of cross-site scripting.
</p>
</overview>
<recommendation>
<p>
To guard against cross-site scripting, consider using contextual output encoding/escaping before
writing user input to the response, or one of the other solutions that are mentioned in the
references.
</p>
</recommendation>
<example>
<p>
The following example code writes part of an HTTP request (which is controlled by the user)
directly to the response. This leaves the website vulnerable to cross-site scripting.
</p>
<sample src="examples/ReflectedXss.js" />
<p>
Sanitizing the user-controlled data prevents the vulnerability:
</p>
<sample src="examples/ReflectedXssGood.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
Scripting</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Reflected cross-site scripting
* @description Writing user input directly to an HTTP response allows for
* a cross-site scripting vulnerability.
* @kind problem
* @problem.severity error
* @precision high
* @id js/reflected-xss
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.ReflectedXss::ReflectedXss
from Configuration xss, DataFlow::Node source, DataFlow::Node sink
where xss.hasFlow(source, sink)
select sink, "Cross-site scripting vulnerability due to $@.",
source, "user-provided value"

View File

@@ -0,0 +1,57 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly writing user input (for example, a URL query parameter) to a webpage
without properly sanitizing the input first, allows for a cross-site scripting vulnerability.
</p>
<p>
This kind of vulnerability is also called <i>DOM-based</i> cross-site scripting, to distinguish
it from other types of cross-site scripting.
</p>
</overview>
<recommendation>
<p>
To guard against cross-site scripting, consider using contextual output encoding/escaping before
writing user input to the page, or one of the other solutions that are mentioned in the
references.
</p>
</recommendation>
<example>
<p>
The following example shows part of the page URL being written directly to the document,
leaving the website vulnerable to cross-site scripting.
</p>
<sample src="examples/Xss.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet">DOM based
XSS Prevention Cheat Sheet</a>.
</li>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://www.owasp.org/index.php/DOM_Based_XSS">DOM Based XSS</a>.
</li>
<li>
OWASP
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
Scripting</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Client side cross-site scripting
* @description Writing user input directly to the DOM allows for
* a cross-site scripting vulnerability.
* @kind problem
* @problem.severity error
* @precision high
* @id js/xss
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss
from Configuration xss, DataFlow::Node source, DataFlow::Node sink
where xss.hasFlow(source, sink)
select sink, "Cross-site scripting vulnerability due to $@.",
source, "user-provided value"

View File

@@ -0,0 +1,10 @@
var app = require('express')();
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id))
// BAD: a request parameter is incorporated without validation into the response
res.send("Unknown user: " + req.params.id);
else
// TODO: do something exciting
;
});

View File

@@ -0,0 +1,12 @@
var escape = require('escape-html');
var app = require('express')();
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id))
// GOOD: request parameter is sanitized before incorporating it into the response
res.send("Unknown user: " + escape(req.params.id));
else
// TODO: do something exciting
;
});

View File

@@ -0,0 +1,6 @@
function setLanguageOptions() {
var href = document.location.href,
deflt = href.substring(href.indexOf("default=")+8);
document.write("<OPTION value=1>"+deflt+"</OPTION>");
document.write("<OPTION value=2>English</OPTION>");
}

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
If a database query (such as a SQL or NoSQL query) is built from
user-provided data without sufficient sanitization, a malicious user
may be able to run malicious database queries.
</p>
</overview>
<recommendation>
<p>
Most database connector libraries offer a way of safely
embedding untrusted data into a query by means of query parameters
or prepared statements.
</p>
</recommendation>
<example>
<p>
In the following example, assume the function <code>handler</code> is
an HTTP request handler in a web application, whose parameter
<code>req</code> contains the request object.
</p>
<p>
The handler constructs two copies of the same SQL query involving
user input taken from the request object, once unsafely using
string concatenation, and once safely using query parameters.
</p>
<p>
In the first case, the query string <code>query1</code> is built by
directly concatenating a user-supplied request parameter with some
string literals. The parameter may include quote characters, so this
code is vulnerable to a SQL injection attack.
</p>
<p>
In the second case, the parameter is embedded into the query string
<code>query2</code> using query parameters. In this example, we use
the API offered by the <code>pg</code> Postgres database connector
library, but other libraries offer similar features. This version is
immune to injection attacks.
</p>
<sample src="examples/SqlInjection.js" />
</example>
<references>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,28 @@
/**
* @name Database query built from user-controlled sources
* @description Building a database query from user-controlled sources is vulnerable to insertion of
* malicious code by the user.
* @kind problem
* @problem.severity error
* @precision high
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
*/
import javascript
import semmle.javascript.security.dataflow.SqlInjection
import semmle.javascript.security.dataflow.NosqlInjection
predicate sqlInjection(DataFlow::Node source, DataFlow::Node sink) {
any(SqlInjection::Configuration cfg).hasFlow(source, sink)
}
predicate nosqlInjection(DataFlow::Node source, DataFlow::Node sink) {
any(NosqlInjection::Configuration cfg).hasFlow(source, sink)
}
from DataFlow::Node source, DataFlow::Node sink
where sqlInjection(source, sink) or
nosqlInjection(source, sink)
select sink, "This query depends on $@.", source, "a user-provided value"

View File

@@ -0,0 +1,18 @@
const pg = require('pg');
const pool = new pg.Pool(config);
function handler(req, res) {
// BAD: the category might have SQL special characters in it
var query1 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='"
+ req.params.category + "' ORDER BY PRICE";
pool.query(query1, [], function(err, results) {
// process results
});
// GOOD: use parameters
var query2 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1"
+ " ORDER BY PRICE";
pool.query(query2, [req.params.category], function(err, results) {
// process results
});
}

View File

@@ -0,0 +1,44 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly evaluating user input (for example, an HTTP request parameter) as code without properly
sanitizing the input first allows an attacker arbitrary code execution. This can occur when user
input is treated as JavaScript, or passed to a framework which interprets it as an expression to be
evaluated. Examples include AngularJS expressions or JQuery selectors.
</p>
</overview>
<recommendation>
<p>
Avoid including user input in any expression which may be dynamically evaluated. If user input must
be included, use context-specific escaping before
including it. It is important that the correct escaping is used for the type of evaluation that will
occur.
</p>
</recommendation>
<example>
<p>
The following example shows part of the page URL being evaluated as JavaScript code. This allows an
attacker to provide JavaScript within the URL. If an attacker can persuade a user to click on a link
to such a URL, the attacker can evaluate arbitrary JavaScript in the browser of the user to,
for example, steal cookies containing session information.
</p>
<sample src="examples/CodeInjection.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/Code_injection">Code Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Code injection
* @description Interpreting unsanitized user input as code allows a malicious user arbitrary
* code execution.
* @kind problem
* @problem.severity error
* @precision high
* @id js/code-injection
* @tags security
* external/cwe/cwe-094
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.CodeInjection::CodeInjection
from Configuration codeInjection, DataFlow::Node source, DataFlow::Node sink
where codeInjection.hasFlow(source, sink)
select sink, "$@ flows to here and is interpreted as code.", source, "User-provided value"

View File

@@ -0,0 +1 @@
eval(document.location.href.substring(document.location.href.indexOf("default=")+8))

View File

@@ -0,0 +1,76 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sanitizing untrusted input is an important technique for preventing injection attacks such as
SQL injection or cross-site scripting. Usually, this is done by escaping meta-characters such
as quotes in a domain-specific way so that they are treated as normal characters.
</p>
<p>
However, directly using the string <code>replace</code> method to perform escaping is notoriously
error-prone. Common mistakes include only replacing the first occurrence
of a meta-character, or backslash-escaping various meta-characters but not the backslash itself.
</p>
<p>
In the former case, later meta-characters are left undisturbed and can be used to subvert the
sanitization. In the latter case, preceding a meta-character with a backslash leads to the
backslash being escaped, but the meta-character appearing un-escaped, which again makes the
sanitization ineffective.
</p>
</overview>
<recommendation>
<p>
Use a (well-tested) sanitization library if at all possible. These libraries are much more
likely to handle corner cases correctly than a custom implementation.
</p>
<p>
Otherwise, make sure to use a regular expression with the <code>g</code> flag to ensure that
all occurrences are replaced, and remember to escape backslashes if applicable.
</p>
</recommendation>
<example>
<p>
For example, assume that we want to embed a user-controlled string <code>accountNumber</code>
into a SQL query as part of a string literal. To avoid SQL injection, we need to ensure that
the string does not contain un-escaped single-quote characters. The following function attempts
to ensure this by doubling single quotes, and thereby escaping them:
</p>
<sample src="examples/IncompleteSanitization.js" />
<p>
As written, this sanitizer is ineffective: if the first argument to <code>replace</code> is
a string literal (as in this case), only the <i>first</i> occurrence of that string is
replaced.
</p>
<p>
As mentioned above, the function <code>escapeQuotes</code> should be replaced with a purpose-built
sanitization library, such as the npm module <code>sqlstring</code>. Many other sanitization
libraries are available from npm and other sources.
</p>
<p>
If this is not an option, <code>escapeQuotes</code> should be rewritten to use a regular expression
with the <code>g</code> ("global") flag instead:
</p>
<sample src="examples/IncompleteSanitizationGood.js" />
<p>
Note that it is very important to include the global flag: <code>s.replace(/'/, "''")</code>
<i>without</i> the global flag is equivalent to the first example above and only replaces the
first quote.
</p>
</example>
<references>
<li>OWASP Top 10: <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">A1 Injection</a>.</li>
<li>npm: <a href="https://www.npmjs.com/package/sqlstring">sqlstring</a> package.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,120 @@
/**
* @name Incomplete sanitization
* @description A sanitizer that does not replace or escape all occurrences of a
* problematic substring may be ineffective.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/incomplete-sanitization
* @tags correctness
* security
* external/cwe/cwe-116
* external/cwe/cwe-20
*/
import javascript
/**
* Gets a character that is commonly used as a meta-character.
*
* We heuristically assume that string replacements involving one of these
* characters are meant to be sanitizers.
*/
string metachar() {
result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_)
}
/** Gets a string matched by `e` in a `replace` call. */
string getAMatchedString(Expr e) {
result = getAMatchedConstant(e.(RegExpLiteral).getRoot()).getValue()
or
result = e.(StringLiteral).getValue()
}
/** Gets a constant matched by `t`. */
RegExpConstant getAMatchedConstant(RegExpTerm t) {
result = t
or
result = getAMatchedConstant(t.(RegExpAlt).getAlternative())
or
result = getAMatchedConstant(t.(RegExpGroup).getAChild())
or
exists (RegExpCharacterClass recc | recc = t and not recc.isInverted() |
result = getAMatchedConstant(recc.getAChild())
)
}
/** Holds if `t` is simple, that is, a union of constants. */
predicate isSimple(RegExpTerm t) {
t instanceof RegExpConstant
or
isSimple(t.(RegExpGroup).getAChild())
or
(
t instanceof RegExpAlt or
t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted()
) and
forall (RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
}
/**
* Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global
* regular expression and `new` prefixes the matched string with a backslash.
*/
predicate isBackslashEscape(MethodCallExpr mce, RegExpLiteral re) {
mce.getMethodName() = "replace" and
re = mce.getArgument(0) and
re.isGlobal() and
exists (string new | new = mce.getArgument(1).getStringValue() |
// `new` is `\$&`, `\$1` or similar
new.regexpMatch("\\\\\\$(&|\\d)")
or
// `new` is `\c`, where `c` is a constant matched by `re`
new.regexpMatch("\\\\\\Q" + getAMatchedString(re) + "\\E")
)
}
/**
* Holds if data flowing into `nd` has no unescaped backslashes.
*/
predicate allBackslashesEscaped(DataFlow::Node nd) {
// `JSON.stringify` escapes backslashes
nd = DataFlow::globalVarRef("JSON").getAMemberCall("stringify")
or
// check whether `nd` itself escapes backslashes
exists (RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) |
// if it's a complex regexp, we conservatively assume that it probably escapes backslashes
not isSimple(rel.getRoot()) or
getAMatchedString(rel) = "\\"
)
or
// flow through string methods
exists (DataFlow::MethodCallNode mc, string m |
m = "replace" or
m = "slice" or m = "substr" or m = "substring" or
m = "toLowerCase" or m = "toUpperCase" or m = "trim" |
mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver())
)
or
// general data flow
allBackslashesEscaped(nd.getAPredecessor())
}
from MethodCallExpr repl, Expr old, string msg
where repl.getMethodName() = "replace" and
old = repl.getArgument(0) and
(
not old.(RegExpLiteral).isGlobal() and
msg = "This replaces only the first occurrence of " + old + "." and
// only flag if this is likely to be a sanitizer
getAMatchedString(old) = metachar() and
// don't flag replace operations in a loop
not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+()
or
exists (RegExpLiteral rel |
isBackslashEscape(repl, rel) and
not allBackslashesEscaped(DataFlow::valueNode(repl)) and
msg = "This does not backslash-escape the backslash character."
)
)
select old, msg

View File

@@ -0,0 +1,3 @@
function escapeQuotes(s) {
return s.replace("'", "''");
}

View File

@@ -0,0 +1,3 @@
function escapeQuotes(s) {
return s.replace(/'/g, "''");
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Functions like the Node.js standard library function <code>util.format</code> accept a
format string that is used to format the remaining arguments by providing inline format
specifiers. If the format string contains unsanitized input from an untrusted source,
then that string may contain unexpected format specifiers that cause garbled output.
</p>
</overview>
<recommendation>
<p>
Either sanitize the input before including it in the format string, or use a
<code>%s</code> specifier in the format string, and pass the untrusted data as corresponding
argument.
</p>
</recommendation>
<example>
<p>
The following program snippet logs information about an unauthorized access attempt. The
log message includes the user name, and the user's IP address is passed as an additional
argument to <code>console.log</code> to be appended to the message:
</p>
<sample src="examples/TaintedFormatStringBad.js"/>
<p>
However, if a malicious user provides <code>%d</code> as their user name, <code>console.log</code>
will instead attempt to format the <code>ip</code> argument as a number. Since IP addresses are
not valid numbers, the result of this conversion is <code>NaN</code>. The resulting log message
will read "Unauthorized access attempt by NaN", missing all the information that it was trying to
log in the first place.
</p>
<p>
Instead, the user name should be included using the <code>%s</code> specifier:
</p>
<sample src="examples/TaintedFormatStringGood.js"/>
</example>
<references>
<li>Node.js Documentation: <a href="https://nodejs.org/api/util.html#util_util_format_format_args">util.format</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,17 @@
/**
* @name Use of externally-controlled format string
* @description Using external input in format strings can lead to garbled output.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/tainted-format-string
* @tags security
* external/cwe/cwe-134
*/
import javascript
import semmle.javascript.security.dataflow.TaintedFormatString::TaintedFormatString
from Configuration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "$@ flows here and is used in a format string.", source, "User-provided value"

View File

@@ -0,0 +1 @@
console.log("Unauthorized access attempt by " + user, ip);

View File

@@ -0,0 +1 @@
console.log("Unauthorized access attempt by %s", user, ip);

View File

@@ -0,0 +1,57 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Software developers often add stack traces to error messages, as a
debugging aid. Whenever that error message occurs for an end user, the
developer can use the stack trace to help identify how to fix the
problem. In particular, stack traces can tell the developer more about
the sequence of events that led to a failure, as opposed to merely the
final state of the software when the error occurred.
</p>
<p>
Unfortunately, the same information can be useful to an attacker.
The sequence of function names in a stack trace can reveal the structure
of the application as well as any internal components it relies on.
Furthermore, the error message at the top of a stack trace can include
information such as server-side file names and SQL code that the
application relies on, allowing an attacker to fine-tune a subsequent
injection attack.
</p>
</overview>
<recommendation>
<p>
Send the user a more generic error message that reveals less information.
Either suppress the stack trace entirely, or log it only on the server.
</p>
</recommendation>
<example>
<p>
In the following example, an exception is caught and its stack trace is
sent back to the remote user as part of the HTTP response. As such,
the user is able to see a detailed stack trace, which may contain
sensitive information.
</p>
<sample src="examples/StackTraceExposureBad.js" />
<p>
Instead, the stack trace should be logged only on the server. That way,
the developers can still access and use the error log, but remote users
will not see the information:
</p>
<sample src="examples/StackTraceExposureGood.js" />
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Information_Leak_(information_disclosure)">Information Leak</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Information exposure through a stack trace
* @description Propagating stack trace information to an external user can
* unintentionally reveal implementation details that are useful
* to an attacker for developing a subsequent exploit.
* @kind problem
* @problem.severity warning
* @precision very-high
* @id js/stack-trace-exposure
* @tags security
* external/cwe/cwe-209
*/
import javascript
import semmle.javascript.security.dataflow.StackTraceExposure::StackTraceExposure
from Configuration cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, "Stack trace information from $@ may be exposed to an external user here.",
source, "here"

View File

@@ -0,0 +1,18 @@
var http = require('http');
http.createServer(function onRequest(req, res) {
var body;
try {
body = handleRequest(req);
}
catch (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain");
res.end(err.stack); // NOT OK
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Length", body.length);
res.end(body);
}).listen(3000);

View File

@@ -0,0 +1,19 @@
var http = require('http');
http.createServer(function onRequest(req, res) {
var body;
try {
body = handleRequest(req);
}
catch (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain");
log("Exception occurred", err.stack);
res.end("An exception occurred"); // OK
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Length", body.length);
res.end(body);
}).listen(3000);

View File

@@ -0,0 +1,47 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sensitive information that is stored unencrypted is accessible to an attacker
who gains access to the storage. This is particularly important for cookies,
which are stored on the machine of the end-user.
</p>
</overview>
<recommendation>
<p>
Ensure that sensitive information is always encrypted before being stored.
If possible, avoid placing sensitive information in cookies altogether.
Instead, prefer storing, in the cookie, a key that can be used to lookup the
sensitive information.
</p>
<p>
In general, decrypt sensitive information only at the point where it is
necessary for it to be used in cleartext.
</p>
</recommendation>
<example>
<p>
The following example code stores user credentials (in this case, their account
name) in a cookie in plain text:
</p>
<sample src="examples/CleartextStorage.js"/>
<p>
Instead, the credentials should be encrypted, for instance by using the Node.js
<code>crypto</code> module:
</p>
<sample src="examples/CleartextStorageGood.js"/>
</example>
<references>
<li>M. Dowd, J. McDonald and J. Schuhm, <i>The Art of Software Security Assessment</i>, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.</li>
<li>M. Howard and D. LeBlanc, <i>Writing Secure Code</i>, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Clear text storage of sensitive information
* @description Sensitive information stored without encryption or hashing can expose it to an
* attacker.
* @kind problem
* @problem.severity error
* @precision high
* @id js/clear-text-storage-of-sensitive-data
* @tags security
* external/cwe/cwe-312
* external/cwe/cwe-315
* external/cwe/cwe-359
*/
import javascript
import semmle.javascript.security.dataflow.CleartextStorage::CleartextStorage
from Configuration cleartextStorage, Source source, DataFlow::Node sink
where cleartextStorage.hasFlow(source, sink)
select sink, "Sensitive data returned by $@ is stored here.", source, source.describe()

View File

@@ -0,0 +1,8 @@
var express = require('express');
var app = express();
app.get('/', function (req, res) {
let accountName = req.param("AccountName");
// BAD: Setting a cookie value with cleartext sensitive data.
res.cookie("AccountName", accountName);
});

View File

@@ -0,0 +1,15 @@
var express = require('express');
var crypto = require('crypto'),
password = getPassword();
function encrypt(text){
var cipher = crypto.createCipher('aes-256-ctr', password);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}
var app = express();
app.get('/', function (req, res) {
let accountName = req.param("AccountName");
// GOOD: Encoding the value before setting it.
res.cookie("AccountName", encrypt(accountName));
});

View File

@@ -0,0 +1,21 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Storing a plaintext password in a configuration file allows anyone who can read the file to
access the password-protected resources. Therefore it is a common attack vector.
</p>
</overview>
<recommendation>
<p>
Passwords stored in configuration files should always be encrypted.
</p>
</recommendation>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,52 @@
/**
* @name Password in configuration file
* @description Storing unencrypted passwords in configuration files is unsafe.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/password-in-configuration-file
* @tags security
* external/cwe/cwe-256
* external/cwe/cwe-260
* external/cwe/cwe-313
*/
import javascript
/**
* Holds if some JSON or YAML file contains a property with name `key`
* and value `val`, where `valElement` is the entity corresponding to the
* value.
*
* Dependencies in `package.json` files are excluded by this predicate.
*/
predicate config(string key, string val, Locatable valElement) {
exists (JSONObject obj |
not exists(PackageJSON p | obj = p.getADependenciesObject(_)) |
obj.getPropValue(key) = valElement and
val = valElement.(JSONString).getValue()
)
or
exists (YAMLMapping m, YAMLString keyElement |
m.maps(keyElement, valElement) and
key = keyElement.getValue() and
val = valElement.(YAMLString).getValue()
)
}
/**
* Holds if file `f` should be excluded because it looks like it may be
* a dictionary file, or a test or example.
*/
predicate exclude(File f) {
f.getRelativePath().regexpMatch(".*(^|/)(lang(uage)?s?|locales?|tests?|examples?)/.*")
}
from string key, string val, Locatable valElement
where config(key, val, valElement) and val != "" and
(key.toLowerCase() = "password"
or
key.toLowerCase() != "readme" and
val.regexpMatch("(?is).*password\\s*=(?!\\s*;).*")) and
not exclude(valElement.getFile())
select valElement, "Avoid plaintext passwords in configuration files."

View File

@@ -0,0 +1,53 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using broken or weak cryptographic algorithms can leave data
vulnerable to being decrypted or forged by an attacker.
</p>
<p>
Many cryptographic algorithms provided by cryptography
libraries are known to be weak, or flawed. Using such an
algorithm means that encrypted or hashed data is less
secure than it appears to be.
</p>
</overview>
<recommendation>
<p>
Ensure that you use a strong, modern cryptographic
algorithm. Use at least AES-128 or RSA-2048 for
encryption, and SHA-2 or SHA-3 for secure hashing.
</p>
</recommendation>
<example>
<p>
The following code shows an example of using the builtin
cryptographic library of NodeJS to encrypt some secret
data. When creating a <code>Cipher</code> instance to encrypt
the secret data with, you must specify the encryption
algorithm to use. The first example uses DES, which is an
older algorithm that is now considered weak. The second
example uses AES, which is a strong modern algorithm.
</p>
<sample src="examples/BrokenCryptoAlgorithm.js" />
</example>
<references>
<li>NIST, FIPS 140 Annex a: <a href="http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf"> Approved Security Functions</a>.</li>
<li>NIST, SP 800-131A: <a href="http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf"> Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.</li>
<li>OWASP: <a
href="https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet#Rule_-_Use_strong_approved_cryptographic_algorithms">Rule
- Use strong approved cryptographic algorithms</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,19 @@
/**
* @name Use of a broken or weak cryptographic algorithm
* @description Using broken or weak cryptographic algorithms can compromise security.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/weak-cryptographic-algorithm
* @tags security
* external/cwe/cwe-327
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.security.dataflow.BrokenCryptoAlgorithm::BrokenCryptoAlgorithm
import semmle.javascript.security.SensitiveActions
from Configuration brokenCrypto, Source source, DataFlow::Node sink
where brokenCrypto.hasFlow(source, sink) and
not source.asExpr() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
select sink, "Sensitive data from $@ is used in a broken or weak cryptographic algorithm.", source , source.describe()

View File

@@ -0,0 +1,7 @@
const crypto = require('crypto');
const desCipher = crypto.createCipher('des', key);
let desEncrypted = cipher.write(secretText, 'utf8', 'hex'); // BAD: weak encryption
const aesCipher = crypto.createCipher('aes-128', key);
let aesEncrypted = cipher.update(secretText, 'utf8', 'hex'); // GOOD: strong encryption

View File

@@ -0,0 +1,75 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using a cryptographically weak pseudo-random number generator to generate a security-sensitive value,
such as a password, makes it easier for an attacker to predict the value.
</p>
<p>
Pseudo-random number generators generate a sequence of numbers that only approximates the properties
of random numbers. The sequence is not truly random because it is completely determined by a
relatively small set of initial values, the seed. If the random number generator is
cryptographically weak, then this sequence may be easily predictable through outside observations.
</p>
</overview>
<recommendation>
<p>
Use a cryptographically secure pseudo-random number generator if the output is to be used in a
security-sensitive context. As a rule of thumb, a value should be considered "security-sensitive"
if predicting it would allow the attacker to perform an action that they would otherwise be unable
to perform. For example, if an attacker could predict the random password generated for a new user,
they would be able to log in as that new user.
</p>
<p>
For JavaScript on the NodeJS platform,
<code>crypto.getRandomBytes</code> provides a cryptographically secure
pseudo-random byte generator. Note that the conversion from bytes to
numbers can introduce bias that breaks the security.
</p>
<p>
For JavaScript in the browser,
<code>RandomSource.getRandomValues</code> provides a cryptographically
secure pseudo-random number generator.
</p>
</recommendation>
<example>
<p>
The following examples show different ways of generating a password.
</p>
<p>
In the first case, we generate a fresh password by appending a random integer to the end of a static
string. The random number generator used (<code>Math.random</code>) is not cryptographically secure,
so it may be possible for an attacker to predict the generated password.
</p>
<sample src="examples/InsecureRandomness.js" />
<p>
In the second example, a cryptographically secure random number generator is used for the same
purpose. In this case, it is much harder to predict the generated integers.
</p>
<sample src="examples/InsecureRandomness_fixed.js" />
</example>
<references>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Pseudorandom_number_generator">Pseudo-random number generator</a>.</li>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues">RandomSource.getRandomValues</a>.</li>
<li>NodeJS: <a href="https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback">crypto.randomBytes</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name Insecure randomness
* @description Using a cryptographically weak pseudo-random number generator to generate a
* security-sensitive value may allow an attacker to predict what value will
* be generated.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/insecure-randomness
* @tags security
* external/cwe/cwe-338
*/
import javascript
import semmle.javascript.security.dataflow.InsecureRandomness::InsecureRandomness
from Configuration cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, "Cryptographically insecure $@ in a security context.", source, "random value"

View File

@@ -0,0 +1,6 @@
function insecurePassword() {
// BAD: the random suffix is not cryptographically secure
var suffix = Math.random();
var password = "myPassword" + suffix;
return password;
}

View File

@@ -0,0 +1,6 @@
function securePassword() {
// GOOD: the random suffix is cryptographically secure
var suffix = window.crypto.getRandomValues(new Uint32Array(1))[0];
var password = "myPassword" + suffix;
return password;
}

View File

@@ -0,0 +1,85 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
A server can send the
<code>"Access-Control-Allow-Credentials"</code> CORS header to control
when a browser may send user credentials in Cross-Origin HTTP
requests.
</p>
<p>
When the <code>Access-Control-Allow-Credentials</code> header
is <code>"true"</code>, the <code>Access-Control-Allow-Origin</code>
header must have a value different from <code>"*"</code> in order to
make browsers accept the header. Therefore, to allow multiple origins
for Cross-Origin requests with credentials, the server must
dynamically compute the value of the
<code>"Access-Control-Allow-Origin"</code> header. Computing this
header value from information in the request to the server can
therefore potentially allow an attacker to control the origins that
the browser sends credentials to.
</p>
</overview>
<recommendation>
<p>
When the <code>Access-Control-Allow-Credentials</code> header
value is <code>"true"</code>, a dynamic computation of the
<code>Access-Control-Allow-Origin</code> header must involve
sanitization if it relies on user-controlled input.
</p>
<p>
Since the <code>"null"</code> origin is easy to obtain for an
attacker, it is never safe to use <code>"null"</code> as the value of
the <code>Access-Control-Allow-Origin</code> header when the
<code>Access-Control-Allow-Credentials</code> header value is
<code>"true"</code>.
</p>
</recommendation>
<example>
<p>
In the example below, the server allows the browser to send
user credentials in a Cross-Origin request. The request header
<code>origins</code> controls the allowed origins for such a
Cross-Origin request.
</p>
<sample src="examples/CorsMisconfigurationForCredentials.js"/>
<p>
This is not secure, since an attacker can choose the value of
the <code>origin</code> request header to make the browser send
credentials to their own server. The use of a whitelist containing
allowed origins for the Cross-Origin request fixes the issue:
</p>
<sample src="examples/CorsMisconfigurationForCredentials_fixed.js"/>
</example>
<references>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin">CORS, Access-Control-Allow-Origin</a>.</li>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials">CORS, Access-Control-Allow-Credentials</a>.</li>
<li>PortSwigger: <a href="http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html">Exploiting CORS Misconfigurations for Bitcoins and Bounties</a></li>
<li>W3C: <a href="https://w3c.github.io/webappsec-cors-for-developers/#resources">CORS for developers, Advice for Resource Owners</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name CORS misconfiguration for credentials transfer
* @description Misconfiguration of CORS HTTP headers allows for leaks of secret credentials.
* @kind problem
* @problem.severity error
* @precision high
* @id js/cors-misconfiguration-for-credentials
* @tags security
* external/cwe/cwe-346
* external/cwe/cwe-639
*/
import javascript
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentials::CorsMisconfigurationForCredentials
from Configuration cfg, DataFlow::Node source, Sink sink
where cfg.hasFlow(source, sink)
select sink, "$@ leak vulnerability due to $@.",
sink.getCredentialsHeader(), "Credential",
source, "a misconfigured CORS header value"

View File

@@ -0,0 +1,13 @@
var https = require('https'),
url = require('url');
var server = https.createServer(function(){});
server.on('request', function(req, res) {
let origin = url.parse(req.url, true).query.origin;
// BAD: attacker can choose the value of origin
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", true);
// ...
});

View File

@@ -0,0 +1,21 @@
var https = require('https'),
url = require('url');
var server = https.createServer(function(){});
server.on('request', function(req, res) {
let origin = url.parse(req.url, true).query.origin,
whitelist = {
"https://example.com": true,
"https://subdomain.example.com": true,
"https://example.com:1337": true
};
if (origin in whitelist) {
// GOOD: the origin is in the whitelist
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", true);
}
// ...
});

View File

@@ -0,0 +1,62 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Websites that rely on cookie-based authentication may be vulnerable to cross-site
request forgery (CSRF). Specifically, a state-changing request should include a
secret token so the request can't be forged by an attacker.
Otherwise, unwanted requests can be submitted on behalf of a user who visits
a malicious website.
</p>
<p>
This is typically mitigated by embedding a session-specific secret token in each request.
This token is then checked as an additional authentication measure.
A malicious website should have no way of guessing the correct token to embed in the request.
</p>
</overview>
<recommendation>
<p>
Use a middleware package such as <code>csurf</code> to protect against CSRF attacks.
</p>
</recommendation>
<example>
<p>
In the example below, the server authenticates users before performing the <code>changeEmail</code> POST action:
</p>
<sample src="examples/MissingCsrfMiddlewareBad.js"/>
<p>
This is not secure. An attacker can submit a POST <code>changeEmail</code> request on behalf
of a user who visited a malicious website. Since authentication happens without any action from the user,
the <code>changeEmail</code> action would be executed, despite not being initiated by the user.
</p>
<p>
This vulnerability can be mitigated by installing a CSRF protecting middleware handler:
</p>
<sample src="examples/MissingCsrfMiddlewareGood.js"/>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)">Cross-Site Request Forgery (CSRF)</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,71 @@
/**
* @name Missing CSRF middleware
* @description Using cookies without CSRF protection may allow malicious websites to
* submit requests on behalf of the user.
* @kind problem
* @problem.severity error
* @precision high
* @id js/missing-token-validation
* @tags security
* external/cwe/cwe-352
*/
import javascript
/**
* Checks if `expr` is preceded by the cookie middleware `cookie`.
*
* A router handler following after cookie parsing is assumed to depend on
* cookies, and thus require CSRF protection.
*/
predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHandlerExpr cookie) {
any(HTTP::CookieMiddlewareInstance i).flowsToExpr(cookie) and
expr.getAMatchingAncestor() = cookie
}
/**
* Gets an expression that creates a route handler which protects against CSRF attacks.
*
* Any route handler registered downstream from this type of route handler will
* be considered protected.
*
* For example:
* ```
* let csurf = require('csurf');
* let csrfProtector = csurf();
*
* app.post('/changePassword', csrfProtector, function (req, res) {
* // protected from CSRF
* })
* ```
*
* Currently the predicate only detects `csurf`-based protectors.
*/
DataFlow::CallNode csrfMiddlewareCreation() {
exists (DataFlow::ModuleImportNode mod | result = mod.getACall() |
mod.getPath() = "csurf"
)
}
/**
* Holds if the given route handler is protected by CSRF middleware.
*/
predicate hasCsrfMiddleware(Express::RouteHandlerExpr handler) {
csrfMiddlewareCreation().flowsToExpr(handler.getAMatchingAncestor())
}
from Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler,
Express::RouteHandlerExpr cookie
where router = setup.getRouter()
and handler = setup.getARouteHandlerExpr()
and hasCookieMiddleware(handler, cookie)
and not hasCsrfMiddleware(handler)
// Only warn for the last handler in a chain.
and handler.isLastHandler()
// Only warn for dangerous for handlers, such as for POST and PUT.
and not setup.getRequestMethod().isSafe()
select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.", handler, "here"

View File

@@ -0,0 +1,11 @@
var express = require('express')
var cookieParser = require('cookie-parser')
var passport = require('passport')
var app = express()
app.use(cookieParser())
app.use(passport.authorize({ session: true }))
app.post('/changeEmail', ..., function (req, res) {
})

View File

@@ -0,0 +1,13 @@
var express = require('express')
var cookieParser = require('cookie-parser')
var passport = require('passport')
var csrf = require('csurf')
var app = express()
app.use(cookieParser())
app.use(passport.authorize({ session: true }))
app.use(csrf({ cookie:true }))
app.post('/changeEmail', ..., function (req, res) {
})

View File

@@ -0,0 +1,96 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Dynamically computing object property names from untrusted input
may have multiple undesired consequences. For example,
if the property access is used as part of a write, an
attacker may overwrite vital properties of objects, such as
<code>__proto__</code>. This attack is known as <i>prototype
pollution attack</i> and may serve as a vehicle for denial-of-service
attacks. A similar attack vector, is to replace the
<code>toString</code> property of an object with a primitive.
Whenever <code>toString</code> is then called on that object, either
explicitly or implicitly as part of a type coercion, an exception
will be raised.
</p>
<p>
Moreover, if the dynamically computed property is
used as part of a method call, the attacker may trigger
the execution of unwanted functions such as the
<code>Function</code> constructor or the
<code>eval</code> method, which can be used
for code injection.
</p>
<p>
Additionally, if the name of an HTTP header is user-controlled,
an attacker may exploit this to overwrite security-critical headers
such as <code>Access-Control-Allow-Origin</code> or
<code>Content-Security-Policy</code>.
</p>
</overview>
<recommendation>
<p>
The most common case in which prototype pollution vulnerabilities arise
is when JavaScript objects are used for implementing map data
structures. This case should be avoided whenever possible by using the
ECMAScript 2015 <code>Map</code> instead. When this is not possible, an
alternative fix is to prepend untrusted input with a marker character
such as <code>$</code>, before using it in properties accesses. In this way,
the attacker does not have access to built-in properties which do not
start with the chosen character.
</p>
<p>
When using user input as part of header or method names, a sanitization
step should be performed on the input to ensure that the name does not
clash with existing property and header names such as
<code>__proto__</code> or <code>Content-Security-Policy</code>.
</p>
</recommendation>
<example>
<p>
In the example below, the dynamically computed property
<code>prop</code> is accessed on <code>myObj</code> using a
user-controlled value.
</p>
<sample src="examples/RemotePropertyInjection.js"/>
<p>
This is not secure since an attacker may exploit this code to
overwrite the property <code>__proto__</code> with an empty function.
If this happens, the concatenation in the <code>console.log</code>
argument will fail with a confusing message such as
"Function.prototype.toString is not generic". If the application does
not properly handle this error, this scenario may result in a serious
denial-of-service attack. The fix is to prepend the user-controlled
string with a marker character such as <code>$</code> which will
prevent arbitrary property names from being overwritten.
</p>
<sample src="examples/RemotePropertyInjection_fixed.js"/>
</example>
<references>
<li>Prototype pollution attacks:
<a href="https://github.com/electron/electron/pull/9287">electron</a>,
<a href="https://hackerone.com/reports/310443">lodash</a>,
<a href="https://nodesecurity.io/advisories/566">hoek</a>.
</li>
<li> Penetration testing report:
<a href="http://seclists.org/pen-test/2009/Mar/67">
header name injection attack</a>
</li>
<li> npm blog post:
<a href="https://blog.liftsecurity.io/2015/01/14/the-dangers-of-square-bracket-notation#lift-security">
dangers of square bracket notation</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Remote property injection
* @description Allowing writes to arbitrary properties or calls to arbitrary
* methods of an object may lead to denial-of-service attacks.
*
* @kind problem
* @problem.severity warning
* @precision high
* @id js/remote-property-injection
* @tags security
* external/cwe/cwe-250
* external/cwe/cwe-400
*/
import javascript
import semmle.javascript.security.dataflow.RemotePropertyInjection::RemotePropertyInjection
from Configuration c, DataFlow::Node source, Sink sink
where c.hasFlow(source, sink)
select sink, "A $@ is used as" + sink.getMessage(),
source, "user-provided value"

View File

@@ -0,0 +1,10 @@
var express = require('express');
var app = express();
var myObj = {}
app.get('/user/:id', function(req, res) {
var prop = req.query.userControlled; // BAD
myObj[prop] = function() {};
console.log("Request object " + myObj);
});

View File

@@ -0,0 +1,10 @@
var express = require('express');
var app = express();
var myObj = {}
app.get('/user/:id', function(req, res) {
var prop = "$" + req.query.userControlled; // GOOD
myObj[prop] = function() {};
console.log("Request object " + myObj);
});

View File

@@ -0,0 +1,78 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Websites that do not specify the <code>X-Frame-Options</code>
HTTP header may be vulnerable to UI redress attacks
("clickjacking"). In these attacks, the vulnerable site is
loaded in a frame on an attacker-controlled site which uses
opaque or transparent layers to trick the user into
unintentionally clicking a button or link on the vulnerable
site.
</p>
</overview>
<recommendation>
<p>
Set the <code>X-Frame-Options</code> HTTP header to
<code>DENY</code>, to instruct web browsers to block attempts to
load the site in a frame. Alternatively, if framing is needed in
certain circumstances, specify <code>SAMEORIGIN</code> or
<code>ALLOW FROM: ...</code> to limit the ability to frame the
site to pages from the same origin, or from an allowed whitelist
of trusted domains.
</p>
<p>
For <a href="https://www.npmjs.com/package/express">express</a>
applications, the header may be specified by setting
<code>res.setHeader('X-Frame-Options', 'DENY')</code> on each
request. Several npm modules provide this functionality as
well: <a
href="https://www.npmjs.com/package/frameguard">frameguard</a>,
<a href="https://www.npmjs.com/package/helmet">helmet</a>,
<a href="https://www.npmjs.com/package/x-frame-options">x-frame-options</a>
</p>
<p>
Alternatively, the header can be set by a proxy. As an example,
a <a href="http://www.haproxy.org/">HAProxy</a> configuration
should contain: <code>rspadd X-Frame-Options:\ DENY</code> to
set the header automatically.
</p>
</recommendation>
<example>
<p>
The following example shows an <a
href="https://www.npmjs.com/package/express">express</a>
application that does <em>not</em> set the
<code>X-Frame-Options</code> header on its responses:
</p>
<sample src="examples/missing-X-Frame-Options.js" />
<p>
The application can be made safer by setting the
<code>X-Frame-Options</code> header before responding:
</p>
<sample src="examples/X-Frame-Options.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet">Clickjacking Defense Cheat Sheet</a>.
</li>
<li>
Mozilla:
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">X-Frame-Options</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,19 @@
/**
* @name Missing X-Frame-Options HTTP header
* @description If the 'X-Frame-Options' setting is not provided, a malicious user may be able to
* overlay their own UI on top of the site by using an iframe.
* @kind problem
* @problem.severity error
* @precision low
* @id js/missing-x-frame-options
* @tags security
* external/cwe/cwe-451
* external/cwe/cwe-829
*/
import javascript
import semmle.javascript.frameworks.HTTP
from HTTP::ServerDefinition server
where not exists(server.getARouteHandler().getAResponseHeader("x-frame-options"))
select server, "This server never sets the 'X-Frame-Options' HTTP header."

View File

@@ -0,0 +1,8 @@
var express = require('express'),
app = express();
app.get('/', function (req, res) {
res.set('X-Frame-Options', value)
res.send('X-Frame-Options: ' + res.get('X-Frame-Options'))
})

View File

@@ -0,0 +1,7 @@
var express = require('express'),
app = express();
app.get('/', function (req, res) {
res.send('X-Frame-Options: ' + res.get('X-Frame-Options'))
})

View File

@@ -0,0 +1,52 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Deserializing untrusted data using any deserialization framework that
allows the construction of arbitrary functions is easily exploitable
and, in many cases, allows an attacker to execute arbitrary code.
</p>
</overview>
<recommendation>
<p>
Avoid deserialization of untrusted data if at all possible. If the
architecture permits it, then use formats like JSON or XML that cannot
represent functions. When using YAML or other formats that support the
serialization and deserialization of functions, ensure that the parser
is configured to disable deserialization of arbitrary functions.
</p>
</recommendation>
<example>
<p>
The following example calls the <code>load</code> function of the popular
<code>js-yaml</code> package on data that comes from an HTTP request and
hence is inherently unsafe.
</p>
<sample src="examples/UnsafeDeserializationBad.js"/>
<p>
Using the <code>safeLoad</code> function instead (which does not deserialize
YAML-encoded functions) removes the vulnerability.
</p>
<sample src="examples/UnsafeDeserializationGood.js" />
</example>
<references>
<li>
OWASP vulnerability description:
<a href="https://www.owasp.org/index.php/Deserialization_of_untrusted_data">Deserialization of untrusted data</a>.
</li>
<li>
OWASP guidance on deserializing objects:
<a href="https://www.owasp.org/index.php/Deserialization_Cheat_Sheet">Deserialization Cheat Sheet</a>.
</li>
<li>
Neal Poole:
<a href="https://nealpoole.com/blog/2013/06/code-execution-via-yaml-in-js-yaml-nodejs-module/">Code Execution via YAML in JS-YAML Node.js Module</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name Deserialization of user-controlled data
* @description Deserializing user-controlled data may allow attackers to
* execute arbitrary code.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/unsafe-deserialization
* @tags security
* external/cwe/cwe-502
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeDeserialization::UnsafeDeserialization
from Configuration cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, "Unsafe deserialization of $@.", source, "user input"

View File

@@ -0,0 +1,6 @@
const jsyaml = require("js-yaml");
function requestHandler(req, res) {
let data = jsyaml.load(req.params.data);
// ...
}

View File

@@ -0,0 +1,6 @@
const jsyaml = require("js-yaml");
function requestHandler(req, res) {
let data = jsyaml.safeLoad(req.params.data);
// ...
}

View File

@@ -0,0 +1,38 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Redirecting to a URL that is constructed from parts of the DOM that may be controlled by an
attacker can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected
to a malicious site that looks very similar to the real site they intend to visit, but which is
controlled by the attacker.
</p>
</overview>
<recommendation>
<p>
To guard against untrusted URL redirection, it is advisable to avoid putting user input
directly into a redirect URL. Instead, maintain a list of authorized
redirects on the server; then choose from that list based on the user input provided.
</p>
</recommendation>
<example>
<p>
The following example uses a regular expression to extract a query parameter from the document
URL, and then uses it to construct a new URL to redirect to without any further validation. This
may allow an attacker to craft a link that redirects from a trusted website to some arbitrary
website of their choosing, which facilitates phishing attacks:
</p>
<sample src="examples/ClientSideUrlRedirect.js"/>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet">
XSS Unvalidated Redirects and Forwards Cheat Sheet</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Client-side URL redirect
* @description Client-side URL redirection based on unvalidated user input
* may cause redirection to malicious web sites.
* @kind problem
* @problem.severity error
* @precision high
* @id js/client-side-unvalidated-url-redirection
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
* external/cwe/cwe-601
*/
import javascript
import semmle.javascript.security.dataflow.ClientSideUrlRedirect::ClientSideUrlRedirect
from Configuration urlRedirect, DataFlow::Node source, DataFlow::Node sink
where urlRedirect.hasFlow(source, sink)
select sink, "Untrusted URL redirection due to $@.", source, "user-provided value"

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Directly incorporating user input into a URL redirect request without validating the input
can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a
malicious site that looks very similar to the real site they intend to visit, but which is
controlled by the attacker.
</p>
</overview>
<recommendation>
<p>
To guard against untrusted URL redirection, it is advisable to avoid putting user input
directly into a redirect URL. Instead, maintain a list of authorized
redirects on the server; then choose from that list based on the user input provided.
</p>
</recommendation>
<example>
<p>
The following example shows an HTTP request parameter being used directly in a URL redirect
without validating the input, which facilitates phishing attacks:
</p>
<sample src="examples/ServerSideUrlRedirect.js"/>
<p>
One way to remedy the problem is to validate the user input against a known fixed string
before doing the redirection:
</p>
<sample src="examples/ServerSideUrlRedirectGood.js"/>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet">
XSS Unvalidated Redirects and Forwards Cheat Sheet</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name Server-side URL redirect
* @description Server-side URL redirection based on unvalidated user input
* may cause redirection to malicious web sites.
* @kind problem
* @problem.severity warning
* @id js/server-side-unvalidated-url-redirection
* @tags security
* external/cwe/cwe-601
* @precision high
*/
import javascript
import semmle.javascript.security.dataflow.ServerSideUrlRedirect::ServerSideUrlRedirect
from Configuration urlRedirect, DataFlow::Node source, DataFlow::Node sink
where urlRedirect.hasFlow(source, sink)
select sink, "Untrusted URL redirection due to $@.", source, "user-provided value"

View File

@@ -0,0 +1 @@
window.location = /.*redirect=([^&]*).*/.exec(document.location.href)[1];

View File

@@ -0,0 +1,4 @@
app.get('/some/path', function(req, res) {
// BAD: a request parameter is incorporated without validation into a URL redirect
res.redirect(req.param("target"));
});

View File

@@ -0,0 +1,8 @@
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
app.get('/some/path', function(req, res) {
// GOOD: the request parameter is validated against a known fixed string
let target = req.param("target");
if (VALID_REDIRECT === target)
res.redirect(target);
});

View File

@@ -0,0 +1,57 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Parsing untrusted XML files with a weakly configured XML parser may lead to an
XML External Entity (XXE) attack. This type of attack uses external entity references
to access arbitrary files on a system, carry out denial-of-service (DoS) attacks, or server-side
request forgery. Even when the result of parsing is not returned to the user, DoS attacks are still possible
and out-of-band data retrieval techniques may allow attackers to steal sensitive data.
</p>
</overview>
<recommendation>
<p>
The easiest way to prevent XXE attacks is to disable external entity handling when
parsing untrusted data. How this is done depends on the library being used. Note that some
libraries, such as recent versions of <code>libxml</code>, disable entity expansion by default,
so unless you have explicitly enabled entity expansion, no further action needs to be taken.
</p>
</recommendation>
<example>
<p>
The following example uses the <code>libxml</code> XML parser to parse a string <code>xmlSrc</code>.
If that string is from an untrusted source, this code may be vulnerable to an XXE attack, since
the parser is invoked with the <code>noent</code> option set to <code>true</code>:
</p>
<sample src="examples/Xxe.js"/>
<p>
To guard against XXE attacks, the <code>noent</code> option should be omitted or set to
<code>false</code>. This means that no entity expansion is undertaken at all, not even for standard
internal entities such as <code>&amp;amp;</code> or <code>&amp;gt;</code>. If desired, these
entities can be expanded in a separate step using utility functions provided by libraries such
as <a href="http://underscorejs.org/#unescape">underscore</a>,
<a href="https://lodash.com/docs/latest#unescape">lodash</a> or
<a href="https://github.com/mathiasbynens/he">he</a>.
</p>
<sample src="examples/XxeGood.js"/>
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML External Entity (XXE) Processing</a>.
</li>
<li>
Timothy Morgen:
<a href="https://www.vsecurity.com//download/publications/XMLDTDEntityAttacks.pdf">XML Schema, DTD, and Entity Attacks</a>.
</li>
<li>
Timur Yunusov, Alexey Osipov:
<a href="https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf">XML Out-Of-Band Data Retrieval</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name XML external entity expansion
* @description Parsing user input as an XML document with external
* entity expansion is vulnerable to XXE attacks.
* @kind problem
* @problem.severity error
* @precision high
* @id js/xxe
* @tags security
* external/cwe/cwe-611
* external/cwe/cwe-827
*/
import javascript
import semmle.javascript.security.dataflow.Xxe::Xxe
from Configuration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "A $@ is parsed as XML without guarding against external entity expansion.",
source, "user-provided value"

View File

@@ -0,0 +1,2 @@
const libxml = require('libxmljs');
var doc = libxml.parseXml(xmlSrc, { noent: true });

View File

@@ -0,0 +1,2 @@
const libxml = require('libxmljs');
var doc = libxml.parseXml(xmlSrc);

View File

@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
If an XPath expression is built using string concatenation, and the components of the concatenation
include user input, it makes it very easy for a user to create a malicious XPath expression.
</p>
</overview>
<recommendation>
<p>
If user input must be included in an XPath expression, either sanitize the data or use variable
references to safely embed it without altering the structure of the expression.
</p>
</recommendation>
<example>
<p>
In this example, the code accepts a user name specified by the user, and uses this
unvalidated and unsanitized value in an XPath expression constructed using the <code>xpath</code>
package. This is vulnerable to the user providing special characters or string sequences
that change the meaning of the XPath expression to search for different values.
</p>
<sample src="examples/XpathInjectionBad.js" />
<p>
Instead, embed the user input using the variable replacement mechanism offered
by <code>xpath</code>:
</p>
<sample src="examples/XpathInjectionGood.js" />
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php?title=Testing_for_XPath_Injection_(OTG-INPVAL-010)">Testing for XPath Injection</a>.</li>
<li>OWASP: <a href="https://www.owasp.org/index.php/XPATH_Injection">XPath Injection</a>.</li>
<li>npm: <a href="https://www.npmjs.com/package/xpath">xpath</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name XPath injection
* @description Building an XPath expression from user-controlled sources is vulnerable to insertion of
* malicious code by the user.
* @kind problem
* @problem.severity error
* @precision high
* @id js/xpath-injection
* @tags security
* external/cwe/cwe-643
*/
import javascript
import semmle.javascript.security.dataflow.XpathInjection::XpathInjection
from Configuration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "$@ flows here and is used in an XPath expression.", source, "User-provided value"

View File

@@ -0,0 +1,13 @@
const express = require('express');
const xpath = require('xpath');
const app = express();
app.get('/some/route', function(req, res) {
let userName = req.param("userName");
// BAD: Use user-provided data directly in an XPath expression
let badXPathExpr = xpath.parse("//users/user[login/text()='" + userName + "']/home_dir/text()");
badXPathExpr.select({
node: root
});
});

View File

@@ -0,0 +1,14 @@
const express = require('express');
const xpath = require('xpath');
const app = express();
app.get('/some/route', function(req, res) {
let userName = req.param("userName");
// GOOD: Embed user-provided data using variables
let goodXPathExpr = xpath.parse("//users/user[login/text()=$userName]/home_dir/text()");
goodXPathExpr.select({
node: root,
variables: { userName: userName }
});
});

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Constructing a regular expression with unsanitized user input is dangerous as a malicious user may
be able to modify the meaning of the expression. In particular, such a user may be able to provide
a regular expression fragment that takes exponential time in the worst case, and use that to
perform a Denial of Service attack.
</p>
</overview>
<recommendation>
<p>
Before embedding user input into a regular expression, use a sanitization function such as
lodash's <code>_.escapeRegExp</code> to escape meta-characters that have special meaning.
</p>
</recommendation>
<example>
<p>
The following example shows a HTTP request parameter that is used to construct a regular expression
without sanitizing it first:
</p>
<sample src="examples/RegExpInjection.js" />
<p>
Instead, the request parameter should be sanitized first, for example using the function
<code>_.escapeRegExp</code> from the lodash package. This ensures that the user cannot insert
characters which have a special meaning in regular expressions.
</p>
<sample src="examples/RegExpInjectionGood.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.
</li>
<li>
npm: <a href="https://www.npmjs.com/package/lodash">lodash</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Regular expression injection
* @description User input should not be used in regular expressions without first being escaped,
* otherwise a malicious user may be able to inject an expression that could require
* exponential time on certain inputs.
* @kind problem
* @problem.severity error
* @precision high
* @id js/regex-injection
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
import javascript
import semmle.javascript.security.dataflow.RegExpInjection::RegExpInjection
from Configuration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "This regular expression is constructed from a $@.", source, "user-provided value"

View File

@@ -0,0 +1,9 @@
var express = require('express');
var app = express();
app.get('/findKey', function(req, res) {
var key = req.param("key"), input = req.param("input");
// BAD: Unsanitized user input is used to construct a regular expression
var re = new RegExp("\\b" + key + "=(.*)\n");
});

View File

@@ -0,0 +1,11 @@
var express = require('express');
var _ = require('lodash');
var app = express();
app.get('/findKey', function(req, res) {
var key = req.param("key"), input = req.param("input");
// GOOD: User input is sanitized before constructing the regex
var safeKey = _.escapeRegExp(key);
var re = new RegExp("\\b" + safeKey + "=(.*)\n");
});

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
HTTP request handlers should not perform expensive operations such as accessing
the file system, executing an operating system command or interacting with a database
without limiting the rate at which requests are accepted. Otherwise, the application
becomes vulnerable to denial-of-service attacks where an attacker can cause the
application to crash or become unresponsive by issuing a large number of requests at
the same time.
</p>
</overview>
<recommendation>
<p>
A rate-limiting middleware should be used to prevent such attacks.
</p>
</recommendation>
<example>
<p>
The following example shows an Express application that serves static files without
rate limiting:
</p>
<sample src="examples/MissingRateLimiting.js" />
<p>
To prevent denial-of-service attacks, the <code>express-rate-limit</code> package
can be used:
</p>
<sample src="examples/MissingRateLimitingGood.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Denial_of_Service_Cheat_Sheet">Denial of Service Cheat Sheet</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">Denial-of-service attack</a>.
</li>
<li>
NPM: <a href="https://www.npmjs.com/package/express-rate-limit">express-rate-limit</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Missing rate limiting
* @description An HTTP request handler that performs expensive operations without
* restricting the rate at which operations can be carried out is vulnerable
* to denial-of-service attacks.
* @kind problem
* @problem.severity error
* @precision high
* @id js/missing-rate-limiting
* @tags security
* external/cwe/cwe-770
* external/cwe/cwe-307
* external/cwe/cwe-400
*/
import javascript
import semmle.javascript.security.dataflow.MissingRateLimiting
import semmle.javascript.RestrictedLocations
from ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe,
string explanation, DataFlow::Node reference, string referenceLabel
where r = rhe.getBody() and
r.explain(explanation, reference, referenceLabel) and
not rhe instanceof RateLimitedRouteHandlerExpr
select (FirstLineOf)rhe, "This route handler " + explanation + ", but is not rate-limited.", reference, referenceLabel

View File

@@ -0,0 +1,8 @@
var express = require('express');
var app = express();
app.get('/:path', function(req, res) {
let path = req.params.path;
if (isValidPath(path))
res.sendFile(path);
});

View File

@@ -0,0 +1,18 @@
var express = require('express');
var app = express();
// set up rate limiter: maximum of five requests per minute
var RateLimit = require('express-rate-limit');
var limiter = new RateLimit({
windowMs: 1*60*1000, // 1 minute
max: 5
});
// apply rate limiter to all requests
app.use(limiter);
app.get('/:path', function(req, res) {
let path = req.params.path;
if (isValidPath(path))
res.sendFile(path);
});

View File

@@ -0,0 +1,60 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Parsing untrusted XML files with a weakly configured XML parser may be vulnerable to
denial-of-service (DoS) attacks exploiting uncontrolled internal entity expansion.
</p>
<p>
In XML, so-called <i>internal entities</i> are a mechanism for introducing an abbreviation
for a piece of text or part of a document. When a parser that has been configured
to expand entities encounters a reference to an internal entity, it replaces the entity
by the data it represents. The replacement text may itself contain other entity references,
which are expanded recursively. This means that entity expansion can increase document size
dramatically.
</p>
<p>
If untrusted XML is parsed with entity expansion enabled, a malicious attacker could
submit a document that contains very deeply nested entity definitions, causing the parser
to take a very long time or use large amounts of memory. This is sometimes called an
<i>XML bomb</i> attack.
</p>
</overview>
<recommendation>
<p>
The safest way to prevent XML bomb attacks is to disable entity expansion when parsing untrusted
data. How this is done depends on the library being used. Note that some libraries, such as
recent versions of <code>libxmljs</code> (though not its SAX parser API), disable entity expansion
by default, so unless you have explicitly enabled entity expansion, no further action is needed.
</p>
</recommendation>
<example>
<p>
The following example uses the XML parser provided by the <code>node-expat</code> package to
parse a string <code>xmlSrc</code>. If that string is from an untrusted source, this code may be
vulnerable to a DoS attack, since <code>node-expat</code> expands internal entities by default:
</p>
<sample src="examples/XmlBomb.js"/>
<p>
At the time of writing, <code>node-expat</code> does not provide a way of controlling entity
expansion, but the example could be rewritten to use the <code>sax</code> package instead,
which only expands standard entities such as <code>&amp;amp;</code>:
</p>
<sample src="examples/XmlBombGood.js"/>
</example>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Billion_laughs">Billion Laughs</a>.
</li>
<li>
Bryan Sullivan:
<a href="https://msdn.microsoft.com/en-us/magazine/ee335713.aspx">Security Briefs - XML Denial of Service Attacks and Defenses</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name XML internal entity expansion
* @description Parsing user input as an XML document with arbitrary internal
* entity expansion is vulnerable to denial-of-service attacks.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/xml-bomb
* @tags security
* external/cwe/cwe-776
* external/cwe/cwe-400
*/
import javascript
import semmle.javascript.security.dataflow.XmlBomb::XmlBomb
from Configuration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "A $@ is parsed as XML without guarding against uncontrolled entity expansion.",
source, "user-provided value"

View File

@@ -0,0 +1,5 @@
const expat = require('node-expat');
var parser = new expat.Parser();
parser.on('startElement', handleStart);
parser.on('text', handleText);
parser.write(xmlSrc);

View File

@@ -0,0 +1,5 @@
const sax = require('sax');
var parser = sax.parser(true);
parser.onopentag = handleStart;
parser.ontext = handleText;
parser.write(xmlSrc);

View File

@@ -0,0 +1,45 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Including unencrypted hard-coded authentication credentials in source code is dangerous because
the credentials may be easily discovered. For example, the code may be open source, or it may
be leaked or accidentally revealed, making the credentials visible to an attacker. This, in turn,
might enable them to gain unauthorized access, or to obtain privileged information.
</p>
</overview>
<recommendation>
<p>
Remove hard-coded credentials, such as user names, passwords and certificates, from source code.
Instead, place them in configuration files, environment variables or other data stores if necessary.
If possible, store configuration files including credential data separately from the source code,
in a secure location with restricted access.
</p>
</recommendation>
<example>
<p>
The following code example connects to a Postgres database using the <code>pg</code> package
and hard-codes user name and password:
</p>
<sample src="examples/HardcodedCredentials.js"/>
<p>
Instead, user name and password can be supplied through the environment variables
<code>PGUSER</code> and <code>PGPASSWORD</code>, which can be set externally without hard-coding
credentials in the source code.
</p>
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Use_of_hard-coded_password">Use of hard-coded password</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Hard-coded credentials
* @description Hard-coding credentials in source code may enable an attacker
* to gain unauthorized access.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/hardcoded-credentials
* @tags security
* external/cwe/cwe-259
* external/cwe/cwe-321
* external/cwe/cwe-798
*/
import javascript
private import semmle.javascript.security.dataflow.HardcodedCredentials::HardcodedCredentials
from Configuration cfg, DataFlow::Node source, Sink sink, string value
where cfg.hasFlow(source, sink) and
// use source value in message if it's available
if source.asExpr() instanceof ConstantString then
value = "The hard-coded value \"" + source.asExpr().(ConstantString).getStringValue() + "\""
else
value = "This hard-coded value"
select source, value + " is used as $@.", sink, sink.getKind()

View File

@@ -0,0 +1,10 @@
const pg = require('pg')
const client = new pg.Client({
user: 'dbuser',
host: 'database.server.com',
database: 'mydb',
password: 'secretpassword',
port: 3211,
})
client.connect()

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using user-controlled data in a permissions check may
allow a user to gain unauthorized access to protected functionality or
data.
</p>
</overview>
<recommendation>
<include src="recommendation.qhelp" />
</recommendation>
<example>
<include src="example.qhelp" />
</example>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,90 @@
/**
* @name User-controlled bypass of security check
* @description Conditions that the user controls are not suited for making security-related decisions.
* @kind problem
* @problem.severity error
* @precision high
* @id js/user-controlled-bypass
* @tags security
* external/cwe/cwe-807
* external/cwe/cwe-290
*/
import javascript
import semmle.javascript.security.dataflow.ConditionalBypass::ConditionalBypass
/**
* Holds if the value of `nd` flows into `guard`.
*/
predicate flowsToGuardExpr(DataFlow::Node nd, SensitiveActionGuardConditional guard) {
nd = guard or
flowsToGuardExpr(nd.getASuccessor(), guard)
}
/**
* A comparison that guards a sensitive action, e.g. the comparison in:
* `var ok = x == y; if (ok) login()`.
*/
class SensitiveActionGuardComparison extends Comparison {
SensitiveActionGuardConditional guard;
SensitiveActionGuardComparison() {
flowsToGuardExpr(DataFlow::valueNode(this), guard)
}
/**
* Gets the guard that uses this comparison.
*/
SensitiveActionGuardConditional getGuard() {
result = guard
}
}
/**
* An intermediary sink to enable reuse of the taint configuration.
* This sink should not be presented to the client of this query.
*/
class SensitiveActionGuardComparisonOperand extends Sink {
SensitiveActionGuardComparison comparison;
SensitiveActionGuardComparisonOperand() {
asExpr() = comparison.getAnOperand()
}
override SensitiveAction getAction() {
result = comparison.getGuard().getAction()
}
}
/**
* Holds if `sink` guards `action`, and `source` taints `sink`.
*
* If flow from `source` taints `sink`, then an attacker can
* control if `action` should be executed or not.
*/
predicate isTaintedGuardForSensitiveAction(Sink sink, DataFlow::Node source, SensitiveAction action) {
action = sink.getAction() and
// exclude the intermediary sink
not sink instanceof SensitiveActionGuardComparisonOperand and
exists (Configuration cfg |
// ordinary taint tracking to a guard
cfg.hasFlow(source, sink) or
// taint tracking to both operands of a guard comparison
exists (SensitiveActionGuardComparison cmp, DataFlow::Node lSource, DataFlow::Node rSource |
sink = cmp.getGuard() and
cfg.hasFlow(lSource, DataFlow::valueNode(cmp.getLeftOperand())) and
cfg.hasFlow(rSource, DataFlow::valueNode(cmp.getRightOperand())) |
source = lSource or
source = rSource
)
)
}
from DataFlow::Node source, DataFlow::Node sink, SensitiveAction action
where isTaintedGuardForSensitiveAction(sink, source, action)
select sink, "This condition guards a sensitive $@, but $@ controls it.",
action, "action",
source, "a user-provided value"

View File

@@ -0,0 +1,35 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Many programmers mistakenly believe that information stored in
cookies and hidden HTML form fields is tamper-proof and cannot be
changed by the user. Hence they might try to verify other request data
by comparing it against this information, for example by checking
whether a user name embedded in a request parameter matches data
stored in an (unsigned) cookie.
In fact, however, all of these sources of data are user-controlled, so
a malicious user can easily bypass such checks to gain unauthorized
access to protected functionality or data.
</p>
</overview>
<recommendation>
<include src="recommendation.qhelp" />
</recommendation>
<example>
<include src="example.qhelp" />
</example>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Comparison of user-controlled data of different kinds
* @description Comparing different kinds of HTTP request data may be a symptom of an insufficient security check.
* @kind problem
* @problem.severity error
* @precision low
* @id js/different-kinds-comparison-bypass
* @tags security
* external/cwe/cwe-807
* external/cwe/cwe-290
*/
import javascript
import semmle.javascript.security.dataflow.DifferentKindsComparisonBypass::DifferentKindsComparisonBypass
from DifferentKindsComparison cmp, DataFlow::Node lSource, DataFlow::Node rSource
where lSource = cmp.getLSource() and rSource = cmp.getRSource() and
not (
// Standard names for the double submit cookie pattern (CSRF protection)
exists (DataFlow::PropRead s | s = lSource or s = rSource |
s.getPropertyName().regexpMatch("(?i).*(csrf|state|token).*")
)
)
select cmp, "This comparison of $@ and $@ is a potential security risk since it is controlled by the user.", lSource, lSource.toString(), rSource, rSource.toString()

View File

@@ -0,0 +1,30 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<fragment>
<p>
In this example, we have a server that shows private
information for a user, based on the request parameter
<code>userId</code>. For privacy reasons, users may only view their
own private information, so the server checks that the request
parameter <code>userId</code> matches a cookie value for the user
who is logged in.
</p>
<sample src="examples/bypass.js" />
<p>
This security check is, however, insufficient since an
attacker can craft his cookie values to match those of any user. To
prevent this, the server can cryptographically sign the security
critical cookie values:
</p>
<sample src="examples/bypass_fixed.js" />
</fragment>
</qhelp>

View File

@@ -0,0 +1,13 @@
var express = require('express');
var app = express();
// ...
app.get('/full-profile/:userId', function(req, res) {
if (req.cookies.loggedInUserId !== req.params.userId) {
// BAD: login decision made based on user controlled data
requireLogin();
} else {
// ... show private information
}
});

Some files were not shown because too many files have changed in this diff Show More