安全编程: 避免竞争条件(3)

发表于:2007-05-25来源:作者:点击数: 标签:安全编程遵守竞争条件
遵守一些简单的规则,可以避免这些问题: 不要使用 access(2) 来判定您是否可以做某件事情;通常攻击者会在调用 access(2) 后改变该情形,所以,通过调用 access(2) 获得的任何数据都可能不再是可信任的。换一种方式,将您的程序的特权设置得恰好是您想要的
遵守一些简单的规则,可以避免这些问题:

  不要使用 aclearcase/" target="_blank" >ccess(2) 来判定您是否可以做某件事情;通常攻击者会在调用 access(2) 后改变该情形,所以,通过调用 access(2) 获得的任何数据都可能不再是可信任的。换一种方式,将您的程序的特权设置得恰好是您想要的特权(例如,设置它的有效 id、文件系统 id 或者有效 gid,并通过 setgroups 来清除所有不需要的组);然后调用 open(2) 直接打开或创建您需要的文件。在类 Unix 系统上,open(2) 调用是原子的(与以前的 NFS 系统版本 1 和版本 2 不同)。

  当创建一个新文件时,使用 O_CREAT | O_EXCL 模式打开它(确保只有在创建一个新文件时调用 O_EXCL 才会成功)。最初只授与非常有限的权限;至少禁止任意的用户修改它!通常,这表示您需要使用 umask 和/或打开参数,将初始的访问权限局限于用户,也可以局限于用户所在的组。不要尝试在创建完文件后再去减少权限,因为这样做会导致竞争条件。在大部分类 Unix 系统上,只在打开文件时才检查权限,所以,攻击者可以在权限位(permission bit)允许时打开文件,并使该文件一直处于打开状态,不管权限如何改变。如果您愿意,还可以在以后将权限修改得更为开放。您还需要为打开失败做好准备。如果您绝对需要能打开某个新文件,那么应该创建一个循环:(1)创建一个“随机”的文件名,(2)使用 O_CREAT | O_EXCL 选项打开文件,
(3)成功打开文件后停止循环。

  当对文件的元信息进行操作时(比如修改它的所有者、对文件进行统计,或者修改它的权限位),首先要打开该文件,然后对打开的文件进行操作。只要有可能,应尽量避免使用获取文件名的操作,而是使用获取文件描述符的操作。这意味着要使用 fchown( )、fstat( ) 或 fchmod( ) 系统调用,而不使用取得文件名的函数,比如 chown()、chgrp() 和 chmod()。这样做将避免文件在您的程序运行时被替换(一种可能的竞争条件)。例如,如果您关闭一个文件,然后使用 chmod() 来修改其权限,那么攻击者很可能在这两个步骤之间移动或删除该文件,并创建指向另一个文件(比如 /etc/passwd)的符号链接。

  如果您的程序需要遍历文件系统(递归地遍历子目录),那么要提防攻击者可能会利用您正在遍历的目录结构。这种情形的一个常见的例子是,运行您的程序的管理员、系统程序或者有特权的服务器正在遍历的是由普通用户控制的文件系统部分。GNU 文件实用程序(fileutils)可以完成递归目录删除和目录移动,但是在版本 4.1 之前,当遍历目录结构时,它只是简单的遵循“..”这个特殊条目。当文件被删除时,攻击者可以将一个低层级的目录移动到更高的层级;fileutils 将会遵循“..”目录向上到更高层级,可能会一直到文件系统的根。通过在适当的时间删除目录,攻击者可以删除计算机中的任何文件。您不应该信任“..”或“.”,如果它们是由攻击者控制的。

  如果可以,不要将文件放置在可以由不信任用户共享的目录中。如果不是那样,那么应该尽量不使用在用户间共享的目录。不要介意创建只能由受信任的特定进程访问的目录。

  考虑避免使用传统的共享目录 /tmp 和 /var/tmp。如果您可以只使用一个管道,将数据从一个位置发送到另一个位置,那么您就可以简化程序,并排除潜在的安全问题。如果您确实需要创建一个临时文件,那么可以考虑将临时文件存储到其他地方。如果您不是在编写一个有特权的程序,那么这点尤其需要考虑;如果您的程序没有特权,那么将临时文件放置在用户目录内部会更安全一些(处理 root 用户时要当心,它以“/”作为其主目录)。这样,即使您没有“正确地”创建临时文件,攻击者通常也无法引发问题(因为攻击者不能利用用户主目录的内容)。

  但是,无法总是能够避免使用共享目录,所以我们需要理解如何处理 /tmp 等共享目录。这一点非常复杂,所以它应该自己占用一节!


共享目录(比如 /tmp)


共享目录基本概念

  如果您可信任的程序将要与潜在的非信任用户共享一个目录,那么要特别小心。在类 Unix 系统中,最常见的共享目录是 /tmp 和 /var/tmp,对这些目录的错误使用滋生了很多安全缺陷。最初创建 /tmp 目录,是将它作为一个创建临时文件的方便位置,通常不应该与任何其他人共享临时文件。不过,该目录很快它就有了第二个用途 —— 创建用户间共享对象的标准位置。由于这些标准目录有多种用途,使得操作系统难以加强访问控制来防止攻击;因此,您必须正确地使用它们,以避免受到攻击。

  当您使用共享目录时,确保目录和文件有适当的权限。显然,您需要限制哪些人可以对共享目录中创建的文件进行读写操作。但是,在类 Unix 系统中,如果多个用户都可以向同一目录添加文件,而且您计划通过一个有特权的程序向该目录添加文件,那么要确保为该目录设置“sticky”位。在一个普通的目录中(没有 sticky 位),任何人对它都有写权限 —— 包括攻击者 —— 可以删除或者重命名文件,导致各种各样的问题。例如,一个可信任的程序可以在这样一个目录下创建一个文件,而一个不受信任的用户可以删除或者重命名它。在类 Unix 系统上,需要设置共享目录的 “sticky”位;在 sticky 目录中,文件只能由 root 或者文件的所有者解除链接或者重新命名。/tmp 和 /var/tmp 目录通常实现为“sticky”目录,以排除一些问题。

  程序有时会留下一些没用的临时文件,所以,大部分类 Unix 系统会自动删除特定目录 /tmp 和 /var/tmp 下的原有临时文件(“tmpwatch”程序可以完成这项任务),一些程序会“自动”删除它们所使用的特定临时目录下的文件。这听起来很方便...只可惜攻击者可能会让系统特别繁忙,使活动文件成为旧文件。结果:系统可能会自动删除正被使用的文件名称。然后会发生什么?攻击者可能会尝试创建他们自己的相同名称的文件,或者至少让系统创建另一个进程,并重新使用相同的文件名称。结果:混乱。这类问题叫做“tmpwatch”问题。解决这种问题的方法是,一旦自动创建了一个临时文件,就必须始终使用打开该文件时得到的文件描述符或文件流。永远不要重新打开文件或者使用任何以文件为参数的操作 —— 始终使用文件描述符或者相关的流,否则,tmpwatch 竞争将引发一些问题。您甚至无法先创建文件、然后关闭它、然后再重新打开它,即使权限已经限制了谁可以打开该文件。

  攻击 sticky 目录以及您创建的文件的受限权限只是第一步。在运行安全程序期间,攻击者可能会尝试进行插入操作。常见的一种攻击是,当您的程序正在运行时,在共享目录中创建和反创建指向其他一些文件的符号链接 —— /etc/passwd 或者 /dev/zero 是常见的目标。攻击者的目标是,创造这样一种情形,即让安全程序判定某个给定的文件名并不存在,然后,攻击者就可以创建指向另一个文件的符号链接,而后安全程序继续执行某些操作(但是现在,它打开的实际上是一个意料之外的文件)。重要的文件经常会被这样破坏或更改。这种做法的另一个变种是,创建和反创建攻击者可以进行写操作的普通文件,这样,攻击者有时就可以控制有特权的程序创建的“内部”文件。

  在这些共享目录中创建文件时,常遇见的一个问题是,您必须确保您计划使用的文件名在创建时并不存在,然后自动创建该文件。在创建该文件“之前”进行检查没有用,因为在已经进行了检查但还没有创建该文件之前,另一个进程可以使用该文件名创建出这个文件。使用“不可预知的”或者“惟一的”文件名也没有用,因为攻击者可以反复猜测该文件名,直到成功为止。所以,您需要执行一个或者创建一个新文件或者失败的操作 —— 不做其他任何事情。类 Unix 系统可以这样做,但是您需要知道如何要求系统去做。

原文转自:http://www.ltesting.net