Tokens - Pwn2Win
28 Mar 2016 • LeanderDescription: Tokens was a python exploitation challenge during the 2016 Pwn2Win CTF competition held from 25-27 March. When solved the team was awarded 50 points. This challenge focused on being able to identify exploitable python code which would allow the competitor to take advantage of the running service. This challenge explored the vulnerability of misusing the python eval()
method.
Introduction
Tokens was a fairly straight-forward challenge based around secure programming practices and involved a minimal amount of reversing. We were given a little bit of information about the program using a fixed value for the seed, and the source code.
Tokens
Points: 50
Category: Python Exploitation
Description:
We discovered a Club’s “homemade” token generator system which uses a fixed value as a seed (is it a joke?). Some Club systems use this token scheme, so we need to make a leak in order to compromise them. Due to a week-long effort, our hardcore newbie SkyMex was able to obtain the token generator source code from a private git repository before it received the official seed.
Submit the flag in the format: CTF-BR{seed}.
Problem
Initially, our thought was to brute force the keyspace for the seed, because we knew it should be of a limited size, and had an idea of what characters it would contain based on the gentoken()
function which produced the token for us. However, after several hours of trying to brute force the seed unsuccessfully, I revisted the source code and look for poor programming practices that didn’t initially jump out at me and then I found the eval()
method. 1 So in order to test my solution locally I did two things:
- I created a test harness based on the original source code.
- Maintained a separate window with an interactive python session to test verify my inputs were valid python statements.
After having created these two testing windows I went to work trying to devise a way to take advantage of the eval()
method. I found several resources online to include an blog by Ned Batchelder, Vipul Chaskar, and floyd’s IT Security. Which all of these blogs pointed out something very interesting. When eval()
is used with the __import__
2 python __builtin__ module it can become very dangerous. For example, You can do something like this which would provide you all the local variables in the current namespace:
Solution
In order to beat this challenge first you had to pass the validation()
function which contained a blacklist of invalid characters based on your input. The list is contained the following ASCII characters which you had to avoid: " < > ! @ # $ % ^ & * - / 3 4 6 9 ? \ ` | ; { }
. After passing the validation you had to pass the first if
condition which ensured you at least provided the characters gen
as your first argument. Last, the script allowed us to take advantage of the eval()
method as described in the problem statement.
The input was crafted with gen
first, then __import__
in order to dynamically import the os
module, and last execute the system
method so that we could get a bash
shell. The entire command was constructed as such: gen __import__('os').system('bash')
. Which allowed us to get our shell, cat the source code for tokens.py, and reveal the seed it was using. Score 50 points!
Code Snippets
All code, solutions, and challenge materials are presented in this section in the case that anyone would like to try replicating our solution.
Tokens.py Source code
Local Testing Harness
Endnotes
-
eval()
is a __builtin__ module which allows a programmer to evaluate a python expression. It will also allow dynamic execution of arbitrary code objects such ascompile()
,locals()
, andglobals()
. Read more here. ↩ -
import()
is a __builtin__ module which according to the pydocs is not needed for everyday Python programming. It is invoked by the import statement and is usually only used in cases where you only know the name of a given module at runtime. What this also means is that we can potentially import any module we want from a CTF perspective - unless it is specifically left out or through sandboxing. ↩