优化 xss 的代码实现,独立 xss 包

This commit is contained in:
YunaiV 2023-01-25 11:17:52 +08:00
parent c744e115e3
commit 179fdc8464
10 changed files with 83 additions and 76 deletions

View File

@ -2,27 +2,18 @@ package cn.iocoder.yudao.framework.web.config;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService; import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.web.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter; import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@ -34,7 +25,7 @@ import javax.annotation.Resource;
import javax.servlet.Filter; import javax.servlet.Filter;
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties({WebProperties.class, XssProperties.class}) @EnableConfigurationProperties(WebProperties.class)
public class YudaoWebAutoConfiguration implements WebMvcConfigurer { public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
@Resource @Resource
@ -107,15 +98,6 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER); return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
} }
/**
* 创建 XssFilter Bean解决 Xss 安全问题
*/
@Bean
@ConditionalOnBean(XssCleaner.class)
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
}
/** /**
* 创建 DemoFilter Bean演示模式 * 创建 DemoFilter Bean演示模式
*/ */
@ -125,33 +107,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER); return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
} }
public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
/**
* Xss 清理者
*
* @return XssCleaner
*/
@Bean
@ConditionalOnMissingBean(XssCleaner.class)
public XssCleaner xssCleaner() {
return new JsoupXssCleaner();
}
/**
* 注册 Jackson 的序列化器用于处理 json 类型参数的 xss 过滤
*
* @return Jackson2ObjectMapperBuilderCustomizer
*/
@Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤可以替换使用 XssStringJsonSerializer在序列化时进行处理
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
}
private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter); FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(order); bean.setOrder(order);
return bean; return bean;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.web.config; package cn.iocoder.yudao.framework.xss.config;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.xss.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
@AutoConfiguration
@EnableConfigurationProperties(XssProperties.class)
public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
/**
* Xss 清理者
*
* @return XssCleaner
*/
@Bean
@ConditionalOnMissingBean(XssCleaner.class)
public XssCleaner xssCleaner() {
return new JsoupXssCleaner();
}
/**
* 注册 Jackson 的序列化器用于处理 json 类型参数的 xss 过滤
*
* @return Jackson2ObjectMapperBuilderCustomizer
*/
@Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤可以替换使用 XssStringJsonSerializer在序列化时进行处理
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
}
/**
* 创建 XssFilter Bean解决 Xss 安全问题
*/
@Bean
@ConditionalOnBean(XssCleaner.class)
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
}
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.framework.web.core.clean; package cn.iocoder.yudao.framework.xss.core.clean;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist; import org.jsoup.safety.Safelist;
/** /**
* jsonp 过滤字符串 * 基于 JSONP 实现 XSS 过滤字符串
*/ */
public class JsoupXssCleaner implements XssCleaner { public class JsoupXssCleaner implements XssCleaner {
@ -24,21 +24,6 @@ public class JsoupXssCleaner implements XssCleaner {
this.baseUri = ""; this.baseUri = "";
} }
public JsoupXssCleaner(Safelist safelist) {
this.safelist = safelist;
this.baseUri = "";
}
public JsoupXssCleaner(String baseUri) {
this.safelist = buildSafelist();
this.baseUri = baseUri;
}
public JsoupXssCleaner(Safelist safelist, String baseUri) {
this.safelist = safelist;
this.baseUri = baseUri;
}
/** /**
* 构建一个 Xss 清理的 Safelist 规则 * 构建一个 Xss 清理的 Safelist 规则
* 基于 Safelist#relaxed() 的基础上: * 基于 Safelist#relaxed() 的基础上:
@ -67,7 +52,6 @@ public class JsoupXssCleaner implements XssCleaner {
// 虽然可以重写 WhiteList#isSafeAttribute 来处理但是有隐患所以暂时不支持相对路径 // 虽然可以重写 WhiteList#isSafeAttribute 来处理但是有隐患所以暂时不支持相对路径
// WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto"); // WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
// WHITELIST.removeProtocols("img", "src", "http", "https"); // WHITELIST.removeProtocols("img", "src", "http", "https");
return relaxedSafelist; return relaxedSafelist;
} }

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.web.core.clean; package cn.iocoder.yudao.framework.xss.core.clean;
/** /**
* html 文本中的有 Xss 风险的数据进行清理 * html 文本中的有 Xss 风险的数据进行清理
@ -12,4 +12,5 @@ public interface XssCleaner {
* @return 清理后的 html * @return 清理后的 html
*/ */
String clean(String html); String clean(String html);
} }

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.web.core.filter; package cn.iocoder.yudao.framework.xss.core.filter;
import cn.iocoder.yudao.framework.web.config.XssProperties; import cn.iocoder.yudao.framework.xss.config.XssProperties;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner; import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@ -14,8 +14,6 @@ import java.io.IOException;
/** /**
* Xss 过滤器 * Xss 过滤器
* <p>
* Xss 不了解的胖友可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
* *
* @author 芋道源码 * @author 芋道源码
*/ */

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.framework.web.core.filter; package cn.iocoder.yudao.framework.xss.core.filter;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner; import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletRequestWrapper;
@ -13,6 +13,7 @@ import java.util.Map;
* @author 芋道源码 * @author 芋道源码
*/ */
public class XssRequestWrapper extends HttpServletRequestWrapper { public class XssRequestWrapper extends HttpServletRequestWrapper {
private final XssCleaner xssCleaner; private final XssCleaner xssCleaner;
public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) { public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.framework.web.core.json; package cn.iocoder.yudao.framework.xss.core.json;
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner; import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;

View File

@ -0,0 +1,6 @@
/**
* 针对 XSS 的基础封装
*
* XSS 说明https://tech.meituan.com/2018/09/27/fe-security.html
*/
package cn.iocoder.yudao.framework.xss;

View File

@ -2,3 +2,4 @@ cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration