1. 程式人生 > >解決XSS攻擊漏斗的過濾器

解決XSS攻擊漏斗的過濾器

新上線的系統被測試出有XSS攻擊漏洞,做的過程中一直沒有考慮到這個問題。被查出這個問題,通過從網上查閱的資料,將解決方法記錄一下,以備不時之需。

  • 什麼是XSS
XSS攻擊是Web攻擊中最常見的攻擊方法之一,它是通過對網頁注入可執行程式碼且成功地被瀏覽器
執行,達到攻擊的目的,形成了一次有效XSS攻擊,一旦攻擊成功,它可以獲取使用者的聯絡人列
表,然後向聯絡人傳送虛假詐騙資訊,可以刪除使用者的日誌等等,有時候還和其他攻擊方式同時實
施比如SQL注入攻擊伺服器和資料庫、Click劫持、相對連結劫持等實施釣魚,它帶來的危害是巨
大的,是web安全的頭號大敵。
  • 攻擊的條件
實施XSS攻擊需要具備兩個條件:

一、需要向web頁面注入惡意程式碼;

二、這些惡意程式碼能夠被瀏覽器成功的執行。

  • XSS攻擊能做些什麼

竊取cookies,讀取目標網站的cookie傳送到黑客的伺服器上,如下面的程式碼;

讀取使用者未公開的資料,如果:郵件列表或者內容、系統的客戶資料,聯絡人列表等等

  • XSS攻擊的過程

  • 怎麼解決

我的解決辦法是加過濾器轉義特殊字元。

首先寫好過濾器,具體程式碼如下:

XssSecurityFilter.java

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 org.apache.log4j.Logger;

public class XssSecurityFilter implements Filter {

	protected final Logger log = Logger.getLogger(this.getClass());

	public void init(FilterConfig config) throws ServletException {
		if (log.isInfoEnabled()) {
			log.info("XSSSecurityFilter Initializing ");
		}
	}

	/**
	 * 銷燬操作
	 */
	public void destroy() {
		if (log.isInfoEnabled()) {
			log.info("XSSSecurityFilter destroy() end");
		}
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest) request;

		XssHttpRequestWrapper xssRequest = new XssHttpRequestWrapper(
				httpRequest);
		httpRequest = XssSecurityManager.wrapRequest(xssRequest);
		chain.doFilter(xssRequest, response);
	}
}
XssHttpRequestWrapper.java
import java.io.UnsupportedEncodingException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
 * @author
 * @date
 * @describe 主要是對引數進行xss過濾,替換原始的request的getParameter相關功能 執行緒不安全
 * 
 */
public class XssHttpRequestWrapper extends HttpServletRequestWrapper {

	protected Map parameters;

	/**
	 * 封裝http請求
	 * 
	 * @param request
	 */
	public XssHttpRequestWrapper(HttpServletRequest request) {
		super(request);
	}

	@Override
	public void setCharacterEncoding(String enc)
			throws UnsupportedEncodingException {
		super.setCharacterEncoding(enc);
		// 當編碼重新設定時,重新載入重新過濾快取。
		refiltParams();
	}

	void refiltParams() {
		parameters = null;
	}

	@Override
	public String getParameter(String string) {
		String strList[] = getParameterValues(string);
		if (strList != null && strList.length > 0)
			return strList[0];
		else
			return null;
	}

	@Override
	public String[] getParameterValues(String string) {
		if (parameters == null) {
			paramXssFilter();
		}
		return (String[]) parameters.get(string);
	}

	@Override
	public Map getParameterMap() {
		if (parameters == null) {
			paramXssFilter();
		}
		return parameters;
	}

	/**
	 * 
	 * 校驗引數,若含xss漏洞的字元,進行替換
	 */
	private void paramXssFilter() {
		parameters = getRequest().getParameterMap();
		XssSecurityManager.filtRequestParams(parameters, this.getServletPath());
	}

}
XssSecurityManager.java
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/** 
 * @author  
 * @date 
 * @describe 安全過濾配置管理類,統一替換可能造成XSS漏洞的字元為中文字元 
 */  
public class XssSecurityManager {
	private static Logger log = Logger.getLogger(XssSecurityManager.class);  
	  
    // 危險的javascript:關鍵字j av a script  
    private final static Pattern[] DANGEROUS_TOKENS = new Pattern[] { Pattern  
            .compile("^j\\s*a\\s*v\\s*a\\s*s\\s*c\\s*r\\s*i\\s*p\\s*t\\s*:",  
                    Pattern.CASE_INSENSITIVE) };  
  
    // javascript:替換字串(全形中文字元)  
    private final static String[] DANGEROUS_TOKEN_REPLACEMENTS = new String[] { "JAVASCRIPT:" };  
  
    // 非法的字符集  
    private static final char[] INVALID_CHARS = new char[] { '<', '>', '\'',  
            '\"', '\\' };  
  
    // 統一替換可能造成XSS漏洞的字元為全形中文字元  
    private static final char[] VALID_CHARS = new char[] { '<', '>', '’', '“',  
            '\' };  
  
    // 開啟xss過濾功能開關  
    public static boolean enable=false;  
  
    // url-patternMap(符合條件的url-param進行xss過濾)<String ArrayList>  
    public static Map urlPatternMap = new HashMap();  
      
    private static HashSet excludeUris=new HashSet();  
  
    private XssSecurityManager() {  
        // 不可被例項化  
    }  
    static {  
        init();  
    }  
  
    private static void init() {  
        try {  
            String xssConfig = "/xss_security_config.xml";  
            if(log.isDebugEnabled()){  
                log.debug("XSS config file["+xssConfig+"] init...");  
            }  
            InputStream is = XssSecurityManager.class  
                    .getResourceAsStream(xssConfig);  
            if (is == null) {  
                log.warn("XSS config file["+xssConfig+"] not found.");  
            }else{  
                // 初始化過濾配置檔案  
                initConfig(is);  
                log.info("XSS config file["+xssConfig+"] init completed.");  
            }  
        }  
        catch (Exception e) {  
              
            log.error("XSS config file init fail:"+e.getMessage(), e);  
        }  
          
    }  
  
    private static void initConfig(InputStream is) throws Exception{  
        DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();  
  
        DocumentBuilder builder=factory.newDocumentBuilder();  
  
        Element root = builder.parse(is).getDocumentElement();  
        //-------------------  
        NodeList nl=root.getElementsByTagName("enable");  
        Node n=null;  
        if(nl!=null && nl.getLength()>0){  
            n=((org.w3c.dom.Element)nl.item(0)).getFirstChild();  
        }  
        if(n!=null){  
            enable = new Boolean(n.getNodeValue().trim()).booleanValue();  
        }  
        log.info("XSS switch="+enable);  
        //-------------------------  
        nl=root.getElementsByTagName("filter-mapping");  
        NodeList urlPatternNodes=null;  
        if(nl!=null && nl.getLength()>0){  
            Element  el=(Element)nl.item(0);  
            urlPatternNodes=el.getElementsByTagName("url-pattern");  
            //-----------------------------------------------------  
            NodeList nl2=el.getElementsByTagName("exclude-url");  
            if(nl2!=null && nl2.getLength()>0){  
                for(int i=0;i<nl2.getLength();i++){  
                    Element e=(Element)urlPatternNodes.item(i);  
                    Node paramNode=e.getFirstChild();  
                    if(paramNode!=null){  
                        String paramName=paramNode.getNodeValue().trim();  
                        if(paramName.length()>0){  
                            excludeUris.add(paramName.toLowerCase());  
                        }  
                    }  
                }  
            }  
        }  
        //----------------------  
        if(urlPatternNodes!=null && urlPatternNodes.getLength()>0){  
            for(int i=0;i<urlPatternNodes.getLength();i++){  
                Element e=(Element)urlPatternNodes.item(i);  
                String urlPattern=e.getAttribute("value");  
                if(urlPattern!=null && (urlPattern=urlPattern.trim()).length()>0){  
                    List filtParamList = new ArrayList(2);  
                    if(log.isDebugEnabled()){  
                        log.debug("Xss filter mapping:"+urlPattern);  
                    }  
                    //-------------------------------  
                    NodeList temp=e.getElementsByTagName("include-param");  
                    if(temp!=null && temp.getLength()>0){  
                        for(int m=0;m<temp.getLength();m++){  
                            Node paramNode=(temp.item(m)).getFirstChild();  
                            if(paramNode!=null){  
                                String paramName=paramNode.getNodeValue().trim();  
                                if(paramName.length()>0){  
                                    filtParamList.add(paramName);  
                                }  
                            }  
                              
                        }  
                          
                    }  
                    //一定得將url轉換為小寫  
                    urlPatternMap.put(urlPattern.toLowerCase(), filtParamList);  
                }  
            }  
        }  
          
        //----------------------  
    }  
      
    
public static HttpServletRequest wrapRequest(HttpServletRequest httpRequest){  
        if(httpRequest instanceof XssHttpRequestWrapper){  
//          log.info("httpRequest instanceof XssHttpRequestWrapper");  
            //include/forword指令會重新進入此Filter  
            XssHttpRequestWrapper temp=(XssHttpRequestWrapper)httpRequest;  
            //include指令會增加引數,這裡需要清理掉快取  
            temp.refiltParams();  
            return temp;  
        }else{  
//          log.info("httpRequest is not instanceof XssHttpRequestWrapper");  
            return httpRequest;  
        }  
    }  

public static List getFiltParamNames(String url){ //獲取需要xss過濾的引數 url = url.toLowerCase(); List paramNameList = (ArrayList) urlPatternMap.get(url); if(paramNameList==null || paramNameList.size()==0){ return null; } return paramNameList; } public static void filtRequestParams(Map params,String servletPath){ long t1=System.currentTimeMillis(); //得到需要過濾的引數名列表,如果列表是空的,則表示過濾所有引數 List filtParamNames=XssSecurityManager.getFiltParamNames(servletPath); filtRequestParams(params, filtParamNames); } public static void filtRequestParams(Map params,List filtParamNames){ // 獲取當前引數集 Set parameterNames = params.keySet(); Iterator it = parameterNames.iterator(); //得到需要過濾的引數名列表,如果列表是空的,則表示過濾所有引數 while (it.hasNext()) { String paramName = (String) it.next(); if(filtParamNames==null || filtParamNames.contains(paramName) ){ String[] values = (String[])params.get(paramName); proceedXss(values); } } } /** * 對引數進行防止xss漏洞處理 * * @param value * @return */ private static void proceedXss(String[] values) { for (int i = 0; i < values.length; ++i) { String value = values[i]; if (!isNullStr(value)) { values[i] = replaceSpecialChars(values[i]); } } } /** * 替換非法字元以及危險關鍵字 * * @param str * @return */ private static String replaceSpecialChars(String str) { for (int j = 0; j < INVALID_CHARS.length; ++j) { if (str.indexOf(INVALID_CHARS[j]) >= 0) { str = str.replace(INVALID_CHARS[j], VALID_CHARS[j]); } } str=str.trim(); for (int i = 0; i < DANGEROUS_TOKENS.length; ++i) { str = DANGEROUS_TOKENS[i].matcher(str).replaceAll( DANGEROUS_TOKEN_REPLACEMENTS[i]); } return str; } /** * 判斷是否為空串,建議放到某個工具類中 * * @param value * @return */ private static boolean isNullStr(String value) { return value == null || value.trim().length()==0; } public static void main(String args[]) throws Exception{ Map datas=new HashMap(); String paramName="test"; datas.put(paramName,new String[]{ "Javascript:<script>alert('yes');</script>"}); filtRequestParams(datas,"/test/sample.do"); System.out.println(((String[])datas.get(paramName))[0]); } } 然後配置Web.xml檔案:
<filter>  
       <filter-name>XSSFiler</filter-name>  
       <filter-class>com.fh.filter.XssSecurityFilter</filter-class>  
    </filter>  
    <filter-mapping>  
        <filter-name>XSSFiler</filter-name>  
        <url-pattern>*.jsp</url-pattern>  
    </filter-mapping>  
    <filter-mapping>  
        <filter-name>XSSFiler</filter-name>  
        <url-pattern>*.do</url-pattern>  
    </filter-mapping>  
    <filter-mapping>  
        <filter-name>XSSFiler</filter-name>  
        <url-pattern>*.screen</url-pattern>  
    </filter-mapping>  
    <filter-mapping>  
        <filter-name>XSSFiler</filter-name>  
        <url-pattern>*.shtml</url-pattern>  
    </filter-mapping>  
    <filter-mapping>  
        <filter-name>XSSFiler</filter-name>  
        <servlet-name>dispatcher</servlet-name>  
    </filter-mapping>
  • 解決前輸入的非法字串展示如下(輸入的企業名稱是[<script>alert('1111');</script>]):

  • 加入過濾器後,輸入的非法字元展示如:

  • XSS過濾加入後引發的問題如下:

由於系統中附件的上傳下載封裝了共同頁面,使用了<jsp:include>和<jsp:param>標籤,XSS過濾加入之後,<jsp:param>的引數就傳遞不到共通頁面。

根據分析查詢,可能是過濾器中一下程式碼影響:

public static HttpServletRequest wrapRequest(HttpServletRequest httpRequest){  
        if(httpRequest instanceof XssHttpRequestWrapper){  
//          log.info("httpRequest instanceof XssHttpRequestWrapper");  
            //include/forword指令會重新進入此Filter  
            XssHttpRequestWrapper temp=(XssHttpRequestWrapper)httpRequest;  
            //include指令會增加引數,這裡需要清理掉快取  
            temp.refiltParams();  
            return temp;  
        }else{  
//          log.info("httpRequest is not instanceof XssHttpRequestWrapper");  
            return httpRequest;  
        }  
    }  
httpRequest = XssSecurityManager.wrapRequest(xssRequest);
但是將上述程式碼去掉後,依然沒有解決問題,目前還在百思不得其解中,如果有哪位高手經過,可以指點一下。。。。。。