[译] Martin Fowler - Web 应用安全基础

Github Repo:https://github.com/wxyyxc1992/infosecurity-handbook/blob/master/Reinforce/WebSecurity/basics-of-web-application-security.md
原文:The Basics of Web Application Security

现代的软件开发者已经有点像瑞士军刀了,首先,你需要来保证完成用户的功能或者业务需求,并且要保证又快又好地完成。其次,你希望你的代码能够拥有充分的可理解性或者可扩展性:能够随着IT需求的快速变迁而有着充分的扩展空间,与此同时还需要稳定与可用。开发者必须列举出有用的接口,优化数据库,以及频繁地建立或者维护一个交付渠道。不过,当我们审视这长长的需求列表的时候,在快速、低成本以及灵活可扩展之下的,即是安全性。或许直到一些东西出了问题,或者你构建的系统被攻击了之后才能深刻感受到安全才是最重要的。安全这个概念有点像性能,是个泛化的跨越了多个领域的概念。所以一个开发者怎么才能在模糊的安全需求与未知的风险面前选择合适的开发规划呢?当然如果能够明确这些安全需求与定位到威胁的话毫无疑问非常值得推荐,但是这个准备本身就需要耗费大量的时间与金钱。

Trust(信赖)

首先,在讨论具体的输入输出之前,我们需要来强调下自认为安全中最重要也是最根本的原则:Trust。作为一个开发者,也需要不断地问自己,我们相信来自于用户浏览器的请求吗?我们相信上游系统正常工作来保证了我们数据的干净与安全吗?我们相信服务器与浏览器之间的信道就不会被监听或者伪造吗?我们相信我们系统本身依赖的服务或者数据存储吗?呵呵,都不可信。

当然,就像安全一样,Trust也不是一个双选题,非黑即白。我们需要明白系统的风险忍受力与数据的安全边界。为了能够正确的、基于某个统一规则的预估,我们需要审视威胁与风险,这个评估方法与标准会在另一篇文章中讲解。

Reject Unexpected Form Input(拒绝未知的表单输入)

HTML表单本身就可能带来些好像很安全的错觉,表单的构建者肯定觉得他们限制了输入类型、做了数据校验,这样整个表单输入就是安全的。但确信无疑的是,这只是个错觉,尽管客户端地JavaScript脚本可以从安全地角度来说提供完整的校验。

Untrusted Input

无论我们是否在客户端提供了表单验证或者是否使用了HTTPs连接,我们能够信赖来自用户浏览器的连接的比例都是0。用户可以轻易地在发送之前修改标记,或者使用类似于curl这样的命令行来提交没有经过校验的数据。乃至于一个不明所以的用户可能在一个怀有恶意的网站莫名其妙地添了些内容。浏览器的同源策略并不能够避免来自于恶意站点的提交。为了保证输入数据的完整性,服务器端务必要进行数据校验。

不过估计有人有疑问了,为啥说这个畸形的数据就会导致安全问题呢?这往往取决于你的应用业务逻辑与输出的编码,为了避免不可预知的行为、数据泄露与潜在攻击,需要在输入的数据与可执行代码之间架构一个过滤层。譬如,我们的表单里有一个选择的按钮来允许用户选择合适的通信类型,我们的业务逻辑代码可能是这样的:

final String communicationType = req.getParameter("communicationType");
if ("email".equals(communicationType)) {
    sendByEmail();
} else if ("text".equals(communicationType)) {
    sendByText();
} else {
    sendError(resp, format("Can't send by type %s", communicationType));
}

上面代码危不危险取决于sendError这个方法是怎么定义的,而我们肯定无法确定下游的代码就一定是安全的。最好的选择就是我们在控制流中移除这个危险,而使用的方法就是输入验证。

Input Validation

输入验证即是保证实际输入与应用预期的输入的一致性。超出预期的输入数据会导致我们系统抛出未知的结果,譬如逻辑崩坏、触发错误乃至于允许攻击者控制系统的一部分。其中像数据库查询这样的能够在服务端作为可执行代码的输入与JavaScript这样在客户端能够被执行的代码更是特别的危险。因此验证输入时保证系统安全性与防卫危险的第一道防线。

开发者们在构建应用系统的过程中会进行一些基本的验证,譬如判断值是否为空或者是否为正数。而从安全的角度考虑,我们需要将输入限定到系统允许的最小集合中,譬如数值型值可以被限定在某个特定的范围内。譬如,系统不会允许用户将一个负值添加到购物车中。这种限制性的验证手段就是所谓的positive validation或者whitelisting。一个白名单可以用于限定某个具体的URL或者yyyy/mm/dd这样的时间日期。它可以限制输入的长度、单个字符的编码规范或者上面例子中的只有给定值可以被接受。

另外一种考虑输入验证的思维角度就是把它当做服务端与消费者之间签订的一种协议,任何违背了这个协议的请求都是无效的并且被拒绝。你的这个协议越严格,你的系统在未知情况下遭受的风险就会越小。而当对于某个输入验证失败之后,开发者也要好好考虑应该如何反馈。最严格,也是最有争议的办法就是全部拒绝,并且没有任何反馈,不过要注意将这个事情通过日志或者监控记录下来。不过为啥一点反馈都没有呢?我们需要提供给用户哪些信息是无效的吗?这一点还是要取决于你的约定。在上面的例子中,如果你接收到了除了email或者text之外的内容,那你有可能被攻击了。不过如果你进行了反馈,可能正中全套。譬如如果开发者直接返回:俺们并不认识你传入的communicationType,可能这个还无伤大雅,但是如果是这样的呢:

这种情况下你就会面临一个用来盗取你的Cookies的XSS攻击代码,如果你一定要给用户反馈,你必须保证不会把不受信任的用户内容直接返回,而应该使用固定的提示信息。如果你不可避免地要把用户的输入反馈回去,你要保证它是被编码的。

In Practice

实践中,我们经常要通过过滤

你可能感兴趣的:(安全)