hash-only-2 picoCTF 2025 Solution

Published: April 2, 2025

Description

The second flaghasher binary lives in /usr/local/bin and still shells out to md5sum. Escape the restricted shell, drop a fake md5sum earlier in PATH, and cat the flag.

SSH to rescued-float.picoctf.net -p 49568 (password f3b61b38).

Confirm the login shell is restricted (try cd /tmp - it should fail), then type bash (not exec bash) to drop into an unrestricted shell while keeping the SSH session alive.

Check the common location first: ls -la /usr/local/bin/flaghasher. Falling back to a whole-FS find is slower and rarely necessary.

bash
ssh -p 49568 ctf-player@rescued-float.picoctf.net
bash
echo $SHELL
bash
ls -la /usr/local/bin/flaghasher   # check the obvious location first
bash
bash                                # drop into unrestricted bash (NOT exec bash)
bash
strings /usr/local/bin/flaghasher | grep md5sum
bash
cd /tmp && echo '/bin/cat /root/flag.txt' > md5sum && chmod +x md5sum
bash
export PATH=.:$PATH && /usr/local/bin/flaghasher

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
Restricted shells and PATH-trust bugs are core Linux post-exploitation; the Linux CLI for CTF guide covers rbash escapes, SUID enumeration, and PATH hardening.
  1. Step 1
    Escape rbash restrictions
    Observation
    I noticed that cd /tmp failed immediately after login, which is a hallmark of restricted bash (rbash), suggesting I needed to spawn an unrestricted child shell before any PATH manipulation or file writes would be possible.
    The login shell is restricted (rbash blocks cd, PATH= edits, and slashes in commands). Type plain bash - not exec bash - so the unrestricted shell runs as a child of your SSH session; if anything in it crashes, the SSH session keeps you connected. Then cd /tmp to land in a writable directory.
    bash
    # In rbash, this fails:
    cd /tmp
    
    # Escape (type bash, NOT exec bash):
    bash
    cd /tmp
    What didn't work first

    Tried: Run exec bash instead of plain bash to escape rbash.

    exec bash replaces the current shell process rather than spawning a child. If the new bash session exits or crashes for any reason, the SSH connection drops immediately with no fallback. Using plain bash keeps rbash alive as the parent, so the session survives an accidental exit. The command result is otherwise the same, but the risk to connectivity is real during an exam or timed CTF.

    Tried: Try python3 -c 'import pty; pty.spawn("/bin/bash")' or vim :shell to escape rbash before checking whether plain bash is allowed.

    Those escapes work when bash itself is blocked by the restricted shell's allowed-command list. On this instance bash is not blocked, so running it directly is the simplest and most reliable path. The Python or vim route adds unnecessary complexity and may itself be blocked; always try the most direct escape first.

    Learn more

    Restricted bash (rbash) is a hardened shell mode that prevents users from changing directories with cd, modifying PATH, redirecting output, or running commands with slashes in them. System administrators use it to lock down SSH accounts to a narrow set of approved commands.

    The classic escape is to invoke a full shell binary directly. If bash or sh is accessible, running it without the -r flag drops you into an unrestricted session. Other escapes include launching a text editor (vim's :shell command), using scripting interpreters (python3 -c 'import pty; pty.spawn("/bin/bash")'), or abusing allowed programs that themselves spawn shells (like man with !/bin/sh).

    In real-world penetration testing, restricted shells come up frequently after gaining initial SSH access to a locked-down account. Escaping rbash is one of the first post-exploitation steps and is covered extensively in OSCP and other security certifications.

  2. Step 2
    Hijack md5sum again
    Observation
    I noticed that strings /usr/local/bin/flaghasher showed a bare md5sum call with no absolute path, which confirmed the same PATH-hijack vector from hash-only-1 and suggested dropping a fake md5sum earlier in PATH to redirect execution to /bin/cat /root/flag.txt.
    Same PATH-hijack technique as hash-only-1 - only the binary's path and the restricted login shell differ here. Write a one-line md5sum that cats /root/flag.txt, prove it works standalone before invoking flaghasher, and never include any md5sum invocation inside the fake (the hijacked PATH would resolve back to itself and infinite-loop).
    bash
    echo '/bin/cat /root/flag.txt' > md5sum
    bash
    chmod +x md5sum
    bash
    export PATH=.:$PATH
    bash
    ./md5sum                       # validate: should print the flag standalone
    bash
    /usr/local/bin/flaghasher

    Expected output

    picoCTF{Co-@utH0r_Of_Sy5tem_b!n@riEs_fc06...}
    What didn't work first

    Tried: Write the fake md5sum as #!/bin/sh md5sum /root/flag.txt to try to hash the flag directly.

    Calling md5sum inside the fake script creates an infinite loop: flaghasher calls your fake md5sum, your fake md5sum calls md5sum again, which resolves back to your fake because . is still first in PATH. The script recurses until it hits a process limit and crashes with a fork error. The correct fake must use an absolute path like /bin/cat that is not itself hijacked.

    Tried: Set PATH with PATH=/tmp:$PATH and write the fake to /tmp/md5sum while still in rbash, before running bash to escape.

    rbash blocks PATH= assignments entirely - attempting it prints rbash: PATH: readonly variable. The PATH export must happen after escaping to an unrestricted shell. Additionally, /tmp must already exist and be writable, while the current directory (.) approach works from any writable directory including /tmp once you are there.

    Learn more

    PATH hijacking exploits how Unix-like systems resolve command names. When a program calls system("md5sum") or exec("md5sum") without an absolute path, the OS searches each directory in PATH left-to-right and runs the first match it finds.

    By prepending . (the current directory) to PATH and dropping a malicious script named md5sum there, you guarantee the OS runs your script instead of /usr/bin/md5sum. Your script can do anything the process's effective user can do - in this case, reading /root/flag.txt. This is the same attack that makes sudo unsafe when . appears in a root user's PATH.

    Real-world mitigations include using absolute paths in all system() and exec() calls, auditing PATH in privileged scripts with tools like shellcheck, and applying the principle of least privilege so the target binary never runs with elevated permissions. CVE databases are full of PATH hijacking vulnerabilities in commercial software.

Interactive tools
  • Pwntools ForgeGenerate a complete pwntools exploit script from a template: ret2win, shellcode, ret2libc, ROP chain, format string, or blank scaffold. Fill the form, copy or download the .py file. Fully editable before saving.

Flag

Reveal flag

picoCTF{Co-@utH0r_Of_Sy5tem_b!n@riEs_fc06...}

Any equivalent fake `md5sum` works as long as it doesn't invoke another `md5sum` and `.` stays first in PATH.

Key takeaway

Restricted shells like rbash are a containment layer, not a security boundary: they block convenience features (cd, PATH edits, slashes in commands) but cannot prevent a user from spawning a child process that is a full, unrestricted shell. Any binary in the allowed command set that itself can launch a shell, such as bash, vim, python, or man, breaks the restriction immediately. The lesson for real environments is that rbash is appropriate only as a last-line convenience guard on accounts that have no access to shell-capable interpreters, and that true privilege isolation requires a different mechanism such as a containerized process, mandatory access control, or a purpose-built protocol (sftp-only accounts, for example).

Related reading

Want more picoCTF 2025 writeups?

Useful tools for Binary Exploitation

What to try next