
简介这是一份面向JavaScript初学者与Web安全入门者的轻量级学习资源围绕浏览器Cookie窃取这一经典安全实验场景展开帮助读者理解Cookie在客户端存储与传输中的安全风险适合用于课堂演示、安全课程作业或本地环境下的攻防原理验证。压缩包共7个文件整体约33KB包含3个js脚本、1个json配置、1个html页面、1个md说明文档和1个py服务端脚本分别承担前端逻辑、扩展清单、演示页面、使用说明与本地服务端接收等职责结构紧凑、便于快速阅读与二次修改。目前已有186人学习下载可作为入门参考。读者可从中获得一套可运行的Cookie窃取演示代码结合服务端脚本与扩展配置理解数据回传链路并借助说明文档梳理实验流程与排错思路从而加深对Cookie安全防护要点的认识。1. 从「cookie-stealer」说起一个 JavaScript 工具到底在偷什么打开浏览器开发者工具在 Console 里敲一行document.cookie回车你就能看到当前站点在你机器上留下的那串键值对。很多人第一次意识到「原来 Cookie 就这么明晃晃地躺在 JS 能碰到的地方」也是第一次意识到——只要页面里混进一段第三方脚本它同样能读到这串东西。所谓 cookie-stealer字面意思就是「Cookie 窃取器」一个用 JavaScript 写的、把document.cookie读出来再发到某个接收端的小工具。它不神秘核心逻辑往往不到二十行但它背后牵扯的是前端安全里最经典的一条边界哪些 Cookie 是 JS 能碰的哪些碰不到碰不到的时候攻击者又会怎么绕。这篇文章不教你去偷别人的东西而是把这个工具拆开讲清楚它依赖哪些浏览器机制、HttpOnly和SameSite是怎么把它挡在门外的、一个防御方该怎么自查自己的站点有没有暴露面。适合做前端开发、做安全测试、或者单纯想搞明白「我的登录态到底安不安全」的从业者。读完你应该能自己写一个只在自己站点上跑的检测脚本判断哪些 Cookie 处于风险之中。2. cookie-stealer 的取数链路从 document.cookie 到接收端2.1 为什么document.cookie能读到东西又读不全要理解 cookie-stealer先得理解document.cookie这个 API 的脾气。它返回的是当前文档可见的全部 Cookie格式是keyvalue; key2value2这样一串用分号和空格拼接的字符串。注意两个关键词当前文档和可见。「当前文档」意味着它受同源策略约束。https://a.example.com页面里的脚本读不到https://b.example.com的 Cookie除非两者共享了父域。这就是为什么很多站点会把 Cookie 的 Domain 设成.example.com本意是让子域共享登录态副作用是所有子域都能读到它。「可见」则是最关键的一道闸。服务端在Set-Cookie响应头里加一个HttpOnly标记这个 Cookie 就再也不会出现在document.cookie的返回值里。它是浏览器层面硬性隔离的JS 无论怎么折腾都拿不到。所以一个真实的 cookie-stealer 能偷到的永远只是没有加 HttpOnly 的那部分 Cookie。那为什么还有的偷因为很多站点把会话标识、用户 ID、埋点标识、CSRF token 之类的东西随手写进了非 HttpOnly 的 Cookie 里。这些东西单独看可能不致命但拼起来往往能还原出一个可用的身份。2.2 一个最小可运行的取数与上报实现下面这段代码是 cookie-stealer 类工具的骨架我把它写成只在你自己的测试域名下运行的形式方便你验证自己站点的暴露面。不要把它部署到任何你不拥有或未获授权的站点上。// 仅用于自有站点的安全自查运行前确认 location.hostname 属于你自己 (function () { // 1. 读取当前文档可见的全部 Cookie const raw document.cookie; // 形如 sidabc; uid123; themedark if (!raw) { console.log([check] 当前文档没有 JS 可见的 Cookie); return; } // 2. 解析成键值对方便逐条判断 const pairs raw.split(;).reduce((acc, item) { const idx item.indexOf(); if (idx -1) return acc; const k item.slice(0, idx).trim(); const v item.slice(idx 1).trim(); acc[k] v; return acc; }, {}); // 3. 只上报键名和长度不上报真实值避免自查过程本身泄露 const report Object.keys(pairs).map((k) ({ name: k, length: pairs[k].length, looksSensitive: /sid|session|token|uid|auth/i.test(k), })); console.log([check] JS 可见 Cookie 清单, report); // 4. 上报到自己的接收端自查场景下可以只 console 输出 // fetch(https://your-own-endpoint.example.com/collect, { // method: POST, // headers: { Content-Type: application/json }, // body: JSON.stringify(report), // credentials: omit, // 自查时不要带上 Cookie 本身 // }); })();逻辑说明第一步用document.cookie拿到可见 Cookie 字符串这是整个工具唯一的取数入口。第二步按分号切分并解析成对象注意indexOf()而不是split()因为 value 里本身可能含等号比如 base64 编码的 token 结尾常有。第三步做了一个防御性设计——只上报键名和长度不上报真实值这样即使自查脚本被误放到生产环境也不会把真实凭据发出去。第四步的上报用credentials: omit避免把 Cookie 又带进请求头形成二次泄露。参数说明looksSensitive那个正则是我常用的敏感键名匹配sid、session、token、uid、auth这几个词覆盖了大部分会话类命名习惯你可以按自己团队的命名规范调整。真实攻击工具不会做这些收敛它会把raw整个字符串直接发走这也是为什么防御方看到异常外联请求时payload 里往往是一长串keyvalue。2.3 接收端长什么样以及为什么它总是很短命接收端通常是一个极简的 HTTP 接口收到 POST 就把 body 落库或转发。它短命的原因有两个一是域名很快会被浏览器安全厂商标记二是这类接口的流量特征太明显——一个陌生域名在页面加载后几百毫秒内收到一个携带 Cookie 字符串的 POST几乎必然触发告警。从防御视角看这反而是好事cookie-stealer 的取数依赖document.cookie上报依赖网络请求两个环节都留痕。真正难防的是不落地的内存型窃取但那是另一个话题了。3. HttpOnly、SameSite、Secure三道闸门怎么把 stealer 挡在外面3.1 HttpOnly 的隔离边界与验证方法HttpOnly是抵御 cookie-stealer 最直接的一道闸。它的语义是这个 Cookie 只能通过 HTTP 请求头携带不能通过 JS 访问。浏览器在实现上把它从document.cookie的可见集合里直接剔除所以任何基于document.cookie的 stealer 对它完全无效。验证方法很朴素在服务端给会话 Cookie 加上HttpOnly然后在浏览器 Console 里执行document.cookie看这个键还在不在。不在说明生效了。# 用 curl 查看服务端下发的 Set-Cookie 头确认 HttpOnly 标记 curl -sI https://your-site.example.com/login | grep -i set-cookie # 期望看到类似Set-Cookie: sidxxx; Path/; HttpOnly; Secure; SameSiteLax逻辑说明-I只取响应头grep -i set-cookie过滤出 Cookie 设置行。参数上重点看三个标记HttpOnly挡 JSSecure要求只在 HTTPS 下发送SameSite控制跨站携带行为。三个都齐了这个 Cookie 对 cookie-stealer 基本免疫。提示HttpOnly只挡 JS 读取不挡 CSRF。如果你的站点有 CSRF 风险还需要SameSite或 token 机制配合。3.2 SameSite 的三种取值与跨站场景的取舍SameSite控制的是「跨站请求时要不要带上这个 Cookie」它和 cookie-stealer 的关系在于很多 stealer 的上报请求是跨站的如果 Cookie 设了严格的 SameSite即使被读到跨站外联时也不会自动携带——当然stealer 是主动把值放进 body 里的SameSite 挡不住这种主动外发但它能挡住一类「靠浏览器自动携带」的间接泄露。三种取值的行为差异取值跨站请求是否携带典型适用场景对 stealer 的影响Strict完全不携带高敏感操作、后台管理跨站外联不带 Cookie间接泄露面最小Lax顶层导航的 GET 携带大多数普通站点默认平衡可用性与安全推荐默认值None总是携带需要跨站嵌入的第三方场景必须同时加Secure风险最高选型上我一般这样定登录态 Cookie 用Lax支付、改密这类敏感操作的二次校验 Cookie 用Strict确实需要跨站嵌入的比如被 iframe 引用的组件才用None并且强制Secure。3.3 Secure 与域名作用域的联合配置Secure要求 Cookie 只在 HTTPS 连接中发送。它本身不直接挡 stealer但能防止 Cookie 在明文 HTTP 链路上被中间人截获——这是比 JS 窃取更底层的一种泄露。域名作用域是另一个容易被忽视的点。Domain.example.com会让所有子域共享这个 Cookie包括那些你可能没怎么维护的测试子域、旧版子域。一个子域被 XSS整个父域下的 Cookie 就都暴露了。我的习惯是能不用Domain就不用让 Cookie 默认绑定到当前主机名把作用域收到最小。// 自查列出当前页面可见 Cookie 的键名人工核对哪些本该是 HttpOnly const names document.cookie.split(;).map(s s.split()[0].trim()).filter(Boolean); console.table(names.map(n ({ name: n, shouldBeHttpOnly: /sid|session|token|auth/i.test(n) })));逻辑说明这段脚本只做键名枚举和敏感度标注shouldBeHttpOnly为 true 的项就是你应该去服务端检查是否漏加HttpOnly的候选。参数上正则和 2.2 节保持一致方便对照。4. 从 XSS 到外联cookie-stealer 的常见注入路径与排查4.1 注入点stealer 脚本是怎么进到页面里的cookie-stealer 本身不会凭空出现在页面里它需要一个注入点。最常见的三类第一类是存储型 XSS。用户在评论区、昵称、个人简介里提交的内容没被正确转义被存进数据库其他用户访问时脚本执行。这类注入的 payload 往往就是一段script或img onerror。第二类是第三方脚本供应链。页面引入的统计、广告、客服组件任何一个被篡改或本身带恶意逻辑都能读到document.cookie。这也是为什么 CSP内容安全策略里限制script-src很重要。第三类是浏览器扩展或本地环境被污染。这类不在 Web 层防御范围内但排查时要意识到可能性。4.2 排查清单五条具体的踩坑记录现象一document.cookie返回空字符串但抓包明明看到 Set-Cookie。原因Cookie 加了HttpOnlyJS 读不到或者 Cookie 的Path不匹配当前页面路径。 解决先看响应头的HttpOnly标记再核对Path是否覆盖当前 URL。别急着怀疑代码。现象二本地开发能读到 Cookie部署到线上就读不到。原因线上用了不同域名或子域Cookie 的Domain没覆盖到或者线上强制 HTTPS 而 Cookie 设了Secure本地 HTTP 下不发送。 解决对比两边的Set-Cookie头重点看Domain、Secure、Path三个字段的差异。现象三跨域请求时 Cookie 死活带不上。原因SameSiteLax或Strict挡住了跨站携带或者前端fetch没设credentials: include服务端没设Access-Control-Allow-Credentials: true。 解决确认业务是否真的需要跨站携带需要的话把SameSite调成None并加Secure同时前后端 CORS 配置对齐。这是血泪经验三个条件缺一个都不行。现象四自查脚本上报成功但接收端收到的 Cookie 是旧的。原因浏览器缓存了页面或者 Service Worker 拦截了请求返回了缓存响应。 解决自查时强制刷新并禁用缓存或在请求上加时间戳参数绕过缓存。现象五加了 CSP 之后页面功能异常但 Cookie 确实安全了。原因CSP 的script-src收得太紧把合法的内联脚本也挡了。 解决用 nonce 或 hash 白名单放行必要的内联脚本别为了安全把功能全砍了。CSP 是渐进收紧的过程一次到位往往翻车。4.3 用 CSP 和输出编码把注入点堵死防御 cookie-stealer 的根本不在 Cookie 本身而在不让恶意脚本进到页面。两条主线输出编码所有用户可控内容在渲染到 HTML 前做上下文相关的转义。HTML 上下文转义JS 上下文用JSON.stringify而不是字符串拼接URL 上下文用encodeURIComponent。CSP通过响应头Content-Security-Policy限制脚本来源。一个务实的起步配置是default-src self; script-src self nonce-随机值; object-src none。object-src none能挡掉一类基于插件的注入成本极低。# 检查线上响应头里有没有 CSP curl -sI https://your-site.example.com | grep -i content-security-policy逻辑说明没有输出说明没配 CSP有输出则检查script-src是否包含unsafe-inline——如果包含CSP 对 XSS 的防护基本形同虚设因为内联脚本照样能跑。5. 把检测脚本用起来一个可复用的 Cookie 暴露面自查技巧前面讲了原理和防御最后落到一个我日常真在用的技巧把 Cookie 暴露面自查做成一个只在开发环境启用的浏览器书签脚本每次改完登录逻辑点一下几秒钟就能看出有没有 Cookie 漏加HttpOnly。// 保存为书签URL 填 javascript:((){ ... })() 形式 // 仅在开发/测试环境使用生产环境不要执行 (() { const raw document.cookie; if (!raw) return console.warn(无 JS 可见 Cookie可能全部 HttpOnly良好); const items raw.split(;).map(s { const i s.indexOf(); return { name: s.slice(0, i).trim(), len: s.slice(i 1).trim().length }; }); const risky items.filter(it /sid|session|token|auth|uid/i.test(it.name)); console.group(Cookie 暴露面自查); console.table(items); if (risky.length) { console.error(以下 Cookie 疑似敏感却对 JS 可见请检查 HttpOnly, risky.map(r r.name)); } else { console.log(未发现明显敏感的 JS 可见 Cookie); } console.groupEnd(); })();逻辑说明整段包在 IIFE 里避免污染全局console.group和console.table让输出在 DevTools 里可折叠可排序。risky的判定沿用敏感键名正则命中就报 error 级别方便在控制台一眼看到红色。参数上len只记长度不记值避免把真实凭据打印到控制台被截图带出去——这个习惯我踩过坑早期自查脚本直接把值打出来结果截图发群里等于自己泄露了一遍。这个脚本的价值在于把一次性的安全检查变成可重复的动作。团队里每个人改完认证相关代码点一下书签比写一堆文档让人去记「记得加 HttpOnly」有效得多。它也有边界只能检测 JS 可见的 Cookie检测不了Secure、SameSite、Path这些属性——那些得看响应头。所以我的习惯是书签脚本查可见性curl -I查属性两条腿走路。再补一个进阶用法把敏感键名正则抽成团队共享的配置不同项目按自己的命名规范维护。比如有的团队用jwt而不是token有的用sso_id正则里补上就行。这样自查脚本能跟着项目走而不是每次重新写。说到底cookie-stealer 这类工具能得手靠的从来不是技术多高明而是防御方在某个环节偷了懒——一个 Cookie 忘了加HttpOnly一处输出忘了转义一个第三方脚本没做完整性校验。把这几处守住它就拿你没办法。希望帮到你。本文还有配套的精品资源点击获取