When I run window.localStorage
on https://stackoverflow.com
it returns the localStorage
but when I use the same command on Discord page it returns undefined
every time.
Is there something that Discord is doing to remove their localStorage
from the client side? And if so, is there a way to change the localStorage
before it is removed, prevent it from being removed or any other way to somehow access it before it is undefined
?
Anything help?
You can still obtain access programmatically. They’ve deleted window.localStorage
. This is an own-property of window instances, but it’s not a data property, it’s an accessor. The accessor’s get
function will return the localStorage
value associated with any appropriate receiver (this
arg)*. Deleting the localStorage
accessor has no impact on whether the object itself exists, so all you’ll need to do to access it again is find another get localStorage()
function. Fortunately, you can obtain one many ways — every Window object gets created with one.
// If we create an <iframe> and connect it to our document, its
// contentWindow property will return a new Window object with
// a freshly created `localStorage` property. Once we obtain the
// property descriptor, we can disconnect the <iframe> and let it
// be collected — the getter function itself doesn’t depend on
// anything from its origin realm to work**.
function getLocalStoragePropertyDescriptor() {
const iframe = document.createElement('iframe');
document.head.append(iframe);
const pd = Object.getOwnPropertyDescriptor(iframe.contentWindow, 'localStorage');
iframe.remove();
return pd;
}
// We have several options for how to use the property descriptor
// once we have it. The simplest is to just redefine it:
Object.defineProperty(window, 'localStorage', getLocalStoragePropertyDescriptor());
window.localStorage.heeeeey; // yr old friend is bak
// You can also use any function application tool, like `bind` or `call`
// or `apply`. If you hold onto a reference to the object somehow, it
// won’t matter if the global property gets deleted again, either.
const localStorage = getLocalStoragePropertyDescriptor().get.call(window);
If this is an arms race thing, the party interested in hiding localStorage could attempt patching every method and accessor throughout the DOM that could return a reference to a window object whose localStorage property has not already been deleted. That’s harder to do than might be obvious. Even if they succeeded, though, the final word will remain on the user side: a browser extension’s manifest can declare a content script that will be evaluated before any other code when the document loads. Even a CSP can’t prevent it. (On one hand this sucks ... on the other, without that capability, adblocking extensions could never work.)
The Storage API, whether sessionStorage or localStorage, isn’t meant for holding sensitive data. It’s not protected at all. An attempt to hide it is a hint that it may be getting misused — you might want to be careful about entering sensitive data on that site if its authors are under the impression that deleting the property is providing some kind of security.
* An appropriate receiver here would be any window object that hasn’t had storage disabled by the user (e.g. Safari incognito) and which has a ‘non-opaque’ origin (meaning for example that it doesn’t work in the browser’s
about:blank
page, since storage needs to be associated with a normal origin).Aside: bonus curio regarding global branded attributes.
** It doesn’t depend on its origin realm normally, anyway. Properties created from Web IDL attributes on [Global] interfaces can exhibit a unique behavior when the receiver is null or undefined, though, where the origin realm can become significant. If we were to call the
get
function with no receiver, e.g.const { get } = getLocalStoragePropertyDescriptor(); get(); // ... null?
...then it would, instead of throwing a TypeError like is typical, return null. In fact, all platform attributes, not just those on [Global] interfaces, have a default receiver. The default receiver, which kicks in if the given receiver is null or undefined, is the global object of the realm where the function was created. What’s different for the [Global] case is that this default can actually be, and usually is, a valid receiver — i.e. it can be an object that really does implement the interface whose attribute this is (any other functions would still end up throwing the same TypeError, so you’d never know about this default receiver).
In that example, it defaults to the window object from the original iframe. It tries to grab its
localStorage
object, but because the iframe is long gone, it instead returns null. I’m not actually sure where that last behavior is specified. I’m guessing it’s not specified, actually, since it contradicts the IDL definition for the attribute. It makes sense though (it’s gotta do something), and it’s what Chrome does. In Firefox, attempting this leads to an internal engine error, suggesting this edge case might just not have gotten a lot of consideration.
Like user AkiraMiura has already mentioned - "Discord" page developers had deleted the window.localStorage
property. They had done it like follows (citate from Reddit post):
How does Discord hide localStorage?
....(function() { var privateLocalStorage = window.localStorage; delete window.localStorage; console.log(privateLocalStorage); }()); console.log(window.localStorage); //undefined
They create a closure where all of their local storage operations are and copy
localStorage
to a variable and then delete the object from the window.
But we can use localStorage from iframes
If you want to restore use the localStorage on Discord page then you can take it from a new iframe with iframe.src = 'about:blank';
This gives you the possibility to work with stored items for "Discord" page. You could write:
window.localStorage = iframe.contentWindow.localStorage;
after iframe load, but this localStorage
object is from iframe and because of this you do not need to write it so - better is like in my code below.
The following code you can put on "Discord" page in developer console and you will get in console output something like this:
"45235333...WKDrSWDKIS"
Do not forget to login on "Discord" page before! (you don't need to have to be logged in on this page even).
Code
var iframe = document.createElement('iframe');
iframe.onload = function()
{
//Iframes must be appended to the DOM in order to be loaded
//Iframes do not load immediately nor synchronously
//now (after load) we can use iframe.contentWindow:
//window.localStorage = iframe.contentWindow.localStorage; //you can use it, but better is like:
var ifrLocalStorage = iframe.contentWindow.localStorage;
//But DO NOT: "document.body.removeChild(iframe);" because after it access on 'Storage' will be denied
console.log(ifrLocalStorage.getItem('fingerprint'));
//OUTPUT: "45235333...WKDrSWDKIS"
};
iframe.src = 'about:blank';
document.body.appendChild(iframe);
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With