
比如 XMLHttpRequest,有没有什么手段可以知道这个东西有没有被人为重写?
网站找了不少方法都做不到
下面是我做的一些尝试,这些检测方法都能被绕过
{ function isNative(api) { return /native code/.test(api.toString()) && typeof api !== 'undefined' } let test = function (input, fake) { console.log("------------------------") console.log("是否是伪造:", fake) console.log("toString:", input.toString()) console.log("toString.toString:", input.toString) console.log("prototype 方法", input.hasOwnProperty("prototype")) console.log("toString.call","方法",Function.prototype.toString.call(input)) console.log("网传最不靠谱方法:isNative", isNative(input)) } test(XMLHttpRequest, false) { let XMLHttpRequest = function () { "[native code]" } XMLHttpRequest.toString = function () { return "function XMLHttpRequest() { [native code] }" } let toString = function () { return "function toString() { [native code] }" } toString.toString = toString XMLHttpRequest.toString.toString = toString Function.prototype.toString = toString delete XMLHttpRequest.prototype test(XMLHttpRequest, true) // XMLHttpRequest.prototype = undefined // test(XMLHttpRequest, true) } } 1 JK9993 2021-05-08 17:05:15 +08:00 Function.prototype.toString.call |
2 JK9993 2021-05-08 17:05:53 +08:00 哦,不行 |
3 JK9993 2021-05-08 17:07:39 +08:00 如果原型链上的 toString 被修改了,就只能检测到 toString 被修改这一步了 |
4 xieqiqiang00 OP @JK9993 怎么检测 tostring 被修改了? |
![]() | 5 JK9993 2021-05-08 17:12:25 +08:00 你可以构造一个函数,然后 toString 输出 |
6 xieqiqiang00 OP @JK9993 不行,我试过 |
7 7075 2021-05-08 17:15:30 +08:00 首先你先想想怎么给“原生 /非原生”下一个明确的、可量化、可操作的定义。 有了定义,才有分类的判断标准。 如果能拿到目标函数原始的代码,那么可以用代码做指纹... |
8 mopig 2021-05-08 17:18:07 +08:00 @xieqiqiang00 Function.prototype.toString.call(Function.prototype.toString) 这样检测 |
9 xieqiqiang00 OP @7075 我指的原生就是看不到代码,可能都不是 JS 实现的,浏览器内置的这些东西<br> fetch 、XMLHTTPRequest 这种 |
10 daysv 2021-05-08 17:20:09 +08:00 XMLHttpRequest.toString() "function XMLHttpRequest() { [native code] }" |
11 xiangwan 2021-05-08 17:20:38 +08:00 via Android 如果你信任你的运行时,这个判断才有意义 |
12 daysv 2021-05-08 17:22:23 +08:00 再加一个 XMLHttpRequest.toString.toString() ? |
13 xieqiqiang00 OP @JK9993 懂了 |
14 xieqiqiang00 OP @daysv 我最上面的代码就能绕开这个了 |
15 xieqiqiang00 OP @xiangwan 提高一下成本,打开控制台随便改改就能操作,门槛也太低了 |
16 chogath 2021-05-08 17:25:20 +08:00 没懂需要判断的需求是什么,可否描述下场景和需求啊 |
17 7075 2021-05-08 17:36:28 +08:00 这个问题往大了说是一个哲学问题。怎么证明你是你。 还不如从原始需求出发,解决最直接的需求问题。 |
18 xieqiqiang00 OP @chogath 想法是用 electron 写一个程序,代码用 vm 预编译,网络用 electron 的证书绑定,但如果直接改 js 文件重写了 XHR,通信的具体细节还是会一览无余。希望提高破解者的破解成本 |
19 chogath 2021-05-08 17:43:23 +08:00 @xieqiqiang00 js 有转移二进制码,或者可执行文件的第三方库,或许你可以从这个角度去考虑? |
20 maichael 2021-05-08 17:44:23 +08:00 如果你想沿着现有思路走基本是走不通的,毕竟你的检测工具都可能被篡改。 还不如回到你的需求本身,考虑下其它的方向。 |
21 3dwelcome 2021-05-08 17:50:16 +08:00 用 websocket+自定义协议就可以避免被简单抓包。 类似一个主流网络游戏的自定义加密通讯协议,绝对没那么容易破解。 或者学微信,自己造 HTTPS/SSL 轮子,也没那么容易被破解。直接用标准的 http/https 确实很不安全,客户端注入 JS,解开后就是明文了。 |
22 renmu123 2021-05-08 17:50:16 +08:00 via Android 如果可以直接改你的代码,你代码底裤都被看到了,那么你防止修改 xhr 又有什么用。 代码混淆再找找有什么好的加壳工具吧 |
23 xieqiqiang00 OP @chogath 代码保护这块我打算用 vm 编译成字节码,但如果运行环境事先被修改了一样会上钩 |
24 xieqiqiang00 OP @renmu123 不想被看到请求了什么接口,代码加密现在有 JS 预编译,够了 |
25 xieqiqiang00 OP @maichael 没有绝对的安全嘛,提高一下门槛 |
26 BoringTu 2021-05-08 18:00:30 +08:00 emmmm,我来给你个正确答案吧~ 思路其实很简单,给你个小 demo~ window.ABC = function() { console.log('origin ABC'); }; window.tempABC = window.ABC; window.ABC = function() { window.tempABC.call(this, arguments); console.log('new ABC'); } 有思路没~ 我只是模拟了一下被人为覆盖浏览器内置函数的逻辑 那现在公布答案~ 在 HTML head 标签里所有 script 标签的最前面加上一个 script,里面: window.originXMLHttpRequest = window.XMLHttpRequest; 然后在你想判断的时候: originXMLHttpRequest === XMLHttpRequest 其实换句话说,window.originXMLHttpRequest 这东西就是浏览器原装内置函数,因为人为覆盖的逻辑肯定在后面执行的 是不是豁然开朗?(手动抠鼻 |
27 xieqiqiang00 OP @BoringTu 在这之前执行了替换的话不就 GG |
28 BoringTu 2021-05-08 18:04:55 +08:00 @xieqiqiang00 都在所有脚本执行之前了,你这疑问不成立啊 |
29 ch2 2021-05-08 18:06:19 +08:00 你首先得保证你的检测代码不能被修改 |
30 des 2021-05-08 18:07:02 +08:00 既然你是 electron 程序,建议在 c++层面动手脚 js 层面加东西不说白费功夫,但起码能力有限 |
31 carlclone 2021-05-08 18:07:16 +08:00 运行的时候下断点, 在 console 输入函数名, 会输出一个可以点击跳转的函数定义位置 |
32 luofeii 2021-05-08 18:12:25 +08:00 建立沙箱比如 iframe,需要用到哪个函数直接从 iframe 生成的 window 对象调用就可以 |
33 xieqiqiang00 OP 要是这么简单就能解决我的顾虑,我就不提这个问题了 |
34 xieqiqiang00 OP @BoringTu 要是这么简单就能解决我的顾虑,我就不提这个问题了 |
35 xieqiqiang00 OP @ch2 检测部分打算预编译成字节码,就当他是改不了的 |
36 xieqiqiang00 OP @des 魔改 electron 哈哈哈 |
37 xieqiqiang00 OP @carlclone 这个是可以改的,vm 就可以 |
38 BoringTu 2021-05-08 18:29:08 +08:00 |
39 BoringTu 2021-05-08 18:30:34 +08:00 @xieqiqiang00 ? 哪里有问题可以提出来,不提出来咋给你解决。。 |
40 BoringTu 2021-05-08 18:32:38 +08:00 只有一种情况是我说的方案失效的,就是如果是说 electron 是在加载 html 之前就覆盖了内置函数 |
41 luofeii 2021-05-08 18:38:55 +08:00 via Android @BoringTu 正是因为它俩不等于,所以 window.XMLHttpRequest 无论做任何更改,iframe.contentWindow.XMLHttpRequest 对象还是原生的 |
42 xiangwan 2021-05-08 18:39:54 +08:00 直接不用 XMLHttpRequest 。用其他语言的 http client 编译成 wasm 调用。 |
43 gamexg 2021-05-08 18:44:16 +08:00 @BoringTu #38 以前实现过 js 的浏览器伪装,iframe 也会被处理。 记不清具体细节,印象是 createElement 、getElementById 函数都替换为自己的函数。 浏览器插件来可以实现页面代码之前执行替换代码。 当时考虑过网站检测对抗,只能考虑寻找各个未处理好的细节。 楼主也许可以考虑故意用落后几个版本的 electron(或魔改版) ,然后去依赖老版本不支持的 js 新特征来检查。 例如,看似正常的功能,依赖新特征,浏览器不支持这个功能时 js 回退使用兼容实现。 但是应该故意让 electron 不支持这个特征,那个客户端支持就证明是破解版。 |
44 daysv 2021-05-08 18:46:50 +08:00 |
45 ochatokori 2021-05-08 18:53:05 +08:00 via Android js 层面别费劲了,不管怎么绕都只能过滤一些通用 hook 工具,针对你的程序的话你没办法 就算是 native 层也不是不可能的 |
46 KuroNekoFan 2021-05-08 18:55:24 +08:00 via iPhone 这种问题跟“如何避免 https 抓包”是一个性质的吧 |
47 ochatokori 2021-05-08 18:56:10 +08:00 via Android @daysv #44 直接重写 String.toString.call |
48 no1xsyzy 2021-05-08 19:48:55 +08:00 如果替换方法是 js,其实是可以用 js 检测的,之前 V2 上面有人做过不记得是 JS 还是 Python 的思考题的,如何判断一个对象是不是 Proxy hint:递归 但如果对面直接掉替换你的 V8 那也是白搭。 |
49 xieqiqiang00 OP |
50 ychost 2021-05-08 21:16:43 +08:00 很难防御,之前用类似的方式破过很多竞赛网站,比如 10fastfingers.com 之类的,前端没法防御只能通过服务端来校验提交有没有异常 |
51 rekulas 2021-05-08 21:28:14 +08:00 抛砖引玉 ``` var iframe = document.createElement('iframe') document.body.appendChild(iframe) XMLHttpRequest === iframe.contentWindow.XMLHttpRequest // true or false? ``` |
52 no1xsyzy 2021-05-08 21:28:43 +08:00 @xieqiqiang00 我尝试找了一下,但没找到…… 假设一个期望中调用层数为 k 的待测函数 就是去撞递归最大层数 撞到了退回来 k-1 层,调用待测的函数 f,应当调用失败( InternalError: too much recursion ) 再多退一层到 k 层,调用待测函数 f,应当调用成功 但是似乎最近浏览器都随机化了最大调用层(防 fingerprinting 吧),不确定 Electron 如何。 (其实最方便的、最坚固的可能是干脆换 Qt 商业授权) |
53 musi 2021-05-08 21:51:59 +08:00 |
54 JerryCha 2021-05-08 22:26:52 +08:00 那你不如直接用 C++写个 addon 专门负责网络请求 |
55 learningman 2021-05-09 00:29:06 +08:00 js 预编译不好使的,建议 wasm |
56 jones2000 2021-05-09 00:51:59 +08:00 直接后台渲染, 不就行了。 后台生成静态页面,显示。什么 js 都没有。 |
59 musi 2021-05-09 06:25:29 +08:00 via iPhone @aaronlam 本来就是可行的,现在微前端里的沙箱基本都离不开 iframe,比如比较流行的 qiankun 框架,比如阿里云的 console os,都是拿 iframe 来做的沙箱隔离,因为目前的 realm api 还在草案阶段 |
60 rekulas 2021-05-09 09:08:27 +08:00 @musi 正常是可以判断的,你可以运行下 不过... @luofeii @BoringTu 正如 @gamexg 提到的,iframe 仍然不完美,客户端仍然能进行攻击,举个栗子 ``` document.createElement = () => { return {contentWindow: {XMLHttpRequest: XMLHttpRequest}} }; document.body.appendChild = () => {}; var iframe = document.createElement('iframe') document.body.appendChild(iframe) console.log(XMLHttpRequest === iframe.contentWindow.XMLHttpRequest) ``` 最后判断始终是 true,除非你继续证明 document.createElement 也是原生,这。。。俄罗斯套娃 我觉得可以考虑从某些无法被覆盖的对象入手,例如 navigator 之类 |
61 rekulas 2021-05-09 09:32:19 +08:00 我想到一种结合 valueof 的判断方式 ,大家考虑下如何可以绕过 ``` function isNative(api) { if (typeof XMLHttpRequest != 'function') return false; if (typeof XMLHttpRequest.valueOf != 'function' || XMLHttpRequest.valueOf().toString() != 'function XMLHttpRequest() { [native code] }') { return false; } return /native code/.test(api.toString()) && typeof api !== 'undefined' } console.error(isNative(XMLHttpRequest)) ``` 不过最终始终有疑问,如果客户端可以针对性劫持的话,那无论怎么写代码都无法判断的,客户端只需要在判断函数 return true 就行了 |
62 musi 2021-05-09 10:36:52 +08:00 @rekulas 我直接在这个帖子开启 console,把你的代码粘贴运行,结果为 false 。 我重新建了个 html 页面,把你的代码粘贴进去运行还是 false,这种没修改都是 false 的要怎么判断? |
63 rekulas 2021-05-09 10:52:54 +08:00 @musi 应该是这句 != 'function XMLHttpRequest() { [native code] }' 这个在不同环境下有一点差异所以需要尝试下多种环境,我使用的 chrome90,其他版本或 firefox 的话需要针对性判断下 你可以先 console.log(XMLHttpRequest.valueOf()) 拿到真实值替换进去再测试 |
65 rekulas 2021-05-09 11:15:31 +08:00 @musi 测试好像是不行了,奇怪昨天我测试还可以,我待会再试试 不过不用纠结这个,因为这个最终还是有漏洞的,你可以看看我最新的代码如何绕过 |
66 Huelse 2021-05-09 11:27:12 +08:00 幸好楼主解释了,不然又是一个 x-y 问题了 |
67 musi 2021-05-09 13:25:27 +08:00 @rekulas #63 也不是很难 ``` let tem = XMLHttpRequest; function test() { console.log(111) return tem } XMLHttpRequest = test isNative(XMLHttpRequest) // false test.toString = () => "function XMLHttpRequest() { [native code] }" isNative(XMLHttpRequest) // true ``` |
68 gzzhanghao 2021-05-09 16:01:26 +08:00 function guard(value) { function toString() { try { null.a } catch (error) { // TODO 判断 error.stack,在 chrome 下 send 和 Object.toString 应该是相邻的 // 注意:不能用 prototype,只能访问 str.length 和 str[index],逐个字符判断 console.log(error.stack) return value } } return { toString } } function send(method, url, body = undefined) { const xhr = new XMLHttpRequest() xhr.open(guard(method), guard(url)) xhr.send(guard(body)) } send('POST', '/', JSON.stringify({ foo: 'bar' })) /** * 测试劫持 */ XHR = XMLHttpRequest XMLHttpRequest = class { constructor() { this.xhr = new XHR() } open(method, url) { return this.xhr.open(method, url) } send(body) { return this.xhr.send(body) } } send('POST', '/', JSON.stringify({ foo: 'bar' })) |
69 gzzhanghao 2021-05-09 16:17:40 +08:00 https://codepen.io/gzzhanghao/full/PopqWZr v2 回复居然没有缩进… |
70 gzzhanghao 2021-05-09 16:37:15 +08:00 我错了,不用判断 stack 那么麻烦,直接 fn.caller 就能秒解 ```js function guard(value) { function toString() { if (toString.caller === send) { return value } } return { toString } } function send(method, url, body = undefined) { const xhr = new XMLHttpRequest() xhr.open(guard(method), guard(url), false) xhr.send(guard(body)) console.log(xhr.status, xhr.responseText) } send('GET', '/') ``` |
71 rekulas 2021-05-09 21:47:24 +08:00 @musi 确实又绕过了。。又升级了下,再看看呢 ``` ``` function isNative(api) { if (XMLHttpRequest.hasOwnProperty('toString')) return false; if (typeof XMLHttpRequest != 'function') return false; if (typeof XMLHttpRequest.valueOf != 'function' || XMLHttpRequest.valueOf().toString() != 'function XMLHttpRequest() { [native code] }') { return false; } return /native code/.test(api.toString()) && typeof api !== 'undefined' } console.error(isNative(XMLHttpRequest)) ``` ``` |
72 BoringTu 2021-05-10 10:07:27 +08:00 @luofeii #41 但是你怎么做对比呢? 如果不考虑是否是 electron 环境,只是浏览器环境的话,你可以参考我提供的思路,这是最简单也最有效的解决方案 要的就是同一个对象的引用,这样才能判断出是否是同一个内存地址 @binux 为啥做不到呢?在.html 文件里直接写死就好啊,你是考虑会有动态插入 script 标签么?这不需要考虑啊,HTML 在浏览器内核上的渲染逻辑没有那么玄幻,都是自上而下的,就算有动态插入也都是执行到了具体脚本才会有的动作,但早在这一步之前,我想要执行的那句脚本就已经执行完毕了 @rekulas 嗯,我不推荐 iframe 的这种做法哇,虽然按我最早发的那个思路,一样是可以避免你提的这个可能的,我拿到内置 document.createElement 的原生函数引用不就好了嘛。而且你的思路最后的那个判断本来就不是应该的,这个判断木有意义:“XMLHttpRequest === iframe.contentWindow.XMLHttpRequest”,如果不执行你的第一句那个重写 createElement,前面这个判断是永远是 false 的,因为并不是同一个环境,所以也就不是同一个对象 @aaronlam @musi 以及楼上某些木有点名到的童鞋,你们都没注意听讲啊。。 我给各位总结然后回答一下楼主这个问题(两种情况): 1. 如果只是判断前端环境中是否有脚本重写了 XMLHttpRequest,那最简单也最靠谱的就是我提供的那个方案: “在 HTML head 标签里所有 script 标签的最前面加上一个 script,里面: window.originXMLHttpRequest = window.XMLHttpRequest; 然后在你想判断的时候: originXMLHttpRequest === XMLHttpRequest” (顺便聊一下 iframe 的这个方案,用来判断的话是没戏的,上下文环境不同,就算没被重写,也没法判断,因为不可能是同一个对象。iframe 的方案,你只能是 iframe 环境里的 XMLHttpRequest 拿来直接当做原生内置函数来用 2. 如果是说 electron 直接修改了浏览器内核里的 XMLHttpRequest,那就无解了,人家都不是在前端环境修改的,你怎么判断?你就算 toString 了,一样拿到的是这个:'function XMLHttpRequest() { [native code] }'。这种情况你想要知道是否被重写了,只能肉眼去看 electron 的源码,没有其他方式了 |
73 binux 2021-05-10 10:10:48 +08:00 via Android @BoringTu 如果我控制了浏览器呢?甚至不需要是浏览器,一个插件,mitm 修改 HTML 都行。 |
74 BoringTu 2021-05-10 10:26:29 +08:00 @binux 你怎样控制用户的浏览器呢?你说插件,你只能在自己电脑上安装插件,你怎样让用户也安装?如果你随意的就能控制他人浏览器,那浏览器这个行业也不用干了。。 你只能在你电脑的浏览器上装你想装的插件,然后你在自己浏览器上通过插件篡改了原生函数,这都可以啊,但能说明啥问题。。 这个其实没啥好杠的,而且都不需要去查什么资料去验证 这就好像之前我碰到有问这么个事儿的,说我通过自己浏览器开发者工具拿到了我登录后的 token,然后发给别人,不就把我的登录信息暴露出去了么。。我。。当时是真不知道该回啥。。 |
75 SakuraKuma 2021-05-10 11:07:37 +08:00 , 楼主都说是 electron, js 怎么弄都么得用, 还是上面说的走 c++写 dll 用 ffi 吧. |
77 gzzhanghao 2021-05-10 11:32:06 +08:00 via iPhone @BoringTu 楼主好像说过不能控制 script 标签的位置,另外这里应该不用讨论改内核的场景,那是无解的 |
78 musi 2021-05-10 12:06:45 +08:00 @BoringTu #72 你没仔细看我说的,我说的就是直接用 iframe 的 xhr 去请求,就不判断了。至于修改内核,能修改内核了你只靠 js 是无解的。 还有,你的方案也不是最简单的,我要是多页面那我每个页面都要修改这侵入性也太大了,这也能叫最简单的? |
79 BoringTu 2021-05-10 14:16:07 +08:00 @musi 就算再多页面,你肯定也是按模板来的,难道你这多页面要每个页面都单独完整写一份 html ? 而且你谈侵入性,我的方案只是暴露原对象引用出来而已,并没有做哪怕一丁点的修改,这叫有侵入性? 至于你前半段说的,我都已经说过一遍了。。 @gzzhanghao 为啥不能控制?详见 #72 |
80 gzzhanghao 2021-05-10 18:19:54 +08:00 via iPhone @BoringTu 见#27,另外我之前也遇到过类似的问题,作为第三方库提供出去,这种情况是控制不了的 |