­

java 攔截器解決xss攻擊

一、xss攻擊

XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令程式碼到網頁,使用戶載入並執行攻擊者惡意製造的網頁程式。這些惡意網頁程式通常是JavaScript,但實際上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻擊成功後,攻擊者可能得到包括但不限於更高的許可權(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。

簡單說就是說,通過在輸入框輸入一些js程式碼,如在帳號密碼輸入框中輸入

<video src=1 onerror=alert(/xss/)/>

或者

 <script>alert("@@") </script>

這樣點擊提交的時候就會觸發alert彈窗,分別彈出 xss  和 @@ 的內容,這裡只是做個簡單的演示,彈了個窗口,還能存儲病毒下載地址到服務端,進入的時候自動下載,或者修改你的cookie啥的,這裡感興趣可以百度查查xss攻擊。

二、如何防禦

解決思路對用戶提交表單的參數進行轉移,如把< 轉換為 &lt;  把 > 轉換為 &rt;

java有很多的過濾工具類

<dependency>
      <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
        <version>2.6</version>
</dependency>

然後通過下面的程式碼即可過濾

StringEscapeUtils.escapeHtml(string); 

底層也是將一切標籤進行轉移,達到js調用不生效的作用。

 

這裡使用的是filter過請求進行攔截處理。

過濾的內容報過get請求的參數、對象, post形式body中的參數

1)添加xss過濾器

        <!-- xss過濾器 -->
    <filter>
        <filter-name>XssgFilter</filter-name>
        <filter-class>com.train.web.filter.XssFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>XssgFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>    

這裡過濾了所有的請求,其中XssFilter是我們自己過濾器

2)添加自己的過濾器,

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class XssFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        XssHttpServletRequestWrapper req=new XssHttpServletRequestWrapper((HttpServletRequest)servletRequest);

        filterChain.doFilter(req,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

3)定義自己的http包裝類

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    boolean isUpData = false;//判斷是否是上傳 上傳忽略
    public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
        super(servletRequest);
        String contentType = servletRequest.getContentType ();
        if (null != contentType)
            isUpData =contentType.startsWith ("multipart");
    }

    @Override
    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;
    }
    
    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    /**
     * 獲取request的屬性時,做xss過濾
     */
    @Override
    public Object getAttribute(String name) {
        Object value = super.getAttribute(name);
        if (null != value && value instanceof String) {
            value = cleanXSS((String) value);
        }
        return value;
    }

    @Override
    public String getHeader(String name) {

        String value = super.getHeader(name);
        if (value == null)
            return null;
        return cleanXSS(value);
    }
    private  static  String cleanXSS(String value) {
        value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        value = value.replaceAll("%3C", "&lt;").replaceAll("%3E", "&gt;");
        value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
        value = value.replaceAll("%28", "&#40;").replaceAll("%29", "&#41;");
        value = value.replaceAll("'", "&#39;");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");
        return value;
    }


    @Override
    public ServletInputStream getInputStream () throws IOException {
        if (isUpData){
            return super.getInputStream ();
        }else{

            final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());

            return new ServletInputStream() {

                @Override
                public int read() throws IOException {
                    return bais.read();
                }

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) { }
            };
        }

    }
    public   String inputHandlers(ServletInputStream servletInputStream){
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader (servletInputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (servletInputStream != null) {
                try {
                    servletInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return  cleanXSS(sb.toString ());
    }
}

但是這裡有個問題,假如這裡還有一些特殊的需求,有些html標籤是希望在前端能顯示的,前端通過一些已經防止了xss攻擊的富文本控制項輸入資訊,後台不希望將這些資訊轉義

三、添加一些額外的內容

1)希望能動態的開關

2)期待部分介面的介面的參數是能存在標籤的

添加一個xss開關的控制類, 這裡使用了配置中心

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class XSSFilterConfigUtil {

    public static Boolean openXssProtect;

    public static Boolean getOpenXssProtect() {
        return openXssProtect == null ? false : openXssProtect;
    }

    @Value("${open.xss.protect}")
    public void setOpenXssProtect(Boolean openXssProtect) {
        XSSFilterConfigUtil.openXssProtect = openXssProtect;
    }

}

注意的是:

  1. @Value無法為靜態屬性注入值,所以需要添加set方法為其注入值;

  2. 工具類必須添加@Component或者@Service註解,否則@Value不起作用。

靜態方法中注入了值以後,Filter中就可以直接使用了。

 

修改上面的http包裝類,這裡不對” 進行過濾,過濾的話,會把json的””個去除,使用@RequestBody沒辦法解析成為一個正常的對象

import com.alibaba.fastjson.JSONObject;
import com.train.service.impl.XSSFilterConfigUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 防護http處理
 * 1.過濾xss
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private static final Logger LOGGER = LoggerFactory.getLogger(XssHttpServletRequestWrapper.class);

    boolean isUpData = false;//判斷是否是上傳 上傳忽略

    //不期待被過濾的的鏈接和欄位(管理後台使用了富文本,希望有可編輯的內容)
    HashMap<String, String> doNotFilterURLAndParamMap = new HashMap<String, String>() {
        {
            put("/api/v2/group/manage", "description");
            put("/api/v1/sendNews", "content");

        }
    };



    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        String contentType = request.getContentType ();
        if (null != contentType)
            isUpData =contentType.startsWith ("multipart");
    }

    /**
     * 過濾單個參數
     * @param name
     * @return
     */
    @Override
    public String getParameter(String name) {
        String parameter = super.getParameter(name);
        if(StringUtils.isNotBlank(parameter) && XSSFilterConfigUtil.getOpenXssProtect()){
                //這裡使用的阿帕奇的common-lang3中的轉義html方法,也可以自己實現,
            String escapeParameter = this.cleanXSS(parameter);
            return escapeParameter;
        }
        return parameter;
    }

    /**
     * 過濾實體的每個參數
     * @param name
     * @return
     */
    @Override
    public String[] getParameterValues(String name) {

        String[] parameterValues = super.getParameterValues(name);
        if (parameterValues == null) {
            return null;
        }
        if (XSSFilterConfigUtil.getOpenXssProtect()) {
            for (int i = 0; i < parameterValues.length; ++i) {
                String value = parameterValues[i];
                parameterValues[i] = this.cleanXSS(value);
            }
        }

        return parameterValues;

    }

    /**
     * 處理@RequestBody的形式傳入的json數據
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream () throws IOException {
        if(!XSSFilterConfigUtil.getOpenXssProtect()) {
            return super.getInputStream ();
        }

        if (isUpData){
            return super.getInputStream ();
        }else{

            final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());

            return new ServletInputStream() {

                @Override
                public int read() throws IOException {
                    return bais.read();
                }
            };
        }

    }


    public String inputHandlers(ServletInputStream servletInputStream){
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (servletInputStream != null) {
                try {
                    servletInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String requestUrl = StringUtils.replaceOnce(this.getRequestURI(), this.getContextPath(), StringUtils.EMPTY);
        boolean needFilter = false;
        String key = "";
        String param = "";
        for(Map.Entry<String, String> entry : doNotFilterURLAndParamMap.entrySet()){

            key = entry.getKey();

            int index = StringUtils.indexOf(key, "*");
            if (index > 0) {
                String[] array = key.split("\\*");
                StringBuffer stringBuffer = new StringBuffer();
                for (String s : array) {
                    stringBuffer.append(s).append("(.*)");
                }
                Pattern p = Pattern.compile(stringBuffer.toString());
                Matcher m = p.matcher(requestUrl);
                if (m.find()) {
                    needFilter = true;
                    param = entry.getValue();
                    break;
                }
            } else {
                if (requestUrl.equals(key)) {
                    needFilter = true;
                    param = entry.getValue();
                    break;
                }
            }
        }


        if(needFilter) {   //有需要特殊處理的欄位,不希望過濾標籤
            try {
                /*String param = doNotFilterURLAndParamMap.get(requestUrl);*/
                JSONObject jsonObject = JSONObject.parseObject(sb.toString());
                if(jsonObject.containsKey(param)) {
                    Object notFilterValue = jsonObject.get(param);
                    String cleanXSSParams = cleanXSS(sb.toString ());
                    JSONObject filteredJson = JSONObject.parseObject(cleanXSSParams);
                    filteredJson.put(param, notFilterValue);
                    return filteredJson.toJSONString();
                }else {
                    return cleanXSS(sb.toString ());
                }

            }catch (Exception e) {
                LOGGER.error("XssHttpServletRequestWrapper轉換json數據失敗",e);
                return cleanXSS(sb.toString ());  //異常時,就直接過濾,不管需要特殊處理的參數
            }


        }else {
            return cleanXSS(sb.toString ());
        }
    }

    /**
     * 過濾規則,這裡不直接使用StringEscapeUtils.escapeHtml,因為獲取的是一個json字元串,會將" 替換導致數據異常,沒有""進行分割,無法正常注入到@RequestBody
     * @param value
     * @return
     */
    private static String cleanXSS(String value) {
        value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        value = value.replaceAll("%3C", "&lt;").replaceAll("%3E", "&gt;");
//        value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
        value = value.replaceAll("%28", "&#40;").replaceAll("%29", "&#41;");
//        value = value.replaceAll("'", "&#39;");
       /* value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");*/
        return value;
    }


}

 感謝你的閱讀,接外包、也找兼職