Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2239f86

Browse files
committed
JS: add query MethodNameInjection
1 parent bc3b983 commit 2239f86

8 files changed

Lines changed: 260 additions & 0 deletions

File tree

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
| Enabling Node.js integration for Electron web content renderers (`js/enabling-electron-renderer-node-integration`) | security, frameworks/electron, external/cwe/cwe-094 | Highlights Electron web content renderer preferences with Node.js integration enabled, indicating a violation of [CWE-94](https://cwe.mitre.org/data/definitions/94.html). Results are not shown on LGTM by default. |
2525
| File data in outbound network request | security, external/cwe/cwe-200 | Highlights locations where file data is sent in a network request. Results are not shown on LGTM by default. |
2626
| Host header poisoning in email generation | security, external/cwe/cwe-640 | Highlights code that generates emails with links that can be hijacked by HTTP host header poisoning, indicating a violation of [CWE-640](https://cwe.mitre.org/data/definitions/640.html). Results shown on LGTM by default. |
27+
| Method name injection (`js/method-name-injection` ) | security, external/cwe/cwe-094 | Highlights code that invokes a user-controlled method on an object with unsafe methods. |
2728
| Replacement of a substring with itself (`js/identity-replacement`) | correctness, security, external/cwe/cwe-116 | Highlights string replacements that replace a string with itself, which usually indicates a mistake. Results shown on LGTM by default. |
2829
| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. |
2930
| Unclear precedence of nested operators (`js/unclear-operator-precedence`) | maintainability, correctness, external/cwe/cwe-783 | Highlights nested binary operators whose relative precedence is easy to misunderstand. Results shown on LGTM by default. |
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Invoking a user-controlled method on certain objects can lead to invocation of unsafe functions,
9+
such as <code>eval</code> or the <code>Function</code> constructor. In particular, the global object
10+
contains the <code>eval</code> function, and any function object contains the <code>Function</code> constructor
11+
in its <code>constructor</code> property.
12+
</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>
17+
Avoid invoking user-controlled methods on the global object or on any function object.
18+
</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>
23+
In the following example, a user-controlled string is used as the method name on a cross-window message handler.
24+
In this case, a malicious website can embed the page in an iframe, and execute arbitary code in the page by
25+
sending a message to it with <code>name</code> set to <code>eval</code>.
26+
</p>
27+
28+
<sample src="examples/MethodNameInjection.js" />
29+
</example>
30+
31+
<references>
32+
<li>
33+
OWASP:
34+
<a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.
35+
</li>
36+
<li>
37+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Function_properties">Global functions</a>.
38+
</li>
39+
<li>
40+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function">Function constructor</a>.
41+
</li>
42+
</references>
43+
</qhelp>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @name Method name injection
3+
* @description Invoking user-controlled methods on a arbitrary objects can lead to remote code execution.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @precision high
7+
* @id js/method-name-injection
8+
* @tags security
9+
* external/cwe/cwe-094
10+
*/
11+
import javascript
12+
import semmle.javascript.security.dataflow.MethodNameInjection::MethodNameInjection
13+
import DataFlow::PathGraph
14+
15+
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
16+
where cfg.hasFlowPath(source, sink)
17+
select sink, source, sink, "Invocation of method derived from $@ may lead to remote code execution.", source.getNode(), "user-controlled value"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
window.addEventListener("message", (ev) => {
2+
let message = JSON.parse(ev.data);
3+
window[message.name](message.payload);
4+
});
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about method invocations
3+
* with a user-controlled method name.
4+
*/
5+
6+
import javascript
7+
import semmle.javascript.frameworks.Express
8+
9+
module MethodNameInjection {
10+
private import DataFlow::FlowLabel
11+
12+
/**
13+
* A data flow source for method name injection.
14+
*/
15+
abstract class Source extends DataFlow::Node {
16+
/**
17+
* Gets the flow label relevant for this source.
18+
*/
19+
DataFlow::FlowLabel getFlowLabel() {
20+
result = data()
21+
}
22+
}
23+
24+
/**
25+
* A data flow sink for method name injection.
26+
*/
27+
abstract class Sink extends DataFlow::Node {
28+
/**
29+
* Gets the flow label relevant for this sink
30+
*/
31+
abstract DataFlow::FlowLabel getFlowLabel();
32+
}
33+
34+
/**
35+
* A sanitizer for method name injection.
36+
*/
37+
abstract class Sanitizer extends DataFlow::Node { }
38+
39+
/**
40+
* Gets the flow label describing values that may refer to an unsafe
41+
* function as a result of an attacker-controlled property name.
42+
*/
43+
UnsafeFunction unsafeFunction() { any() }
44+
private class UnsafeFunction extends DataFlow::FlowLabel {
45+
UnsafeFunction() { this = "UnsafeFunction" }
46+
}
47+
48+
/**
49+
* A taint-tracking configuration for reasoning about method name injection.
50+
*/
51+
class Configuration extends TaintTracking::Configuration {
52+
Configuration() { this = "RemotePropertyInjection" }
53+
54+
override predicate isSource(DataFlow::Node source) {
55+
source instanceof Source
56+
}
57+
58+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
59+
sink.(Sink).getFlowLabel() = label
60+
}
61+
62+
override predicate isSanitizer(DataFlow::Node node) {
63+
super.isSanitizer(node) or
64+
node instanceof Sanitizer
65+
}
66+
67+
/**
68+
* Holds if a property of the given object is an unsafe function.
69+
*/
70+
predicate isUnsafeBaseObject(DataFlow::SourceNode node) {
71+
// eval an friends can be accessed from the global object.
72+
node = DataFlow::globalObjectRef()
73+
or
74+
// 'constructor' property leads to the Function constructor.
75+
node.analyze().getAValue() instanceof AbstractCallable
76+
or
77+
// Assume that a value that is invoked can refer to a function.
78+
exists (node.getAnInvocation())
79+
}
80+
81+
/**
82+
* Holds if the `node` is of form `Object.create(null)` and so it has no prototype.
83+
*/
84+
predicate isPrototypeLessObject(DataFlow::MethodCallNode node) {
85+
node = DataFlow::globalVarRef("Object").getAMethodCall("create") and
86+
node.getArgument(0).asExpr() instanceof NullLiteral
87+
}
88+
89+
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel, DataFlow::FlowLabel dstlabel) {
90+
// Reading a property of the global object or of a function
91+
exists (DataFlow::PropRead read |
92+
isUnsafeBaseObject(read.getBase().getALocalSource()) and
93+
src = read.getPropertyNameExpr().flow() and
94+
dst = read and
95+
srclabel = taint() and
96+
dstlabel = unsafeFunction())
97+
or
98+
// Reading a chain of properties from any object with a prototype can lead to Function
99+
exists (PropertyProjection proj |
100+
not isPrototypeLessObject(proj.getObject().getALocalSource()) and
101+
src = proj.getASelector() and
102+
dst = proj and
103+
srclabel = taint() and
104+
dstlabel = unsafeFunction())
105+
}
106+
}
107+
108+
/**
109+
* A source of remote user input, considered as a source for method name injection.
110+
*/
111+
class RemoteFlowSourceAsSource extends Source {
112+
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
113+
}
114+
115+
/**
116+
* The page URL considered as a flow source for method name injection.
117+
*/
118+
class DocumentUrlAsSource extends Source {
119+
DocumentUrlAsSource() { isDocumentURL(asExpr()) }
120+
}
121+
122+
/**
123+
* A function invocation of an unsafe function, as a sink for remote method name injection.
124+
*/
125+
class CalleeAsSink extends Sink {
126+
CalleeAsSink() {
127+
this = any(DataFlow::InvokeNode node).getCalleeNode()
128+
}
129+
130+
override DataFlow::FlowLabel getFlowLabel() {
131+
result = unsafeFunction()
132+
}
133+
}
134+
135+
/**
136+
* A binary expression that sanitzes a value for method name injection. That
137+
* is, if a string is prepended or appended to the remote input, an attacker
138+
* cannot access arbitrary properties.
139+
*/
140+
class ConcatSanitizer extends Sanitizer, DataFlow::ValueNode {
141+
142+
override BinaryExpr astNode;
143+
144+
ConcatSanitizer() {
145+
astNode.getAnOperand() instanceof ConstantString
146+
}
147+
}
148+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
nodes
2+
| tst.js:3:37:3:38 | ev |
3+
| tst.js:4:9:4:37 | message |
4+
| tst.js:4:19:4:37 | JSON.parse(ev.data) |
5+
| tst.js:4:30:4:31 | ev |
6+
| tst.js:4:30:4:36 | ev.data |
7+
| tst.js:5:5:5:24 | window[message.name] |
8+
| tst.js:5:12:5:18 | message |
9+
| tst.js:5:12:5:23 | message.name |
10+
| tst.js:6:9:6:28 | window[message.name] |
11+
| tst.js:6:16:6:22 | message |
12+
| tst.js:6:16:6:27 | message.name |
13+
| tst.js:10:5:10:19 | f[message.name] |
14+
| tst.js:10:7:10:13 | message |
15+
| tst.js:10:7:10:18 | message.name |
16+
edges
17+
| tst.js:3:37:3:38 | ev | tst.js:4:30:4:31 | ev |
18+
| tst.js:4:9:4:37 | message | tst.js:5:12:5:18 | message |
19+
| tst.js:4:9:4:37 | message | tst.js:6:16:6:22 | message |
20+
| tst.js:4:9:4:37 | message | tst.js:10:7:10:13 | message |
21+
| tst.js:4:19:4:37 | JSON.parse(ev.data) | tst.js:4:9:4:37 | message |
22+
| tst.js:4:30:4:31 | ev | tst.js:4:30:4:36 | ev.data |
23+
| tst.js:4:30:4:36 | ev.data | tst.js:4:19:4:37 | JSON.parse(ev.data) |
24+
| tst.js:5:12:5:18 | message | tst.js:5:12:5:23 | message.name |
25+
| tst.js:5:12:5:23 | message.name | tst.js:5:5:5:24 | window[message.name] |
26+
| tst.js:6:16:6:22 | message | tst.js:6:16:6:27 | message.name |
27+
| tst.js:6:16:6:27 | message.name | tst.js:6:9:6:28 | window[message.name] |
28+
| tst.js:10:7:10:13 | message | tst.js:10:7:10:18 | message.name |
29+
| tst.js:10:7:10:18 | message.name | tst.js:10:5:10:19 | f[message.name] |
30+
#select
31+
| tst.js:5:5:5:24 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:5:5:5:24 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
32+
| tst.js:6:9:6:28 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:6:9:6:28 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
33+
| tst.js:10:5:10:19 | f[message.name] | tst.js:3:37:3:38 | ev | tst.js:10:5:10:19 | f[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-094/MethodNameInjection.ql
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
let obj = {};
2+
3+
window.addEventListener('message', (ev) => {
4+
let message = JSON.parse(ev.data);
5+
window[message.name](message.payload); // NOT OK - may invoke eval
6+
new window[message.name](message.payload); // NOT OK - may invoke jQuery $ function or similar
7+
window["HTMLElement" + message.name](message.payload); // OK - concatenation restricts choice of methods
8+
9+
function f() {}
10+
f[message.name](message.payload)(); // NOT OK - may acccess Function constructor
11+
12+
obj[message.name](message.payload); // OK - may crash, but no code execution involved
13+
});

0 commit comments

Comments
 (0)