How we found Unintended bypass to exploiting entire CyberThreatForce discord server
Prologue
A few days ago, specifically on 2nd- 4th June 2021, I played CyberThreatForce CTF, a france CTF organized by CyberThreatForce Team. As for detail, it was clearly described on this webpage.
It’s been a while since I have a long-time period of hiatus, especially when it comes to Capture the Flag (CTF). So that, I didn’t really expect to solve much of challenges. With that resolve, I asked my teammate from AmpunBangJago team if I there’re something I could help during the competition. At last, I’ve given a main task to help with forensic & network category.
Shortly afterwards, the CyberThreatForce CTF had started. I managed to complete all my main task on 2nd day, even got 1st blood
on Network chall. Overall, it was a fun & painful experience since there're downtime and guessy challenges. But that's doesn't matter, since admin
were responsive & willing to lend an ear whenever there's a question.
With that, the event finally ended on 4th June 2021. Surprisingly, we managed to secure our position on 1st rank
. It was such a clutch game though, considering how close the score is. So props and ggwp for all teams.
Shortly afterwards, we exchanged and discussed solution on CyberThreatForce Discord. In a meantime, I was interested with one of challenges that we didn’t manage to solve during competition. So, I asked someone on a channel chat.
As you can see, it was clearly stated that you can inject python code
to manipulate the Discord bot. But, where does it come from?
Well, let’s talk about it, shall we?
Into the Challenge
First of all, I want to clarify that I’ve asked the author for a permission to disclose, so don’t judge me wrong, considering I don’t have any intention to steal any confidential information from it.
The original bot has been taken down, so all these things are considered as a case reproduction using the same code on different environment.
Challenge Description
For starter, this challenge came with a scenario where a discord channel was used to communicate with victims. The objective’s pretty clear, we need to obtain c2 role in order to access a certain channel, named #flag
.
The discord link itself was previously obtained on a pastebin as additional information from an OSINT challenge (Rush B). Inside of #general
channel, we're informed by organizer that we can talk to @Root
bot on a isolated-channel created by @Ticket Tool
.
Well then, let’s talk about bot’s supported command
. By sending ?help
command, a set of command
will be displayed.
As expected, there’re no functionality to obtain c2 role
. At this point, we also try to invite the bot
into a private server with enough permission
. Apparently, we found more hidden commands
to be analyzed.
Exactly! There’s ?giverole
that hopefully can be used to complete the objective. Unfortunately, our hope went down when whe noticed that it was permission-restricted
too.
Digging more deeper?
After playing around, we managed to discover that refresh
is suspicious enough since it returns an unusual variable_name
.
From this point of view, we can safely assumed that our user must be included on liste_admin
in order to run ?refresh
command. We also assumed that liste_admin
's kind of list/array
object. But, how can we accomplish such a thing?
Yup, it’s time for ?ping
to shine. As stated on list command before, it requires size
parameter which logically intended to be an integer
. So, what will happen if we pass non-integer
value?
Not enough? Well, let’s try to play by comparing expected argument with escaped ones, shall we?
Gotcha. All of them seems to be fine, considering there’s no stderr
returned. Also judging from our 3rd attempt, it's confirmed that the bot used python
where print
and #
character were evaluated properly.
Since multi-line
command was successfully executed, we came up with a potential scenario, such as:
exec(f"int('{size}')")
With these discovery, it’s clearly hinted that we have the capability to inject python
code.
Intended way
Our main objective’s to get our username
included on liste_admin
. If our assumption was right, we can apply append
to add our own username
Thankfully, it was successfully granted, so that we could access #flag
channel in order to retrieve the flag!
With our main objective achieved, let’s talk about the fun part, shall we?
Unintended way
Based on our previous research, we already know that it’s possible to inject any python
code. So, why don't we try to play with RCE stuff?
Let’s try with basic __import__
stuff
Huh, clever enough! Let’s try to modify the script with getattr
It works! But we couldn’t retrieved any stdout
. Let's try to pass it into a HTTP request then.
Voila, we got our first RCE!
Another hacks we could do is to utilize the predefined-argument ctx
from python-discord
. That's mean we could simply override ctx.author
and trigger an error, so that the final response will be like Requested by {stdout}
.
Easter Egg
It’s been a long way, but we haven’t talk about its security risk yet.
As we know that discord-bot using a special TOKEN
which is usually declared inside env or code itself. The token itself is used to communicate with Discord API
stuff including associated chat
, guild
, and channel
. It became more dangerous if the bot has more privileges
though.
Well, apparently using RCE we can leak these information, so that allow us to manipulate it as much as we want. In our case, the TOKEN
was hard-coded inside bot.py
At first, it was nothing special. But when we logged in using Discord bot client, it turned out that @Root
bot has already invited to organizer (admin) guild as a test bot. Moreover, it has enough permission to read any message, including flag and admin credentials
Fortunately, this issue was found after event has ended. So there was no significant impact happened. The bot and its token had been taken down & revoked, so it couldn’t be used again.
Conclusion
At last, we covered the potential risk from an exposed Discord Token
from an actual Capture the Flag (CTF) scenario. Originally, it could be considered as low-severity risk if only if there’re no associated user/channel/guild and it’s not exposed as a public service. But in the worst case scenario, an attacker can abuse this stuff as media of impersonation to log and collect user sensitive data, such as chat, shared-credentials, etc.
As for mitigation, it could be as simple as never trust any user input
. Always ensure that our code is properly filtered/sanitized when it comes to user input
. Keep in mind to avoid any usage function like eval/exec
since it can leads to code injection. At last, manage your discord bot
with a proper permission & role to ensure the user chat is sound and safe
Thanks for reading..