AquaCrypt
AquaCrypt
Platform: Undutmaning | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-21 | Status: Solved Techniques: robots_txt_enumeration, source_code_leak, python_deobfuscation, rsa_small_key_factorization, pkcs1_signature_forgery
Summary
English summary: A Flask web application for file sharing requires a valid signature to download files. The /files endpoint lists available files including flag and flag.txt. Goal is to bypass the signature verification to download the flag file.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
undutmaning| ID:20260321_undutmaning_aquacrypt - Tags: flask, robots_txt, obfuscation, rsa, source_code_leak, weak_key, pkcs1_v1_5, signature_verification
- Indicators: robots.txt with admin paths, Flask/Werkzeug server, signature parameter required for download, obfuscated Python code with gzip+base64, 128-bit RSA key
- Source:
20260321_undutmaning_aquacrypt.md
Foothold
Vulnerability / Misconfiguration
- Robots_txt_enumeration
- Source_code_leak
- Python_deobfuscation
- Rsa_small_key_factorization
- Pkcs1_signature_forgery
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- robots_txt_enumeration
- source_code_leak
- python_deobfuscation
- rsa_small_key_factorization
- pkcs1_signature_forgery
- Tags: flask, robots_txt, obfuscation, rsa, source_code_leak, weak_key, pkcs1_v1_5, signature_verification
Original Writeup
<details><summary>Click to expand original content</summary>Description
Efter en mängd kreativt omkopplande lyckas de nödställda på CASCADA få tillgång till ytterligare några delar av nätverket. Där hittar Harriet ganska snabbt en webbtjänst som verkar vara till för att ladda ner filer. Vissa av de tillgängliga filerna har intressanta namn, men Harriet behöver hjälp med att komma åt dem.
Kan du hjälpa henne?
Du kan surfa in till tjänsten på: https://undutmaning-aquacrypt.chals.io
English summary: A Flask web application for file sharing requires a valid signature to download files. The /files endpoint lists available files including flag and flag.txt. Goal is to bypass the signature verification to download the flag file.
Analysis
Initial Reconnaissance
The target is a Flask/Werkzeug (3.1.5, Python 3.13.1) web application. The main page shows a "Secure File Sharing" service with a download form requiring a filename and a signature.
robots.txt Discovery
Checking /robots.txt revealed three disallowed paths:
/files_old(404)/admin(accessible!)/staging/(404)
Source Code Leak
The /admin endpoint showed an admin panel with server configuration and a link to /admin/download-app which returned the full Flask application source code.
Obfuscated Verification Function
The source code contained an obfuscated verification function:
a = "H4sIAAAAAAACA+2dO3brMAxE+6zCXZrsKCf730bq96rYJoH5XNeShqLBwWD40ffnY+D3+fUYwZkD..."
import base64, gzip
exec(''.join(map(lambda x: chr(len(x)), eval(gzip.decompress(base64.b64decode(a)).decode('utf-8')))))
The obfuscation technique: a Python list of strings where len(string) maps to the ASCII character code, joined together to form the verification function source.
Weak RSA Key
Deobfuscating the blob revealed a verify(signature, message) function implementing RSA PKCS#1 v1.5 signature verification with an embedded PEM public key:
- Key size: Only 128 bits (trivially factorable)
- Public exponent: e = 3
- Modulus n:REDACTED
Solution
Step 1: Deobfuscate the Verification Function
import base64, gzip
a = "H4sIAAAAAAACA+2dO3brMAxE+6zCXZrsKCf730bq96rYJoH5XNeShqLBwWD40ffnY+D3+fUYwZkD..."
data = gzip.decompress(base64.b64decode(a)).decode('utf-8')
result = ''.join(map(lambda x: chr(len(x)), eval(data)))
print(result)
This reveals the RSA public key embedded in the verification function.
Step 2: Extract and Factor the RSA Key
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from sympy import factorint
# Extract public key parameters
# nREDACTED
# e = 3
# Factor the 128-bit modulus (trivial)
factors = factorint(REDACTED)
# {REDACTED: 1,REDACTED: 1}
p = REDACTED
q =REDACTED
Step 3: Compute Private Key
nREDACTED e = 3 p = REDACTED q =REDACTED phi = (p - 1) * (q - 1) # phiREDACTED882904702279312174776 d = pow(e, -1, phi) # d = 156362387859240733255269801519541449851
Step 4: Forge PKCS#1 v1.5 Signature
import base64
def pkcs1_pad(message: bytes, key_size_bytes: int) -> int:
"""Create PKCS#1 v1.5 signature padding"""
# Format: 0x00 0x01 [0xFF padding] 0x00 [message]
padding_len = key_size_bytes - 3 - len(message)
padded = b'\x00\x01' + (b'\xff' * padding_len) + b'\x00' + message
return int.from_bytes(padded, 'big')
# Sign "flag" filename
message = b"flag"
nREDACTED
d = 156362387859240733255269801519541449851
key_size_bytes = 16 # 128 bits
padded_int = pkcs1_pad(message, key_size_bytes)
signature_int = pow(padded_int, d, n)
signature_bytes = signature_int.to_bytes(key_size_bytes, 'big')
signature_b64 = base64.b64encode(signature_bytes).decode()
print(f"Signature: {signature_b64}")
# Output: pML6OO3r7xqMD8H+1czLKA==
Step 5: Download the Flag
curl 'https://undutmaning-aquacrypt.chals.io/download/flag?signature=pML6OO3r7xqMD8H%2B1czLKA%3D%3D'
Note: flag.txt was a decoy containing just "test". The actual flag was in the file named flag.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR