mirror of
https://github.com/github/codeql.git
synced 2026-03-17 04:56:58 +01:00
QL code and tests for C#/C++/JavaScript.
This commit is contained in:
56
javascript/ql/src/Security/CWE-022/TaintedPath.qhelp
Normal file
56
javascript/ql/src/Security/CWE-022/TaintedPath.qhelp
Normal 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>
|
||||
23
javascript/ql/src/Security/CWE-022/TaintedPath.ql
Normal file
23
javascript/ql/src/Security/CWE-022/TaintedPath.ql
Normal 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"
|
||||
13
javascript/ql/src/Security/CWE-022/examples/TaintedPath.js
Normal file
13
javascript/ql/src/Security/CWE-022/examples/TaintedPath.js
Normal 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));
|
||||
});
|
||||
44
javascript/ql/src/Security/CWE-078/CommandInjection.qhelp
Normal file
44
javascript/ql/src/Security/CWE-078/CommandInjection.qhelp
Normal 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>
|
||||
21
javascript/ql/src/Security/CWE-078/CommandInjection.ql
Normal file
21
javascript/ql/src/Security/CWE-078/CommandInjection.ql
Normal 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"
|
||||
@@ -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
|
||||
});
|
||||
52
javascript/ql/src/Security/CWE-079/ReflectedXss.qhelp
Normal file
52
javascript/ql/src/Security/CWE-079/ReflectedXss.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-079/ReflectedXss.ql
Normal file
20
javascript/ql/src/Security/CWE-079/ReflectedXss.ql
Normal 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"
|
||||
57
javascript/ql/src/Security/CWE-079/Xss.qhelp
Normal file
57
javascript/ql/src/Security/CWE-079/Xss.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-079/Xss.ql
Normal file
20
javascript/ql/src/Security/CWE-079/Xss.ql
Normal 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"
|
||||
10
javascript/ql/src/Security/CWE-079/examples/ReflectedXss.js
Normal file
10
javascript/ql/src/Security/CWE-079/examples/ReflectedXss.js
Normal 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
|
||||
;
|
||||
});
|
||||
@@ -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
|
||||
;
|
||||
});
|
||||
6
javascript/ql/src/Security/CWE-079/examples/Xss.js
Normal file
6
javascript/ql/src/Security/CWE-079/examples/Xss.js
Normal 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>");
|
||||
}
|
||||
56
javascript/ql/src/Security/CWE-089/SqlInjection.qhelp
Normal file
56
javascript/ql/src/Security/CWE-089/SqlInjection.qhelp
Normal 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>
|
||||
28
javascript/ql/src/Security/CWE-089/SqlInjection.ql
Normal file
28
javascript/ql/src/Security/CWE-089/SqlInjection.ql
Normal 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"
|
||||
18
javascript/ql/src/Security/CWE-089/examples/SqlInjection.js
Normal file
18
javascript/ql/src/Security/CWE-089/examples/SqlInjection.js
Normal 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
|
||||
});
|
||||
}
|
||||
44
javascript/ql/src/Security/CWE-094/CodeInjection.qhelp
Normal file
44
javascript/ql/src/Security/CWE-094/CodeInjection.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-094/CodeInjection.ql
Normal file
20
javascript/ql/src/Security/CWE-094/CodeInjection.ql
Normal 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"
|
||||
@@ -0,0 +1 @@
|
||||
eval(document.location.href.substring(document.location.href.indexOf("default=")+8))
|
||||
@@ -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>
|
||||
120
javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
Normal file
120
javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
Normal 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
|
||||
@@ -0,0 +1,3 @@
|
||||
function escapeQuotes(s) {
|
||||
return s.replace("'", "''");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
function escapeQuotes(s) {
|
||||
return s.replace(/'/g, "''");
|
||||
}
|
||||
46
javascript/ql/src/Security/CWE-134/TaintedFormatString.qhelp
Normal file
46
javascript/ql/src/Security/CWE-134/TaintedFormatString.qhelp
Normal 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>
|
||||
17
javascript/ql/src/Security/CWE-134/TaintedFormatString.ql
Normal file
17
javascript/ql/src/Security/CWE-134/TaintedFormatString.ql
Normal 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"
|
||||
@@ -0,0 +1 @@
|
||||
console.log("Unauthorized access attempt by " + user, ip);
|
||||
@@ -0,0 +1 @@
|
||||
console.log("Unauthorized access attempt by %s", user, ip);
|
||||
57
javascript/ql/src/Security/CWE-209/StackTraceExposure.qhelp
Normal file
57
javascript/ql/src/Security/CWE-209/StackTraceExposure.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
Normal file
20
javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
Normal 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"
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
47
javascript/ql/src/Security/CWE-312/CleartextStorage.qhelp
Normal file
47
javascript/ql/src/Security/CWE-312/CleartextStorage.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-312/CleartextStorage.ql
Normal file
20
javascript/ql/src/Security/CWE-312/CleartextStorage.ql
Normal 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()
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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>
|
||||
19
javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
Normal file
19
javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
Normal 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()
|
||||
@@ -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
|
||||
75
javascript/ql/src/Security/CWE-338/InsecureRandomness.qhelp
Normal file
75
javascript/ql/src/Security/CWE-338/InsecureRandomness.qhelp
Normal 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>
|
||||
18
javascript/ql/src/Security/CWE-338/InsecureRandomness.ql
Normal file
18
javascript/ql/src/Security/CWE-338/InsecureRandomness.ql
Normal 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"
|
||||
@@ -0,0 +1,6 @@
|
||||
function insecurePassword() {
|
||||
// BAD: the random suffix is not cryptographically secure
|
||||
var suffix = Math.random();
|
||||
var password = "myPassword" + suffix;
|
||||
return password;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
|
||||
// ...
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// ...
|
||||
});
|
||||
@@ -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>
|
||||
71
javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql
Normal file
71
javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql
Normal 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"
|
||||
@@ -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) {
|
||||
})
|
||||
@@ -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) {
|
||||
})
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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>
|
||||
19
javascript/ql/src/Security/CWE-451/MissingXFrameOptions.ql
Normal file
19
javascript/ql/src/Security/CWE-451/MissingXFrameOptions.ql
Normal 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."
|
||||
@@ -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'))
|
||||
})
|
||||
@@ -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'))
|
||||
})
|
||||
@@ -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>
|
||||
18
javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql
Normal file
18
javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql
Normal 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"
|
||||
@@ -0,0 +1,6 @@
|
||||
const jsyaml = require("js-yaml");
|
||||
|
||||
function requestHandler(req, res) {
|
||||
let data = jsyaml.load(req.params.data);
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const jsyaml = require("js-yaml");
|
||||
|
||||
function requestHandler(req, res) {
|
||||
let data = jsyaml.safeLoad(req.params.data);
|
||||
// ...
|
||||
}
|
||||
@@ -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>
|
||||
20
javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql
Normal file
20
javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql
Normal 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"
|
||||
@@ -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>
|
||||
18
javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql
Normal file
18
javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql
Normal 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"
|
||||
@@ -0,0 +1 @@
|
||||
window.location = /.*redirect=([^&]*).*/.exec(document.location.href)[1];
|
||||
@@ -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"));
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
57
javascript/ql/src/Security/CWE-611/Xxe.qhelp
Normal file
57
javascript/ql/src/Security/CWE-611/Xxe.qhelp
Normal 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;</code> or <code>&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>
|
||||
20
javascript/ql/src/Security/CWE-611/Xxe.ql
Normal file
20
javascript/ql/src/Security/CWE-611/Xxe.ql
Normal 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"
|
||||
2
javascript/ql/src/Security/CWE-611/examples/Xxe.js
Normal file
2
javascript/ql/src/Security/CWE-611/examples/Xxe.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const libxml = require('libxmljs');
|
||||
var doc = libxml.parseXml(xmlSrc, { noent: true });
|
||||
2
javascript/ql/src/Security/CWE-611/examples/XxeGood.js
Normal file
2
javascript/ql/src/Security/CWE-611/examples/XxeGood.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const libxml = require('libxmljs');
|
||||
var doc = libxml.parseXml(xmlSrc);
|
||||
40
javascript/ql/src/Security/CWE-643/XpathInjection.qhelp
Normal file
40
javascript/ql/src/Security/CWE-643/XpathInjection.qhelp
Normal 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>
|
||||
18
javascript/ql/src/Security/CWE-643/XpathInjection.ql
Normal file
18
javascript/ql/src/Security/CWE-643/XpathInjection.ql
Normal 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"
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
@@ -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 }
|
||||
});
|
||||
});
|
||||
48
javascript/ql/src/Security/CWE-730/RegExpInjection.qhelp
Normal file
48
javascript/ql/src/Security/CWE-730/RegExpInjection.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-730/RegExpInjection.ql
Normal file
20
javascript/ql/src/Security/CWE-730/RegExpInjection.ql
Normal 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"
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
48
javascript/ql/src/Security/CWE-770/MissingRateLimiting.qhelp
Normal file
48
javascript/ql/src/Security/CWE-770/MissingRateLimiting.qhelp
Normal 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>
|
||||
25
javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql
Normal file
25
javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql
Normal 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
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
60
javascript/ql/src/Security/CWE-776/XmlBomb.qhelp
Normal file
60
javascript/ql/src/Security/CWE-776/XmlBomb.qhelp
Normal 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;</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>
|
||||
20
javascript/ql/src/Security/CWE-776/XmlBomb.ql
Normal file
20
javascript/ql/src/Security/CWE-776/XmlBomb.ql
Normal 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"
|
||||
5
javascript/ql/src/Security/CWE-776/examples/XmlBomb.js
Normal file
5
javascript/ql/src/Security/CWE-776/examples/XmlBomb.js
Normal 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);
|
||||
@@ -0,0 +1,5 @@
|
||||
const sax = require('sax');
|
||||
var parser = sax.parser(true);
|
||||
parser.onopentag = handleStart;
|
||||
parser.ontext = handleText;
|
||||
parser.write(xmlSrc);
|
||||
@@ -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>
|
||||
25
javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
Normal file
25
javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
Normal 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()
|
||||
@@ -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()
|
||||
27
javascript/ql/src/Security/CWE-807/ConditionalBypass.qhelp
Normal file
27
javascript/ql/src/Security/CWE-807/ConditionalBypass.qhelp
Normal 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>
|
||||
90
javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
Normal file
90
javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
Normal 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"
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
30
javascript/ql/src/Security/CWE-807/example.qhelp
Normal file
30
javascript/ql/src/Security/CWE-807/example.qhelp
Normal 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>
|
||||
13
javascript/ql/src/Security/CWE-807/examples/bypass.js
Normal file
13
javascript/ql/src/Security/CWE-807/examples/bypass.js
Normal 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
Reference in New Issue
Block a user