欢迎学员来参加小肩膀广东逆向安全百人聚会:
广东安全逆向圈子 | 十年逆向教育二次聚会
本目录分析 BrowserScan 首页 https://www.browserscan.net/zh 的浏览器版本检测,使用当前定制 Firefox 完成 baseline 与 hooked 两轮采集。
目标类型:浏览器指纹/版本一致性检测分析。没有验证码、签名 token 或业务登录链。
成功口径:
页面显示 Browser= Firefox、Browser Version=151.0。Header UA 与 JavaScript UA 一致。 /api/8e3f1bbb的isBrowserVersionCorrect返回通过。baseline 与 hooked 结果一致。
采集结果
https://www.browserscan.net/zh | ||
/api/8e3f1bbb | [1,1,1,1,null,"",null] | |
页面存在时区与 IP 位置减分,但浏览器名称、版本和 UA 没有减分。这些网络位置问题与本次版本检测结论无关。
3. 当前版本身份面
navigator.userAgent
= Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0)
Gecko/20100101 Firefox/151.0
navigator.appVersion = 5.0 (Windows)
navigator.platform = Win32
navigator.oscpu = Windows NT 10.0; Win64; x64
navigator.vendor = ""
navigator.product = Gecko
navigator.productSub = 20100101
navigator.buildID = 20181001000000
navigator.userAgentData = null
UA 中存在两个应同步的 Firefox 版本点:
rv:151.0
Firefox/151.0
只修改其中一个会形成直接矛盾。
4. 检测点一:UAParser
BrowserScan 当前共享 bundle 使用 UAParser。Browser Checker 在 CxFbXgIB.js 中执行:
new UAParser().getResult()
随后读取:
result.browser.name
result.browser.version
result.browser.major
result.engine.name
result.os.name
result.ua
本轮解析结果:
browser.name = Firefox
browser.version = 151.0
browser.major = 151
engine.name = Gecko
页面卡片直接展示 Firefox 和 151.0。
UAParser 的浏览器匹配存在优先级,Edge/Opera 必须先于 Chrome,Safari 使用 Version/x 而不是 Safari/605... 作为浏览器版本。code.js 已按这一原则复现常见浏览器解析。
5. 检测点二:Header UA 与 JavaScript UA
BrowserScan 请求:
GET https://tls.browserscan.net/api/tls
返回的 user_agent 是服务端看到的 HTTP Header UA。本轮为 Firefox 151.0,与 navigator.userAgent 完全一致。
首页 Cozm_HMb.js 将以下材料传给从 ClTriCii.js 导入的 q0:
q0({
browser: jsUA.ua,
header: headerUA,
clientHints,
experimental,
speechVoice,
})
这说明版本判断不是单纯读取 DOM:服务端同时拿到 JavaScript UA 和网络 Header UA。
6. 检测点三:后端一致性接口
真实请求:
POST https://www.browserscan.net/api/8e3f1bbb
网络 body 为:
{"_k":"...","_p":"..."}
_k/_p 每轮变化,是加密封装。不能把历史值写死,也不能仅凭抓包密文推断字段;明文参数结构以主 bundle 的 q0({...}) 调用点为准。
当前响应:
[1,1,1,1,null,"",null]
主 bundle 的数组映射为:
isUACorrect | ||
isOSCorrect | ||
isBrowserVersionCorrect | ||
isBrowserCorrect | ||
uaFullVersion | ||
platformVersion | ||
isMobile |
Firefox 没有 navigator.userAgentData,因此 uaFullVersion=null 在当前环境是合理结果,不代表版本检测失败。
7. 检测点四:UA Client Hints
当前 Firefox:
navigator.userAgentData = null
Sec-CH-UA* = 不发送
BrowserScan 会异步尝试获取 Client Hints,并将结果纳入一致性接口。Firefox 强行增加 Chromium 风格的 brands、fullVersionList 或 Sec-CH-UA-Full-Version-List 会与 Gecko 外观冲突。
Chromium 版本修改则必须同时维护:
brands
fullVersionList
uaFullVersion
platformVersion
Sec-CH-UA
Sec-CH-UA-Full-Version-List
Sec-CH-UA-Platform
Sec-CH-UA-Mobile
8. 检测点五:CSS/Navigator/Window 特征版本
CxFbXgIB.js 的 Browser Checker 不止运行 UAParser。它并行采集:
CSS computed 特征
Navigator computed 特征
Window feature computed 特征
然后调用特征推算函数,获得:
engine
version
liedCSS
liedJS
liedWindow
页面将推算版本与 UAParser 的 browser.major 比较。比较规则:
没有特征版本 -> 暂不判错
完全相等 -> 通过
特征版本为 A-B 且 UA major 位于区间 -> 通过
其它情况 -> 版本不一致
因此将 Firefox 151 UA 改成明显不同的大版本,但保留当前 CSS/API/Window feature,可能被判定为 Different browser version。
9. 检测点六:引擎与跨 realm 外观
Browser Checker 还观察:
Gecko/Blink/WebKit 的异常栈长度特征。 DocumentFragment、隐藏元素与 iframecontentWindow。主窗口与 iframe/跨 realm 的属性和方法行为。 descriptor、prototype、constructor、异常和 native toString外观。
这意味着仅在主窗口覆盖 navigator.userAgent 不够。about:blank iframe、worker 或新 realm 暴露原始版本时,版本身份会分裂。
10. Hooked 调用证据
hooked/jscall/browser_version_calls.jsonl 中,BrowserScan 自身脚本读取:
Navigator.userAgent | CCZS-DkR.js:11:2421 | |
Navigator.appVersion | k4WjIakY.jsCozm_HMb.js | |
Navigator.vendor | Cozm_HMb.js:18 | |
Navigator.product | CCZS-DkR.js:9 | |
Navigator.platform | CCZS-DkR.js:25 | |
Navigator.oscpu | Cozm_HMb.jsCCZS-DkR.js |
buildID、productSub 和 userAgentData 在 probe 中被读取,但本轮 Hook 日志没有记录到 BrowserScan 自身直接调用栈。因此它们属于环境一致性面;不能把它们描述成已确认的首页主判定输入。
11. JavaScript 复现
文件:code.js
入口:
BrowserScanBrowserVersionRepro.collect({
headerUA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0) Gecko/20100101 Firefox/151.0",
featureVersion: "150-152",
}).then(console.log);
复现内容:
Navigator 版本字段采集。 Firefox、Chrome、Chromium、Edge、Opera、Safari UA 版本解析。 UA Client Hints brands/fullVersionList 解析。 Header UA 与 JavaScript UA 对比。 Header 与 JS 的浏览器名称/版本对比。 Browser Checker 的精确版本/版本区间比较。
12. 修改建议
12.1 配置模型
不要增加互不关联的散点版本开关。建议在当前 fpfile/配置体系中建立一个 browser profile:
browserProfile
├─ name
├─ version / major
├─ engine / engineVersion
├─ userAgent / appVersion
├─ vendor / product / productSub
├─ platform / oscpu
├─ userAgentData / UA-CH
├─ HTTP User-Agent
└─ TLS/HTTP2 profile
所有版本相关返回值和 Header 从同一 profile 派生。
12.2 Firefox 同版本修改
若只在 Firefox 内切换版本,应同步:
rv:x.y
Firefox/x.y
浏览器主版本
引擎 feature version
版本相关 CSS.supports/WebAPI 行为
插件/PDF Viewer/字体等随版本变化的组合
升级通道、buildID 和资源版本的合理关系
保持:
vendor = ""
product = Gecko
productSub = 20100101
userAgentData = null
不发送 Sec-CH-UA*
12.3 跨引擎伪装
Firefox 直接伪装 Chromium 风险很高,因为还要同步:
UA Client Hints
window.chrome / Blink API
CSS 与 Window feature
异常栈/descriptor/prototype
iframe/worker realm
TLS/HTTP2
PDF Viewer/plugins
字体、Canvas、WebGL、Audio
建议默认仅支持同引擎、同 OS profile 内的版本控制。
12.4 Firefox/domtrace 实现位置建议
配置解析层:
读取 browserProfile,默认关闭,不新增散点环境变量。
网络层:
HTTP User-Agent 与 UA-CH 统一从 profile 派生。
Navigator 层:
userAgent/appVersion/vendor/product/productSub/platform/oscpu/userAgentData。
Realm 层:
主窗口、iframe、about:blank、worker 使用同一 profile。
一致性层:
descriptor、prototype、native toString 与真实 Firefox WebIDL 外观一致。
验证层:
BrowserScan Header/JavaScript UA 相同;
Browser Version 正确;
/api/8e3f1bbb 前四位为 1;
Browser Checker feature version 与 UA major 匹配。
13. 最终结论
BrowserScan 的浏览器版本检测是多层组合:
UAParser 版本
+ HTTP Header UA
+ JavaScript UA
+ UA Client Hints
+ CSS/Navigator/Window feature version
+ 引擎与跨 realm 外观
+ 服务端一致性接口
当前 Firefox 151.0 在 baseline 和 hooked 两轮均通过浏览器版本、浏览器名称、UA 和 OS 一致性检查。
修改版本时,最关键的不是替换一个字符串,而是让网络层、Navigator、UA-CH、引擎特征、跨 realm 和 TLS/HTTP2 从同一 browser profile 派生并保持一致。