Untrusted code execution

Chris Angelico rosuav at gmail.com
Tue Apr 5 14:14:58 EDT 2016


On Wed, Apr 6, 2016 at 3:52 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
> On Tue, Apr 5, 2016 at 11:48 AM, Chris Angelico <rosuav at gmail.com> wrote:
>> Your code is a *lot* safer for using 'eval' rather than 'exec'.
>> Otherwise, you'd be easily exploited using exceptions, which carry a
>> ton of info. But even so, I would not bet money (much less the
>> security of my systems) on this being safe.
>
> Not to mention "import". :-P

Nah. That one's easy to blank out. Once you go to a restricted
builtins, the import statement breaks:

>>> def safe_exec(untrusted_code):
...     tree = compile(untrusted_code, "<script>", "exec", ast.PyCF_ONLY_AST)
...     for node in ast.walk(tree):
...         if (isinstance(node, ast.Name) and node.id.startswith("_") or
...             isinstance(node, ast.Attribute) and node.attr.startswith("_")):
...                 raise ValueError("Access to private values is not allowed.")
...     namespace = {"__builtins__": {"int": int, "str": str, "len": len}}
...     print(eval(compile(tree, "<script>", "exec"), namespace))
...
>>> safe_exec("import foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in safe_exec
  File "<script>", line 1, in <module>
ImportError: __import__ not found

But exceptions aren't blocked, and you can retrieve them and do all
manner of stuff. You can also create objects of various types using
literal/display syntax, and that might let you craft some weird
construct that effectively access those attributes without actually
having an attribute that starts with an underscore. (Think of
"getattr(x, '\x5f_class__')", although obviously it'll take more work
than that, since getattr itself isn't available.)

ChrisA



More information about the Python-list mailing list