OverTheWire Natas Level 10 → 11 tutorial!!
Login
URL: http://natas11.natas.labs.overthewire.org Credentials: natas11:UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk
# Using curl (optional):
curl -u natas11:UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk http://natas11.natas.labs.overthewire.org/
Task
The page says: “Cookies are protected with XOR encryption.”
We’ll recover the XOR key using a known-plaintext trick, then forge a cookie that sets showpassword
to yes
.
A little bit of Theory
index-source.html
shows the flow:
$defaultdata = array("showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
$key = '<censored>';
$out = '';
for ($i=0; $i<strlen($in); $i++) {
$out .= $in[$i] ^ $key[$i % strlen($key)];
}
return $out; // then base64-encoded into the cookie
}
The cookie pipeline is:
json_encode(data) → XOR with key → base64 → Cookie “data=…”
Because we know the plaintext structure:
{"showpassword":"no","bgcolor":"#ffffff"}
and we can read the ciphertext from our cookie, we can apply the XOR identity:
Plaintext ⊕ Key = Ciphertext ⇒ Ciphertext ⊕ Plaintext = Key
Solution (Python)
1) Capture the encrypted cookie
Use DevTools (Application → Cookies) or Burp to grab data=...
.
2) Recover the XOR key
Note: PHP’s
json_encode
has no spaces around:
or,
. We’ll match that exactly.
# derive_key.py
import base64, json
# Paste your cookie value here (URL-decoded, the base64 blob *after* data=)
cipher_b64 = "HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg="
cipher = base64.b64decode(cipher_b64)
# Match PHP's json: no spaces
plain = json.dumps({"showpassword":"no","bgcolor":"#ffffff"}, separators=(',', ':')).encode()
# Repeat-plaintext XOR (same length as cipher) to recover repeating key stream
key_stream = bytes(c ^ p for c, p in zip(cipher, (plain * ((len(cipher)//len(plain))+1))[:len(cipher)]))
# The actual secret key is the smallest repeating period of key_stream.
# Quick & practical: try to guess a short period by eye OR print both forms.
print("Key (hex):", key_stream.hex())
print("Key (ASCII best-guess):", key_stream.decode('latin-1', errors='ignore'))
You’ll see a repeating pattern (yours looked like it repeated every few bytes). Any 1 full period of that pattern is a valid XOR key.
3) Forge a new cookie (showpassword":"yes"
)
# forge_cookie.py
import base64, json, itertools
# Put the repeating key you inferred here (one period). Example:
key_period = b"eDWo" # ← replace with the period you recovered
def xorb(data, key):
return bytes(d ^ k for d, k in zip(data, itertools.cycle(key)))
pt_yes = json.dumps({"showpassword":"yes","bgcolor":"#ffffff"},
separators=(',', ':')).encode()
new_cookie_b64 = base64.b64encode(xorb(pt_yes, key_period)).decode()
print(new_cookie_b64)
Set the forged cookie in DevTools (Application → Cookies → data
), or via console:
document.cookie = 'data=' + '<PASTE_BASE64_FROM_SCRIPT>';
Refresh the page—if showpassword
is yes, the site reveals the next credential.
Password
yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB
Troubleshooting
- Gibberish key output? Make sure the plaintext JSON exactly matches PHP’s format (use
separators=(',', ':')
) and that your cookie value is URL-decoded before base64 decoding. - Key looks longer than expected? That’s fine; identify the shortest repeating period (what repeats over and over) and use that as your
key_period
. -
No password after setting cookie?
- Confirm the cookie name is
data
. - Ensure it’s just the base64 string (no
data=
prefix inside the value). - Clear old cookies and set again, then refresh.
- Confirm the cookie name is
Nice! 🎉 You performed a classic known-plaintext attack on a repeating-key XOR cookie and forged your way to natas12.
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨