02 Cookie Tamper
Flag:
FLAG{cwa2026_18d3e1884e9eca82}The blurb, taken at face value
An internal administrative session manager encodes client state directly into browser cookies using predictable transformations. Alter the serialized identity parameters to elevate privileges to administrator status.
That is a precise specification. It names a mechanism, a location, and an objective: a running application, a cookie carrying serialized state, a role field somewhere inside it, and a tamper that flips it to administrator. Each of those elements is directly testable.
Expected artefact
A login page that sets a cookie. The cookie would be base64 or JSON or a JWT, and somewhere inside
it a field like role, is_admin, privilege or level. The expected procedure would be to decode
it, edit one value, re-encode it, send it back, and hold administrator rights. The "predictable
transformations" wording suggested something as plain as base64 or a hex encoding, which is to say
no signature to defeat.
What the target actually was
The root of ch-002.ctf.africa returns this:
HTTP/2 200
content-type: text/html
last-modified: Mon, 28 Sep 2026 15:09:49 GMT
server: SimpleHTTP/0.6 Python/3.11.16
content-length: 169<html><head><title>002-web-02-cookie-tamper</title></head><body><h2>Cyberweek Africa CTF Target: 002-web-02-cookie-tamper</h2><p>Endpoint operational.</p></body></html>The Server header settles the question of target type. SimpleHTTP/0.6 is
Python's http.server, which serves a directory of files. It has no sessions, no serialization,
no role logic, and no request routing beyond filename lookup. There was no administrative session
manager to tamper with.
Testing the cookie hypothesis
The absence of cookies was confirmed directly rather than assumed. The cookie jar after a request
to the root was empty, not populated with something unreadable, because the response carried no
Set-Cookie header at all. Every reachable path was then checked explicitly:
/ 0 set-cookie headers
/flag.txt 0 set-cookie headers
/index.html 0 set-cookie headersAn application was probed for before concluding that none existed. Twenty-four paths were chosen to cover the vocabulary of the blurb as well as the obvious defaults:
admin admin/ login signin session dashboard panel console app
api api/session auth flag flag.txt .env config.php index.php
index.htm home robots.txt sitemap.xml static/ assets/ js/ app.jsAll returned 404 except index.html and flag.txt. There was no login route, no session route,
no administrative prefix, and nothing that returned a cookie.
The tampering mechanic was therefore not merely awkward to exercise. There was no cookie, no serialized identity parameter, and no privilege field anywhere on the target to alter.
The actual find
flag.txt was already visible in the probe list above, returning 200 with 31 bytes. A flag
served at a guessable path carries the same shape as a decoy, so two properties were established
before accepting it.
First, that flag.txt was the only thing in the directory. Candidates were generated by crossing
eighty session, admin, cookie, identity and infrastructure words against eleven extensions, giving
880 paths in recon/paths.txt:
ffuf -w recon/paths.txt -u https://ch-002.ctf.africa/FUZZ -fs 335,169 -mc all -t 30 -timeout 15 -sFiltering out the 404 body at 335 bytes and the index stub at 169, one hit:
flag.txtSecond, that the value was specific to this challenge rather than a generic placeholder. The
sibling challenge 01-header-sleuth in the same event had already been solved, and its value differs:
ch-001: FLAG{cwa2026_0cef5e17574204d2}
ch-002: FLAG{cwa2026_18d3e1884e9eca82}That per-challenge difference is the discriminating evidence. A shared or decoy value would have been
identical across targets. The sibling account also documents the same deployment pattern, a Python
SimpleHTTP file directory where the blurb described response headers but the value was a plain
file in the body, which is consistent with the behaviour observed here.
The fetch:
HTTP/2 200
content-type: text/plain
date: Fri, 02 Oct 2026 21:21:10 GMT
last-modified: Mon, 28 Sep 2026 15:09:49 GMT
server: SimpleHTTP/0.6 Python/3.11.16
content-length: 31
FLAG{cwa2026_18d3e1884e9eca82}Decoy material in the challenge folder
The supplied test-file.html contains:
<!-- KCR{always_check_the_source} class="hljs-keyword">-->This is not FLAG{ }, so it is not the challenge value, but it is shaped exactly like one and its
filename invites inspection. Two separate traps sit in this challenge, one in the folder and one on
the target, and both are strings that look like values without being the answer.
Methodological assessment
The early phase of this challenge was spent on cookie mechanics before the existence of a cookie had
been established. Reading the Server header, concluding that http.server was serving a
directory, and proceeding directly to file enumeration would have been the correct order; an attempt
was made to reverse a mechanism before confirming that the mechanism was deployed.
flag.txt was nearly discarded for the opposite reason. A flag served at a plain path felt too easy
given a brief that described cookie tampering, so it was treated as a probable decoy while the search
continued for a hidden application that did not exist. That was the more expensive of the two
errors. The applicable standard is not whether a result appears too easy but whether the value is
specific to the target and consistent with the deployment, and by that standard flag.txt qualified
from the moment it was compared against the sibling challenge.
The broader lesson across this event is that the blurbs describe an intended technique
rather than the shipped target. 01-header-sleuth promised response headers and delivered a
static file. This one promises cookie tampering and delivers the same. Reading the server banner
first converts that from a guessing game into a file listing.
Reproducing
The candidate list is in recon/paths.txt, the enumeration hits in recon/ffuf-crossed.txt, the
response headers in evidence/flag-txt-headers.txt, and the value itself in loot/flag.txt.
No browser automation was required; the whole target is three files served over HTTPS.
