Part Zero: Unpacking the source code and finding the initial sink (TheGrandPew)
After downloading the challenge and running the installer on my windows machine, I headed over to the installation path C:\Users\%username%\AppData\Local\Programs\pbchat and found that this electron app was using asar to bundle the electron code. So I moved the app.asar over to my working directory and used 7zip to extract the contents of the asar file.
1
2
3
4
5
6
7
8
9
10
11
12
app.unpacked/
node_modules/*
assets/*
build/
js/
app.js -> The client side source code
app.js.map
index.html -> Main window html
package.json
main.js -> The main electron process code
preload-chat.js -> Chat window preload
preload.js -> Main window preload
Does setup
After setting up the firebase and all needed to run the electron app, I decided I would open the electron app for the first time.
I then opened up a chat window and had a play around with the messages, tried some simple html injection payloads such as pew but they did not work because proper escaping was in place. From there I decided to open chrome devtools and have a good look at the app.js file which turned out to be webpacked but a map file was also provided so chrome devtools auto provided a webpack directory in the sources with all the app.js source. First look at the code told me this was a ReactJS + Firebase application so I started to use the devtools->sources find feature to search through the codebase fast. My Initial searches where dangerouslySetInnerHTML, innerHTML, outerHTML, replace, … , eval, Function(
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React from "react";
import Arg from "@vunamhung/arg.js";
const URL_REGEX = /((?:[\w]+):(?:\/\/)?(?:(?:[^/\s]+\.?)+)(?:[^#?\s]+)?(?:\?[^#\s]+)?(?:#[^\s]+)?)/gi;
export default function Linkify({ text }) {
const addTracking = (url) => {
const u = new URL(url);
u.search = Arg.stringify({
...Arg.parse(u.search),
utm_source: "pbchat",
utm_content: "link",
});
return u.toString();
};
const parse = (t) => {
return t.split(URL_REGEX).map((part, i) => {
if (part.match(URL_REGEX)) {
const url = addTracking(part);
let checkurl = new URL(url);
if (checkurl.protocol === "https:" && checkurl.hostname.endsWith("www.youtube.com")){
checkurl = "https://www.youtube.com/embed/" + checkurl.searchParams.get("v");
return (
<iframe sandbox="allow-scripts allow-same-origin allow-presentation" key={i} src={checkurl} className="preview mt-3">
</iframe>
);
}
else {
return (
<a key={i} href={url}>
{part}
</a>
);
}
} else {
return <span key={i}>{part}</span>;
}
});
};
return <span>{parse(text || "")}</span>;
}
- was to abuse the lack of validation on the v url parameter to preform a directory traversal on the iframe src to iframe https://www.youtube.com/redirect?u=https://attacker.com/. However the redirect on youtube.com turned into a dead-end because it requires user interaction to procced with the redirect. My Second Idea was to look into the use of Arg.js to add “tracking parameters”, I searched up “@vunamhung/arg.js” on https://snyk.io/vuln and found that the version they where using was vulnerable to prototype pollution so I guess we found our first sink (the iframe) + vulnerability (prototype pollution).
Part One: The search for a prototype pollution gadget (s1r1us)
Ok so now that I have prototype pollution, I need to find a gadget so that I can turn this into a XSS. I firstly had a look for DOM XSS sinks again using the find feature but this time paying close atention to the librarys with the sinks. After looking through the results of those searches I concluded that I must find a prototype pollution in ReactJS, first I tried searching google for a known ReactJS gadget but couldn’t find one :(. The second thing I did was to setup an enviroment where I could easyly change Object.prototype and insert a simple iframe into the document using React, I ended up using jsbin for this.
With that done I did a console.log(<iframe id=pew>pew</iframe>) to dump how React structures these html Objects before ReactDom.Render is called on them. I did because I thought the easyiest way to get xss would be to inject a property into these Objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"$$typeof": Symbol(react.element),
"key":null,
"props": {
"id":"pew"
},
"ref":null,
"type":"iframe",
"_owner":null,
"_store":{
"validated":false
}
}