在计算机语言中,许多输出例程都有一个参数来控制生成的格式。在 C 语言中,最明显的例子是 printf() 例程家族(包括 printf()、sprintf()、snprintf()、fprintf() 等等)。C 语言中的其他例子包括 syslog() (用于写入系统日志信息) 和 setproctitle() (用于设置显示进程标识符信息的字符串)。许多函数名以 ``err'' 或 ``warn'' 开头,包含 ``log'' 或以 ``printf'' 结尾的函数都值得考虑。Python 包括 "%" 操作,它在字符串上以类似的方式控制格式化。许多程序和库定义了格式化函数,通常通过调用内置例程并进行额外的处理 (例如,glib 的 g_snprintf() 例程)。
格式化语言本质上是小型编程语言 - 因此,允许攻击者控制格式化字符串的开发人员实际上是在运行攻击者编写的程序!令人惊讶的是,许多人似乎忘记了这些格式化功能的强大之处,并使用来自不受信任用户的数据作为格式化参数。这里的指导原则很明确 - 永远不要使用来自不受信任用户的未经过滤的数据作为格式化参数。不遵循此指南通常会导致格式化字符串漏洞(也称为格式化漏洞)。也许最好用例子来说明这一点
/* Wrong way: */ printf(string_from_untrusted_user); /* Right ways: */ printf("%s", string_from_untrusted_user); /* safe */ fputs(string_from_untrusted_user); /* better for simple strings */ |
如果攻击者控制了格式化信息,攻击者可以通过仔细选择格式来造成各种恶作剧。C 语言的 printf() 就是一个很好的例子 - 在 printf() 中有很多方法可以利用用户控制的格式化字符串。这些方法包括通过创建长格式化字符串来导致缓冲区溢出(这可能导致攻击者完全控制程序),使用未传递参数的转换说明符(导致插入意外数据),以及创建产生完全出乎意料的结果值的格式(例如,通过预先添加或附加笨拙的数据,从而在以后使用中引起问题)。一个特别糟糕的例子是 printf() 的 %n 转换说明符,它将到目前为止写入的字符数写入指针参数;使用它,攻击者可以覆盖原本用于打印的值!攻击者甚至可以覆盖几乎任意的位置,因为攻击者可以指定一个实际上没有传递的 “参数”。%n 转换说明符自 C 语言诞生以来一直是 C 语言的标准组成部分,是所有 C 标准的要求,并且被实际程序使用。在 2000 年,Greg KH 快速搜索了源代码,并确定程序 BitchX (一个 irc 客户端)、Nedit (一个程序编辑器) 和 SourceNavigator (一个程序编辑器/IDE/调试器) 正在使用 %n,而且无疑还有更多。弃用 %n 可能是个好主意,但即使没有 %n 也可能存在严重问题。许多论文更详细地讨论了这些攻击,例如,您可以查看 Avoiding security holes when developing an application - Part 4: format strings。
由于在许多情况下结果会发送回用户,因此这种攻击也可以用于暴露关于堆栈的内部信息。然后,这些信息可以用于绕过堆栈保护系统,例如 StackGuard 和 ProPolice;StackGuard 使用常量 “canary” 值来检测攻击,但是如果堆栈的内容可以显示,则 canary 的当前值将被暴露,突然使软件再次容易受到堆栈粉碎攻击。
格式化字符串几乎总是应该是一个常量字符串,可能涉及函数调用来实现国际化查找(例如,通过 gettext 的 _())。请注意,此查找必须限制为程序控制的值,即,必须允许用户仅从程序控制的消息文件中进行选择。可以在使用用户数据之前对其进行过滤(例如,通过设计一个过滤器来列出格式化字符串的合法字符,例如 [A-Za-z0-9]),但是通常最好通过使用常量格式化字符串或 fputs() 来简单地防止问题。请注意,尽管我已将其列为“输出”问题,但这可能会在输出之前在程序内部引起问题(因为输出例程可能会保存到文件,或者甚至只是生成内部状态,例如通过 snprintf())。
输入格式化引起安全问题的可能性并非无稽之谈;有关使用此弱点进行利用的示例,请参见 CERT 咨询 CA-2000-13。有关如何利用这些问题的更多信息,请参见 Pascal Bouchareine 的电子邮件文章,标题为 ``[Paper] Format bugs'',发表于 2000 年 7 月 18 日的 Bugtraq。截至 2000 年 12 月,gcc 编译器的开发版本支持针对不安全的格式化字符串用法的警告消息,旨在帮助开发人员避免这些问题。
当然,这一切都引出了一个问题,即国际化查找实际上是否安全。如果您正在创建自己的国际化查找例程,请确保不受信任的用户只能指定合法的区域设置,而不是其他内容,例如任意路径。
显然,您希望将通过国际化创建的字符串限制为您可以信任的字符串。否则,攻击者可能会利用此功能来利用格式化字符串中的漏洞,尤其是在 C/C++ 程序中。这一直是 Bugtraq 中的讨论项目(例如,请参阅 John Levon 于 2000 年 7 月 26 日在 Bugtraq 上发布的帖子)。有关更多信息,请参阅 第 5.8.3 节中关于允许用户仅选择合法语言值的讨论。
虽然这实际上是一个编程错误,但值得一提的是,不同的国家/地区以不同的方式表示数字,特别是句点 (.) 和逗号 (,) 都用于分隔整数及其小数部分。如果您保存或加载数据,则需要确保活动区域设置不会干扰数据处理。否则,法国用户可能无法与英国用户交换数据,因为存储和检索的数据将使用不同的分隔符。我不知道这是否被用作安全问题,但这可能是可以想象的。