Skip to content

Commit ab65ec4

Browse files
committed
Add Codeql to detect missing 'Message.origin' validation when using postMessage API
1 parent 398678a commit ab65ec4

4 files changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
8+
<p>If you use cross-origin communication between Window objects and do expect to receive messages from other sites, always verify the sender's identity using the origin and possibly source properties of the recevied `MessageEvent`. </p>
9+
10+
<p>Unexpected behaviours, like `DOM-based XSS` could occur, if the event handler for incoming data does not check the origin of the data received and handles the data in an unsafe way.</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Always verify the sender's identity of incoming messages.
16+
</p>
17+
18+
</recommendation>
19+
20+
<example>
21+
<p>In the first example, the `MessageEvent.data` is passed to the `eval` function withouth checking the origin. This means that any window can send arbitrary messages that will be executed in the window receiving the message</p>
22+
<sample src="examples/postMessageNoOriginCheck.js" />
23+
24+
<p> In the second example, the `MessageEvent.origin` is checked against a trusted origin.
25+
<sample src="examples/postMessageWithOriginCheck.js" />
26+
27+
</example>
28+
29+
<references>
30+
31+
<li><a href="https://cwe.mitre.org/data/definitions/20.html">CWE-20: Improper Input Validation</a></li>
32+
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">Window.postMessage()</a></li>
33+
<li><a href="https://portswigger.net/web-security/dom-based/web-message-manipulation">Web-message manipulation</a></li>
34+
<li><a href="https://labs.detectify.com/2016/12/08/the-pitfalls-of-postmessage/">The pitfalls of postMessage</a></li>
35+
36+
</references>
37+
</qhelp>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @name Missing `MessageEvent.origin` verification in `postMessage` handlers
3+
* @description Missing the `MessageEvent.origin` verification in `postMessage` handlers, allows any windows to send arbitrary data to the `MessageEvent` listener.
4+
* This could lead to unexpected behaviour, especially when `MessageEvent.data` is used in an unsafe way.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @precision high
8+
* @id js/missing-postmessageorigin-verification
9+
* @tags correctness
10+
* security
11+
* external/cwe/cwe-20
12+
*/
13+
14+
import javascript
15+
16+
/**
17+
* A call to a `window` event listener
18+
*/
19+
class WindowListeners extends DataFlow::CallNode {
20+
WindowListeners() {
21+
exists(DataFlow::SourceNode source |
22+
source = DataFlow::globalVarRef("window") and
23+
this = source.getAMethodCall("addEventListener")
24+
)
25+
}
26+
}
27+
28+
/**
29+
* A call to a `window` event listener for the `MessageEvent`
30+
*/
31+
class PostMessageListeners extends DataFlow::CallNode {
32+
PostMessageListeners() {
33+
exists(WindowListeners listener |
34+
listener.getArgument(0).mayHaveStringValue("message") and
35+
this = listener
36+
)
37+
}
38+
}
39+
40+
/**
41+
* A function handler for the `MessageEvent`. It is the second argument of a `MessageEvent` listener
42+
*/
43+
class PostMessageHandler extends DataFlow::FunctionNode {
44+
PostMessageHandler() {
45+
exists(PostMessageListeners listener | this = listener.getArgument(1).getAFunctionValue())
46+
}
47+
}
48+
49+
/**
50+
* The `MessageEvent` received by the handler
51+
*/
52+
class PostMessageEvent extends DataFlow::SourceNode {
53+
PostMessageEvent() { exists(PostMessageHandler handler | this = handler.getParameter(0)) }
54+
55+
/**
56+
* Holds if a call to a method on `MessageEvent.origin` or a read of `MessageEvent.origin` is in an `if` statement
57+
*/
58+
predicate isOriginChecked() {
59+
exists(IfStmt ifStmt |
60+
ifStmt = this.getAPropertyRead("origin").getAMethodCall*().asExpr().getEnclosingStmt() or
61+
ifStmt = this.getAPropertyRead("origin").asExpr().getEnclosingStmt()
62+
)
63+
}
64+
}
65+
66+
from PostMessageEvent event, PostMessageHandler handler
67+
where
68+
event = handler.getParameter(0) and
69+
not event.isOriginChecked()
70+
select "The `MessageEvent.origin` property is not checked.", event, handler
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function postMessageHandler(event) {
2+
let origin = event.origin.toLowerCase();
3+
4+
console.log(origin)
5+
// BAD: the origin property is not checked
6+
eval(event.data);
7+
}
8+
9+
window.addEventListener('message', postMessageHandler, false);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function postMessageHandler(event) {
2+
console.log(event.origin)
3+
// GOOD: the origin property is checked
4+
if (event.origin === 'www.example.com') {
5+
// do something
6+
}
7+
}
8+
9+
window.addEventListener('message', postMessageHandler, false);

0 commit comments

Comments
 (0)