Untrusted code execution

Jon Ribbens jon+usenet at unequivocal.co.uk
Tue Apr 5 13:26:50 EDT 2016


On 2016-04-05, Rustom Mody <rustompmody at gmail.com> wrote:
> On Tuesday, April 5, 2016 at 7:19:39 PM UTC+5:30, Jon Ribbens wrote:
>> On 2016-04-03, Jon Ribbens wrote:
>> > I'd just like to say up front that this is more of a thought experiment
>> > than anything else, I don't have any plans to use this idea on any
>> > genuinely untrusted code. Apart from anything else, there's the
>> > denial-of-service issue.
>> >
>> > That said, is there any way that the following Python 3.4 code could
>> > result in a arbitrary code execution security hole?
>> >
>> >     tree = compile(untrusted_code, "<script>", "eval", 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>", "eval"), namespace))
>> 
>> Nobody has any thoughts on this at all?
>
> i actually did...
>
> But dont know enough of the AST API to figure out what you are
> trying/avoiding etc

The idea is that it should prevent you from accessing any names or
attributes that start with an underscore.

Python 2 used to have an 'rexec' module that was supposed to allow
restricted execution of code. This was deprecated because it was
insecure and in Python 3 it was completely removed.

Generally speaking the way that people broke out of restricted
environments was to try and get access to the unrestricted "builtins"
so you could use __import__(), open(), the code type, etc which then
mean you can do almost anything or at least mess with the file system. 

The way that you went looking for "builtins" was to wander about the
object tree by doing things like:
  "".__class__.__base__.__subclasses__()
etc and, e.g. finding a function and doing
  func.__globals__["__builtins__"]
or perhaps creating a raw code object via:
  (lambda a: 1).__code__.__class__(...arbitrary code...)()

The point is that as far as I can see basically all such techniques
are completely prevented if you cannot access "__" attributes (let
alone "_" ones). 

The received wisdom is that restricted code execution in Python is
an insolubly hard problem, but it looks a bit like my 7-line example
above disproves this theory, provided you choose carefully what you
provide in your restricted __builtins__ - but people who knows more
than me about Python seem to have thought about this problem for
longer than I have and come up with the opposite conclusion so I'm
curious what I'm missing.

(Again this all comes with the caveat that DoS is always easy -
just something trivial like '"*"*10**10**10' is going to break things.
But DoS is a far cry from remote code execution.)



More information about the Python-list mailing list