沙箱技术早在Windows2000的时候就已经出现了,但是因为大部分的人都不会接触到也没有机会应用这种技术,导致大部分的人根本就对这个没有印象。而在一本讲到沙箱技术的书上,上面的翻译是‘沙框’,当然这本书的翻译质量很是有些问题。在Windows操作系统上,沙箱实际上是一个内核对象,用一个句柄来存取的,用CloseHandle函数来销毁,这个内核对象与一个进程关联起来。这样,通过设计沙箱对象的属性,就可以控制这个进程的CPU使用率,对文件系统访问的控制。沙箱可以被绕过,因此沙箱技术也不是万能的。
安全性是Chromium最重要的目标之一。安全性的关键是理解:如果我们完全明白,一个在所有可能的输入的所有可能的组合情况下的系统的行为,我们就能够让一个系统真正的安全。对像Chromium这样一个巨大而且复杂的编码来说,推理出它所有部分的行为的组合几乎是不可能的。在这种情况下,沙箱对象用来提供有力的保证最终一段代码是否能够执行,而不管输入是什么。
沙箱改变系统提供的安全性,允许代码执行,但不允许对计算机的更改或者是对机密数据的读取。沙箱结构与沙箱提供的精确把握依赖于操作系统。目前只在Windows上完成了实现。[译注:实际上linux也有沙箱]
如果你不想读整个文档,你可以读沙箱的FAQ。
设计原理
不要重新发明轮子:不要试图去使用一个更好的安全模型来扩展操作系统的内核。将操作系统提供的安全性应用到它控制的对象上。另一方面,创建应用程序级别的对象拥有自定义的安全模型。
最小权限原理:这个原理应当应用于沙箱控制的代码与控制沙箱的代码。换句话说,在用户不能切换到超级用户的时候,沙箱也应当能够正常工作。
假设沙箱控制的代码是恶意代码:我们假定在main函数运行后,马上执行沙箱是有危害性的(运行恶意代码)。实际上,当接收第一次外部输入时,或者是在进入main循环前,这是可能发生的。
变得明智:非恶意代码不会尝试去访问它不不能获得的资源。在这种情况下,沙箱对性能应当几乎没有影响。当一个敏感的资料在可控行为下被请求时,异常情况下的性能损失是可以接受的。这在系统安全机制被恰当应用的情况下是很常见的。
模拟是不安全的:模拟和虚拟机机制本身并不提供安全性。沙箱不应该依赖于代码的模拟,代码的"翻译”,还有提供安全性的补丁。
沙箱的Windows架构
Windows沙箱是一个用户模型的沙箱。没有特殊的内核模型驱动,用户不需要为了沙箱的正确操作而使用管理员权限。沙箱设计用于32位机器,并且已经在Windows2000, Windows XP 32位版本和Winmdows Vista 32和64位版本上通过测试。
沙箱在进程级别上操作。所有需要装在沙箱中的东西都需要生存在进程中。最小的沙箱配置需要两个进程:一个是权限控制,称为broker(代-理),和一个或者多个沙箱化的进程,称为目标。在文档和代码当中,这两个术语始终有精确的内涵。沙箱作为一个动态链接库提供,必须链接到代-理和目标可执行程序中。
代-理进程
在Chromium中,代-理始终是浏览器进程。代-理是一个广义的术语,一个沙箱进程的权限控制者/管理者。
代-理进程的任务有:
为每一个目标进程提供策略
产生目标进程
安置沙箱策略引擎服务
安置沙箱拦截管理器
安置沙箱IPC服务(发送到目标进程)
执行目标进程请求的策略允许的行为。
代-理进程必须在所有目标进程终止后才能终止。沙箱IPC是一个低层次的机制(与Chromium的IPC不同),它用来透明地发送来自于目标进程的特定的windows API调用到代-理进程:这些调用被评估是否违反了策略。策略允许的调用被代-理进程执行,结果通过相同的IPC返回到目标进程。拦截管理器的工作是调整要通过IPC发送到代-理进程的windows API调用。
目标进程
在Chromium中,渲染进程始终是一个目标进程,除非你将--no-sandbox选项指定给浏览器进程。将插件进程运行在一个沙箱进程中也是可以的,指定--safe-plugins选项就可以了。目标进程拥有所有将要在沙箱中运行的代码,再加上沙箱本身:
所有的沙箱化代码
沙箱IPC客户端
沙箱策略引擎客户端
沙箱拦截
2,3,4项是沙箱库的一部分,链接到要沙箱化的代码中。
拦截(又称为HOOK,钩子),负责将windows API调用通过沙箱IPC发送到代-理进程。由代-理进程重新调用API然后返回函数调用结果或者是简单地返回调用失败。拦截+IPC机制不保证安全性。它只是用来提供兼容性。
沙箱限制
沙箱的核心依赖于四个windows机制提供的保护
一个限制令牌
Windows 作业对象
Windows desktop对象
限于Vista:完整性级别 (译注:Windows 7亦有)
这些机制在保护OS上是非常高效的,它们的配置和用户数据提供了:
所有的安全资源有一个比空安全描述符更好的描述符。换句话说,没有未配置安全性的关键资源。
计算机不再受到恶意软件的危害
第三方软件不会减弱系统的安全性。
令牌
其它类似的沙箱项目面临的一个问题是拥有一个正常工作的进程的令牌和作业如何做限制。
对于Chromium沙箱来说,最具有限制性的令牌如下:
普通组
登录 SID : 强制的
所有其它SID : 强制拒绝
受限级:
S-1-0-0 : 强制的
特权:
没有
根据上面的附加说明,几乎不可能找到一个存在的资源,操作系统会赋给它这样的令牌。如果磁盘根目录有非空的安全,即使文件是空安全也可以被读取。在Vista下,最有限制性的令牌与上表相同但是它包含了低integrity等级标签。Chromium渲染进程通常在这个令牌下运行,这意味着几乎所有渲染进程使用的资源都必须向浏览器主进程请求,然后它们句柄的副本将返回渲染进程。
注意代-理进程不是从匿名或者是guest令牌继承下来的,它是从用户令牌继续的,与一个用户登录相关。因此,任何系统或者是域审核仍然有效。
在设计上,沙箱令牌不能保护下面的不安全资源:
挂载的FAT或者是FAT32卷:它们的安全描述符实际上是空的。在目标进程上运行的恶意软件可以读写这些卷,只要它能猜到或者推出卷的路径。
TCP/IP:TCP/IP socket的描述符在windows 2000和XP上实际上是空的。在目标进程上的恶意代码可能发送或者接收数据到任何一台机器上。
作业对象
目标进程运行在一个Job对象下面。使用这个Windows机制,一些有趣的没有传统对象或者是安全描述符的限制得以实施:
禁止使用SystemParametersInfo做用户系统修改,函数可以切换鼠标左右键,或者设置屏保超时时间。
禁止创建或者是切换桌面
禁止修改用户显示配置例如分辨率和主显示器。
不允许读写剪切板
禁止广播Windows消息
禁止设置全局钩子
禁止读取全局Atom表
禁止读取Job对象外创建的USER 句柄
一个活动进程限制(不允许创建子进程)
Chromium渲染进程通常在这些限制下运行。每一个渲染进程有自己的作业对象。 使用作业对象,沙箱可以阻止(但当前没有实现):
过度使用CPU
过度使用内存
过度使用IO
可替代的桌面
令牌和作业对象定义了一个安全界线:所有拥有相同的令牌和相同作业对象的进程拥有相同的安全上下文。然而,一个不太好理解的事实是,拥有窗口的在同一个桌面运行的程序同样也拥有相同的安全上下文,因为发送和接收windows消息并不面向任何安全检查。跨桌面发送消息是不允许的。这就是臭名昭著的'shatter"攻击的起源,这也是为什么一个服务不应该拥有与桌面交互的窗口的原因。一个桌面是一个普通的内核对象,可以被创建也可以被赋予一个安全描述符。
在一个标准的Windows XP安装中,最少有两个桌面附加到交互窗口:正常(默认)桌面,和登录桌面。沙箱创建第三个桌面,关联到所有的目标进程。这个桌面不可见不可交互,实际上阻止沙箱化的进程查探其它用户的交互,发送消息到其它窗口做超出权限的操作。
可替代的桌面的唯一的缺点就是要额外使用4MB内存。在Vista上可能更多。
其它附加说明
操作系统可能有BUG。有趣的是Windows API中的BUG允许绕过正常的安全检查,如果存在一个这样的BUG,恶意软件将可以绕过沙箱限制和代-理策略也就可能危害电脑:在Windows下面,实际上没有办法阻止沙箱中的代码调用系统服务。
沙箱策略
应用到目标进程的限制实际上是用策略来配置的。策略只是一个纲领性的接口,代-理调用它来定义限制和允许。四个函数控制限制,大体上等同于Windows的四个机制:
TargetPolicy::SetTokenLevel()
TargetPolicy::SetJobLevel()
TargetPolicy::SetIntegrityLevel()
TargetPolicy::SetDesktop()
前三个函数带一个从严格到宽松的整数参数:例如,令牌等级有7个级别,同时Job有5个级别。Chromium渲染进程运行在四个机制中最严格的限制下。最后一个(桌面)策略是二进制的只能用来区分一个目标进程是否运行在一个代替桌面。
限制定义得很粗糙,它们影响所有目标进程可以获取的安全资源,但有时候,需要一个更细的解决方案。策略接口允许代-理指定例外(exception)。一个例外是执行目标进程申请的windows API调用的一种方式,代-理可以检查参数然后重新按原样安排函数调用,使用不同的参数重新安排函数调用,或者简单地拒绝调用。指定异常只需要一个函数: AddRule。目前,支持下面这些用于不同windows子系统的规则类型。
Files
Named pipes
Process creation
Registry
Synchronization objects
每一个子系统的规则的精确形式不相同,但是一般而言,规则基于字符串形式。例如,一个可能的文件规则是:
AddRule(SUBSYS_FILES, FILES_ALLOW_READONLY, L"c:\\temp\\app_log\\d*.dmp")
这个规则说明了,只要文件符合样式,如果一个目标进程想要以只读形式打开它的话,是允许的。例如c:\temp\app_log\domino.dmp是一个符合样式的文件。头文件中包含最新的支持对象和行为的列表。
规则只能在每一个子目标进程产生前添加,只能在一个目标进程运行时修改,不同的目标进程可以拥有不同的规则。
目标进程自启动
目标进程启动时不带策略指定的限制。它们启动时带着与普通用户进程非常相近的令牌。原因是,在进程启动时,OS加载器要读取许多资源,它们中的大部分确实没有文档描述而且任何时候也不能更改。同样的,大多数应用程序使用标准的CRT(C运行时库)提供标准的开发工具。在进程启动后,CRT还需要做初始化,CRT初始化的内部过程同样没有文档描述。
因此,在进程启动的过程中实际上使用两个令牌:锁定令牌,是一个进程令牌;初始令牌,作为模拟令牌设置到初始线程。实际上SetTokenLevel 定义如下:
SetTokenLevel(TokenLevel initial, TokenLevel lockdown)
所有的关键初始化完成后,继续执行main或者WinMain,在这里这两个令牌仍然是活动的。但是只有初始线程可以使用更有威力的初始令牌。目标进程负责丢弃初始令牌,这通过一个调用完成:
LowerToken()
在目标进程调用这个函数后,只有锁定令牌是可用的,而且所有的沙箱限制开始起效。这个调用不能回退。注意初始令牌是一个模拟令牌,只对主线程有效,其它在目标进程中创建的线程只使用锁定令牌,因此不应该试图访问任何面向安全检查的系统资源。
目标进程随着特权令牌启动实际上简化了严格策略,因为需要特权的资源在一次访问后就完成。