XSS過濾器

今天天氣晴朗出刷,氣候炎熱,是時候來一點java代碼來降降溫了坯辩。

在做web開發(fā)的時候馁龟,可能會遇到無聊的人,或者對你的網(wǎng)站圖謀不軌的人漆魔,當(dāng)然這個是假如坷檩。俗話說:害人之心不可有,防人之心不可無改抡。那么矢炼,我們就來做一個XSS過濾器,來抵擋一部分惡意的訪問吧阿纤。

講一下原理:把request中的請求參數(shù)放入我們自己寫的包裝request類中句灌,然后寫一個過濾器對這個包裝request中的參數(shù)進行過濾。

為什么需要對Request進行一次包裝呢欠拾?直接用不是更好嗎胰锌。其實這里面主要是針對post帶請求體這種的,如果要拿請求體中的參數(shù)藐窄,就要從request獲取去參數(shù)资昧,也就是從流中獲取參數(shù)。但是一旦從流中獲取到參數(shù)枷邪,在后面流也就關(guān)閉了榛搔,無法再次獲取,再次獲取就會報錯东揣。就好比男生在飛機完就會進入賢者模式践惑,此刻你再來一個刺激,那就事情不妙了嘶卧。在代碼中也是一樣的尔觉,也就是我們的Controler無法獲取到參數(shù)。所以芥吟,才有了包裝request的事侦铜。

好,開啟代碼之旅吧钟鸵。

首先钉稍,就是定義一些常量,后面的代碼會用得著:

package cn.wjp.mydaily.common.filter;

public class HttpConst {
    /**
     * 幾種常見的Content-Type
     */
    public static final String FORM_URLENCODED_CONTENT_TYPE ="application/x-www-form-urlencoded";

    public static final String JSON_CONTENT_TYPE = "application/json";

    public static final String MULTIPART_CONTENT_TYPE = "multipart/form-data";

    /**
     * 常見的post/get請求方式
     */
    public static final String POST_METHOD = "post";

    public static final String GET_METHOD = "get";

    public static final String OPTIONS_METHOD = "options";

}

接下來需要分別針對不同的表單提交類型寫包裝類棺耍,對于普通的get/post請求的包裝類:

package cn.wjp.mydaily.common.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * 適用于普通get post請求  不包含multipart/form-data   application/json等請求
 * 緩存請求參數(shù)   重寫獲取參數(shù)的方法
 */
public class HttpServletRequestNormalWrapper extends HttpServletRequestWrapper {

    private Map<String, String[]> parameterMap = new HashMap<>(); // 所有參數(shù)的Map集合

    public HttpServletRequestNormalWrapper(HttpServletRequest request){
        super(request);
        Enumeration params = request.getParameterNames();//獲得所有請求參數(shù)名
        StringBuffer paramsValue = new StringBuffer("");
        while (params.hasMoreElements()) {
            String name = params.nextElement().toString(); //得到參數(shù)名
            String[] value = request.getParameterValues(name);//得到參數(shù)對應(yīng)值
            parameterMap.put(name,value);
        }
    }

    /**
     * 獲取所有參數(shù)名
     *
     * @return 返回所有參數(shù)名
     */
    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<String>(parameterMap.keySet());
        return vector.elements();
    }

    /**
     * 獲取指定參數(shù)名的值贡未,如果有重復(fù)的參數(shù)名,則返回第一個的值 接收一般變量 ,如text類型
     *
     * @param name 指定參數(shù)名
     * @return 指定參數(shù)名的值
     */
    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        if(values==null||values.length==0){
            return null;
        }
        return values[0];
        //如果有多個參數(shù)值的俊卤,請放開該注釋  我這里沒有做這么細致
        /*StringBuffer sb = new StringBuffer();
        for(int i=0;i<values.length;i++){
            if(i==values.length-1){
                sb.append(values[i]);
            }else{
                sb.append(values[i]).append(",");
            }
        }
        return sb.toString();*/
    }
    /**
     * 獲取指定參數(shù)名的所有值的數(shù)組嫩挤,如:checkbox的所有數(shù)據(jù)
     * 接收數(shù)組變量 ,如checkobx類型
     */
    @Override
    public String[] getParameterValues(String name) {
        return parameterMap.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }

    public void setParameterMap(Map<String, String[]> parameterMap) {
        this.parameterMap = parameterMap;
    }
}
對于post+application/json的請求消恍,對應(yīng)的包裝類:

package cn.wjp.mydaily.common.filter;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * 將body中的數(shù)據(jù)緩存起來  重寫getInputStream  getReader 等方法  適用于application/json的post請求
 */
public class HttpServletRequestBodyReaderWrapper extends HttpServletRequestWrapper{

    private String body ="{}";//緩存請求體的內(nèi)容

    public HttpServletRequestBodyReaderWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder stringBuilder = new StringBuilder("");
        BufferedReader bufferedReader = null;
        try {
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[1024];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("utf-8"));
        ServletInputStream servletInputStream = new ServletInputStream() {
            public boolean isFinished() {
                return false;
            }
            public boolean isReady() {
                return true;
            }
            public void setReadListener(ReadListener readListener) {}
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
    public String getBody() {
        return this.body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

接下來就是XSS校驗啦:

package cn.wjp.mydaily.common.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class XssFilter implements Filter {

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

        String contentType = request.getContentType();//獲取contentType請求頭
        String method = request.getMethod();//獲取請求方法  post/get
        //1 處理get請求  get請求的Content-Type一般為application/x-www-form-urlencoded  或者  text/html
        if(method.trim().equalsIgnoreCase(HttpConst.GET_METHOD)){
            HttpServletRequestNormalWrapper wrapperRequest = new HttpServletRequestNormalWrapper(request);
            Map<String, String[]> parameterMap = wrapperRequest.getParameterMap();
            parameterMap =cleanXSSForNormalRequest(parameterMap);
            wrapperRequest.setParameterMap(parameterMap);
            chain.doFilter(wrapperRequest, response);
            return;
        }
        //2 處理post請求  只處理application/x-www-form-urlencoded  application/json,對于multipart/form-data岂昭,直接放行
        if(method.trim().equalsIgnoreCase(HttpConst.POST_METHOD)){
            if(contentType.trim().toLowerCase().contains(HttpConst.MULTIPART_CONTENT_TYPE)){
                chain.doFilter(request, response);
                return;
            }
            //處理application/x-www-form-urlencoded
            if(contentType.trim().toLowerCase().contains(HttpConst.FORM_URLENCODED_CONTENT_TYPE)){
                HttpServletRequestNormalWrapper wrapperRequest = new HttpServletRequestNormalWrapper(request);
                Map<String, String[]> parameterMap = wrapperRequest.getParameterMap();
                parameterMap =cleanXSSForNormalRequest(parameterMap);
                wrapperRequest.setParameterMap(parameterMap);
                chain.doFilter(wrapperRequest, response);
                return;
            }
            //處理application/json
            if(contentType.trim().toLowerCase().contains(HttpConst.JSON_CONTENT_TYPE)){
                HttpServletRequestBodyReaderWrapper requestWrapper = new HttpServletRequestBodyReaderWrapper(request);
                String body = requestWrapper.getBody();
                body =cleanXSSForPostJsonRequest(body);
                requestWrapper.setBody(body);
                chain.doFilter(requestWrapper, response);
                return;
            }
        }
        chain.doFilter(request, response);
        return;

    }

    public String cleanXSS(String value) {
        if(value==null||value.trim().isEmpty()){
            return null;
        }
        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
        value = value.replaceAll("'", "& #39;");
        value = value.replaceAll("\"", "& #34;");
        value = value.replaceAll("`", "");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");
        return value;
    }

    /**
     * 普通的post/get請求
     * @param parameterMap
     */
    public Map<String,String[]> cleanXSSForNormalRequest(Map<String,String[]> parameterMap){
        Map<String,String[]> cleanMap = new HashMap<>();
        if(parameterMap==null||parameterMap.size()==0){
            return cleanMap;
        }
        for (Map.Entry<String,String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] value = entry.getValue();
            String cleanKey = cleanXSS(key);
            String[] cleanValue = null;
            if(value!=null&&value.length>0){
                cleanValue = new String[value.length];
                for(int i=0;i<value.length;i++){
                    cleanValue[i]=cleanXSS(value[i]);
                }
            }
            cleanMap.put(cleanKey,cleanValue);
        }
        //打印用
        StringBuffer printStr = new StringBuffer();
        for (Map.Entry<String,String[]> entry1 : cleanMap.entrySet()) {
            printStr.append(entry1.getKey()).append("=").append(Arrays.asList(entry1.getValue())).append("&");
        }
        System.out.println("XssFilter:發(fā)送的請求參數(shù):"+JSON.toJSONString(printStr));
        return cleanMap;
    }

    /**
     * post的application/json請求
     * @param body
     */
    public String cleanXSSForPostJsonRequest(String body){
        String cleanBody = "{}";
        if(body==null||body.trim().isEmpty()||body.trim().equalsIgnoreCase("{}")||!body.trim().contains(":")){
            return cleanBody;
        }
        Map<String,Object> map = JSON.parseObject(body,new TypeReference<Map<String,Object>>(){});
        if(map==null||map.size()==0){
            return cleanBody;
        }
        Map<String,Object> cleanMap = new HashMap<>();
        for (Map.Entry<String,Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            String valueStr = String.valueOf(value);
            if(valueStr==null||valueStr.trim().isEmpty()||valueStr.trim().equalsIgnoreCase("null")){
                valueStr = null;
            }
            cleanMap.put(cleanXSS(key),cleanXSS(valueStr));
        }
        cleanBody = JSON.toJSONString(cleanMap);
        System.out.println("XssFilter:發(fā)送的請求參數(shù):"+cleanBody);
        return cleanBody;
    }


    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig arg0) {
    }
}

“好,那個誰狠怨,到我辦公室來一下约啊。今天的xss過濾工作做得不錯,就獎勵你今晚一個加班吧取董,要知道棍苹,這個加班名額真的好不容易,我費了很大的勁才說服咱們組今晚早點下班的茵汰,好讓你有加班的機會枢里,你可以不要讓我失望喲。好蹂午,你繼續(xù)忙吧”栏豺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市豆胸,隨后出現(xiàn)的幾起案子奥洼,更是在濱河造成了極大的恐慌,老刑警劉巖晚胡,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灵奖,死亡現(xiàn)場離奇詭異,居然都是意外死亡估盘,警方通過查閱死者的電腦和手機瓷患,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遣妥,“玉大人擅编,你說我怎么就攤上這事◇锊龋” “怎么了爱态?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長境钟。 經(jīng)常有香客問我锦担,道長,這世上最難降的妖魔是什么慨削? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任吆豹,我火速辦了婚禮鱼的,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痘煤。我一直安慰自己,他們只是感情好猿规,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布衷快。 她就那樣靜靜地躺著,像睡著了一般姨俩。 火紅的嫁衣襯著肌膚如雪蘸拔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天环葵,我揣著相機與錄音调窍,去河邊找鬼。 笑死张遭,一個胖子當(dāng)著我的面吹牛邓萨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菊卷,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼缔恳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了洁闰?” 一聲冷哼從身側(cè)響起歉甚,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扑眉,沒想到半個月后纸泄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡腰素,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年聘裁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耸弄。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡咧虎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出计呈,到底是詐尸還是另有隱情砰诵,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布捌显,位于F島的核電站茁彭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扶歪。R本人自食惡果不足惜理肺,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一摄闸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妹萨,春花似錦年枕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至树姨,卻和暖如春摩桶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帽揪。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工硝清, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人转晰。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓芦拿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挽霉。 傳聞我的和親對象是個殘疾皇子防嗡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容