简介一个演示Cookie窃取原理的轻量级JavaScript工具包定位Web安全入门与前端会话管理学习适合安全测试人员、Web开发者及对浏览器存储机制感兴趣的读者用于直观理解Cookie劫持的路径及防御思路。压缩包共7个文件以3个JavaScript脚本为主体分别承担扩展后台逻辑、页面内容注入与Cookie数据回传等职责另有JSON扩展清单用于声明权限HTML页面作为本地演示入口Python服务端脚本模拟远程收集端配合Markdown说明文档便于快速对照阅读整体仅33KB结构紧凑。目前已有186人学习下载。借助完整的浏览器扩展到服务器链路可逐行拆解Cookie被读取、封装、发送的过程看清脚本与服务端的配合方式从而理解缺少适当属性保护时凭证泄露的具体原理并进一步体会HttpOnly、Secure与SameSite等防护机制为何能有效阻断此类攻击。适用于授权环境下的安全实验也可作为Web开发者自查会话安全的小型参考。1. Cookie 读取脚本先搞清楚它到底能拿走什么很多人拿到名字带“stealer”的 JS 脚本第一反应是“这东西能不能偷别人的登录态”。实际上在 2025 年主流浏览器的安全模型下任何纯前端脚本能做的事情都非常有限它读不了 HttpOnly 标记的 Cookie跨域读取被同源策略锁死第三方 Cookie 也在逐步退役。我拆过一个类似的模拟项目 X结论是这类工具真正的价值不在“偷”而在于帮开发者理解 Cookie 的边界——哪些属性真正能拦住脚本哪些属性只是摆设。如果你在做登录态设计、前端安全测试或者调试接口鉴权这份代码值得跑一遍。它解决的问题很具体提供一个基础 JavaScript 脚本在当前域名下枚举并格式化 Cookie 的 key-value、Path、Expires 等属性用来验证站点 Cookie 配置是否符合预期。适合前端工程师、测试人员也能作为安全学习的基础素材。至于配套的坑和边界下面展开讲。2. 拆脚本结构document.cookie 的读取上限与两个致命误用2.1 脚本核心逻辑读、拆、格式化模拟项目 X 的核心代码其实只有几十行核心是围绕document.cookie做字符串解析。不要小看这一步因为document.cookie返回的是一整段分号分隔的字符串而且它不区分 Path、Domain、Expires——脚本需要自己拆。function readCookies() { const raw document.cookie; if (!raw) return { count: 0, cookies: [], raw }; const parts raw.split(;); const cookies parts.map((part) { const idx part.indexOf(); const name part.slice(0, idx).trim(); let value part.slice(idx 1).trim(); // 解码百分号编码 try { value decodeURIComponent(value); } catch (e) { // 解码失败保持原样 } return { name, value }; }); return { count: cookies.length, cookies, raw }; }这段逻辑很直白先判断有没有 Cookie再用 split 切分然后定位第一个等号来分离 name 和 value。注意这里用indexOf()而不是split()因为 value 里可能本身包含等号比如tokenabc123。用indexOf取第一个等号的位置再前后切片能保证 name 干净、value 完整。解码环节容易翻车。document.cookie返回的值是经过 URL 编码的空格会变成%20中文会变%E4%B8%AD%E6%96%87。如果脚本不处理你看到的就是一串十六进制。加 decodeURIComponent 包一层同时用 try/catch 兜底——因为某些非标准编码会直接抛 URIError一旦没接住整个脚本就崩了。这套解析代码放在任何项目中都成立它不依赖框架。如果你想看完整版的脚本文件结构项目包里多数情况会配一个cookie-parser.js和一个test.html前者是纯逻辑后者是演示用页面直接文件协议打开也能跑。2.2 必须理解的限制HttpOnly 与 SameSite 的真实拦截效果读 Cookie 的脚本只能拿到非 HttpOnly 的 Cookie。这是浏览器层面的硬限制没有任何前端脚本能绕过去。很多刚接触的人会以为“用 fetch 配合 credentials 就能拿到头部的 Set-Cookie”这是搞混了两个层面document.cookie只见当前域、当前路径下可见的非 HttpOnly Cookiefetch/xhr 响应头Set-Cookie 能否被 JavaScript 读取取决于响应头是否暴露。默认情况下 CORS 请求的响应头里Set-Cookie 不会出现在 JavaScript 能读到的范围内。所以项目包里的脚本如果你拿到后去读一个登录态 Session大概率读出来是空的——因为正规系统都会给 Session Cookie 打上 HttpOnly 标记。这不是脚本坏了是浏览器安全模型在起作用。SameSite 属性对脚本读取本身没有拦截作用但它决定 Cookie 会不会在“跨站子请求”中被发送。调试的时候同类脚本常见的翻车现场是站点开了 SameSiteStrict脚本在 iframe 里跑结果 Cookie 始终读不到。这是 SameSite 在拦截发送不是脚本逻辑错误。2.3 防御视角为什么这种脚本在很多站点“什么都拿不到”把这类脚本拿到生产环境域名下跑你会得到三种结果全部字段可见、部分字段可见、完全空白。空白的情况大概率是站点全局启用了 HttpOnly脚本属于合法但无用。部分可见的情况更常见用户的个性化偏好、非敏感标记用非 HttpOnly登录态走 HttpOnly。这个现象告诉你一个事实纯前端的 Cookie 读取脚本能做的工作原本就是浏览器默许前端能做的事。它不属于漏洞更多是安全验证工具用来确认站点有没有把敏感的 Cookie 误写成非 HttpOnly。在 A 同学的一次测试里某个模拟电商平台的返利 Cookie 被错误地设成非 HttpOnly导致任意脚本都能读到用户的推广码并构造垃圾请求这个脚本一读就暴露了问题。这就是它的真实应用场景不是偷是体检。3. 实战复现本地搭一个可观测的 Cookie 读写环境3.1 起一个本地 HTTP 服务摆脱 file:// 协议限制刚拿到项目包时很多人直接双击test.html就跑结果发现 Set-Cookie 时灵时不灵。因为浏览器的 Cookie 行为在file://协议下是受限的很多浏览器甚至根本不给文件协议页面写 Cookie 的能力。我建议直接起一个本地静态服务两行命令的事# 进入项目包解压目录 cd cookie-stealer-demo # 起一个 8000 端口的静态服务Python 3 自带 python3 -m http.server 8000然后浏览器访问http://localhost:8000/test.html。逻辑说明Python 的http.server模块会在当前目录起一个只读的静态文件服务默认监听 8000 端口。局域网内其他设备也可以通过http://本机IP:8000访问防火墙放行即可适合在手机上测 Cookie 行为。如果你本机 8000 被别的服务占了换端口加参数python3 -m http.server 8080 --bind 127.0.0.1这里--bind 127.0.0.1表示只允许本机访问安全性更好不会暴露到局域网。3.2 手动种 Cookie验证脚本的解析结果服务起来后我们需要先种几个 Cookie 进去才能测脚本。直接在浏览器控制台执行// 种一个普通的会话级 Cookie document.cookie themedark; path/; // 种一个带过期时间的 Cookie有效期 1 天 const expires new Date(Date.now() 86400 * 1000).toUTCString(); document.cookie preflang_zh; expires${expires}; path/; // 种一个带 SameSite 的 Cookie document.cookie trackingdisabled; path/; samesitestrict;控制台执行完这三行后刷新页面调用脚本的读取函数你应该能看到三组键值对。这里要注意种 Cookie 的时候不需要手动写 Domain浏览器默认会用当前 hostlocalhost如果脚本页面也是 localhost 访问路径匹配就能读到。三个参数说明一下。path/表示全站路径生效如果只写path/admin那么脚本在根路径下是读不到这个 Cookie 的。expires必须是 UTC 格式字符串直接传数字时间戳毫秒不生效很多人在这里翻车。samesitestrict在当前页面测试中影响不大但如果把这个页面嵌到 iframe 里行为就会变。这几行也是后端 Set-Cookie 头的前端等价操作方便你对比。3.3 对照后端 Set-Cookie 的行为差异后端种 Cookie 和前端种 Cookie 有一个关键差异后端可以设置 HttpOnly 和 Secure 属性前端脚本种不了这两个。这是浏览器 API 的硬限制目的是防止 JS 自己伪造安全标记。如果你想真实体验 HttpOnly 的拦截效果需要后端配合。项目包里一般会带一个极简的server.js或者你可以用 Node.js 原生来写const http require(http); http.createServer((req, res) { res.setHeader(Content-Type, text/html; charsetutf-8); if (req.url /set) { // 种一个 HttpOnly Cookie res.setHeader(Set-Cookie, sessionabc123; HttpOnly; Path/); res.end(Cookie set. a href/readRead/a); } else { res.end( script // 这个脚本只能看到非 HttpOnly 字段 document.write(document.cookie || no visible cookie); /script ); } }).listen(8000, () { console.log(server on http://localhost:8000); });跑起来后访问/set种下 HttpOnly 的 session Cookie然后访问根路径/read你会看到document.cookie输出no visible cookie但浏览器 DevTools 里明明有这条记录。这就是 HttpOnly 最直观的拦截效果。注意 Node.js 的http模块不会自动处理 URL 编码种中文值需要手动 encodeURIComponent否则存进去是乱码。4. 避坑指南Cookie 脚本调试中的五个高频翻车点4.1 Path 与 Domain 不一致导致“读不到”现象脚本明明在站内跑document.cookie的输出为空但 DevTools 里能看到 Cookie。原因最常见的两种情况。一种是目标 Cookie 的 Path 写的是/admin而脚本运行的页面在/另一种是 Domain 写成了带点前缀.example.com但你访问的是裸域example.com。解决访问要在 Cookie Domain 匹配的域名下并且运行脚本的页面路径必须落在 Cookie 的 Path 范围内。调试时直接用浏览器 DevTools Application Cookies 查看 Path 和 Domain 两列对照脚本所在 URL 就知道了。我一般会把脚本页放在根路径/cookie-test.html避免路径层级引起误判。4.2 SameSiteLax 的影响被低估现象在本站页面直接打开脚本能读到 Cookie但用户从外部链接跳转过来后第一次访问时某些 Cookie 没出现在请求里。原因Chrome 默认 SameSiteLax跨站顶级导航发送 Cookie 的限制相对宽松但如果是 iframe 嵌入场景Lax 会把绝大多数跨站发送拦下来。你脚本跑在 iframe 里自然读不到。解决确认业务到底需不需要 iframe 场景。如果需要把 Cookie 的 SameSite 改成 None 且加 Secure 属性否则被浏览器拒绝如果不需要让业务方在外部链接跳转的场景单独走一次跳转页。这类脚本调试时优先看当前页面的请求头里有没有 Cookie从而区分是“脚本读不到”还是“请求根本没带”。4.3 过期时间格式错误导致 Cookie 立即失效现象用 JS 种 Cookie 时expires写的是new Date()对象而不是.toUTCString()字符串结果 Cookie 种下去刷新就没了。原因document.cookie的赋值值会被隐式转成字符串new Date()对象会被调toString()输出是类似于Wed Oct 25 2025 10:00:00 GMT0800的格式。浏览器解析这种格式可能直接认为解析失败Cookie 按会话级处理页面关闭即失效。解决稳定做法是new Date(Date.now() 3600 * 1000).toUTCString()得到一个GMT格式字符串。后端 Set-Cookie 头也有同样问题Expires必须是 IMF-fixdate 格式不能用Date对象序列化结果。4.4 误把“读不到”当脚本缺陷现象一套脚本在测试环境正常输出 Cookie切到预发布环境就成了空数组反复换版本没用。原因预发布环境的后端给敏感 Cookie 打了 HttpOnly 标记这是环境配置差异不是代码回归。也可能预发布域名和生产域名不同Cookie Domain 范围对不上。解决先看 DevTools 的 Network 面板某个请求的 Response Header 里 Set-Cookie 带不带上 HttpOnly。带上就放弃治疗不带上才需要排查代码。这类问题的排查顺序是Network 的 Set-Cookie 头 → Application 的 Cookie 列表 → 脚本代码不能被第一反应带偏。4.5 本地 localhost 测试移动端失效现象PC 浏览器里脚本一切正常手机浏览器访问同一个局域网 IP脚本读到的 Cookie 为空。原因Cookie 的 Domain 属性匹配严格手机访问的是http://192.168.x.x:8000如果种 Cookie 时没有显式指定 Domain浏览器默认只把这个 Cookie 绑定到当前 host——脚本在同一个 host 下应该没问题。但部分移动端的默认隐私模式会直接禁掉第三方 Cookie如果你是通过二维码从别的域名跳过来的整个 Cookie 都会被当成第三方处理。解决移动端调试时让手机的起始页直接落在测试域名下不要经过中间跳转。另外关闭浏览器的“阻止第三方 Cookie”选项。我常踩的坑是PC 上开了浏览器自动填充功能种了 Cookie 没清理手机没自动填充导致看到的页面状态完全不一样——这和数据无关是环境差异。5. 加固方案把读 Cookie 脚本升级成一棵自动体检 API默认脚本只能提供“能读”或“不能读”的二分结果但实际排查时需要按严重级别自动提示。我在模拟项目 X 的基础上加了一个分级检测逻辑把脚本从调试工具升级成一个可以写进 CI 的安全体检项。class CookieAuditor { constructor() { this.issues []; } audit() { const { cookies } readCookies(); // 复用上一节封装的函数 for (const cookie of cookies) { // 检查名称是否包含敏感关键词 if (/session|token|auth|sid/i.test(cookie.name)) { this.issues.push({ level: error, name: cookie.name, msg: 敏感命名的 Cookie 被 JS 读取应为 HttpOnly }); } // 检查值是否像高危格式 if (cookie.value.length 16 /[A-Za-z0-9_-]{20,}/.test(cookie.value)) { this.issues.push({ level: warning, name: cookie.name, msg: 长随机值暴露给 JS建议确认是否有业务必要 }); } } return this.issues; } }这套分级逻辑不复杂但很实用按 Cookie 名称的敏感度分级按值的形态判断暴露风险。名称匹配用正则/session|token|auth|sid/i不区分大小写。长度阈值 16 和正则[A-Za-z0-9_-]{20,}判断 20 位以上的随机串——大部分 token 都会落在这个区间但把这个规则接进正式项目时记得把已知的普通偏好 Cookie 名加白名单否则天天误报。你还可以把它跑成一个独立脚本配合无头浏览器来做一个自动化审计任务node audit-cookies.js https://staging.example.com这类工具的输出格式可以做成 JSON方便接 CI 的断言也可以做成文本报告输出到邮件看你的发布流程怎么排。从那以后我每次做前端 Cookie 相关改造都会强制走一遍这个体检先确认敏感项不可见再确认非敏感项没有误伤最后用无头模式跑一遍自动化。这套习惯救过我一次——Spring Session 的默认 Cookie 名从JSESSIONID换成自定义名时正则没覆盖差点把新的 Session 名放行。细节补上之后每次 Cookie 改动都稳当多了。希望这些拆解和踩坑记录能帮你把这份资源真正用起来不只是跑通而是跑明白。本文还有配套的精品资源点击获取