最小化特权 Unix安保编程
安保的程序必定最小化特权,以降落 bug 转化为安保毛病的或许性。本文探讨了如何经过最小化有特权的模块、授与的特权以及特权的有效期间来最小化特权。
文章不只探讨了一些传统的类 UNIX 特权机制,还探讨了较新的机制,如 FreeBSD 的 jail(),Linux 安保模块(Linux Security Modules,LSM)框架,以及 Security-Enhanced Linux(SELinux)。
2003 年 3 月 3 日,Internet Security Systems 对 Sendmail 中的一个严重的破绽提出了正告。一切的电子邮件都经过邮件传输代理(mail transfer agent,MTA)来传输,Sendmail 则是最盛行的 MTA,所以这个正告影响了环球范畴内的很多组织。疑问在于,按通常的性能,精心设置了“from”、 “to”或许“cc”域的电子邮件信息可以让发送者齐全(root)控制任何一台运转着 Sendmail 的机器。更严重的是,普通的防火墙将 不能包全其外部的机器免受这种攻打。
形成这一破绽的直接要素是,Sendmail 的一个安保检测是有毛病的,可以出现缓冲区溢出。不过,一个关键的作用要素是,Sendmail 经常被装置为一个繁多的“setuid root”程序,对运转它的系统有齐全的控制权限。这样,Sendmail 中的任何毛病都可以让攻打者直接控制整个系统。
这个设计是必定的吗?不是;Wietse Venema 的 Postfix 是一个经常出现的可以与之匹敌的 MTA。相似于 Sendmail,Postfix 会去做很多安保检测,不过,为了 最小化特权,Postfix 设计为一组模块。结果,Postfix 通常被以为是比 Sendmail 更安保的程序。本文探讨了如何最小化特权,您可以将雷同的思维运行到您的程序中。
最小化特权的基础
实践运行的程序会有毛病。不是咱们宿愿那样,然而确实是有。复杂的需求、日程的压力和环境的变动使得不太或许获取适用的无毛病的程序。甚至那些经过复杂而且准确的技术正式地证实是正确的程序,也会有毛病。为什么?其中一个要素是,验证必定做很多假定,而且通常这些假定并不是齐全正确。无论如何,出于种种要素,大局部程序没有获取严厉的测验。而且,即使当天没有任何毛病(不太或许),日后保养的扭转或许环境的扭转都或许会引入毛病。所以,要处置实践的疑问,咱们不得不以某种方式来开发安保的程序, 虽然 咱们的程序中有毛病。
虽然有这些毛病,对安保编程来说最关键的方法是 最小化特权。特权只是支持去做并不是 每团体 都可以做的事情。在类 UNIX 系统中,领有“root”用户、其余用户或许一个组的成员的特权是最经常出现的特权种类。一些系统让您可以授与读或写特定文件的特权。不过,不论怎样样,要最小化特权:
只为程序中须要特权的局部授与特权
只授与局部相对须要的详细特权
将特权的有效期间或许可以有效的期间限度到相对最小
这些其实是指标,不是相对的。您的基础组织(比如您的操作系统或许虚构机)或许使得严厉成功这些并不容易,或许严厉成功这些或许会很复杂,而造成在尝试严厉成功时引入更多毛病。然而,你距离这些指标越近,毛病造成安保疑问的或许性就越低。即使毛病造成了安保疑问,它造成的安保疑问的严重性或许会更低。而且,假设您可以确保只要小局部程序领有特定的特权,您就可以用少量额外的期间来确保 那 一局部能抵御攻打。这个思维并不新; Saltzer 和 Schroeder 的低劣的 1975 论文探讨了安保的原理,明白地将最小化特权作为一个准则(检查 参考资料)。有一些思维是永久的,比如最小化特权。
接上去的三节将依次探讨这些指标,包括如何在类 UNIX 系统中成功他们。而后,咱们将探讨 FreeBSD 和 Linux 中可用的一些特意的机制,包括对 NSA 的 Security-Enhanced Linux(SELinux)的探讨。
最小化有特权的模块
如前所述,只要须要特权的局部程序才运行领有特权。这就是说,当您设计您的程序时,尽量将程序合成为独立的局部,以使得只要小而独立的局部须要特定的特权。
假设不同的局部必定同时运转,那么在类 UNIX 系统中经常使用进程(不是线程)。线程共享它们的安保特权,有疑问的线程或许会搅扰进程中一切其余线程。编写有特权的局部时,就当作其它的程序正在攻打它:某一天会或许!确保有特权的局部只去做尽或许少的事情;受限的性能象征着更不易被应用。
一个通常的方法是,创立性能极度受限的领有特定特权(比如是 setuid 或许 setgid)命令行工具。UNIX 的 passwd 命令就是一个例子;它是一个具备特定特权的命令行工具,用于修正明码(setuid root),然而它所能做的只是修正明码。于是各种 GUI 工具可以要求 passwd 来做实践的更改。假设有或许,尽量齐全防止创立 setuid 或 setgid 程序,由于很难确保您正在真正包全一切输入。不过,有时您须要创立 setuid/setgid 程序,所以,当须要时,尽或许使程序最小且最受限度。
有很多其余的方法。例如,您可以有一个具备特定特权的小的“主机(server)”进程;那个主机只支持特定的恳求,而且只是在确认恳求者被支持收回恳求之后。另一个经常出现的方法是,经常使用特权启动一个程序,这个程序而后派生丢弃一切特权的第二个进程,而由这个进程来做大局部上班。
要留神这些模块彼此之间如何通讯。在很多类 UNIX 系统上,命令行值和环境可以被其余用户看到,所以不是在进程间隐秘地发送数据的上策。管道可以胜任,然而要细心肠防止死锁(一个两端都可以刷新的繁难的恳求/照应协定就可以胜任)。
最小化授与的特权
确保您只授与特权给确实须要的程序——到此为止。UNIX 启动取得特权的关键路径是它们以哪个用户或组的身份运转。通常,进程以经常使用它们的用户和组身份运转,不过,“setuid” 或 “setgid” 的程序会取得领有这个程序的用户或组的特权。
悲痛的是,还是有一些不自主地给予程序“setuid root”特权的类 UNIX 系统上的开发者。这些开发者以为他们使得事情对自己来说变得“容易”,由于他们不用再去深化思索他们的程序确切须要什么特权。疑问是,由于这些程序程序可以在大局部类 UNIX 系统上做差不多一切的事情,所以任何一个毛病都可以很快成为一个安保劫难。
不要只是由于您须要成功一个繁难的义务就给出一切或许的特权。而应该只给予程序它们所须要的特权。假设您可以,以 setgid 来运转它们,而不要用 setuid——setgid 给予的特权更少。创立特定的用户和组(不要经常使用 root),并依据您的须要经常使用它们。确保 root 所领有的那些可口头程序只能由 root 来写,这样其他人就不能修正它们。设置十分严厉的文件权限——假设不是相对须要,不要让一切人都可以读或写文件,并且经常使用那些特定的用户和组。能说明一切这些的一个例子或许是游戏“top ten”分数的规范惯例。很多程序都是“setgid games”,以使得只要游戏程序可以修正“top ten”分数,而且存储这些分数的文件的客人是 games 组(而且只要这个组可以写)。即使攻打者攻打并进入了一个游戏程序,一切他能做的事情将是修正分数文件。无论如何,游戏开发者还是须要编写他们的程序来防止恶意的分数文件。
chroot() 系统调用是一个适用的工具——可怜的是有一些难用。当进程检查文件系统的 “root”时,这个系统调用会修正进程所看到的内容。假设您方案用它——而且它或许是适用的——要预备好花一些期间来用好它。必定精心预备 “new root”,这是复杂的,由于确切的运行程序依赖于平台和运行程序的个性。您 必定 以 root 身份来启动 chroot() 调用,而且您 应该极速地 扭转为非 root 身份(root 用户可以脱离 chroot 环境,所以假设它要失效,您须要解除那个特权)。而且 chroot 不会扭转网络访问。这可以是一个适用的系统调用,一切有时刻须要思索它,然而要做好付出致力的预备。
限度资源是一个经常被遗忘的工具,这既包括存储的资源也包括进程的资源。这些限度拒绝服务攻打尤其有用:
对存储来说,您可以为每个用户或组设置每个挂载的文件系统的存储量或文件数的配额(限定)。在 GNU/Linux 系统中检查 quota(1)、quotactl(2) 和 quotaon(8) 来深化了解这一性能,不过,虽然它们不是哪里都能用,大局部类 UNIX 系统都蕴含了 quota 系统。在 GNU/Linux 和很多其余系统中,您可以设置“硬”界限(永远不能超出)和“软”界限(可以暂时超出)。
对进程来说,您可以设置很多限定,比如关上文件的数目、进程的数目,等等。这种才干实践上是规范的一局部(比如繁多 UNIX 规范(Single UNIX Specification)),一切它们在类 UNIX 系统上简直广泛存在;要深化了解,请检查 getrlimit(2)、 setrlimit(2) 以及 getrusage(2)、sysconf(3) 和 ulimit(1)。进程永远不能超出“界限”,然而它们可以将界限一路回升到“下限”。可怜的是,这里有一个不合常理的术语疑问或许会使您蛊惑。“界限”也被称为“软”界限,下限也称为 “硬”界限。这样,您就会处在一个不同凡响的情景,进程 永远 不能超出进程界限的软()界限——而关于 quota 来说您 可以 超出软界限。我倡导为进程界限经常使用术语“界限”和“下限”(永远不要经常使用术语“软”和“硬”),那样就没有任何蛊惑了。
最小化特权的期间
只是当须要的时刻才给予特权——片刻也不要多给。
只需或许,经常使用无论什么您立刻须要的特权,而后 终身地 丢弃它们。一旦它们被终身丢弃,起初的攻打者就不能以其余方式应用那些特权。例如,须要一般的 root 特权的程序或许以 root 身份启动(比如说,经过成为 setuid root)而后切换到以较少特权用户身份运转。这是很多 Internet 主机(包括 Apache Web 主机)所驳回的方法。类 UNIX 系统不支持任何程序关上 0 到 1024 TCP/IP 端口;您必定领有 root 特权。然而大局部主机只是在启动的时刻须要关上局口,就再也不须要特权了。一个方法是以 root 身份运转,尽或许快地关上须要特权的端口,而后终身去除 root 特权(包括进程所属的任何有特权的组)。也要尝试去除一切其余承袭而来的特权;例如,尽快封锁须要特定的特权才干关上的文件。
假设您不能终身地丢弃特权,那么您至少可以尽或许经常暂时去除特权。这不如终身地去除特权好,由于假设攻打者可以控制您的程序,攻打者就可以从新启用特权并应用它。虽然如此,还是值得去做。
很多攻打只要在它们诈骗有特权的程序做一些方案外的事情而且程序的特权被启用时才会成功(例如,经过创立不合常理的符号链接和硬链接)。假设程序通常不启用它的特权,那么攻打者想应用这个程序就会更艰巨。
较新的机制
到目前为止,咱们所探讨的准则实践上适用于简直一切操作系统,而且,自 19 世纪 70 年代以来,简直一切的类 UNIX 系统的惯例机制都是相似的。那并不是说它们没有用途;繁难和期间的测验是它们自身的长处。不过,一些较新的类 UNIX 系统曾经参与了支持起码权限的机制,值得去了解。虽然很容易找出经过期间测验的机制,可是关于较新的机制的资料还没有广为人知。所以,在这里我将探讨选出的一些有价值的机制:FreeBSD jail() 、Linux 安保模块(LSM)框架和 Security-Enhanced Linux(SELinux)。
FreeBSD jail()
chroot() 系统调用有很多疑问,如前所述。例如,它难以正确经常使用,root 用户还是可以从中脱离,而且它基本不去控制网络访问。FreeBSD 开发者选择参与一个新的系统调用来处置这些疑问,这个新的系统调用叫做 jail() 。这个调用相似于 chroot() ,不过尽力更易用且更用效。在一个 jail 中,一切的恳求(即使是 root 的)都被 jail 所限,进程只能与 jail 中的其余进程通讯,而且系统封锁了 root 用户试图从 jail 中脱离的典型路径。jail 会被调配一个特定的 IP 地址,不能经常使用任何其余地址作为它自己的地址。
jail() 调用是 FreeBSD 所独有的,这就限度了它的成效。不过,各个 OSS/FS 内核之间有很多交叉影响(cross-pollination)。例如,曾经经常使用 Linux 安保框架为 Linux 开发了一个 jail 版本。而且, FreeBSD 5 曾经参与了一个灵敏的 MAC 框架(来自 TrustedBSD 名目),包括一个具备相似 SELinux 基本色能的模块。一切,未来看到更多这种状况不要感到奇异。
Linux 安保模块(LSM)
在 2001 年的 Linux Kernel Summit 上,Linus Torvalds 遇到了一个疑问。一些不同的安保名目,包括 Security-Enhanced Linux(SELinux)名目,要求他将他们的安保方法参与到 Linux 内核中。疑问是,这些不同的方法经常是不兼容的。 Torvalds 没有繁难的方法可以判定哪个是最好的,所以他要求那些名目为 Linux 协作创立某种通用的安保框架。那样,治理员就可以给他们特意的系统装置恣意他们想要的安保方法。与 Torvalds 探讨了几次,Crispin Cowan 建设了一个小组来创立这个通用的安保框架。这个框架被命名为 Linux 安保模块(LSM)框架,如今是规范 Linux 内核的一局部(如 2.6 版本内核)。
概念上讲,LSM 框架特意繁难。Linux 内核仍去做它惯例的安保检测;例如,假设您要写一个文件,您仍须要对其有写权限。
不过,不论何时假设 Linux 内核须要判定能否应该支持访问,它还要启动核查——经过一个“book”去要求一个安保模块来启动—— 来确定举措能否获取支持。这样,治理员可以繁难地选出他想要经常使用的安保模块,并像其余 Linux 内核模块一样将其拔出。从那时,那个安保模块将判定什么是支持的。
LSM 框架设计得如此灵敏,它可以成功很多不同种类的安保战略。实践上,一些不同的名目启动协作以确保 LSM 框架足以胜任真正的上班。例如,当外部对象被创立或被删除时 LSM 引入一些调用——不是由于那些操作或许会停止,而是让安保模块可以坚持对关键数据的追踪。经常使用了一些不同的剖析工具来确保 LSM 框架不会遗漏其指标的任何关键异常分支。结果证实,这个名目比很多人想像的要艰巨,它的成功是来之不易的。
有必要了解 LSM 所做的基本的设计选择。基本上,LSM 框架故意设计为简直一切异常分支都是受限的,而不是可信的(authoritative)。一个 可信的 异常分支做出相对最终的选择:假设异常分支以为一个恳求应该被支持,那么它就会被无条件支持。雷同, 可信的 异常分支只能参与另外的限度;它不能授与新权限。通常上,假设一切 LSM 异常分支都是可信的,LSM 框架将会愈加灵敏。有一个名为 capable() 的异常分支是可信赖的——然而只是由于它不得不支持惯例的 POSIX 才干。不过,要让 一切 异常分支都可信,就要对 Linux 内核启动很多基本的扭转,还说不准这种扭转是不是会被接受。
有很多人担忧,假设大局部异常分支都可信,即使是最小的毛病也将成为劫难;而让异常分支受限象征着用户将不会感到异常(不论怎样,原来的 UNIX 权限仍反常上班)。所以 LSM 框架开发者无心选用了限度方法,而且它的大局部开发者自信他们可以在框架内上班。
了解 LSM 框架的其余限度也是关键的。LSM 框架设计只是用来支持访问控制,不是审计等其余安保疑问。 LSM 模块自身不能记载一切恳求或它们的结果,由于它们不能看到所有。为什么?一个要素是,内核或许没有调用 LSM 模块就拒绝了恳求;假设您想审计这个拒绝就会有疑问。还有,出于性能的思索,有一些提议的用于网络的 LSM 异常分支和数据域没有被主线内核所驳回。它可以控制一些网络访问,但无余以支持“labelled”网络数据流(在这种状况下,不同的数据包有不同的由操作系统处置的安保标签)。这些都是不适合的限度,也不合乎普通思维的基本准则;LSM 框架有宿愿终有一天获取裁减以废弃这些限度。
虽然如此,即使有这些限度,LSM 框架对给特权参与限度来说仍是十分适用。Torvalds 的指标由 LSM 框架基本上成功了:“我不青睐在不同的安保人群之间奋斗。我宿愿是直接的,让 我跳出这场奋斗,而后市场奋斗就可以选择哪个战略和成功最终获取 运行。”
所以,假设您想在 Linux 下限度授与您的程序的特权,您可以创立齐全您自己的安保模块。假设您应用真正外来的限度,或许会须要那样做——幸亏这是或许的。不过,这很关键;不论怎样,您还是要编写内核代码。假设或许,您最好不要经常使用已有的 Linux 安保模块,而是尝试去编写自己安保模块。有一些可用的 LSM 模块,不过,Security-Enhanced Linux (SELinux)是最成熟的 Linux 安保模块之一,所以让咱们来钻研这个模块。
Security-Enhanced Linux(SELinux)的历史
一个小历史将有助于协助您了解 Security-Enhanced Linux(SELinux)——而且它自身也是段幽默的历史。美国国度安保局(National Security Agency,NSA)常年间以来就关注大局部操作系统中受限的安保才干。毕竟,他们的上班之一就是要确保美国国防部经常使用的计算机在面临没完没了的攻打时坚持安保。NSA 发现大局部操作系统的安保机制,包括 Windows 和大局部 UNIX 和 Linux 系统,只成功了“选用性访问控制(discretionary access control)”(DAC)机制。DAC 机制只是依据运转程序的用户的身份和文件等对象的一切者来选择程序可以做什么。NSA 以为这是一个严重的疑问,由于 DAC 自身对软弱的或恶意的程序来说是一个不合格的防护者。取而代之的,NSA 常年以来不时宿愿操作系统雷同能支持“强迫访问控制(mandatory access control)”(MAC)机制。
MAC 机制使得系统治理员可以定义整个系统的安保战略,这个战略可以基于其余要素,像是用户的角色、程序的可信性及预期经常使用、程序将要经常使用的数据的类型等等,来限度程序可以做哪些事情。一个小例子,有了 MAC 后用户不能随便地将“隐秘的(Secret)”数据转化为“不隐秘的(Unclassified)”的数据。不过,MAC 实践上可以做的比那要多得多。
NSA 曾经与操作系统提供商协作了多年,然而很多占有最大市场的提供商关于将 MAC 集成出去没有兴味。即使是那些集成了 MAC 的提供商也通常是将其做为“独自的产品”,而不是惯例产品。一局部要素只是由于新式的 MAC 不够灵敏。于是 NSA 的钻研力气尽力去使 MAC 更灵敏并且并容易被蕴含在操作系统中。他们经常使用 Mach 操作系统开发了他们的思维的原型,起初动员的上班裁减了“Fluke”钻研操作系统。
不过,难以让人们信服这些思维可以适用于 “实在的”操作系统 ,由于一切这些上班都基于微型的“玩具级的”钻研名目。极少可以在原型之外启动尝试以检查这些思维在实在的运行程序中上班得如何。NSA 不能压服具备一切权的提供商来参与这些思维,而且 NSA 也没有权益去修正私有的操作系统。这不是个新疑问;多年前 DARPA 试图强迫它的操作系统钻研人员经常使用私有的操作系统 Windows,但遇到了很多疑问(参见上方的 参考资料)。
于是,NSA 偶然发现了一个回顾起来似乎显而易见的想法:经常使用一个 不是 玩具的放开源代码操作系统,并成功他们的安保思维,以显示(1)它可以上班,(2)它详细如何上班(经过为一切人提供源代码)。他们选用了主导市场的放开源代码内核(Linux)并在其中成功了他们的思维,即“security-enhanced Linux”(SELinux)。毫无异常,经常使用真正的系统(Linux)让 NSA 钻研人员可以处置他们在玩具中无法处置的疑问。例如,在大局部基于 Linux 的系统中,简直一切都是灵活链接的,所以他们不得不做一些关于程序如何口头的深化剖析(查阅他们关于“entrypoint”和 “execute”权限的文档以取得更多资料)。这是一个更为成功的方法;正在经常使用 SELinux 的人比经常使用先前的原型的人多得多。
SELinux 如何上班
那么,SELinux 如何上班呢?SELinux 的方法实践上十分普通。每一个关键的内核查象,比如每个文件系统对象和每个进程,都有一个关联到它们的“安保高低文(security context)”。安保高低文可以基于军事安保层级(如不隐秘的、隐秘的和高度隐秘的)、基于用户角色、基于运行程序(这样,一个 Web 主机可以领有它自己的安保高低文),或许基于很多其余内容。当它口头另一个程序时,进程的安保高低文可以扭转。甚至,取决于调用它的程序,一个给定的程序可以在不同的安保高低文中运转,即使是同一个用户启动了一切程序。
而后系统治理员就可以创立一个指定哪些特权授与哪个安保高低文的“安保战略(security policy)”。当出现系统调用时,SELinux 去审核能否一切须要的特权都曾经授与了——假设没有,它就拒绝那个恳求。
例如,要创立一个文件,当行进程的安保高低文必定对父目录的安保高低文的“搜查(search)”和“add_name”特权,而且它须要有关于(要创立的)文件的安保高低文的“创立(create)”特权。雷同,那个文件的安保高低文必定有特权与文件系统“关联(associated)”(所以,举例来说,“高度隐秘”的文件不能写到一个“不隐秘”的磁盘)。还有用于套接字、网络接口、主机和端口的网络访问控制。假设安保战略为那些所有授与了权限,那么恳求就会被 SELinux 所支持。否则,就会被制止。假设循序渐进地去做一切这些审核将会较慢,不过有很多优化方案(基于多年的钻研)使其变得很迅速。
这一审核齐全独立于类 UNIX 系统中的通常的权限位;在 SELinux 系统中,您必定 既 有规范的类 UNIX 权限, 又 有 SELinux 权限才干去做一些事情。不过,SELinux 审核可以做很多对传统的类 UNIX 权限来说难以成功的事情。
经常使用 SELinux,您可以繁难地创立一个只能运转特定程序并且只能在特定的高低文中写文件的 Web 主机。更幽默的是,假设一个攻打者攻入了 Web 主机并成为 root,攻打者不会取得整个系统的控制权——假设有一个好的安保战略的话。
那就有了艰巨:为了使 SELinux 有效,您须要有一个好的安保战略因由 SELinux 口头。大局部用户将须要一个他们容易修正的适用的初始战略。几年前我开局体验 SELinux;那时,初始战略还不成熟,有很多疑问。例如,在那些以前的日子中我发现早期的样例战略不支持系统更新配件时钟(最后我提交了一个补丁以处置这一疑问)。设计好的初始安保战略相似对产品分类, NSA 宿愿由商业界来做,而且看起来是要这样做。Red Hat、一些 Debian 开发者、Gentoo 以及其他人正在经常使用基本的 SELinux 框架,并且正在创立初始安保战略,这样用户可以马上开局经常使用它。确实,Red Hat 方案为一切用户在他们的 Fedora 内核中都启用 SELinux,并提供繁难的工具来使得非专业用户可以经过选用一些经常出现选项来修正他们的安保战略。Gentoo 有一个可疏导的 SELinux LiveCD。这些集团将使得最小化程序特权变得更繁难,而不须要少量代码。
在这里咱们又回到了原处。SELinux 只要当程序口头时才支持出现安保传输,它控制进程的权限(不是一个进程的一局部)。所以,为了充散施展 SELinux 的后劲,您须要将您的运行程序合成为独立的进程和程序,只要一些小的有特权的组件—— 这恰恰恰像如何在没有 SELinux 的状况开发安保的程序。像 SELinux 这样的工具让您可以更好地控制授与的权限,并这样创立一个更强有力的进攻,然而,您仍须要将您的程序拆分为更小的组件,以使得那些控制能施展最大的成效。
完结语
最小化特权是对各种安保疑问的最关键进攻。由于毛病是无法防止的,您会宿愿大大降落毛病造成安保疑问的或许性。不过,至少一个安保的程序的 一些 局部必定有触及安保的代码,所以您不能只是最小化特权而漠视一切其余。甚至在您曾经最小化了那些触及安保的局部,那些局部还是必定是正确的。为了是正确的,您须要防止经常出现的失误。