当前位置: 首页 > news >正文

【某数WAF 动态Cookie实战】

某数WAF 动态Cookie绕过

    • JS RPC与MITM协同实战 🛡️
      • 缘起:动态Cookie的挑战 🤔
      • 核心思路:JSRPC + MITM 💡
      • 实现细节 🛠️
        • 1. 浏览器控制台注入脚本1:Cookie捕获与展示
        • 2. 浏览器控制台注入脚本2:JS RPC 客户端 (Hlclient)
        • 3. MITM 替换脚本 (Python with mitmproxy)
      • 部署与使用步骤 🚀
      • 总结与展望 🌟

JS RPC与MITM协同实战 🛡️

在现代Web渗透测试中,动态刷新的Cookie和前端JavaScript混淆是常见的棘手问题。前者使得传统的请求重放变得困难,后者则增加了分析客户端逻辑的复杂度。本文将介绍一种结合浏览器端脚本注入、JavaScript RPC (JS RPC)以及中间人代理 (MITM) 技术的组合拳,有效应对此类挑战,并实现对关键数据包中Cookie的动态替换。

缘起:动态Cookie的挑战 🤔

在对某网站进行安全测试时,遇到了一个典型场景:

  1. Cookie实时刷新:用户的每次关键点击或交互都会导致服务器下发新的Cookie值。这意味着之前捕获的包含旧Cookie的数据包一旦重放,就会因为Cookie失效而请求失败。
  2. 前端JS混淆:网站的前端JavaScript代码经过了高度混淆,直接分析JS逻辑以理解Cookie生成和更新机制变得非常耗时且低效。

为了能够顺利进行渗透测试,需要一种方法来获取浏览器当前最新的Cookie,并在重放数据包时将其动态替换进去。传统的手动复制粘贴显然无法满足自动化测试的需求。

核心思路:JSRPC + MITM 💡

解决方案围绕以下三个核心组件构建:

  1. 浏览器端脚本注入 (Browser-Side Scripting):在浏览器控制台注入JS脚本,用于劫持Cookie的设置 (document.cookie) 和页面跳转相关API。当Cookie更新时,脚本捕获最新的Cookie值,并提供一种机制供外部调用。
  2. JavaScript RPC (JS RPC):利用JS RPC框架( jxhczhl/JsRpc ),在浏览器端运行一个RPC客户端,它通过WebSocket与本地启动的RPC服务端通信。这允许外部程序(如MITM脚本)远程调用浏览器环境中的JS函数。
  3. MITM代理脚本 (Man-in-the-Middle Proxy):使用mitmproxy等工具,编写Python脚本拦截流经代理的HTTP请求。当检测到目标站点的请求时,MITM脚本通过JS RPC从浏览器获取最新的Cookie,然后替换请求头中的旧Cookie,再将请求发往服务器。

调用关系图 (Mermaid):
在这里插入图片描述

实现细节 🛠️

1. 浏览器控制台注入脚本1:Cookie捕获与展示

此脚本的核心功能是:

  • 劫持 document.cookie 的setter:当网站JS设置Cookie时,脚本1会先捕获到这个值。
  • 提取纯净Cookie:通常document.cookie = "key=value; path=/; domain=..."这样设置,其实只用只关心key=value部分。
  • 存储最新Cookie:将提取到的纯净Cookie存入全局变量 window.cookieValueToDisplay,方便后续RPC调用。
  • 原始Cookie设置:通过创建一个临时的iframe来确保原始的Cookie设置逻辑仍然执行,避免破坏网站正常功能。
  • 劫持导航API:覆盖window.location.assign, window.location.replace, window.location.href的setter, HTMLFormElement.prototype.submit以及window.open,在页面即将跳转前强制弹出Cookie展示框,确保在跳转导致Cookie可能变化或丢失前能捕获到最后的值。
  • 自定义弹窗:创建一个浮层显示捕获到的Cookie,并提供点击复制功能。这里使用了navigator.clipboard.writeText进行无感复制。
(function() {const originalCookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');window.cookieValueToDisplay = ''; // 用于存储最新的 Cookie 值// --- 辅助函数:复制到剪贴板 (不带弹窗) ---async function copyToClipboard(text) {try {await navigator.clipboard.writeText(text);console.log('%c[Cookie Catcher] Cookie 已复制到剪贴板。', 'color: #4CAF50;');} catch (err) {console.error('%c[Cookie Catcher] 无法复制到剪贴板:', 'color: #ff0000;', err);// Fallback for older browsers or if Clipboard API failsconst textarea = document.getElementById('cookieTextarea');if (textarea) {textarea.select();try {document.execCommand('copy');console.log('%c[Cookie Catcher] Cookie 已复制到剪贴板 (使用 fallback 方式)。', 'color: #4CAF50;');} catch (execErr) {console.error('%c[Cookie Catcher] 无法复制到剪贴板。请手动复制。', 'color: #ff0000;');}} else {console.error('%c[Cookie Catcher] 无法复制到剪贴板。请手动复制。', 'color: #ff0000;');}}}// --- 辅助函数:提取纯净的 Cookie 值 ---function extractPureCookie(cookieString) {// 使用正则表达式匹配第一个分号(;)之前的内容,或者整个字符串如果没有分号const match = cookieString.match(/^([^;]+)/);return match ? match[1].trim() : cookieString.trim();}// --- 劫持 document.cookie setter ---Object.defineProperty(document, 'cookie', {configurable: true,enumerable: true,get: function() {return originalCookieDescriptor.get.call(this);},set: function(val) {console.log('%c[Cookie Catcher] Detected cookie set:', 'color: #1a73e8; font-weight: bold;', val);const pureCookie = extractPureCookie(val); // 提取纯净的 CookiecookieValueToDisplay = pureCookie; // 更新要显示的 Cookie 值// 1. 确保原始的 cookie 仍然被设置 (使用 iframe 方式)try {const existingTempDiv = document.getElementById('tempCookieSetterDiv');if (existingTempDiv) {document.body.removeChild(existingTempDiv);}var tempDiv = document.createElement('div');tempDiv.id = 'tempCookieSetterDiv';tempDiv.style.display = 'none';document.body.appendChild(tempDiv);// 为了确保原始 cookie 属性被设置,这里仍然使用原始的 valconst escapedVal = val.replace(/\\/g, '\\\\').replace(/'/g, "\\'");tempDiv.innerHTML = `<iframe srcdoc="<script>document.cookie='${escapedVal}';</script>"></iframe>`;setTimeout(() => {if (document.body.contains(tempDiv)) {document.body.removeChild(tempDiv);}}, 100);} catch (e) {console.error('%c[Cookie Catcher] Error setting original cookie via iframe:', 'color: #ff0000;', e);}// !!! 当 Cookie 被设置时,立即尝试显示弹窗 !!!showCookiePopup(cookieValueToDisplay);}});// --- 劫持页面跳转相关 API ---// 1. 劫持 window.location.assign / replace 方法const originalLocationAssign = window.location.assign;const originalLocationReplace = window.location.replace;window.location.assign = function(url) {console.warn('%c[Cookie Catcher] Location.assign detected! Value:', 'color: #ff9800;', url);showCookiePopup(cookieValueToDisplay); // 强制弹窗originalLocationAssign.call(this, url); // 继续原始跳转};window.location.replace = function(url) {console.warn('%c[Cookie Catcher] Location.replace detected! Value:', 'color: #ff9800;', url);showCookiePopup(cookieValueToDisplay); // 强制弹窗originalLocationReplace.call(this, url); // 继续原始跳转};// 2. 劫持 window.location.href 的赋值操作try {const originalHrefDescriptor = Object.getOwnPropertyDescriptor(window.location, 'href');if (originalHrefDescriptor && originalHrefDescriptor.set) {Object.defineProperty(window.location, 'href', {configurable: true,enumerable: true,get: function() {return originalHrefDescriptor.get.call(this);},set: function(url) {console.warn('%c[Cookie Catcher] Location.href set detected! Value:', 'color: #ff9800;', url);showCookiePopup(cookieValueToDisplay); // 强制弹窗originalHrefDescriptor.set.call(this, url); // 继续原始跳转}});} else {console.warn('%c[Cookie Catcher] Could not successfully hook window.location.href setter.', 'color: #ff9800;');}} catch (e) {console.error('%c[Cookie Catcher] Error hooking window.location.href:', 'color: #ff0000;', e);}// 3. 劫持 form.submit() (提交表单也可能导致跳转)const originalFormSubmit = HTMLFormElement.prototype.submit;HTMLFormElement.prototype.submit = function() {console.warn('%c[Cookie Catcher] Form submission detected!', 'color: #ff9800;', this);showCookiePopup(cookieValueToDisplay); // 强制弹窗originalFormSubmit.call(this); // 继续原始提交};// 4. 劫持 window.open() (如果目标是当前页面,会跳转)const originalWindowOpen = window.open;window.open = function(url, name, features) {console.warn('%c[Cookie Catcher] Window.open detected! Value:', 'color: #ff9800;', url);// 如果 window.open 目标是 "_self" 或未指定且是当前窗口,则会跳转if (name === '_self' || !name || name === window.name) {showCookiePopup(cookieValueToDisplay); // 强制弹窗}return originalWindowOpen.apply(this, arguments);};// --- 创建自定义弹窗的函数 ---function showCookiePopup(cookieString) {const oldPopup = document.getElementById('cookieCatcherPopup');if (oldPopup) {// 如果已有弹窗,只更新内容,不重新创建document.getElementById('cookieTextarea').value = cookieString;document.getElementById('cookiePopupTitle').textContent = '最新捕获的 Cookie (更新)';return;}const popup = document.createElement('div');popup.id = 'cookieCatcherPopup';popup.style.cssText = `position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: #f8f8f8;border: 2px solid #333;padding: 20px;box-shadow: 0 4px 8px rgba(0,0,0,0.2);z-index: 99999;font-family: monospace;max-width: 80vw;max-height: 80vh;overflow: auto;word-break: break-all;display: flex;flex-direction: column;gap: 10px;border-radius: 8px;pointer-events: all; /* 确保可以点击和复制 */`;const title = document.createElement('h3');title.id = 'cookiePopupTitle';title.textContent = '最新捕获的 Cookie';title.style.cssText = `margin: 0 0 10px 0;color: #333;font-size: 1.2em;`;const textarea = document.createElement('textarea');textarea.id = 'cookieTextarea';textarea.value = cookieString;textarea.rows = Math.min(10, Math.max(3, Math.ceil(cookieString.length / 80)));textarea.readOnly = true;textarea.style.cssText = `width: calc(100% - 4px);flex-grow: 1;padding: 10px;border: 1px solid #ccc;resize: vertical;font-size: 0.9em;background-color: #fff;color: #000;cursor: text;`;// 使用新的复制函数,不再弹窗textarea.onclick = function() {this.select(); // 仍然选中以便用户手动复制copyToClipboard(this.value);};const copyButton = document.createElement('button');copyButton.textContent = '点击此处或文本框复制';copyButton.style.cssText = `padding: 8px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 1em;margin-top: 10px;`;copyButton.id = 'copyButtonme';// 使用新的复制函数,不再弹窗copyButton.onclick = function() {const textareaValue = document.getElementById('cookieTextarea').value;copyToClipboard(textareaValue);};const closeButton = document.createElement('button');closeButton.textContent = '关闭';closeButton.style.cssText = `padding: 8px 15px;background-color: #f44336;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 1em;`;closeButton.onclick = function() {document.body.removeChild(popup);};popup.appendChild(title);popup.appendChild(textarea);popup.appendChild(copyButton);popup.appendChild(closeButton);document.body.appendChild(popup);textarea.focus();textarea.select();}console.log('%c[Cookie Catcher] Cookie setter and navigation APIs hooked. Waiting for updates...', 'color: #2196F3; font-weight: bold;');
})();for (let i = 0; i < 115; i++) {const copyButton = document.getElementById('cookieCatcherPopup').querySelector('button');if (copyButton) {copyButton.click();console.log(`${i + 1} 次模拟点击复制按钮`);// 每次点击后,浏览器可能会显示一个“已复制到剪贴板!”的alert,您需要手动关闭它才能继续下一次点击。// 在实际自动化中,会使用更高级的方法来处理alert,但此处是手动模拟。}
}
2. 浏览器控制台注入脚本2:JS RPC 客户端 (Hlclient)

此脚本在浏览器端建立一个WebSocket连接到本地的JS RPC服务端,并注册可供远程调用的函数。

  • Hlclient(wsURL)构造函数:初始化WebSocket连接。
  • connect()方法:负责建立和重连WebSocket。
  • regAction(func_name, func)方法:注册一个动作。当RPC服务端收到特定action的请求时,会调用浏览器端注册的对应func
  • handlerRequest(requestJson)方法:处理从服务端通过WebSocket收到的消息,解析JSON,找到对应的action处理器并执行。
  • sendResult(action, e)方法:将执行结果通过WebSocket回传给服务端。
  • transjson(formdata)函数:一个辅助函数,用于处理可能非标准的JSON字符串。
  • 关键注册demo.regAction("getData", function (resolve, param){ eval(param); res=window.cookieValueToDisplay; resolve(res);})
    • 注册了一个名为 getData 的动作。
    • 当被调用时,它首先会执行传入的 param 字符串(通过 eval(param))。这提供了一定的灵活性,例如param可以是"document.getElementById('copyButtonme').click()"来模拟点击,或者其他需要在获取Cookie前执行的JS代码。
    • 然后,它获取全局变量 window.cookieValueToDisplay(由脚本1更新)的值。
    • 最后,通过 resolve(res) 将Cookie值返回给RPC服务端。
function Hlclient(wsURL) {// 检查传入的wsURL是否为空if (!wsURL) { throw new Error('wsURL can not be empty!!'); }this.wsURL = wsURL;this.handlers = {_execjs: function (resolve, param) {// 使用eval执行传入的JavaScript字符串var res = eval(param);// 如果没有返回值,则返回默认消息if (!res) { resolve("没有返回值"); } else { resolve(res); }}};this.socket = undefined;// 初始化连接this.connect();
}Hlclient.prototype.connect = function () {console.log('begin of connect to wsURL: ' + this.wsURL);var _this = this;try {// 创建WebSocket实例并绑定事件处理函数this.socket = new WebSocket(this.wsURL);this.socket.onmessage = function (e) { _this.handlerRequest(e.data); };} catch (e) {console.log("connection failed, reconnect after 10s");// 连接失败后,延迟10秒重试setTimeout(function () { _this.connect(); }, 10000);}// 监听socket关闭事件,尝试重新连接this.socket.onclose = function () { console.log('rpc已关闭');setTimeout(function () { _this.connect(); }, 10000);};// 添加open和error事件监听器this.socket.addEventListener('open', (event) => { console.log("rpc连接成功"); });this.socket.addEventListener('error', (event) => { console.error('rpc连接出错,请检查是否打开服务端:', event.error); });
};// 发送消息到服务器
Hlclient.prototype.send = function (msg) {this.socket.send(msg);
};// 注册新的动作处理器
Hlclient.prototype.regAction = function (func_name, func) {if (typeof func_name !== 'string') { throw new Error("an func_name must be string"); }if (typeof func !== 'function') { throw new Error("must be function"); }console.log("register func_name: " + func_name);this.handlers[func_name] = func;return true;
};// 处理从服务器收到的消息
Hlclient.prototype.handlerRequest = function (requestJson) {var _this = this;try {var result = JSON.parse(requestJson);} catch (error) {console.log("catch error", requestJson);result = transjson(requestJson);}if (!result['action']) {this.sendResult('', 'need request param {action}');return;}var action = result["action"];var theHandler = this.handlers[action];if (!theHandler) {this.sendResult(action, 'action not found');return;}try {if (!result["param"]) {theHandler(function (response) {_this.sendResult(action, response);});return;}var param = result["param"];try {param = JSON.parse(param);} catch (e) {}theHandler(function (response) {_this.sendResult(action, response);}, param);} catch (e) { console.log("error: " + e); _this.sendResult(action, e); }
};// 发送结果回服务器
Hlclient.prototype.sendResult = function (action, e) {if (typeof e === 'object' && e !== null) {try { e = JSON.stringify(e); } catch (v) {console.log(v); // 不是JSON无需操作}}this.send(action + atob("aGxeX14") + e);
};// 将非标准JSON字符串转换为对象
function transjson(formdata) {var regex = /"action":(?<actionName>.*?),/g;var actionName = regex.exec(formdata).groups.actionName;stringfystring = formdata.match(/{..data..:.*..\w+..:\s...*?..}/g).pop();stringfystring = stringfystring.replace(/\\"/g, '"');paramstring = JSON.parse(stringfystring);tens = `{"action":` + actionName + `,"param":{}}`;tjson = JSON.parse(tens);tjson.param = paramstring;return tjson;
}// 示例:创建一个Hlclient实例
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");
demo.regAction("getData",function (resolve,param){//document.getElementById('copyButtonme').click(); eval(param);   res=window.cookieValueToDisplay;resolve(res);})
3. MITM 替换脚本 (Python with mitmproxy)

此Python脚本配合mitmproxy工具使用,拦截HTTP请求,并通过JS RPC获取最新Cookie来替换请求头。

  • TARGET_HOST: 定义目标网站域名。
  • STATIC_EXTENSIONS: 定义静态资源后缀,避免对这些资源也进行Cookie替换处理。
  • rpc() 函数:
    • 向本地JS RPC服务端 (例如 http://localhost:12080/go) 发起HTTP GET请求。
    • 请求参数中指定了group, name, action (“getData”) 和 param。这里的param是希望在浏览器RPC客户端执行的JS代码字符串,例如 document.getElementById('copyButtonme').click()
    • JS RPC服务端会将此actionparam通过WebSocket转发给浏览器端的Hlclient
    • Hlclient执行后返回Cookie,rpc()函数最终返回这个Cookie。
  • is_static_resource() 函数: 判断请求是否为静态资源。
  • request(flow: http.HTTPFlow) 函数: mitmproxy的核心处理函数。
    • 如果请求是静态资源或非目标主机,则跳过。
    • 如果请求头中包含Cookie
      • 调用rpc()获取浏览器端最新的Cookie (rpc_str)。
      • 可以预设一个基础Cookie (Cookie ="FSSBBIl1UgzbN7NO=..."),然后将RPC获取的动态部分追加或替换进去。在示例中,是直接拼接。更稳妥的做法是解析现有Cookie,替换掉动态变化的部分。
      • final_str替换原始请求头中的Cookie
from mitmproxy import http
import os
import requests
# -*- coding:utf-8 -*-# 目标主机
TARGET_HOST = "www.example.com"# 静态资源后缀列表(根据需要可继续添加)
STATIC_EXTENSIONS = [".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg",".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".map", ".mp4", ".webm"
]def rpc():url = "http://localhost:12080/go"params = {"group": "zzz","name": "hlg","action": "getData","param": "document.getElementById('copyButtonme').click()"}res = requests.get(url, params=params)return res.json()["data"]def is_static_resource(path: str) -> bool:_, ext = os.path.splitext(path.lower())return ext in STATIC_EXTENSIONSdef request(flow: http.HTTPFlow):# 排除静态资源if is_static_resource(flow.request.path):return# 仅处理目标 host 的请求if TARGET_HOST in flow.request.pretty_host:if "cookie" in flow.request.headers:Cookie ="FSSBBIl1UgzbN7NO=60fkObZ5wotjKLbDtdwWiwavQA5TRgIPjuw128_3ZCtcwiSjtWIswZjagalTr2h0ok_x_A5bQA7Xk2nW_qdy1GrA;"rpc_str = rpc()final_str = f"{Cookie} {rpc_str}"old_cookie = flow.request.headers["cookie"]flow.request.headers["cookie"] = final_strprint(f"[+] Replaced Cookie for {flow.request.pretty_url}")print(f"    Old: {old_cookie}")print(f"    New: {final_str}")

启动mitmproxy:
mitmproxy -s replace_cookie.py --listen-port 7777

部署与使用步骤 🚀

  1. 启动JS RPC服务端:根据 jxhczhl/JsRpc 项目说明,下载并运行其服务端。确保WebSocket服务 (如 ws://127.0.0.1:12080/ws) 和HTTP接口 (如 http://localhost:12080/go) 正常工作。修改MITM脚本中的RPC_SERVER_URLRPC_PARAMS以匹配RPC服务端配置。
    在这里插入图片描述

  2. 运行MITM代理脚本:执行 mitmproxy -s replace_cookie.py --listen-port 7777。配置浏览器或系统代理指向mitmproxy监听的地址(默认为localhost:7777)。

  3. 浏览器注入脚本

    • 访问目标网站。
    • 打开浏览器开发者工具(通常是F12)。
    • 在控制台(Console)中,先粘贴并执行脚本1 (Cookie捕获与展示)
    • 接着,粘贴并执行脚本2 (JS RPC客户端)。确保其中的WebSocket URL (ws://127.0.0.1:12080/ws?group=zzz) 与之前的RPC服务端和MITM脚本配置一致。
      在这里插入图片描述
  4. 开始测试:正常浏览目标网站。当网站JS更新Cookie时,脚本1会捕获它。配置Burpsuite下一跳代理为 localhost:7777通过Burpsuite发出请求到目标主机时,MITM脚本会通过RPC从浏览器获取最新的Cookie并替换到请求中。

总结与展望 🌟

通过结合浏览器端脚本注入、JS RPC和MITM代理,成功地构建了一套自动化获取并替换动态Cookie的解决方案。这种方法:

  • 绕过了JS混淆:无需深入分析混淆的JS代码,直接从运行时获取结果。
  • 实现了动态替换:确保了每次请求都使用当前浏览器中最新的有效Cookie。
  • 提高了测试效率:为后续的自动化渗透测试工具(如SQLMap、Burp Intruder等配合上游代理)提供了便利。





















竟然还往下翻,好了,复杂的方法你学会了,是时候告诉你点简单的方法了,参考项目:

  • https://github.com/R0A1NG/Botgate_bypass
  • https://github.com/wjlin0/riverPass

虽然做了点无用功,但至少累着了,至少下次针对加解密有法子了,哈哈哈哈
在这里插入图片描述


相关文章:

  • MyBatis 动态 SQL 详解:灵活构建强大查询
  • git基础语法回顾
  • .NET 8使用AOT发布ASP.NET Core应用
  • 【算法深练】双序列双指针:用“双轨并行”思维,高效破解算法难题
  • Unity中partial的作用
  • python33天打卡
  • vscode java debug terminal 中文乱码
  • Veeam Backup 13 beta ui 方式备份 VMware esxi 虚拟机
  • 《软件工程》第 8 章 - 人机交互设计
  • 【监控】PromQL 查询语言
  • 什么是时空学习(Spatiotemporal Learning)
  • linux文件权限管理
  • React与Vue的内置指令对比
  • 原生小程序与 UniApp 中传递循环数据(整个对象)的对比与实现
  • Haproxy编译安装
  • 大模型量化原理
  • 什么场景下能够用到根据id批量查询用户
  • Oracle 的 TX、TM、UL 锁对比
  • 计算机网络】深入解析 TCP 协议:从三次握手到拥塞控制
  • 人工智能工程师学习路线总结(上)
  • 食品网站源码/百度的网址是什么呢
  • 哈尔滨政府招标信息网/搜索引擎排名优化技术
  • 沈阳做网站的公司排行/广州网站建设
  • 音乐网站系统源码/公司网站建设费用多少
  • 南昌地宝网招聘信息网最新招聘/网站怎样优化文章关键词
  • 天津建设局网站首页/免费云服务器