thinking outside the [sand]box
DESCRIPTION
A 40 minute talk about sandboxes, Python, creating Python in process sandboxes, and defeating them. (Based in part on PySandbox).TRANSCRIPT
Thinking Outside the [Sand]Box
>>> dir(self)
• Michael Genkin
• A computer engineer
• A researcher• A jack of many trades
• And a master of some
• Prefers Python [2.7] to your favorite programming language since 2008.• Isn’t afraid of the
bytecode.
Outline
• Sandboxes – how & why?
• A bit of Python• Code execution
• __builtins__
• Python Sandbox – HowTo & Examples• Blacklisting
• Whitelisting
• Modifying __builtins__
• If time allows• CPython implementation details
• Code objects
What’s a Sandbox?
“A security mechanism for separating running programs. It is often used to execute untested code, or untrusted programs from unverified third parties, suppliers, untrusted users and untrusted websites.The sandbox typically provides a tightly controlled set of resources for guest programs to run in…” [Wikipedia]
Why a Sandbox?
• UNTRUSTED CODE? Why we’d ever want to execute untrusted code?• Learning platform
• A certain challenge site
• Development environment as a Service
How to Sandbox?
OS Level
• Linux seccomp
• PyPy Sandboxing
Language Level/In-Process*
• PySandbox
• rexec
Don’t use those examples @Home/Production
A Bit of Python…Quick detour
Code Execution in Python
• How does one execute untrusted code?• Or simply dynamically generated code…
• A few ways…• exec(file) – compile & execute a statement (or a file).
• eval – compile & execute an expression.• if you really need eval – try using ast.literal_eval()
• os.exec* – create & execute a new shell• subprocess...
• pickle – a minefield
• Don’t do this at home..!• Really. Don’t. Ever.
Shit Can Happen…
• Resource exhaustion – DoS
• Information disclosure
• Server takeover
Tools of Chaos
• file/open• Though we might need those…
• eval/exec(file)
• exit/quit
• pickle/os/subprocess• We might need those as well
Nice to Meet You __builtins__>>> print dir(__builtins__)['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
We Need a Sandbox…A Builder & Breaker How-To
An Optimal [Python] Sandbox
• How does this *black magic* really looks like?
class Sandbox(object):def __make_secure(self, unsafecode):
""" Black Magic """return safecode
def execute(self, code):exec self.__make_secure(code)
if __name__ == '__main__':s = Sandbox()s.execute("print 'Hello World!'") # Hello World!s.execute("*bad stuff*") # RuntimeException
Blacklisting __builtins__
def __make_secure(self, unsafecode):keyword_blacklist = ["file", "quit", "eval", "exec",
"execfile", "exit"]for keyword in keyword_blacklist:
if keyword in unsafecode:raise ValueError("Blacklisted")
return unsafecode
Circumventing a Blacklist
• The problem with blacklist is that they’re always incomplete…• What isn’t in the blacklist?
• Lesson learned…• If we can get a reference to
something – we caninvoke it.
s.execute("""__builtins__.__dict__["ZXZhbA==".decode("base64")](*bad stuff*)
""")
Whitelisting __builtins__
import sysdef __make_secure(self, unsafecode):
# Blacklisting codemain = sys.modules["__main__"].__dict__orig_builtins = main["__builtins__"].__dict__builtins_whitelist = set((
'ArithmeticError', 'AssertionError', 'AttributeError', ... # Exceptions'False', 'None', 'True', ... # Constants'basestring', 'bytearray', 'bytes', 'complex', 'dict', ... # Types'__import__', 'abs', 'all', 'any', 'apply', 'bin', 'bool', ... # Functions
# Block: eval, execfile, file, quit, exit, reload, etc.))
for builtin in orig_builtins.keys():if builtin not in builtins_whitelist:
del orig_builtins[builtin]
return unsafecode # No way to do bad stuff now...
s.execute('__builtins__.__dict__["ZXZhbA==".decode("base64")](*bad stuff*)') # NameError
I brought This Little Something…
• The whitelist insures we don’t have anything useful in scope…• But, can we bring more stuff into the scope?
• Lesson learned…• Whitelisting __builtins__
isn’t enough if the attacker canjust import stuff
s.execute("""import osos.exec("python -c '*something bad*'")""")
Whitelisting Imports
• Ever wondered how do Python imports work?
• And how to roll your own?
importer = __builtins__.__dict__.get('__import__')os = importer('os')
Whitelisting Imports
def safe_importer(module_name, globals={}, locals={}, fromlist=[], level=-1):print "You can't import anything bad now..."good_modules = ['string', 're', ...]# Doesn't include os, subprocess, or pickle!
if module_name in good_modules:return __import__(module_name, globals, locals, fromlist, level)
else:raise ImportError('You can\'t import this!')
def __make_secure(self, unsafecode):# Blacklisting code# Whitelisting codeorig_builtins['__import__'] = safe_importer
s.execute("""import osos.exec("python -c '*something bad*'")
""") # ImportError
I Know I Left This Somewhere…
• What do we have left?
• Do we have anything useful left?• We have some types… let’s check them out
• If we have a class – why not have a metaclass as well?• PEP 0253 - __bases__ & __subclasses__()
I Know I Left This Somewhere…
If We Have a Reference…
s.execute("""__builtins__.__dict__['__import__'] = ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']import osos.exec("python -c '*something bad*'")
""")
Questions Time!How many interactive Python interpreters were harmed while preparing this talk?
Thanks for [email protected]