轉:http://blog.csdn.net/zpf0918/article/details/43952511
Spring MVC防御CSRF、XSS和SQL注入攻擊
本文說一下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&fwe=hacker@email.com">,結果她登錄Gmail,打開郵件就默默無聞的中招了,所有郵件被轉發到黑客的賬號。因為當時Gmail設置轉發的設置頁面有漏洞,其設置方法是打開一個窗口,點擊確定后實際URL是https://mail.google.com/mail?ui=2&fw=true&fwe=newMail@email.com:
?
其實即使不是在同一個頁面打開,在不同的tab打開也是一樣可以通過網站登錄驗證的,因為受害者首先已經登錄了網站,在瀏覽網站的過程中,若網站設置了Session cookie,那么在瀏覽器進程的生命周期內,即使瀏覽器同一個窗口打開了新的tab頁面,Session cookie也都是有效的,他們在瀏覽器同一個窗口的多個tab頁面里面是共享的(注:現在Gmail支持多個tab同時持有多個SessionID)。所以攻擊步驟是,第一,受害者必須在同一瀏覽器窗口(即使不是同一tab)內訪問并登陸目標站點;第二,這使得Session cookie有效,從而利用受害者的身份進行惡意操作。
再舉個實際的例子,假設我們界面上有刪除某一項的鏈接,例如:<a href="javascript:void(0)" οnclick="region_del.do?name=0000001">Delete</a>;
其Java Spring MVC后臺有個函數是刪除某個item,注意是GET不是POST:
public?String?regionDel(@RequestParam?String?name,?Locale?locale)
{
????//Delete?region?name=@name....
????????
????return?"redirect:/region.html";
}
點擊界面上那個<a href="javascript:void(0)" οnclick="region_del.do?name=0000001">Delete</a>鏈接,就后臺刪除某項,看起來非常正常啊。
好,現在你登錄你的網站,然后在另外一個tab打開這個html文件:
<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提交:
?<input?type="hidden"?name="name"?value="0000001">
????????<input?type="submit"?value="Delete"?/>
</form>
看起來安全多了。OK,現在你登錄你的網站,然后在另外一個tab打開這個html文件:
<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?οnlοad='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>
?
?
?type:?"POST",
?url:....
});
解決辦法就是在Form表單加一個hidden field,里面是服務端生成的足夠隨機數的一個Token,使得黑客猜不到也無法仿照Token。
先寫一個類,生成足夠隨機數的Token(注:Java的Random UUID已經足夠隨機了,參考這個和這個)
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:?
然后在后臺提交的時候驗證token :
?
public?String?regionDel(@RequestParam?String?name,?@RequestParam?String?CSRFToken,?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";
????????}?
????????????????
????//Delete?region?name=@name....
????????
????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里面:
????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里面:
????????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加上:
???<param-name>defaultHtmlEscape</param-name>
???<param-value>true</param-value>
</context-param>
Forms加上:
?
?
更多信息查看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:
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 :
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-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等的攻擊手段,例如:
?
?輸入用戶名:???? 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-->