上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
1234下一页
返回列表 发新帖

[油猴脚本开发指南]理解fetch劫持

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    636

    主题

    5189

    回帖

    6071

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6071

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-8-25 18:43:11 | 显示全部楼层 | 阅读模式

    前言

    Fetch劫持是基于Promise进行的,我们之前已经学习过了Promise,那么现在我们来研究一下fetch劫持

    2021-8-28留

    以下代码可能存在错误,如果想直接摘抄请使用这个代码

    2021-8-28下午留

    我又参考了一部分代码和资料,提出一个相对来说目前可能正确的fetch代理

    之前代码出现报错是因为this指向问题,为了防止调用函数出错,我们传入了原response的this指针,然后有些网页喜欢往上挂载函数以及乱读乱写

    所以获取了this指针后就饶过了我们的proxy代理,并且还在使用proxy代理以及非proxy指针,导致冲突问题。

    这里我们使用了proxy直接传入原reponse以及代理原reponse对象

    保证写入的数据都在reponse上,以及网页获取的this也在reponse上

    这时候调用函数会经过我们的proxy上,可以比之前的可靠性更好一点

    如果使用该代码出现错误请立刻停止继续使用,并反馈。

    更推荐使用cxxjackie的进行fetch劫持!!!

    let oldfetch=fetch
    function newobj(){}
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('get',target, prop, receiver)
                        if(typeof Reflect.get(target,prop)==='function')
                        {
                            if(Reflect.get(target,prop+'proxy')===undefined)
                            {
                                target[prop+'proxy']=new Proxy(Reflect.get(target,prop), {
                                    apply: (target, thisArg, argumentsList)=> {
                                        console.log('fetchfunction',target.name, response, argumentsList)
                                        return Reflect.apply(target, response, argumentsList);
                                    }
                                });
                            }
                            return Reflect.get(target,prop+'proxy')
                        }
    
                        return Reflect.get(target, prop);
                    },
    
                    set(target, prop, value) {
                        return Reflect.set(target, prop, value);
                    },
                };
                let proxy=new Proxy(response, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    开始

    一个基本的fetch还是很简单的

    fetch('http://example.com/movies.json')
      .then(function(response) {
        return response.json();
      })
      .then(function(myJson) {
        console.log(myJson);
      });

    fetch提交一个地址,然后返回了一个promise,对promise挂载hen,收到了一个response,然后返回response的json函数返回的内容

    图片.png

    我们可以看到现在a是履行的

    返回的是一个Response对象,并且json在原型链上

    图片.png

    为了保守性和通用性,我们应该对promise返回的response对象进行劫持

    这里我们开始对fetch函数进行劫持吧!

    实战

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
    
            });
      }
    

    由于fetch返回了一个promise,所以我们也需要返回一个promise,oldfetch是为了保存原fetch的引用,因为我们会替换掉window上fetch函数,对fetch实现一个劫持的功能。arguments是为了收集传入函数的参数,并且原封不动的传入到原fetch中。

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
            oldfetch(arguments)
            });
      }

    由于我们是劫持fetch函数嘛,我们需要在promise进行fetch的函数调用,并传入调用我们函数的参数原封不动的传给他,只要我们不resolve/reject,这个函数永远都是pengding的状态,所以我们可以放心干,大胆干!

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
            oldfetch(arguments).then(function(response) {
                        console.log(response)
                 resolve('niub')
                      })
    
            });
      }

    这里对原fetch返回的promise挂载了一个then,收到response并输出出来,然后将我们这个函数的返回的promise设置为履行,值为国粹niub。

    我们首先测试一下

    图片.png

    可以看到输出的url并不是我们提交的,也报错了一个404

    我们调试一下看看,因为我们的函数没有实际功能,所以100%是在oldfetch上出错了,打断点看一点

    图片.png

    这里看到arguments是new promise传给我们的参数,我们疏忽了!

    我们应该获取到fuckfetch上的参数,应该怎么办呢?

    可以考虑用变量拓展运算符

    let oldfetch=fetch
    function fuckfetch(...bianliang){
       return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                        console.log(response)
                 resolve('Fuccck')
                      })
    
            });
      }
    window.fetch=fuckfetch
    

    ...会收集没有赋值到参数上变量的所有其他剩余参数,而在函数体内对变量使用...,则会将其还原至原来的参数样子。

    因为我们这里没有其他参数,所以...bianliang会收集所有传给这个函数的参数。

    图片.png

    可以看到...c收集了所有剩余参数。

    图片.png

    而在函数体内使用...c,会将收集的剩余参数形成的数组重新还原成参数的形式

    图片.png

    修改之后可以看到非常成功,然后我们可以开始对Response对象进行Proxy劫持了!

    let oldfetch=fetch
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
    
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('target',target, prop, receiver)
                        return Reflect.get(target, prop);
                    },
    
                    set(target, prop, value) {
                        console.log('set',target, prop, value)
                        return Reflect.set(target, prop, value);
                    },
                };
                let proxy=new Proxy(response, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    由于对对象常见的操作我认为只有set和get,所以这里目前仅对这两个操作进行劫持,如果存在其他的,我们可以查询手册,继续添加。

    对其进行调用测试,发现报出错误

    图片.png

    a.then(function(response) {console.log('getresponse',response)
    return response.json();
    })

    我们使用的是response.json()

    会触发get属性,获取到json的函数并且调用,但是由于我们进行了target包装

    所以调用的变成了Proxy对象的json函数,由于根本没有,所以会触发报错。

    这里会不会想到xhr劫持部分?我们可以模仿xhr劫持部分对函数的指向进行劫持。

    let oldfetch=fetch
    function newobj(){}
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                let tagetobk=new newobj();
                tagetobk.oldfetch=response;
    
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('get',target, prop, receiver)
                                    if(prop==='oldfetch'){
                    return Reflect.get(target,prop);
                }
                        if(typeof Reflect.get(target.oldfetch,prop)==='function')
                        {
                            if(Reflect.get(target.oldfetch,prop+'proxy')===undefined)
                            {
                                target.oldfetch[prop+'proxy']=new Proxy(Reflect.get(target.oldfetch,prop), {
                                    apply: function(target, thisArg, argumentsList) {
                                        return Reflect.apply(target, thisArg.oldfetch, argumentsList);
                                    }
                                });
    
                            }
                            return Reflect.get(target.oldfetch,prop+'proxy')
                        }
                        return Reflect.get(target.oldfetch, prop);
                    },
    
                    set(target, prop, value) {
                        console.log('set',target, prop, value)
                        return Reflect.set(target.oldfetch, prop, value);
                    },
                };
                let proxy=new Proxy(tagetobk, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    我们可以在函数的proxy内进行判断

                                target.oldfetch[prop+'proxy']=new Proxy(Reflect.get(target.oldfetch,prop), {
                                    apply: function(target, thisArg, argumentsList) {
                                        console.log('function',target, thisArg, argumentsList)
                                        if(target.name==='json'){
                                            return {a:6,b:6,c:6}
                                        }
                                        return Reflect.apply(target, thisArg.oldfetch, argumentsList);
                                    }
                                });

    这里我输出了function+target,thisArg,argumentsList三个参数

    第一个target是我们调用的目标函数,我们可以对这个函数调用.name来获得他的函数名字符,然后与json对比,如果相等了

    就返回一些我们特定的内容,如果不相等,则调用原来的函数并返回内容。

    图片.png

    图片.png

    图片.png

    这里可以看到返回了我们设定的内容,这个对象下的其他函数也可以通过这种办法进行劫持,这里只是以json函数进行举例

    警告

    proxy会拖慢网页的执行效率,在频繁数据交互的情况下不推荐使用,以及希望大家时候的时候可以考虑在fuckfetch函数内对参数进行判断,有针对性的对fetch进行劫持过滤,proxy虽好,可不要贪杯哦~

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-25 21:22:35 | 显示全部楼层

    我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个情景下只关心response.json(),那就劫持json好了,这个函数也是Promise,那就像劫持fetch一样再套一层Promise:

    let oldfetch = fetch;
    function fuckfetch() {
        return new Promise((resolve, reject) => {
            oldfetch.apply(this, arguments).then(response => {
                const oldJson = response.json;
                response.json = function() {
                    return new Promise((resolve, reject) => {
                        oldJson.apply(this, arguments).then(result => {
                            result.hook = 'success';
                            resolve(result);
                        });
                    });
                };
                resolve(response);
            });
        });
    }
    window.fetch = fuckfetch;

    这里利用了箭头函数的一个特性:不改变this和arguments的指向。由于Promise用了箭头函数,oldfetch.apply(this, arguments)这里的this和arguments其实指的是fuckfetch的this和arguments,这样就可以省去传参这一步,我下面劫持response.json又用回function,所以oldJson.apply(this, arguments)这句的this和arguments指向的是response.json(我是故意这么写的,其实这里的this等于response,arguments是空,所以代码可以写成oldJson.call(response))。箭头函数不只是写着好看的,很多时候多利用这些特性,可以解决很多问题。

    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    636

    主题

    5189

    回帖

    6071

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6071

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-8-25 22:13:00 | 显示全部楼层
    cxxjackie 发表于 2021-8-25 21:22
    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    也想过用这种方法,但是想了想感觉搞个通用的例子更普适一点
    代码确实太复杂了,写这鬼东西我调了来来回回快半个小时....
    下节课以哥哥的为例子我拆了说一下
    哥哥的挺nice!

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    1 小时前
  • 签到天数: 709 天

    [LV.9]以坛为家II

    27

    主题

    733

    回帖

    7216

    积分

    荣誉开发者

    精通各种语言的HelloWord!

    积分
    7216

    荣誉开发者油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-25 23:17:50 | 显示全部楼层
    ggnb!!!!!!!!
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    636

    主题

    5189

    回帖

    6071

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6071

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-8-26 09:08:25 | 显示全部楼层

    哥哥牛逼!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-14 00:00
  • 签到天数: 119 天

    [LV.6]常住居民II

    29

    主题

    598

    回帖

    535

    积分

    专家

    积分
    535

    油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-27 19:56:19 | 显示全部楼层
    呜,二楼的哥哥代码似乎更简洁一点,但我想李大道大神是以教学为目的,出发点不一样吧,虽然我没看懂。试了一下二楼代码是可以直接拿来用的呀,哎呀我靠
    入驻爱发电 让这世界充满爱 https://afdian.net/a/vpannice
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-14 00:00
  • 签到天数: 119 天

    [LV.6]常住居民II

    29

    主题

    598

    回帖

    535

    积分

    专家

    积分
    535

    油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-27 20:17:29 | 显示全部楼层
    cxxjackie 发表于 2021-8-25 21:22
    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    所以代码可以写成oldJson.call(response))
    这又该怎么写?数据还能再传导出去吗?
    入驻爱发电 让这世界充满爱 https://afdian.net/a/vpannice
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-27 21:25:09 | 显示全部楼层
    脚本体验师001 发表于 2021-8-27 20:17
    所以代码可以写成oldJson.call(response))
    这又该怎么写?数据还能再传导出去吗? ...

    是指oldJson.apply(this, arguments)这句可以等效换成oldJson.call(response)
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-14 00:00
  • 签到天数: 119 天

    [LV.6]常住居民II

    29

    主题

    598

    回帖

    535

    积分

    专家

    积分
    535

    油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2021-8-27 21:31:58 | 显示全部楼层
    cxxjackie 发表于 2021-8-27 21:25
    是指oldJson.apply(this, arguments)这句可以等效换成oldJson.call(response)

    哦!!!
    入驻爱发电 让这世界充满爱 https://afdian.net/a/vpannice
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    636

    主题

    5189

    回帖

    6071

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6071

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-8-28 01:10:10 | 显示全部楼层
    cxxjackie 发表于 2021-8-25 21:22
    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    今天进行测试,我的proxy过滤存在严重问题,会导致网页的很多读写属性都查不到
    哥哥的方法很有可能是正确的,我明天测试一下
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表