安全编程: 避免竞争条件
来源:互联网 发布:4g网络测试工程师 编辑:程序博客网 时间:2024/06/11 19:56
安全编程: 避免竞争条件 | ||||
通过一个偷窃而来的口令,Mallory 成功地登录到一台运行 Linux 的重要服务器。其帐号是一个非常受限的帐号,但是 Mallory 知道如何使用它来制造麻烦。Mallory 安装并运行了一个行为非常奇怪的小程序,该程序使用多个进程在 /tmp 目录下快速地创建和删除很多不同的符号链接文件。(符号链接文件也称为 symlink,是一种简单的文件,当被访问时,它会将请求重定向到另一个文件。)Mallory 的程序不停地创建和删除很多指向同一特殊文件(/etc/passwd,口令文件)的不同符号链接文件。 这台重要的服务器的安全措施之一是,它每天都运行 Tripwire —— 具体地说,是较老的 2.3.0 版本。Tripwire 是一个检测重要文件是否被篡改的安全程序。与很多程序一样,Tripwire 启动时会尝试着创建一个临时文件。Tripwire 会查看并断定不存在名为“/tmp/twtempa19212”的文件,所以看起来这是一个合适的临时文件名称。但是在 Tripwire 完成检查后,Mallory 的程序就会使用该名称创建一个符号链接文件。这不是偶然的;Mallory 程序的设计目标就是创建最有可能为 Tripwire 所使用的文件名。然后 Tripwire 就会打开该文件,开始写入临时信息,但不用创建新的空文件,Tripwire 现在正在重写口令文件!从那时起,任何人 —— 甚至是管理员 —— 都不能登录到该系统,因为口令文件已经被破坏了。更糟的是,Mallory 的攻击完全可以覆盖所有文件,包括服务器上存储的重要数据。 竞争条件简介 当由于事件次序异常而造成对同一资源的竞争,从而导致程序无法正常运行时,就会出现“竞争条件”。注意,竞争条件无需介入同一程序的两个部分之间的竞争;如果一个外部的攻击者可以通过意想不到的方式干扰程序,那么就会出现很多安全问题。例如,如果 Tripwire 2.3.0 确定某个文件不存在,它就会尝试着创建该文件,而不去考虑在进行这两个步骤期间,该文件是否已经被攻击者创建。几十年前,竞争条件还不是什么问题;那时,计算机系统通常在同一时刻只能运行一个单独的程序,什么都不能打断它或者与它竞争。但是,当今的计算机通常需要同时运行大量的进程和线程,经常还会有多个处理器确实在同时运行不同的程序。这样做更灵活,但是有一个危险:如果这些进程和线程共享了所有的资源,那么它们都可能互相影响。实际上,竞争条件缺陷是软件的更常见缺陷之一,此外,在类 Unix 系统上,/tmp 和 /var/tmp 目录经常会被错误地使用,从而导致竞争条件。 不过,我们首先需要了解一些术语。所有 类-Unix 系统都支持用户进程;每个进程都有自己的内存空间(其他进程通常无法访问)。底层的内核会尽量使进程看起来像是在同时运行;在多处理器的系统中,它们确实可以同时运行。从理论上讲,一个进程可以拥有一个或多个线程;这些线程可以共享内存。线程也可以同时运行。由于线程可以共享内存,所以,相对于进程,线程之间更有可能产生竞争条件;正是由于这个原因,多线程程序的调试要困难得多。Linux 内核有一个非常好的基本设计:只有线程,并且一些线程可以与其他线程共享内存(这样实现了传统的线程),而另外一些线程则不能(这样就实现了独立进程)。 为了理解竞争条件,让我们首先来看一个非常普通的 C 声明: 清单 1. 普通的 C 声明
看起来非常简单,不是吗?但是,让我们假定有两个线程在运行这一行代码,在这里,“b”是一个由两个线程共享的变量,“b”的初始值为“5”。以下是一个似是而非的执行次序: 清单 2. 使用共享的“b”的可能执行次序
初始值为 5,然后两个线程分别加 1,但是最终的结果是 6... 而不是应该得到的 7。问题在于,两个线程互相干扰,从而导致产生错误的最终答案。 通常,线程不是以原子的方式执行的;另一个线程可以在任何两个指令期间打断它,而且还可以使用一些共享的资源。如果一个安全程序的线程没有预防这些中断,那么另一个线程就可以干扰该安全程序的线程。在安全程序中,不管在任何一对指令中间运行了多少其他线程的代码,程序都必须正确地运行。关键是,当您的程序访问任意资源时,要确定其他某个线程是否可能因为使用该资源对您的程序造成干扰。 解决竞争条件 有时,可以一次执行一个单独操作来完成一些特殊的操作,从而使您不需要显式地对某个资源进行加锁而后再解锁。这类操作称为“原子”操作,只要能够使用这类操作,它们通常是最好的解决方案。 有一些错误是如此常见,所以,为了避免犯这些错误,您需要了解它们。一个问题是,以不总是锁定某资源的方式创建锁文件;您应该学习如何正确创建它们,或者转而采取不同的加锁机制。您还需要正确地处理文件系统中的竞争,其中包括如何处理永远危险的共享目录 /tmp 和 /var/tmp,以及如何安全地使用信号。下一章中将描述如何安全使用它们。 锁文件 如果您正在创建单独的文件来表示锁,那么要注意一个常见的错误:对 如果您使用文件来表示锁,那么要确保这些锁文件放置在攻击者无法利用(例如,不能删除它们或者添加干扰它们的文件)的位置。典型的解决方案是使用一个目录,使该目录的权限根本不允许未经授权的程序添加或者删除文件。确保只有您可以信任的程序才能添加或者删除锁文件! 文件系统层次结构标准(Filesystem Hierarchy Standard,FHS)得到了 Linux 系统的广泛使用,同时还引入了这类锁文件的标准约定。如果您只是希望确保您的服务器在一台给定的机器上运行不超过一次,那么您通常应该创建一个名为 /var/run/NAME.pid 的进程标识符,以进程 id 作为文件内容。根据同样的思路,您应该将设备锁文件之类的锁文件放置在 /var/lock 中。 锁文件的代替者 只有所有程序都共同合作的时候,使用单独的文件或者 在一个进程内部,线程可能也同样需要锁;有很多书都非常详细地讨论了这些问题。在这里,我们要讨论的主要问题是确保您小心地涵盖了所有情况;很容易忘记某个特定情形,或者没有正确处理。事实上,正确使用锁是很难的,攻击者可能利用这些锁处理中的错误。如果您需要在一个进程内部对线程使用很多锁,那么可以考虑使用自动完成锁的维护的语言或者语言结构。有很多语言,比如 Java 和 Ada95,都有内置的可以自动处理锁维护(并使结果有可能更正确)的语言结构。 只要有可能,在开发程序时最好根本不使用锁。一个单独的服务器进程每次只接受一个客户机请求,然后处理该请求,直到完成该请求为止,而后再获得下一个请求,从某种意义上讲,进程内部的所有对象是被自动锁定的;这种简单的设计可以避免很多危险的加锁问题。如果您需要一个锁,那么保持其简单性(比如为几乎所有内容都使用惟一的锁)是有好处的。这并不总是实用的,因为这样设计有时会损害性能。具体地说,单服务器系统需要确保无论哪个操作都无法占用过长的时间。但是这个建议是值得考虑的;使用很多锁的系统会更可能有缺陷,而且维护这些锁也会影响性能。 处理文件系统 有很多确定为安全的程序都存在称为“time of check - time of use”(TOCTOU)的竞争条件缺陷。这只说明了程序检查某种情形是否可行,然后稍后使用那一信息,但是攻击者可能会在这两个步骤之间改变该情形。对文件系统来说,以下问题尤为突出;在这两个步骤之间,攻击者通常可以创建一个普通的文件或者一个符号链接。例如,如果某个已授予特权的程序检查是否不存在给定名称的文件,然后打开该文件写入信息,那么在那两个步骤之间,攻击者可以创建一个使用该名称的符号链接文件... 比如 /etc/passwd 或者其他一些敏感文件。 遵守一些简单的规则,可以避免这些问题:
如果可以,不要将文件放置在可以由不信任用户共享的目录中。如果不是那样,那么应该尽量不使用在用户间共享的目录。不要介意创建只能由受信任的特定进程访问的目录。 考虑避免使用传统的共享目录 /tmp 和 /var/tmp。如果您可以只使用一个管道,将数据从一个位置发送到另一个位置,那么您就可以简化程序,并排除潜在的安全问题。如果您确实需要创建一个临时文件,那么可以考虑将临时文件存储到其他地方。如果您不是在编写一个有特权的程序,那么这点尤其需要考虑;如果您的程序没有特权,那么将临时文件放置在用户目录内部会更安全一些(处理 root 用户时要当心,它以“/”作为其主目录)。这样,即使您没有“正确地”创建临时文件,攻击者通常也无法引发问题(因为攻击者不能利用用户主目录的内容)。 但是,无法总是能够避免使用共享目录,所以我们需要理解如何处理 /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 系统可以这样做,但是您需要知道如何要求系统去做。 共享目录的解决方案 在 C 中,为了在共享(sticky)目录中安全地创建临时文件,通常的解决方案是将 C 程序员实际上不需要直接这样去做;只需要调用库函数 当为了安全地打开临时文件而在共享(临时)目录中创建文件系统对象时,GNOME 编程向导推荐您使用下面的 C 代码: 清单 3. 推荐使用的创建临时文件的 C 代码
注意,尽管使用了不安全的 整个这个打开文件的方法展示出了标准的 C IO 库的一个奇特之处:没有标准的方法来指定 O_EXCL 选项使用 Perl 程序员应该使用 File::Temp,它尝试提供一个安全创建临时文件的跨平台方法。不过,首先要仔细阅读如何正确使用它的文档;它同样有不安全的函数接口。我建议您显式地将 safe_level 设置为 HIGH;这样就会调用附加的安全检查。对大部分编程库来说都是如此;大部分库都既有安全接口,又有不安全接口,所以您需要查阅文档,并确保选择了安全的版本。 注意,在旧版的 NFS(版本 1 或者版本 2)的目录上使用 O_EXCL 是没有用的,因为 NFS 的这些旧版本没有正确地支持 O_EXCL。如果您自己使用了 O_EXCL,而且共享目录是使用这些旧版 NFS 实现的,那么您的程序将是不安全的。实际上,关于旧版 NFS 中 您可以尝试使用 如果您正在编写 shell 脚本,那么可以使用管道,或者在用户的主目录存入临时文件。根本不要使用 /tmp 或 /var/tmp 目录;普通的 shell 通常无法支持文件描述符,所以临时文件清除器(tmpfile cleaners)最终将使它们失败。如果没有临时文件清除器,而且您只是必须在 /tmp 中创建临时文件,那么至少要使用
不要再次使用临时文件名称(即不要删除和重新创建文件),不管您最初是如何获得“安全的”临时文件名称。攻击者都有可能观察到原始的文件名称,并在您第二次重新创建它时非法控制它。当然,要始终使用合适的文件权限。 做好自己的清理工作,或者通过使用退出处理器,或者利用 UNIX 文件系统语义,在创建后立即 使这些对策成为操作系统的一部分已经取得了一些成功,尽管它们当前还没有被广为使用。要获得更多资料,请参阅参考资料列表中关于来自 OpenWall 项目的 Solar Designer 的 Linux 内核补丁、RaceGuard,以及 Eugene Tsyrklevich 和 Bennet Yee 的工作链接。 信号处理 结束语 当然,重要的是保护您的程序不受竞争条件的破坏。但是,当前的大部分程序都不能自己完成所有的事情;它们必须向其他程序库和程序(比如命令解释程序和 SQL 服务器)发出请求。攻击程序使用的最常见方式之一就是利用这些程序向其他程序发出请求的方式。所以,接下来我们将分析如何在不暴露缺陷的同时去调用其他程序。
|
- 安全编程: 避免竞争条件
- 安全编程: 避免竞争条件
- 安全编程: 避免竞争条件
- 安全编程: 避免竞争条件
- Linux安全编程:避免竞争条件
- 安全编程: 避免竞争条件--资源争用可能对您不利
- 避免竞争条件的实例
- 【Linux编程】竞争条件
- 一些避免竞争条件的实例
- linux中避免竞争条件的途径
- 一些避免竞争条件的实例
- 避免竞争条件的程序 | Unix进程控制
- 《UNIX环境高级编程》(竞争条件)
- 竞争条件
- 《UNIX网络编程 卷1》 笔记: 竞争条件!
- Race Condition(竞争条件)
- 如何避免站内关键词竞争
- 《unix环境高级编程》中 竞争条件 的解决(setjmp longjmp)
- 用 hashcash 打击垃圾邮件
- 认识ASP.NET配置文件Web.config
- XmlHttp对象及其方法
- 做一个简单的访问来源统计
- SQL Server日期计算
- 安全编程: 避免竞争条件
- 一个ASP写的侧边菜单栏
- 唤醒善良:2004最震撼的网络照片
- 为Linux应用构造有限状态机
- Linux 系统内核空间与用户空间通信的实现与分析
- Linux 2.6 调度系统分析
- Linux 汇编语言开发指南
- 浅析GLib
- Linux 线程实现机制分析