如果你是一个工作繁忙的开发人员,那么本文将是你构建安全的应用程序体系架构的一个很好的起点。

与过去相比,如今的开发人员可以更专注于构建软件,这是一件好事儿。

我们受益于创客文化中“持续交付”的理念、开源的协作模式,以及许多可以帮助我们确定优先级,提高执行效率的应用软件。

我们正处于一个创新不断的大环境中,无论是团队还是独资企业家都可以最大限度地提供生产力。

但有时,这种惊人的生产力也会暴露出它的缺点。

在我研究有关于安全性的最佳实践的过程中,我发现越来越多的应用程序在这方面做得毫无头绪。他们的开发人员似乎普遍缺乏安全意识,这就导致了不能对不直接影响产品上线的任务进行正确的优先级排序。

市场的情况似乎使得发布一个可用的产品比一个安全的产品更为重要。他们普遍的态度是:“我们之后可以再补充一些安全性方面的内容”。

在构建整个应用程序时,基于权宜之计,而非长久眼光来建立的基础架构,并不是一个明智的选择。除非你想留下很多的安全风险。

所谓的安全风险(类似于技术风险)会在开发人员作出(通常是仓促的)决策时造成麻烦,从而导致之后的应用程序更加难以维护。

如果你之前了解过关于“左推”的概念(或者有读过我的《敏感数据公开》),那么你就会知道,在安全性方面,“下个版本”就已经晚了。

实际上,在开发工作的初期就遵循一些具有高收益的基本的安全实践,并不会比这样做花费更多的时间。总之,拥有一些基础且重要的知识,会帮助你作出更安全的决策。

尽管不同的应用程序架构差异很大,但你仍可以运用一些基本原则。本文将为你做一个总体的概览,从而引导你步入正确的方向。

之所以我们能把应用程序体系称为“架构”,我想大概是因为它跟建筑学中的架构有相似之处。(至少,以我一个建筑领域的小白来说,是这样觉得的。)

关于如何构建一个安全的应用程序架构,我总结为以下三个基本要点:

  1. 隔离存储
  2. 定制化配置
  3. 受控访问及用户范围

当然,上面的总结只是一个起点,旨在指引我们起步前往正确的方向。一个具有完整安全性的应用程序架构除了本文涉及到的部分之外,还会包括身份验证、日志记录、监控系统、集成性以及合规性方面的内容。

1. 隔离存储

从安全性的角度来看,“隔离”的概念是指在不同位置存储服务于不同目的的文件。

比如当你在设计一栋楼的房间分配的时候,你大概会把储物间放在偏角落的地方,会议室放在一些宽敞的,大一点的房间,而管理层的办公室可能会放在视野更宽阔的高层。虽然这些都是房间,但你也会知道它们会有不同的用途。而根据它们功能性的不同,需要考虑的安全性也会有非常大的区别。

separation

类比于应用程序中的文件目录结构,可能对你来说也更容易理解一些:

application/
 ├───html/
 │   └───index.html
 ├───assets/
 │   ├───images/
 │   │   ├───rainbows.jpg
 │   │   └───unicorns.jpg
 │   └───style.css
 └───super-secret-configurations/
     └───master-keys.txt

在这个简化的例子中,假设你程序中所有的图片都保存在 application/assets/images/ 这个路径下,那么当你的用户创建个人资料并上传某些图片时,该图片也会被保存在这个路径下。可能你会问,这本来就是一张图片,保存到图片都在的路径下,这有什么问题呢?

如果你熟悉终端操作,那你很可能见过这种语法:../../。两个点表示返回上层路径。假如你在 images/ 这个路径下执行命令 cd ../../,那么你会返回到 assets/,然后再返回一层,到这个程序的根目录 appliaction/ 下。这里就会有一个由路径遍历而引发的问题。

虽然看上去这种写法减少了一些键入,但实际上它并不明确的知道,它的父级目录的名字是啥。

试想下,如果一个攻击脚本进到了 images/ 这个目录,该脚本循环地使用 cd ../,然后把所有遍历到的内容发送给攻击者。那么它将最终到程序的根目录,并且访问到 super-secret-configurations/,这就很糟糕了😰。

虽然我们本应该采取其他措施来防止这种路径遍历和由于用户上传导致的漏洞,但就目前而言,最简单的预防措施就是隔离存储。核心部分的程序文件不应该与其他数据文件混合在一起,尤其是用户输入的内容。最好是将用户上传的文件和用户行为的日志(可能包含其他容易造成注入攻击的数据)与程序主体分开。

你可以通过不同的服务器、不同的实例、独立的 IP 范围或者独立的域名来实现这种存储的隔离。

2. 定制化化配置

尽管花费时间进行定制化会影响生产率,但配置的设置还是你需要进行定制化的部分。

安全性配置错误被列举在 OWASP Top 10中。大量的安全事件的发生都是由于服务器、防火墙或者管理员账号的相关信息以默认配置运行在生产环境中。在实际的工作中,你也要格外注意这些安全隐患。

defaultkey

通常,与默认设置有关的攻击的受害者并不是被专门针对的。相反地,它们大都是由自动扫描工具发现的,攻击者可以在许多可能的目标上运行这些工具。攻击者会测试许多不同的系统,以查看是否有任何发现并暴露出可利用的漏洞的机会。

这种攻击的自动性意味着,你必须检查整个应用程序架构中的每个部分的设置。这一点很重要。因为即使单个的部分看起来并不重要,但在整体上它也可能会提供一个漏洞,使得攻击者可以侵入整个程序。

特别是要检查一些不太会被注意到的架构组件,例如:

  • 默认账号,尤其是使用默认密码的账户信息;
  • 示例网页、应用程序开发的教程或者在程序中遗留的样例数据;
  • 不必要的服务端口或者开放到 Internet 的端口;
  • 不限权的 HTTP 方法;
  • 保存在自动化日志中的敏感信息;
  • 管理服务中的默认配置权限;
  • 默认可访问的目录或敏感的文件类型。

上述的列表并不详尽。特定的架构组件(例如云存储或 Web 服务)还会有其他的可配置内容以供查看。通常,也可以通过使用最少的架构组件来尽量减少应用程序收到类似攻击的可能性。如果你使用较少的组件,或者避免安装不必要的模块内容,那么可能受到攻击的地方也将更少。

3. 受控访问及用户范围

应用程序的测试工作中,最困难的一种安全性测试内容就是访问受控。自动化的测试工具在查找一个用户不该访问的应用模块的能力是非常有限的。因此,这部分工作通常会由人工测试或源代码审查来完成。

开发人员可以减少留有以后更难以解决的问题的风险。在制定整体架构,整个软件生命周期的初期就应当尽可能的考虑到这些风险问题。毕竟,你应该也不会把万能钥匙随便的丢在哪里,然后看着谁拿着它随意而行。

access-1

被破坏的访问受控也在 OWASP Top 10 中被提到。其中更详细的介绍了其各种形式。举个简单的例子,比如一个具有两种访问级别的应用程序,分为管理者和普通用户。开发人员希望实现一个功能:审核或禁止某些用户。但是只有管理员的访问级别才可以使用这个功能。

如果你有意识到这可能存在的访问级别漏洞,则可以选择在与普通用户可访问空间外的不同区域中,去实现这个功能。这所谓的不同区域可能是在另一个域名下,或者是普通用户不共享到的一个模型。这样就降低了访问控制配置错误,或者由于特定的等级提升漏洞可能导致的普通用户也能使用到这个功能的风险。

当然了,强大的访问控制需要更多的技术支持才得以生效。例如考虑到敏感令牌、密钥通过 URL 参数进行传递或者是控制本身的安全性。即便如此,在架构设计时考虑到授权认证的内容,也会使得将来在加强程序安全性方面的工作变得更容易些。

基础的安全保障带来最大化的效益

选择适合的框架会帮助开发人员避免更多的技术负担。同样地,开发人员可以通过意识到常见的漏洞,有助于减少这些漏洞的架构决策来避免一些安全隐患。如果你想了解更多关于如何从一开始就将安全性考虑到应用程序设计中,那么 OWASP 应用程序安全验证标准则是一份不错的指南。

原文:How to make your app's architecture secure right now: separation, configuration, and access,作者:Victoria Drake