JavaScript: Add query IncompleteUrlSchemeCheck.ql.

This commit is contained in:
Max Schaefer
2019-11-08 11:22:45 +00:00
parent 3a7f9a588d
commit ab583b7994
10 changed files with 127 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
URLs starting with <code>javascript:</code> can be used to encode JavaScript code to be executed
when the URL is visited. While this is a powerful mechanism for creating feature-rich and responsive
web applications, it is also a potential security risk: if the URL comes from an untrusted source,
it might contain harmful JavaScript code. For this reason, many frameworks and libraries first check
the URL scheme of any untrusted URL, and reject URLs with the <code>javascript:</code> scheme.
</p>
<p>
However, the <code>data:</code> and <code>vbscript:</code> schemes can be used to represent
executable code in a very similar way, so any validation logic that checks against
<code>javascript:</code> but not against <code>data:</code> and <code>vbscript:</code> is likely to
be insufficient.
</p>
</overview>
<recommendation>
<p>
Add checks covering both <code>data:</code> and <code>vbscript:</code>.
</p>
</recommendation>
<example>
<p>
The following function validates a (presumably untrusted) URL <code>url</code>. If it starts with
<code>javascript:</code> (case-insensitive and potentially preceded by whitespace), the harmless
placeholder URL <code>about:blank</code> is returned to prevent code injection; otherwise
<code>url</code> itself is returned.
</p>
<sample src="examples/IncompleteUrlSchemeCheck.js"/>
<p>
While this check provides partial projection, it should be extended to cover <code>data:</code>
and <code>vbscript:</code> as well:
</p>
<sample src="examples/IncompleteUrlSchemeCheckGood.js"/>
</example>
<references>
<li>WHATWG: <a href="https://wiki.whatwg.org/wiki/URL_schemes">URL schemes</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,57 @@
/**
* @name Incomplete URL scheme check
* @description Checking for the "javascript:" URL scheme without also checking for "vbscript:"
* and "data:" suggests a logic error or even a security vulnerability.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/incomplete-url-scheme-check
* @tags security
* correctness
* external/cwe/cwe-020
*/
import javascript
import semmle.javascript.dataflow.internal.AccessPaths
/** A URL scheme that can be used to represent executable code. */
class DangerousScheme extends string {
DangerousScheme() { this = "data:" or this = "javascript:" or this = "vbscript:" }
}
/** Gets a data-flow node that checks `nd` against the given `scheme`. */
DataFlow::Node schemeCheck(
DataFlow::Node nd, DangerousScheme scheme
) {
// check of the form `nd.startsWith(scheme)`
exists(StringOps::StartsWith sw | sw = result |
sw.getBaseString() = nd and
sw.getSubstring().mayHaveStringValue(scheme)
)
or
// propagate through trimming, case conversion, and regexp replace
exists(DataFlow::MethodCallNode stringop |
stringop.getMethodName().matches("trim%") or
stringop.getMethodName().matches("to%Case") or
stringop.getMethodName() = "replace"
|
result = schemeCheck(stringop, scheme) and
nd = stringop.getReceiver()
)
or
// propagate througb local data flow
result = schemeCheck(nd.getASuccessor(), scheme)
}
/** Gets a data-flow node that checks an instance of `ap` against the given `scheme`. */
DataFlow::Node schemeCheckOn(AccessPath ap, DangerousScheme scheme) {
result = schemeCheck(ap.getAnInstance().flow(), scheme)
}
from AccessPath ap, int n
where
n = strictcount(DangerousScheme s) and
strictcount(DangerousScheme s | exists(schemeCheckOn(ap, s))) < n
select schemeCheckOn(ap, "javascript:"),
"This check does not consider " +
strictconcat(DangerousScheme s | not exists(schemeCheckOn(ap, s)) | s, " and ") + "."

View File

@@ -0,0 +1,6 @@
function sanitizeUrl(url) {
let u = decodeURI(url).trim().toLowerCase();
if (u.startsWith("javascript:"))
return "about:blank";
return url;
}

View File

@@ -0,0 +1,6 @@
function sanitizeUrl(url) {
let u = decodeURI(url).trim().toLowerCase();
if (u.startsWith("javascript:") || u.startsWith("data:") || u.startsWith("vbscript:"))
return "about:blank";
return url;
}