Spring MVC防護CSRF、XSS和SQL注入攻擊

解決CSRF的辦法:客戶端向服務器提交請求時,服務器必定要校驗口令。
客戶端指定頁面要有服務器端提供的口令

javascript

本文說一下SpringMVC如何防護CSRF(Cross-site request forgery跨站請求僞造)和XSS(Cross site script跨站腳本攻擊)。php

說說CSRF

CSRF來講,其實Spring3.1ASP.NET MVC3RailsDjango等都已經支持自動在涉及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:html

 

其實即便不是在同一個頁面打開,在不一樣的tab打開也是同樣能夠經過網站登陸驗證的,由於受害者首先已經登陸了網站,在瀏覽網站的過程當中,若網站設置了Session cookie,那麼在瀏覽器進程的生命週期內,即便瀏覽器同一個窗口打開了新的tab頁面,Session cookie也都是有效的,他們在瀏覽器同一個窗口的多個tab頁面裏面是共享的(注:如今Gmail支持多個tab同時持有多個SessionID)。因此攻擊步驟是,第一,受害者必須在同一瀏覽器窗口(即便不是同一tab)內訪問並登錄目標站點;第二,這使得Session cookie有效,從而利用受害者的身份進行惡意操做。 java

再舉個實際的例子,假設咱們界面上有刪除某一項的連接,例如:<a href="javascript:void(0)" onclick="region_del.do?name=0000001">Delete</a>;web

其Java Spring MVC後臺有個函數是刪除某個item,注意是GET不是POST:ajax

複製代碼
@RequestMapping(value = "region_del.do", method = RequestMethod.GET)
public String regionDel(@RequestParam String name, Locale locale)
{
    //Delete region name=@name....
        
    return "redirect:/region.html";
}
複製代碼

點擊界面上那個<a href="javascript:void(0)" onclick="region_del.do?name=0000001">Delete</a>連接,就後臺刪除某項,看起來很是正常啊。 spring

好,如今你登陸你的網站,而後在另一個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被利用)json

 

好了,如今 後臺改爲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, @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裏面:

複製代碼
    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

跨站腳本(Cross site script,簡稱xss)是一種「HTML注入」,因爲攻擊的腳本多數時候是跨域的,因此稱之爲「跨域腳本」。

咱們經常聽到「注入」(Injection),如SQL注入,那麼到底「注入」是什麼?注入本質上就是把輸入的數據變成可執行的程序語句。SQL注入是如此,XSS也如此,只不過XSS通常注入的是惡意的腳本代碼,這些腳本代碼能夠用來獲取合法用戶的數據,如Cookie信息。



關於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檢測報告 

 

 

業界安全編程標準最佳實踐

 

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

相關文章
相關標籤/搜索