spring boot實(shí)戰(zhàn)之XSS(跨站腳本攻擊)

跨站腳本攻擊(Cross Site Scripting),為了不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫為XSS。惡意攻擊者往Web頁(yè)面里插入惡意Script代碼,當(dāng)用戶瀏覽該頁(yè)之時(shí),嵌入其中Web里面的Script代碼會(huì)被執(zhí)行,從而達(dá)到惡意攻擊用戶的目的。

你可以自己做個(gè)簡(jiǎn)單嘗試:

  1. 在任何一個(gè)表單內(nèi),你輸入一段簡(jiǎn)單的js代碼:<script>for(var i=0;i<1000;i++){alert("彈死你"+i);}</script>,將其存入數(shù)據(jù)庫(kù);
  2. 在頁(yè)面上一個(gè)div元素內(nèi)直接展示第一步內(nèi)存入的值,你會(huì)發(fā)現(xiàn)彈出框出現(xiàn)了;

以上XSS攻擊只算一個(gè)小惡作劇,但如果這玩意被發(fā)到了網(wǎng)站的首頁(yè)上,我估計(jì)老板一定會(huì)因?yàn)轭l繁的投訴而和你來場(chǎng)愉快的談話...

以上兩個(gè)示例僅僅算是惡作劇,惡意用戶能做的更多,如獲取用戶信息,進(jìn)行“網(wǎng)絡(luò)釣魚”攻擊等。

應(yīng)對(duì)XSS攻擊的其中一個(gè)方式就是后端對(duì)輸入內(nèi)容進(jìn)行過濾,輸入內(nèi)容里面的敏感信息直接過濾,如<script>標(biāo)簽等,以下來說明如何在spring boot項(xiàng)目?jī)?nèi)方便快捷的實(shí)現(xiàn)XSS過濾。

1、Jsoup組件

Jsoup使用標(biāo)簽白名單的機(jī)制用來進(jìn)行防止XSS攻擊, 假設(shè)白名單中只允許p標(biāo)簽存在, 此時(shí)在一段HTML代碼中, 只能存在p標(biāo)簽 , 其他標(biāo)簽將會(huì)被清除只保留被標(biāo)簽所包裹的內(nèi)容,因此使用Jsoup組件來進(jìn)行內(nèi)容過濾。

添加maven依賴:

<!-- xss過濾組件 -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.9.2</version>
</dependency>

JsoupUtil提供基于Jsoup過濾非法標(biāo)簽的工具類:

/**
 * xss非法標(biāo)簽過濾
 * {@link http://www.itdecent.cn/p/32abc12a175a?nomobile=yes}
 */
public class JsoupUtil {

    /**
     * 使用自帶的basicWithImages 白名單
     * 允許的便簽有a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
     * strike,strong,sub,sup,u,ul,img
     * 以及a標(biāo)簽的href,img標(biāo)簽的src,align,alt,height,width,title屬性
     */
    private static final Whitelist whitelist = Whitelist.basicWithImages();
    /** 配置過濾化參數(shù),不對(duì)代碼進(jìn)行格式化 */
    private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
    static {
        // 富文本編輯時(shí)一些樣式是使用style來進(jìn)行實(shí)現(xiàn)的
        // 比如紅色字體 style="color:red;"
        // 所以需要給所有標(biāo)簽添加style屬性
        whitelist.addAttributes(":all", "style");
    }

    public static String clean(String content) {
        return Jsoup.clean(content, "", whitelist, outputSettings);
    }
    
    public static void main(String[] args) throws FileNotFoundException, IOException {
        String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\">sss</a><script>alert(0);</script>sss";
        System.out.println(clean(text));
    }

}

2、創(chuàng)建XssHttpServletRequestWrapper

這是實(shí)現(xiàn)XSS過濾的關(guān)鍵,在其內(nèi)重寫了getParameter,getParameterValues,getHeader等方法,對(duì)http請(qǐng)求內(nèi)的參數(shù)進(jìn)行了過濾。

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {  
    HttpServletRequest orgRequest = null;  
    private boolean isIncludeRichText = false;
  
    public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {  
        super(request);  
        orgRequest = request;
        this.isIncludeRichText = isIncludeRichText;
    }  
  
    /** 
    * 覆蓋getParameter方法,將參數(shù)名和參數(shù)值都做xss過濾。<br/> 
    * 如果需要獲得原始的值,則通過super.getParameterValues(name)來獲取<br/> 
    * getParameterNames,getParameterValues和getParameterMap也可能需要覆蓋 
    */  
    @Override  
    public String getParameter(String name) {  
            if(("content".equals(name) || name.endsWith("WithHtml")) && !isIncludeRichText){
                return super.getParameter(name);
            }
            name = JsoupUtil.clean(name);
        String value = super.getParameter(name);  
        if (StringUtils.isNotBlank(value)) {
            value = JsoupUtil.clean(value);  
        }
        return value;  
    }  
    
    @Override
    public String[] getParameterValues(String name) {
        String[] arr = super.getParameterValues(name);
        if(arr != null){
            for (int i=0;i<arr.length;i++) {
                arr[i] = JsoupUtil.clean(arr[i]);
            }
        }
        return arr;
    }
    
  
    /** 
    * 覆蓋getHeader方法,將參數(shù)名和參數(shù)值都做xss過濾。<br/> 
    * 如果需要獲得原始的值,則通過super.getHeaders(name)來獲取<br/> 
    * getHeaderNames 也可能需要覆蓋 
    */  
    @Override  
    public String getHeader(String name) {  
            name = JsoupUtil.clean(name);
        String value = super.getHeader(name);  
        if (StringUtils.isNotBlank(value)) {  
            value = JsoupUtil.clean(value); 
        }  
        return value;  
    }  
  
    /** 
    * 獲取最原始的request 
    * 
    * @return 
    */  
    public HttpServletRequest getOrgRequest() {  
        return orgRequest;  
    }  
  
    /** 
    * 獲取最原始的request的靜態(tài)方法 
    * 
    * @return 
    */  
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {  
        if (req instanceof XssHttpServletRequestWrapper) {  
            return ((XssHttpServletRequestWrapper) req).getOrgRequest();  
        }  
  
        return req;  
    }  
  
} 

3、創(chuàng)建XssFilter

XssFilter是過濾XSS請(qǐng)求的入口,在這里通過XssHttpServletRequestWrapper將HttpServletRequest進(jìn)行了封裝,filterChain.doFilter(xssRequest, response);保證了后續(xù)代碼執(zhí)行request.getParameter,request.getParameterValues,request.getHeader時(shí)調(diào)用的都是XssHttpServletRequestWrapper內(nèi)重寫的方法,獲取到的參數(shù)是已經(jīng)進(jìn)行過標(biāo)簽過濾的內(nèi)容,從而消除了敏感信息。

/** 
 * 攔截防止xss注入
 * 通過Jsoup過濾請(qǐng)求參數(shù)內(nèi)的特定字符
 * @author yangwk 
 */  
public class XssFilter implements Filter {  
    private static Logger logger = LoggerFactory.getLogger(XssFilter.class);
    
    private static boolean IS_INCLUDE_RICH_TEXT = false;//是否過濾富文本內(nèi)容
    
    public List<String> excludes = new ArrayList<String>();
  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException {  
        if(logger.isDebugEnabled()){
            logger.debug("xss filter is open");
        }
        
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if(handleExcludeURL(req, resp)){
            filterChain.doFilter(request, response);
            return;
        }
        
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request,IS_INCLUDE_RICH_TEXT);
        filterChain.doFilter(xssRequest, response);
    }
    
    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {

        if (excludes == null || excludes.isEmpty()) {
            return false;
        }

        String url = request.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if(logger.isDebugEnabled()){
            logger.debug("xss filter init~~~~~~~~~~~~");
        }
        String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
        if(StringUtils.isNotBlank(isIncludeRichText)){
            IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
        }
        
        String temp = filterConfig.getInitParameter("excludes");
        if (temp != null) {
            String[] url = temp.split(",");
            for (int i = 0; url != null && i < url.length; i++) {
                excludes.add(url[i]);
            }
        }
    }

    @Override
    public void destroy() {}  
  
}

4、注冊(cè)XssFilter

通過java config的方式注冊(cè)XSSFilter,使其生效。

/**
 * xss過濾攔截器
 */
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new XssFilter());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.setEnabled(true);
    filterRegistrationBean.addUrlPatterns("/*");
    Map<String, String> initParameters = Maps.newHashMap();
    initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
    initParameters.put("isIncludeRichText", "true");
    filterRegistrationBean.setInitParameters(initParameters);
    return filterRegistrationBean;
}
  • excludes用于配置不需要參數(shù)過濾的請(qǐng)求url
  • isIncludeRichText默認(rèn)為true,主要用于設(shè)置富文本(項(xiàng)目?jī)?nèi)約束以content為名或以WithHtml結(jié)尾)內(nèi)容是否需要過濾,該選項(xiàng)可根據(jù)公司具體情況調(diào)整,建議約束富文本編輯框支持的標(biāo)簽并開啟改約束,減少安全隱患

小結(jié)

防御XSS攻擊,可以通過后端統(tǒng)一進(jìn)行標(biāo)簽過濾,去掉所有輸入內(nèi)容中包含的類似于<script>這樣的非法標(biāo)簽來實(shí)現(xiàn)。

  1. 標(biāo)簽過濾實(shí)現(xiàn)可使用Jsoup,功能強(qiáng)大,使用方便,更多內(nèi)容可參考Jsoup 防止富文本 XSS 攻擊
  2. 繼承HttpServletRequestWrapper,重寫從request內(nèi)獲取參數(shù)的方法,在其內(nèi)調(diào)用JsoupUtil的方法,進(jìn)行參數(shù)脫敏處理;
  3. 通過XssFilter將XssHttpServletRequestWrapper設(shè)置入處理鏈中,從而達(dá)到后續(xù)處理類內(nèi)通過Request獲取參數(shù)時(shí)調(diào)用的是重寫后的獲取參數(shù)的方法,進(jìn)而達(dá)成業(yè)務(wù)代碼無(wú)感知的實(shí)現(xiàn)了XSS過濾的目的。

本人搭建好的spring boot web后端開發(fā)框架已上傳至GitHub,包含本文內(nèi)的全部代碼示例。
https://github.com/q7322068/rest-base,已用于多個(gè)正式項(xiàng)目,當(dāng)前可能因?yàn)榘姹締栴}不是很完善,后續(xù)持續(xù)優(yōu)化,希望你能有所收獲!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 跨站腳本(XSS)是web應(yīng)用中的一種典型的計(jì)算機(jī)安全漏洞。XSS允許攻擊者可以在其他用戶瀏覽的web頁(yè)面中注入客...
    留七七閱讀 8,301評(píng)論 1 26
  • 1、漏洞概述 XSS 是指攻擊者在網(wǎng)頁(yè)中嵌入客戶端腳本,通常是 JavaScript 編寫的惡意代碼,當(dāng)用戶使 用...
    linkally閱讀 1,785評(píng)論 2 10
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,544評(píng)論 19 139
  • 在那個(gè)年代,大家一般用拼接字符串的方式來構(gòu)造動(dòng)態(tài) SQL 語(yǔ)句創(chuàng)建應(yīng)用,于是 SQL 注入成了很流行的攻擊方式。在...
    Gundy_閱讀 593評(píng)論 0 5
  • 在那個(gè)年代,大家一般用拼接字符串的方式來構(gòu)造動(dòng)態(tài) SQL 語(yǔ)句創(chuàng)建應(yīng)用,于是 SQL 注入成了很流行的攻擊方式。在...
    Safesonic閱讀 721評(píng)論 0 4

友情鏈接更多精彩內(nèi)容