rustjail
b01lersc
Task: a remote Rust jail compiles attacker code under a harsh substring blacklist, hiding the flag as a randomized file in the working directory. Solution: use test::TestOpts.logfile to reach safe PathBuf procfs primitives, find the parent shell's open runner.sh FD, then overwrite the script so dash resumes into `cat flag_*.txt` at the old EOF offset.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
rustjail — b01lers CTF 2026
Description
The original organizer prompt was not preserved in the provided files; the challenge was solved through the remote Rust jail service and local notes.
The service accepted a Rust payload over ncat --ssl, wrote it into src/main.rs, and executed cargo run with nightly 2025-09-25 and edition 2024. The goal was to escape the restrictions, discover the randomized flag_<32hex>.txt in the temporary working directory, and print its contents.
Overview / Challenge Setup
At runtime the working directory looked like /tmp/tmp_x/src and contained:
Cargo.tomlrunner.shwrapper.pyget_payload.pycargo_home/src/main.rsflag_<32hex>.txt
The filter was a simple Python substring blacklist over the Rust source. These raw substrings were banned:
unsafe, path, flag, std, alloc, core, include, impl, concat, macro, <, >, '
This is much weaker than an AST-based filter. It blocks exact text, not semantics, so string escapes like "\x66lag_*.txt" still produce the forbidden string at runtime.
Restriction Analysis
The obvious read-file routes were cut off:
std::fs::*was impossible becausestdwas blacklisted.- FFI through
libc::openwas blocked because edition 2024 requiresunsafe extern, andunsafewas blacklisted. include_*and#[path]were also blocked directly.
The important positive discovery was that extern crate test; and #![feature(test)] still worked. That gave access to test::TestOpts, test::TestDescAndFn, and test::test_main.
Two key primitives came out of that:
- Blacklist bypass inside strings. Raw source checks can be dodged with escapes such as
"\x66lag_*.txt". - Indirect
PathBufconstruction.test::TestOpts.logfilehas typeOption<PathBuf>, so writing
let p = test::TestOpts { logfile: Some("/proc/self/exe".into()), ..opts }.logfile.unwrap();
produced a safe path object without ever writing banned tokens like std or path in source. From there, safe methods such as read_link, read_dir, join, and metadata became available.
...
$ grep --similar
Similar writeups
- [misc][free]blazinglyfast— b01lersc
- [web][free]Prison Pipeline— hackthebox_business_ctf_2024
- [pwn][Pro]cat /flag under seccomp— spbctf
- [misc][free]build-a-builtin-revenge— b01lersc
- [misc][Pro]HashCashSlash— 0xl4ugh