Python的字符串.format()方法能否安全地用于不受信任的格式化字符串?

27

我正在开发一个Web应用程序,在该应用程序中,用户可以提供字符串,然后服务器将替换变量。

最好使用PEP 3101format()语法,我正在研究重载Formatter中的方法以使其对不受信任的输入更加安全。

这是我目前看到的.format()存在的风险:

  • 填充允许您指定任意长度,因此'{:>9999999999}'.format(..)可能会耗尽服务器的内存并引起拒绝服务攻击。我需要禁用它。
  • 格式化允许您访问对象内部的字段,这很有用,但您可以访问dunder变量并开始进入标准库的位。无法确定哪里会有具有副作用或返回某些机密信息的getattr()。我将通过重载get_field()来白名单属性/索引访问。
  • 我自然需要捕获一些异常。

我的假设是:

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

你怎么想?可能吗?不可能?仅仅是不明智的?


编辑:Armin Ronacher概述了一个丑陋的信息泄漏,如果您没有过滤掉dunder变量访问,则似乎认为保护format()是可行的:

{local_foo.__init__.__globals__[secret_global]}

小心Python的新式字符串格式化 | Armin Ronacher的思考和写作

个人而言,我在我的产品中实际上并没有使用不受信任的format()方法,但是为了完整起见,我会进行更新。


听起来不明智。你不能只使用 replace() 吗? - grc
首先,replace() 需要为字符串中可能出现的每个变量都提供一个 pass,这会导致可扩展性极差。 - Craig Timpany
让我详细说明一下,因为我错过了编辑的机会。我的用例是类似于MUD的情况,我将使用大量字符串格式化,并具有许多潜在变量。我可以回到像string.Template这样的较弱的字符串格式化形式,但是能够引用对象内部的字段在我的情况下非常有用。这是我可以通过构建每个调用中每个参数的每个字段的参数映射来模拟的内容,但最好有一些可以更好地扩展的东西。 - Craig Timpany
1
Craig,也许你可以开始定义一组你实际需要的.format()功能子集。然后你可以检查格式字符串本身,或者创建自己的模板语言来映射到.format()格式字符串。 - Davide R.
这似乎是不明智的做法,同样地,在Python 3.6中使用f-string字面量似乎存在安全风险,特别是涉及到任何用户输入的数据。 - Peter Cock
2个回答

9

很好的直觉。是的,在 Python 中,攻击者能够提供任意格式字符串是一种漏洞。

  • 拒绝服务可能是最简单的应对方式。在这种情况下,限制字符串的大小或字符串中运算符的数量将有助于减轻此问题。应该设置一个合理的用户不需要生成超过 X 个变量的字符串,并且此计算量不会受到 DoS 攻击的风险。
  • 能够访问对象内部属性可能是危险的。然而,我认为 Object 父类没有任何有用的信息。提供给格式化的对象必须包含某些敏感信息。无论如何,这种符号表示法都可以通过正则表达式进行限制。
  • 如果格式字符串由用户提供,则用户可能需要知道错误消息进行调试。但是,错误消息可能包含敏感信息,例如本地路径或类名。确保限制攻击者可以获取的信息。

查看 Python 格式化字符串规范 并使用正则表达式禁止您不希望用户具备的功能。


17
更好的做法是,允许支持你需要的功能,这样当你升级到具有新(可能存在风险)格式选项的 Python 新版本时,就不会被困扰住了。 - Michael
1
@Michael 是的,我同意白名单方法会更好。 - rook

4

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

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')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接