Description
Picker II adds a simple blacklist so you cannot type win directly, but eval still executes arbitrary Python expressions. Abuse the file read inside win() yourself.
Setup
Review picker-II.py to see the string blacklist that prevents submitting win.
Look at the win() helper; the first line reads flag.txt, and nothing stops you from doing the same.
wget https://artifacts.picoctf.net/c/523/picker-II.pycat picker-II.pySolution
- Step 1Copy the critical lineInside win(), the statement flag = open('flag.txt', 'r').read() loads the flag. Because user input still flows into eval, you can execute the exact same print call.
Learn more
A blacklist (also called a denylist) is a security control that blocks specific known-bad values while allowing everything else. Picker II blocks the string
"win"directly, preventing you from calling thewinfunction by name. However, blacklists are notoriously fragile because they must anticipate every possible attack vector - missing even one route defeats the protection entirely.The vulnerability here is that the service still passes user input directly into
eval()or an equivalent dynamic execution mechanism.eval()in Python executes an arbitrary expression, meaning any valid Python code you submit runs with the service's privileges. The blacklist only checks the literal string"win", so any other Python code that accomplishes the same goal bypasses the filter.This pattern - input validation via blacklist rather than whitelist - is one of the most common root causes of injection vulnerabilities. Whitelists (allowlists) are almost always safer: instead of blocking known-bad values, you only permit known-good ones. For this service, a whitelist approach might only accept the function names
getRandomNumberand nothing else, making thewinfunction completely unreachable regardless of how the input is crafted. - Step 2Submit the payload remotelyConnect to the remote host and paste print(open('flag.txt', 'r').read()). The service executes your code server-side and prints the flag.
printf "print(open('flag.txt','r').read())\n" | nc saturn.picoctf.net 56771Learn more
The payload
print(open('flag.txt', 'r').read())is a complete Python expression: it opens the fileflag.txtin read mode, reads its entire contents, and prints the result. Because the service evaluates this expression with the same permissions it uses to read flag.txt itself, the file is accessible and its contents are returned directly.This is a textbook example of server-side code injection - specifically, arbitrary Python execution. In real applications, eval() vulnerabilities have appeared in template engines (SSTI - Server-Side Template Injection), configuration parsers, and scripting APIs. The impact is typically full server compromise because eval() has access to the entire Python standard library, including file I/O, network sockets, and subprocess spawning.
The key lesson: never pass user-controlled data to eval(), exec(), or any dynamic code execution function. If dynamic dispatch is required (calling functions by name), use a whitelist of allowed function names checked against a pre-built dictionary, never raw eval. Libraries like
ast.literal_eval()can safely evaluate Python literals (strings, numbers, lists, dicts) without the risks of fulleval().
Flag
picoCTF{f1l73r5_f41l_c0d3_r3f4c70r_m1gh7_5ucc3...44590}
Because the blacklist only checks for win, any other Python code that reads flag.txt still succeeds.