Spring Boot 應(yīng)用如何防護 XSS 攻擊
1. XSS跨站腳本攻擊
①:XSS漏洞介紹
跨站腳本攻擊XSS是指攻擊者往Web頁面里插入惡意Script代碼,當(dāng)用戶瀏覽該頁之時,嵌入其中Web里面的Script代碼會被解析執(zhí)行,從而達到惡意攻擊用戶的目的。XSS攻擊針對的是用戶層面的攻擊!
②:XSS漏洞分類
存儲型XSS:?存儲型XSS,持久化,代碼是存儲在服務(wù)器中的,如在個人信息或發(fā)表文章等地方,插入代碼,如果沒有過濾或過濾不嚴(yán),那么這些代碼將儲存到服務(wù)器中,用戶訪問該頁面的時候觸發(fā)代碼執(zhí)行。這種XSS比較危險,容易造成蠕蟲,盜竊cookie

反射型XSS:?非持久化,需要欺騙用戶自己去點擊鏈接才能觸發(fā)XSS代碼(服務(wù)器中沒有這樣的頁面和內(nèi)容),一般容易出現(xiàn)在搜索頁面

DOM型XSS:?不經(jīng)過后端,DOM-XSS漏洞是基于文檔對象模型(Document Objeet Model
,DOM)的一種漏洞,DOM-XSS是通過url傳入?yún)?shù)去控制觸發(fā)的,其實也屬于反射型XSS。
③:防護建議
限制用戶輸入,表單數(shù)據(jù)規(guī)定值得類型,例如年齡只能是int,name為字母數(shù)字組合。
對數(shù)據(jù)進行html encode處理。
過濾或移除特殊的html標(biāo)簽。
過濾javascript事件的標(biāo)簽。
2. SQL注入攻擊
①:SQL注入漏洞介紹
SQL注入(SQLi)是一種注入攻擊,可以執(zhí)行惡意SQL語句。它通過將任意SQL代碼插入數(shù)據(jù)庫查詢,使攻擊者能夠完全控制Web應(yīng)用程序后面的數(shù)據(jù)庫服務(wù)器。攻擊者可以使用SQL注入漏洞繞過應(yīng)用程序安全措施;可以繞過網(wǎng)頁或Web應(yīng)用程序的身份驗證和授權(quán),并檢索整個SQL數(shù)據(jù)庫的內(nèi)容;還可以使用SQL注入來添加,修改和刪除數(shù)據(jù)庫中的記錄
SQL注入漏洞可能會影響使用SQL數(shù)據(jù)庫(如MySQL,Oracle,SQL Server或其他)的任何網(wǎng)站或Web應(yīng)用程序。犯罪分子可能會利用它來未經(jīng)授權(quán)訪問用戶的敏感數(shù)據(jù):客戶信息,個人數(shù)據(jù),商業(yè)機密,知識產(chǎn)權(quán)等。SQL注入攻擊是最古老,最流行,最危險的Web應(yīng)用程序漏洞之一。
②:防護建議
使用mybatis中#{}
可以有效防止sql注入
使用
#{}
時:
<select?id="getBlogById"?resultType="Blog"?parameterType=”int”>
???????select?id,title,author,content
???????from?blog?where?id=#{id}
</select>
打印出執(zhí)行的sql語句,會看到sql是這樣的:
select?id,title,author,content?from?blog?where?id?=??
不管輸入什么參數(shù),打印出的sql都是這樣的。這是因為mybatis啟用了預(yù)編譯功能,在sql執(zhí)行前,會先將上面的sql發(fā)送給數(shù)據(jù)庫進行編譯,執(zhí)行時,直接使用編譯好的sql,替換占位符“?”就可以了。因為sql注入只能對編譯過程起作用,所以像#{}這樣預(yù)編譯成?的方式就很好地避免了sql注入的問題。
mybatis是如何做到sql預(yù)編譯的呢?
其實在框架底層,是jdbc中的PreparedStatement
類在起作用,PreparedStatement
是我們很熟悉的Statement的子類,它的對象包含了編譯好的sql語句。這種“準(zhǔn)備好”的方式不僅能提高安全性,而且在多次執(zhí)行一個sql時,能夠提高效率,原因是sql已編譯好,再次執(zhí)行時無需再編譯。
使用
${}
時
<select?id="orderBlog"?resultType="Blog"?parameterType=”map”>
???????select?id,title,author,content
???????from?blog?order?by?${orderParam}
</select>
仔細(xì)觀察,內(nèi)聯(lián)參數(shù)的格式由“#{xxx}
”變?yōu)榱?code>${xxx}。如果我們給參數(shù)“orderParam
”賦值為”id”,將sql打印出來,是這樣的:
select?id,title,author,contet?from?blog?order?by?id
顯然,這樣是無法阻止sql注入的,參數(shù)會直接參與sql編譯,從而不能避免注入攻擊。但涉及到動態(tài)表名和列名時,只能使用“${}
”這樣的參數(shù)格式,所以,這樣的參數(shù)需要我們在代碼中手工進行處理來防止注入。
我們創(chuàng)建了一個高質(zhì)量的技術(shù)交流群,與優(yōu)秀的人在一起,自己也會優(yōu)秀起來,趕緊點擊加群,享受一起成長的快樂。
3. SpringBoot中如何防止XSS攻擊和sql注入
話不多說,上代碼
對于Xss攻擊和Sql注入,我們可以通過過濾器來搞定,可根據(jù)業(yè)務(wù)需要排除部分請求
①:創(chuàng)建Xss請求過濾類XssHttpServletRequestWraper

代碼如下:
public?class?XssHttpServletRequestWraper?extends?HttpServletRequestWrapper?{
????Logger?log?=?LoggerFactory.getLogger(this.getClass());
????public?XssHttpServletRequestWraper()?{
????????super(null);
????}
????public?XssHttpServletRequestWraper(HttpServletRequest?httpservletrequest)?{
????????super(httpservletrequest);
????}
?//過濾springmvc中的?@RequestParam?注解中的參數(shù)
????public?String[]?getParameterValues(String?s)?{
????????String?str[]?=?super.getParameterValues(s);
????????if?(str?==?null)?{
????????????return?null;
????????}
????????int?i?=?str.length;
????????String?as1[]?=?new?String[i];
????????for?(int?j?=?0;?j?<?i;?j++)?{
????????????//System.out.println("getParameterValues:"+str[j]);
????????????as1[j]?=?cleanXSS(cleanSQLInject(str[j]));
????????}
????????log.info("XssHttpServletRequestWraper凈化后的請求為:=========="?+?as1);
????????return?as1;
????}
?//過濾request.getParameter的參數(shù)
????public?String?getParameter(String?s)?{
????????String?s1?=?super.getParameter(s);
????????if?(s1?==?null)?{
????????????return?null;
????????}?else?{
????????????String?s2?=?cleanXSS(cleanSQLInject(s1));
????????????log.info("XssHttpServletRequestWraper凈化后的請求為:=========="?+?s2);
????????????return?s2;
????????}
????}
?//過濾請求體?json?格式的
????@Override
????public?ServletInputStream?getInputStream()?throws?IOException?{
????????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?());
????}
????public?String?cleanXSS(String?src)?{
????????String?temp?=?src;
????????src?=?src.replaceAll("<",?"<").replaceAll(">",?">");
????????src?=?src.replaceAll("\\(",?"(").replaceAll("\\)",?")");
????????src?=?src.replaceAll("'",?"'");
????????src?=?src.replaceAll(";",?";");
????????//bgh?2018/05/30??新增
????????/**-----------------------start--------------------------*/
????????src?=?src.replaceAll("<",?"&?lt;").replaceAll(">",?"&?gt;");
????????src?=?src.replaceAll("\\(",?"&?#40;").replaceAll("\\)",?"&?#41");
????????src?=?src.replaceAll("eval\\((.*)\\)",?"");
????????src?=?src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']",?"\"\"");
????????src?=?src.replaceAll("script",?"");
????????src?=?src.replaceAll("link",?"");
????????src?=?src.replaceAll("frame",?"");
????????/**-----------------------end--------------------------*/
????????Pattern?pattern?=?Pattern.compile("(eval\\((.*)\\)|script)",
????????????????Pattern.CASE_INSENSITIVE);
????????Matcher?matcher?=?pattern.matcher(src);
????????src?=?matcher.replaceAll("");
????????pattern?=?Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']",
????????????????Pattern.CASE_INSENSITIVE);
????????matcher?=?pattern.matcher(src);
????????src?=?matcher.replaceAll("\"\"");
????????//?增加腳本
????????src?=?src.replaceAll("script",?"").replaceAll(";",?"")
????????????????/*.replaceAll("\"",?"").replaceAll("@",?"")*/
????????????????.replaceAll("0x0d",?"").replaceAll("0x0a",?"");
????????if?(!temp.equals(src))?{
????????????// System.out.println("輸入信息存在xss攻擊!");
????????????//?System.out.println("原始輸入信息-->"?+?temp);
????????????//?System.out.println("處理后信息-->"?+?src);
????????????log.error("xss攻擊檢查:參數(shù)含有非法攻擊字符,已禁止繼續(xù)訪問!!");
????????????log.error("原始輸入信息-->"?+?temp);
????????????throw?new?CustomerException("xss攻擊檢查:參數(shù)含有非法攻擊字符,已禁止繼續(xù)訪問??!");
????????}
????????return?src;
????}
????//輸出
????public?void?outputMsgByOutputStream(HttpServletResponse?response,?String?msg)?throws?IOException?{
????????ServletOutputStream?outputStream?=?response.getOutputStream();?//獲取輸出流
????????response.setHeader("content-type",?"text/html;charset=UTF-8");?//通過設(shè)置響應(yīng)頭控制瀏覽器以UTF-8的編碼顯示數(shù)據(jù),如果不加這句話,那么瀏覽器顯示的將是亂碼
????????byte[]?dataByteArr?=?msg.getBytes("UTF-8");//?將字符轉(zhuǎn)換成字節(jié)數(shù)組,指定以UTF-8編碼進行轉(zhuǎn)換
????????outputStream.write(dataByteArr);//?使用OutputStream流向客戶端輸出字節(jié)數(shù)組
????}
????//?需要增加通配,過濾大小寫組合
????public?String?cleanSQLInject(String?src)?{
????????String?lowSrc?=?src.toLowerCase();
????????String?temp?=?src;
????????String?lowSrcAfter?=?lowSrc.replaceAll("insert",?"forbidI")
????????????????.replaceAll("select",?"forbidS")
????????????????.replaceAll("update",?"forbidU")
????????????????.replaceAll("delete",?"forbidD").replaceAll("and",?"forbidA")
????????????????.replaceAll("or",?"forbidO");
????????if?(!lowSrcAfter.equals(lowSrc))?{
????????????log.error("sql注入檢查:輸入信息存在SQL攻擊!");
????????????log.error("原始輸入信息-->"?+?temp);
????????????log.error("處理后信息-->"?+?lowSrc);
????????????throw?new?CustomerException("sql注入檢查:參數(shù)含有非法攻擊字符,已禁止繼續(xù)訪問??!");
????????}
????????return?src;
????}
}
②:把請求過濾類XssHttpServletRequestWraper
添加到Filter中,注入容器
@Component
public?class?XssFilter?implements?Filter?{
????Logger?log??=?LoggerFactory.getLogger(this.getClass());
????//?忽略權(quán)限檢查的url地址
????private?final?String[]?excludeUrls?=?new?String[]{
????????????"null"
????};
????public?void?doFilter(ServletRequest?arg0,?ServletResponse?arg1,?FilterChain?arg2)
????????????throws?IOException,?ServletException?{
????????HttpServletRequest?req?=?(HttpServletRequest)?arg0;
????????HttpServletResponse?response?=?(HttpServletResponse)?arg1;
????????String?pathInfo?=?req.getPathInfo()?==?null???""?:?req.getPathInfo();
????????//獲取請求url的后兩層
????????String?url?=?req.getServletPath()?+?pathInfo;
????????//獲取請求你ip后的全部路徑
????????String?uri?=?req.getRequestURI();
????????//注入xss過濾器實例
????????XssHttpServletRequestWraper?reqW?=?new?XssHttpServletRequestWraper(req);
????????//過濾掉不需要的Xss校驗的地址
????????for?(String?str?:?excludeUrls)?{
????????????if?(uri.indexOf(str)?>=?0)?{
????????????????arg2.doFilter(arg0,?response);
????????????????return;
????????????}
????????}
????????//過濾
????????arg2.doFilter(reqW,?response);
????}
????public?void?destroy()?{
????}
????public?void?init(FilterConfig?filterconfig1)?throws?ServletException?{
????}
}
上述代碼已經(jīng)可以完成 請求參數(shù)、JSON請求體 的過濾,但對于json請求體還有其他的方式實現(xiàn),有興趣的請看下面的擴展!
擴展:還可以重寫spring中的MappingJackson2HttpMessageConverter
來過濾Json請求體
因為請求體在進出Contoroller時,會經(jīng)過MappingJackson2HttpMessageConverter
的一個轉(zhuǎn)換,把請求體轉(zhuǎn)換成我們需要的json格式,所以可以在這里邊做一些修改!
@Configuration
public?class?MyConfiguration?{
????@Bean
????public?MappingJackson2HttpMessageConverter?mappingJackson2HttpMessageConverter(){
????????//自定義轉(zhuǎn)換器
????????MappingJackson2HttpMessageConverter?converter?=?new?MappingJackson2HttpMessageConverter();
????????//轉(zhuǎn)換器日期格式設(shè)置
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????SimpleDateFormat?smt?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss");
????????objectMapper.setDateFormat(smt);
????????converter.setObjectMapper(objectMapper);
????????//轉(zhuǎn)換器添加自定義Module擴展,主要是在這里做XSS過濾的??!,其他的是其他業(yè)務(wù),不用看
????????SimpleModule?simpleModule?=?new?SimpleModule();
????????//添加過濾邏輯類!
????????simpleModule.addDeserializer(String.class,new?StringDeserializer());
????????converter.getObjectMapper().registerModule(simpleModule);
????????//設(shè)置中文編碼格式
????????List<MediaType>?list?=?new?ArrayList<>();
????????list.add(MediaType.APPLICATION_JSON_UTF8);
????????converter.setSupportedMediaTypes(list);
????????return?converter;
????}
}
真正的過濾邏輯類StringDeserializer
:
//檢驗請求體的參數(shù)
@Component
public?class?StringDeserializer?extends?JsonDeserializer<String>?{
????@Override
????public?String?deserialize(JsonParser?jsonParser,?DeserializationContext?deserializationContext)?throws?IOException,?JsonProcessingException?{
????????String?str?=?jsonParser.getText().trim();
????????//sql注入攔截
????????if?(sqlInject(str))?{
??????????throw?new?CustomerException("參數(shù)含有非法攻擊字符,已禁止繼續(xù)訪問!");
????????}
????????return?xssClean(str);
????}
????public?boolean?sqlInject(String?str)?{
????????if?(StringUtils.isEmpty(str))?{
????????????return?false;
????????}
????????//去掉'|"|;|\字符
????????str?=?org.apache.commons.lang3.StringUtils.replace(str,?"'",?"");
????????str?=?org.apache.commons.lang3.StringUtils.replace(str,?"\"",?"");
????????str?=?org.apache.commons.lang3.StringUtils.replace(str,?";",?"");
????????str?=?org.apache.commons.lang3.StringUtils.replace(str,?"\\",?"");
????????//轉(zhuǎn)換成小寫
????????str?=?str.toLowerCase();
????????//非法字符
????????String[]?keywords?=?{"master",?"truncate",?"insert",?"select",?"delete",?"update",?"declare",?"alert","alter",?"drop"};
????????//判斷是否包含非法字符
????????for?(String?keyword?:?keywords)?{
????????????if?(str.indexOf(keyword)?!=?-1)?{
????????????????return?true;
????????????}
????????}
????????return?false;
????}
????//xss攻擊攔截
????public?String?xssClean(String?value)?{
????????if?(value?==?null?||?"".equals(value))?{
????????????return?value;
????????}
????????//非法字符
????????String[]?keywords?=?{"<",?">",?"<>",?"()",?")",?"(",?"javascript:",?"script","alter",?"''","'"};
????????//判斷是否包含非法字符
????????for?(String?keyword?:?keywords)?{
????????????if?(value.indexOf(keyword)?!=?-1)?{
???????????????throw?new?CustomerException("參數(shù)含有非法攻擊字符,已禁止繼續(xù)訪問!");
????????????}
????????}
????????return?value;
????}
}
使用這種形式也可以完成json請求體的過濾,但個人更推薦使用XssHttpServletRequestWraper
的形式來完成xss過濾?。?/p>