指南, 教程 & 参考手册

Browse

Contents

好书推荐

国内书籍(将于2012年出版)

国外书籍(电子图书)

安全指导

本章译者:@nixil

虽然Play在设计之初就考虑了安全性问题,但是任何人都无法阻止程序员们自毁长城。以下的向导将会涉及web应用常见的安全性问题,以及在Play中该如何避免。

Sessions

你经常会需要保存一些跟用户有关的信息,比如登录状态之类的。如果没有session,用户就得在每个请求当中都携带认证信息。

所谓session就是一组储存在用户浏览器的cookie中的数据,用于标识用户,有时候还会根据应用的需要存储一些额外信息,比如用户的语言之类的。

安全第一要素 - 别把秘密公开

session是一组键/值的哈希,它带有数字签名但是并未加密。这意味着,如果你的签名是安全的,任何第三方都无法伪造session。

这个数字签名保存在@conf/application.conf@中. 一定要保证机密(数字签名)的私有性,绝对不要把它提交到公有代码库中。当你安装了一个由其他人构建的应用时,记得一定要用这个命令@play secret@,来修改原来的数字签名,。

不在session中储存关键数据

尽管有数字签名,但由于cookie是未加密的,你还是不应在session当中存储关键性数据。否则可能被通过查看cookie,或者在局域网/wifi的网关截获http请求等方式暴露。

Play的session是存储在cookie中的,而cookie的大小被浏览器限制为4KB. 除了空间限制外, cookie中还仅能保存文本值。

跨站脚本攻击

跨站脚本攻击是web应用最大的弱点之一。其原理是在使用web应用的表单提交信息时,注入恶意JavaScript脚本. (这就好像送快递的往你家的邮箱里塞一个夹带了窃听器的包裹)

Let’s say you’re writing a blog, and anyone can add a comment. If you blindly include what commenters have written into your HTML page, you’re opening your site to attacks. It can be:

  • Show a popup to your visitors
  • Redirect your visitors to a site controlled by the attacker
  • Steal information supposed to be visible only to the current user, and send it back to the attacker’s site

假设你有一个写博客的程序,而任何其它人都可以对博文添加评论,而评论最终会被显示在该。如果你允许评论者们将任何评论信息(比如一段html代码,其中可能还夹杂了javascript)内容提交到你的页面中,你的站点就会被攻击。可能会导致:

  • 你的blog的访客将会收到一个弹出窗,这可能只是恶搞一下
  • 你的blog的访客会被重定向到一个在攻击者控制之下的站点
  • 窃取到本应只有当前用户才能看到的信息,并将其发送到攻击者的站点。

因此避开这些攻击是至关重要的

Play的模板引擎会自动的将文本转义。如果你确实需要在模板中插入未转义的HTML,可以使用raw() 这个java扩展方法。但是如果文本是来自用户的输入,那你就要谨慎为之,确保先对这些输入进行“消毒”。

审查用户输入的时候,使用“白名单(只允许某些安全标签)”比使用“黑名单(禁止某些不安全标签而允许其他标签)”来的安全。

更多关于跨站脚本

SQL 注入

SQL注入是一种利用用户的输入来执行SQL脚本的攻击方式。这种攻击可能摧毁你的数据,也可能使你的数据暴露给攻击者。

如果你使用高级的“find”方法,你必须考虑到对付SQL注入的问题。当你手工创建查询语句时,一定要小心不要使用字符串拼接(+)的方式传入参数,而应该使用@?@作为占位符然后替换。

这种是安全的:

createQuery("SELECT * from Stuff WHERE type= ?1").setParameter(1, theType);

而这一种则是危险的:

createQuery("SELECT * from Stuff WHERE type=" + theType;

CSRF-跨站请求伪造

CSRF-跨站请求伪造(又有戏称session-riding)也是web应用的一个大问题:

这种攻击方法的前提是用户(在当前或者最近一段时间)登录了你的应用,浏览器中还保留着cookie。此时如果在该用户访问的某个页面(可能是任何一个其他的网站)中引入一段恶意的代码或者链接,使之向你的应用发起请求。那由于这个用户的session还没有过期,该请求就会利用这个“伪造”的session来通过应用的认证,执行恶意的操作了。

要防止这种攻击,首先要正确的使用GET和POST方法. 也就是说,POST方法应该仅用于更改应用的状态. (而相应的GET方法不应该用于更改应用状态,这里也就是所谓的GET方法应该具有等幂性,这样一来攻击者就无法通过恶意的链接来执行恶意操作)。

而对于接受POST请求的controller来说,保证每次收到的请求都是安全的方法之一,就是要求每个请求都提供一个认证口令(这个口令不是存放在cookie中而是作为随表单一起提交的一个隐藏字段)。Play提供了一些内置的帮助类和方法来处理这些事情。

  • controller中有一个@checkAuthenticity()@方法,它会检查包含在请求参数中的口令的合法性,如果发现不对就会返回一个forbidden(403)的响应。
  • session.getAuthenticityToken() 方法会生成一个仅对当前session有效的口令
  • 在html模板的form中使用 #{authenticityToken /} 会生成一个包含口令的隐藏域

So for example:
例如下面的代码(controller中的一个方法):

public static destroyMyAccount() {
    checkAuthenticity();
    …
}

checkAuthenticity()会进行检查,只有提交的表单中包含正确的认证口令(模板中要加入如下面的代码)时才会执行

<form method="post" action="/account/destroy">
    #{authenticityToken /}
    <input type="submit" value="destroy my account">
</form>

Play的“表单标签”: tags:#form 当其要提交到的action接受POST方法时会自动生成一个认证口令

#{form @destroyMyAccount()}
    <input type="submit" value="destroy my account">
#{/form}

当然如果你想把对所有controller的访问都保护起来的话,你可以在增加一个controller的“before filter”: controllers#before,把@checkAuthenticity()@方法加在里面

更多关于跨站请求伪造的信息

Continuing the discussion

Next: Play modules.