网址就是csdn官网
今日在csdn浏览文章时候发现:无法复制文字内容,总是弹出一个登录窗口(我没登录);devtool的console.log的功能不可用
console.log不可用解决
先在 源代码->事件侦听器断点->勾选脚本事件->f5刷新页面
此时会断住,然后在控制台输入:ori_log=console.log并回车,接着取消勾选脚本事件断点,等待页面加载完成,即可用ori_log替代
这是因为bot-score-v1.js劫持了console对象下的["log", "warn", "info", "error", "exception", "table", "trace"]方法,并绑定到了一个空函数上
这个可以通过在控制台输入console.dir(console.log),会发现:
正常的应该是:
顺着[[BoundThis]]下的方法点进去,这是一个典型的ob混淆,可以在 https://obf-io.deobfuscate.io/ 里面解混淆
让我们来看看做了什么(AI生成):
这是一个经过高度混淆的脚本,主要用于设备指纹采集、机器人检测以及数据上报。从请求路径 /cdn_cgi_bs_bot/ 可以判断,它属于 Cloudflare Bot Management(Cloudflare 的机器人管理服务)的一部分。
下面按执行顺序和功能模块,详细解释代码做了什么。
1. 自保护与反调试机制(开头两个闭包)
const _0x260d53 = function () { ... }();
const _0x4703da = _0x260d53(this, function () {
return _0x4703da.toString().search("(((.+)+)+)+$") ...
});
_0x4703da();
_0x260d53是一个单次执行封装器,它会返回一个函数,但内部函数只能被调用一次(通过闭包标志位_0x18bbbe控制)。这通常用于防止外部重复调用或调试器干扰。_0x4703da调用自身toString()并执行正则(((.+)+)+)+$搜索。这个正则是一个经典的ReDoS(正则拒绝服务)攻击模式,同时也可以用于检测代码是否被格式化或篡改——如果函数体被改动,search结果会不同,可能直接抛出异常,起到防逆向作用。
类似的第二个闭包 _0x228bed 和 _0x4c84d5 功能更具体,见下一条。
2. 劫持控制台方法(_0x4c84d5)
const _0x228bed = function () { ... }();
const _0x4c84d5 = _0x228bed(this, function () {
const _0x17b582 = function () {
// 获取全局对象 (window)
};
const _0x23f78c = _0x17b582().console;
const _0x37fa34 = ["log", "warn", "info", "error", "exception", "table", "trace"];
for (...) {
const _0x219b17 = _0x228bed.constructor.prototype.bind(_0x228bed);
_0x219b17.__proto__ = _0x228bed.bind(_0x228bed);
_0x219b17.toString = _0x16e0f1.toString.bind(_0x16e0f1);
_0x23f78c[methodName] = _0x219b17;
}
});
_0x4c84d5();
- 它通过
Function("return (function() {}.constructor(\"return this\")( ));")()来获取全局对象window(此方式可避开严格模式的限制)。 - 遍历
console.log、warn、info、error等方法,将它们全部替换成一个绑定了_0x228bed的空白函数。 - 同时把替换函数的
__proto__和toString也做了重定向,让它在开发者工具中看起来像是原生的 console 方法,但实际上禁止了所有控制台输出,防止调试信息泄露或被人从控制台观察脚本行为。
3. Cookie 读取函数(_0x4a7a78)
function _0x4a7a78(cookieName) {
var cookies = document.cookie.split("; ");
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=');
if (cookieName == parts[0]) return unescape(parts[1]);
}
return '';
}
简单地从 document.cookie 中解析并返回指定名称的 cookie 值。在后续流程中用于读取名为 "bc_bot_session" 的 cookie。
4. 加密函数(_0x26f755)
function _0x26f755(plaintext, keyStr) {
var keyMd5 = CryptoJS.MD5(keyStr).toString();
var key = CryptoJS.enc.Utf8.parse(keyMd5.substring(0, 16));
var iv = CryptoJS.enc.Utf8.parse(keyMd5.substring(16));
return CryptoJS.AES.encrypt(plaintext, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
- 依赖全局的
CryptoJS库(AES 加密,CBC 模式,Pkcs7 填充)。 - 先将密钥字符串进行 MD5 哈希,取结果的前 16 个字符作为 AES 密钥,后 16 个字符作为 IV(初始化向量)。
- 返回加密后的密文(通常是 Base64 编码字符串)。
5. 指纹数据整理(_0x44786b)
function _0x44786b(fingerprintResult) {
let data = {
visitorId: fingerprintResult.visitorId,
confidence: fingerprintResult.confidence.score
};
// 提取 FingerprintJS 组件信息
let components = fingerprintResult.components;
if (components) {
data.fontsLength = ...;
data.audio = ...;
data.colorGamut = ...;
data.canvas = ...; // winding 等
data.touchSupport = ...;
data.cookiesEnabled = ...;
data.videoCard = ...;
data.pdfViewerEnabled = ...;
data.platform = ...;
// 等等
}
// 提取 Botd 机器人检测信息
let botd = fingerprintResult.botd;
let botdComps = botd.components;
if (botdComps) {
data.userAgent = ...;
data.windowSize = ...;
data.pluginsLength = ...;
data.webGL = ...;
data.webDriver = ...; // 是否启用自动化工具(如 Selenium)
data.android = ...;
data.distinctiveProps = ...;
data.browserKind = ...;
// ...
}
data.bot = botd.bot;
// 如果 botd 组件不存在且 bot 标志不为 true,则强制标记为机器人
if (botd.bot != true && (botdComps == undefined)) {
data.bot = true;
}
return data;
}
- 将 FingerprintJS v3 生成的庞大指纹对象精简成一个只包含关键字段的平面对象。
- 同时合并 Botd (Bot Detection) 模块的检测结果。
- 特别关注
webDriver(自动化驱动检测)、bot标记等字段,用于判断访问者是否为机器人。
6. 加密并上报数据(_0x4f90d3)
function _0x4f90d3(processedFp) {
let sessionCookie = _0x4a7a78("bc_bot_session");
let visitorId = processedFp.visitorId;
let encrypted = _0x26f755(JSON.stringify(processedFp), sessionCookie + visitorId);
const payload = { fp: visitorId, enc: encrypted };
fetch("/cdn_cgi_bs_bot/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => data.status == 1)
.catch(err => console.error('Error:', err));
}
- 从 cookie 读取
bc_bot_session作为会话标识。 - 用
bc_bot_session+visitorId拼接后作为加密密钥,对精简后的指纹数据进行 AES 加密。 - 将
visitorId(明文)和密文一起通过POST发送到/cdn_cgi_bs_bot/api。 - 期望响应 JSON 中
status为 1 表示成功。
7. 主执行流程
let botdResult;
const botdPromise = import("/cdn_cgi_bs_bot/static/fingerprintjs-botd-v1.js")
.then(module => module.load());
botdPromise
.then(loaded => {
let result = loaded.detect();
result.components = loaded.components;
result.detections = loaded.detections;
return result;
})
.then(res => { botdResult = res; })
.catch(err => console.error(err));
const fpPromise = import("/cdn_cgi_bs_bot/static/fingerprintjs-v3.js")
.then(module => module.load());
fpPromise
.then(fp => fp.get())
.then(fpData => {
fpData.botd = botdResult; // 将 botd 检测结果挂到指纹对象上
let processed = _0x44786b(fpData); // 精简数据
_0x4f90d3(processed); // 加密并上报
});
- 使用动态
import()加载两个外部脚本:- fingerprintjs-botd-v1.js:机器人检测库,提供浏览器特征检测(如 WebDriver、headless chrome 痕迹等)。
- fingerprintjs-v3.js:FingerprintJS v3,生成浏览器指纹(Visitor ID)。
- Botd 先执行
load()和detect(),结果暂存到变量botdResult。 - FingerprintJS 调用
get()获取完整的浏览器指纹对象。 - 将 Botd 结果合并进指纹对象,再经过
_0x44786b提取关键字段,最后调用_0x4f90d3加密上报。
整体目的
这段脚本是 Cloudflare 的反机器人/安全防护系统 在客户端埋下的检测代码,其主要功能为:
- 收集多维度的浏览器指纹(Canvas、WebGL、字体、音频、硬件并发、插件、屏幕尺寸等)。
- 识别自动化工具与机器人(如 Selenium WebDriver、Headless Chrome、Android 模拟器等)。
- 将收集到的数据用动态密钥加密(密钥来自 Cookie 和指纹 ID)后回传到 Cloudflare 的接口。
- 抑制开发者工具的控制台输出,增加逆向分析的难度。
- 采用混淆和反调试技巧,防止篡改和静态分析。
最终,服务端根据这些加密的指纹数据判断访问者是否为正常用户,从而决定放行、拦截或提出人机验证挑战。
再让我们看看fingerprintjs-botd-v1.js和fingerprintjs-v3.js是干什么的(AI生成):
这两个脚本是Cloudflare机器人管理系统(Bot Management)中使用的客户端检测模块。fingerprintjs-v3.js 负责生成浏览器指纹,而 fingerprintjs-botd-v1.js 则用于检测自动化工具和机器人。
fingerprintjs-v3.js:浏览器指纹识别
脚本 fingerprintjs-v3.js 是FingerprintJS v3.4.2开源库,主要功能是生成一个独特的"访问者ID"(Visitor ID)。
- 技术原理:它通过收集浏览器的多种"信号"(如Canvas渲染、WebGL、音频处理、字体列表、屏幕色深等),利用MurmurHash3哈希算法生成一个紧凑的标识符。后续分析中提到的"Picasso"技术,也是其指纹采集的一部分,被用于生成信号。该ID在隐身模式下也能保持稳定,但纯客户端版本准确率有限,约为40%-60%。
fingerprintjs-botd-v1.js:机器人检测
脚本 fingerprintjs-botd-v1.js 是Fingerprint BotD v1.9.1库,专门用于识别访问者是否为自动化程序。
- 检测机制:它不仅检测通用的无头浏览器和自动化框架(如Selenium、Playwright),还会根据
navigator.userAgent来判断浏览器内核(如Chromium/Blink、WebKit、Gecko)。 - 高级检测:它还会检查设备是否开启WebDriver模式,并检查
navigator.plugins等属性,以区分真实浏览器和模拟浏览器的机器人。
与Cloudflare的协同工作
这两个脚本会被Cloudflare动态注入到HTML页面中。其生成的信号(如JA3/JA4指纹)会被纳入后台的机器人评分(Bot Score)系统,用于区分人类与自动流量。默认情况下,脚本每30分钟才执行一次。
总结
总的来说,这两个脚本共同构建了一个双层检测体系:第一个脚本用于识别"谁在访问"(即生成设备指纹),第二个脚本用于判断"是不是真人"(即检测机器人)。它们是Cloudflare机器人管理系统中不可或缺的一环。
未登录无法复制解决
这个就需要下断点跟踪了,我们现在元素中找到所有的事件绑定,这里面没有copy的绑定,所以就只好看keydown了,其实也可以全局搜索copy 的,但用前者却无意间发现了一个关键区别,这个等会再说。
进去下断点后,刚把鼠标挪到页面内就断住了,这不是我们想要的,我们先看看断点的情况:
这是鼠标移动触发的,说明多个事件都被绑到这个'代理'上了,我们要的是copy事件,故改为条件断点:a.type=='copy',放开执行,进行复制操作,果然断住了。
接下来分析断点处的三元表达式得出下一步会走n.event.dispatch.apply(k.elem, arguments),让我们跟进:
a = n.event.fix(a);对a进行了细微的改动,不用管i = e.call(arguments)e是slice,将arguments复制给ij = (n._data(this, "events") || {})[a.type] || []这个就是取出对应事件的绑定,目前的a.type='copy'
让我们执行完j的赋值:
先埋一个伏笔:当前文件是detail-b15e6c982b.min.js
在return n(),行处下一个断点,然后直接让程序走到这里,跟进到n函数,发现又调用的window.csdn.loginBox.show函数,继续跟进去:
我把这个e函数拿过来了:
function e(e) {
for (var t = document.cookie.split("; "), i = 0; i < t.length; i++) {
var o = t[i].split("=");
if (o[0] == e) return decodeURIComponent(o[1])
}
}
这就是一个从cookie中取值的函数,传入了'UserName',在控制台执行e("UserName")返回undefined
当时我的猜测是登录了的肯定会返回有值,可能登录可以复制的原因就是执行了return void void 0;,所以我直接一把梭哈,把show的函数体换为了return void void 0;然后保存,刷新,再次复制的时候发现没有弹出窗口了,但是依然无法复制。。。
于是我直接另开一个无痕窗口登录了csdn,重复上面的路程,直到执行完那个j的赋值然后进入到copy的handler:
这里的文件名变为了edit_copy_code-43c2d050a0.min.js
原来检测了登录状态,执行不同的脚本
然后我回到了未登录的窗口继续观察,最后发现这是检测未登录才对copy等事件进行重新绑定的,调用getCookie("UserName"):
进入到getCookie函数发现是另一个文件的,这个大概率挂载到全局了,切换到登录的窗口中,控制台输入getCookie发现返回函数,点进去到了相同文件的地方
于是我反手写代理getCookie的代码,接着在if (copyPopSwitch && !getCookie("UserName"))处打一个断点:
ori_getcookie = getCookie
getCookie = new Proxy(ori_getcookie, {
apply(target, thisArg, argumentsList) {
const [key] = argumentsList;
if (key == 'UserName') {
return true;
}
const result = Reflect.apply(target, thisArg, [key]);
return result;
}
});
然后刷新网页,断住后粘贴代理,继续运行,发现并没有重新绑定,继续运行等待页面加载完成,再次复制,发现可以正常复制
但是这样也有弊端,就是需要找到特定代码,然后下断点,刷新,注入代码,流程麻烦复杂
那么就直接将getCookie更改(替代文件)最终也可以实现直接进行复制
浙公网安备 33010602011771号