Python 的字符串 .format() 可以对不受信任的格式字符串安全吗?

Posted

技术标签:

【中文标题】Python 的字符串 .format() 可以对不受信任的格式字符串安全吗?【英文标题】:Can Python's string .format() be made safe for untrusted format strings? 【发布时间】:2013-02-27 16:41:58 【问题描述】:

我正在开发一个网络应用程序,用户可以在其中提供字符串,然后服务器会将变量代入其中。

我最好使用 PEP 3101 format() 语法,并且我正在研究在 Formatter 中覆盖方法以确保不受信任的输入的可行性。

以下是我可以看到的 .format() 的风险:

填充允许您指定任意长度,因此 ':>9999999999'.format(..) 可能会使服务器内存不足并成为 DOS。我需要禁用它。 格式允许您访问对象内的字段,这很有用,但令人毛骨悚然的是,您可以访问 dunder 变量并开始钻研标准库的一些内容。不知道哪里可能有一个 getattr() 有副作用或返回一些秘密。我会通过覆盖 get_field() 将属性/索引访问列入白名单。 我自然需要捕获一些异常。

我的假设是:

传统的 C 格式字符串漏洞都不适用于 Python,因为指定参数是对集合的边界检查访问,而不是直接从线程堆栈中弹出。 我使用的 Web 框架会转义替换到页面模板中的每个变量,只要它是输出前的最后一站,我就可以安全地避免因反转义而出现的跨站点脚本攻击。

你的想法是什么?可能的?不可能的?只是不明智?


编辑:如果您不过滤掉 dunder 变量访问,Armin Ronacher 概述了一个令人讨厌的信息泄漏,但似乎认为保护 format() 是可行的:

local_foo.__init__.__globals__[secret_global]

http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/

(就我个人而言,我实际上并没有在我的产品中采用不受信任的 format() 路线,但为了完整起见,我正在更新)

【问题讨论】:

听起来不明智。你不能用replace()吗? 好吧,首先,replace() 需要为字符串中可能存在的每个变量传递一个参数,这将可怕地扩展。 Lemmie 对此进行了扩展,因为我错过了编辑它的机会。我的用例是类似 MUD 的情况,我将使用大量潜在变量进行大量字符串格式化。我可以回到较弱的字符串格式形式,例如string.Template,但在我的情况下,能够引用对象内部的字段也非常有用。我可以通过从每次调用中的每个参数构建每个字段的参数映射来模拟这一点,但如果有一些可以更好地扩展的东西会让人放心。 克雷格,也许开始定义你真正需要的.format() 功能的子集。然后您可以检查格式字符串本身,或者制作您自己的模板语言来映射到.format() 格式字符串。 怀疑这是不明智的,同样地,使用 f-string 文字的 Python 3.6 似乎对任何用户输入的数据都有安全风险。 【参考方案1】:

良好的直觉。是的,能够提供任意格式字符串的攻击者是python下的一个漏洞。

拒绝服务可能是最简单的解决方法。在 这种情况下,限制字符串的大小或运算符的数量 在字符串中将缓解此问题。应该有一个 设置没有合理的用户需要生成一个字符串 比 X 更多的变量,并且这个计算量没有风险 在 DoS 攻击中被利用。 能够访问对象内的属性可能很危险。 但是,我认为Object 父类没有任何用处 信息。提供给格式的对象必须包含 敏感的东西。无论如何,这种类型的符号可以限制 正则表达式。 如果格式字符串是用户提供的,那么用户可能需要 知道调试的错误信息。但是,错误消息可以 包含敏感信息,例如本地路径或类名。制作 一定要限制攻击者可以获得的信息。

查看python format string specification 并通过正则表达式禁止您不希望用户拥有的功能。

【讨论】:

或者更好的是,allow 你确实想要支持的功能,所以当你升级到新版本的 Python 并使用新的(并且有潜在危险的)格式选项时,你不要不要被抓到。 @Michael 是的,我同意白名单方法会更好。【参考方案2】:

这个简单的格式化程序覆盖阻止用户访问属性。它仍然允许格式化和转换类型。

from string import Formatter
class SafeFormatter(Formatter):
        def get_field(self, field_name, args, kwargs):
            if '.' in field_name or '[' in field_name:
                raise Exception('Invalid format string.')
            return super().get_field(field_name,args,kwargs)

form = SafeFormatter()
fname = form.format(format,num=1,id='hello')

【讨论】:

以上是关于Python 的字符串 .format() 可以对不受信任的格式字符串安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

python字符串的format函数如何使用?

format在python中的用法

python Counter()用法

python-字符串

Python对于input函数直接对两个字符串赋值的试验

python format 用法详解