发现 XSS 漏洞?别急,用这招 SpringBoot 3.3 技巧轻松搞定!

开发 前端
我们创建了一个包含完整 XSS 防护的 Spring Boot 应用。自定义注解、过滤器和参数解析器的结合使我们的解决方案灵活且易于扩展。此方案不仅能有效防止 XSS 攻击,还能保证应用的可维护性和代码的整洁度。

在 Web 开发中,XSS(跨站脚本攻击)是一类常见且危险的漏洞。本文将介绍如何在 Spring Boot 3.3 项目中使用自定义注解和过滤器来防止 XSS 攻击,并结合前端使用 Thymeleaf 模板引擎、JavaScript 及 Bootstrap 实现完整的防护方案。首先,让我们了解一下 XSS 攻击的类型、原理及示例。

XSS 攻击类型及原理

XSS 攻击可以分为以下三类:

  1. 存储型 XSS(Stored XSS):攻击者将恶意脚本存储在目标服务器上。例如,通过提交带有恶意脚本的表单,服务器在后续响应中将其返回给客户端并执行。
  2. 反射型 XSS(Reflected XSS):恶意脚本作为请求的一部分被发送到服务器,然后在响应中返回并执行。这种攻击通常通过带有恶意脚本的 URL 来实现。
  3. DOM 型 XSS(DOM-based XSS):攻击者通过修改网页的 DOM 环境(例如 JavaScript 操作 DOM)来执行恶意脚本。这种攻击利用的是客户端环境而非服务器。

运行效果:

图片图片

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

攻击示例

存储型 XSS 示例:

<form action="/submit" method="post">
       <input type="text" name="comment" value="<script>alert('XSS');</script>">
       <button type="submit">Submit</button>
   </form>

反射型 XSS 示例:

http://example.com/search?query=<script>alert('XSS');</script>

DOM 型 XSS 示例:

<div id="content"></div>
   <script>
       var unsafeContent = '<script>alert("XSS");<\/script>';
       document.getElementById('content').innerHTML = unsafeContent;
   </script>

这些攻击利用了网页对用户输入缺乏适当的验证和过滤,从而使得恶意代码得以执行。接下来,本文将介绍如何在 SpringBoot 项目中实现 XSS 防护。

项目配置

首先,我们创建一个 Spring Boot 项目。这里是项目的基本结构和配置:

项目结构:

src
├── main
│   ├── java
│   │   └── com
│   │       └── icoderoad
│   │           └── xss_protection
│   │               ├── XssProtectionApplication.java
│   │               ├── annotation
│   │               │   └── XssProtection.java
│   │               ├── config
│   │               │   └── WebConfig.java
│   │               ├── controller
│   │               │   └── XssController.java
│   │               ├── filter
│   │               │   └── XssFilter.java
│   │               └── util
│   │                   └── XssUtil.java
│   ├── resources
│   │   ├── templates
│   │   │   └── index.html
│   │   ├── application.yml
├── pom.xml
pom.xml 配置
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>xss-protection</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>xss-protection</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-text</artifactId>
		    <version>1.12.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
application.yml 配置
server:
  port: 8080
spring:
  thymeleaf:
    cache: false
logging:
  level:
    root: INFO
    
xss:
  enabled: true
  type: annotation  # 两种处理类型 annotation 或者 filter

实现自定义注解

我们将定义一个自定义注解,用于标记需要进行 XSS 过滤保护的控制器方法参数。

创建注解 @XssProtection
package com.icoderoad.xss_protection.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 用于标记需要 XSS 保护的方法参数
@Documented
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface XssProtection {
}

自定义注解处理

为了确保我们的注解生效,我们需要在控制器方法参数上正确处理 @XssProtection 注解。一个有效的方法是通过自定义参数解析器。

自定义参数解析器 XssRequestParameterResolver
package com.icoderoad.xss_protection.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.icoderoad.xss_protection.annotation.XssProtection;
import com.icoderoad.xss_protection.util.XssUtil;

@Component
public class XssRequestParameterResolver implements HandlerMethodArgumentResolver {

    private static final Logger logger = LoggerFactory.getLogger(XssRequestParameterResolver.class);

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean hasAnnotation = parameter.hasParameterAnnotation(XssProtection.class);
        logger.debug("supportsParameter: {} has annotation: {}", parameter.getParameterName(), hasAnnotation);
        return hasAnnotation;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    	String paramName = parameter.getParameterName();
        String paramValue = webRequest.getParameter(paramName);
        if (paramValue != null) {
            return XssUtil.sanitize(paramValue);
        }
        return null;
    }
}

创建过滤器

接下来,我们实现一个过滤器,读取请求的内容,并进行 XSS 清理。

创建过滤器 XssFilter
package com.icoderoad.xss_protection.filter;

import java.io.IOException;

import com.icoderoad.xss_protection.util.XssUtil;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

public class XssFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        XssHttpServletRequestWrapper xssRequestWrapper = new XssHttpServletRequestWrapper(httpServletRequest);
        chain.doFilter(xssRequestWrapper, response);
    }

    @Override
    public void destroy() {}

    private static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
        public XssHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public String getParameter(String name) {
            String parameter = super.getParameter(name);
            return parameter == null ? null : XssUtil.sanitize(parameter);
        }

        @Override
        public String[] getParameterValues(String name) {
            String[] values = super.getParameterValues(name);
            if (values != null) {
                for (int i = 0; i < values.length; i++) {
                    values[i] = XssUtil.sanitize(values[i]);
                }
            }
            return values;
        }
    }
}

配置过滤器

在 SpringBoot 配置类中注册该过滤器或参数解析器:

配置类 WebConfig
package com.icoderoad.xss_protection.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.icoderoad.xss_protection.filter.XssFilter;

import jakarta.servlet.Filter;

@Configuration
public class WebConfig implements WebMvcConfigurer {

	@Value("${xss.enabled}")
	private boolean xssEnabled;

	@Value("${xss.type}")
	private String xssType;
	
	@Autowired
	private XssRequestParameterResolver xssRequestParameterResolver;


	@Bean
	@ConditionalOnProperty(name = "xss.type", havingValue = "filter")
	public Filter xssFilter() {
		return new XssFilter();
	}
	

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    	if (xssEnabled && "annotation".equalsIgnoreCase(xssType)) {
    		resolvers.add(xssRequestParameterResolver); // 优先级最高
    	}
    }

	@Bean
	@ConditionalOnProperty(name = "xss.type", havingValue = "filter")
	public FilterRegistrationBean<XssFilter> xssFilterRegistrationBean() {
		FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
		registrationBean.setFilter(new XssFilter());
		registrationBean.addUrlPatterns("/*");
		return registrationBean;
	}

}

创建控制器

创建一个简单的控制器来演示我们的 XSS 保护方案:

控制器类 XssController
package com.icoderoad.xss_protection.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.icoderoad.xss_protection.annotation.XssProtection;

@Controller
public class XssController {

    @GetMapping("/")
    public String index() {
        return "index";
    }

    @PostMapping("/submit")
    public String submit( @XssProtection String input, Model model) {
        model.addAttribute("input", input);
        return "index";
    }
}

创建前端页面

创建一个 Thymeleaf 模板页面,用于展示和提交数据。

index.html 页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>XSS 防护示例</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"/>
    <style>    
      body {
        padding-top: 20px;
    }
    </style>
</head>
<body>
<div class="container">
    <h2 class="text-center">XSS 防护示例</h2>
    <form action="/submit" method="post" class="mt-4">
        <div class="form-group">
            <label for="input">请输入文本:</label>
            <input type="text" class="form-control" id="input" name="input" required>
        </div>
        <button type="submit" class="btn btn-primary">提交</button>
    </form>
    <div class="mt-4">
        <h4>提交的文本:</h4>
        <p th:text="${input}"></p>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

代码详细讲解

  1. 自定义注解:注解 @XssProtection 用于标记我们希望保护的控制器参数。此注解没有实际功能,但在参数解析器中将使用它来判断哪些参数需要进行 XSS 过滤。
  2. 过滤器:XssFilter 过滤器会以 XssHttpServletRequestWrapper 包装请求对象。这一包装对象的作用是读取请求体并进行 XSS 清理。我们使用了 Apache Commons Text 提供的方法来转义 HTML 字符,防止恶意脚本注入。
  3. 参数解析器:XssProtectionResolver 自定义参数解析器在控制器方法被调用前处理标记有 @XssProtection 注解的参数。参数解析器使用 StringEscapeUtils.escapeHtml4 方法对参数值进行 HTML 转义,去除潜在的 XSS 攻击向量。
  4. 前端页面:前端页面使用 Thymeleaf 模板引擎,结合 Bootstrap 框架来创建一个简单的展示页面。用户提交的文本会被展示在页面上,且变化后的内容会通过 XssProtectionResolver 进行 XSS 处理后再显示。
  5. 配置类:在配置类中注册自定义过滤器和参数解析器,确保它们在项目启动时生效。
1. 正常文本
输入: Hello World
期望输出: Hello World
说明: 正常文本应保持不变,因为它不包含任何潜在的恶意内容。
2. 简单的 HTML 标签
输入: <b>Hello</b>
期望输出: <b>Hello</b>
说明: 为了防止 HTML 注入,将标签内容转义为实体,确保其不会被浏览器解释为实际的 HTML。
3. JavaScript 注入
输入: <script>alert('XSS');</script>
期望输出: <script>alert('XSS');</script>
说明: 转义 <script> 标签及其内容,防止脚本注入并在浏览器中执行。
4. URL 注入
输入: <a href="http://example.com">Click me</a>
期望输出: <a href="http://example.com">Click me</a>
说明: 链接内容应当被转义,防止恶意链接注入并自动执行。
5. 恶意属性
输入: <img src="x" notallow="alert('XSS')">
期望输出: <img src="x" notallow="alert('XSS')">
说明: 移除或转义潜在的恶意属性,如 onerror,以防止利用属性注入执行恶意代码。

总结

通过上述配置,我们创建了一个包含完整 XSS 防护的 Spring Boot 应用。自定义注解、过滤器和参数解析器的结合使我们的解决方案灵活且易于扩展。此方案不仅能有效防止 XSS 攻击,还能保证应用的可维护性和代码的整洁度。

责任编辑:武晓燕 来源: 路条编程
相关推荐

2010-04-30 15:45:09

2009-09-13 20:28:38

Linq插入数据

2016-03-17 17:35:15

云容器虚拟化管理Docker

2020-05-11 10:59:02

PythonWord工具

2015-01-04 15:36:52

XSS漏洞XSS

2013-08-07 10:00:03

XSS谷歌

2017-05-11 15:01:43

Androidweb布局

2009-12-11 15:37:58

Linux日志处理

2024-05-06 08:56:31

PythonHTML正则表达式

2022-09-16 08:04:25

阿里云权限网络

2021-09-03 08:23:21

Vue 插槽子组件

2023-12-31 09:06:08

2024-07-17 08:29:20

2024-08-29 08:58:30

JPA编写数据操

2009-04-27 11:17:51

网络管理子网划分

2019-03-25 07:27:14

XSS漏洞Ecshop

2020-12-07 16:20:53

Python 开发编程语言

2020-08-23 12:27:39

测试接口技巧

2009-10-23 17:51:51

Oracle用户密码

2010-09-17 14:04:14

JVM内存设置
点赞
收藏

51CTO技术栈公众号