From 59e2d4a0b06b2da0f64434c7afcf08ececec043f Mon Sep 17 00:00:00 2001 From: Mark Pilgrim Date: Wed, 15 Jul 2009 13:39:16 -0400 Subject: [PATCH] rewrote end of eval() section to mention DoS attacks [thanks SW] --- advanced-iterators.html | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/advanced-iterators.html b/advanced-iterators.html index b54dedd..43d7dc6 100755 --- a/advanced-iterators.html +++ b/advanced-iterators.html @@ -532,7 +532,7 @@ for guess in itertools.permutations(digits, len(characters)):

Well, the evil part is evaluating arbitrary expressions from untrusted sources. You should only use eval() on trusted input. Of course, the trick is figuring out what’s “trusted.” But here’s something I know for certain: you should NOT take this alphametics solver and put it on the internet as a fun little web service. Don’t make the mistake of thinking, “Gosh, the function does a lot of string manipulation before getting a string to evaluate; I can’t imagine how someone could exploit that.” Someone WILL figure out how to sneak nasty executable code past all that string manipulation (stranger things have happened), and then you can kiss your server goodbye. -

But surely there’s some way to evaluate expressions safely? To put eval() in a sandbox where it can’t access or harm the outside world? Well, yeah, but it’s tricky. +

But surely there’s some way to evaluate expressions safely? To put eval() in a sandbox where it can’t access or harm the outside world? Well, yes and no.

 >>> x = 5
@@ -568,13 +568,9 @@ NameError: name 'math' is not defined

Yeah, that means you can still do nasty things, even if you explicitly set the global and local namespaces to empty dictionaries when calling eval(): -

->>> eval("__import__('subprocess').getoutput('rm -rf /')", {}, {})  
-
    -
  1. Please don’t do this. -
+
>>> eval("__import__('subprocess').getoutput('rm /some/random/file')", {}, {})
-

Oops. I’m glad I didn’t make that alphametics web service. Is there any way to use eval() safely? +

Oops. I’m glad I didn’t make that alphametics web service. Is there any way to use eval() safely? Well, yes and no.

 >>> eval("__import__('math').sqrt(5)",
@@ -591,10 +587,20 @@ NameError: name '__import__' is not defined
 NameError: name '__import__' is not defined
  1. To evaluate untrusted expressions safely, you need to define a global namespace dictionary that maps "__builtins__" to None, the Python null value. Internally, the “built-in” functions are contained within a pseudo-module called "__builtins__". This pseudo-module (i.e. the set of built-in functions) is made available to evaluated expressions unless you explicitly override it. -
  2. You may do this, but be very, very careful not to make any typos. In particular, be sure you’ve overridden __builtins__ and not __builtin__ or __built-ins__ or some other variation. +
  3. Be sure you’ve overridden __builtins__. Not __builtin__, __built-ins__, or some other variation that will work just fine but expose you to catastrophic risks.
-

So, in the end, it is possible to safely evaluate untrusted Python expressions. Passing {"__builtins__": None} as the second parameter to the eval() function is non-intuitive (and not the default behavior), but it does work. If you understand why it works, you’re less likely to use eval() incorrectly, in a way that works with trusted input but has potentially devastating consequences with untrusted input. +

So eval() is safe now? Well, yes and no. + +

+>>> eval("2 ** 2147483647",
+...     {"__builtins__":None}, {})          
+
+
    +
  1. Even without access to __builtins__, you can still launch a denial-of-service attack. For example, trying to raise 2 to the 2147483647th power will spike your server’s CPU utilization to 100% for quite some time. (If you’re trying this in the interactive shell, press Ctrl-C a few times to break out of it.) Technically this expression will return a value eventually, but in the meantime your server will be doing a whole lot of nothing. +
+ +

In the end, it is possible to safely evaluate untrusted Python expressions, for some definition of “safe” that turns out not to be terribly useful in real life. It’s fine if you’re just playing around, and it’s fine if you only ever pass it trusted input. But anything else is just asking for trouble.