SUDO MAKE ME A SANDWICH picoCTF 2026 Solution

Published: March 20, 2026

Description

Can you read the flag? I think you can!

Launch the challenge instance and SSH in.

Check what sudo privileges the current user has.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Check sudo permissions
    Observation
    I noticed the challenge description says 'I think you can' read the flag and involves SSH access to a remote instance, which suggested a privilege escalation scenario where the key first step is to enumerate what sudo rules are already in place for the current user.
    Run sudo -l. The first move on any sudo box. If a NOPASSWD rule exists for any binary that can shell out (vim, less, find, awk, perl, emacs), GTFOBins has a tested escape sequence.
    bash
    sudo -l
    What didn't work first

    Tried: Check /etc/sudoers directly with cat /etc/sudoers to read the rules yourself.

    As a non-root user, /etc/sudoers is owned by root and readable only by root (mode 440), so you get a permission denied error. The sudo -l command is the correct tool because it queries sudod directly and prints only the rules that apply to your current user, without needing read access to the file itself.

    Tried: Run sudo su or sudo bash immediately to get a root shell before checking permissions.

    If the sudoers rule only grants specific binaries (like emacs) and not ALL or a shell, sudo su and sudo bash will fail with 'Sorry, user X is not allowed to execute ...' errors. You must enumerate with sudo -l first to discover exactly which binaries are permitted before attempting any escalation.

    Learn more

    sudo reads rules from /etc/sudoers describing which user can run which command as which target user, with or without a password. sudo -l prints the rules that apply to you. Always run it first.

    The classic misconfigurations: text editors (vim, emacs, nano), interpreters (python, perl, ruby), file utilities that shell out (find with -exec, awk with system()), pagers (less, man), and the NOPASSWD directive which makes the escape silent.

    Always go to GTFOBins first. It is the canonical, version-tested catalog of Unix binaries that bypass local restrictions when invoked with elevated privileges, and the listed escape sequence almost always works on the first try. Memorizing escapes is a waste of time; reading GTFOBins is not.

  2. Step 2
    Escape to a root shell from emacs
    Observation
    I noticed that sudo -l revealed a NOPASSWD rule for emacs, which suggested using the GTFOBins emacs escape (either the interactive M-x term route or the headless --batch --eval mode) to gain a root shell without needing a password.
    Pick the right escape. Interactive (M-x term) if you want a stable root shell to poke around in - good for learning, good when you control the terminal. Non-interactive (--batch --eval) if the environment is restricted (no PTY, no proper terminal allocation, automation pipeline) - dumps the file in one shot and exits.
    bash
    sudo emacs
    bash
    # Inside emacs:
    bash
    # Press Alt+X (M-x), type 'term', press Enter
    bash
    # Then at the terminal prompt: cat /home/ctf-player/flag.txt
    bash
    bash
    # Non-interactive alternative:
    bash
    sudo emacs -Q -nw --eval '(term "/bin/bash")'
    bash
    # Or direct file read:
    bash
    sudo emacs -Q --batch --eval '(with-temp-buffer (insert-file-contents "/home/ctf-player/flag.txt") (message "%s" (buffer-string)))'
    What didn't work first

    Tried: Use sudo emacs to open the flag file directly as an argument: sudo emacs /home/ctf-player/flag.txt.

    This opens the file in the emacs editor buffer which does display the contents, but the session runs interactively and many CTF environments allocate no proper PTY for SSH sessions. More importantly, players often get confused by the emacs UI and never realize they need M-x term to get a shell - they read the flag from the buffer but miss that this is the intended escalation path. The batch --eval approach is more reliable in headless environments.

    Tried: Try the vim GTFOBins escape (sudo vim -c ':!/bin/bash') since both are text editors with sudo.

    The sudoers rule grants emacs specifically, not vim. Running sudo vim will immediately fail with 'Sorry, user X is not allowed to execute /usr/bin/vim ...' because sudo checks the exact binary path against the rule. You must use the exact binary listed in sudo -l output - in this challenge that is emacs.

    Learn more

    GNU Emacs is a Lisp-based computing environment that happens to edit text. M-x term (Meta+X, type "term", Enter) spawns an interactive terminal emulator inside Emacs. The shell it launches inherits the process privileges of Emacs itself - which, when Emacs is run via sudo, means root.

    Annotated batch escape:

    sudo emacs -Q --batch --eval '
      (with-temp-buffer            ;; create a throwaway buffer (no UI)
        (insert-file-contents "/home/ctf-player/flag.txt")  ;; read as root
        (message "%s" (buffer-string)))'                    ;; print to stderr

    with-temp-buffer creates an in-memory scratch buffer; insert-file-contents performs the privileged read; (message ...) writes to stderr, which the calling shell captures and displays. No PTY, no interactive shell, no flag-text-to-screen indirection - exactly what you want when the box is locked down.

    The challenge name nods to xkcd #149: "sudo make me a sandwich." The serious lesson behind the joke is the principle this challenge embodies: any program that can shell out, when run via sudo, becomes a full privilege escalation primitive. Editors, pagers, scripting interpreters, file-finding tools - they all collapse the "run X as root" permission into "run anything as root." See the Linux CLI for CTF guide for adjacent enumeration patterns.

  3. Step 3
    Read the flag
    Observation
    I noticed the emacs terminal or batch eval now runs as root, which means the flag file at /home/ctf-player/flag.txt is readable directly with cat since all file permission checks are bypassed by the root privilege.
    With root privileges in the spawned shell, read the flag file.
    bash
    cat /home/ctf-player/flag.txt
    bash
    # or: find / -name flag.txt 2>/dev/null

    Expected output

    picoCTF{g0tt4_l0v3_s4ndw1ch3s_...}
    Learn more

    In CTF challenges, flag files are commonly placed in the home directory of a specific user (/home/ctf-player/), in /root/, or in /flag.txt at the filesystem root. With a root shell, all of these are accessible. The find / -name flag.txt 2>/dev/null command searches the entire filesystem for files named flag.txt, with stderr redirected to /dev/null to suppress permission errors from directories you cannot read (though with root, that is no longer an issue).

    In real-world privilege escalation scenarios, post-exploitation tasks are more varied: reading sensitive configuration files (/etc/shadow, database credentials), accessing other users' home directories, modifying system files to maintain persistence, or pivoting to other networked systems. In a CTF, the target is simply the flag file, making the privilege escalation goal clear and specific.

    Proper defenses against sudo abuse include: applying the principle of least privilege (grant only the specific capabilities users truly need), auditing sudoers files regularly, using sudo -l output as part of security reviews, and preferring container-based privilege separation over UNIX user permission models where possible.

Flag

Reveal flag

picoCTF{g0tt4_l0v3_s4ndw1ch3s_...}

The sudo config allows running emacs as root. Emacs includes a full terminal emulator (M-x term) - any shell spawned from within it runs as root, giving direct access to the flag file.

Key takeaway

Granting sudo to any binary that can spawn a subshell - editors, pagers, scripting interpreters, file-search tools with -exec - is functionally equivalent to granting sudo ALL, because every such binary on GTFOBins has a tested one-liner that opens an unrestricted root shell. The principle of least privilege means granting only the specific capability needed, using Linux capabilities or a purpose-built wrapper script rather than sudo on a general-purpose tool.

How to prevent this

Granting sudo to a feature-rich binary (vim, less, emacs, find, awk, perl) is equivalent to granting sudo ALL. GTFOBins enumerates them.

  • Audit your /etc/sudoers against GTFOBins. Any binary with a shell-out command (:!sh in vim, !sh in less, M-x term in emacs) breaks the principle of least privilege.
  • If you genuinely need a user to run one task as root, write a tiny purpose-built script and grant sudo on that script only. Validate every argument; reject anything starting with -.
  • Replace sudo grants with capabilities. Capabilities split UID 0 into ~40 fine-grained privileges; sudo hands over the whole thing. The canonical case: a Python web server that needs to bind port 80 (a privileged port). Don't do sudo python server.py - that gives the script root. Instead:
    # Grant *only* "may bind low ports" to the python interpreter:
    sudo setcap cap_net_bind_service+ep $(readlink -f $(which python3))
    
    # Now this works without sudo, with no other root powers:
    python3 -m http.server 80
    cap_net_bind_service+ep is permitted+effective for that one capability. The script cannot read /etc/shadow, modify /root, or shell out as root - it can only bind low ports. systemd unit files express the same idea via AmbientCapabilities=.

Related reading

Want more picoCTF 2026 writeups?

Useful tools for General Skills

What to try next