Picker II

Published: March 5, 2024

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.

Python sandbox bypassDownload picker-II.py

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.py
cat picker-II.py

Solution

  1. Step 1Copy the critical line
    Inside 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 the win function 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 getRandomNumber and nothing else, making the win function completely unreachable regardless of how the input is crafted.

  2. Step 2Submit the payload remotely
    Connect 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 56771
    Learn more

    The payload print(open('flag.txt', 'r').read()) is a complete Python expression: it opens the file flag.txt in 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 full eval().

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.

Want more picoGym Exclusive writeups?

Useful tools for Reverse Engineering

Related reading

What to try next