界: Time and State

分布式计算是关于时间和状态的。也就是说,为了让多个组件相互通信,必须共享状态,所有这些都需要时间。

大多数程序员都会将其工作拟人化。他们会让一个控制线程以同样的方式(他们必须自己完成工作时采取的方式)执行整个程序。然而,现代计算机不同任务之间切换得非常快,在多核、多 CPU 或分布式系统中,两个事件可能完全同时发生。程序员预期的程序执行过程与实际情况之间存在差距,即存在缺陷。这些缺陷与线程、流程、时间和信息之间的意外交互有关。这些交互是通过共享状态发生的:信号量、变量、文件系统,以及总而言之,可以存储信息的任何内容。

Insecure Temporary File

Abstract
创建和使用 insecure temporary file 会容易使应用程序和系统数据受到攻击。
Explanation
应用程序会非常频繁地使用临时文件,因此您可以使用多种不同的机制在 C 库和 Windows(R) API 中创建临时文件。而多数函数都很容易受到各种攻击。
示例:以下代码使用一个临时文件,在它被处理之前用来存储从网络上收集到的中间数据。


...
if (tmpnam_r(filename)){
FILE* tmp = fopen(filename,"wb+");
while((recv(sock,recvbuf,DATA_SIZE, 0) > 0)&&(amt!=0))
amt = fwrite(recvbuf,1,DATA_SIZE,tmp);
}
...


这种其他情况下无法标记的代码很容易受到多种不同形式的攻击,因为它靠一种不安全的方法来创建临时文件。以下部分主要描述了由该函数和其他函数引入的漏洞。大部分与临时文件创建有关的突出安全问题已经在基于 Unix 的操作系统上屡见不鲜,但是 Windows 应用程序同样存在着这样的风险。这部分对在 Unix 和 Windows 系统上创建临时文件的问题进行了讨论。

不同系统之间使用的方法和行为可能各不不同,但是被引入的基本风险则相差不大。针对创建临时文件的安全方法,要了解有关安全的核心语言函数的信息及建议,请查看“建议”部分。

对于旨在帮助创建临时文件的函数,可以根据它们是仅提供文件名还是实际打开新文件分成两个组。

第 1 组 —“唯一的”文件名:

在第一组中,C 库和 WinAPI 函数用来帮助创建一个临时文件。它们可为程序随后打开的新的临时文件生成一个唯一的文件名。这组包含了诸如 tmpnam()tempnam()mktemp() 等 C 库函数以及 C++ 中以 _(下划线)开头的相应函数和 Windows API 中的 GetTempFileName() 函数。这组函数在文件名的选择方面很可能会在底层碰到 race condition。虽然函数可以保证在选择文件时其文件名是唯一的,但是还无法防止其他进程或攻击者在选择文件后,而应用程序尚未尝试打开该文件前的这段时间内创建一个同名文件。不止是由其他程序调用相同函数所引发的合法冲突,攻击者还非常有可能创建一个恶意的冲突,因为这些函数创建的文件名没有进行充分的随机化,使其难以被攻击者猜测。

如果使用选定的名称创建文件,那么根据打开方式的不同,文件现有的内容或访问权限可能会保持不变。如果文件的现有内容是恶意的,攻击者可能会在应用程序从临时文件中读取数据时向程序中注入危险数据。如果攻击者预先创建了一个能轻松获取访问权限的文件,那么可能会访问、修改或破坏应用程序存储在临时文件里的数据。在基于 Unix 的系统上,如果攻击者预先创建了一个作为另一个重要文件链接的文件,则可能会引发更加严重的攻击。然后,如果应用程序被截短或向文件中写入数据,那么它可能会在不知不觉中帮助攻击者,为其执行各种恶意操作。如果程序再使用提高了的权限运行,那会使问题变得更加严重。

最后,最好的情况就是通过调用 open() 函数并使用 O_CREATO_EXCL 标记来打开文件,或者通过调用 CreateFile() 函数并使用 CREATE_NEW 属性来打开文件,这样,如果文件已经存在,该操作就会失败,因此可以有效地防止上述攻击类型。然而,如果攻击者可以准确预测一系列临时文件名,那么就可以阻止应用程序打开必要的临时存储空间,从而导致拒绝服务 (DoS) 攻击。如果仅从一小部分随机数中选择由这些函数生成的文件名,那么会很容易发动这种类型的攻击。

第 2 组 —“唯一的”文件:

在第二组中,C 库函数通过生成唯一的文件名且打开这个文件,来解决一些与临时文件有关的安全问题。这部分包含了像 tmpfile() 这样的 C 库函数和与之对应的以 _(下划线)开头的 C++ 函数,以及表现更为出色的 C 库函数 mkstemp()

tmpfile() 样式的函数可以构造唯一的文件名,并在传递了 "wb+" 标志的情况下能够按照与 fopen() 函数相同的方式(即,作为在读/写模式下的二进制文件)打开文件。如果文件已存在,tmpfile() 将把文件的大小缩小为 0,也许能缓解前面提到的安全问题(唯一文件名的选择与随后打开所选文件之间的 race condition)。然而,该操作显然不能解决函数的安全性问题。首先,攻击者可以预先创建一个能轻松获取访问权限的文件,该文件可能会被用 tmpfile() 函数打开的文件保留。其次,在基于 Unix 的系统上,如果攻击者预先创建了一个文件作为另一重要文件的链接,应用程序可能会使用提高了的权限去截短该文件,这样就能按照攻击者的意愿执行破坏。最后,如果 tmpfile() 创建了一个新文件,那么应用在该文件上的访问权限在不同的操作系统间是不同的,因此,应用程序的数据极易受到攻击,即便是攻击者无法预测要使用的文件名。

最后,mkstemp() 函数是一种创建临时文件的安全方法。它根据用户提供的模板(该模板由一系列随机生成的字符组成),尝试创建或打开一个唯一的文件。如果它无法创建一个这样的文件,则操作失败,并返回 -1。在最新的系统中,文件使用 0600 模式打开,这就意味着文件不会被篡改,除非用户直接更改其访问权限。然而,mkstemp() 仍然会受到使用可预测文件名的威胁,且如果攻击者通过猜测和预先创建将要使用的文件名的文件,而导致 mkstemp() 函数失效,将使应用程序极易受到 denial of service 攻击。
References
[1] B. Schneier Yarrow: A secure pseudorandom number generator
[2] CryptLib
[3] Crypto++
[4] BeeCrypt
[5] OpenSSL
[6] CryptoAPI: CryptGenRandom() Microsoft
[7] RtlGenRandom() Microsoft
[8] .NET System.Security.Cryptography: Random Number Generation Microsoft
desc.semantic.cpp.insecure_temporary_file
Abstract
创建和使用 insecure temporary file 会容易使应用程序和系统数据受到攻击。
Explanation
应用程序需要非常频繁地使用临时文件,因此存在许多不同的机制来创建这些临时文件。而多数函数都很容易受到各种攻击。
示例:以下代码使用一个临时文件,在它被处理之前用来存储从网络上收集到的中间数据。


...
try:
tmp_filename = os.tempnam()
tmp_file = open(tmp_filename, 'w')
data = s.recv(4096)
while True:
more = s.recv(4096)
tmp_file.write(more)
if not more:
break
except socket.timeout:
errMsg = "Connection timed-out while connecting"
self.logger.exception(errMsg)
raise Exception
...


这种其他情况下无法标记的代码很容易受到多种不同形式的攻击,因为它靠一种不安全的方法来创建临时文件。以下部分主要描述了由该函数和其他函数引入的漏洞。大部分与临时文件创建有关的突出安全问题已经在基于 Unix 的操作系统上屡见不鲜,但是 Windows 应用程序同样存在着这样的风险。

不同系统之间使用的方法和行为可能各不不同,但是被引入的基本风险则相差不大。针对创建临时文件的安全方法,要了解有关安全的核心语言函数的信息及建议,请查看“建议”部分。

对于旨在帮助创建临时文件的函数,可以根据它们是仅提供文件名还是实际打开新文件分成两个组。

第 1 组 —“唯一的”文件名:

旨在帮助创建临时文件的第一组函数,可为程序随后打开的新临时文件生成唯一的文件名。这组函数在文件名的选择方面很可能会在底层碰到 race condition。虽然函数可以保证在选择文件时其文件名是唯一的,但是还无法防止其他进程或攻击者在选择文件后,而应用程序尚未尝试打开该文件前的这段时间内创建一个同名文件。不止是由其他程序调用相同函数所引发的合法冲突,攻击者还非常有可能创建一个恶意的冲突,因为这些函数创建的文件名没有进行充分的随机化,使其难以被攻击者猜测。

如果使用选定的名称创建文件,那么根据打开方式的不同,文件现有的内容或访问权限可能会保持不变。如果文件的现有内容是恶意的,攻击者可能会在应用程序从临时文件中读取数据时向程序中注入危险数据。如果攻击者预先创建了一个能轻松获取访问权限的文件,那么可能会访问、修改或破坏应用程序存储在临时文件里的数据。在基于 Unix 的系统上,如果攻击者预先创建了一个作为另一个重要文件链接的文件,则可能会引发更加严重的攻击。然后,如果应用程序被截短或向文件中写入数据,那么它可能会在不知不觉中帮助攻击者,为其执行各种恶意操作。如果程序再使用提高了的权限运行,那会使问题变得更加严重。

最后,最好通过使用 os.O_CREATos.O_EXCL 标记调用 open() 来打开文件,如果该文件已存在,则操作将失败,从而可防止上述攻击类型。然而,如果攻击者可以准确预测一系列临时文件名,那么就可以阻止应用程序打开必要的临时存储空间,从而导致拒绝服务 (DoS) 攻击。如果仅从一小部分随机数中选择由这些函数生成的文件名,那么会很容易发动这种类型的攻击。

第 2 组 —“唯一的”文件:

第二组函数通过生成唯一的文件名且打开这个文件,来解决一些与临时文件有关的安全问题。这一组包括 tmpfile() 等函数。

tmpfile() 样式的函数可以构造唯一的文件名,并在传递了 "wb+" 标志的情况下能够按照与 open() 函数相同的方式(即,作为在读/写模式下的二进制文件)打开文件。如果文件已存在,tmpfile() 将把文件的大小缩小为 0,也许能缓解前面提到的安全问题(唯一文件名的选择与随后打开所选文件之间的 race condition)。然而,该操作显然不能解决函数的安全性问题。首先,攻击者可以预先创建一个能轻松获取访问权限的文件,该文件可能会被用 tmpfile() 函数打开的文件保留。其次,在基于 Unix 的系统上,如果攻击者预先创建了一个文件作为另一重要文件的链接,应用程序可能会使用提高了的权限去截短该文件,这样就能按照攻击者的意愿执行破坏。最后,如果 tmpfile() 创建了一个新文件,那么应用在该文件上的访问权限在不同的操作系统间是不同的,因此,应用程序的数据极易受到攻击,即便是攻击者无法预测要使用的文件名。
References
[1] B. Schneier Yarrow: A secure pseudorandom number generator
[2] Python Library Reference: os Python
[3] Python Library Reference: tempfile Python
[4] Symlink race WikiPedia
[5] Time of check to time of use WikiPedia
desc.semantic.python.insecure_temporary_file