> For the complete documentation index, see [llms.txt](https://mistx0.gitbook.io/mistx0/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mistx0.gitbook.io/mistx0/write-ups/online/nexhunt-ctf/super-secret-storage.md).

# Super Secret Storage

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

From the sound of the description, it is strongly hinting for a file upload type of challenge, Let's take a look at the main page.

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

A simple page, With a file upload end point.

At first i tried uploading a php file, to test the server's behavior.

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

File type not supported, typical behavior. First thought lets try to bypass, But wait

```
Server: Werkzeug/3.1.4 Python/3.9.25
```

the server is running python, so uploading a php won't help, maybe a python file would help, i tried many ways to bypass that, including changing Content-Type and Mime-Type, Or giving the python file a png header. but all didn't work. wait i didn't upload a valid file yet.

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

it turns out the server only checks the extension. but the uploaded file get uploaded to this path

```
/files/6c8baa7e-555b-4117-ab66-b62b1016c4da/evil.png
```

the question that got in my mind that , can i upload my file in a different category by doing something like filename="/../../../../../test.png"

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

jackpot, we got the an error from the debugger, i really long error, but the things that caught my attention are :

```html
   <script>
      var CONSOLE_MODE = false,
          EVALEX = false,
          EVALEX_TRUSTED = false,
          SECRET = "T3BkEqzo1Cth0zuLytkh";
    </script>
```

We have a secret here for the console , maybe somehow we could get the pin and get RCE from that but i kept looking and wrote that in my notes

I also found this

```html
<pre class="line before"><span class="ws">    </span>uid = str(uuid.uuid4())</pre>

<pre class="line before"><span class="ws">    </span>filepath = os.path.join(UPLOAD_FOLDER, f&#34;{uid}_{filename}&#34;)</pre>

<pre class="line current"><span class="ws">    </span>file.save(filepath)</pre>

```

the server seems to join my input directly when i try to access this path

```python
os.path.join(UPLOAD_FOLDER, f&#34;{uid}_{filename}&#34;)
```

this path is familiar, it looks like the same path i got when i uploaded a valid file.

```
/files/7d959911-7c43-47a0-8693-456aa2ab8401/test.png
```

its probably vulnerable to path traversal , Lets check

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

Finally we now have file read on the backend server, Only objective now is to find the flag, i tried several paths that might contain the flag

```
/files/7d959911-7c43-47a0-8693-456aa2ab8401/../../../../../flag.txt
/files/7d959911-7c43-47a0-8693-456aa2ab8401/../../../../../home/ctf/flag.txt
```

that's weird its not in the root nor the home of the user we have, as habit i like to read the source of the page im working with in that case its probably in /app/main.py

```python
#!/usr/bin/env python
import re
import os
import time
import uuid
from uuid import UUID
from urllib.parse import unquote

from werkzeug.serving import run_simple
from werkzeug.debug import DebuggedApplication
from flask import Flask, Response, request, send_from_directory, render_template, render_template_string, send_file

class Application(DebuggedApplication):
    def check_host_trust(self, environ):
        xff = environ.get("HTTP_X_FORWARDED_FOR", "")
        if "127.0.0.1" in xff:
            return True
        return False

app = Flask(__name__, template_folder="./templates")
app.config["MAX_CONTENT_LENGTH"] = 5 * 1024 * 1024  # 5 MB
app.debug = True

SAFE_FILENAME = re.compile(r'^[A-Za-z0-9._\-]+$')
SAFE_UUID = re.compile(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
ALLOWED_EXTENSIONS = {".pdf", ".png", ".jpg", ".jpeg"}
UPLOAD_FOLDER = "uploads"


os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Ensure DB directory exists




def is_allowed_file(filename):
    # print(os.path.splitext(filename)[1].lower())
    return os.path.splitext(filename)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("upload.html")

    file = request.files["file"]
    filename = file.filename

    if not is_allowed_file(filename):
        return "File type not allowed", 400

    uid = str(uuid.uuid4())
    filepath = os.path.join(UPLOAD_FOLDER, f"{uid}_{filename}")
    file.save(filepath)

    return f"/files/{uid}/{filename}"





@app.route("/files/<uuid>/<path:filename>", strict_slashes=False)
def get_file(uuid, filename):


    full_path = os.path.normpath(os.path.join(UPLOAD_FOLDER, f"{uuid}_{filename}"))

    if not os.path.exists(full_path):
        return "File not found", 404
    if not os.path.isfile(full_path):
        return "Not a regular file", 400
    if not os.access(full_path, os.R_OK):
        return "Permission denied", 403

    try:
        with open(full_path, "rb") as f:
            content = f.read()
        return Response(content, mimetype="application/octet-stream")
    except Exception as e:
        return f"Error reading file: {e}", 500


if __name__ == "__main__":
    debugger = Application(app, evalex=True)
    run_simple("0.0.0.0", 8990, debugger, use_reloader=False)
```

no hint on where the flag could be, but its fun to see why this was vulnerable

```python
@app.route("/files/<uuid>/<path:filename>", strict_slashes=False)
def get_file(uuid, filename):


    full_path = os.path.normpath(os.path.join(UPLOAD_FOLDER, f"{uuid}_{filename}"))

    if not os.path.exists(full_path):
        return "File not found", 404
    if not os.path.isfile(full_path):
        return "Not a regular file", 400
    if not os.access(full_path, os.R_OK):
        return "Permission denied", 403

    try:
        with open(full_path, "rb") as f:
            content = f.read()
        return Response(content, mimetype="application/octet-stream")
    except Exception as e:
        return f"Error reading file: {e}", 500
```

its this part that caused the vulnerability, its not sanitizing the user input, not its checking whether the user left the /files directory, its only checking if the file exist, is it a file or does the backend user have access to it.

back to finding the flag.

Last place i didn't check is the environment variable

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

here we find the flag

```
FLAG=nexus{w3rkz3ug_p1n_3xpl01t_v1a_lfi}
```

from what the flag suggests that my solve wasn't the intended solve, but in the end of the day it works.


---

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

```
GET https://mistx0.gitbook.io/mistx0/write-ups/online/nexhunt-ctf/super-secret-storage.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.
