<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          漏洞警告:SpringBoot 該如何預(yù)防 XSS 攻擊 ??

          共 18893字,需瀏覽 38分鐘

           ·

          2022-10-31 21:44

          大家好,我是寶哥!

          前言

          寫此文章的目的是為了記錄一下在工作中解決的 XSS漏洞 問題。XSS漏洞是生產(chǎn)上比較常見的問題。雖然是比較常見并且是基本的安全問題,但是我們沒有做??? ,也怪我沒有安全意識。于是終于有一天被制裁了。所以這次就補上了,記錄一下。

          正文


          看看問題

          XSS 漏洞到底是什么,說實話我講不太清楚。但是可以通過遇到的現(xiàn)象了解一下。在前端Form表單的輸入框中,用戶沒有正常輸入,而是輸入了一段代碼: </input><img src=1 onerror=alert1> ?這個正常保存沒有問題。問題出在了列表查詢的時候,上面的代碼就生效了,由于圖片的地址亂寫的,所以這個alert就起作用了來看圖。

          2abd3413fa45e4d3c0abd81b967d422c.webp

          那根據(jù)這個原理,實際上如果沒有做任何的限制,有心人就可以為所欲為了??梢栽诶锩媲度胍恍╆P(guān)鍵代碼,把你的信息拿走。確實是個很嚴重的問題。

          解決思路

          既然是因為輸入框中輸入了不該輸入的東西,那自然就萌生一些想法:

          • 校驗輸入內(nèi)容,不允許用戶輸入特殊字符,特殊標簽
          • 允許用戶輸入,但是保存的時候?qū)⑻厥獾淖址苯犹鎿Q為空串
          • 允許用戶輸入,將特殊字符轉(zhuǎn)譯保存。

          第一種方法,特殊字符過濾。既然要過濾特殊字符,那就得自己把所有的特殊字符列出來進行匹配,比較麻煩,而且要定義好什么才是特殊字符?況且用戶本身不知道什么是特殊字符。突如其來的報錯,會讓用戶有點摸不著頭腦,不是很友好。

          第二種方法,特殊字符替換為空串。未免有點太暴力。萬一真的需要輸入一點特殊的字符,保存完查出來發(fā)現(xiàn)少了好多東西,人家以為我們的BUG呢。也不是很好的辦法。

          第三種辦法,特殊字符轉(zhuǎn)譯。這個辦法不但用戶數(shù)據(jù)不丟失,而且瀏覽器也不會執(zhí)行代碼。比較符合預(yù)期。

          那辦法確定了,怎么做呢?前端來做還是后端來做?想了想還是要后端來做。畢竟使用切面或者Filter可以一勞永逸。

          心路歷程

          經(jīng)過抄襲,我發(fā)現(xiàn)了一些問題,也漸漸的有了一些理解。下面再說幾句廢話:

          查到的預(yù)防XSS攻擊的,大多數(shù)的流程是:

          • 攔截請求
          • 重新包裝請求
          • 重寫 HttpServletRequest 中的獲取參數(shù)的方法
          • 將獲得的參數(shù)進行XSS處理
          • 攔截器放行

          于是我就逮住一個抄了一下。抄襲完畢例行測試,發(fā)現(xiàn)我用 @RequestBody 接受的參數(shù),并不能過濾掉特殊字符。怎么肥四?大家明明都這么寫。為什么我的不好使?

          這個時候突然一個想法萌生。SpringMVC在處理 @RequestBody 類型的參數(shù)的時候,是不是使用的我重寫的這些方法呢?( getQueryString() 、 getParameter(String name) 、 getParameterValues(String name) getParameterMap() )。打了個日志,發(fā)現(xiàn)還真不是這些方法。

          于是搜索了一下Springboot攔截器獲取 @RequestBody 參數(shù),碰到了這篇文章。首先的新發(fā)現(xiàn)是Spring MVC 在獲取 @RequestBody 參數(shù)的時候使用的是 getInputStream() 方法。嗯?(斜眼笑)那我是不是可以重寫這個方法獲取到輸入流的字符串,然后直接處理一下?

          說干就干,一頓操作。進行測試。發(fā)現(xiàn)直接JSON 轉(zhuǎn)換的報錯了。腦裂。估計是獲得的字符串在轉(zhuǎn)換的時候把不該轉(zhuǎn)的東西轉(zhuǎn)譯了,導(dǎo)致不能序列化了。眼看就要成功了,一測回到解放前。

          該怎么辦呢?其實思路是沒錯的,就是在獲取到流之后進行處理。但是錯就錯在處理的位置。果然處理的時間點很重要。(就像伴侶一樣,某人出現(xiàn)的時間點很重要)。那既然不能在現(xiàn)在處理,那就等他序列化完畢之后再處理就好了。那怎么辦呢?難道要寫一個AOP 攔截到所有的請求?用JAVA反射處理?

          正在迷茫的時候,看到了一篇文章,知識增加了。原來可以在序列化和反序列化的時候進行處理。

          最終實現(xiàn)

          看一下最終的代碼實現(xiàn)(有些導(dǎo)入的包被我刪了)

          重新包裝Request的代碼
                      
                      
                        import?org.apache.commons.text.StringEscapeUtils;
          import?org.slf4j.Logger;
          import?org.slf4j.LoggerFactory;

          import?javax.servlet.ReadListener;
          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.StandardCharsets;
          import?java.util.Map;

          /**
          ?*?重新包裝一下Request。重寫一些獲取參數(shù)的方法,將每個參數(shù)都進行過濾
          ?*/

          public?class?XSSHttpServletRequestWrapper?extends?HttpServletRequestWrapper?{
          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(XSSHttpServletRequestWrapper.class);

          ????private?HttpServletRequest?request;
          ????/**
          ?????*?請求體?RequestBody
          ?????*/

          ????private?String?reqBody;

          ????/**
          ?????*?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);
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?created-----");
          ????????this.request?=?request;
          ????????reqBody?=?getBodyString();
          ????}


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

          ????/**
          ?????*?The?default?behavior?of?this?method?is?to?return?getParameter(String
          ?????*?name)?on?the?wrapped?request?object.
          ?????*
          ?????*?@param?name
          ?????*/

          ????@Override
          ????public?String?getParameter(String?name)?{
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?work??getParameter-----");
          ????????String?parameter?=?request.getParameter(name);
          ????????if?(StringUtil.isNotBlank(parameter))?{
          ????????????logger.info("----filter?before--name:{}--value:{}----",?name,?parameter);
          ????????????parameter?=?StringEscapeUtils.escapeHtml4(parameter);
          ????????????logger.info("----filter?after--name:{}--value:{}----",?name,?parameter);
          ????????}
          ????????return?parameter;
          ????}

          ????/**
          ?????*?The?default?behavior?of?this?method?is?to?return
          ?????*?getParameterValues(String?name)?on?the?wrapped?request?object.
          ?????*
          ?????*?@param?name
          ?????*/

          ????@Override
          ????public?String[]?getParameterValues(String?name)?{
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?work??getParameterValues-----");
          ????????String[]?parameterValues?=?request.getParameterValues(name);
          ????????if?(!CollectionUtil.isEmpty(parameterValues))?{
          ?????????//?經(jīng)?“@Belief_7”?指正?這種方式不能更改parameterValues里面的值,要換成下面??的寫法
          ????????????//for?(String?value?:?parameterValues)?{
          ????????????//????logger.info("----filter?before--name:{}--value:{}----",?name,?value);
          ????????????//????value?=?StringEscapeUtils.escapeHtml4(value);
          ????????????//????logger.info("----filter?after--name:{}--value:{}----",?name,?value);
          ????????????//?}
          ????????????for?(int?i?=?0;?i?<?parameterValues.length;?i++)?
          ?????????{?
          ?????????????parameterValues[i]?=?StringEscapeUtils.escapeHtml4(parameterValues[i]);?
          ?????????}?
          ????????}
          ????????return?parameterValues;
          ????}

          ????/**
          ?????*?The?default?behavior?of?this?method?is?to?return?getParameterMap()?on?the
          ?????*?wrapped?request?object.
          ?????*/

          ????@Override
          ????public?Map<String,?String[]>?getParameterMap()?{
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?work??getParameterMap-----");
          ????????Map<String,?String[]>?map?=?request.getParameterMap();
          ????????if?(map?!=?null?&&?!map.isEmpty())?{
          ????????????for?(String[]?value?:?map.values())?{
          ????????????????/*循環(huán)所有的value*/
          ????????????????for?(String?str?:?value)?{
          ????????????????????logger.info("----filter?before--value:{}----",?str,?str);
          ????????????????????str?=?StringEscapeUtils.escapeHtml4(str);
          ????????????????????logger.info("----filter?after--value:{}----",?str,?str);
          ????????????????}
          ????????????}
          ????????}
          ????????return?map;
          ????}

          ????/*重寫輸入流的方法,因為使用RequestBody的情況下是不會走上面的方法的*/
          ????/**
          ?????*?The?default?behavior?of?this?method?is?to?return?getReader()?on?the
          ?????*?wrapped?request?object.
          ?????*/

          ????@Override
          ????public?BufferedReader?getReader()?throws?IOException?{
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?work??getReader-----");
          ????????return?new?BufferedReader(new?InputStreamReader(getInputStream()));
          ????}

          ????/**
          ?????*?The?default?behavior?of?this?method?is?to?return?getInputStream()?on?the
          ?????*?wrapped?request?object.
          ?????*/

          ????@Override
          ????public?ServletInputStream?getInputStream()?throws?IOException?{
          ????????logger.info("---xss?XSSHttpServletRequestWrapper?work??getInputStream-----");
          ????????/*創(chuàng)建字節(jié)數(shù)組輸入流*/
          ????????final?ByteArrayInputStream?bais?=?new?ByteArrayInputStream(reqBody.getBytes(StandardCharsets.UTF_8));
          ????????return?new?ServletInputStream()?{
          ????????????@Override
          ????????????public?boolean?isFinished()?{
          ????????????????return?false;
          ????????????}

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

          ????????????@Override
          ????????????public?void?setReadListener(ReadListener?listener)?{
          ????????????}

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


          ????/**
          ?????*?獲取請求體
          ?????*
          ?????*?@return?請求體
          ?????*/

          ????private?String?getBodyString()?{
          ????????StringBuilder?builder?=?new?StringBuilder();
          ????????InputStream?inputStream?=?null;
          ????????BufferedReader?reader?=?null;

          ????????try?{
          ????????????inputStream?=?request.getInputStream();

          ????????????reader?=?new?BufferedReader(new?InputStreamReader(inputStream));

          ????????????String?line;

          ????????????while?((line?=?reader.readLine())?!=?null)?{
          ????????????????builder.append(line);
          ????????????}
          ????????}?catch?(IOException?e)?{
          ????????????logger.error("-----get?Body?String?Error:{}----",?e.getMessage(),?e);
          ????????}?finally?{
          ????????????if?(inputStream?!=?null)?{
          ????????????????try?{
          ????????????????????inputStream.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????logger.error("-----get?Body?String?Error:{}----",?e.getMessage(),?e);
          ????????????????}
          ????????????}
          ????????????if?(reader?!=?null)?{
          ????????????????try?{
          ????????????????????reader.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????logger.error("-----get?Body?String?Error:{}----",?e.getMessage(),?e);
          ????????????????}
          ????????????}
          ????????}
          ????????return?builder.toString();
          ????}
          }
          定義過濾器
                      
                      
                        import?org.slf4j.Logger;
          import?org.slf4j.LoggerFactory;

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

          /**
          ?*?Filter?過濾器,攔截請求轉(zhuǎn)換為新的請求
          ?*/

          public?class?XssFilter?implements?Filter?{
          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(XssFilter.class);

          ????/**
          ?????*?初始化方法
          ?????*/

          ????@Override
          ????public?void?init(FilterConfig?filterConfig)?throws?ServletException?{
          ????????logger.info("----xss?filter?start-----");
          ????}
          ????/**
          ?????*?過濾方法
          ?????*/

          ????@Override
          ????public?void?doFilter(ServletRequest?request,?ServletResponse?response,?FilterChain?chain)?throws?IOException,?ServletException?{
          ????????ServletRequest?wrapper?=?null;
          ????????if?(request?instanceof?HttpServletRequest)?{
          ????????????HttpServletRequest?servletRequest?=?(HttpServletRequest)?request;
          ????????????wrapper?=?new?XSSHttpServletRequestWrapper(servletRequest);
          ????????}

          ????????if?(null?==?wrapper)?{
          ????????????chain.doFilter(request,?response);
          ????????}?else?{
          ????????????chain.doFilter(wrapper,?response);
          ????????}
          ????}
          }
          注冊過濾器

          注冊過濾器我了解到的有兩種方式。我用的下面的這種

          一種通過 @WebFilter 注解的方式來配置,但這種啟動類上要加 @ServletComponentScan ?注解來指定掃描路徑

          另外一種就是以Bean 的方式來注入(不知道放哪里,就把Bean放到啟動類里面)

                      
                      
                        /**
          ?*?XSS?的Filter注入
          ?*?用來處理getParameter的參數(shù)
          ?*?@return
          ?*/

          @Bean
          public?FilterRegistrationBean?xssFilterRegistrationBean(){
          ????FilterRegistrationBean?filterRegistrationBean?=?new?FilterRegistrationBean();
          ????filterRegistrationBean.setFilter(new?XssFilter());
          ????filterRegistrationBean.setOrder(1);
          ????filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
          ????filterRegistrationBean.setEnabled(true);
          ????filterRegistrationBean.addUrlPatterns("/*");
          ????return?filterRegistrationBean;
          }

          上面配的是使用 request.getParameter() 的時候生效的,但是當我使用 @RequestBody 來接收參數(shù)的時候是不行的,所以還得有下面的代碼:

          處理請求中的JSON數(shù)據(jù)
                      
                      
                        import?com.fasterxml.jackson.core.JsonParser;
          import?com.fasterxml.jackson.core.JsonProcessingException;
          import?com.fasterxml.jackson.databind.DeserializationContext;
          import?com.fasterxml.jackson.databind.JsonDeserializer;
          import?org.apache.commons.text.StringEscapeUtils;
          import?java.io.IOException;

          /**
          ?*?反序列化,用來處理請求中的JSON數(shù)據(jù)
          ?*?處理RequestBody方式接收的參數(shù)
          ?*/

          public?class?XssJacksonDeserializer?extends?JsonDeserializer<String>?{

          ????@Override
          ????public?String?deserialize(JsonParser?jp,?DeserializationContext?ctxt)?throws?IOException,?JsonProcessingException?{
          ????????return?StringEscapeUtils.escapeHtml4(jp.getText());
          ????}
          }
          處理返回值的JSON數(shù)據(jù)
                      
                      
                        import?com.fasterxml.jackson.core.JsonGenerator;
          import?com.fasterxml.jackson.databind.JsonSerializer;
          import?com.fasterxml.jackson.databind.SerializerProvider;
          import?org.apache.commons.text.StringEscapeUtils;
          import?java.io.IOException;


          /**
          ?*?處理向前端發(fā)送的JSON數(shù)據(jù),將數(shù)據(jù)進行轉(zhuǎn)譯后發(fā)送
          ?*/

          public?class?XssJacksonSerializer?extends?JsonSerializer<String>?{
          ????@Override
          ????public?void?serialize(String?value,?JsonGenerator?jgen,?SerializerProvider?provider)?throws?IOException?{
          ????????jgen.writeString(StringEscapeUtils.escapeHtml4(value));
          ????}
          }
          注冊、配置自定義的序列化方法
                      
                      
                        @Override
          public?void?extendMessageConverters(List<HttpMessageConverter<?>>?converters)?{
          ?Jackson2ObjectMapperBuilder?builder?=?new?Jackson2ObjectMapperBuilder();
          ?ObjectMapper?mapper?=?builder.build();
          ?/*注入自定義的序列化工具,將RequestBody的參數(shù)進行轉(zhuǎn)譯后傳輸*/
          ????SimpleModule?simpleModule?=?new?SimpleModule();
          ????//?XSS序列化
          ????simpleModule.addSerializer(String.class,?new?XssJacksonSerializer());
          ????simpleModule.addDeserializer(String.class,?new?XssJacksonDeserializer());
          ????mapper.registerModule(simpleModule);
          ????converters.add(new?MappingJackson2HttpMessageConverter(mapper));
          }
          測試

          所有東西都配置完了,接下來進行愉快的測試階段了。

          我依然在輸入框中輸入這段代碼 </input><img src=1 onerror=alert1> 并進行保存。來看一下數(shù)據(jù)庫中的保存結(jié)果:

          0cf760b7cc1a61743033c30c088adc54.webp

          可以看到數(shù)據(jù)庫中保存的數(shù)據(jù),已經(jīng)經(jīng)過轉(zhuǎn)譯了。那查詢一下列表是什么樣的呢?

          e0bc85decd9e8f51fb951f87c153b44a.webp

          可以看到兩條數(shù)據(jù),上面的是我們經(jīng)過轉(zhuǎn)譯的,正常的展示出來了。而下面的是沒經(jīng)過轉(zhuǎn)譯的,直接空白,并且給我彈了個窗。

          總結(jié)

          • 就是注意要分情況處理。
          • 攔截器處理一部分,并注意攔截器的注冊方式
          • Jackson的方式處理另一部分,也是注意配置方式

          補充

          代碼經(jīng)過驗證后,發(fā)現(xiàn)了一個問題。今天來補充一下。問題是這樣的:

          如果使用 @RequestBody 的形式接受參數(shù),也就是需要使用自定義的序列化方式。然而有時候,我們的業(yè)務(wù)需要傳遞一些JSON串到后端,如 {\"username\":\"zx\",\"pwd\":\"123\"} (注意這是個字符串)。但是因為我不管三七二十一直接暴力轉(zhuǎn)譯,導(dǎo)致里面的雙引號以及其他符號都被轉(zhuǎn)譯了。那么當我們拿到這個字符串之后,再自己反序列化的時候就會出錯了。

          為了解決這個問題,我在自定義的序列化方法中判斷了一下這個字段的值是否是JSON形式,如果是JSON形式,那就不做處理,直接返回,以保證能夠順利反序列化。判斷是否是JSON的方式,我選擇最簡單的,判斷首尾是否是 { } [ ] 的組合。代碼如下:

                      
                      
                        public?class?XssJacksonDeserializer?extends?JsonDeserializer<String>?{

          ????@Override
          ????public?String?deserialize(JsonParser?jp,?DeserializationContext?ctxt)?throws?IOException,?JsonProcessingException?{
          ????????//?判斷一下?值是不是JSON的格式,如果是JSON的話,那就不處理了。
          ????????/*判斷JSON,可以用JSON.parse但是所有字段都Parse一下,未免有點太費性能,所以粗淺的認為,不是以{?或者[?開頭的文本都不是JSON*/
          ????????if?(isJson(jp.getText()))?{
          ????????????return?jp.getText();
          ????????}
          ????????return?StringEscapeUtils.escapeHtml4(jp.getText());
          ????}


          ????/**
          ?????*?判斷字符串是不是JSON
          ?????*
          ?????*?@param?str
          ?????*?@return
          ?????*/

          ????private?boolean?isJson(String?str)?{
          ????????boolean?result?=?false;
          ????????if?(StringUtil.isNotBlank(str))?{
          ????????????str?=?str.trim();
          ????????????if?(str.startsWith("{")?&&?str.endsWith("}"))?{
          ????????????????result?=?true;
          ????????????}?else?if?(str.startsWith("[")?&&?str.endsWith("]"))?{
          ????????????????result?=?true;
          ????????????}
          ????????}
          ????????return?result;
          ????}
          }

          但是經(jīng)過這樣的改動之后,可能又沒那么安全了。所以還是要看自己的取舍了。


          對此,你有什么想補充的?歡迎在評論區(qū)留言~
                  
          來源:blog.csdn.net/sinat_31420295/ article/ details/121519010
                                      

          精彩推薦:

          丟棄 HttpClient 了,這款輕量級框架更強!

          SpringBoot接入輕量級分布式日志框架GrayLog實戰(zhàn)!

          現(xiàn)在的公司都這么卷了?自家這么牛逼的統(tǒng)一管理平臺開源了!

          JWT 實現(xiàn)登錄認證 + Token 自動續(xù)期方案,這才是正確的使用姿勢!

          別用XShell了,這款SSH工具絕對驚艷,還支持網(wǎng)頁版.....

          大文件上傳下載實現(xiàn)思路,分片、斷點續(xù)傳代碼實現(xiàn)

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲第一狼区 | 亚洲视频在线观看免费观看 | 三级国产三级在线 | 国产一级免费观看视频 | 国产女子乱伦AAA片 |