Description
This challenge runs a Python 2 script on a remote server that asks you two questions. The vulnerability is in the Python 2 input() function, which evaluates whatever you type as a Python expression rather than treating it as a plain string. By typing a variable name instead of a literal answer, you can read the server's internal state - or spawn a shell entirely.
Setup
Connect to the challenge server with netcat.
nc mercury.picoctf.net <PORT_FROM_INSTANCE>Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Understand why Python 2 input() is dangerous
ObservationThe description mentions a Python 2 script reading answers with input(). Python 2's input() behaves quite differently from Python 3's, and that difference is a well-known source of code execution.Python 2's input() is equivalent to eval(raw_input()). When the server calls input(), it takes your text and runs it as Python code before comparing the result. This means you can type any valid Python expression - including a variable name - and the script will evaluate it and use the result as your answer.Learn more
In Python 3,
input()always returns a plain string. But in Python 2,input()is shorthand foreval(raw_input()). Theevalcall executes your text as Python code. If you typecity, Python evaluates the namecityand returns whatever value that variable holds in the script's scope - rather than the string"city".This is a classic Python 2 footgun. The safe alternative is
raw_input(), which always returns a string. Any script usinginput()in Python 2 to read untrusted data has this vulnerability.Step 2Connect and read the server's variable by name
ObservationThe server script keeps the expected city in a variable named city before calling input(). Type that bare identifier at the prompt and Python 2's eval-based input() resolves the variable and prints its value.When prompted for your favorite number, type the variable name city. Because Python 2 input() evaluates this, the server looks up the variable city in its scope and echoes its value back as 'You said: Round Lake Beach', which leaks the city it picked. At the second prompt type city again: the evaluated answer is the same string the script is comparing against, so the check passes and the flag prints.bashnc mercury.picoctf.net <PORT_FROM_INSTANCE>bash# Prompt: What's your favorite number?bash# Type: citybash# The server echoes the city it picked, e.g. You said: Round Lake Beachbash# Prompt: What's the best city to visit?bash# Type: city (or the leaked name as a quoted string, e.g. 'Round Lake Beach')What didn't work first
Tried: Typing a quoted string like "city" at the first prompt to answer literally.
Python 2's input() evaluates a quoted string as a string literal, so it compares that literal text against the expected number and fails. Type the identifier without quotes, and Python resolves it as a variable in scope rather than as a string.
Tried: Typing the city name as a quoted string at the city prompt, e.g. "Round Lake Beach".
Python 2's input() evaluates the quoted value and returns the string, which does match the expected city, so this works. The leak from step one already gave you the raw string, so quote it exactly as printed; an unquoted multi-word phrase is a syntax error, not a value.
Learn more
By typing
cityat the first prompt, Python 2 evaluates the identifiercityand returns its value, which the script echoes back, leaking the name it picked. Typingcityagain at the second prompt makes the checkif "Round Lake Beach" == "Round Lake Beach", which passes.This technique works because the variable
cityis already defined in the script's global scope beforeinput()is called. Python's eval runs in that same scope, so all previously defined names are accessible.Step 3Alternative: spawn a shell directly
ObservationPython 2's input() evaluates any expression, not just variable names. So __import__('os').system('cat flag') runs a shell command and reads the flag without passing either comparison.Because input() runs eval(), you can type any Python expression - including one that imports a module and executes a shell command. Typing __import__('os').system('cat flag') at either prompt causes the server to run that command directly and print the flag without needing to pass any comparison.bashnc mercury.picoctf.net <PORT_FROM_INSTANCE>bash# Prompt: What's your favorite number?bash# Type: __import__('os').system('cat flag')bash# The flag is printed immediately.What didn't work first
Tried: Typing import os; os.system('cat flag') at the prompt instead of using __import__.
eval() runs a single expression, never a statement, and import is a statement, so an eval containing it raises a SyntaxError and nothing runs. The __import__() builtin is a function call, which is an expression, so eval accepts it. Use __import__('os').system('cat flag') wherever a top-level import is unavailable.
Tried: Trying to inject os.system('cat flag') directly, assuming os is already imported.
The server script may never import os at module level, so that name is not in scope when eval runs your input and referencing it raises a NameError. The self-contained __import__ form avoids that by importing inline within the same expression, assuming nothing about what the script already loaded.
Learn more
__import__('os').system('cat flag')is a one-liner that works without a prior import statement. Python evaluates it, which imports theosmodule and callssystem(), runningcat flagon the server. The flag contents are sent back over the netcat connection.For broader shell access you can inject
__import__('os').system('sh')instead. This drops you into an interactive shell on the remote server, from which you can run any command including listing directory contents withlsand reading the flag withcat flag.
Interactive tools
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
- Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
- Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.
Flag
Reveal flag
picoCTF{v4lua4bl3_1npu7_...}
This challenge exploits Python 2 input() evaluating your input as code. Type a variable name to leak its value, or inject __import__('os').system('cat flag') to read the flag directly.