Avatar
Part time CTF Player learn every day!!
🌠 I Love Hoshimachi Suisei!! 🌠
🌠 I Love Hoshimachi Suisei!! 🌠

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/

homepage


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)

Use DevTools (Application → Cookies) or Burp to grab data=....

burp

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.

# 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>';

cookie-after

Refresh the page—if showpassword is yes, the site reveals the next credential.

success


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.

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!! 💖☄️✨

Cinema

all tags

GOT-overwrite aboutme aead ai alphanumeric-shellcode apt argc0 argon2 aslr assembly asymmetric atoi automation backbox bandit base64 bash beginner behemoth binary binary-exploitation binary-to-ascii blackarch blind blind-sqli blogging blue-team bruteforce buffer-overflow buffer-overwrite c caesar canary capabilities checksec command-injection commonmark cookie cron crypto cryptography ctf cutter cyberchef cybersecurity defenders detection dev directory-traversal dnf docs drifter ecc education elf env envp exploitation finale forensics format-string formulaone frequency frequency-analysis gcc gdb getchar gfm ghidra github-pages governance gpg guide hashing hkdf http jekyll jmpbuf kali kasiski kdf kernel keylength kramdown krypton lab ld_preload leviathan lfi lfsr linux linux-syscall llmops log-poisoning ltrace manpage markdown maze memcpy mitigations mitmproxy mlops narnia natas networking newline-injection nonce nop-sled nx object-injection obsidian openssl osint overflow overthewire package-manager pacman parrot path path-hijacking pathname php pie pkc pki pointer-trick pqc priv-esc privilege-escalation provable-security pwn pwntools pyshark python race-condition radare2 rag randomness recon red-team redirect relro requests ret2env ret2libc reverse-engineering reversing ricing roadmap rop rot13 rsa scapy security seed seo serialization session setjmp-longjmp setuid shell shellcode smoke soc sockets sprintf sql-injection srop stack-canary stack-overflow strace strcmp strcpy streamcipher strings strncpy strtoul substitution suid suisei symlink symmetric terminal test threat-intel time-based tls troubleshooting tshark type-juggling ubuntu udp utumno vigenere virtualbox virtualization vmware vortex walkthrough web windows wireshark writing wsl x86
dash theme for Jekyll by bitbrain made with