Skip to content

Commit 86afd54

Browse files
committed
Moved new query to 'experimental'
Moved lists of domains to data extensions, including adding those to the overall qlpack.yml Expanded scope of new query to further domains operated by the untrusted owners of polyfill.io
1 parent 1fe14e2 commit 86afd54

22 files changed

+204
-136
lines changed

javascript/ql/lib/qlpack.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ dependencies:
1616
dataExtensions:
1717
- semmle/javascript/frameworks/**/model.yml
1818
- semmle/javascript/frameworks/**/*.model.yml
19+
- semmle/javascript/security/domains/**/*.model.yml
1920
warnOnImplicitThis: true

javascript/ql/lib/semmle/javascript/security/FunctionalityFromUntrustedSource.qll

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,10 @@ module StaticCreation {
3737
predicate isCdnUrlWithCheckingRequired(string url) {
3838
// Some CDN URLs are required to have an integrity attribute. We only add CDNs to that list
3939
// that recommend integrity-checking.
40-
url.regexpMatch("(?i)^https?://" +
41-
[
42-
"code\\.jquery\\.com", //
43-
"cdnjs\\.cloudflare\\.com", //
44-
"cdnjs\\.com", //
45-
] + "/.*\\.js$")
46-
}
47-
48-
/** Holds if `url` refers to a compromised CDN, that should not be trusted. */
49-
bindingset[url]
50-
predicate isCompromisedCdn(string url) {
51-
url.regexpMatch("(?i)^https?://" +
52-
[
53-
"cdn\\.polyfill\\.io", // See https://sansec.io/research/polyfill-supply-chain-attack for details
54-
"polyfill\\.io", // "
55-
] + "/.*$")
40+
exists(string hostname, string requiredCheckingHostname |
41+
hostname = url.regexpCapture("(?i)^(?:https?:)?//([^/]+)/.*\\.js$", 1)
42+
and isCdnDomainWithCheckingRequired(requiredCheckingHostname) and hostname = requiredCheckingHostname
43+
)
5644
}
5745

5846
/** A script element that refers to untrusted content. */
@@ -67,24 +55,15 @@ module StaticCreation {
6755
override string getProblem() { result = "Script loaded using unencrypted connection." }
6856
}
6957

70-
/** A script element that refers to compromised content. */
71-
class CdnFromCompromisedSource extends AddsUntrustedUrl, HTML::ScriptElement {
72-
CdnFromCompromisedSource() {
73-
isCompromisedCdn(this.getSourcePath())
74-
}
75-
76-
override string getUrl() { result = this.getSourcePath() }
77-
78-
override string getProblem() {
79-
result = "Script loaded from compromised content delivery network."
80-
}
81-
}
82-
8358
/** A script element that refers to untrusted content. */
8459
class CdnScriptElementWithUntrustedContent extends AddsUntrustedUrl, HTML::ScriptElement {
8560
CdnScriptElementWithUntrustedContent() {
8661
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
87-
isCdnUrlWithCheckingRequired(this.getSourcePath())
62+
(
63+
isCdnUrlWithCheckingRequired(this.getSourcePath())
64+
or
65+
isUrlWithUntrustedDomain(super.getSourcePath())
66+
)
8867
}
8968

9069
override string getUrl() { result = this.getSourcePath() }
@@ -104,6 +83,29 @@ module StaticCreation {
10483
}
10584
}
10685

86+
/** Holds if `url` refers to an URL that uses an untrusted domain. */
87+
bindingset[url]
88+
predicate isUrlWithUntrustedDomain(string url) {
89+
exists(string hostname |
90+
hostname = url.regexpCapture("(?i)^(?:https?:)?//([^/]+)/.*", 1)
91+
and isUntrustedHostname(hostname)
92+
)
93+
}
94+
95+
/** Holds if `hostname` refers to a domain or subdomain that is untrusted. */
96+
bindingset[hostname]
97+
predicate isUntrustedHostname(string hostname) {
98+
exists(string domain |
99+
(hostname = domain or hostname.matches("%." + domain)) and
100+
isUntrustedDomain(domain)
101+
)
102+
}
103+
104+
// The following predicates are extended in data extensions under javascript/ql/lib/semmle/javascript/security/domains/
105+
// and can be extended with custom model packs as necessary.
106+
extensible predicate isCdnDomainWithCheckingRequired(string hostname);
107+
extensible predicate isUntrustedDomain(string domain);
108+
107109
/** Looks for dyanmic creation of an element and source. */
108110
module DynamicCreation {
109111
/** Holds if `call` creates a tag of kind `name`. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: isCdnDomainWithCheckingRequired
5+
data:
6+
- ["code.jquery.com"]
7+
- ["cdnjs.cloudflare.com"]
8+
- ["cdnjs.com"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: isUntrustedDomain
5+
data:
6+
- ["polyfill.io"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: isUntrustedDomain
5+
data:
6+
# new location of the polyfill.io CDN, which was used to serve malware. See: https://www.cside.dev/blog/the-polyfill-attack-explained
7+
- ["polyfill.com"]
8+
- ["polyfillcache.com"]
9+
10+
# domains operated by the same owner as polyfill.io, which was used to serve malware. See: https://www.cside.dev/blog/the-polyfill-attack-explained
11+
- ["bootcdn.net"]
12+
- ["bootcss.com"]
13+
- ["staticfile.net"]
14+
- ["staticfile.org"]

javascript/ql/src/Security/CWE-830/FunctionalityFromUntrustedSource.ql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ import javascript
1515
import semmle.javascript.security.FunctionalityFromUntrustedSource
1616

1717
from AddsUntrustedUrl s
18+
// do not alert on explicitly untrusted domains
19+
// another query can alert on these, js/functionality-from-untrusted-domain
20+
where not isUrlWithUntrustedDomain(s.getUrl())
1821
select s, s.getProblem()

javascript/ql/src/Security/CWE-830/PolyfillIOCompromisedScript.qhelp

Lines changed: 0 additions & 77 deletions
This file was deleted.

javascript/ql/src/Security/CWE-830/PolyfillIOCompromisedScript.ql

Lines changed: 0 additions & 18 deletions
This file was deleted.

javascript/ql/src/change-notes/2024-07-01-polyfill-io-compromised-script.md

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added a new query, `js/functionality-from-untrusted-domain`, which detects uses in HTML and JavaScript scripts from untrusted domains, including the compromised `polyfill.io` content delivery network, and can be extended to detect other compromised scripts using data extensions.
5+
* Modified existing query, `js/functionality-from-untrusted-source`, to allow adding this new query, but reusing the same logic.
6+
* Created a shared library, `semmle.javascript.security.FunctionalityFromUntrustedSource`, to separate the logic from that existing query and allow having a separate "untrusted domain" query.

0 commit comments

Comments
 (0)