Hey all,
As an attempt to convince Python-Dev of the merits of a
functions-based approach to security in Python, I've come up with a
simple challenge.
If enough smart hackers look at this and it holds up, Guido promises
to accept a patch which would enable this on both App Engine and
future Python versions.
So, please try the challenge and let me know how you find it. Thanks!
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
[safelite.py is attached to this mail]
Please note that the aim of this isn't to protect Python against
crashes/segfaults or exhaustion of resources attacks, so those don't
count.
I'm keen to know your experiences even if you don't manage to write to
the filesystem -- and especially if you do!
Dinner and drinks on me for an evening -- when you are next in London
or I am in your town -- to the first person who manages to break
safelite.py and write to the filesystem.
Good luck and thanks! =)
--
love, tav
plex:espians/tav | [email protected] | +44 (0) 7809 569 369
http://tav.espians.com | http://twitter.com/tav | skype:tavespian
"""
Please try and break this.
On a fresh Python interpreter, do the following:
>>> from safelite import FileReader
You should now be able to read files as you want...
Now, please try and *write* to a file on the filesystem from within the
interpreter.
Please note that the aim of this isn't to protect Python against segfaults or
exhaustion of resources attacks, so those don't count.
Let me know <[email protected]> or Python-Dev how your experience goes -- whether
this seems to work for you or not. Thanks!
"""
import __builtin__
import sys
from sys import _getframe as get_frame
from types import FunctionType, GeneratorType
__all__ = ['FileReader']
# ------------------------------------------------------------------------------
# map funktion attribute names for python versions >= 2.6
# ------------------------------------------------------------------------------
FUNCTION_PY26_ATTRS = {
'func_code': '__code__',
'func_globals': '__globals__',
'func_closure': '__closure__'
}
# ------------------------------------------------------------------------------
# sekure the interpreter!
# ------------------------------------------------------------------------------
def secure_python():
"""Remove insecure variables from the Python interpreter."""
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
if sys.version_info >= (3, 0):
py_version = 2
elif sys.version_info >= (2, 6):
py_version = 1
else:
py_version = 0
for attr in FUNCTION_PY26_ATTRS.keys():
if py_version <= 1:
del dictionary_of(FunctionType)[attr]
if py_version >= 1:
del dictionary_of(FunctionType)[FUNCTION_PY26_ATTRS[attr]]
del dictionary_of(type)['__subclasses__']
del dictionary_of(GeneratorType)['gi_frame']
if py_version:
del dictionary_of(GeneratorType)['gi_code']
def secure_python_builtins():
"""Remove dangerous builtins like ``file`` and patch appropriately."""
for item in ['open', 'file', 'execfile']:
del __builtin__.__dict__[item]
def null(*args, **kwargs):
pass
import linecache
linecache.open = FileReader
import site
site.file = FileReader
__builtin__.__import__ = null
# ------------------------------------------------------------------------------
# pseudo-klass-like namespase wrapper
# ------------------------------------------------------------------------------
class NamespaceContext(type):
"""A Namespace Context metaclass."""
def __call__(klass, __getter):
for name, obj in __getter:
setattr(klass, name, obj)
return type.__call__(klass, __getter)
def __str__(klass):
return 'NamespaceContext%s' % (tuple(klass.__dict__.keys()),)
def _Namespace():
__private_data = {}
def Namespace(*args, **kwargs):
"""Return a Namespace from the current scope or the given arguments."""
class NamespaceObject(tuple):
__metaclass__ = NamespaceContext
__slots__ = ()
def __new__(klass, __getter):
return tuple.__new__(klass, __getter)
ns_items = []; populate = ns_items.append
if args or kwargs:
frame = None
for arg in args:
kwargs[arg.__name__] = arg
for name, obj in kwargs.iteritems():
if isinstance(obj, FunctionType):
populate((name, staticmethod(obj)))
else:
populate((name, obj))
else:
frame = get_frame(1)
for name, obj in frame.f_locals.iteritems():
if isinstance(obj, FunctionType):
if not (name.startswith('_') and not name.startswith('__')):
populate((name, staticmethod(obj)))
elif name.startswith('__') and name.endswith('__'):
populate((name, obj))
del frame, args, kwargs
# @/@ what should we do with __doc__ and __name__ ??
return NamespaceObject(ns_items)
return Namespace
Namespace = _Namespace()
del _Namespace
# ------------------------------------------------------------------------------
# file reader
# ------------------------------------------------------------------------------
open_file = __builtin__.open
def FileReader(filename, mode='r', buffering=0):
"""A secure file reader."""
if mode not in ['r', 'rb', 'rU']:
mode = 'r'
fileobj = open_file(filename, mode, buffering)
def __iter__():
if fileobj.closed:
raise ValueError("I/O operation on closed file.")
return self
def __repr__():
return '<FileReader: %r>' % filename
def close():
return fileobj.close()
def next():
return fileobj.next()
def read(bufsize=-1):
return fileobj.read(bufsize)
def readline(size=-1):
return fileobj.readline(size)
def readlines(size=-1):
return fileobj.readlines(size)
def seek(offset, whence=0):
fileobj.seek(offset, whence)
def tell():
return fileobj.tell()
def is_closed():
return fileobj.closed
def is_atty():
return fileobj.isatty()
def get_encoding():
return fileobj.encoding
def get_mode():
return fileobj.mode
def get_name():
return fileobj.name
def get_newlines():
return fileobj.newlines
self = Namespace()
return self
# ------------------------------------------------------------------------------
# self runner
# ------------------------------------------------------------------------------
secure_python()
secure_python_builtins()
--
http://mail.python.org/mailman/listinfo/python-list