16

This question is about the property disguiseToken itself, not about the error directly.

disguiseToken does not show up in the codebase and a google search does not bring up anything.

  • What is disguiseToken?
  • What is it a property of?
  • What is trying to access this property?

The error happens when calling getOwnPropertyDescriptor.


Example stack trace :

TypeError: Cannot read property 'disguiseToken' of undefined
at getOwnPropertyDescriptor (eval at  (:1:38695), :560:24)
at Function.Object.getOwnPropertyDescriptor (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:39700)
at a (https://www.mywebsite.com/main.25a9fda6ea42f4308b79.js:1:1075141)
at https://www.mywebsite.com/5.6c58d8732681a35a1f8b.js:1:1533
at Object.26NW (https://www.mywebsite.com/5.6c58d8732681a35a1f8b.js:1:1805)
at i (https://www.mywebsite.com/runtime.8928e149b3f1200cf1ca.js:1:507)
at Module.L6id (https://www.mywebsite.com/5.6c58d8732681a35a1f8b.js:1:62181)
at i (https://www.mywebsite.com/runtime.8928e149b3f1200cf1ca.js:1:507)
at https://www.mywebsite.com/main.25a9fda6ea42f4308b79.js:1:914848
at t.invoke (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:8160)
at M (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:14076)
at M (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:13634)
at https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:14864
at t.invokeTask (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:8844)
at Object.onInvokeTask (https://www.mywebsite.com/main.25a9fda6ea42f4308b79.js:1:467756)
at t.invokeTask (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:8765)
at e.runTask (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:4026)
at g (https://www.mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:11111)

This error happens in multiple browsers with different engines. Over the past 3 months (today is 2020-06-13) we have seen it in :

  • Chrome
  • Mobile Safari
  • Chrome Mobile
  • Firefox
  • Samsung Internet

Update (1) :

I totally understand what the error means. My question is what is a 'disguise Token'? I've never heard of that, and it's not something in my code. I searched my entire code base for the word 'disguise', and no hits, so it must be something inside of Angular. What the heck is it? I am getting this error a few dozen times a day, but I have no idea what it is. I am running Angular 7.

Looking through the source code of angular and angular.js does not give any results for disguiseToken


Update (2) :

This code results in the exact error message as seen in the error reports. This snippet was not taken from actual source code. It is included here so others can reproduce the error.

<script type="application/javascript">
    try {
        class Dummy extends HTMLElement {
            get dummyFuncA() {
                return 'dummy';
            }
        }

        var old = Object.getOwnPropertyDescriptor;
        Object.getOwnPropertyDescriptor = function(obj, property) {
            var descriptor = old.call(this, obj, property);

            var _disguiseToken = descriptor.disguiseToken;

            return descriptor;
        };

        var descriptorA = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummyFuncA');
        var descriptorB = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummyFuncB');
    } catch (err) {
        console.warn(err);
    }
</script>

This snippet on it's own can be used to test if Object.getOwnPropertyDescriptor has been overridden :

<script type="application/javascript">
    try {
        class Dummy extends HTMLElement {
            get dummyFuncA() {
                return 'dummy';
            }
        }

        var descriptorA = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummyFuncA');
        var descriptorB = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummyFuncB');
    } catch (err) {
        if (
            err &&
            err.toString &&
            err.toString().indexOf('Cannot read property \'disguiseToken\' of undefined') > -1
        ) {
            console.log('Something did override \'Object.getOwnPropertyDescriptor\'');
            console.warn(err);
            console.log(Object.getOwnPropertyDescriptor);
        }
    }
</script>

Update (3) :

We added the above but didn't learn much :/

Log for console.log(Object.getOwnPropertyDescriptor); :

function getOwnPropertyDescriptor() { [native code] }

So that seems to be fine.

We also added logs to send all script tag source url's or innerHTML to our bug reporting service when this error happens. This did not yield any unexpected results.


Update (4) :

We added code and logs to trap use of Proxy. This is not something we use in our code bases and this can theoretically also be part of this issue.

A few hours later we had some hits and it does seem that something is injecting code that creates Proxy's. This is the first direct evidence of code that isn't ours running on these pages.

Stacktrace captured when something tried to construct a Proxy :

[redacted-url]:72:24 
[redacted-url]:356:25 ObjectWithDefaultValues
[redacted-url]:404:59 parseMetaTags
[redacted-url]:453:18 
[redacted-url]:465:3 global code

The first row with line 72 is where we called new Error(). Lines 356-456 are not our code but something that was injected.

The code we used to detect Proxy use :

<script type="text/javascript">
    var proxyUses = [];

    function reportProxyUse() {
        if (true) { // [redacted] check if error reporting is ready
            for (var i = 0; i < proxyUses.length; i++) {
                console.log('target', proxyUses[i].target);
                console.log('handler', proxyUses[i].handler);
                
                // [redacted]
                // error reported here
            }

            proxyUses = [];
        } else {
            setTimeout(function() {
                reportProxyUse();
            }, 2000);
        }
    }

    window.Proxy = function( target, handler ) {
        proxyUses.push({
            'target': target,
            'handler': handler,
            'error': new Error() // this captures a stack trace
        });

        reportProxyUse();
        return target;
    };
</script>

We will add a MutationObserver next to try and trap all scripts. Even those that delete themselves.


Update (5) :

Haven't analysed this bit fully yet but it seems that "Add to Reader list" in Safari does an initial render of a page and injects the Javascript that includes the Proxy stuff.

As this is obviously only Safari the use of Proxy and the stacktrace above does not explain disguiseToken.

R Menke
  • 8,183
  • 4
  • 35
  • 63
Brian Kitt
  • 665
  • 1
  • 6
  • 20
  • 1
    can you share some code to understand the problem – TheParam Mar 16 '19 at 06:18
  • 2
    recreating this on stackblitz will make it easier to investigate – Akber Iqbal Mar 16 '19 at 06:36
  • The problem is, that I really don't know where it's coming from. I make heavy use of SPA concepts, and yes, I do have the URL where this is happening, but it could be anywhere in 1,000+ lines of code. I just have no clue where to even look for this. since I don't use the term 'disguiseToken' anywhere ever in my code, I have no idea where that is coming. – Brian Kitt Mar 19 '19 at 20:33
  • Found something? I'm experiencing this too. – Dima Grossman Sep 24 '19 at 08:11
  • No, I haven't I get these errors every single day, and have no choice but to ignore them. I've never found a good reason for it. The problem is random, and I have a large app, and it could be anywhere. People have asked that I post sample source, but I don't know where it's happening, and I can't recreate it. – Brian Kitt Sep 25 '19 at 14:28
  • @BrianKitt I have added more info and some updates after some research. Hopefully you approve of these edits. – R Menke Jun 13 '20 at 15:42
  • Virus? `disguise` + `eval` + (as I understand) no real business errors on the page + recent appearance, kinda implies the possibility. Also looking at `polyfills.d8680adf69e7ebd1de57.js:1:39700` and posting the content here (or better yet, posting the link to the page) would be a good starting point (even if it's minimized). – x00 Jun 13 '20 at 16:03
  • Also have you tried deploying the app from the scratch on another server? – x00 Jun 13 '20 at 16:09
  • @x00 this is unrelated to hosting/deployment. We did a search for `disguiseToken` in the source code, node_modules folder, build output and hosted files. No results in any of those. The files are hosted in a GCS bucket and the hosted files match the build output, the files have not been tampered with. Finding `getOwnPropertyDescriptor` calls is not an issue and we can alter/remove these. That does not explain what `disguiseToken` actually is. – R Menke Jun 13 '20 at 16:18
  • 1
    This is a common trait of many if not almost all of viruses to cipher there own code. And you don't even need to do some fun staff like `(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]] === "fail"` to resist the search of `"disguiseToken`. Doing `"disg"+ "gui"+"seTo"+"ken"` will be enough – x00 Jun 13 '20 at 16:27
  • If you can find `getOwnPropertyDescriptor` and alter/remove these, why can't you find `disguiseToken`? And are you looking in **all** dependencies? And against what snapshot are you checking that non of your dependencies were tampered with? (Sorry I don't really know how GCS bucket works) – x00 Jun 13 '20 at 16:32
  • We were thinking more in the direction of a browser build in, or a browser extension. This would explain why we can not find `disguiseToken ` in our sources. Although it is technically possible that it was obfuscated. – R Menke Jun 13 '20 at 16:35
  • Same extension in **all** browsers of **all** users who've experienced this issue? I doubt that. At the same time catching a virus is as simple as `npm install` https://duo.com/decipher/hunting-malicious-npm-packages and you don't even have to use some bogus package, sometimes wellknown packages get hijacked – x00 Jun 13 '20 at 16:57
  • 1 ) Over the last 90 days we had 110 error events over ±50 sites with about 3 pageviews per seconds. It might be that code is injected somehow (we do not see how and see no evidence of this) but that the injected code only throws errors in very rare cases. Or it might be that some users have a different environment (browser extension, ...) which causes the error. 2 ) Browser extensions are mostly just javascript and these are portable between different vendors. 3 ) A GCS bucket is similar to an S3 bucket or other static file hosting solutions. There is no server process involved. – R Menke Jun 13 '20 at 17:07
  • Ah! So you never saw the error with your own eyes, only in log? Then yes, it can be an extension. And for the reasons from my first comment it still looks like a malicious one. But in that case a lot of sites will have that error in their logs and surely it have been noticed after 3 months. So probably you'll have you answer soon from someone who saw it too. – x00 Jun 13 '20 at 18:14
  • @x00 Small correction from my previous comment: our error logs are only retained for 90 days, the error has been around for longer. We will also look into your suggestions. Aside from that it might be good to create a minimal code snippet that can trigger the error (we will try some things). We can include this snippet hard coded in each footer, bypassing any build process and dependencies. This might also give some interesting reports. Other people can then also test this snippet and maybe someone spots the origin. – R Menke Jun 13 '20 at 18:30
  • Could also be some kind of security software injecting it's own JavaScript: network firewall (could be transparent = totally invisible) or antivirus software intercepting traffic as transparent proxy. – Sebastian B. Jun 13 '20 at 20:34
  • @SebastianB. Our traffic is https only, would this be possible over https? I thought encryption prevented proxies or ISP's modifying responses? – R Menke Jun 13 '20 at 20:50
  • 1
    @RMenke yes, this is possible, but *only* if the client has been modified by installing a new certificate authority to the trusted root CA store. Then the software can act as man-in-the-middle. For corporate networks, this is not uncommon, and some antivirus software does this also locally. See https://en.wikipedia.org/wiki/Man-in-the-middle_attack – Sebastian B. Jun 13 '20 at 21:25
  • We have added more logs to try and capture with what `getOwnPropertyDescriptor` is overridden. It might take some time after deploying before improved reports come in (caches...) – R Menke Jun 14 '20 at 18:16
  • @RMenke, 110 errors in 90 days gives an average of 1 error per day... so any luck yet? – x00 Jun 17 '20 at 09:42
  • @x00 a few events have come in and updated the question above. We however only learned that we seem to be looking in the wrong place :/ – R Menke Jun 17 '20 at 14:57
  • @RMenke. Hmm... `Function.Object.getOwnPropertyDescriptor` is not the same as `Object.getOwnPropertyDescriptor`. As the matter of fact `Function` originally doesn't have property `Object`. Yes, it looks like you really looking in the wrong place :). Try `console.log(Function.Object.getOwnPropertyDescriptor)` – x00 Jun 17 '20 at 16:14
  • @x00 Yes we noticed that too. Function inherits from Object. But static class functions are not inherited. The OP sees `Function.Object.getOwnPropertyDescriptor` in error logs, but we see `Function.getOwnPropertyDescriptor`. Our source code however definitely is `Object.getOwnPropertyDescriptor`. No idea what would be calling `Function.Object.getOwnPropertyDescriptor` or `Function.getOwnPropertyDescriptor` as we only make a call to `Object.getOwnPropertyDescriptor` and this is native code. – R Menke Jun 17 '20 at 16:25
  • 2
    @RMenke, but the possible suspects so far are: malicious packages, extensions or antiviruses. Non of these is your code. – x00 Jun 17 '20 at 18:01
  • @x00 Updated the question above. We got lucky today and caught something! – R Menke Jun 18 '20 at 16:43
  • @RMenke. Great! By the way, how do you log the error in the first place? With `window.onerror` or with `try-catch`? And how do you trap `Proxy` usage? This is something new to me. – x00 Jun 19 '20 at 11:04
  • @x00 Updated the question with the snippet we used. – R Menke Jun 19 '20 at 11:46
  • Yes, of course, silly me - it's JS you can always do staff like that :) But the main question was about `window.onerror`. And a couple more ideas: 1) Can you/Do you collect IP addresses in your logs? Is there any pattern? For example MiM attacks can be possible in some countries even within `https`... Or it can be even a single IP of just one infected user. 2) Do you consider a possibility of a mandate JS injection through user input, or there is no user inputs on your sites? – x00 Jun 19 '20 at 12:20
  • @RMenke, shouldn't the `new Error()` in the trap bring some interesting insights worth sharing? – x00 Jun 19 '20 at 12:27
  • https://gitlab.com/minds/front/-/issues/1864 they have the same error as you guys – user120242 Jun 19 '20 at 12:31
  • 1
    @x00 the `new Error()` and `console.log(...)` did not reveal anything conclusive yet. We first wanted too see if `Proxy` was in play. Now that we know that, we can add more specific reporting. We don't deploy on fridays and weekends, so monday we hope to know more. – R Menke Jun 19 '20 at 12:46
  • 1
    @user120242 true, we also found that. Unfortunately they closed it without comment :/ – R Menke Jun 19 '20 at 12:47
  • Maybe if you cross check npm packages and the module call tree (which looks pretty similar) you might find some clues? – user120242 Jun 20 '20 at 00:24
  • Might be totally unrelated but a search through GH brings up [js-reflection](https://github.com/demetriusj/js-reflection/blob/e7f473301c53f4501983df95a8e2e747aefdca91/lib/reflection.js#L19) for `parseMetaTags`. It is not unreasonable that injected code that tries to hijack page code needs some reflection. – R Menke Jun 20 '20 at 10:52
  • 1
    Would make for a really good xss injection vector if you used that code anywhere. Basically an open door to eval. Probably unrelated though – user120242 Jun 20 '20 at 11:08
  • @x00 you got my vote! Hopefully we will soon find the root cause so that you can update your answer ;) – R Menke Jun 20 '20 at 11:31
  • 1
    You could try overwriting `eval` and log the string that should be evaluated and the source code of the call stack using [Function.caller](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller). It is also strange how the call stack does not include `at eval`, I've not found any way to achieve that. – Wendelin Jun 20 '20 at 20:23
  • 1
    @RMenke, I'll definitely wait for updates on the matter. – x00 Jun 21 '20 at 14:03
  • @RMenke. If you test one hypothesis at a time, it will take forever :) 1) I suppose you can reproduce Safari's `Proxy` trick locally? 2) Take a step back, and start from the initial error: as @Wendelin suggested override `eval` (keeping its functionality but also logging), and also try to deploy your code without minification. 3) As I understand, no injections were found with `MutationObserver`? – x00 Jun 24 '20 at 07:59
  • Maybe you can also try https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP Probably it's a good measure in any case. – x00 Aug 04 '20 at 08:18

1 Answers1

3

The jury is still out there. Just to sum up the current state and the comments:

  1. disguise + eval + no errors noticeable by users on the pages + inability to find the string. All of that implies the possibility of malicious npm package. With probably obfuscated code, that's why search for disguiseToken yields nothing. Running npm install is all you need to get one. Sometimes even respectable packages get hijacked.

  2. But there're some other possibilities

    1. injection by browser extension
    2. injection by user's software (firewalls, antiviruses or viruses, and God knows what else)
    3. injection by some lousy ISP. Even with https it is still possible, at least in some countries (Kazakhstan). Or maybe in come corporate networks.
    4. manual injection by a user
  3. It seams to be already figured out that the issue arises outside of the own code base. But that was known from the starts.

  4. Looks like the issue arises from within a Proxy. But that's not nessasery so from what we can see in the question now:

    • The fact that some code creates a Proxy doesn't mean that the error is thrown from that Proxy.
    • And because you prevent creation of the proxy by just returning the target, you shouldn't see the error.
    • And if you do - then it's not the Proxy that causes the troubles
    • And if you don't - it doesn't mean much because the error rarely occurs.
  5. What can be done? Some open questions:

    1. If you use try-catch for your logs it will capture only error from inside of your code and it's dependencies (implies malicious npm package).
    2. window.onerror - it can catch some errors from some injected scripts (those that was injected an thrown after window.onerror handler was set up)
    3. MutationObserver can catch scripts that also injected only after it was set up, and only those that was injected from the scripts that already present on a page. That again means - inside of your code's dependencies. It won't catch MiM scripts because in this case a user loads a page with whose scripts already there. So you probably should log <head> as well. Right after page load. And on the error. (But script can be as well injected at the end of HTML or elsewhere).
    4. Although there is a possibility that a page got modified only after browser loads it. That's probable with extension. Less probable with antiviruses. Improbable with MiM.
    5. Get user's IP addresses, maybe you'll find some patterns there. Even if you don't use server it looks like it's still possible: How to get client's IP address using JavaScript?
    6. As @user120242 have noted, someone had the same issue: https://gitlab.com/minds/front/-/issues/1864 I've posted a comment there asking for help, but no reply yet. The reply unfortunately was:

      I don't think we made any changes to fix this, it was closed because it stopped getting picked up in Sentry. I guess a vendor update resolved the underlying issue.

x00
  • 13,643
  • 3
  • 16
  • 40
  • 5.3 : `MutationObserver` should catch all script tags defined, added or deleted after the `MutationObserver` was created. If MiM injected scripts are defined after the `MutationObserver` they will be caught. Not sure if there is a way to detect a self deleting injected script that is placed as the first node in the HTML head. – R Menke Jun 20 '20 at 10:38
  • 5.5 : This is not an option for everyone because of GPDR. We can not capture client IP's because of this. – R Menke Jun 20 '20 at 10:39
  • @RMenke According to GDPR: "**Processing shall be lawful** only **if** and to the extent that **at least one** of the following **applies**: [...] d) processing is **necessary** in order **to protect** the vital **interests of the data subject** or of **another** natural **person**" But haven't read it all yet. – x00 Jun 24 '20 at 08:36