比如 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 2
这个问题往大了说是一个哲学问题。怎么证明你是你。
还不如从原始需求出发,解决最直接的需求问题。 |
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,另外我之前也遇到过类似的问题,作为第三方库提供出去,这种情况是控制不了的
|