-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JS: Dynamic import as code injection sink #14293
JS: Dynamic import as code injection sink #14293
Conversation
Hi, I wanted to add two sinks, one of the sinks should have a |
@amammad Not sure flow labels are needed for this use case. Can you try removing them and checking if the taint flows through the string concatenations and URL construction (keeping the existing flow step)? |
@pwntester I'm using labels because dynamic import doesn't need a URL construction but worker needs. |
Does the Worker constructor accept either a String or an URL and only the later is vulnerable? If thats the case, you need the Flow labels and affixing a label on the URL constructor that you can later check at the sink. Note that you can ignore that check for the imports sink but enforce it for the Worker one. |
@pwntester |
@amammad Ok, in that case you need to use flow labels as you were doing. Sorry for the confusion. You can start use the regular
|
@pwntester Thank you a lot! I can see the correct results now. you can see the tests and sanitizer also is working for both sinks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks OK for an experimental query, just a note about the query-id.
The solution that you guys figured out which flow-labels is nice 👍
Also, could you do a merge with main
.
The CI hasn't run, and I think that might be because your branch is lacking a lot of progress from main
.
* @problem.severity error | ||
* @security-severity 9.3 | ||
* @precision high | ||
* @id js/code-injection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't have the same query-id as the query in the standard suite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ this is still an issue.
And now the CI is complaining about it.
QHelp previews: javascript/ql/src/Security/CWE-094/CodeInjection.qhelpCode injectionDirectly 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. RecommendationAvoid 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. ExampleThe 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. eval(document.location.href.substring(document.location.href.indexOf("default=")+8)) The following example shows a Pug template being constructed from user input, allowing attackers to run arbitrary code via a payload such as const express = require('express')
var pug = require('pug');
const app = express()
app.post('/', (req, res) => {
var input = req.query.username;
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
input#name.form-control(type='text)
button.btn.btn-primary(type='submit') Submit
p Hello `+ input
var fn = pug.compile(template);
var html = fn();
res.send(html);
}) Below is an example of how to use a template engine without any risk of template injection. The user input is included via an interpolation expression const express = require('express')
var pug = require('pug');
const app = express()
app.post('/', (req, res) => {
var input = req.query.username;
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
input#name.form-control(type='text)
button.btn.btn-primary(type='submit') Submit
p Hello #{username}`
var fn = pug.compile(template);
var html = fn({username: input});
res.send(html);
}) References
javascript/ql/src/experimental/Security/CWE-094-dataURL/CodeInjection.qhelpCode injectionDirectly 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. RecommendationAvoid 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. ExampleThe following example shows part of the page URL being evaluated as JavaScript code on the server. This allows an attacker to provide JavaScript within the URL and send it to server. client side attacks need victim users interaction like clicking on a attacker provided URL. const { Worker } = require('node:worker_threads');
var app = require('express')();
app.post('/path', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
const payloadURL = new URL(payload)
new Worker(payloadURL);
});
app.post('/path2', async function (req, res) {
const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//'
await import(payload)
});
References
javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-094/CodeInjection.qhelpCode injection with additional heuristic sourcesDirectly 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. RecommendationAvoid 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. ExampleThe 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. eval(document.location.href.substring(document.location.href.indexOf("default=")+8)) The following example shows a Pug template being constructed from user input, allowing attackers to run arbitrary code via a payload such as const express = require('express')
var pug = require('pug');
const app = express()
app.post('/', (req, res) => {
var input = req.query.username;
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
input#name.form-control(type='text)
button.btn.btn-primary(type='submit') Submit
p Hello `+ input
var fn = pug.compile(template);
var html = fn();
res.send(html);
}) Below is an example of how to use a template engine without any risk of template injection. The user input is included via an interpolation expression const express = require('express')
var pug = require('pug');
const app = express()
app.post('/', (req, res) => {
var input = req.query.username;
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
input#name.form-control(type='text)
button.btn.btn-primary(type='submit') Submit
p Hello #{username}`
var fn = pug.compile(template);
var html = fn({username: input});
res.send(html);
}) References
|
javascript/ql/src/experimental/Security/CWE-094-dataURL/CodeInjection.ql
Fixed
Show fixed
Hide fixed
The I get the below diff, can you look into that? -| test.js:7:16:7:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:7:16:7:25 | payloadURL | payloadURL depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
-| test.js:10:16:10:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:10:16:10:25 | payloadURL | payloadURL depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
-| test.js:18:18:18:24 | payload | test.js:17:21:17:44 | req.que ... rameter | test.js:18:18:18:24 | payload | payload depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value |
-| test.js:19:18:19:30 | payload + sth | test.js:17:21:17:44 | req.que ... rameter | test.js:19:18:19:30 | payload + sth | payload + sth depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value |
+| test.js:7:16:7:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:7:16:7:25 | payloadURL | payloadURLThis command line depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
+| test.js:10:16:10:25 | payloadURL | test.js:5:21:5:44 | req.que ... rameter | test.js:10:16:10:25 | payloadURL | payloadURLThis command line depends on a $@. | test.js:5:21:5:44 | req.que ... rameter | user-provided value |
+| test.js:18:18:18:24 | payload | test.js:17:21:17:44 | req.que ... rameter | test.js:18:18:18:24 | payload | payloadThis command line depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value |
+| test.js:19:18:19:30 | payload + sth | test.js:17:21:17:44 | req.que ... rameter | test.js:19:18:19:30 | payload + sth | payload + sthThis command line depends on a $@. | test.js:17:21:17:44 | req.que ... rameter | user-provided value | It's from the |
@erik-krogh sorry I should've run tests before pushing changes to remote. |
The format check is failing on You can run |
Dynamic import in nodejs support URLs starts with
data:
which is dangerous.There is another nodejs API that accepts the data: URL which is:
but it needs to be a URL Type as input, not any string value that starts with
data:
, I'm not sure what is the best way to implement it.