zoukankan      html  css  js  c++  java
  • 你以为你请求的就是你想请求的吗?

    在当今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,所以我们重写XMLHttpRequestopen方法进行拦截。重写前,需要先保存一下原生方法。

    好了,现在开始正式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参数进行修改,然后调用原生方法。

    通过我们的日志信息,可以看到,访问修改已经成功:

    image-20211103164438126

    没问题,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完全一样。

    通过同样的方法,也可以把imglinkiframea给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)就可以重写的,这下好办了,我们重写一下srcsetter

    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);
    

    通过同样的方法,也可以把imglinkiframeastyle中和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属性,会直接发起请求,我们无法拦截。但是,我们可以通过调用CSSStyleDeclarationsetProperty方法进行属性设置,所以我们需要在CSSStyleDeclaration的原型链上定义上面的属性,通过设置settergetter,然后调用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-imagebackground等属性进行hook时,调用了setProperty方法进行设置,若原代码中直接就调用的setProperty方法进行设置,则需要对setProperty的属性描述符(property descriptor)进行重写。

    HTML中的请求

    HTML中的请求,我们无法进行拦截,但可以使用MutationObserver监听DOM对象的创建,对于其中的a标签,可以修改href属性。对于imgsrc属性也可以修改,但无法阻止请求的发出,修改后的请求也会正常发出。

    我们先在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();
    

    保存,然后刷新一下页面,可以发现显示的图片已经发生了改变。

    image-20211103094141533

    在这里,虽然我们看到的图片已经发生了变化,但实际是在HTML中指定的图片依然会发出请求。

    image-20211103094342364

    在Developer Tools的网络标签中,可以看到,发出了两次图片请求。

    关于MutationObserver的具体用法,请可以参考

    在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;
    

    image-20211103161320432

    未解决的问题

    如果在网页的脚本中,有通过locationlocation.href来重定向页面地址,则无法对这个动作进行拦截,location对象已经被浏览器定义为了不可伪造,目前没有找到好的办法,只能通过服务端代理,将调用该属性的js代码进行替换。

    通过属性描述可以看到,window上的locationlocation.href均设置了不可修改。

    {enumerable: true, configurable: false, get: ƒ, set: ƒ}
    

    写在最后

    本文是以前在将淘宝手机页面搬进微信里显示的时候的研究结果,但后来这个也没有用起来,现在基于互联互通的政策要求,也就更没有使用场景了。

    本文的相关代码已上传:

  • 相关阅读:
    .net 5.0
    多线程synchronized锁
    多线程(Thread、线程创建、线程池)
    电商秒杀方法
    sb @EnableAsync与@Async 20210310
    spring boot @EnableAsync 异步调用
    五代十国军事人物
    唐朝末年,七大割据军阀势力
    盘点万历之后,镇守辽东的8位军事统帅,堪称有军事作为的仅三人
    Cookie-Session or JWT
  • 原文地址:https://www.cnblogs.com/zsea/p/15508324.html
Copyright © 2011-2022 走看看