| 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: |
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.