PostMessage Vulnerabilities - J2EEScan – Javascript postMessage Handler Detected
23rd Feb 2022Nếu bạn không muốn nhận tin nhắn từ các trang khác, đừng thêm bất kỳ trình nghe sự kiện nào cho các sự kiện tin nhắn. Đây là một cách chứng minh hoàn toàn đánh lừa để tránh các vấn đề về bảo mật. Nếu bạn muốn nhận tin nhắn từ các trang khác, hãy luôn xác minh danh tính của người gửi bằng cách sử dụng nguồn gốc và có thể là thuộc tính nguồn. Bất kỳ cửa sổ nào (bao gồm, ví dụ: http://evil.example.com) đều có thể gửi tin nhắn đến bất kỳ cửa sổ nào khác và bạn không có gì đảm bảo rằng người gửi không xác định sẽ không gửi tin nhắn độc hại. Tuy nhiên, đã xác minh danh tính, bạn vẫn phải luôn xác minh cú pháp của tin nhắn nhận được. Nếu không, một lỗ hổng bảo mật trên trang web mà bạn tin cậy để chỉ gửi các tin nhắn đáng tin cậy có thể mở ra một lỗ hổng tập lệnh giữa các trang web trong trang web của bạn. Luôn chỉ định nguồn gốc đích chính xác, không phải *, khi bạn sử dụng postMessage để gửi dữ liệu đến các cửa sổ khác. Một trang web độc hại có thể thay đổi vị trí của cửa sổ mà bạn không biết và do đó nó có thể chặn dữ liệu được gửi bằng postMessage.
This is the first part of a two series of articles about postMessage vulnerabilities. This part is an introduction to what is a postMessage, basic exploitation, detection and mitigation. The second part is an analysis of real cases reported in Bug Bounty scenarios.
What is a postMessage?
According to Mozilla:
The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
Let’s see an example.
Supose we have a main website (1.html) that communicates with another website (2.html). In the second website there is a back button that changes when the navigation in the first website changes. For example, in website1 we navigate to “changed.html”, then the back button in website2 points to “changed.html”. To do that, postMessage is used and sends the value of website1 to website2.
The code in 1.html is the following:
<!DOCTYPE html> <html> <head> <title>Website 1</title> <meta charset="utf-8" /> <script> var child; function openChild() {child = window.open('2.html', 'popup', 'height=300px, width=500px'); } function sendMessage(){ let msg={url : "changed.html"}; // In production, DO NOT use '*', use toe target domain child.postMessage(msg,'*')// child is the targetWindow child.focus(); }</script> </head> <body> <form> <fieldset> <input type='button' id='btnopen' value='Open child' onclick='openChild();' /> <input type='button' id='btnSendMsg' value='Send Message' onclick='sendMessage();' /> </fieldset> </form> </body> </html>
There are two buttons:
The first one opens a popup containing 2.html through openChild() function
The second one sends a message through sendMessage() function. To do that a message is set defining msg variable and then calling postMessage(msg,'*').
The code in 2.html is the following:
<!DOCTYPE html> <html> <head> <title>Website 2</title> <meta charset="utf-8" /> <script> // Allow window to listen for a postMessage window.addEventListener("message", (event)=>{ // Normally you would check event.origin // To verify the targetOrigin matches // this window's domain document.getElementById("redirection").href=`${event.data.url}`; // event.data contains the message sent });function closeMe() { try {window.close(); } catch (e) { console.log(e) } try {self.close(); } catch (e) { console.log(e) }} </script> </head> <body> <form> <h1>Recipient of postMessage</h1> <fieldset> <a type='text' id='redirection' href=''>Go back</a> <input type='button' id='btnCloseMe' value='Close me' onclick='closeMe();' /> </fieldset> </form> </body> </html>
There is a button and a link:
- The link handles the back redirection. The href field changes according the data received with the listener window.addEventListener("message", (event). After receiving the message, the data in the event is read from event.data.url and passed to href.
- The button closes the window calling the function closeMe()
A basic vulnerability
PostMessages if are not implemented correctly could lead to information disclosure or cross-site scripting vulnerabilities (XSS).
In this case, 2.html is expecting a message without validating the origin, therefore we could host a webpage 3.html that will load 2.html as an iframe and the invoke the postMessage() function to manipulate the href value.
<!DOCTYPE html> <html> <head> <title>XSS PoC</title> <meta charset="utf-8" /> </head> <body> <iframe id="frame" src="2.html" ></iframe> <script> let msg={url : "javascript:prompt(1)"}; var iFrame = document.getElementById("frame") iFrame.contentWindow.postMessage(msg, '*'); </script> </body> </html>
In this example, the malicious msg variable contains the data {url : "javascript:prompt(1)"};, that will be send to 2.html. 2.html after processing, will change the value in the <a href to the value of the msg.url. An iframe is used to load in the same website the attack scenario. When a user clicks the Go back link, a XSS will be executed.
This is a simple case, in PostMessage Vulnerabilities. Part II real case scenarios will be analyzed.
Mitigations
According to Mozilla:
- If you do not expect to receive messages from other sites, do not add any event listeners for message events. This is a completely foolproof way to avoid security problems.
- If you do expect to receive messages from other sites, always verify the sender’s identity using the origin and possibly source properties. Any window can send a message to any other window, and you have no guarantees that an unknown sender will not send malicious messages. Having verified identity, however, you still should always verify the syntax of the received message. Otherwise, a security hole in the site you trusted to send only trusted messages could then open a cross-site scripting hole in your site.
- Always specify an exact target origin, not *, when you use postMessage to send data to other windows. A malicious site can change the location of the window without your knowledge, and therefore it can intercept the data sent using postMessage.
In the previous scenario, the following code should be modified in 1.html:
child.postMessage(msg,'*')
to
child.postMessage(msg,'2.html')
And in 2.html:
window.addEventListener("message", (event)=>{ ... }
To:
window.addEventListener("message", (event)=>{ if (event.origin !== "http://safe.com") return; ... }
Detection
The way to detect postMessage vulnerabilities is reading JavaScript code. There is not an easy automated tool to help with this because when a listener is defined, the event data flow must be followed to analyze if the code ends in a vulnerable function. Anyways, I recommend two ways to detect the function calls:
With J2EEScan, from git repository, not BApp Store because I think is not updated:
- Github - J2EEScan
With BurpBounty, defining a set of Passive response strings searching for keywords like: postMessage , addEventListener("message, .on("message".
- Github - Burp Bounty
Add new comment