2 代码安全(Code Security)
没有完美的软件。尽管许多缺陷是由于设计错误,一些最具破坏性的缺陷是由实现错误引起的。设计缺陷和执行缺陷可以损害的数据,可以使您的软件行为不当。攻击实现缺陷有时也会导致你的软件来执行攻击者提供的任意的二进制代码。
阻止这些攻击,威胁模型只能帮你到此。他们识别的部分基础设施可能被合理攻击,但是他们不确定具体这些碎片可能是攻击的方法。
2.1代码硬化(Code Hardening)
代码硬化指修复安全漏洞的代码本身(而不是设计错误)。本质上,代码硬化就像修理城堡的墙上的坏的砂浆以防止攻击者从此进来,加固船的船尾修复泄漏,或隐藏一个统治者的秘密,这样他或她不能被敲诈成为一个傀儡统治者。
这里有一些代码硬化的技术:
- 添加来验证输代码入,以防止整数溢出。
- 代替任何不安全的字符串函数调用,通过调用缓冲区大小感知(buffer-size-aware)的方法防止缓冲区溢出。
- 尽可能避免传递数据到解释器(interpreters)。当必须要使用解释器时,通过一个安全的方式传递数据。
为了防止在SQL查询命令注入攻击,使用参数化api(或手动引用字符串如果参数化api不可用)。
避免POSIX(可移植操作系统接口) - 设置合理的环境变量的值(PATH, USER,等等),不基于他们的值进行安全决策。
- 修改bug导致竞争条件;这些可能导致不正确的行为(或更糟)。
在本章结束时,你会发现一个链接到文档更详细地描述这些技术,连同其他code-hardening技术。
2.2代码签名(Code Signing)
代码签名技术保证可执行代码的可靠性。通过签署你的代码,操作系统可以验证应用程序没有被其他软件修改,可以验证更新应用程序确实是作者发布的。其他技术,如钥匙链和应用沙盒,利用这个签名来更好地保护用户的数据。
了解细节,不过,你得学习更多的概念。出于这个原因,在第4章加密服务(Cryptographic Services)重新审视代码签名。
2.3 最小特权原则(Principle of Least Privilege)
最小特权原则声明一段代码,在某种程度上实用、准确运行需要的权限,没有更多。
最接近的模拟物理世界中指定某些领域某些活动,并且只有当他们有一个合法的需要执行这些活动时才允许人们进入这些地区。例如,在一次会议上,技术人员不需要访问演讲者支持中心,演讲者(通常)也不需要访问后台区域。
几乎以相同的方式,你的软件不应该承担或授予他人任何比必要做特定的工作更多的权限。例如,应用程序不应该:
- 在不是确实必要的情况下请求权限提升
- 部分应用程序包放宽权限
- 不必要的网络连接
- 在不必要的网络端口上监听连接
- 无意的在公共网络接口监听连接
- 在没有用户指示的情况下公开可写权限的文件夹读或写文件
这些只是几个例子。因为这些活动是随附于你的应用的主要行为, 当您添加新代码时有规律的执行威胁建模是至关重要的。
2.4 应用沙盒(App Sandboxing)
对父母来说,沙盒是一个安全的港湾,在这里孩子们可以玩,而不需要担心他们受伤。这是一个比较封闭的,安全的环境,远离可能会伤害到他们的事物。只要孩子们在沙箱内,他们不能(容易)导致沙箱之外的恶作剧。
同样,在计算机中,一个沙箱的好处不仅限于糟糕的应用程序。任何包含安全漏洞的应用程序如果利用得当都可以“变坏” ,可能导致应用程序运行任意代码。因此,在计算,沙箱应该被广泛应用到所有app,确保如果他们被侵害不能造成太多的损害。
为了实现这一目标,沙箱限制应用程序的功能来匹配其预期行为(如使用的api,在某些情况下, 应用程序的作者通过一系列附加权利要求)。
因此,在计算中,沙箱更像邻里监督组织监督违法程序的眼睛。如果有人出现行为可疑,邻居们可以采取行动。以类似的方式,沙箱环境允许应用程序做通常会做的事情。然而,如果踩到红线的,操作失败,在某些情况下,这个进程会被立刻杀死。
2.5 权限分离(Privilege Separation)
在一个理想世界中,根据最小特权原则编写软件将会是简单的;用户授予做一份工作每个流程所需的足够的特权,没有更多。然而,这种方法在实践中可能是一个挑战,特别是当工作的性质定义不明确的时候。
一个真实世界的例子,清洁工需要能够把垃圾拿出去。清洁的位置通常并不需要一个高度安全许可。假设有一个垃圾桶在一个房间里充满了绝密文件。因为这垃圾桶表面上是清洁工的职责,工作相当广泛的最小特权清洁工需要一个高度安全的许可。更好的解决方案是将垃圾桶放到门外。或者,一个已经有了必要许可的员工可以在一天结束的时候把垃圾桶拿出来。
在计算机中,此问题的解决方案是特权分离,将软件分成多个部分,每一块单独需要较少的权限,这样可以免受应用程序的其他部分,工具,或守护进程的不当使用。这个分离块称为信任边界(trust boundary)。
例如,一个字处理器通过网络访问帮助文件的网络部分可能分离到一个单独的帮助文件下载应用。主要应用需要仔细检查从这个助手发回的数据,既要确保它在运输途中未被篡改,并确这个助手如果被入侵并不能轻易攻击的主应用程序。
重要:助手和主应用之间的不信任必须是相互的。网络助手在前面的例子必须仔细审查主要应用程序的请求,确保:
- 所有的请求都去向正确的服务器
- 所有的请求资源的请求应用程序都被授权
- 只在适当的时候进行HTTP重定向
没有这些检查,攻击者破坏的主要应用程序可能使用网络帮助转发一份用户的数据到其他地方。
特权分离是通过编写一个助手,守护进程,或代理,目的是使它在软件的另一部分工作。助手,守护进程,或代理人可能沙箱封装化的,未沙盒封装化得或享有特权的。
- 沙盒化的助手,守护进程,或代理权限少于一个普通用户运行的应用程序。它可能还是比调用应用程序更多的权限,然而,由于调用者可能在一个更严格的沙箱。
- 未沙盒化的助手,守护进程,或代理和用户具有相同的权限。然而,由于调用应用程序可能在沙箱中运行,这个未沙盒化的助手可能比调用者有更多的特权。
- 特权助手、守护进程或代理运行另一个更广泛的用户权限(经常作为根用户,或超级用户,这本质不限制操作)。
特权助手无法创建在一个沙箱环境;然而,他们在使环境更有用上发挥关键的作用。例如,一个特权代理(特权只因为它是运行在沙箱)使用操作系统向沙箱应用提供一系列的服务,如powerbox,显示一个“打开文件”对话框代表应用程序然后暂时将所选文件添加到应用程序的沙箱。
因为不同的特权助手、守护进程或代理有可能允许调用者明显违反了权限范围,它必须用这种方式限制其调用者能做什么。例如,powerbox允许应用程序访问文件应用程序的容器目录之外,但它用户采取一个明确的行动来表示同意。
OS X v10.7介绍了XPC服务API来创建沙箱助手应用程序特定于一个单一的应用程序。这些helper应用程序可以与主要应用有不同的权限。OS X v10.8后来还提供NSXPC API,这使得特权分离的过程更透明,让主应用程序远程调用指定的方法在特定的辅助应用程序中的对象,反之亦然。