本章译者:@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.