HI Cyber Con CTF 2026. Binary exploitation.

LetMeOut

A calculator that allows three letters. The lab walked out of it with one fullwidth underscore.

Published 2 November 2026. Challenge by Hidden Investigations.

The brief

This is just a calculator, try {{ 1+1 }}. The source is attached.

  • letmeout.zip, 3 KB

In short

A Jinja2 sandbox jail that accepts only ASCII templates of at most 170 characters with three different letters. A custom filter normalizes text after the sandbox checks it, so a fullwidth underscore carries __globals__ past the check and breakpoint() opens a shell in the flag directory.

A calculator with three gates

The service renders any template you send in a Jinja2 sandbox with every global and filter removed. A template must be plain ASCII, at most 170 characters long and use no more than three different letters.

Ten names, one late filter

Each connection keeps two filters, named at random from ten words, buff to tuff. Both return text that NFKC normalizes, one at once and one only when used. A short probe through each name finds the late one.

Checked before, resolved after

The sandbox refuses names that start with an underscore and checks the raw text, but it looks up the normalized one. A fullwidth low line, U+FF3F, passes the check and turns into a plain underscore after it.

Three letters are enough

Every filter name ends in uff, and U+FF3F is written \uff3f, so f and u come free. The filter adds one more letter and every other letter becomes an octal escape. The live payload was 157 characters.

From format to a debugger

''['format'] gives the sandbox's own format wrapper. Its __globals__ is the jinja2.sandbox module, where __builtins__ is a plain dict key with no check. Calling breakpoint() from there opens a pdb shell.

Four files, one flag

The shell starts in the flag directory. It holds four files with random hex names, and three are decoys that read not the flag you're looking for. The fourth held the flag, the same on three connections.

The insight

The sandbox checked a name before normalization and used it after, so one character that folds into an underscore was enough.

The flag

hiCTF{__^_^__n0rm4l1z3_b3f0r3_y0u_tru5t__>_<___f57d17bd05}

Loading