当前位置: 代码迷 >> SQL >> Spring MVC防守CSRF、XSS和SQL注入攻击[转]
  详细解决方案

Spring MVC防守CSRF、XSS和SQL注入攻击[转]

热度:19   发布时间:2016-05-05 11:49:45.0
Spring MVC防御CSRF、XSS和SQL注入攻击[转]

转;http://www.cnblogs.com/Mainz/archive/2012/11/01/2749874.html

?

本文说一下SpringMVC如何防御CSRF(Cross-site request forgery跨站请求伪造)和XSS(Cross site script跨站脚本攻击)。

说说CSRF

对CSRF来说,其实Spring3.1、ASP.NET MVC3、Rails、Django等 都已经支持自动在涉及POST的地方添加Token(包括FORM表单和AJAX POST等),似乎是一个tag的事情,但如果了解一些实现原理,手工来处理,也是有好处的。因为其实很多人做web开发,但涉及到web安全方面的都是 比较资深的开发人员,很多人安全意识非常薄弱,CSRF是什么根本没有听说过。所以对他们来说,CSRF已经是比较高深的东西了。先说说什么是CSRF?你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。CSRF一般都是利用你的已登录已验证的身份来发送恶意请求。比较著名的一个例子就是2009年黑客利用Gmail的一个CSRF漏 洞成功获取好莱坞明星Vanessa Hudgens的独家艳照。其攻击过程非常简单,给该明星的gmail账户发了一封email,标题是某大导演邀请你来看看这个电影,里面有个图 片:<img src="https://mail.google.com/mail?ui=2&fw=true& [email protected]">,结果她登录Gmail,打开邮件就默默无闻的中招了,所有邮件被转发到黑客的账号。因为当时 Gmail设置转发的设置页面有漏洞,其设置方法是打开一个窗口,点击确定后实际URL是https://mail.google.com /mail?ui=2&fw=true&[email protected]:

?

其实即使不是在同一个页面打开,在不同的tab打开也是一样可以通过网站登录验证的,因为受害者首先已经登录了网站,在浏览网站的过程中,若网站设 置了Session cookie,那么在浏览器进程的生命周期内,即使浏览器同一个窗口打开了新的tab页面,Session cookie也都是有效的,他们在浏览器同一个窗口的多个tab页面里面是共享的(注:现在Gmail支持多个tab同时持有多个SessionID)。 所以攻击步骤是,第一,受害者必须在同一浏览器窗口(即使不是同一tab)内访问并登陆目标站点;第二,这使得Session cookie有效,从而利用受害者的身份进行恶意操作。

再举个实际的例子,假设我们界面上有删除某一项的链接,例如:<a href="javascript:void(0)" onclick="region_del.do?name=0000001">Delete</a>;

其Java Spring MVC后台有个函数是删除某个item,注意是GET不是POST:

复制代码
@RequestMapping(value?=?"region_del.do",?method?=?RequestMethod.GET)
public?String?regionDel(@RequestParam?String?name,?Locale?locale)
{
????//[email protected]...
????????
????return?"redirect:/region.html";
}
复制代码

点击界面上那个<a href="javascript:void(0)" onclick="region_del.do?name=0000001">Delete</a>链接,就后台删除某项,看起来非常正常啊。

好,现在你登录你的网站,然后在另外一个tab打开这个html文件:

复制代码
<!DOCTYPE?html?PUBLIC?"-//W3C//DTD?XHTML?1.0?Transitional//EN"?"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html?xmlns="http://www.w3.org/1999/xhtml"?xml:lang="en"?lang="en">
<head>
????<meta?http-equiv="Content-Type"?content="text/html;?charset=utf-8"?/>
????<title>hack</title>
</head>
<body>
??<img?src="http://localhost/testsite/region_del.do?name=0000001"/>
?</body>
</html>
复制代码

发现同样被删除了某项。试想,如果是网银,你的钱已经被转账......(除了referer不一样,session cookie被利用)

?

好了,现在 后台改成POST(写操作尽量用POST),前台界面那个删除的链接改成Form提交:

<form?action="region_del.do"?method="POST">
?<input?type="hidden"?name="name"?value="0000001">
????????<input?type="submit"?value="Delete"?/>
</form>

看起来安全多了。OK,现在你登录你的网站,然后在另外一个tab打开这个html文件:

复制代码
<!DOCTYPE?html?PUBLIC?"-//W3C//DTD?XHTML?1.0?Transitional//EN"?"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html?xmlns="http://www.w3.org/1999/xhtml"?xml:lang="en"?lang="en">
<head>
????<title>Hack</title>
????<script>
??????
function?steal(){
????????
var?mySubmit?=?document.getElementById('steal_form');
????????mySubmit.submit();
??????}
????
</script>
??</head>
??<body?onload='steal()'>
<form?id?=?"steal_form"?method="POST"?action="http://localhost/testsite/region_del.do">
???<input?type="hidden"?name="func"?value="post">
<input?type="hidden"?name="name"?value="0000001">
</form>
??</body>
</html>
复制代码

?

发现同样被删除了某项。试想,如果是网银,你的钱已经被转账......

当然,你如果前台还是用链接,但改成js,用AJAX POST提交,也是一样的效果:

?

$.ajax({
?type:?"POST",
?url:....
});

解决办法就是在Form表单加一个hidden field,里面是服务端生成的足够随机数的一个Token,使得黑客猜不到也无法仿照Token

先写一个类,生成足够随机数的Token(注:Java的Random UUID已经足够随机了,参考这个和这个)

复制代码
package?com.ibm.cn.web.beans;

import?java.util.UUID;

import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpSession;

/**
*?A?manager?for?the?CSRF?token?for?a?given?session.?The?{
@link?#getTokenForSession(HttpSession)}?should?used?to
*?obtain?the?token?value?for?the?current?session?(and?this?should?be?the?only?way?to?obtain?the?token?value).
*?**
*/

public?final?class?CSRFTokenManager?{

????/**
?????*?The?token?parameter?name
?????
*/
????static?final?String?CSRF_PARAM_NAME?=?"CSRFToken";

????/**
?????*?The?location?on?the?session?which?stores?the?token
?????
*/
????public?static?final??String?CSRF_TOKEN_FOR_SESSION_ATTR_NAME?=?CSRFTokenManager.class
????????????.getName()?+?".tokenval";

????public?static?String?getTokenForSession(HttpSession?session)?{
????????String?token?=?null;
????????
????????//?I?cannot?allow?more?than?one?token?on?a?session?-?in?the?case?of?two
????????
//?requests?trying?to
????????
//?init?the?token?concurrently
????????synchronized?(session)?{
????????????token?=?(String)?session
????????????????????.getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);
????????????if?(null?==?token)?{
????????????????token?=?UUID.randomUUID().toString();
????????????????session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME,?token);
????????????}
????????}
????????return?token;
????}

????/**
?????*?Extracts?the?token?value?from?the?session
?????*?
?????*?
@param?request
?????*?
@return
?????
*/
????public?static?String?getTokenFromRequest(HttpServletRequest?request)?{
????????return?request.getParameter(CSRF_PARAM_NAME);
????}

????private?CSRFTokenManager()?{
????};

}
复制代码

打开Form页面的时候在服务端生成Token并保存到Session中,例如:model.addAttribute("csrf", CSRFTokenManager.getTokenForSession(this.session));

然后在Form中添加Hidden field:?

<input type="hidden" name="CSRFToken" value="${csrf}" />

然后在后台提交的时候验证token :

?

复制代码
@RequestMapping(value?=?"region_del.do",?method?=?RequestMethod.GET)
public?String?regionDel(@RequestParam?String?name,[email protected],?Locale?locale)
????{
????????if(CSRFToken?==?null?||?!CSRFToken.equals(session.getAttribute(CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME).toString())){
????????????????logger.debug("CSRF?attack?detected.?URL:?region_edit.do");
????????????????return?"redirect:/login.form";
????????}?
????????????????
????//[email protected]...
????????
????return?"redirect:/region.html";
}
复制代码

?

你还可以把上面的步骤写到BaseController里面,或者写到拦截器里面,拦截所有POST请求,验证CSRF Token。这里掠过....

如果你用AJAX POST的方法,那么后台一样,前台也要有Hidden field保存Token,然后在提交AJAX POST的时候加上该csrf参数即可。(更多csrf参考这个和这个。)

?

AJAX POST的CSRF防御

?

首先在页面进入的时候从后台生成一个Token(每个session),放到一个Hidden input(用Spring tag或freemarker可以写) 。然后在ajax post提交的时候放到http请求的header里面:

复制代码
????var?headers?=?{};
????headers['__RequestVerificationToken']?=?$("#CSRFToken").val();
????
????$.ajax({
????????type:?"POST",
????????headers:?headers,
????????cache:?false,
????????url:?base?+?"ajax/domain/delete.do",
????????data:?"id=123",
????????dataType:"json",
????????async:?true,
????????error:?function(data,?error)?{},
????????success:?function(data)
????????{
????????????
????????}
????});
复制代码

然后在后台controller里面校验header里面这个token,也可以把这个函数放到baseController里面:

复制代码
protected?boolean?isValidCsrfHeaderToken()?{
????????if?(getRequest().getHeader("__RequestVerificationToken")?==?null
????????????????||?session
????????????????????????.getAttribute(CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME)?==?null
????????????????||?!this.getRequest()
????????????????????????.getHeader("__RequestVerificationToken")
????????????????????????.equals(session
????????????????????????????????.getAttribute(
????????????????????????????????????????CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME)
????????????????????????????????.toString()))?{
????????????return?false;
????????}
????????return?true;
????}
复制代码

?

xss

关于xss的介绍可以看这个和这个网页,具体我就讲讲Spring MVC里面的预防:

web.xml加上:

<context-param>
???<param-name>defaultHtmlEscape</param-name>
???<param-value>true</param-value>
</context-param>

Forms加上:

?

<spring:htmlEscape defaultHtmlEscape="true" />

?

更多信息查看OWASP的页面

?

第二种方法是手动escape,例如用户可以输入:<script>alert()</script> 或者输入<h2>abc<h2>,如果有异常,显然有xss漏洞。

首先添加一个jar包:commons-lang-2.5.jar ,然后在后台调用这些函数:StringEscapeUtils.escapeHtml(string); StringEscapeUtils.escapeJavaScript(string); StringEscapeUtils.escapeSql(string);

前台js调用escape函数即可。

?

第三种方法是后台加Filter,对每个post请求的参数过滤一些关键字,替换成安全的,例如:< > ' " \ /? # &

方法是实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,替换掉getParameter函数即可。

首先添加一个XssHttpServletRequestWrapper:

复制代码
package?com.ibm.web.beans;

import?java.util.Enumeration;

import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletRequestWrapper;

public?class?XssHttpServletRequestWrapper?extends?HttpServletRequestWrapper?{??
????public?XssHttpServletRequestWrapper(HttpServletRequest?servletRequest)?{
????????super(servletRequest);
????}
????public?String[]?getParameterValues(String?parameter)?{
??????String[]?values?=?super.getParameterValues(parameter);
??????if?(values==null)??{
??????????????????return?null;
??????????}
??????int?count?=?values.length;
??????String[]?encodedValues?=?new?String[count];
??????for?(int?i?=?0;?i?<?count;?i++)?{
?????????????????encodedValues[i]?=?cleanXSS(values[i]);
???????}
??????return?encodedValues;
????}
????public?String?getParameter(String?parameter)?{
??????????String?value?=?super.getParameter(parameter);
??????????if?(value?==?null)?{
?????????????????return?null;
??????????????????}
??????????return?cleanXSS(value);
????}
????public?String?getHeader(String?name)?{
????????String?value?=?super.getHeader(name);
????????if?(value?==?null)
????????????return?null;
????????return?cleanXSS(value);
????}
????private?String?cleanXSS(String?value)?{
????????????????//You'll?need?to?remove?the?spaces?from?the?html?entities?below
????????value?=?value.replaceAll("<",?"&?lt;").replaceAll(">",?"&?gt;");
????????value?=?value.replaceAll("\\(",?"&?#40;").replaceAll("\\)",?"&?#41;");
????????value?=?value.replaceAll("'",?"&?#39;");
????????value?=?value.replaceAll("eval\\((.*)\\)",?"");
????????value?=?value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']",?"\"\"");
????????value?=?value.replaceAll("script",?"");
????????return?value;
????}

}?
复制代码

然后添加一个过滤器XssFilter :

复制代码
package?com.ibm.web.beans;

import?java.io.IOException;??

import?javax.servlet.Filter;??
import?javax.servlet.FilterChain;??
import?javax.servlet.FilterConfig;??
import?javax.servlet.ServletException;??
import?javax.servlet.ServletRequest;??
import?javax.servlet.ServletResponse;??
import?javax.servlet.http.HttpServletRequest;??
import?javax.servlet.http.HttpServletResponse;

public?class?XssFilter?implements?Filter?{
????FilterConfig?filterConfig?=?null;

????public?void?init(FilterConfig?filterConfig)?throws?ServletException?{
????????this.filterConfig?=?filterConfig;
????}

????public?void?destroy()?{
????????this.filterConfig?=?null;
????}

????public?void?doFilter(ServletRequest?request,?ServletResponse?response,
????????????FilterChain?chain)?throws?IOException,?ServletException?{
????????chain.doFilter(new?XssHttpServletRequestWrapper(
????????????????(HttpServletRequest)?request),?response);
????}
}
复制代码

最后在web.xml里面配置一下,所有的请求的getParameter会被替换,如果参数里面 含有敏感词会被替换掉:

复制代码
??<filter>
?????<filter-name>XssSqlFilter</filter-name>
?????<filter-class>com.ibm.web.beans.XssFilter</filter-class>
??</filter>
??<filter-mapping>
?????<filter-name>XssSqlFilter</filter-name>
?????<url-pattern>/*</url-pattern>
?????<dispatcher>REQUEST</dispatcher>
??</filter-mapping>
复制代码

?(这个Filter也可以防止SQL注入攻击)?

?

登录页面的攻击例子

假设登录页面有个输入用户名和密码的输入框,可以有很多Xss/csrf/注入钓鱼网站/SQL等的攻击手段,例如:

?

?输入用户名 :??? >"'><script>alert(1779)</script>
?输入用户名:???? usera>"'><img src="javascript:alert(23664)">
?输入用户名:???? "'><IMG SRC="/WF_XSRF.html--end_hig--begin_highlight_tag--hlight_tag--">
?输入用户名:???? usera'"><iframe src=http://demo.testfire.net--en--begin_highlight_tag--d_highlight_tag-->

?

?

Web安全漏洞检测工具

推荐使用IBM Rational AppScan(IBM Rational AppScan下载、版权购买和破解、注册码自己解决)

?

可以录制脚本,设置URL,如果网站需要登录,可以设置自动登录:

?

检测结果还可以保持为专业的pdf检测报告

?

?

业界安全编程标准最佳实践

  • OWASP Guide
  • SANS CWE Top 25
  • CERT Secure Coding
  • The CERT Oracle Secure Coding Standard for Java
  • The CERT Oracle Secure Coding Standard for C++
  • The CERT Oracle Secure Coding Standard for C
  • The CERT Oracle Secure Coding Standard for Perl
  • 2011 CWE/SANS Top 25 Most Dangerous Software Errors
  • Top 10 Secure Coding Practices
?
  相关解决方案