经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
前端黑魔法 —— 隐藏网络请求的调用栈
来源:cnblogs  作者:EtherDream  时间:2023/8/7 9:46:04  对本文有异议

前言

浏览器网络控制台会记录每个请求的调用栈(Initiator/启动器),可协助调试者定位到发起请求的代码位置。

为了不让破解者轻易分析程序,能否隐藏请求的调用栈?

事件回调

事实上,使用之前 《如何让 JS 代码不可断点》 文中的方案,通过「内置回调」到「原生函数」,即可隐藏请求的调用栈:

  1. const fn = fetch.bind(window, '/?by-click')
  2. window.addEventListener('click', fn, {once: true})

点击页面任意位置即可产生网络请求,并且 Initiator 显示为 Other:

由于该方案依赖事件,因此得使用极易触发的事件,例如 mousemove。但用户一动不动的话,程序流程就会卡在这里。

如果直接用 JS 驱动该事件,例如:

  1. const fn = fetch.bind(window, '/?by-dispatch')
  2. window.addEventListener('click', fn, {once: true})
  3. window.dispatchEvent(new Event('click'))

那么调用栈会原形毕露:

类似的,人为制造一个 error 事件:

  1. const img = new Image()
  2. img.onerror = fetch.bind(window, '/?by-onerror')
  3. img.src = ''

或者人为产生一个 message 事件:

  1. const fn = fetch.bind(window, '/?by-message')
  2. window.addEventListener('message', fn, {once: true})
  3. window.postMessage('')

这些都会暴露调用栈。

动画事件

我们回顾下浏览器中总共有哪些事件:

https://developer.mozilla.org/en-US/docs/Web/API/Event#interfaces_based_on_event

事实上最容易触发的事件就在第一个:AnimationEvent。因为 CSS 动画播放时间是可控的,所以我们可创建一个 0 秒的动画,从而立即触发 animationend 事件:

  1. <style>
  2. @keyframes k {}
  3. #el {
  4. animation-duration: 0;
  5. animation-name: k;
  6. }
  7. </style>
  8. <div id="el">Hello</div>
  9. <script>
  10. const fn = fetch.bind(window, '/?by-animation')
  11. el.addEventListener('animationend', fn)
  12. </script>

并且能完美隐藏调用栈:

过渡事件

除了动画事件,CSS 中的过渡事件 TransitionEvent 也能实现同样的效果。

  1. const el = document.createElement('div')
  2. el.style = 'transition: font 1s;'
  3. document.body.appendChild(el)
  4. const fn = fetch.bind(window, '/?by-transition')
  5. document.body.addEventListener('transitionrun', fn)
  6. requestAnimationFrame(() => {
  7. el.style.fontSize = '0'
  8. })

相比动画事件需创建一个 @keyframes CSS 规则,过渡事件直接使用元素自身 style 属性即可,因此更简单。

不过需注意的是,动态添加的元素立即修改 CSS 是无法产生过渡事件的,需稍作延时才能修改。MDN 文档 也提到这点。

演示

考虑到「过渡事件」需推迟执行并多一个回调,这里选择「动画事件」的方案,封装成纯 JS 实现:

  1. const xhr = new XMLHttpRequest()
  2. xhr.onload = () => {
  3. console.log('xhr onload')
  4. }
  5. xhr.open('GET', '/privacy')
  6. const container = document.createElement('div')
  7. container.style = 'animation-duration:0; animation-name:__a__;'
  8. container.innerHTML = '<style>@keyframes __a__{}</style>'
  9. document.body.appendChild(container)
  10. const sendFn = xhr.send.bind(xhr)
  11. container.addEventListener('animationend', sendFn)
  12. const removeFn = container.remove.bind(container)
  13. container.addEventListener('animationend', removeFn)
  14. // 用于参照对比
  15. const xhr2 = new XMLHttpRequest()
  16. xhr2.open('GET', '/public')
  17. xhr2.send()

为了请求后续操作,这里使用 XHR 取代 fetch,方便回调和获取数据。

同时注册了两个 animationend 事件,一个发送请求,另一个删除元素,这样页面中不会有 DOM 残留。

Chrome:

FireFox:

Safari:

原文链接:https://www.cnblogs.com/index-html/p/hide-request-initiator.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号