天天看點

網站安全 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& [email protected]">,結果她登入Gmail,打開郵件就默默無聞的中招了,所有郵件被轉發到黑客的賬号。因為當時 Gmail設定轉發的設定頁面有漏洞,其設定方法是打開一個視窗,點選确定後實際URL是https://mail.google.com /mail?ui=2&fw=true&[email protected]:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

其實即使不是在同一個頁面打開,在不同的tab打開也是一樣可以通過網站登入驗證的,因為受害者首先已經登入了網站,在浏覽網站的過程中,若網站設 置了Session cookie,那麼在浏覽器程序的生命周期内,即使浏覽器同一個視窗打開了新的tab頁面,Session cookie也都是有效的,他們在浏覽器同一個視窗的多個tab頁面裡面是共享的(注:現在Gmail支援多個tab同時持有多個SessionID)。 是以攻擊步驟是,第一,受害者必須在同一浏覽器視窗(即使不是同一tab)内通路并登陸目标站點;第二,這使得Session cookie有效,進而利用受害者的身份進行惡意操作。 

再舉個實際的例子,假設我們界面上有删除某一項的連結,例如:<a href="javascript:void(0)" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" οnclick="region_del.do?name=0000001">Delete</a>;

其Java Spring MVC背景有個函數是删除某個item,注意是GET不是POST:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

@RequestMapping(value = "region_del.do", method = RequestMethod.GET)

public String regionDel(@RequestParam String name, Locale locale)

{

     // Delete region [email protected]....

     return "redirect:/region.html";

}

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

點選界面上那個<a href="javascript:void(0)" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" οnclick="region_del.do?name=0000001">Delete</a>連結,就背景删除某項,看起來非常正常啊。 

好,現在你登入你的網站,然後在另外一個tab打開這個html檔案:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

<! 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 >

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

發現同樣被删除了某項。試想,如果是網銀,你的錢已經被轉賬......(除了referer不一樣,session cookie被利用)

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊
網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

好了,現在 背景改成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檔案:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

<! 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 >

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

發現同樣被删除了某項。試想,如果是網銀,你的錢已經被轉賬...... 當然,你如果前台還是用連結,但改成js,用AJAX POST送出,也是一樣的效果:

$.ajax({

 type: "POST",

 url:....

});

解決辦法就是在Form表單加一個hidden field,裡面是服務端生成的足夠随機數的一個Token,使得黑客猜不到也無法仿照Token。 

先寫一個類,生成足夠随機數的Token(注:Java的Random UUID已經足夠随機了,參考這個和這個) 

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

package com.ibm.cn.web.beans;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

public  final  class CSRFTokenManager {

     static  final String CSRF_PARAM_NAME = "CSRFToken";

     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;

    }

     public  static String getTokenFromRequest(HttpServletRequest request) {

         return request.getParameter(CSRF_PARAM_NAME);

    }

     private CSRFTokenManager() {

    };

}

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

打開Form頁面的時候在服務端生成Token并儲存到Session中,例如:model.addAttribute("csrf", CSRFTokenManager.getTokenForSession(this.session));

然後在Form中添加Hidden field: 

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

然後在背景送出的時候驗證token :

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

@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 [email protected]....

     return "redirect:/region.html";

}

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

你還可以把上面的步驟寫到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裡面:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

     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)

        {

        }

    });

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

然後在背景controller裡面校驗header裡面這個token,也可以把這個函數放到baseController裡面:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

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;

    }

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

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:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

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;

    }

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

然後添加一個過濾器XssFilter :

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

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);

    }

}

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

最後在web.xml裡面配置一下,所有的請求的getParameter會被替換,如果參數裡面 含有敏感詞會被替換掉:

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

   < 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 >

網站安全 Spring MVC防禦CSRF、XSS和SQL注入攻擊

 (這個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-->

繼續閱讀