【问题标题】:Passing Proxy object as thisArgument to apply throws TypeError: Illegal Invocation将代理对象作为 thisArgument 传递以应用抛出 TypeError: Illegal Invocation
【发布时间】:2021-10-22 09:09:48
【问题描述】:

我正在尝试捕获对Storage 的调用。据我所知,有两种方法可以调用setItemgetItem

    sessionStorage.setItem("foo", "bar");
    let item = sessionStorage.getItem("foo");

    Storage.prototype.setItem.call(sessionStorage, "foo", "bar");
    let item2 = Storage.prototype.getItem.call(sessionStorage, "foo");

在代码片段中使用sessionStorage 会引发安全错误,因此here's the code in JS Fiddle

TLDR:如果我这样做,我可以处理所有情况,但它看起来很老套。有没有更好/更清洁的方法来实现我的目标? (注意:我无法控制调用者,这就是我涵盖这两种情况的原因):

try {
    console.log("\n\n");
    let ss = new Proxy(sessionStorage, {
        get: function (getTarget, p) {
            if (p === "__this") {
                // kinda hacky, but allows us to unwrap Proxy for binding
                return getTarget;
            }
            console.log("sessionStorage.get proxy called")
            return new Proxy(Reflect.get(getTarget, p), {
                apply(applyTarget, thisArg, argArray) {
                    console.log("sessionStorage.get.apply called");
                    Reflect.apply(applyTarget, getTarget, argArray);
                }
            })
        },
    });

    SP = new Proxy(Object.create(Storage.prototype), {
        get: function (getTarget, p) {
            console.log("Storage.get proxy called")
            return new Proxy(Reflect.get(getTarget, p), {
                apply(applyTarget, thisArg, argArray) {
                    console.log("Storage.get.apply called");
                    try {
                        return Reflect.apply(applyTarget, thisArg, argArray);
                    } catch (e) {
                        // unpack proxy if we're double-wrapped (both target and thisArg are Proxy)
                        return Reflect.apply(applyTarget, thisArg.__this, argArray);
                    }
                }
            })
        },
    });
    SPW = {};
    Object.defineProperty(SPW, 'prototype', {
        value: SP,
        configurable: false,
    });
    si = SPW.prototype.setItem;
    gi = SPW.prototype.getItem;
    si.call(ss, "foo", "3");
    console.log(`Storage.prototype.getItem: ${gi.call(ss, "foo")}`);
    console.log(`Storage.prototype Worked`)
} catch (e) {
    console.log(`Storage.prototype Caught ${e.stack}`);
}

JSFiddle.

结果:

Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 3
Storage.prototype Worked

除了包含“隐藏”__this 属性之外,我没有看到任何其他方法,以便调用者可以“解包”Proxy 对象并获取对原始sessionStorage 的引用。有没有更好的方法来做到这一点?

背景

如果有帮助,这里是仅包装 sessionStorageStorage 的示例,但不能同时包装两者:

换行sessionStorage

对于apply 陷阱,我必须传递getTarget 而不是thisArg,因为后者是 Proxy 对象,如果我传递它,则会引发Illegal Invocation 错误。

    try {
        let ss = new Proxy(sessionStorage, {
            get: function (getTarget, p) {
                console.log("sessionStorage.get called")
                return new Proxy(Reflect.get(getTarget, p), {
                    apply(applyTarget, thisArg, argArray) {
                        console.log("sessionStorage.get.apply called");
                        Reflect.apply(applyTarget, getTarget, argArray);
                    }
                })
            },
        });
        ss.setItem("foo", "1");
        console.log(`Proxy.sessionStorage.getItem: ${ss.getItem("foo")}`);
        console.log(`Proxy.sessionStorage Worked`)
    } catch (e) {
        console.log(`Proxy.sessionStorage Caught ${e.stack}`);
    }

JS Fiddle

结果:

sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
Proxy.sessionStorage.getItem: undefined
Proxy.sessionStorage Worked

换行Storage.prototype

在这里,我必须首先使用单独的prototype 创建一个新对象,因为Storageprototype 属性描述符将configurable 设置为false。完成此操作后,我基本上必须通过传递“未包装”getTarget 而不是thisArg 指向的Proxy 实例来做与前一个案例相同的事情。

    try {
        console.log("\n\n");
        SP = new Proxy(Object.create(Storage.prototype), {
            get: function (getTarget, p) {
                console.log("Storage.get proxy called")
                return new Proxy(Reflect.get(getTarget, p), {
                    apply(applyTarget, thisArg, argArray) {
                        console.log("Storage.get.apply called");
                        try {
                            return Reflect.apply(applyTarget, thisArg, argArray);
                        } catch (e) {
                            console.log("apply failed when passing thisArg");
                            return Reflect.apply(applyTarget, getTarget, argArray);
                        }
                    }
                })
            },
        });
        SPW = {};
        Object.defineProperty(SPW, 'prototype', {
            value: SP,
            configurable: false,
        });
        si = SPW.prototype.setItem;
        gi = SPW.prototype.getItem;
        si.call(sessionStorage, "foo", "2");
        console.log(`Storage.prototype.getItem: ${gi.call(sessionStorage, "foo")}`);
        console.log(`Storage.prototype Worked`)
    } catch (e) {
        console.log(`Storage.prototype Caught ${e.stack}`);
    }

JS Fiddle

结果:

Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 2
Storage.prototype Worked

【问题讨论】:

  • 您试图拦截的调用和/或分配究竟是什么?可能你根本不应该使用Proxy,它只会让一切变得更复杂。
  • 我正在尝试拦截浏览器指纹脚本的调用。我的目的是记录他们并分析他们收集的数据。我看过的其他脚本无法完全记录所有收集的数据,这是指纹如何规避拦截操作尝试的一个示例:通过使用不可配置的对象原型。
  • 能否请您添加一些示例浏览器指纹识别脚本所做的具体调用?它是只调用localStorage.getItem()localStorage.setItem(),还是以其他方式访问localStorage
  • @Bergi 在这种情况下让我搜索的具体示例使用了我在问题开始时演示的原型方法。虽然我的方法的部分原因是因为脚本通常被混淆,所以我试图通过拦截而不是逆向工程来观察它们的调用。因此,我不是在单个时间点解决单个网站,而是尝试开发一个广泛的解决方案,该解决方案可以观察任意脚本以及收集数据的变化以及收集方式的变化。

标签: javascript javascript-proxy


【解决方案1】:

我认为你把这件事弄得太复杂了。这里没有理由涉及代理,只需对这两种方法进行猴子补丁:

const proto = Storage.prototype;
const originalSet = proto.setItem;
const originalGet = proto.getItem;
Object.assign(proto, {
    setItem(key, value) {
        console.log(`Setting ${JSON.stringify(key)} to ${JSON.stringify(value)} on a ${this.constructor.name}`);
        return originalSet.call(this, key, value);
    },
    getItem(key) {
        console.log(`Getting ${JSON.stringify(key)} from a ${this.constructor.name}`);
        return originalGet.call(this, key);
    },
});

【讨论】:

  • 我会接受这个作为我的答案,因为它确实回答了“有没有更简单的方法”这个问题。但是,我试图在浏览器中包装大量对象,如果没有为每个网站进行逆向工程混淆脚本,我不知道所有预先使用的方法或属性。对于我可能需要的所有方法重复此选项对我来说是不切实际的,所以我将坚持我原来的方法。
  • @MarkJMiller 您实际上可以枚举所有全局本机对象及其原型和方法。至于代理内置方法(无论是 js 原生的还是宿主提供的),我们already discussed this in your last qustion - 仍然是同样的问题。
  • 为此答案添加了JSFiddle
猜你喜欢
  • 1970-01-01
  • 2016-06-22
  • 1970-01-01
  • 1970-01-01
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-19
相关资源
最近更新 更多