ruoyi-vue-plus-XSS功能
# Xss
XSS是种常见的攻击手段 , 攻击者将可输入的地方写入脚本代码 , 在渲染HTML页面时会加载这些js脚本 , 这么一个过程可以理解成XSS攻击
详解XSS : https://zhuanlan.zhihu.com (opens new window)
ruoyi-vue-plus框架采用以下两种处理方案 :
- 过滤器参数处理过滤
- 自定义Validator注解校验
# 过滤器方案
思路 : 在配置文件中控制指定 过滤路径和排除路径 , 匹配路径的 request(请求上下文) 进行包装处理 , 处理后响应本身 request
涉及文件
名称 | 说明 |
---|---|
XssFilter | XSS过滤器类 |
XssHttpServletRequestWrapper | Xss过滤处理类 |
FilterConfig | 过滤器配置类 |
XssProperties | 配置文件映射类 |
应用 : 在配置文件中配置XSS过滤路径即可实现对请求参数防XSS攻击
源码执行大致流程
- 实例过滤器 , 在 FilterConfig 中加载配置文件填充数据实例化
- 过滤器类加载处理
- XssFilter#init() 初始化时读取过滤器排除路径的参数 , 转为list方便过滤
- XssFilter#doFilter() 过滤时 , 判断排除路径 , 如果是进行包装化处理
- 包装化参数处理
- XssHttpServletRequestWrapper#getParameterValues() 基本参数处理 , 去除所有与HTML相关标签的字段值
- XssHttpServletRequestWrapper#getInputStream() json参数处理 , 读取json字节流并转化为字符串 , 拿到字符串去除HTML标签后转为流响应
# 源码
application.yml
配置文件
点击展开
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
FilterConfig
过滤器配置类
点击展开
@Configuration
public class FilterConfig {
@Autowired
private XssProperties xssProperties;
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", xssProperties.getExcludes());
registration.setInitParameters(initParameters);
return registration;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}
XssFilter
Xss过滤器类
点击展开
public class XssFilter implements Filter {
/**
* 排除链接
*/
public List<String> excludes = new ArrayList<>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes)) {
String[] url = tempExcludes.split(StringUtils.SEPARATOR);
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (handleExcludeURL(req, resp)) {
chain.doFilter(request, response);
return;
}
// 处理请求 request
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
String url = request.getServletPath();
String method = request.getMethod();
// GET DELETE 不过滤
if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {
return true;
}
return StringUtils.matches(url, excludes);
}
@Override
public void destroy() {
}
}
XssHttpServletRequestWrapper
Xss过滤处理包装类
点击展开
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapesValues = new String[length];
for (int i = 0; i < length; i++) {
// 防xss攻击和过滤前后空格
escapesValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
}
return escapesValues;
}
return super.getParameterValues(name);
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 非json类型,直接返回
if (!isJsonRequest()) {
return super.getInputStream();
}
// 为空,直接返回
String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);
if (StringUtils.isEmpty(json)) {
return super.getInputStream();
}
// xss过滤
json = HtmlUtil.cleanHtmlTag(json).trim();
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public int available() throws IOException {
return jsonBytes.length;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
/**
* 是否是Json请求
*/
public boolean isJsonRequest() {
String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
}
# Validator校验方案
Validator注解校验方案能够在请求传参的时校验拦截 , 如果不符合要求则相应异常message(消息)
应用 (写在对象属性即可实现约束)
- 请求参数校验
- 编程式 校验对象 , ValidatorUtils (opens new window) 工具类
# 源码
Xss
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Constraint(validatedBy = {XssValidator.class})
public @interface Xss {
String message() default "不允许任何脚本运行";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
XssValidator
校验类
自定义Validator , 对Xss注解进行自定义校验
public class XssValidator implements ConstraintValidator<Xss, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
// 组合使用hutool工具
return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
}
}