Merge branch 'main' into js/shared-dataflow-merge-main

This commit is contained in:
Asger F
2024-08-02 13:18:38 +02:00
1505 changed files with 135513 additions and 35699 deletions

View File

@@ -0,0 +1,36 @@
# Insecure Helmet Configuration - customizations
You can extend the required [Helmet security settings](https://helmetjs.github.io/) using [data extensions](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-javascript/) in a [CodeQL model pack](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack).
They are defaulted to just `frameguard` and `contentSecurityPolicy`, but you can add more using this method, to require them not to be set to `false` (which explicitly disables them) in the Helmet configuration.
For example, this YAML model can be used inside a CodeQL model pack to require `frameguard` and `contentSecurityPolicy`:
```yaml
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: requiredHelmetSecuritySetting
data:
- ["frameguard"]
- ["contentSecurityPolicy"]
```
Note: Using `frameguard` and `contentSecurityPolicy` is an example: the query already enforces these, so it is not necessary to add it with your own data extension.
A suitable [model pack](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack) might be:
```yaml
name: my-org/javascript-helmet-insecure-config-model-pack
version: 1.0.0
extensionTargets:
codeql/java-all: '*'
dataExtensions:
- models/**/*.yml
```
## References
- [Helmet security settings](https://helmetjs.github.io/)
- [Customizing library models for javascript](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-javascript/)
- [Creating and working with CodeQL packs](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack)

View File

@@ -0,0 +1,71 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
<a href="https://helmetjs.github.io/">Helmet</a> is a collection of middleware functions for securing Express apps. It sets various HTTP headers to guard against common web vulnerabilities.
This query detects Helmet misconfigurations that can lead to security vulnerabilities, specifically:
</p>
<ul>
<li>Disabling frame protection</li>
<li>Disabling Content Security Policy</li>
</ul>
<p>
Content Security Policy (CSP) helps spot and prevent injection attacks such as Cross-Site Scripting (XSS).
Removing frame protections exposes an application to attacks such as clickjacking, where an attacker can trick a user into clicking on a button or link on a targeted page when they intended to click on the page carrying out the attack.
</p>
<p>
Users of the query can extend the set of required Helmet features by adding additional checks for them, using CodeQL <a href="https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-javascript/">data extensions</a> in a <a href="https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack">CodeQL model pack</a>. See <code>CUSTOMIZING.md</code> in the query source for more information.
</p>
</overview>
<recommendation>
<p>
To help mitigate these vulnerabilities, ensure that the following Helmet functions are not disabled, and are configured appropriately to your application:
</p>
<ul>
<li><code>frameguard</code></li>
<li><code>contentSecurityPolicy</code></li>
</ul>
</recommendation>
<example>
<p>
The following code snippet demonstrates Helmet configured in an insecure manner:
</p>
<sample src="examples/helmet_insecure.js" />
<p>
In this example, the defaults are used, which enables frame protection and a default Content Security Policy.
</p>
<sample src="examples/helmet_default.js" />
<p>
You can also enable a custom Content Security Policy by passing an object to the <code>contentSecurityPolicy</code> key. For example, taken from the <a href="https://helmetjs.github.io/#content-security-policy">Helmet docs</a>:
</p>
<sample src="examples/helmet_custom.js" />
</example>
<references>
<li>
<a href="https://helmetjs.github.io/">helmet.js website</a>
</li>
<li>
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">Content Security Policy (CSP) | MDN</a>
</li>
<li>
<a href="https://infosec.mozilla.org/guidelines/web_security">Mozilla Web Security Guidelines</a>
</li>
<li>
<a href="https://developer.mozilla.org/en-US/docs/Web/Security#protect_against_clickjacking">Protect against clickjacking | MDN</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Insecure configuration of Helmet security middleware
* @description The Helmet middleware is used to set security-related HTTP headers in Express applications. This query finds instances where the middleware is configured with important security features disabled.
* @kind problem
* @problem.severity error
* @security-severity 7.0
* @precision high
* @id js/insecure-helmet-configuration
* @tags security
* external/cwe/cwe-693
* external/cwe/cwe-1021
*/
import javascript
import DataFlow
import semmle.javascript.frameworks.ExpressModules
class HelmetProperty extends DataFlow::Node instanceof DataFlow::PropWrite {
ExpressLibraries::HelmetRouteHandler helmet;
HelmetProperty() {
this = helmet.(DataFlow::CallNode).getAnArgument().getALocalSource().getAPropertyWrite()
}
ExpressLibraries::HelmetRouteHandler getHelmet() { result = helmet }
predicate isFalse() { DataFlow::PropWrite.super.getRhs().mayHaveBooleanValue(false) }
string getName() { result = DataFlow::PropWrite.super.getPropertyName() }
predicate isImportantSecuritySetting() {
// read from data extensions to allow enforcing custom settings
// defaults are located in javascript/ql/lib/semmle/frameworks/helmet/Helmet.Required.Setting.model.yml
requiredHelmetSecuritySetting(this.getName())
}
}
extensible predicate requiredHelmetSecuritySetting(string name);
from HelmetProperty helmetProperty, ExpressLibraries::HelmetRouteHandler helmet
where
helmetProperty.isFalse() and
helmetProperty.isImportantSecuritySetting() and
helmetProperty.getHelmet() = helmet
select helmet,
"Helmet security middleware, configured with security setting $@ set to 'false', which disables enforcing that feature.",
helmetProperty, helmetProperty.getName()

View File

@@ -0,0 +1,10 @@
app.use(
helmet({
contentSecurityPolicy: {
directives: {
"script-src": ["'self'", "example.com"],
"style-src": null,
},
},
})
);

View File

@@ -0,0 +1 @@
app.use(helmet());

View File

@@ -0,0 +1,6 @@
const helmet = require('helmet');
app.use(helmet({
frameguard: false,
contentSecurityPolicy: false
}));

View File

@@ -0,0 +1,43 @@
# Extending the library list of untrusted sources and domains
You can expand the list of untrusted domains in the CodeQL library used by the `js/functionality-from-untrusted-source` and `js/functionality-from-untrusted-domain` queries using [CodeQL data extensions](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-javascript/).
This allows you to add additional domains to warn users about and to require Subresource Integrity (SRI) checks on specific content delivery network (CDN) hostnames.
For example, this YAML model can be used inside a CodeQL model pack to alert on uses of `example.com` in imported functionality, extending the `js/functionality-from-untrusted-domain` query:
```yaml
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: untrustedDomain
data:
- ["example.com"]
```
To add new hostnames that always require SRI checking, this YAML model can be used to require SRI on `cdn.example.com`, extending the `js/functionality-from-untrusted-source` query:
```yaml
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: isCdnDomainWithCheckingRequired
data:
- ["cdn.example.com"]
```
You would create a model pack with this information using metadata similar to that in the example below:
```yaml
name: my-org/javascript-untrusted-functionality-model-pack
version: 1.0.0
extensionTargets:
codeql/java-all: '*'
dataExtensions:
- models/**/*.yml
```
## References
- [Customizing library models for javascript](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-javascript/)
- [Creating and working with CodeQL packs](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack)

View File

@@ -0,0 +1,102 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Content Delivery Networks (CDNs) are used to deliver content to users quickly and efficiently.
However, they can change hands or be operated by untrustworthy owners, risking the security of the sites that use them.
Some CDN domains are operated by entities that have used CDNs to deliver malware, which this query identifies.
</p>
<p>
For example, <code>polyfill.io</code> was a popular JavaScript CDN,
used to support new web browser standards on older browsers.
In February 2024 the domain was sold, and in June 2024 it was publicised that the domain
had been used to serve malicious scripts. It was taken down later in that month, leaving a window
where sites that used the service could have been compromised.
The same operator runs several other CDNs, undermining trust in those too.
</p>
<p>
Including a resource from an untrusted source or using an untrusted channel may
allow an attacker to include arbitrary code in the response.
When including an external resource (for example, a <code>script</code> element) on a page,
it is important to ensure that the received data is not malicious.
</p>
<p>
Even when <code>https</code> is used, an untrustworthy operator might deliver malware.
</p>
<p>
See the [`CUSTOMIZING.md`](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-830/CUSTOMIZING.md) file in the source code for this query for information on how to extend the list of untrusted domains used by this query.
</p>
</overview>
<recommendation>
<p>
Carefully research the ownership of a Content Delivery Network (CDN) before using it in your application.
</p>
<p>
If you find code that originated from an untrusted domain in your application, you should review your logs to check for compromise.
</p>
<p>
To help mitigate the risk of including a script that could be compromised in the future, consider whether you need to
use polyfill or another library at all. Modern browsers do not require a polyfill, and other popular libraries were made redundant by enhancements to HTML 5.
</p>
<p>
If you do need a polyfill service or library, move to using a CDN that you trust.
</p>
<p>
When you use a <code>script</code> or <code>link</code> element,
you should check for <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">subresource integrity (SRI)</a>,
and pin to a hash of a version of the service that you can trust (for example, because you have audited it for security and unwanted features).
A dynamic service cannot be easily used with SRI. Nevertheless,
it is possible to list multiple acceptable SHA hashes in the <code>integrity</code> attribute,
such as hashes for the content required for the major browsers used by your users.
</p>
<p>
You can also choose to self-host an uncompromised version of the service or library.
</p>
</recommendation>
<example>
<p>
The following example loads the Polyfill.io library from the <code>polyfill.io</code> CDN. This use was open to malicious scripts being served by the CDN.
</p>
<sample src="polyfill-compromised.html" />
<p>
Instead, load the Polyfill library from a trusted CDN, as in the next example:
</p>
<sample src="polyfill-trusted.html" />
<p>
If you know which browsers are used by the majority of your users, you can list the hashes of the polyfills for those browsers:
</p>
<sample src="polyfill-sri.html" />
</example>
<references>
<li>Sansec: <a href="https://sansec.io/research/polyfill-supply-chain-attack">Polyfill supply chain attack hits 100K+ sites</a></li>
<li>Cloudflare: <a href="https://cdnjs.cloudflare.com/polyfill">Upgrade the web. Automatically. Delivers only the polyfills required by the user's web browser.</a></li>
<li>Fastly: <a href="https://community.fastly.com/t/new-options-for-polyfill-io-users/2540">New options for Polyfill.io users</a></li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Polyfill_(programming)">Polyfill (programming)</a></li>
<li>MDN Web Docs: <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">Subresource Integrity</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name Untrusted domain used in script or other content
* @description Using a resource from an untrusted or compromised domain makes your code vulnerable to receiving malicious code.
* @kind problem
* @security-severity 7.2
* @problem.severity error
* @id js/functionality-from-untrusted-domain
* @precision high
* @tags security
* external/cwe/cwe-830
*/
import javascript
import semmle.javascript.security.FunctionalityFromUntrustedSource
from AddsUntrustedUrl s
where isUrlWithUntrustedDomain(s.getUrl())
select s, "Content loaded from untrusted domain with no integrity check."

View File

@@ -28,11 +28,21 @@
</p>
<p>
Subresource integrity checking is commonly recommended when importing a fixed version of
Subresource integrity (SRI) checking is commonly recommended when importing a fixed version of
a library - for example, from a CDN (content-delivery network). Then, the fixed digest
of that version of the library can easily be added to the <code>script</code> element's
<code>integrity</code> attribute.
</p>
<p>
A dynamic service cannot be easily used with SRI. Nevertheless,
it is possible to list multiple acceptable SHA hashes in the <code>integrity</code> attribute,
such as those for the content generated for major browers used by your users.
</p>
<p>
See the [`CUSTOMIZING.md`](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-830/CUSTOMIZING.md) file in the source code for this query for information on how to extend the list of hostnames required to use SRI by this query.
</p>
</overview>
<recommendation>

View File

@@ -12,158 +12,10 @@
*/
import javascript
/** 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();
}
module StaticCreation {
/** Holds if `host` is an alias of localhost. */
bindingset[host]
predicate isLocalhostPrefix(string host) {
host.toLowerCase()
.regexpMatch([
"(?i)localhost(:[0-9]+)?/.*", "127.0.0.1(:[0-9]+)?/.*", "::1/.*", "\\[::1\\]:[0-9]+/.*"
])
}
/** Holds if `url` is a url that is vulnerable to a MITM attack. */
bindingset[url]
predicate isUntrustedSourceUrl(string url) {
exists(string hostPath | hostPath = url.regexpCapture("(?i)http://(.*)", 1) |
not isLocalhostPrefix(hostPath)
)
}
/** Holds if `url` refers to a CDN 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("(?i)^https?://" +
[
"code\\.jquery\\.com", //
"cdnjs\\.cloudflare\\.com", //
"cdnjs\\.com" //
] + "/.*\\.js$")
}
/** A script element that refers to untrusted content. */
class ScriptElementWithUntrustedContent extends AddsUntrustedUrl instanceof HTML::ScriptElement {
ScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | super.getIntegrityDigest() = digest) and
isUntrustedSourceUrl(super.getSourcePath())
}
override string getProblem() { result = "Script loaded using unencrypted connection." }
}
/** A script element that refers to untrusted content. */
class CdnScriptElementWithUntrustedContent extends AddsUntrustedUrl, HTML::ScriptElement {
CdnScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
isCdnUrlWithCheckingRequired(this.getSourcePath())
}
override string getProblem() {
result = "Script loaded from content delivery network with no integrity check."
}
}
/** An iframe element that includes untrusted content. */
class IframeElementWithUntrustedContent extends AddsUntrustedUrl instanceof HTML::IframeElement {
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(super.getSourcePath()) }
override string getProblem() { result = "Iframe loaded using unencrypted connection." }
}
}
module DynamicCreation {
/** Holds if `call` creates a tag of kind `name`. */
predicate isCreateElementNode(DataFlow::CallNode call, string name) {
call = DataFlow::globalVarRef("document").getAMethodCall("createElement") and
call.getArgument(0).getStringValue().toLowerCase() = name
}
DataFlow::Node getAttributeAssignmentRhs(DataFlow::CallNode createCall, string name) {
result = createCall.getAPropertyWrite(name).getRhs()
or
exists(DataFlow::InvokeNode inv | inv = createCall.getAMemberInvocation("setAttribute") |
inv.getArgument(0).getStringValue() = name and
result = inv.getArgument(1)
)
}
/**
* Holds if `createCall` creates a `<script ../>` element which never
* has its `integrity` attribute set locally.
*/
predicate isCreateScriptNodeWoIntegrityCheck(DataFlow::CallNode createCall) {
isCreateElementNode(createCall, "script") and
not exists(getAttributeAssignmentRhs(createCall, "integrity"))
}
DataFlow::Node urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker t) {
t.start() and result.getStringValue().regexpMatch("(?i)http:.*")
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())
}
/** Holds if `sink` is assigned to the attribute `name` of any HTML element. */
predicate isAssignedToSrcAttribute(string name, DataFlow::Node sink) {
exists(DataFlow::CallNode createElementCall |
sink = getAttributeAssignmentRhs(createElementCall, "src") and
(
name = "script" and
isCreateScriptNodeWoIntegrityCheck(createElementCall)
or
name = "iframe" and
isCreateElementNode(createElementCall, "iframe")
)
)
}
class IframeOrScriptSrcAssignment extends AddsUntrustedUrl {
string name;
IframeOrScriptSrcAssignment() {
name = ["script", "iframe"] and
exists(DataFlow::Node n | n.asExpr() = this |
isAssignedToSrcAttribute(name, n) and
n = urlTrackedFromUnsafeSourceLiteral()
)
}
override string getProblem() {
name = "script" and result = "Script loaded using unencrypted connection."
or
name = "iframe" and result = "Iframe loaded using unencrypted connection."
}
}
}
import semmle.javascript.security.FunctionalityFromUntrustedSource
from AddsUntrustedUrl s
// do not alert on explicitly untrusted domains
// another query can alert on these, js/functionality-from-untrusted-domain
where not isUrlWithUntrustedDomain(s.getUrl())
select s, s.getProblem()

View File

@@ -0,0 +1,9 @@
<html>
<head>
<title>Polyfill.io demo</title>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js" crossorigin="anonymous"></script>
</head>
<body>
...
</body>
</html>

View File

@@ -0,0 +1,9 @@
<html>
<head>
<title>Polyfill demo - Cloudflare hosted with pinned version (with integrity checking for a *very limited* browser set - just an example!)</title>
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?version=4.8.0" integrity="sha384-i0IGVuZBkKZqwXTD4CH4kcksIbFx7WKFMdxN8zUhLFHpLdELF0ym0jxa6UvLhW8/ sha384-3d4jRKquKl90C9aFG+eH4lPJmtbPHgACWHrp+VomFOxF8lzx2jxqeYkhpRg18UWC" crossorigin="anonymous"></script>
</head>
<body>
...
</body>
</html>

View File

@@ -0,0 +1,9 @@
<html>
<head>
<title>Polyfill demo - Cloudflare hosted with pinned version (but no integrity checking, since it is dynamically generated)</title>
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?version=4.8.0" crossorigin="anonymous"></script>
</head>
<body>
...
</body>
</html>