资讯专栏INFORMATION COLUMN

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

赵春朋 / 1013人阅读

摘要:如果一定要用的话,那么就需要注意一下下面这些安全相关的问题。全局变量和内置函数在执行的代码中,默认可以访问执行时的局部变量和全局变量,同样也会修改全局变量。所以我们的检查代码可以这样写我所知道的使用函数时需要注意的安全问题就是这些了。

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

>>> code = """
   ...: a = "hello"
   ...: print(a)
   ...: """
>>> exec(code)
hello
>>> a
"hello"

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

全局变量和内置函数

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

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

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

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

>>> 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 "", line 1, in 
  File "", line 1, in 
NameError: name "int" is not defined
>>>

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

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

通过函数对象:

>>> def a(): pass
...
>>> a.__globals__["__builtins__"]

>>> a.__globals__["__builtins__"].open

通过内置类型对象:

>>> for cls in {}.__class__.__base__.__subclasses__():
...     if cls.__name__ == "WarningMessage":
...         b = cls.__init__.__globals__["__builtins__"]
...         b["open"]
...

>>>

获取 os.system:

>>> cls = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == "_wrap_close"][0]
>>> cls.__init__.__globals__["path"].os

>>>

对于这两种办法又如何应对呢? 一种办法就是禁止访问以 _ 开头的属性:

如果可以控制 code 的生成,那么就在生成 code 的时候判断

如果不能的话,可以通过 dis 模块分析生成的 code (dist 无法分析嵌套函数的代码)

使用 tokenize 模块:

    In [68]: from io import BytesIO
    In [69]: code = """
       ....: a = "b"
       ....: a.__str__
       ....: def b():
       ....:     b.__get__
       ....: """
    In [70]: t = tokenize(BytesIO(code.encode()).readline)
    In [71]: for x in t:
       ....:     print(x)
       ....:
    TokenInfo(type=59 (ENCODING), string="utf-8", start=(0, 0), end=(0, 0), line="")
    TokenInfo(type=58 (NL), string="
", start=(1, 0), end=(1, 1), line="
")
    TokenInfo(type=1 (NAME), string="a", start=(2, 0), end=(2, 1), line="a = "b"
")
    TokenInfo(type=53 (OP), string="=", start=(2, 2), end=(2, 3), line="a = "b"
")
    TokenInfo(type=3 (STRING), string=""b"", start=(2, 4), end=(2, 7), line="a = "b"
")
    TokenInfo(type=4 (NEWLINE), string="
", start=(2, 7), end=(2, 8), line="a = "b"
")
    TokenInfo(type=1 (NAME), string="a", start=(3, 0), end=(3, 1), line="a.__str__
")
    TokenInfo(type=53 (OP), string=".", start=(3, 1), end=(3, 2), line="a.__str__
")
    TokenInfo(type=1 (NAME), string="__str__", start=(3, 2), end=(3, 9), line="a.__str__
")
    TokenInfo(type=4 (NEWLINE), string="
", start=(3, 9), end=(3, 10), line="a.__str__
")
    TokenInfo(type=1 (NAME), string="def", start=(4, 0), end=(4, 3), line="def b():
")
    TokenInfo(type=1 (NAME), string="b", start=(4, 4), end=(4, 5), line="def b():
")
    TokenInfo(type=53 (OP), string="(", start=(4, 5), end=(4, 6), line="def b():
")
    TokenInfo(type=53 (OP), string=")", start=(4, 6), end=(4, 7), line="def b():
")
    TokenInfo(type=53 (OP), string=":", start=(4, 7), end=(4, 8), line="def b():
")
    TokenInfo(type=4 (NEWLINE), string="
", start=(4, 8), end=(4, 9), line="def b():
")
    TokenInfo(type=5 (INDENT), string="    ", start=(5, 0), end=(5, 4), line="    b.__get__
")
    TokenInfo(type=1 (NAME), string="b", start=(5, 4), end=(5, 5), line="    b.__get__
")
    TokenInfo(type=53 (OP), string=".", start=(5, 5), end=(5, 6), line="    b.__get__
")
    TokenInfo(type=1 (NAME), string="__get__", start=(5, 6), end=(5, 13), line="    b.__get__
")
    TokenInfo(type=4 (NEWLINE), string="
", start=(5, 13), end=(5, 14), line="    b.__get__
")
    TokenInfo(type=6 (DEDENT), string="", start=(6, 0), end=(6, 0), line="")
    TokenInfo(type=0 (ENDMARKER), string="", start=(6, 0), end=(6, 0), line="")

从上面的输出我们可以知道当 type 是 OP 并且 string 等于 "." 时,下一条记录就是
点之后的属性名称。所以我们的检查代码可以这样写:

    import io
    import tokenize


    def check_unsafe_attributes(string):
        g = tokenize.tokenize(io.BytesIO(string.encode("utf-8")).readline)
        pre_op = ""
        for toktype, tokval, _, _, _ in g:
            if toktype == tokenize.NAME and pre_op == "." and tokval.startswith("_"):
                attr = tokval
                msg = "access to attribute "{0}" is unsafe.".format(attr)
                raise AttributeError(msg)
            elif toktype == tokenize.OP:
                pre_op = tokval

我所知道的使用 exec 函数时需要注意的安全问题就是这些了。 如果你还知道其他需要注意的安全问题的话,欢迎留言告知。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37950.html

相关文章

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

    摘要:如果一定要用的话,那么就需要注意一下下面这些安全相关的问题。全局变量和内置函数在执行的代码中,默认可以访问执行时的局部变量和全局变量,同样也会修改全局变量。所以我们的检查代码可以这样写我所知道的使用函数时需要注意的安全问题就是这些了。 众所周知,在 python 中可以使用 exec 函数来执行包含 python 源代码的字符串: >>> code = ...: a = hel...

    B0B0 评论0 收藏0
  • 深度辨析 Python eval() 与 exec()

    摘要:内置函数们能够被提拔出来,这就意味着它们皆有独到之处,有用武之地。因此,掌握内置函数的用法,就成了我们应该点亮的技能。报错包含了内置命名空间中的名称,在控制台中输入,就能发现很多内置函数异常和其它属性的名称。 Python 提供了很多内置的工具函数(Built-in Functions),在最新的 Python 3 官方文档中,它列出了 69 个。 大部分函数是我们经常使用的,例如 p...

    AndroidTraveler 评论0 收藏0
  • 让我们一起来构建一个模板引擎(四)

    摘要:在本文中我们将解决一些用于生成的模板引擎需要面对的一些安全问题。整个系列的所有文章地址让我们一起来构建一个模板引擎一让我们一起来构建一个模板引擎二让我们一起来构建一个模板引擎三让我们一起来构建一个模板引擎四文章中涉及的代码已经放到上了 在 上篇文章 中我们的模板引擎实现了对 include 和 extends 的支持, 到此为止我们已经实现了模板引擎所需的大部分功能。 在本文中我们将解...

    yuxue 评论0 收藏0
  • PHP exec system passthru系统函数

    摘要:相同点都可以获得命令执行的状态码作为一种服务器端的脚本语言,象编写简单,或者是复杂的动态网页这样的任务,它完全能够胜任。于是的设计者们给加了一个门安全模式。第二个参数是可选的,用来得到命令执行后的状态码。 详细的介绍了关于PHP exec system passthru系统函数用法与安全以及其它应用功能,有需要的朋友参考一下。区别:system() 输出并返回最后一行shell结果。e...

    zhou_you 评论0 收藏0
  • synchronized用法简单梳理

    摘要:共享资源临界资源修饰实例方法输出结果上述代码与前面不同的是我们同时创建了两个新实例,然后启动两个不同的线程对共享变量进行操作,但很遗憾操作结果是而不是期望结果。 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点 一是存在共享数据(也称临界资源) 二是存在多条线程共同操作共享数据 因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享...

    lindroid 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<