前言
最近項目需要做一些軟件測評、軟件安全測試等內容,然後就發現項目上一些漏洞,這次就針對 XSS 攻擊做一個詳細的處理。
一、什麼是 XSS ?
XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使用戶加載並執行攻擊者惡意製造的網頁程序。這些惡意網頁程序通常是JavaScript,但實際上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻擊成功後,攻擊者可能得到包括但不限於更高的權限(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。— 來源於百度百科
二、深入理解 XSS
簡單來說就是這樣的情況,比如我現在有個保存用戶的表單,需要輸入用戶名和密碼,現在我用戶名,我輸入下面這串代碼
<script>alert('1');</script>
如果程序未進行處理,那麼很可能會出現圖片中的情況,彈出一個提示框
當然可能這只是一個小小的惡作劇,一個簡單的彈窗,要是換成cookie的發送呢?就可以用戶在不知情的情況下,獲取到用戶的cookie,會獲取用戶的一系列信息,從而進行其他用戶信息的盜取。
三、防止 XSS 攻擊方案
第一種方法,針對表單傳值的情況
網上看了很多資料,大部分都是這種處理方案
package com.wxw.springboot_xss.common;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;
/**
* @author wuxiongwei
* @date 2020/5/26 9:29
* @Description
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getQueryString() {
return StringEscapeUtils.escapeHtml4(super.getQueryString());
}
@Override
public String getParameter(String name) {
return StringEscapeUtils.escapeHtml4(super.getParameter(name));
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (ArrayUtils.isEmpty(values)) {
return values;
}
int length = values.length;
String[] escapeValues = new String[length];
for (int i = 0; i < length; i++) {
escapeValues[i] = StringEscapeUtils.escapeHtml4(values[i]);
}
return escapeValues;
}
}
package com.wxw.springboot_xss.common;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* @author wuxiongwei
* @date 2020/5/26 9:29
* @Description
*/
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
@Component
public class XssFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest req = (HttpServletRequest) request;
XssHttpServletRequestWrapper xssRequestWrapper = new XssHttpServletRequestWrapper(req);
chain.doFilter(xssRequestWrapper, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
上面這中方法只能對這兩種傳參形式
Content-Type 傳參方式 接收方式
application/x-www-form-urlencoded 表單key-value HttpServletRequest Parameters 獲取
multipart/form-data 表單key-value HttpServletRequest Parameters 獲取
第二種方法,針對前後端分離使用 @RequestBody 接收 json 數據的情況,繼承 WebMvcConfigurationSupport 類
Content-Type 傳參方式 接收方式
application/json json格式文本 HttpServletRequest IO流獲取
繼承 WebMvcConfigurationSupport 類後,重寫extendMessageConverters,修改已經配置好的轉化器列表,遍歷轉化器列表,找到MappingJackson2HttpMessageConverter,可以根據類型來判斷哪個是 MappingJackson2HttpMessageConverter ,然後移除(注意遍歷移除一定要用迭代器),把我們自定義的轉化器添加進去。
package com.wxw.springboot_xss.config;
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 com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* @author wuxiongwei
* @date 2020/5/26 9:29
* @Description
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
/**
* 替換默認的MappingJackson2HttpMessageConverter,過濾(json請求參數)xss
*/
ListIterator<HttpMessageConverter<?>> listIterator = messageConverters.listIterator();
while(listIterator.hasNext()) {
HttpMessageConverter<?> next = listIterator.next();
if(next instanceof MappingJackson2HttpMessageConverter) {
listIterator.remove();
break;
}
}
messageConverters.add(getMappingJackson2HttpMessageConverter());
}
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
// 創建自定義ObjectMapper
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new JsonHtmlXssDeserializer1(String.class));
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.getApplicationContext()).build();
objectMapper.registerModule(module);
// 創建自定義消息轉換器
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
//設置中文編碼格式
List<MediaType> list = new ArrayList<>();
list.add(MediaType.APPLICATION_JSON_UTF8);
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
return mappingJackson2HttpMessageConverter;
}
}
/**
* 對傳入的json進行轉義
*/
class JsonHtmlXssDeserializer1 extends JsonDeserializer<String> {
public JsonHtmlXssDeserializer1(Class<String> string) {
super();
}
@Override
public Class<String> handledType() {
return String.class;
}
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getValueAsString();
if (value != null) {
return StringEscapeUtils.escapeHtml4(value.toString());
}
return value;
}
}
使用此方法之後,在控制層跳轉頁面會出現靜態資源無法訪問的情況(可以自己重寫addResourceHandlers()這個方法來訪問資源文件)。
因爲繼承WebMvcConfigurationSupport 會使原本的 springboot 的自動配置類失效,所以框架幫我們自動配置的就沒了
第三種方法,針對前後端分離使用 @RequestBody 接收 json 數據的情況,實現 WebMvcConfigurer、InitializingBean 類 (建議使用)
Content-Type 傳參方式 接收方式
application/json json格式文本 HttpServletRequest IO流獲取
package com.wxw.springboot_xss.config;
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 com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
/**
* @author wuxiongwei
* @date 2020/5/26 9:29
* @Description
*/
@Configuration
public class WebConfig implements WebMvcConfigurer, InitializingBean {
/**
* 默認就是@Autowired(required=true),表示注入的時候,該bean必須存在,否則就會注入失敗required = false,表示忽略當前要注入的bean,如果有直接注入,沒有跳過,不會報錯
*/
@Autowired(required = false)
private ObjectMapper objectMapper;
private SimpleModule getSimpleModule() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(String.class, new JsonHtmlXssDeserializer(String.class));
return simpleModule;
}
/**
* 初始化bean的時候執行,可以針對某個具體的bean進行配置。afterPropertiesSet 必須實現 InitializingBean接口。實現 InitializingBean接口必須實現afterPropertiesSet方法
* 這個方法將在所有的屬性被初始化後調用,但是會在init前調用
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
if (objectMapper != null) {
SimpleModule simpleModule = getSimpleModule();
objectMapper.registerModule(simpleModule);
}
}
}
/**
* 對入參的json進行轉義
*/
class JsonHtmlXssDeserializer extends JsonDeserializer<String> {
public JsonHtmlXssDeserializer(Class<String> string) {
super();
}
@Override
public Class<String> handledType() {
return String.class;
}
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getValueAsString();
if (value != null) {
return StringEscapeUtils.escapeHtml4(value.toString());
}
return value;
}
}
四、測試防止 XSS 攻擊
測試實體類
package com.wxw.springboot_xss.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author wuxiongwei
* @date 2020/5/26 11:51
* @Description
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {
private String name;
private int age;
}
測試 controller
package com.wxw.springboot_xss.controller;
import com.wxw.springboot_xss.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wuxiongwei
* @date 2020/5/26 11:18
* @Description
*/
@RestController
public class TestController {
@RequestMapping("/test")
public String test(@RequestBody User user){
System.out.println(user.getName());
return user.getName();
}
}
如下圖
使用 postman 模擬 XSS 攻擊測試,請求之後,html 文件會轉換成對應的字符串
至此就大功告成了。