手寫SpringBoot項目XSS攻擊過濾器實現


一、先來個簡介

什么是XSS?

百度百科的解釋: XSS又叫CSS  (Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往Web頁面里插入惡意html代碼,當用戶瀏覽該頁之時,嵌入其中Web里面的html代碼會被執行,從而達到惡意用戶的特殊目的。

它與SQL注入攻擊類似,SQL注入攻擊中以SQL語句作為用戶輸入,從而達到查詢/修改/刪除數據的目的,而在xss攻擊中,通過插入惡意腳本,實現對用戶游覽器的控制,獲取用戶的一些信息。

二、XSS分類

xss攻擊可以分成兩種類型:

1.非持久型攻擊
2.持久型攻擊

非持久型xss攻擊:顧名思義,非持久型xss攻擊是一次性的,僅對當次的頁面訪問產生影響。非持久型xss攻擊要求用戶訪問一個被攻擊者篡改后的鏈接,用戶訪問該鏈接時,被植入的攻擊腳本被用戶游覽器執行,從而達到攻擊目的。

持久型xss攻擊:持久型xss,會把攻擊者的數據存儲在服務器端,攻擊行為將伴隨着攻擊數據一直存在。 

也可以分成三類:

反射型:經過后端,不經過數據庫

存儲型:經過后端,經過數據庫

三、代碼走起

先加pom文件加上依賴

1    <dependency>
2             <groupId>org.apache.commons</groupId>
3             <artifactId>commons-text</artifactId>
4             <version>1.4</version>
5    </dependency>

1.首先是要寫個過濾器的包裝類,這也是實現XSS攻擊過濾的核心代碼。

這邊因為業務需要,我拆分了兩種的xss包裝類,針對於不同的接口來使用。

第一中寫法如下:

 
         
package com.hrt.zxxc.fxspg.xss;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* @program: fxspg
* @description: XSS過濾具體核心代碼(第一種)
* @author: liumingyu
* @date: 2020-01-10 14:28
**/
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {

//聲明sql注入的關鍵詞key
private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+";
private static Set<String> notAllowedKeyWords = new HashSet<String>(0);
private static String replacedString="INVALID";
static {
String keyStr[] = key.split("\\|");
//將key添加到Set集合中
for (String str : keyStr) {
notAllowedKeyWords.add(str);
}
}

/**
* @return
* @Author liumingyu
* @Description //TODO 構造函數,傳入參數,執行超類
* @Date 2020/1/10 2:29 下午
* @Param [request]
**/
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

/**
* @return java.lang.String
* @Author liumingyu
* @Description //TODO 重寫getParameter方法 ,getParameter方法是直接通過request獲得querystring類型的入參調用的方法
* @Date 2020/1/10 2:31 下午
* @Param [name]
**/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (!StringUtils.isEmpty(value)) {
//調用Apache的工具類:StringEscapeUtils.escapeHtml4
value = StringEscapeUtils.escapeHtml4(value);
value = cleanSqlKeyWords(value);
}
return value;
}

/**
* @return java.lang.String[]
* @Author liumingyu
* @Description //TODO 重寫getParameterValues
* @Date 2020/1/10 2:32 下午
* @Param [name]
**/
@Override
public String[] getParameterValues(String name) {
String[] parameterValues = super.getParameterValues(name);
if (parameterValues == null) {
return null;
}
for (int i = 0; i < parameterValues.length; i++) {
String value = parameterValues[i];
//調用Apache的工具類:StringEscapeUtils.escapeHtml4
parameterValues[i] = StringEscapeUtils.escapeHtml4(value);
parameterValues[i] = cleanSqlKeyWords(parameterValues[i]);
}
return parameterValues;
}

@Override
public String getHeader(String name) {
//過濾xss攻擊
String value = StringEscapeUtils.escapeHtml4(super.getHeader(name));
if (value == null){
return null;
}
//過濾sql注入
return cleanSqlKeyWords(value);
}

@Override
public String getQueryString() {
return StringEscapeUtils.escapeHtml4(super.getQueryString());
}

/**
* @return javax.servlet.ServletInputStream
* @Author liumingyu
* @Description //TODO 過濾JSON數據中的XSS攻擊
* @Date 2020/1/10 4:58 下午
* @Param []
**/
@Override
public ServletInputStream getInputStream() throws IOException {
//調用方法將流數據return為String
String str = getRequestBody(super.getInputStream());
//如果str為"",則返回0
if ("".equals(str)) {
return new ServletInputStream() {
@Override
public int read() throws IOException {
return 0;
}

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

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

@Override
public void setReadListener(ReadListener readListener) {

}
};
}
//將數據存放至map
Map<String, Object> map = JSON.parseObject(str, Map.class);
//聲明個存放過濾后數據的hashMap
Map<String, Object> resultMap = new HashMap<>(map.size());
//開始遍歷數據
for (String key : map.keySet()) {
Object val = map.get(key);
//如果key=富文本字段名,就不去過濾
if ("content".equals(key)) {
//不過濾
resultMap.put(key, val);
} else {
//不為富文本字段才會過濾
if (map.get(key) instanceof String) {
//通過escapeHtml4去過濾
resultMap.put(key, StringEscapeUtils.escapeHtml4(cleanSqlKeyWords(val.toString())));
} else {
//不過濾
resultMap.put(key, val);
}
}
}
str = JSON.toJSONString(resultMap);
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}

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

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

@Override
public void setReadListener(ReadListener readListener) {

}

};
}

/**
* @return java.lang.String
* @Author liumingyu
* @Description //TODO 獲取JSON數據
* @Date 2020/1/10 4:58 下午
* @Param [stream]
**/
private String getRequestBody(InputStream stream) {
String line = "";
StringBuilder body = new StringBuilder();
int counter = 0;
// 讀取POST提交的數據內容
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8")));
try {
while ((line = reader.readLine()) != null) {
//拼接讀取到的數據
body.append(line);
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
if (body == null) {
return "";
}
//最后返回數據
return body.toString();
}


/**
* @Author liumingyu
* @Description //TODO 過濾可能造成sql注入的關鍵字
* @Date 2020/1/13 9:11 上午
* @Param [value]
* @return java.lang.String
**/
private String cleanSqlKeyWords(String value) {
String paramValue = value;
for (String keyword : notAllowedKeyWords) {
if (paramValue.length() > keyword.length() + 4
&& (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) {
paramValue = StringUtils.replace(paramValue, keyword, replacedString);
}
}
return paramValue;
}

}
 

第二種如下:

 

package com.hrt.zxxc.fxspg.xss;

import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;



/**
 * @ClassName XssHttpServletRequestWrapper3
 * @Description TODO xss過濾核心代碼(第二種)
 * @Author liumingyu
 * @Date 2020/2/4 15:51
 * @Version 1.0
 **/
public class XssHttpServletRequestWrapper3 extends HttpServletRequestWrapper {
    private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+";
    private static Set<String> notAllowedKeyWords = new HashSet<String>(0);
    private static String replacedString="INVALID";
    static {
        String keyStr[] = key.split("\\|");
        for (String str : keyStr) {
            notAllowedKeyWords.add(str);
        }
    }

    private String currentUrl;
    private byte[] body;

    /**
     * @Author liumingyu
     * @Description //TODO 構造函數
     * @Date 2020/2/5 19:58
     * @Param [servletRequest]
     * @return
     **/
    public XssHttpServletRequestWrapper3(HttpServletRequest servletRequest) throws IOException {
        super(servletRequest);
        currentUrl = servletRequest.getRequestURI();
        //獲取到json數據
        this.body = StreamUtils.copyToByteArray(servletRequest.getInputStream());
    }

    /**
     * @Author liumingyu
     * @Description //TODO 重寫該方法過濾json數據
     * @Date 2020/2/5 19:59
     * @Param []
     * @return javax.servlet.ServletInputStream
     **/
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ServletInputStream inputStream = null;
        String bodyStr = new String(body);
        if (!StringUtils.isEmpty(bodyStr)) {
            bodyStr = xssEncode(bodyStr, 1);
            bodyStr = cleanSqlKeyWords(bodyStr);
            final ByteArrayInputStream bais = new ByteArrayInputStream(bodyStr.getBytes());
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

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

                @Override
                public void setReadListener(ReadListener readListener) {

                }

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

    /**
     * @Author liumingyu
     * @Description //TODO 將容易引起xss漏洞的半角字符直接替換成全角字符
     * @Date 2020/2/5 19:57
     * @Param [s, type]
     * @return java.lang.String
     **/
    private static String xssEncode(String s, int type) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (type == 0) {
                switch (c) {
                    case '\'':
                        // 全角單引號
                        sb.append('‘');
                        break;
                    case '\"':
                        // 全角雙引號
                        sb.append('“');
                        break;
                    case '>':
                        // 全角大於號
                        sb.append('>');
                        break;
                    case '<':
                        // 全角小於號
                        sb.append('<');
                        break;
                    case '&':
                        // 全角&符號
                        sb.append('&');
                        break;
                    case '\\':
                        // 全角斜線
                        sb.append('\');
                        break;
                    case '#':
                        // 全角井號
                        sb.append('#');
                        break;
                    // < 字符的 URL 編碼形式表示的 ASCII 字符(十六進制格式) 是: %3c
                    case '%':
                        processUrlEncoder(sb, s, i);
                        break;
                    default:
                        sb.append(c);
                        break;
                }
            } else {
                switch (c) {
                    case '>':
                        // 全角大於號
                        sb.append('>');
                        break;
                    case '<':
                        // 全角小於號
                        sb.append('<');
                        break;
                    case '&':
                        // 全角&符號
                        sb.append('&');
                        break;
                    case '#':
                        // 全角井號
                        sb.append('#');
                        break;
                    // < 字符的 URL 編碼形式表示的 ASCII 字符(十六進制格式) 是: %3c
                    case '%':
                        processUrlEncoder(sb, s, i);
                        break;
                    default:
                        sb.append(c);
                        break;
                }
            }

        }
        return sb.toString();
    }

    public static void processUrlEncoder(StringBuilder sb, String s, int index) {
        if (s.length() >= index + 2) {
            // %3c, %3C
            if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'c' || s.charAt(index + 2) == 'C')) {
                sb.append('<');
                return;
            }
            // %3c (0x3c=60)
            if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '0') {
                sb.append('<');
                return;
            }
            // %3e, %3E
            if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'e' || s.charAt(index + 2) == 'E')) {
                sb.append('>');
                return;
            }
            // %3e (0x3e=62)
            if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '2') {
                sb.append('>');
                return;
            }
        }
        sb.append(s.charAt(index));
    }


    /**覆蓋getParameter方法,將參數名和參數值都做xss過濾。
     * 如果需要獲得原始的值,則通過super.getParameterValues(name)來獲取
     * getParameterNames,getParameterValues和getParameterMap也可能需要覆蓋
     */
    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    @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 Map<String, String[]> getParameterMap(){
        Map<String, String[]> values=super.getParameterMap();
        if (values == null) {
            return null;
        }
        Map<String, String[]> result=new HashMap<>();
        for(String key:values.keySet()){
            String encodedKey=cleanXSS(key);
            int count=values.get(key).length;
            String[] encodedValues = new String[count];
            for (int i = 0; i < count; i++){
                encodedValues[i]=cleanXSS(values.get(key)[i]);
            }
            result.put(encodedKey,encodedValues);
        }
        return result;
    }

    /**
     * 覆蓋getHeader方法,將參數名和參數值都做xss過濾。
     * 如果需要獲得原始的值,則通過super.getHeaders(name)來獲取
     * getHeaderNames 也可能需要覆蓋
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    private String cleanXSS(String valueP) {
        String value = valueP.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        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", "");
        value = cleanSqlKeyWords(value);
        return value;
    }

    private String cleanSqlKeyWords(String value) {
        String paramValue = value;
        for (String keyword : notAllowedKeyWords) {
            if (paramValue.length() > keyword.length() + 3
                    && (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) {
                paramValue = StringUtils.replace(paramValue, keyword, replacedString);
                System.out.println(this.currentUrl + "已被過濾,因為參數中包含不允許sql的關鍵詞(" + keyword
                        + ")"+";參數:"+value+";過濾后的參數:"+paramValue);
            }
        }
        return paramValue;
    }

 

2.看到這里你就已經完成了一半了加油!接下來的事情很簡單寫個過濾器就over 。

 

package com.hrt.zxxc.fxspg.xss;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName XssFilter2
 * @Description TODO
 * @Author liumingyu
 * @Date 2020/2/4 15:45
 * @Version 1.0
 **/
@WebFilter
@Component
public class XssFilter3 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        //獲取請求數據
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        //獲取請求的url路徑
        String path = ((HttpServletRequest) servletRequest).getServletPath();
        //聲明要被忽略請求的數組
        String[] exclusionsUrls = {".js", ".gif", ".jpg", ".jpeg", ".png", ".css", ".ico", "health", "uploadPic", "file", "savePrintTemp", "goods/create", "goods/edit", "goodsBatchImport", "goodsBatchExport","platformtestreport"};
        //聲明帶有富文本的接口數組
        String[] richTextUrls = {"addarticles", "editarticles", "advert"};
        //第一種xss過濾
        XssAndSqlHttpServletRequestWrapper XssAndSqlHttpServletRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req);
        //遍歷忽略的請求數組,若該接口url為忽略的就調用原本的過濾器,不走xss過濾
        for (String str : exclusionsUrls) {
            if (path.contains(str)) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
        }
        //若為帶有富文本的接口,走第一種xss過濾
        for (String rtu : richTextUrls) {
            if (path.contains(rtu)) {
                filterChain.doFilter(XssAndSqlHttpServletRequestWrapper, servletResponse);
                return;
            }
        }
        //將請求放入XSS請求包裝器中,返回過濾后的值
        XssHttpServletRequestWrapper3 xssRequestWrapper3 = new XssHttpServletRequestWrapper3(req);
        filterChain.doFilter(xssRequestWrapper3, servletResponse);
    }

    @Override
    public void destroy() {

    }

}

 

注:上面的注釋我都寫得比較全了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM