抵御即跨站脚本(XSS)攻击

目录

一、XSS 攻击的危害

XSS 攻击的危害包括:

案例分析:

二、导入依赖库

三、定义请求包装类

四、创建过滤器,把所有请求对象传入包装类

 五、给主类添加注解

六、测试拦截XSS脚本


一、XSS 攻击的危害

什么是 XSS 攻击? 跨站脚本攻击(XSS,Cross-Site Scripting)是指攻击者利用网页的漏洞,将恶意代码注入到网页中,从而在其他用户的浏览器中执行这些恶意代码。

XSS 攻击的危害包括:

  1. 窃取用户信息:例如会话 cookie、登录凭证(如 JWT Token)等。

  2. 伪造身份:攻击者利用窃取的 cookieToken 冒充用户执行操作。

  3. 劫持用户操作:在用户不知情的情况下执行敏感操作,如转账、修改密码。

  4. 网页篡改:动态修改网页内容,展示恶意广告等。

案例分析:

用户在某个网站发帖时输入以下代码:

<script>alert('1234')</script>

        如果服务器直接将该内容存储到数据库,且在页面渲染时未做任何过滤处理,浏览器就会直接执行这段代码。这不仅会触发弹窗,还可能执行恶意的 JavaScript,如窃取 cookie 或令牌并发送到攻击者的服务器。


二、导入依赖库

 
        因为 Hutool 工具包带有XSS转义的工具类,所以我们要导入Hutool,然后利用 Servlet 规范提供的请求包装类,定义数据转义功能。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.0</version>
</dependency>

三、定义请求包装类

        在 Web 项目中,`HttpServletRequest` 是一个接口,如果我们想自定义请求类,直接实现这个接口并不实际。因为接口中有大量抽象方法需要实现,过程繁琐且耗时。更简便的方式是继承 `HttpServletRequestWrapper` 类

        `HttpServletRequestWrapper` 是 JavaEE 规范中定义的请求包装类,采用了装饰器模式。无论各个应用服务器(如 Tomcat)如何实现 `HttpServletRequest` 接口,用户只需继承 `HttpServletRequestWrapper`,覆盖所需方法即可。通过包装类,用户可以灵活地修改请求方法,而无需关心接口的具体实现。这种方式有效解耦了用户代码与服务器实现代码,既简化了操作,又增强了代码的扩展性。

package com.example.emos.wx.config.xss;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

// 自定义 HttpServletRequestWrapper 用于 XSS 过滤
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    // 构造函数,接收 HttpServletRequest 对象
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * 重写 getParameter 方法,过滤单个请求参数的值
     * @param name 参数名
     * @return 过滤后的参数值
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name); // 调用父类方法获取参数值
        if (value != null) {
            value = HtmlUtil.filter(value); // 使用 Hutool 工具类进行 XSS 过滤
        }
        return value;
    }

    /**
     * 重写 getParameterValues 方法,过滤数组形式的请求参数值
     * @param name 参数名
     * @return 过滤后的参数值数组
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name); // 调用父类方法获取参数值数组
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                values[i] = HtmlUtil.filter(values[i]); // 逐一过滤参数值
            }
        }
        return values;
    }

    /**
     * 重写 getParameterMap 方法,过滤请求参数的键值对
     * @return 过滤后的参数映射
     */
    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> parameters = super.getParameterMap(); // 获取原始参数映射
        Map<String, String[]> map = new LinkedHashMap<>(); // 用 LinkedHashMap 保证顺序
        if (parameters != null) {
            for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
                String key = entry.getKey(); // 获取参数名
                String[] values = entry.getValue(); // 获取参数值数组
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        if (values[i] != null && !StrUtil.hasEmpty(values[i])) {
                            // 过滤非空参数值
                            values[i] = HtmlUtil.filter(values[i]);
                        }
                    }
                }
                map.put(key, values); // 放入过滤后的映射
            }
        }
        return map;
    }

    /**
     * 重写 getHeader 方法,过滤请求头的值
     * @param name 请求头名
     * @return 过滤后的请求头值
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name); // 调用父类方法获取请求头值
        if (value != null) {
            value = HtmlUtil.filter(value); // 过滤请求头值
        }
        return value;
    }

    /**
     * 重写 getInputStream 方法,处理请求体的内容
     * @return 包含过滤后内容的 ServletInputStream
     * @throws IOException 异常
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 获取原始请求输入流
        ServletInputStream inputStream = super.getInputStream();

        // 读取输入流内容并存入 StringBuffer
        StringBuffer body = new StringBuffer();
        InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line = bufferedReader.readLine();
        while (line != null) {
            body.append(line); // 将每行追加到 body 中
            line = bufferedReader.readLine();
        }

        // 关闭流以释放资源
        reader.close();
        bufferedReader.close();
        inputStream.close();

        // 将请求体内容解析为 Map 对象
        Map<String, Object> map = JSONUtil.parseObj(body.toString());
        Map<String, Object> resultMap = new HashMap<>(map.size());
        for (String key : map.keySet()) {
            Object val = map.get(key);
            if (val instanceof String) {
                // 对 String 类型的值进行 XSS 过滤
                resultMap.put(key, HtmlUtil.filter((String) val));
            } else {
                // 非 String 类型直接放入
                resultMap.put(key, val);
            }
        }

        // 将处理后的 Map 转回 JSON 字符串
        String jsonStr = JSONUtil.toJsonStr(resultMap);

        // 构造新的输入流以返回
        final ByteArrayInputStream bain = new ByteArrayInputStream(jsonStr.getBytes());
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bain.read(); // 从字节数组流中读取数据
            }

            @Override
            public boolean isFinished() {
                return false; // 表示未完成(可以根据实际需要调整逻辑)
            }

            @Override
            public boolean isReady() {
                return false; // 表示未准备好(可以根据实际需要调整逻辑)
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // 空实现
            }
        };
    }
}

四、创建过滤器,把所有请求对象传入包装类

package com.example.emos.wx.config.xss;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Xss 是一个过滤器类,用于防止 XSS 攻击。
 * 通过使用自定义的 HttpServletRequestWrapper(XssHttpServletRequestWrapper)
 * 对请求内容进行过滤。
 */
@WebFilter(urlPatterns = "/*") // 声明过滤器,应用于所有 URL
public class Xss implements Filter {

    /**
     * 初始化方法,当过滤器实例化时调用。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 调用父类的 init 方法(可选操作)
        Filter.super.init(filterConfig);
    }

    /**
     * 核心过滤逻辑:将所有 HTTP 请求包装为 XssHttpServletRequestWrapper,
     * 并将其传递到过滤链的下一环节。
     * @param servletRequest 原始请求对象
     * @param servletResponse 原始响应对象
     * @param filterChain 过滤器链
     * @throws IOException IO 异常
     * @throws ServletException Servlet 异常
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
            throws IOException, ServletException {
        // 将原始 ServletRequest 强制转换为 HttpServletRequest
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        // 使用自定义的 XssHttpServletRequestWrapper 包装请求
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request);

        // 将包装后的请求传递到过滤链的下一环节
        filterChain.doFilter(xssRequest, servletResponse);
    }

    /**
     * 销毁方法,当过滤器被卸载时调用。
     */
    @Override
    public void destroy() {
        // 调用父类的 destroy 方法(可选操作)
        Filter.super.destroy();
    }
}

 五、给主类添加注解

 
给SpringBoot主类添加

@ServletComponentScan

注解。


六、测试拦截XSS脚本


1.在Swagger中,执行 sayHello()方法,向name属性传入<script>HelloWorld</script>,然后观察返回的结果。

### 跨站脚本攻击XSS)事件注入的工作原理 跨站脚本攻击XSS)中的事件注入是指攻击者利用HTML标签内的事件处理属性来触发恶意JavaScript代码的执行。这类攻击通常发生在输入未被充分验证或转义的情况下,允许攻击者将带有事件处理器的HTML标签插入到页面中。 例如,考虑一个简单的HTML表单字段,如果应用程序未能正确过滤用户提交的数据,则可能接受如下形式的输入: ```html <input type="text" onfocus="javascript:alert('XSS')" /> ``` 当此输入保存并显示给其他用户时,`onfocus`事件会在任何用户聚焦于该输入框时触发警报对话框[^1]。这只是一个简单示例;实际攻击可能会更加复杂和有害,比如收集用户的Cookie或其他敏感数据。 ### 防御措施 针对此类攻击的有效预防策略包括但不限于以下几个方面: #### 输入验证与清理 确保所有来自客户端的数据都经过严格的验证和清理过程,移除潜在危险字符以及防止非法内容进入服务器端逻辑或者数据库存储层。 #### 输出编码 对于动态生成的内容,在将其呈现回浏览器之前应进行适当的HTML实体化转换,这样即使存在恶意字符串也无法被执行为有效载荷。 #### HTTP头设置 使用特定HTTP响应头部如Content Security Policy (CSP),它能够定义哪些资源是可以加载和执行的,从而进一步减少成功实施XSS的风险。 #### 使用框架自带的安全特性 现代Web开发框架往往内置了许多用于抵御各种类型的攻击的功能,默认启用这些选项可以帮助减轻开发者手动实现额外安全性的负担。 ```python from flask import Flask, escape app = Flask(__name__) @app.route('/') def index(): user_input = "<script>alert('XSS')</script>" safe_output = escape(user_input) return f"<p>{safe_output}</p>" ``` 上述Python代码片段展示了如何通过Flask库提供的`escape()`函数自动对特殊字符进行转义处理,以防范反射型XSS风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值