mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
replace taint tracking by type tracking and merge remaining queries for CWE-830
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @name Dynamic creation of untrusted source use
|
||||
* @description Finds dynamically created DOM elements that may use
|
||||
* behaviour from http-URLs without integrity checks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @tags security
|
||||
* @id js/dynamic-creation-of-untrusted-source-use
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
|
||||
predicate isCreateElementNode(DataFlow::CallNode call, string name) {
|
||||
call = DataFlow::globalVarRef("document").getAMethodCall("createElement") and
|
||||
call.getArgument(0).getStringValue().toLowerCase() = name
|
||||
}
|
||||
|
||||
predicate isCreateScriptNodeWoIntegrityCheck(DataFlow::CallNode call) {
|
||||
isCreateElementNode(call, "script") and
|
||||
not exists(DataFlow::Node rhs | isScriptPropWrite(call, "integrity", rhs))
|
||||
}
|
||||
|
||||
predicate isScriptPropWrite(
|
||||
DataFlow::CallNode createElementCall, string propName, DataFlow::Node rhs
|
||||
) {
|
||||
exists(DataFlow::PropWrite assignment |
|
||||
isCreateElementNode(createElementCall, "script") and
|
||||
assignment.writes(createElementCall.getALocalUse(), propName, rhs)
|
||||
)
|
||||
}
|
||||
|
||||
class DynamicCreationOfUntrustedSourceUseCfg extends TaintTracking::Configuration {
|
||||
DynamicCreationOfUntrustedSourceUseCfg() { this = "DynamicCreationOfUntrustedSourceUseCfg" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(StringLiteral s | source = s.flow() |
|
||||
s.getValue() = ["http:", "//"] + any(string rest) // TODO match HTTP HtTp etc
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(DataFlow::CallNode createElementCall |
|
||||
isScriptPropWrite(createElementCall, "src", sink) and
|
||||
isCreateScriptNodeWoIntegrityCheck(createElementCall)
|
||||
or
|
||||
exists(DataFlow::CallNode iframeCreateCall, DataFlow::PropWrite srcWrite |
|
||||
isCreateElementNode(iframeCreateCall, "iframe") and
|
||||
srcWrite.getRhs() = sink and
|
||||
srcWrite.getBase() = iframeCreateCall.getALocalUse()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DynamicCreationOfUntrustedSourceUseCfg cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Illegal flow from $@.", source.getNode(), "here"
|
||||
@@ -13,73 +13,169 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.HTML
|
||||
import semmle.javascript.dataflow.TaintTracking
|
||||
|
||||
bindingset[host]
|
||||
predicate isLocalhostPrefix(string host) {
|
||||
host.toLowerCase()
|
||||
.regexpMatch([
|
||||
"localhost(:[0-9]+)?/.*", "127.0.0.1(:[0-9]+)?/.*", "::1/.*", "\\[::1\\]:[0-9]+/.*"
|
||||
])
|
||||
}
|
||||
|
||||
/** A path that is vulnerable to a MITM attack. */
|
||||
bindingset[url]
|
||||
predicate isUntrustedSourceUrl(string url) {
|
||||
url.substring(0, 2) = "//"
|
||||
or
|
||||
exists(string hostPath | hostPath = url.regexpCapture("http://(.*)", 1) |
|
||||
not isLocalhostPrefix(hostPath)
|
||||
)
|
||||
}
|
||||
|
||||
/** A path that needs an integrity check — even with https. */
|
||||
bindingset[url]
|
||||
predicate isCdnUrlWithCheckingRequired(string url) {
|
||||
// Some CDN URLs are required to have an integrity attribute. We only add CDNs to that list
|
||||
// that recommend integrity-checking.
|
||||
url.regexpMatch([
|
||||
"^https?://code\\.jquery\\.com/.*\\.js$", "^https?://cdnjs\\.cloudflare\\.com/.*\\.js$",
|
||||
"^https?://cdnjs\\.com/.*\\.js$"
|
||||
])
|
||||
}
|
||||
|
||||
abstract class IncludesUntrustedContent extends HTML::Element {
|
||||
/** Gets an explanation why this source is untrusted. */
|
||||
abstract string getProblem();
|
||||
}
|
||||
|
||||
/** A script element that refers to untrusted content. */
|
||||
class ScriptElementWithUntrustedContent extends IncludesUntrustedContent, HTML::ScriptElement {
|
||||
ScriptElementWithUntrustedContent() {
|
||||
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
|
||||
isUntrustedSourceUrl(this.getSourcePath())
|
||||
module Generic {
|
||||
/** A `CallNode` that creates an element of kind `name`. */
|
||||
predicate isCreateElementNode(DataFlow::CallNode call, string name) {
|
||||
call = DataFlow::globalVarRef("document").getAMethodCall("createElement") and
|
||||
call.getArgument(0).getStringValue().toLowerCase() = name
|
||||
}
|
||||
|
||||
override string getProblem() {
|
||||
result = "script elements should use an HTTPS url and/or use the integrity attribute"
|
||||
/**
|
||||
* True if `rhs` is being assigned to the `propName` property of an HTML
|
||||
* element created by `createElementCall`.
|
||||
*/
|
||||
predicate isPropWrite(DataFlow::CallNode createElementCall, string propName, DataFlow::Node rhs) {
|
||||
exists(DataFlow::PropWrite assignment |
|
||||
isCreateElementNode(createElementCall, _) and
|
||||
assignment.writes(createElementCall.getALocalUse(), propName, rhs)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A `createElement` call that creates a `<script ../>` element which never
|
||||
* has its `integrity` attribute set locally.
|
||||
*/
|
||||
predicate isCreateScriptNodeWoIntegrityCheck(DataFlow::CallNode createCall) {
|
||||
isCreateElementNode(createCall, "script") and
|
||||
not exists(DataFlow::Node rhs | isPropWrite(createCall, "integrity", rhs))
|
||||
}
|
||||
|
||||
/** A location that adds a reference to an untrusted source. */
|
||||
abstract class AddsUntrustedUrl extends Locatable {
|
||||
/** Gets an explanation why this source is untrusted. */
|
||||
abstract string getProblem();
|
||||
}
|
||||
}
|
||||
|
||||
/** A script element that refers to untrusted content. */
|
||||
class CDNScriptElementWithUntrustedContent extends IncludesUntrustedContent, HTML::ScriptElement {
|
||||
CDNScriptElementWithUntrustedContent() {
|
||||
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
|
||||
isCdnUrlWithCheckingRequired(this.getSourcePath())
|
||||
module StaticCreation {
|
||||
bindingset[host]
|
||||
predicate isLocalhostPrefix(string host) {
|
||||
host.toLowerCase()
|
||||
.regexpMatch([
|
||||
"localhost(:[0-9]+)?/.*", "127.0.0.1(:[0-9]+)?/.*", "::1/.*", "\\[::1\\]:[0-9]+/.*"
|
||||
])
|
||||
}
|
||||
|
||||
override string getProblem() {
|
||||
result =
|
||||
"script elements that depend on this CDN should use an HTTPS url and use the integrity attribute"
|
||||
/** A path that is vulnerable to a MITM attack. */
|
||||
bindingset[url]
|
||||
predicate isUntrustedSourceUrl(string url) {
|
||||
exists(string hostPath | hostPath = url.regexpCapture("http://(.*)", 1) |
|
||||
not isLocalhostPrefix(hostPath)
|
||||
)
|
||||
}
|
||||
|
||||
/** A path that needs an integrity check — even with https. */
|
||||
bindingset[url]
|
||||
predicate isCdnUrlWithCheckingRequired(string url) {
|
||||
// Some CDN URLs are required to have an integrity attribute. We only add CDNs to that list
|
||||
// that recommend integrity-checking.
|
||||
url.regexpMatch([
|
||||
"^https?://code\\.jquery\\.com/.*\\.js$", "^https?://cdnjs\\.cloudflare\\.com/.*\\.js$",
|
||||
"^https?://cdnjs\\.com/.*\\.js$"
|
||||
])
|
||||
}
|
||||
|
||||
/** A script element that refers to untrusted content. */
|
||||
class ScriptElementWithUntrustedContent extends Generic::AddsUntrustedUrl, HTML::ScriptElement {
|
||||
ScriptElementWithUntrustedContent() {
|
||||
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
|
||||
isUntrustedSourceUrl(this.getSourcePath())
|
||||
}
|
||||
|
||||
override string getProblem() {
|
||||
result = "script elements should use an HTTPS url and/or use the integrity attribute"
|
||||
}
|
||||
}
|
||||
|
||||
/** A script element that refers to untrusted content. */
|
||||
class CDNScriptElementWithUntrustedContent extends Generic::AddsUntrustedUrl, HTML::ScriptElement {
|
||||
CDNScriptElementWithUntrustedContent() {
|
||||
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
|
||||
isCdnUrlWithCheckingRequired(this.getSourcePath())
|
||||
}
|
||||
|
||||
override string getProblem() {
|
||||
result =
|
||||
"script elements that depend on this CDN should use an HTTPS url and use the integrity attribute"
|
||||
}
|
||||
}
|
||||
|
||||
/** An iframe element that includes untrusted content. */
|
||||
class IframeElementWithUntrustedContent extends HTML::IframeElement, Generic::AddsUntrustedUrl {
|
||||
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(this.getSourcePath()) }
|
||||
|
||||
override string getProblem() { result = "iframe elements should use an HTTPS url" }
|
||||
}
|
||||
}
|
||||
|
||||
/** An iframe element that includes untrusted content. */
|
||||
class IframeElementWithUntrustedContent extends HTML::IframeElement, IncludesUntrustedContent {
|
||||
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(this.getSourcePath()) }
|
||||
module DynamicCreation {
|
||||
import DataFlow::TypeTracker
|
||||
|
||||
override string getProblem() { result = "iframe elements should use an HTTPS url" }
|
||||
predicate isUnsafeSourceLiteral(DataFlow::Node source) {
|
||||
exists(StringLiteral s | source = s.flow() |
|
||||
s.getValue().toLowerCase() = "http:" + any(string rest)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::Node urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker t) {
|
||||
t.start() and isUnsafeSourceLiteral(result)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2, DataFlow::Node prev |
|
||||
prev = urlTrackedFromUnsafeSourceLiteral(t2)
|
||||
|
|
||||
not exists(string httpsUrl | httpsUrl.toLowerCase() = "https:" + any(string rest) |
|
||||
// when the result may have a string value starting with https,
|
||||
// we're most likely with an assignment like:
|
||||
// e.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
|
||||
// these assignments, we don't want to fix — once the browser is using http,
|
||||
// MITM attacks are possible anyway.
|
||||
result.mayHaveStringValue(httpsUrl)
|
||||
) and
|
||||
(
|
||||
t2 = t.smallstep(prev, result)
|
||||
or
|
||||
TaintTracking::sharedTaintStep(prev, result) and
|
||||
t = t2
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::Node urlTrackedFromUnsafeSourceLiteral() {
|
||||
result = urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
predicate isAssignedToSrcAttribute(string name, DataFlow::Node sink) {
|
||||
exists(DataFlow::CallNode createElementCall |
|
||||
name = "script" and
|
||||
Generic::isCreateScriptNodeWoIntegrityCheck(createElementCall) and
|
||||
Generic::isPropWrite(createElementCall, "src", sink)
|
||||
or
|
||||
name = "iframe" and
|
||||
Generic::isCreateElementNode(createElementCall, "iframe") and
|
||||
Generic::isPropWrite(createElementCall, "src", sink)
|
||||
)
|
||||
}
|
||||
|
||||
class IframeOrScriptSrcAssignment extends Expr, Generic::AddsUntrustedUrl {
|
||||
string name;
|
||||
|
||||
IframeOrScriptSrcAssignment() {
|
||||
exists(DataFlow::Node n | n.asExpr() = this |
|
||||
DynamicCreation::isAssignedToSrcAttribute(name, n) and
|
||||
n = DynamicCreation::urlTrackedFromUnsafeSourceLiteral()
|
||||
)
|
||||
}
|
||||
|
||||
override string getProblem() {
|
||||
name = "script" and
|
||||
result = "script elements should use an HTTPS url and/or use the integrity attribute"
|
||||
or
|
||||
name = "iframe" and result = "iframe elements should use an HTTPS url"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
from IncludesUntrustedContent s, string problem
|
||||
where problem = s.getProblem()
|
||||
select s, "HTML-element uses untrusted content (" + problem + ")"
|
||||
from Generic::AddsUntrustedUrl s
|
||||
select s, "HTML-element uses untrusted content (" + s.getProblem() + ")"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* A new query, `js/dynamic-creation-of-untrusted-source-use`, has been added to the query suite. It finds code
|
||||
that creates HTML elements that load functionality from untrusted sources, like a `script`- or `iframe`-element using http-links.
|
||||
The query is run by default.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* A new query, `js/functionality-from-untrusted-source`, has been added to the query suite. It finds HTML elements
|
||||
* A new query, `js/functionality-from-untrusted-source`, has been added to the query suite. It finds DOM elements
|
||||
that load functionality from untrusted sources, like a `script`- or `iframe`-element using http-links.
|
||||
The query is run by default.
|
||||
@@ -1,20 +0,0 @@
|
||||
nodes
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:97 | ('https ... //www') |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:28:8:96 | 'https: ... ://www' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' |
|
||||
edges
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:97 | ('https ... //www') | DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:97 | ('https ... //www') | DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:28:8:96 | 'https: ... ://www' | DynamicCreationOfUntrustedSourceUse.html:8:27:8:97 | ('https ... //www') |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' | DynamicCreationOfUntrustedSourceUse.html:8:28:8:96 | 'https: ... ://www' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' | DynamicCreationOfUntrustedSourceUse.html:8:28:8:96 | 'https: ... ://www' |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' |
|
||||
#select
|
||||
| DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' | DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' | DynamicCreationOfUntrustedSourceUse.html:8:27:8:118 | ('https ... /ga.js' | Illegal flow from $@. | DynamicCreationOfUntrustedSourceUse.html:8:85:8:96 | 'http://www' | here |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | Illegal flow from $@. | DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | here |
|
||||
@@ -2,7 +2,7 @@
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
// NOT OK (no integrity attribute)
|
||||
// OK (we accept this, as a http document location is vulnerable anyway)
|
||||
var scrpt = document.createElement('script');
|
||||
scrpt.type = 'text/javascript';
|
||||
scrpt.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.cdn.local/ga.js';
|
||||
@@ -10,7 +10,7 @@
|
||||
// OK (integrity digest present)
|
||||
var scrpt2 = document.createElement('script');
|
||||
scrpt2.type = 'text/javascript';
|
||||
scrpt2.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.cdn.local/ga.js';
|
||||
scrpt2.src = 'http://www.cdn.local/ga.js';
|
||||
scrpt2.integrity = 'sha256-h0UuK3mE9taiYlB5u9vT9A0s/XDgkfVd+F4VhN/sky=';
|
||||
|
||||
// NOT OK (http URL)
|
||||
@@ -20,6 +20,13 @@
|
||||
// OK (https URL)
|
||||
var ifrm2 = document.createElement('iframe');
|
||||
ifrm2.src = 'https://www.example.com/';
|
||||
|
||||
// NOT OK (http URL tracked through calls)
|
||||
function getUrl(version) {
|
||||
return 'http://www.cdn.local/'+version+'/ga.js';
|
||||
}
|
||||
var ifrm3 = document.createElement('iframe');
|
||||
ifrm3.src = getUrl('v123');
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Security/CWE-830/DynamicCreationOfUntrustedSourceUse.ql
|
||||
@@ -1,4 +1,5 @@
|
||||
| FunctionalityFromUntrustedSource.html:6:9:6:56 | <script>...</> | HTML-element uses untrusted content (script elements should use an HTTPS url and/or use the integrity attribute) |
|
||||
| FunctionalityFromUntrustedSource.html:9:9:9:58 | <iframe>...</> | HTML-element uses untrusted content (iframe elements should use an HTTPS url) |
|
||||
| FunctionalityFromUntrustedSource.html:11:9:11:53 | <iframe>...</> | HTML-element uses untrusted content (iframe elements should use an HTTPS url) |
|
||||
| FunctionalityFromUntrustedSource.html:20:9:20:155 | <script>...</> | HTML-element uses untrusted content (script elements that depend on this CDN should use an HTTPS url and use the integrity attribute) |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:18:26:18:50 | 'http:/ ... e.com/' | HTML-element uses untrusted content (iframe elements should use an HTTPS url) |
|
||||
| DynamicCreationOfUntrustedSourceUse.html:29:27:29:40 | getUrl('v123') | HTML-element uses untrusted content (iframe elements should use an HTTPS url) |
|
||||
| StaticCreationOfUntrustedSourceUse.html:6:9:6:56 | <script>...</> | HTML-element uses untrusted content (script elements should use an HTTPS url and/or use the integrity attribute) |
|
||||
| StaticCreationOfUntrustedSourceUse.html:9:9:9:58 | <iframe>...</> | HTML-element uses untrusted content (iframe elements should use an HTTPS url) |
|
||||
| StaticCreationOfUntrustedSourceUse.html:21:9:21:155 | <script>...</> | HTML-element uses untrusted content (script elements that depend on this CDN should use an HTTPS url and use the integrity attribute) |
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="http://test.local/foo.js"></script>> <!-- NOT OK -->
|
||||
<script src="http://test.local/foo.js" integrity="some-integrity-hash"></script>> <!-- OK (integrity digest present) -->
|
||||
<script src="https://test.local/bar.js"></script>> <!-- OK (https) -->
|
||||
<iframe src="http://test.local/foo.html"></iframe> <!-- NOT OK -->
|
||||
<iframe src="https://test.local/foo.html"></iframe> <!-- OK (https) -->
|
||||
<iframe src="//test.local/foo.html"></iframe> <!-- NOT OK (protocol-relative url) -->
|
||||
<iframe src="http://::1/foo.html"></iframe> <!-- OK (localhost) -->
|
||||
<iframe src="http://[::1]:80/foo.html"></iframe> <!-- OK (localhost) -->
|
||||
<iframe src="http://127.0.0.1:444/foo.html"></iframe> <!-- OK (localhost) -->
|
||||
|
||||
<!-- Some CDNs recommend using the integrity attribute — for those, we demand it even with https links -->
|
||||
<!-- OK (digest present) -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js" integrity="sha512-7oYXeK0OxTFxndh0erL8FsjGvrl2VMDor6fVqzlLGfwOQQqTbYsGPv4ZZ15QHfSk80doyaM0ZJdvkyDcVO7KFA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<!-- NOT OK (digest missing) -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user