building and breaking a python sandboxweb.mit.edu/jesstess/www/pytennessee_sandbox.pdfexamples in...

Post on 21-Jul-2020

3 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Building and breaking a Python sandbox

Director

Organizer

@jessicamckellarhttp://jesstess.com

Why?

• Learning a language

• Providing a hosted scratch pad

• Distributed computation

• Inspecting running processes safely

Examples in the wild

• Seattle’s peer-to-peer computing network

• Google App Engine’s Python shell

• Codecademy’s empythoned

• CheckIO.org’s online coding game

Building a sandbox

• Language-level sandboxing (pysandbox)

• OS-level sandboxing (PyPy’s sandbox)

Question: How do we execute arbitrary code?

How do we execute arbitrary code?

exec: compiles and evaluates statements

>>> exec "print 'Hello world'"Hello world

eval: compiles and evaluates expressions

>>> eval("1 + 2")3

class Sandbox(object): def execute(self, code_string): exec code_string

sandbox.py

from sandbox import Sandbox

s = Sandbox()

code = """ print "Hello world!" """

s.execute(code)

test_sandbox.py

$ python test_sandbox.pyHello world!

from sandbox import Sandbox

s = Sandbox()

code = """ print "Hello world!" """

s.execute(code)

What should we disallow?

What should we disallow?

• Resource exhaustion

• Information disclosure

• Running unexpected services

• Disabling/quitting/erroring out of the sandbox

from sandbox import Sandbox

s = Sandbox()

code = """ file("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

>>> __builtins__.__dict__.keys()['bytearray', 'IndexError', 'all', 'help', 'vars', 'SyntaxError', 'unicode', 'UnicodeDecodeError', 'memoryview', 'isinstance', 'copyright', 'NameError', 'BytesWarning', 'dict', 'input', 'oct', 'bin', 'SystemExit', 'StandardError', 'format', 'repr', 'sorted', 'False', 'RuntimeWarning', 'list', 'iter', 'reload', 'Warning', '__package__', 'round', 'dir', 'cmp', 'set', 'bytes', 'reduce', 'intern', 'issubclass', 'Ellipsis', 'EOFError', 'locals', 'BufferError', 'slice', 'FloatingPointError', 'sum', 'getattr', 'abs', 'exit', 'print', 'True', 'FutureWarning', 'ImportWarning', 'None', 'hash', 'ReferenceError', 'len', 'credits', 'frozenset', '__name__', 'ord', 'super', '_', 'TypeError', 'license', 'KeyboardInterrupt', 'UserWarning', 'filter', 'range', 'staticmethod', 'SystemError', 'BaseException', 'pow', 'RuntimeError', 'float', 'MemoryError', 'StopIteration', 'globals', 'divmod', 'enumerate', 'apply', 'LookupError', 'open', 'quit', 'basestring', 'UnicodeError', 'zip', 'hex', 'long', 'next', 'ImportError', 'chr', 'xrange', 'type', '__doc__', 'Exception', 'tuple', 'UnicodeTranslateError', 'reversed', 'UnicodeEncodeError', 'IOError', 'hasattr', 'delattr', 'setattr', 'raw_input', 'SyntaxWarning', 'compile', 'ArithmeticError', 'str', 'property', 'GeneratorExit', 'int', '__import__', 'KeyError', 'coerce', 'PendingDeprecationWarning', 'file', 'EnvironmentError', 'unichr', 'id', 'OSError', 'DeprecationWarning', 'min', 'UnicodeWarning', 'execfile', 'any', 'complex', 'bool', 'ValueError', 'NotImplemented', 'map', 'buffer', 'max', 'object', 'TabError', 'callable', 'ZeroDivisionError', 'eval', '__debug__', 'IndentationError', 'AssertionError', 'classmethod', 'UnboundLocalError', 'NotImplementedError', 'AttributeError', 'OverflowError']

>>> __builtins__.__dict__.keys()['bytearray', 'IndexError', 'all', 'help', 'vars', 'SyntaxError', 'unicode', 'UnicodeDecodeError', 'memoryview', 'isinstance', 'copyright', 'NameError', 'BytesWarning', 'dict', 'input', 'oct', 'bin', 'SystemExit', 'StandardError', 'format', 'repr', 'sorted', 'False', 'RuntimeWarning', 'list', 'iter', 'reload', 'Warning', '__package__', 'round', 'dir', 'cmp', 'set', 'bytes', 'reduce', 'intern', 'issubclass', 'Ellipsis', 'EOFError', 'locals', 'BufferError', 'slice', 'FloatingPointError', 'sum', 'getattr', 'abs', 'exit', 'print', 'True', 'FutureWarning', 'ImportWarning', 'None', 'hash', 'ReferenceError', 'len', 'credits', 'frozenset', '__name__', 'ord', 'super', '_', 'TypeError', 'license', 'KeyboardInterrupt', 'UserWarning', 'filter', 'range', 'staticmethod', 'SystemError', 'BaseException', 'pow', 'RuntimeError', 'float', 'MemoryError', 'StopIteration', 'globals', 'divmod', 'enumerate', 'apply', 'LookupError', 'open', 'quit', 'basestring', 'UnicodeError', 'zip', 'hex', 'long', 'next', 'ImportError', 'chr', 'xrange', 'type', '__doc__', 'Exception', 'tuple', 'UnicodeTranslateError', 'reversed', 'UnicodeEncodeError', 'IOError', 'hasattr', 'delattr', 'setattr', 'raw_input', 'SyntaxWarning', 'compile', 'ArithmeticError', 'str', 'property', 'GeneratorExit', 'int', '__import__', 'KeyError', 'coerce', 'PendingDeprecationWarning', 'file', 'EnvironmentError', 'unichr', 'id', 'OSError', 'DeprecationWarning', 'min', 'UnicodeWarning', 'execfile', 'any', 'complex', 'bool', 'ValueError', 'NotImplemented', 'map', 'buffer', 'max', 'object', 'TabError', 'callable', 'ZeroDivisionError', 'eval', '__debug__', 'IndentationError', 'AssertionError', 'classmethod', 'UnboundLocalError', 'NotImplementedError', 'AttributeError', 'OverflowError']

How do we disallow execution of

problematic builtins?

Idea: keyword blacklist

class Sandbox(object): def execute(self, code_string): keyword_blacklist = ["file", "open", "eval", "exec"] for keyword in keyword_blacklist: if keyword in code_string: raise ValueError("Blacklisted") exec code_string

Idea: keyword blacklist

from sandbox import Sandbox

s = Sandbox()

code = """ file("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

Testing: keyword blacklist

from sandbox import Sandbox

s = Sandbox()

code = """ file("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

$ python test_sandbox.pyTraceback (most recent call last): File "test_sandbox.py", line 11, in <module> s.execute(code) File "/Users/jesstess/Desktop/sandbox/sandbox.py", line 86, in execute raise ValueError("Blacklisted")ValueError: Blacklisted

Testing: keyword blacklist

How can we get around a keyword

blacklist?

Circumvention idea: encryption

func = __builtins__["file"]func("test.txt", "w").write("Kaboom!\n")

Circumvention idea: encryption

func = __builtins__["file"]func("test.txt", "w").write("Kaboom!\n")

func = __builtins__["svyr".decode("rot13")]func("test.txt", "w").write("Kaboom!\n")

from sandbox import Sandbox

s = Sandbox()

code = """ func = __builtins__["svyr".decode("rot13")] func("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

Testing: keyword blacklist

Kaboom

Observation: if I can get a reference to something

bad, I can invoke it.

How can we remove all references to

problematic builtins?

Idea: builtins whitelist

builtins_whitelist = set(( # exceptions 'ArithmeticError', 'AssertionError', 'AttributeError', ... # constants 'False', 'None', 'True', ... # types 'basestring', 'bytearray', 'bytes', 'complex', 'dict', ... # functions '__import__', 'abs', 'all', 'any', 'apply', 'bin', 'bool', ... # block: eval, execfile, file, quit, exit, reload, etc.))

import sys

main = sys.modules["__main__"].__dict__orig_builtins = main["__builtins__"].__dict__builtins_whitelist = set(( ... ))

for builtin in orig_builtins.keys(): if builtin not in builtins_whitelist: del orig_builtins[builtin]

from sandbox import Sandbox

s = Sandbox()

code = """ file("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

Testing: builtins whitelist

from sandbox import Sandbox

s = Sandbox()

code = """ file("test.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

$ python test_sandbox.pyTraceback (most recent call last): File "test_sandbox.py", line 9, in <module> s.execute(code) ... File "<string>", line 2, in <module>NameError: name 'file' is not defined

Testing: builtins whitelist

Circumvention idea: import something

dangerous

from sandbox import Sandbox

s = Sandbox()

code = """ import os fd = os.open("test.txt", os.O_CREAT|os.O_WRONLY) os.write(fd, "Kaboom!\\n")"""

s.execute(code)

Testing: builtins whitelist

Kaboom

How do we disallow problematic imports?

Idea: import whitelist

>>> importer = __builtins__.__dict__.get("__import__")>>> os = importer("os")>>> os<module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>>>> os.getcwd()'/Users/jesstess/Desktop/sandbox'

Idea: import whitelist

How does importing a module work in Python?

>>> help(__builtins__.__dict__["__import__"])

__import__(...) __import__(name, globals={}, locals={}, fromlist=[], level=-1) -> module

Idea: import whitelist

What is the expected function signature for the importer?

>>> def my_importer(module_name, globals={}, ... locals={}, fromlist=[], ... level=-1):... print "Using my importer!"... return __import__(module_name, globals, ... locals, fromlist, level)... >>> os = my_importer("os")Using my importer!>>> os.getcwd()'/Users/jesstess/Desktop/sandbox'

Idea: import whitelist

Cool, let’s write our own importer

def _safe_import(__import__, module_whitelist): def safe_import(module_name, globals={}, locals={}, fromlist=[], level=-1):! if module_name in module_whitelist: return __import__(module_name,! ! ! ! ! ! ! ! ! ! ! ! globals, locals, fromlist, level) else: raise ImportError( "Blocked import of %s" ( module_name,)) return safe_import

import sys

main = sys.modules["__main__"].__dict__orig_builtins = main["__builtins__"].__dict__

for builtin in orig_builtins.keys(): if builtin not in builtins_whitelist: del original_builtins[builtin]

safe_modules = ["string", "re"]orig_builtins["__import__"] = _safe_import( __import__, safe_modules)

from sandbox import Sandbox

s = Sandbox()

code = """ import os fd = os.open("test.txt", os.O_CREAT|os.O_WRONLY) os.write(fd, "Kaboom!\\n")"""

s.execute(code)

Testing: import whitelist

from sandbox import Sandbox

s = Sandbox()

code = """ import os fd = os.open("test.txt", os.O_CREAT|os.O_WRONLY) os.write(fd, "Kaboom!\\n")"""

s.execute(code)

Testing: import whitelist

$ python test_sandbox.pyTraceback (most recent call last): File "test_sandbox.py", line 11, in <module> ... raise ImportError("Blocked import of %s" % (module_name,))ImportError: Blocked import of os

Circumvention idea: modifying builtins

Idea: make builtins read-only

How can we make an object read-only in

Python?

class ReadOnlyBuiltins(dict): def __delitem__(self, key): ValueError("Read-only!")

def pop(self, key, default=None): ValueError("Read-only!")

def popitem(self): ValueError("Read-only!") ... def setdefault(self, key, value): ValueError("Read-only!")

def __setitem__(self, key, value): ValueError("Read-only!")

def update(self, dict, **kw): ValueError("Read-only!")

main = sys.modules["__main__"].__dict__orig_builtins = main["__builtins__"].__dict__

for builtin in orig_builtins.keys(): if builtin not in builtins_whitelist: del original_builtins[builtin]

safe_modules = ["string", "re"]orig_builtins["__import__"] = _safe_import( __import__, safe_modules)

safe_builtins = ReadOnlyBuiltins( original_builtins)main["__builtins__"] = safe_builtins

Observation redux: if I can get a reference to something bad, I can

invoke it.

Circumvention idea: exploiting the

inheritance hierarchy

>>> dir([])['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', ...]>>> [].__class__<type 'list'>

What can we find out about an object’s base classes?

>>> [].__class__<type 'list'>>>> [].__class__.__bases__(<type 'object'>,)>>> [].__class__.__bases__[0]<type 'object'>

What can we find out about an object’s base classes?

list subclasses object

>>> [].__class__.__subclasses__()[]>>> int.__subclasses__()[<type 'bool'>]>>> basestring.__subclasses__()[<type 'str'>, <type 'unicode'>]

What can we find out about an object’s subclasses?

subclasses of basestring

>>> [].__class__.__bases__(<type 'object'>,)>>> [].__class__.__bases__[0]<type 'object'>

>>> [].__class__.__bases__[0].__subclasses__()[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'string.Template'>, <class 'string.Formatter'>, <type 'operator.itemgetter'>, <type 'operator.attrgetter'>, <type 'operator.methodcaller'>, <type 'collections.deque'>, <type 'deque_iterator'>, <type 'deque_reverse_iterator'>, <type 'itertools.combinations'>, <type 'itertools.combinations_with_replacement'>, <type 'itertools.cycle'>, <type 'itertools.dropwhile'>, <type 'itertools.takewhile'>, <type 'itertools.islice'>, <type 'itertools.starmap'>, <type 'itertools.imap'>, <type 'itertools.chain'>, <type 'itertools.compress'>, <type 'itertools.ifilter'>, <type 'itertools.ifilterfalse'>, <type 'itertools.count'>, <type 'itertools.izip'>, <type 'itertools.izip_longest'>, <type 'itertools.permutations'>, <type 'itertools.product'>, <type 'itertools.repeat'>, <type 'itertools.groupby'>, <type 'itertools.tee_dataobject'>, <type 'itertools.tee'>, <type 'itertools._grouper'>, <type '_thread._localdummy'>, <type 'thread._local'>, <type 'thread.lock'>, <class 'sandbox.Protection'>, <type 'resource.struct_rusage'>, <class 'sandbox.config.SandboxConfig'>, <class 'sandbox.proxy.ReadOnlySequence'>, <class 'sandbox.sandbox_class.Sandbox'>, <class 'sandbox.restorable_dict.RestorableDict'>]

All of the subclasses of object!

>>> [].__class__.__bases__(<type 'object'>,)>>> [].__class__.__bases__[0]<type 'object'>

>>> obj_class = [].__class__.__bases__[0]>>> for c in obj_class.__subclasses__():... print c.__name__...wrapper_descriptorinstanceellipsismember_descriptorfilePyCapsulecellcallable-iteratoriterator...

>>> [].__class__.__bases__(<type 'object'>,)>>> [].__class__.__bases__[0]<type 'object'>

>>> obj_class = [].__class__.__bases__[0]>>> for c in obj_class.__subclasses__():... print c.__name__...wrapper_descriptorinstanceellipsismember_descriptorfilePyCapsulecellcallable-iteratoriterator...

!!!

from sandbox import Sandbox

s = Sandbox()

code = """ obj_class = [].__class__.__bases__[0]obj_subclasses = dict((elt.__name__, elt) for \ elt in obj_class.__subclasses__())func = obj_subclasses["file"]func("text.txt", "w").write("Kaboom!\\n")"""

s.execute(code)

Testing: read-only builtins

Kaboom

Idea: don’t expose dangerous

implementation details

>>> type.__bases__(<type 'object'>,)>>> del type.__bases__

Let’s delete __bases__ and __subclasses__

>>> type.__bases__(<type 'object'>,)>>> del type.__bases__Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: can't set attributes of built-in/extension type 'type'

Let’s delete __bases__ and __subclasses__

Imposed by the underlying C implementation!

from ctypes import pythonapi, POINTER, py_object

_get_dict = pythonapi._PyObject_GetDictPtr_get_dict.restype = POINTER(py_object)_get_dict.argtypes = [py_object]del pythonapi, POINTER, py_object

def dictionary_of(ob): dptr = _get_dict(ob) return dptr.contents.value

cpython.py

Let’s delete __bases__ and __subclasses__

from cpython import dictionary_of

main = sys.modules["__main__"].__dict__

...

safe_builtins = ReadOnlyBuiltins( original_builtins)main["__builtins__"] = safe_builtins

type_dict = dictionary_of(type)del type_dict["__bases__"]del type_dict["__subclasses__"]

Circumvention idea: would a function by any

other name smell as sweet?

>>> def foo():... print "Meow"... >>> dir(foo)['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

>>> def foo():... print "Meow"... >>> dir(foo)['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

???

>>> foo.func_code<code object foo at 0x100509d30, file "<stdin>", line 1>>>> dir(foo.func_code)['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']>>> foo.func_code.co_code'd\x01\x00GHd\x00\x00S'

>>> def foo():... print "Meow"... >>> def evil_function():... print "Kaboom!"... >>> foo()Meow>>> foo.__setattr__("func_code", evil_function.func_code)>>> foo()Kaboom!

Kaboom

Idea redux: don’t expose dangerous

implementation details

from cpython import dictionary_offrom types import FunctionType

...

type_dict = dictionary_of(type)del type_dict["__bases__"]del type_dict["__subclasses__"]

function_dict = dictionary_of(FunctionType)del function_dict["func_code"]

Delete func_code

Whew. Let’s recap tactics:• Keyword blacklist

• Builtins whitelist

• Import whitelist

• Making important objects read-only (builtins)

• Deleting problematic implementation details (__bases__, __subclasses__, func_code)

• Deleting the ability to construct arbitrary code objects

We have run out of tricks!

We’ve implemented 80% of a full-fledged Python sandbox

builtins_whitelist = set(( # exceptions 'ArithmeticError', 'AssertionError', 'AttributeError', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError','FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError','LookupError', 'MemoryError', 'NameError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError','PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError','UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', # constants 'False', 'None', 'True', '__doc__', '__name__', '__package__', 'copyright', 'license', 'credits', # types 'basestring', 'bytearray', 'bytes', 'complex', 'dict', 'float', 'frozenset', 'int', 'list', 'long', 'object', 'set', 'str', 'tuple', 'unicode', # functions '__import__', 'abs', 'all', 'any', 'apply', 'bin', 'bool', 'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'delattr', 'dir', 'divmod', 'enumerate', 'filter', 'format', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'isinstance', 'issubclass', 'iter', 'len', 'locals', 'map', 'max', 'min', 'next', 'oct', 'ord', 'pow', 'print', 'property', 'range', 'reduce', 'repr', 'reversed', 'round', 'setattr', 'slice', 'sorted', 'staticmethod', 'sum', 'super', 'type', 'unichr', 'vars', 'xrange', 'zip', ))

def _safe_import(__import__, module_whitelist): def safe_import(module_name, globals={}, locals={}, fromlist=[], level=-1): if module_name in module_whitelist: return __import__(module_name, globals, locals, fromlist, level) else: raise ImportError("Blocked import of %s" % (module_name,)) return safe_import

class ReadOnlyBuiltins(dict): def clear(self): ValueError("Read-only!")

def __delitem__(self, key): ValueError("Read-only!")

def pop(self, key, default=None): ValueError("Read-only!")

def popitem(self): ValueError("Read-only!")

def setdefault(self, key, value):! ValueError("Read-only!")

def __setitem__(self, key, value): ValueError("Read-only!")

def update(self, dict, **kw): ValueError("Read-only!")

class Sandbox(object): def __init__(self):! import sys! from types import FunctionType! from cpython import dictionary_of

! original_builtins = sys.modules["__main__"].__dict__["__builtins__"].__dict__

! for builtin in original_builtins.keys(): if builtin not in builtins_whitelist:! ! del sys.modules["__main__"].__dict__["__builtins__"].__dict__[builtin]

original_builtins["__import__"] = _safe_import(__import__, ["string", "re"]) safe_builtins = ReadOnlyBuiltins(original_builtins) sys.modules["__main__"].__dict__["__builtins__"] = safe_builtins

! type_dict = dictionary_of(type)! del type_dict["__bases__"]! del type_dict["__subclasses__"]

! function_dict = dictionary_of(FunctionType)! del function_dict["func_code"]

def execute(self, code_string):! exec code_string

builtins whitelist

import whitelist

read-only builtins

deleting __bases__, __subclasses_, and func_code

Building a sandbox

• Language-level sandboxing (pysandbox)

• OS-level sandboxing (PyPy’s sandbox)

What should we disallow?

• Resource exhaustion

• Information disclosure

• Running unexpected services

• Disabling/quitting/erroring out of the sandbox

Food for thought

Is this level of reflectiveness good

or bad?

Do other languages have these sandboxing

concerns?

If you were designing a new language, how would you do this?

Experiments

• How does an alternative Python implementation like PyPy handle these issues?

• How does the CPython interpreter compile and run bytecode?

• What does the Python stack look like?

• How do ctypes work?

• How can the operating system help provide a secure environment?

Bedtime reading

• The full pysandbox implementation: https://github.com/haypo/pysandbox/

• A retrospective on pysandbox’s challenges: https://lwn.net/Articles/574215/

• PyPy’s sandbox implementation: http://pypy.readthedocs.org/en/latest/sandbox.html

• How PythonAnywhere’s sandbox works: http://blog.pythonanywhere.com/83/

Thank you!

Let’s talk!O’Reilly booth, 3pm

Thank you!

top related