沙箱
安全是Chromium最重要的目标之一。安全的关键在于理解下面这点:在我们完整地理解了系统在所有可能的输入组合下表现出的行为之后,我们才能够真的保证系统安全。对于像Chromium这样庞大而多样化的代码库,推理它的各个部分可能的行为的组合几乎是不可能的。沙箱的目标是提供这样一种保证:不论输入什么,保证一段代码最终能或不能做的事情。
沙盒利用操作系统提供的安全性,允许不能对计算机做出持久性改变或者访问持续变化的信息的代码的执行。沙箱提供的架构和具体保证依赖于操作系统。这个文档覆盖了Windows实现与一般的设计。Linux实现和OSX实现也会在这里描述。
如果你不想要阅读这整个文档,你可以阅读Sandbox FAQ。沙箱保护与不保护的内容也可以在FAQ中找到。
设计原则
- 不要重新发明轮子: 用更好的安全模型扩展操作系统内核很有诱惑力。但不要这样做。让操作系统在所控制的对象上应用它的安全策略。另一方面,创建有自定义安全模型的应用程序层级对象(抽象)是可以的。
- 最小权限原则: 这既应该用于沙箱代码也应该用于控制沙箱的代码。换言之,即使用于不能提升权限到超级用户,沙箱也需要能够工作。
- 假定沙盒代码是恶意代码: 出于威胁建模的目的,我们认为沙箱中的代码一旦执行路径越过了一些main()函数的早期调用,那么它是有害的(即,它会运行有害代码),实践中,在第一外部输入被接收时,或者在进入主循环前,这就可能发生。
- 敏感: 非恶意代码不会尝试访问它不能获得的资源。在这种情况下,沙箱产生的性能影响应该接近零。一旦敏感资源需要以一种控制行为访问时,一点性能损失是必要的。这是在操作系统安全合适事情情况下的常见例子。
- 仿真不是安全: 仿真和虚拟机方案本身不能提供安全。沙箱不会出于安全目的,依赖于代码仿真,或者代码转换,或者代码修复。
沙箱windows架构
Windows沙箱是一种仅用户模式可用的沙箱。没有特殊的内核模式驱动,用户不需要为了沙箱正确运行而成为管理员。沙箱设计了32位和64位两种进程,在所有windows7和windows10之间的所有操作系统版本都被测试过。
沙箱在进程级粒度进行运作。凡是需要沙箱化的任何东西需要放到独立进程里运行。最小化沙箱配置有两个过程:一个是被成为broker的权限控制器,以及被称为target的一个或多个沙箱化进程。在整个文档和代码中这两个词有着上述两种精确的内涵。沙箱是一个必须被链接到broker和target可执行程序的静态库。
broker进程
在Chromium中,broker总是浏览进程。broker,广泛概念里,是一个权限控制器,沙箱进程活动的管理员。broker进程的责任是:
- 指定每个目标进程中的策略
- 生成目标进程
- 维护沙箱策略引擎服务
- 维护沙箱拦截管理器
- 维护沙箱IPC服务(与target进程的通信)
- 代表目标进程执行策略允许的操作。
broker应该始终比所有它生成的目标进程还要活的久。沙箱IPC是一种低级别的机制(与ChromiumIPC机制不同),这些调用会被策略评估。策略允许的调用会由broker执行,结果会通过同样的IPC返回给目标进程。拦截管理器是为应该通过IPC转发给broker的windows API调用提供补丁。
目标进程
在Chromium中,渲染器总是target进程,除非浏览进程被指定了--no-sandbox命令行参数。target进程维护所有将在沙箱中允许的代码,以及沙箱基础设施的客户端:
- 所有代码沙箱化
- 沙箱IPC客户端
- 沙箱策略引擎客户端
- 沙箱拦截
第2,3,4条是沙箱库的一部分,与需要被沙箱化的代码关联。
拦截器(也称为hook)是通过沙箱转发的Windows API调用。由broker重新发出API 调用,并返回结果或者干脆终止调用。拦截器+IPC机制不能提供安全性;它的目的是在沙箱中的代码因沙箱限制不能修改时,提供兼容性。为了节省不必要的IPC,在进行IPC调用前,target中进程策略也会被评估,尽管这不是用作安全保障,但这仅仅是一个速度优化。
期望在未来大部分plugin会运行在target进程里。
沙箱限制
在它的核心,沙箱依赖于4个Windows提供的机制:
- 限定的令牌
- Windows工作对象
- Windows桌面对象
- Windows Vista及以上:集成层
这些机制在保护操作系统,操作系统的限制,用户提供的数据上相当的高效,前提是:
- 所有可以安全化的资源都有一个比null更好的安全描述符。换言之,没有关键资源会有错误的安全配置。
- 计算机并未被恶意软件所损害。
- 第三方软件不能弱化系统安全。
注意:上面具体的措施以及在内核外的措施会在下面的“进程轻量化”部分阐述。
令牌
其他类似的沙箱项目面临的一个问题是,限制程度应当如何,才能使得令牌和作业同时还保持有正常的功能。在Chromium沙箱里,对于Windows XP最严格的令牌如下:
普通组
登录 SID : 强制
其他所有SID : 仅拒绝, 强制
限制组
S-1-0-0 : 强制
特权
无
正如上面所述的警告,如果操作系统授予了这样一个令牌,几乎不可能找到存在的资源。只要磁盘根目录有着非空的安全性,即使空安全的文件也不能被访问。在Vista中,最严格的令牌也是这样的,但它也包括了完整性级别较低的标签。Chromium渲染器通常使用这种令牌,这意味着渲染器进程使用的大部分资源已经由浏览器获取,并且他们的句柄被复制到了渲染器进程中。
注意,令牌不是从匿名令牌或来宾令牌而来的,它继承自用户的令牌,因此与用户的登录相关联。因此,系统或域名拥有的任何备用的审计仍然可以使用。
根据设计,沙箱令牌不能保护下面这些不安全资源:
- 挂载的FAT或FAT32卷: 它们上面的安全描述符是有效空。在target中运行的恶意软件可以读写这些磁盘空间,因为恶意软件可以猜测或者推出出它们的路径。
- TCP/IP: Windows 200和Windows XP(但在Vista中不会)中的TCP/IP socket的安全是有效空。使得恶意代码与任何主机收发网络包成为可能。
关于Windows 令牌对象的更多信息可以在底部参考文献[02]查看。
作业对象
target进程也运行着一个作业对象。使用这个Windows机制,一些有趣的,不拥有传统对象或者不关联安全描述符的全局限制可以被强制执行:
- 禁止用SystemParametersInfo()做用户共享的系统范围的修改,这可以用于切换鼠标按钮或者设置屏幕保护程序超时
- 禁止创建或修改桌面对象
- 禁止修改用户共享的显示设置,比如分辨率和主显示器
- 禁止读写剪贴板
- 禁止设置全局Windows hook(使用SetWindowsHookEx())
- 禁止访问全局原子表
- 禁止访问在作业对象外创建的USER句柄
- 单活跃的进程限制(不允许创建子进程)
Chromium渲染器在激活所有这些限制的情况下允许。每个渲染器运行在自己的作业对象里。使用作业对象,沙箱可以(但当前还不行)避免:
- 过度使用CPU周期
- 过度使用内存
- 过度使用IO
有关Windows作业对象的详细信息可以在底部参考文献[1]中找到。
额外的桌面对象
令牌和作业对象定义来一个安全边界:即,所有的进程有着相同的令牌,同一个作业对象中所有进程也处于同样的安全上下文。然而。一个难以理解的事实是相同桌面上都有窗口上的应用程序也处于相同的安全上下文中,因为收发window消息是不受任何安全检查。通过桌面对象发送消息是不允许的。这是臭名昭著的“shatter”攻击的来源,也是服务不应该在交互桌面上托管窗口的原因。Windows桌面是一个常规的内核对象,它可以被创建然后分配一个安全描述符。
在标准Windows安装中,至少两个桌面会与交互窗口站相关联,一个是常规(默认)桌面,另一个是登录桌面。沙箱创建了第三个与所有target进程关联的桌面。这个桌面永远不可见,也不可交互,它有效地隔离了沙箱化进程,使其不能窥探用户的交互,不能在更多特权的环境下发送消息到Windows。
额外的桌面对象唯一的优点是它从一个隔离的池使用接近4MB的内存,在Vista里可能更多。
信用等级
信用等级在Windows Vista及其之后的版本可用。它们不会用严格的方式定义安全的边界,但他们确实提供了一种强制访问控制(MAC),并且作为微软IE沙箱的基础而存在。
信用等级由一个特殊的SID和ACL对的集合实现,它们代表了五种递增等级:不受信任的,低级的,中级的,高级的,系统的。如果一个对象处于比请求令牌更高级的信用等级,访问它就会受限。信用等级也实现了用户界面权限隔离,这种隔离应用了信用等级规则,让同一个桌面中的不同进程可用交换窗口消息。
默认情况下,令牌可以读高信用等级的对象,但不能写。大多数桌面应用运行在中信用等级(MI),而较不受信任的进程像IE保护模式和我们自己的沙箱运行在低信用等级(LI)。一个低信用等级模式的令牌只可以访问下面这些共享资源:
- 对大部分文件可以做读访问
- 对%USER PROFILE%、AppData、LocalLow目录的写访问
- 读注册表的大部分内容
- 对HKEY_CURRENT_USER\Software\AppDataLow目录做写访问
- 剪贴板(为某些格式做复制粘贴)
- 远程过程调用(RPC)
- TCP/IP Socket
- 通过ChangeWindowMessageFilter暴露窗口消息
- 通过LI标签共享内存
- 拥有LI启动激活的权限,访问COM接口
- 通过LI标签暴露的命名管道
你会注意到之前描述的令牌属性,工作对象,额外的桌面限制性更大,并且事实上会阻碍对上面列出的所有东西的访问。所以,信用等级比其他措施更宽松,但这也可以被视为一种对深度防御的否定,并且,它的使用对性能或者资源使用不会有明显的影响。
更多关于信用等级的信息可以在底部参考文献[03]找到。
进程轻量化策略
大多数进程轻量化策略可以可以通过SetProcessMitigationPolicy方法应用于Mtarget进程。沙箱使用这个API为target进程设置不同的各种策略,以强化安全特性。
重定位图像:
- >= Win8
- 在进程中对所有图片做随机地址加载(ASLR)(必须被所有图片支持)
堆之终结:
- >= Win8
- 结束Windows堆占用进程
自底向上ASLR:
- >= Win8
- 设置随机的下界作为进程的最小用户地址
高熵值ASLR:
- >= Win8
- 为自底向上ASLR增加随机等级到1TB。
严格句柄检查:
- >= Win8
- 对于恶意句柄引用立即抛出异常
Win32k.sys锁定:
- >= Win8
- ProcessSystemCallDisablePolicy,允许选择性关闭target进程可用的系统调用
- 渲染器进程现在把这个功能设置到了DisallowWin32kSystemCalls上,这意味着win32k.sys用户模式的调用不再被允许。这极大地减少了来自渲染器的可用的内核攻击。查看这里获取更多细节。
App容器(Low Box Token):
- >= Win8
在Windows里,这由内核层的一个Low Box Token实现,它是有着限制优先权(通常只有SeChangeNotifyPrivilege和 SeIncreaseWorkingSetPrivilege)的一个剥离版本,运行在低信用等级,这个容器还由一组“能力”实现,它们可以映射到进程允许/拒绝做的事情(查看MSDN获取更详细的描述)。从沙箱角度看,最有趣的能力是否决是对网络的访问,如果令牌是Low Box Token,INTERNET_CLIENT能力没有出现的话,就会执行网络检查。
因此沙箱对已有的限制令牌,添加了Low Box相关的属性,并且不授予任何能力,以获得没有来自沙箱化进程的网络访问这样的额外的网络保护。
禁用字体加载:
- >= Win10
- ProcessFontDisablePolicy
禁用远程设备图像加载:
- >= Win10 TH2
- ProcessImageLoadPolicy
- 例:网络资源的UNC路径
禁用“强制低信用等级”的图像加载:
- >= Win10 TH2
- ProcessImageLoadPolicy
- 例:临时Internet文件
禁用额外的子进程创建:
- >= Win10 TH2
- 如果作业等级\<= JOB_LIMITED_USER,用UpdateProcThreadAttribute()设置PROC_THREAD_ATTRIBUTE_CHILD_PROCESS_POLICY为PROCESS_CREATION_CHILD_PROCESS_RESTRICTED。
- 这是额外层面的防御,使得作业层可以从外部打破。[引用: ticket, Project Zero blog.]
其他警告
操作系统可能有一些bug。令人感兴趣的是Windows API中允许跳过常规安全检查的一些bug。如果存在这样的bug,恶意软件能够穿透安全限制,broker策略,并且可能危害计算机。在Windows环境下,没有实用的方式可以避免沙箱中的代码调用系统服务。
另外,第三方软件,尤其是反病毒解决方案,可能创建新的攻击角度。最麻烦的是为了使用一些(通常是系统不愿其使用的)功能,注入动态链接库的应用程序。这些动态链接库也会注入到沙箱进程中。在最好的情况下,他们会产生故障,在最糟的情况下,可能为其他进程或文件系统本身造出后门,让精心设计的恶意软件逃离沙箱。
沙箱策略
应用与target进程的真实限制通过策略设置。这些策略只是一种broker调用的编程接口,它们定义了限制与权限。四个函数控制这种限制,对应四种Windows机制:
- TargetPolicy::SetTokenLevel()
- TargetPolicy::SetJobLevel()
- TargetPolicy::SetIntegrityLevel()
- TargetPolicy::SetDesktop()
前三个调用接收从非常严格到非常宽松的整数等级参数,例如令牌有七个等级,作业有五个等级。Chromium渲染器通常运行四种机制中最严格的模式。最后,桌面策略有两种,只能用于表示一个target进程是否运行在额外的桌面对象中。
这些限制是粗糙设计的,因为它们会影响目标可访问的所有可保护资源,但有时我们需要更精细粒度的分辨能力。沙箱策略接口允许broker指定例外的情况。一个例外是,在target中发出特定Windows API调用,将其代理给broker的方式。broker可以检查参数,使用不同的参数重新发出调用,或者干脆拒绝调用。为了指定例外情况,需要有一个独立的调用:AddRule。现在支持以下几种针对不同的Windows子系统的规则: 文件 命名管道 进程创建 登记 *同步对象
每种子系统的具体形式各不相同,但通常规则会基于字符串模式得到触发。例如,一种可能的文件规则是:
AddRule(SUBSYS_FILES, FILES_ALLOW_READONLY, L"c:\\temp\\app_log\\d*.dmp")
这个规则指定了当一个target进程想要打开文件时,可以授予的权限,以及匹配字符串格式的文件的只读权限;例如 c:\temp\app_log\domino.dmp是一个满足上面那种格式的文件。查询头文件可以获得最新支持的对象与行为的列表。
规则只能在每个进程产生前添加,当target运行时不能修改,但不同的target可以有不同的规则。
Target引导
Target不会从策略定义的限制开始执行。他们从与常规用户进程拥有的令牌非常接近的一个令牌开始执行。因为在进程引导的过程中,操作系统加载器会访问大量的资源,其中大部分是未认证且随时会变化的。另外,大部分应用程序使用标准开发工具提供的标准CRT,在进程得到引导后,CRT也需要初始化,这时CRT初始化的内部再次变成未认证状态了。
因此,在引导阶段,进程实际上使用了两种令牌:锁定令牌,也是进程令牌,还有初始令牌,被设置为初始线程的模拟令牌。事实上,真正的SetTokenLevel定义是:
SetTokenLevel(TokenLevel initial, TokenLevel lockdown)
在所有的初始化操作完成后,main()或WinMain()会继续执行,还有两个令牌会存活,但只有初始线程可以使用更强大的那个初始令牌。target的责任是在准备完成后销毁初始令牌。通过下面这个简单的调用实现:
LowerToken()
在target声明这个调用之后,唯一可用的令牌是锁定令牌,完整的沙箱限制开始生效。这个调用不可以撤销。注意初始令牌是一个模拟令牌,它只对主线程有效,target进程创建的其他线程只使用锁定令牌,因此不会尝试获取任何需要安全检查的系统资源。
target始于特权令牌的事实简化了显式策略,因为任何特权相关的需要在进程启动时一次完成的东西,可用在LowerToken()调用前完成,并且不需要在策略中设置规则。
重要
请确保初始令牌获取的任何敏感操作系统句柄在调用LowerToken()前关闭。任何泄露的句柄可能被恶意软件利用以逃离沙箱。
参考文献
[01] Richter, Jeffrey "Make Your Windows 2000 Processes Play Nice Together With Job Kernel Objects"
http://www.microsoft.com/msj/0399/jobkernelobj/jobkernelobj.aspx
[02] Brown, Keith "What Is a Token" (wiki)
http://alt.pluralsight.com/wiki/default.aspx/Keith.GuideBook/WhatIsA令牌.htm
[03] Windows Integrity Mechanism Design (MSDN)