Paste number 70003: secure.py

Paste number 70003: secure.py
Pasted by: tav
When:1 year, 9 months ago
Share:Tweet this! | http://paste.lisp.org/+1I0J
Channel:#esp
Paste contents:
Raw Source | XML | Display As
r"""
=============================
Secure the Python Interpreter
=============================

Python is capable of supporting secure capability-style programming through the
use of closures. However, Python's introspective qualities -- which is one of
its strengths -- leaks references to variables closed over by closures through
certain attributes of builtin types.

As a simple example, consider the following:

  >>> from hashlib import sha1

  >>> def create_hasher(secret):
  ...      def get_digest(value):
  ...          return sha1(secret + value).hexdigest()
  ...      return get_digest

  >>> get_digest = create_hasher(secret='nutella')

The ``get_digest`` function could now be passed to untrusted code and ideally
the untrusted code would not be able to use Python's introspective capabilities
to discover the value of ``secret``.

Unfortunately, this is easily accessible:

  >>> get_digest.func_closure[0].cell_contents
  'nutella'

The ``secure_python`` function in this module fixes this by removing certain
attributes from some of Python's builtin types:

  >>> hasattr(FunctionType, 'func_code')
  True
  >>> hasattr(FunctionType, 'func_closure')
  True
  >>> hasattr(FunctionType, 'func_globals')
  True
  >>> hasattr(type, '__subclasses__')
  True

After ``secure_python`` is run, these attributes are no longer directly
accessible:

  >>> secure_python()

  >>> hasattr(FunctionType, 'func_code')
  False
  >>> hasattr(FunctionType, 'func_closure')
  False
  >>> hasattr(FunctionType, 'func_globals')
  False
  >>> hasattr(type, '__subclasses__')
  False

  >>> if sys.version_info >= (2, 6):
  ...     print hasattr(FunctionType, '__code__')
  ...     print hasattr(FunctionType, '__closure__')
  ...     print hasattr(FunctionType, '__globals__')
  ... else:
  ...     print False
  ...     print False
  ...     print False
  False
  False
  False

You can now safely pass along closures to untrusted code -- secure in the
knowledge that it won't be able to access closed over *private* variables:

  >>> get_digest.func_closure[0].cell_contents
  Traceback (most recent call last):
  ...
  AttributeError: 'function' object has no attribute 'func_closure'

However, accessing the various removed attributes of FunctionType is still
useful for trusted code. To cater for this, specific getters are added to the
``sys`` module:

* sys.get_func_closure
* sys.get_func_code
* sys.get_func_globals

So you can still access *private* data in trusted code, e.g.

  >>> sys.get_func_closure(get_digest)[0].cell_contents
  'nutella'

  >>> sys.get_func_globals(get_digest) == globals()
  True

  >>> from types import CodeType
  >>> isinstance(sys.get_func_code(get_digest), CodeType)
  True

  >>> class DictSubClass(dict):
  ...     pass

  >>> DictSubClass in sys.get_subclasses(dict)
  True

This makes the reasonable assumption that *trusted* code will be able to import
the ``sys`` module and untrusted code will not have access to ``sys``.

The ``secure_python`` function automatically patches various functions within
the standard library which access attributes like ``func_code``:

  >>> def _test_function(x, y=3):
  ...     x += 1
  ...     return x + y

  >>> import dis
  >>> dis.dis(_test_function) # careful with the whitespace in the test
    2           0 LOAD_FAST                0 (x)
                3 LOAD_CONST               1 (1)
                6 INPLACE_ADD         
                7 STORE_FAST               0 (x)
  <BLANKLINE>
    3          10 LOAD_FAST                0 (x)
               13 LOAD_FAST                1 (y)
               16 BINARY_ADD          
               17 RETURN_VALUE        

  >>> import inspect

  >>> inspect.getargspec(_test_function) ==  (['x', 'y'], None, None, (3,))
  True

  >>> def _test_generator():
  ...     while 1:
  ...         yield None
  
  >>> if sys.version_info >= (2, 6):
  ...     print inspect.isgeneratorfunction(_test_generator)
  ... else:
  ...     print True
  True

  >>> inspect.findsource(_test_function)
  (['def _test_function(x, y=3):\n', '    x += 1\n', '    return x + y\n'], 0)

  >>> inspect.findsource(_test_generator)
  (['def _test_generator():\n', '    while 1:\n', '        yield None\n'], 0)

Since these attributes are rarely used in Python code, it should be pretty
trivial to patch trusted 3rd-party libraries to use the sys.get_* accessor
functions.

"""

# thanks to PJE on the Python-Dev list!! **kiss**

import sys

from types import FunctionType

# ------------------------------------------------------------------------------
# kore funktion
# ------------------------------------------------------------------------------

def secure_python():
    """Remove insecure variables from the Python interpreter."""

    if secure_python._initialised:
        return

    from ctypes import pythonapi, POINTER, py_object

    get_dict = pythonapi._PyObject_GetDictPtr
    get_dict.restype = POINTER(py_object)
    get_dict.argtypes = [py_object]

    def dictionary_of(ob):
        dptr = get_dict(ob)
        if dptr and dptr.contents:
            return dptr.contents.value

    sys.get_func_closure = dictionary_of(FunctionType)['func_closure'].__get__
    sys.get_func_code = dictionary_of(FunctionType)['func_code'].__get__
    sys.get_func_globals = dictionary_of(FunctionType)['func_globals'].__get__
    sys.get_subclasses = dictionary_of(type)['__subclasses__']

    # import affekted modules
    if sys.version_info >= (2, 6):
        import _abcoll
        import io
        import numbers

    del dictionary_of(FunctionType)['func_closure']
    del dictionary_of(FunctionType)['func_code']
    del dictionary_of(FunctionType)['func_globals']
    del dictionary_of(type)['__subclasses__']

    if sys.version_info >= (2, 6):
        del dictionary_of(FunctionType)['__closure__']
        del dictionary_of(FunctionType)['__code__']
        del dictionary_of(FunctionType)['__globals__']

    secure_python._initialised = True

    # monkeypatch modules which require ``func_code``
    patch_mod = 'secure%s' % ''.join(map(str, sys.version_info[:2]))
    __import__('protoplex.%s' % patch_mod, {}, {}, [patch_mod])

secure_python._initialised = None

This paste has no annotations.

Colorize as:
Show Line Numbers

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.