安全漏洞问题4:跨站请求伪造
1.1. 漏洞描述
跨站请求伪造(CSRF)是一种挟制终端用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。攻击者只要借助少许的社会工程诡计,例如通过电子邮件或者是聊天软件发送的链接,攻击者就能迫使一个Web应用程序的用户去执行攻击者选择的操作。
在跨站请求伪造攻击中,攻击者构造恶意URL请求,然后诱骗合法用户访问此URL链接,以达到在Web应用中以此用户权限执行特定操作的目的。和反射型XSS的主要区别是:反射型XSS的目的是在客户端执行脚本;CSRF的目的是在Web应用中执行操作。
1.2. 漏洞危害
当CSRF针对普通用户发动攻击时,将对终端用户的数据和操作指令构成严重的威胁,当受攻击的终端用户具有管理员帐户的时候,CSRF攻击将危及整个Web应用程序。
例如,如果用户登录网络银行去查看其存款余额,他没有退出网络银行系统就去了自己喜欢的论坛去灌水,如果攻击者在论坛中精心构造了一个恶意的链接并诱使该用户点击了该链接,那么该用户在网络银行帐户中的资金就有可能被转移到攻击者指定的帐户中。
1.3. 解决方案
可以采用如下手段防范跨站脚本攻击:
限制身份认证cookie的到期时间
身份认证cookie的合法时间越短,攻击者利用Web应用程序的机会就越小。不过,这个时间越短,用户就越不方便。因此,需要在安全性和方便性之间进行平衡。
如下代码中,具体功能代码前加入一段检查用户登录超时时间的代码,通过检查用户最后一次操作时间与当前时间的差值是否小于系统设置值(本例中为600秒)来判断:
//获取当前系统时间
long currentTime =new Date().getTime()/1000;
Long lastTime =
((Date)request.getSession().getAttribute("LAST_TIME")).getTime()/1000;
If(currentTime - lastTime > 600)
{
session.invalidate();// 销毁会话
response.getWriter().println(
"
- "");window.close();"
) ;
}
Else
{
……
//实际功能处理代码
……
//更新session中记录的当前用户最后操作时间
}
要求用户提交额外的信息
执行重要业务之前,要求用户提交额外的信息。例如要求用户在进行重要业务前输入口令或图形验证码,这可以防止攻击者发动CSRF攻击(只要浏览器中没有包含口令或验证码信息),因为这种重要信息无法预测或轻易获得。
如下代码中,具体功能代码前加入一段检查用户是否输入了正确的图性验证码的代码,只有图形验证码检查通过才开始执行具体的功能代码:
// 比较验证码
String input_code = request
.getParameter("validate_code");// 用户输入的验证码
String right_code = (String) request.getSession()
.getAttribute("validate_code");// 存在于Session中的正确验证码
if (! input_code.trim().equalsIgnoreCase(right_code)) {
request.setAttribute("MSG", “验证码有误”);
return mapping.findForward("fail");
else
{
……
//实际功能处理代码
……
}
使用一次性令牌
使用一次性令牌,这是当前 Web 应用程序的设计人员广泛使用的一种方式,方法是对于 Get 请求,在 URL 里面加入一个令牌,对于 Post 请求,在隐藏域中加入一个令牌。这个令牌由 server 端生成,由编程人员控制在客户端发送请求的时候使请求携带本令牌然后在 Server 端进行验证。在令牌的设计上应避免采用如下错误的方案:
使用和 Session 独立的令牌生成方式。这种令牌的值和 Session 无关,因此不容易被其他用户伪造。这里的其他用户指的是当前 Web 应用程序的其他用户和活跃在网络传输阶段各个设置上的监听者,这种恶意用户可能使用自己的令牌来进行替换以便达到伪造的目的。
完全使用 Session 认证信息作为令牌的生成方式。这种保护方式可以防范CSRF,但是可能会造成其他危害,具体来说,如果某些 URL 或者网页被拷贝下来与其他人共享,那么这些 URL 或者拷贝下来的网页中可能会含有用户的会话信息,这种信息一旦被恶意用户获得,就能造成极大的危害。
一个正确的令牌设计应该是使用 Session 信息做 Hash,用得出的哈希值来做 CSRF 的令牌。
如下示例代码:
1、生成令牌代码函数
Public String getTokenCode(HttpServletRequest request){
String tokenCode =request.getSession.getId+Math.abs(new SecureRandom().nextLong());
Return tokenCode;
}
2、输出页面链接时,POST域中包含此一次性令牌(a_dynamic_value)
value=“<%= a_dynamic_value %>” />
3、后台处理时,检查传入的一次性令牌是否合法,只有包含合法的令牌数据才执行后续的功能代码
//获取令牌数据
String input_token =request.getParameter(“secret”);
String right_token = getTokenCode(request);
//检查令牌是否合法
if (! input_token.equalsIgnoreCase(right_token)) {
{
request.setAttribute("MSG", “令牌有误”);
return mapping.findForward("fail");
}
Else
{
……
//实际功能处理代码
……
}
检查 HTTP 头部 Referer 信息
检查 HTTP 头部 Referer 信息,这是防止 CSRF 的最简单容易实现的一种手段。根据 RFC 对HTTP 协议里面 Referer 的定义,Referer 信息跟随出现在每个 Http 请求头部。Server 端在收到请求之后,可以去检查这个头信息,只接受来自本域的请求而忽略外部域的请求。该方法仅能作为补充,因为检查方式由于过于简单也有它自身的弱点:
首先是检查 Referer 信息并不能防范来自本域的攻击。在企业业务网站上,经常会有同域的论坛,邮件等形式的 Web 应用程序存在,来自这些地方的 CSRF 攻击所携带的就是本域的 Referer 域信息,因此不能被这种防御手段所阻止。
同样,某些直接发送 HTTP 请求的方式(指非浏览器,比如用后台代码等方法)可以伪造一些 Referer 信息,由于目前客户端手段层出不穷,flash、javascript 等大规模使用,从客户端进行 Referer 的伪造,尤其是在客户端浏览器安装了越来越多的插件的情况下已经成为可能。
如下代码中,具体功能代码前加入一段检查当前访问者的HTTP Referer信息代码,只有Referer信息的主机匹配才继续执行功能代码:
//获取当前URL中的主机名
String url_host =request.getParameter(“host”);
//获取当前访问者的HTTP Refer信息
//获取HTTP Refer信息中的主机名
String http_host =request.getRemoteAddr();
If( ! url_host .equals(http_host))
{
request.setAttribute("MSG", “对不起,您的地址不在访问权限内 !”);
return mapping.findForward("fail");
}
Else
{
……
//实际功能处理代码
……
}
Web 开发人员可以根据自己对应用程序的功能的理解来确定安全级别,从而选择使用不同的保护措施,也推荐在同一应用程序内部结合使用多种方法来进行保护。
避免在URL中明文显示特定操作的参数内容
如果在URL中明文显示特定操作的参数内容,当该Web应用系统存在跨站脚本欺骗漏洞时,能使攻击者很容易地使用该漏洞对Web应用系统进行攻击。例如,银行转账操作的URL http://www.yinhang.com?&account=number&transfer=X时,就会很容易使攻击者利用该漏洞在你不知道的情况下在你的账户中进行转账操作。所以,避免在URL中明文显示特定操作的参数内容,能加大攻击者利用该漏洞的难度。