eval
safe?” Is impossible to count. There is always someone who claims to have found a way to protect themselves from all the possible consequences of performing this function.eval()
function that executes a string with code and returns the execution result: assert eval("2 + 3 * len('hello')") == 17
eval
are not from a trusted source. What happens if the line we decide to feed to eval
'y is os.system('rm -rf /')
? The interpreter will honestly start the process of deleting all data from the computer, and it’s good if it runs on behalf of the least privileged user (in the following examples I will use clear
( cls
if you use Windows) instead of rm -rf /
so that none of the readers will accidentally did not shoot himself in the leg ).eval
safe if you run it without accessing the symbols from globals . As a second (optional) argument, eval()
takes a dictionary that will be used instead of the global namespace (all classes, methods, variables, etc., declared at the “upper” level, accessible from any point of the code) by the code that will be executed by eval
'om If eval
is called without this argument, it uses the current global namespace into which the os
module could be imported. If you pass an empty dictionary, the global namespace for eval
'a will be empty. Here such code can no longer be executed and NameError: name 'os' is not defined
exception NameError: name 'os' is not defined
: eval("os.system('clear')", {})
__import__
built-in function. So, the code below will work without errors: eval("__import__('os').system('clear')", {})
__builtins__
from within eval
'a, since names like __import__ are available to us because they are in the global variable __builtins__
. If we explicitly pass an empty dictionary instead, the code below can no longer be executed: eval("__import__('os').system('clear')", {'__builtins__':{}}) # NameError: name '__import__' is not defined
segfault
if you run it in CPython: s = """ (lambda fc=( lambda n: [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n ][0] ): fc("function")( fc("code")( 0,0,0,0,"KABOOM",(),(),(),"","",0,"" ),{} )() )() """ eval(s, {'__builtins__':{}})
().__class__.__bases__[0]
object
. We cannot simply write object
, since __builtins__
are empty, but we can create an empty tuple (tuple), the first base class of which is object
and, walking through its properties, access the object
class.object
or, in other words, a list of all classes declared in the program at the moment: ().__class__.__bases__[0].__subclasses__()
ALL_CLASSES
for ALL_CLASSES
, it will be easy to see that the expression below finds the class by its name: [c for c in ALL_CLASSES if c.__name__ == n][0]
lambda n: [c for c in ALL_CLASSES if c.__name__ == n][0]
eval
'a, we can neither declare a function (using def
) nor use an assignment operator to bind our lambda to any variable . (lambda fc=( lambda n: [ c for c in ALL_CLASSES if c.__name__ == n ][0] ): # fc )()
code
(an internal class, its instance, for example, is the property func_code
a function object): fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,"")
segfault
'in CPython. " KABOOM " just looks funnier, thanks lvh for this example.code
, but we cannot directly execute it. Then create a function, the code of which will be our object: fc("function")(CODE_OBJECT, {})
(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]): fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})() )()
eval
NOT SAFE , even if you remove access to global and embedded variables.object
class to create code
and function
objects. In exactly the same way, you can get (and instantiate) any class that exists in the program at the time of the eval()
call. s = """ [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == "Quitter" ][0](0)() """ eval(s, {'__builtins__':{}})
Quitter
class, which is called by the interpreter when you type quit()
.eval
in an empty environment, based on the fact that the code specified in the article is the entire code of our program.eval
'and in a real application, an attacker can gain access to all the classes that you use, so that its capabilities will not be limited to almost nothing.eval
safe is that they are all based on the idea of “blacklists”, the idea that we need to remove access to all things that we think can be dangerous when used in eval
'e. With such a strategy, there is virtually no chance of winning, because if anything turns out to be unlawful, the system will be vulnerable.eval
'and in Python, which is another attempt to overcome this problem: >>> eval("(lambda:0).func_code", {'__builtins__':{}}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <module> RuntimeError: function attributes not accessible in restricted mode
__builtins__
inside eval
are different from “official” - eval
goes into protected mode, in which access to some dangerous properties, such as func_code
for functions, is func_code
. A more detailed description of this mode can be found here , but, as we have already seen above , it is not a “silver bullet” either.eval
safe to make? It is hard to say. It seems to me that the attacker cannot be harmed without access to objects with two lower underscores framing the name, so it is possible if we exclude all lines with two lower underscores from processing, we will be safe. Maybe... [ c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings' ][0]()._module.__builtins__
Source: https://habr.com/ru/post/221937/
All Articles