使用 exec 函数时需要注意的一些安全问题

众所周知,在 python 中可以使用 exec 函数来执行包含 python 源代码的字符串:

12345678 >>> code = \’\’\’   …: a = \”hello\”   …: print(a)   …: \’\’\’>>> exec(code)hello>>> a\’hello\’

exec 函数的这个功能很是强大,慎用。如果一定要用的话,那么就需要注意一下下面这些安全相关的问题。

全局变量和内置函数

exec 执行的代码中,默认可以访问执行 exec 时的局部变量和全局变量, 同样也会修改全局变量。如果 exec 执行的代码是根据用户提交的数据生产的话,这种默认行为就是一个安全隐患。

如何更改这种默认行为呢?可以通过执行 exec 函数的时候再传两个参数的方式来 修改这种行为(详见 之前 关于 exec 的文章):

123456789101112 >>> g = {}>>> l = {\’b\’: \’world\’}>>> exec(\’hello = \”hello\” + b\’, g, l)>>> l{\’b\’: \’world\’, \’hello\’: \’helloworld\’}>>> g{\’__builtins__\’: {...}}>>> helloNameError                                 Traceback (most recent call last)...NameError: name \’hello\’ is not defined

如果要限制使用内置函数的话,可以在 globals 参数中定义一下 __builtins__ 这个 key:

12345678910111213 >>> g = {}>>> l = {}>>> exec(\’a = int(\”1\”)\’, g, l)>>> l{\’a\’: 1} >>> g = {\’__builtins__\’: {}}>>> exec(\’a = int(\”1\”)\’, g, l)Traceback (most recent call last):  File \”<stdin>\”, line 1, in <module>  File \”<string>\”, line 1, in <module>NameError: name \’int\’ is not defined>>>

现在我们限制了访问和修改全局变量以及使用内置函数,难道这样就万事大吉了吗? 然而并非如此,还是可以通过其他的方式来获取内置函数甚至 os.system 函数。

另辟蹊径获取内置函数和 os.system

通过函数对象:

123456 >>> def a(): pass...>>> a.__globals__[\’__builtins__\’] >>> a.__globals__[\’__builtins__\’].open<builtin function open>

通过内置类型对象:

1234567 >>> for cls in {}.__class__.__base__.__subclasses__():...     if cls.__name__ == \’WarningMessage\’:...         b = cls.__init__.__globals__[\’__builtins__\’]yon-h\”>         b = cls.__init__.__globals__[\’__builtins__\’]b1f88fa5f833971999\” class=\”crayon-syntax crayon-theme-github crayon-font-monaco crayon-os-pc print-yes notranslate\” data-settings=\” minimize scroll-always\” style=\” margin-top: 12px; margin-bottom: 12px; font-size: 13px !important; line-height: 15px !important;\”>

12345678 >>> code = \’\’\’   …: a = \”hello\”   …: print(a)   …: \’\’\’>>> exec(code)hello>>> a\’hello\’

exec 函数的这个功能很是强大,慎用。如果一定要用的话,那么就需要注意一下下面这些安全相关的问题。

全局变量和内置函数

exec 执行的代码中,默认可以访问执行 exec 时的局部变量和全局变量, 同样也会修改全局变量。如果 exec 执行的代码是根据用户提交的数据生产的话,这种默认行为就是一个安全隐患。

如何更改这种默认行为呢?可以通过执行 exec 函数的时候再传两个参数的方式来 修改这种行为(详见 之前 关于 exec 的文章):

123456789101112 >>> g = {}>>> l = {\’b\’: \’world\’}>>> exec(\’hello = \”hello\” + b\’, g, l)>>> l{\’b\’: \’world\’, \’hello\’: \’helloworld\’}>>> g{\’__builtins__\’: {...}}>>> helloNameError                                 Traceback (most recent call last)...NameError: name \’hello\’ is not defined

如果要限制使用内置函数的话,可以在 globals 参数中定义一下 __builtins__ 这个 key:

12345678910111213 >>> g = {}>>> l = {}>>> exec(\’a = int(\”1\”)\’, g, l)>>> l{\’a\’: 1} >>> g = {\’__builtins__\’: {}}>>> exec(\’a = int(\”1\”)\’, g, l)Traceback (most recent call last):  File \”<stdin>\”, line 1, in <module>  File \”<string>\”, line 1, in <module>NameError: name \’int\’ is not defined>>>

现在我们限制了访问和修改全局变量以及使用内置函数,难道这样就万事大吉了吗? 然而并非如此,还是可以通过其他的方式来获取内置函数甚至 os.system 函数。

另辟蹊径获取内置函数和 os.system

通过函数对象:

123456 >>> def a(): pass...>>> a.__globals__[\’__builtins__\’] >>> a.__globals__[\’__builtins__\’].open<builtin function open>

通过内置类型对象:

1234567 >>> for cls in {}.__class__.__base__.__subclasses__():...     if cls.__name__ == \’WarningMessage\’:...         b = cls.__init__.__globals__[