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

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

[复制链接]

123

主题

932

帖子

550

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
550
发表于 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虽好,可不要贪杯哦~

结语

撒花~

5

主题

70

帖子

116

积分

注册会员

Rank: 2

积分
116

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章

发表于 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))。箭头函数不只是写着好看的,很多时候多利用这些特性,可以解决很多问题。

回复

使用道具 举报

123

主题

932

帖子

550

积分

管理员

Rank: 9Rank: 9Rank: 9

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

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

回复

使用道具 举报

8

主题

155

帖子

453

积分

中级会员

Rank: 3Rank: 3

积分
453

猫咪币纪念章活跃会员三好学生热心会员中秋纪念章

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

使用道具 举报

123

主题

932

帖子

550

积分

管理员

Rank: 9Rank: 9Rank: 9

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

哥哥牛逼!
回复

使用道具 举报

4

主题

85

帖子

30

积分

新手上路

Rank: 1

积分
30

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

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

使用道具 举报

4

主题

85

帖子

30

积分

新手上路

Rank: 1

积分
30

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

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

所以代码可以写成oldJson.call(response))
这又该怎么写?数据还能再传导出去吗?
回复

使用道具 举报

5

主题

70

帖子

116

积分

注册会员

Rank: 2

积分
116

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章

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

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

使用道具 举报

4

主题

85

帖子

30

积分

新手上路

Rank: 1

积分
30

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

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

哦!!!
回复

使用道具 举报

123

主题

932

帖子

550

积分

管理员

Rank: 9Rank: 9Rank: 9

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

今天进行测试,我的proxy过滤存在严重问题,会导致网页的很多读写属性都查不到
哥哥的方法很有可能是正确的,我明天测试一下
回复

使用道具 举报

发表回复

本版积分规则

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