Use this as a quick reference while testing race conditions in labs and authorized assessments.
Core Pattern
check
gap
use
TOCTOU:
Time Of Check
Time Of Use
The application checks one state, then uses another state.
Common Vulnerable Patterns
Filesystem:
exists() then open()
is_file() then open()
access() then open()
stat() then open()
realpath() then later open(original_path)
load file then verify content then reopen path
scan or parse file then render original path
delete then recreate predictable file
scan upload then serve upload
Shell:
test -f "$path" && cat "$path"
[ -e "$file" ] && echo data > "$file"
rm -f /tmp/output
sleep 1
cat input > /tmp/output
Application logic:
check balance then withdraw
check token then consume
check role then act
check stock then purchase
check coupon then redeem
Interesting Locations
/tmp
/var/tmp
/dev/shm
upload staging directories
cache directories
spool directories
backup directories
application work directories
Lab Commands
Normal run:
python3 viewer.py vuln note.txt
Manual swap during processing:
rm -f public/note.txt
ln -s ../private/secret.txt public/note.txt
Reset:
rm -f public/note.txt
cat > public/note.txt <<'EOF'
TITLE: public note
STATUS: approved
BODY:
This report is safe to render.
EOF
Fixed symlink test:
rm -f public/note.txt
ln -s ../private/secret.txt public/note.txt
python3 viewer.py fixed note.txt
Expected:
[Errno 40] Too many levels of symbolic links
Useful Tools
Trace file syscalls:
strace -f -e trace=file ./viewer.py vuln note.txt
Watch filesystem events:
inotifywait -m public
Find symlinks:
find /tmp /var/tmp /dev/shm -type l -ls 2>/dev/null
find public -type l -ls
Monitor Linux processes:
pspy64
Exploit Ideas
Swap a safe file for a symlink:
safe regular file -> symlink to target
Swap content after validation:
benign upload -> malicious upload
Win delete/recreate window:
program removes predictable file
attacker creates symlink with same name
program writes through symlink
Hammer the race:
while true; do
rm -f target
ln -s replacement target 2>/dev/null
done
Defensive Review
Confirm:
- Temporary filenames are unpredictable.
- Safe APIs such as
mkstempare used. - Sensitive files are not created in user-writable directories.
- The application opens once and validates the opened descriptor.
- The application does not validate a path and later reopen the path.
- Symlinks are rejected where appropriate.
O_NOFOLLOWis used for sensitive opens.- Directory permissions prevent untrusted swaps.
- Database state changes use transactions.
- Tokens and coupons are consumed atomically.
- Duplicate requests are handled safely.
Key Fixes
Filesystem:
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
st = os.fstat(fd)
read from fd
Database:
BEGIN
SELECT row FOR UPDATE
validate state
update state
COMMIT
Design:
remove the timing gap
make the operation atomic
validate the object actually used