Windows截图工具Acropalypse漏洞:开发者中心安全的重要教训
Acropalypse是一个最初在Google Pixel手机截图工具中发现的漏洞,裁剪图片后原始图像仍可恢复。由于被裁剪部分可能包含敏感信息,这是一个严重的安全问题。问题出现的原因是Android API默认行为从截断文件变为保留现有内容。因此,生成的图像文件开头包含裁剪后的内容,但原始文件的末尾仍然存在。图像查看器会忽略这些数据并正常打开文件,但通过对使用的压缩算法进行巧妙分析,可以(部分)恢复原始图像。
在该漏洞公布后不久,有人注意到Windows默认截图工具Snip and Sketch似乎存在相同问题,尽管这是一个完全不同操作系统上的无关应用程序。我在2004年也发现过与JPEG缩略图相关的类似问题。当相同漏洞反复出现时,表明我们构建软件的方式存在系统性问题,因此我开始深入了解Windows Snip and Sketch中存在此漏洞的原因。
有缺陷的API
我发现的第一个问题是现代Windows文件保存API存在与Android非常相似的问题。具体来说,默认情况下不会截断现有文件。可以说这个漏洞更严重,因为与Android不同,Windows没有截断文件的选项。Windows文档在是否需要截断文件以及实现所需结果的代码方面最多只能说是含糊不清。
情况并非一直如此。旧的Win32保存文件API大致是显示文件选择器,获取用户选择的文件名,然后打开文件。要打开文件,程序员必须指定是否覆盖文件,示例代码通常会覆盖文件。然而,新的"更安全"的通用Windows平台(UWP)将文件选择器沙盒化在单独进程中,允许基于功能的访问控制等简洁功能。它会在需要时创建文件并返回一个句柄,如果所选文件存在,该句柄不会覆盖现有内容。
然而,从文档来看,程序员可以理解地假设文件将是空的。
“此storageFile的文件名、扩展名和位置与用户指定的匹配,但文件没有内容。”
除非程序员显式截断文件,否则现有文件的内容将被保留。如果写入的数据小于现有文件的大小,旧内容将保留,导致StackOverflow上出现困惑的帖子。FileSavePicker的文档没有提到这个问题,尽管示例代码通过使用简单的FileIO API避免了漏洞,该API在写入前隐式截断文件。
然而,更复杂的程序会使用DataWriter,这些示例不会截断文件,文档也没有指出两种API之间的这种差异。不截断现有文件的默认行为很常见,尽管这不是大多数人想要的。
文档可以更新以澄清风险,这总是受欢迎的,特别是伴随着安全的示例代码(众所周知这些代码会被直接复制粘贴到应用程序中)。然而,这两者都不能弥补Windows UWP或Android中有缺陷的API。使用这些API编写安全代码是可能的,但默认行为既不安全也不是大多数人想要的。以开发者为中心的安全的一个基本原则是设计API,使默认行为安全,并且不可能意外创建不安全的程序。更安全的API是让FileSavePicker默认截断现有文件。或者,OpenAsync可以有打开流进行写入的选项。目前,它只有Read和ReadWrite,不像功能丰富得多的Win32 CreateFile API。
我们应该重新审视Postel法则吗?
但为什么这个缺陷持续了这么长时间?Android 10于2019年发布,Windows Snip and Sketch自2018年发布以来似乎一直存在漏洞。当然,因为这些应用程序生成的文件已损坏,有人会抱怨吗?实际上,标准做法是遵循Postel法则:“发送时要保守,接受时要自由”。图像查看器应用程序可以找到有效裁剪图像的末尾,并将原始文件的残留视为可以安全忽略的垃圾。因此,很长时间没有人发现问题,当有人最终发现时,最初并没有认识到这是一个严重问题。
也许是时候摆脱Postel法则了。它对互联网的发展很重要,但现在正在成为一种负担。拒绝无效输入可以帮助在问题造成较少损害时更早地识别问题。我不是第一个指出这一点的人,甚至Jon Postel也认为他的原则被误解了。
接下来怎么办?
Acropalypse已在Android(CVE-2023-21036)和Windows Snip and Sketch(CVE-2023-28303)上修复,但它也为未来提供了教训。它作为一个案例研究,说明了良好文档的重要性,更重要的是,精心设计的API和安全的示例代码。它还表明,解决相同问题的多个程序经常具有相同的漏洞,因此仅仅比较独立实现的结果获得的好处可能比你最初预期的要少。该漏洞还提出了一些问题,比如我们应该如何教授安全软件开发。
此外,虽然我没有彻底调查UWP API,但我最初看到的内容确实让我有些担忧。例如,OpenAsync的API非常薄弱。Win32 CreateFile允许在打开前检查现有文件并设置安全参数。CreateFile做了很多工作来确保一切以原子方式发生以避免竞争条件。UWP要求这些相同的步骤分开。是否可能隐藏着一些竞争条件?
最后,鉴于有缺陷的API,期望其他应用程序容易受到Acropalypse攻击似乎是合理的。扫描此类问题并非易事,但我认为可以在Process Monitor中发现该行为。这是在Snip and Sketch中覆盖文件的样子。文件选择器进行一些检查,运行时代理打开它,Snip and Sketch写入文件。重要的是,NtCreateFile的处置是FILE_OPEN,因此不会覆盖文件。
然而,应该发生的是在写入前截断文件。在Windows中有几种方法可以做到这一点,但这是我调用stream.SetLength(0)时发生的情况。请注意,文件将始终存在,因为选择器会在需要时创建它。
因此,我认为如果存在文件被打开、非零长度以及在截断前有写入的序列,那可能是Acropalypse易受攻击应用程序的实例。
如果有人想编写代码在Process Monitor日志中搜索此类序列,我认为会非常有趣。如果你尝试,请告诉我!