在当今SPA利用风行的状况下,页面上的所有货色都是通过javascript进行加载,本文将带你一步一步截获用户申请,并批改申请地址。
咱们次要应用的办法为Hook原生接口进行接口调用拦挡;在拦挡前,先定义一个URL批改的函数,对立将URL申请中的before批改为after,你在你的理论解决中可能会更加简单。
function srcHook(url) { let nUrl = url.replace("hook-before", "hook-after"); return nUrl;}
Ajax申请
在前端中,个别是通过Ajax向后盾申请数据,所以首要须要拦挡的就是Ajax的申请。
先来看一下如何收回一个Ajax申请:
var xhr = new XMLHttpRequest();xhr.timeout = 3000;xhr.ontimeout = function (event) { alert("申请超时!");}xhr.open('GET', '/data/hook-before.txt');xhr.send();xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { resolve(xhr.responseText); WriteLogs("====响应 " + xhr.responseText); }}
能够看到,传入URL参数的办法是xhr.open
,所以咱们重写XMLHttpRequest
的open
办法进行拦挡。重写前,须要先保留一下原生办法。
好了,当初开始正式Hook了:
var $open = XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open = function () { if (srcHook) { var src = srcHook(arguments[1]); if (src === false) return; if (src) { arguments[1] = src; } } return $open.apply(this, arguments);}
是的,就是这么简略,在重写的open
办法中,对URL参数进行批改,而后调用原生办法。
通过咱们的日志信息,能够看到,拜访批改曾经胜利:
没问题,Hook胜利。然而,你看上面,还有一个fetch
类型的申请没有Hook到,别着急,马上解决它。
依然首先来看一下fetch
的调用办法:
fetch("/data/hook-before.txt").then(function(response){ return response.text();}).then(function(text){ alert(text);})
fetch
是一个全局函数,第一个参数为须要申请的网址。咱们只须要重写window
对象上的fetch
函数即可。
var $fetch = window.fetch;window.fetch = function () { if (srcHook) { var src = srcHook(arguments[0]); if (src === false) return; if (src) { arguments[0] = src; } } return $fetch.apply(window, arguments);}
这下没问题了,两种申请形式都拦挡了。
DOM申请
对于失常的Ajax申请,咱们曾经进行了解决,但在有些状况下,会在页面中应用JSONP
来进行跨域申请。
咱们依然是先来看一下JSONP
的实现:
var url="/data/hook-before.js";var script = document.createElement('script');script.setAttribute('src', url);document.getElementsByTagName('head')[0].appendChild(script);
能够看出,JSONP的实质是向DOM中插入一个SCRIPT的Element。从代码中,我轻松的找到的Hook点,Element实例的setAttribute
办法。
var $setAttribute = Element.prototype.setAttribute;Element.prototype.setAttribute = function () { if (this.tagName=="SCRIPT"&&arguments[0]=="src"&&srcHook) { var src = srcHook(arguments[1]); if (src === false) return; if (src) { arguments[1] = src; } } return $setAttribute.apply(this, arguments);}
和xhr的hook齐全一样。
通过同样的办法,也能够把img
、link
、iframe
、a
给hook掉。
然而,下面的hook如同也差了点啥。请看上面的代码:
var url="/data/hook-before.js";var script = document.createElement('script');script.src=url;document.getElementsByTagName('head')[0].appendChild(script);
没错,不调用setAttribute
办法一样能够设置src
。
先看一看src
在原型链上的定义:
{get: ƒ, set: ƒ, enumerable: true, configurable: true}
通过定义能够晓得src
的属性描述符(property descriptor)就能够重写的,这下好办了,咱们重写一下src
的setter
。
var descriptor=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, "src");var setter=descriptor["set"];descriptor["set"]=function(value){ if (srcHook) { var src = srcHook(arguments[0]); if (src === false) return; if (src) { arguments[0] = src; } } return setter.apply(this, arguments);}descriptor["configurable"]=false;//因为src的set有可能会被其它脚本批改回去,此处通过设置configurable=false来强行禁止批改Object.defineProperty(HTMLScriptElement.prototype, "src", descriptor);
通过同样的办法,也能够把img
、link
、iframe
、a
,style
中和URL相干的属性解决掉。
提醒:innerHTML
也是通过这种办法进行解决。
CSS中的申请
要发动一个申请,除了下面形容的办法外,也能够通过css中的background-image
属性发动。
document.getElementById("#id").style.background="url(/data/hook-before.jpg)";
CSS属性属于CSSStyleDeclaration
对象,该对象的原型上有以下属性能够发动申请:
- cssText
- background-image
- background
- border-image
- borderImage
- border-image-source
- borderImageSource
应用代码示例中的办法设置CSS属性,会间接发动申请,咱们无奈拦挡。然而,咱们能够通过调用CSSStyleDeclaration
的setProperty
办法进行属性设置,所以咱们须要在CSSStyleDeclaration
的原型链上定义下面的属性,通过设置setter
和getter
,而后调用setProperty
办法进行理论设置。代码示例如下:
Object.defineProperty(CSSStyleDeclaration.prototype, "background", { get: function () { return this.getPropertyValue("background"); }, set: function (v) { v=srcHook(v); this.setProperty("background", v); } } ); Object.defineProperty(CSSStyleDeclaration.prototype, "background-image", { get: function () { return this.getPropertyValue("background-image"); }, set: function (v) { v=srcHook(v); this.setProperty("background-image", v); } } ); var descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "setProperty"); var valuer = descriptor["value"]; descriptor["value"] = function () { if (srcHook) { var src = srcHook(arguments[1]); if (src === false) return; if (src) { arguments[1] = src; } } return valuer.apply(this, arguments); } descriptor["configurable"] = false; //因为src的set有可能会被其它脚本批改回去,此处通过设置configurable=false来强行禁止批改 Object.defineProperty(CSSStyleDeclaration.prototype, "setProperty", descriptor);
因为在对background-image
,background
等属性进行hook时,调用了setProperty
办法进行设置,若原代码中间接就调用的setProperty
办法进行设置,则须要对setProperty
的属性描述符(property descriptor)进行重写。
HTML中的申请
HTML中的申请,咱们无奈进行拦挡,但能够应用MutationObserver
监听DOM
对象的创立,对于其中的a
标签,能够批改href
属性。对于img
的src
属性也能够批改,但无奈阻止申请的收回,批改后的申请也会失常收回。
咱们先在HTML中增加一个图片显示的DOM
<img src="/data/hook-before.jpg" />
在没有监听和批改前,页面显示的是HOOK前的图片,如下:
而后,咱们在JS中增加监听和批改的代码,咱们仅用IMG进行测试:
function DomWatch() { // part 1 var observer = new MutationObserver(function(mutationsList, mutationObserver){ mutationsList.forEach(function(mutation){ if(!mutation.addedNodes) return; mutation.addedNodes.forEach(function(node){ if(node.tagName!=="IMG") return; node.src=srcHook(node.src); }) }) }); // part 2 observer.observe(document, {childList:true,attributes:true,subtree:true});}DomWatch();
保留,而后刷新一下页面,能够发现显示的图片曾经产生了扭转。
在这里,尽管咱们看到的图片曾经产生了变动,但理论是在HTML中指定的图片仍然会发出请求。
在Developer Tools的网络标签中,能够看到,收回了两次图片申请。
对于MutationObserver
的具体用法,请能够参考
- https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
- https://www.zhangxinxu.com/wordpress/2019/08/js-dom-mutation-observer/
在HTML中的DOM,也能够通过遍历的形式进行批改,然而如果用innerHTML
创立的DOM,解决上就会比拟麻烦。
WebSocket中的申请
WebSocket中的申请是在new
的时候指定的,如下:
new WebSocket("ws://121.40.165.18:8800")
咱们须要拦挡WebSocket的new
操作,并将连贯地址批改为咱们须要的地址,对于new
的拦挡,这里应用ES6的Proxy
进行解决。在这里,咱们对立将地址批改为ws://119.29.3.36:6700/
。
const __WebSocket = new Proxy(window.WebSocket, { construct(target, args) { args[0]="ws://119.29.3.36:6700/"; return new target(...args); }});window.WebSocket = __WebSocket;
未解决的问题
如果在网页的脚本中,有通过location
或location.href
来重定向页面地址,则无奈对这个动作进行拦挡,location
对象曾经被浏览器定义为了不可伪造,目前没有找到好的方法,只能通过服务端代理,将调用该属性的js代码进行替换。
通过属性形容能够看到,window
上的location
和location.href
均设置了不可批改。
{enumerable: true, configurable: false, get: ƒ, set: ƒ}
写在最初
本文是以前在将淘宝手机页面搬进微信里显示的时候的钻研后果,但起初这个也没有用起来,当初基于互联互通的政策要求,也就更没有应用场景了。
本文的相干代码已上传:
- https://gitee.com/zsea/jshook
- https://github.com/zsea/jshook