项目被扫描了一个 XSS 漏洞风险,接口代码如下:
@RequestMapping("/redirect")
public void sendRedirect(HttpServletResponse response,
@RequestParam(name = SPRING_SECURITY_FORM_USERNAME_KEY) String userName,
@RequestParam(name = SPRING_SECURITY_FORM_PASSWORD_KEY) String password,
@RequestParam(name = LOGIN_URL) String redirectUrl,
HttpServletRequest request) throws IOException {
//...
}
这个接口接收了 userName, password, redirectUrl 三个参数用于登录重定向。即用户可以在参数中注入恶意代码,从而实现跨站脚本攻击
(XSS)。
例如 构造以下攻击 URL,就会执行对应的 Script 。
.../redirect?redirect_url=&password=&username="><script>alert( "xss")</script>
XSS 漏洞是指攻击者通过在网页中注入恶意代码,使得用户在访问网页时执行这些恶意代码,从而达到攻击的目的。XSS 攻击主要有以下两种形式:
XSS 攻击的危害非常大,攻击者可以通过 XSS 攻击偷取用户的敏感信息(如用户名、密码、银行卡号等)、篡改网页内容、重定向用户到恶意网站等。
@RequestMapping("/redirect")
public void sendRedirect(HttpServletResponse response,
@RequestParam(name = SPRING_SECURITY_FORM_USERNAME_KEY) String userName,
@RequestParam(name = SPRING_SECURITY_FORM_PASSWORD_KEY) String password,
@RequestParam(name = LOGIN_URL) String redirectUrl,
HttpServletRequest request) throws IOException {
// 使用 HtmlUtils 过滤特殊字符
String userName = HtmlUtils.htmlEscape(userName);
String password = HtmlUtils.htmlEscape(password);
String redirectUrl = HtmlUtils.htmlEscape(redirectUrl);
// ...
}
// 对所有输入参数使用Jsoup进行白名单过滤
userName = Jsoup.clean(userName, Whitelist.none());
password = Jsoup.clean(password, Whitelist.none());
redirectUrl = Jsoup.clean(redirectUrl, Whitelist.none());
// ...
装饰者模式
(HttpServletRequestWrapper) + 过滤器 (Filter)
装饰者模式定义: 动态将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。
装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
新建 ParameterRequestWrapper继承 HttpServletRequestWrapper ,并且在 getParameter()和getParameterValues()方法中对参数通过 HtmlUtils.htmlEscape() 进行了过滤转义。
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
public ParameterRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
return HtmlUtils.htmlEscape(value, "UTF-8");
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
escapseValues[i] = HtmlUtils.htmlEscape(values[i], "UTF-8");
}
return escapseValues;
}
@Override
public Enumeration<String> getParameterNames() {
Enumeration<String> enumeration = super.getParameterNames();
List<String> list = new ArrayList<String>();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
list.add(name);
}
return Collections.enumeration(list);
}
}
新建一个名为 xssFilter的过滤器,并将它应用到所有的URL上。在 doFilter() 方法中,我们使用上面创建的 ParameterRequestWrapper对请求进行包装。
@WebFilter(filterName = "xssFilter", urlPatterns = "/*")
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作
}
@Override
public void destroy() {
// 销毁操作
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 对参数进行过滤
HttpServletRequest httpRequest = (HttpServletRequest) request;
ParameterRequestWrapper wrapper = new ParameterRequestWrapper(httpRequest);
chain.doFilter(wrapper, response);
}
}
需要注意的是,这种方式有可能会对一些合法的输入进行误判,例如用户希望输入一些特殊字符或者HTML标签。故有需求时还需根据实际自行更改其中的实现。
拦截器的实现与过滤器类似,不同的是拦截器可以更加细粒度地控制请求的处理过程。
首先,创建一个拦截器类
,该类需要实现HandlerInterceptor接口,并重写preHandle() 方法。preHandle() 方法在请求到达目标 Controller 之前被调用,可以在此方法中进行参数过滤。
public class XssInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key : parameterMap.keySet()) {
String[] values = parameterMap.get(key);
for (int i = 0; i < values.length; i++) {
values[i] = HtmlUtils.htmlEscape(values[i], "UTF-8");
}
request.getParameterMap().put(key, values);
}
return true;
}
}
} 然后,需要在 Spring Boot 应用程序中注册该拦截器。可以通过在配置类中添加 @Bean 注解来创建拦截器实例,并通过addInterceptors() 方法将拦截器添加到WebMvcConfigurer中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public XssInterceptor xssInterceptor() {
return new XssInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(xssInterceptor()).addPathPatterns("/**");
}
}
前后端分离场景下,基本都是通过 @RequestBody 注解接收 application/json 格式的请求体,所以上述 装饰者模式+过滤器 方式不再适用。我们可以直接定义一个全局的 JSON 反序列化器来进行处理。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。