On Jun 12, 1:51 pm, bvdp <[EMAIL PROTECTED]> wrote: > Matimus wrote: > > On Jun 11, 9:16 pm, George Sakkis <[EMAIL PROTECTED]> wrote: > >> On Jun 11, 8:15 pm, bvdp <[EMAIL PROTECTED]> 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.
You probably missed the point in the posted examples. A malicious user doesn't need to modify your program code to have access to far more than you would hope, just devise an appropriate string s and pass it to your "safe" eval. Here's a simpler example to help you see the back doors that open. So you might think that eval(s, {"__builtins__":None}, {}) doesn't provide access to the `file` type. At first it looks so: >>> eval('file', {"__builtins__":None}, {}) NameError: name 'file' is not defined >>> eval('open', {"__builtins__":None}, {}) NameError: name 'open' is not defined "Ok, I am safe from users messing with files since they can't even access the file type" you reassure yourself. Then someone comes in and passes to your "safe" eval this string: >>> s = "(t for t in (42).__class__.__base__.__subclasses__() if t.__name__ == >>> 'file').next()" >>> eval(s, {"__builtins__":None}, {}) <type 'file'> Oops. Fortunately the file() constructor has apparently some extra logic that prevents it from being used in restricted mode, but that doesn't change the fact that file *is* available in restricted mode; you just can't spell it "file". 25 builtin types are currently available in restricted mode -- without any explicit import -- and this number has been increasing over the years: $ python2.3 -c 'print eval("(42).__class__.__base__.__subclasses__().__len__()", {"__builtins__":None}, {})' 13 $ python2.4 -c 'print eval("(42).__class__.__base__.__subclasses__().__len__()", {"__builtins__":None}, {})' 17 $ python2.5 -c 'print eval("(42).__class__.__base__.__subclasses__().__len__()", {"__builtins__":None}, {})' 25 $ python2.6a1 -c 'print eval("(42).__class__.__base__.__subclasses__().__len__()", {"__builtins__":None}, {})' 32 Regards, George -- http://mail.python.org/mailman/listinfo/python-list