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 pythoncode.

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..