Description
Can you read the flag? I think you can!
Setup
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.
Step 1
Check sudo permissionsObservationI 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.Runsudo -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.bashsudo -lWhat 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/sudoersdescribing which user can run which command as which target user, with or without a password.sudo -lprints 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 withsystem()), 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.
Step 2
Escape to a root shell from emacsObservationI 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.bashsudo emacsbash# Inside emacs:bash# Press Alt+X (M-x), type 'term', press Enterbash# Then at the terminal prompt: cat /home/ctf-player/flag.txtbashbash# Non-interactive alternative:bashsudo emacs -Q -nw --eval '(term "/bin/bash")'bash# Or direct file read:bashsudo 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 stderrwith-temp-buffercreates an in-memory scratch buffer;insert-file-contentsperforms 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.
Step 3
Read the flagObservationI 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.bashcat /home/ctf-player/flag.txtbash# or: find / -name flag.txt 2>/dev/nullExpected 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.txtat the filesystem root. With a root shell, all of these are accessible. Thefind / -name flag.txt 2>/dev/nullcommand searches the entire filesystem for files namedflag.txt, with stderr redirected to/dev/nullto 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
How to prevent this
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/sudoersagainst GTFOBins. Any binary with a shell-out command (:!shin vim,!shin less,M-x termin 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+epis 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 viaAmbientCapabilities=.