We have written a lot about SenseCon by now, but there is one more thing we can talk about! In this post I want to detail the Discord bot and associated challenges that we built. We were going to use Discord as our main communication channel and wanted a way to ensure that it was only accessible to Orange Cyberdefense hackers in an automated way.
This was a good opportunity to look into writing a Discord bot. If you are looking for the source code, you can find it here.
Discord python basics
I won’t do a deep dive into how to get a bot up and running as there are enough posts available for that, for example. Our Discord bot was implemented using the Discord Python API, which is event based. This means that should one of the available events occur on Discord, the SDK will trigger callbacks and perform the relevant and associated actions. An example of the on_message event being implemented is:
Seeing the various events that we can implement, I figured we could add a few challenges into this bot to make the interaction on Discord a little more fun. To keep track of which users had completed a challenge we assigned a role to them. These roles had the name
x was a specific challenge. We also had the bot announce that a user had completed a specific challenge with the hope that others will see that there are challenges to be solved.
The first challenge was called
challenge:sneaky, which was implemented using the on_raw_message_edit event. The idea behind this challenge was to watch for people going back to their messages and editing them. If they edited a message they were probably up to something and so we assigned them the
challenge:sneaky role. This challenge was the most common one to have been completed, probably because not everyone was being sneaky but rather fixing typos.
Like with any piece of software going into production without having gone through thorough edge case testing, we noticed a bug. Whenever a user posted a link, for instance to a GIF, the event would fire away as Discord automatically embedded the media into the message. People were unintentionally being assigned the
challenge:sneaky role for posting GIFs and YouTube videos.
This was later fixed by inspecting whether the edited message contained a list of embedded content. If the list was empty, it was safe to assume that the user did not post a link.
The next challenge was
challenge:hacker. While writing the code for the verification process I realised that my logic was flawed and could easily be bypassed, so we turned it into a challenge. Before we go into the details of the challenge lets describe the verification process quick. Once users accepted the Discord invite and selected their country flag, they had to send a direct message to the bot. The message had to be in the format of
!verify <their alias>@orangecyberdefense.com. The bot would then send an email with an OTP which they had to send back to the bot via a direct message.
The logic used to check that people provided their
@orangecyberdefense.com email, was to check for the domain anywhere in the received message string. The regex
([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+) was applied to extract email addresses provided by the user and the first email address extracted was then used to send the OTP. So you could easily bypass this verification process by providing two email addresses, the first would be your actual email and the second a random
@orangecyberdefense.com address. The payloads some people used to bypass this logic were:
That’s not all though. During one of our late nights coding away we almost introduced another bug. Have a look at the following; can you see the bug? Hint:
otp is the user supplied OTP.
The issue? Should a user enter the OTP incorrectly, their state server side would have changed the OTP to the number
0 to prevent brute force attacks. A quick note though, we have validations performed on the OTP number which required you to enter a 5 digit OTP. Now if you figured out that we clear the OTP to
0 on the first incorrect entry, then you could enter
00000 is casted to an int, it will turn to a
0 which would match the
0 that we set in the user’s state allowing you to bypass the OTP process. Bullet dodged.
The fix was to do the following:
Probably the hardest challenge to implement was
challenge:eavesdropper, due to race conditions. The initial approach was to have this challenge kick off when there was quite a bit of activity on the Discord server, which we monitored using the on_voice_state_update event. This event fires for a number of actions but the one we were interested in was whenever a member joined a voice channel. The bot would count how many active voice channels there were, and by active voice channels we mean at least one user connected. If the bot found that there was sufficient activity on the server, it would create a new voice channel called
Bots only and connect to it. Once connected to the channel, the bot would play a number of audio files, one of which contained Morse code. Once done, the bot would disconnect and delete the voice channel.
This implementation did not fair well due to race conditions. We looked at implementing a Lock which looked promising but later had to remove them due to some other changes which weren’t happy with the lock. We ended up changing the logic somewhat. The monitoring component was removed and the
Bots only voice channel was left active on the Discord server. Should a user connect to the channel, the bot would connect shortly after and begin to play the different audio files. Once done, the bot would disconnect from the voice channel until a user connected again.
The Morse code that was played contained the message
IAMSAD,NOONEEVERTELLSMEIAMBEAUTIFUL. The idea behind this was that peeps who extracted the message would then send a direct message to the bot saying it is in fact beautiful. A
on_message event handler was used to look out for any direct messages received which contained the word beautiful. This meant that the user was eavesdropping on the channel and the bot assigned the
The next challenge required a bit of team work. The challenge was called
challenge:mexicanwave, which was implemented using the
on_message event handler. Should ten unique users post the exact same message without a user disrupting the chain, they would be assigned the
challenge:mexicanwave role. Given the challenge name, people thought they had to post the Mexican flag and wave emoji’s to get it.
We had the bot post an
ole message after a Mexican wave was detected to stop the chain as we feared this could have gone into a long loop and lots of spam.
The last challenge, and the one that no one got without us giving a hint, was
challenge:fuzzer. We wanted people to identify themselves by the region they are based in, as we now have teams in various countries. This was done by having each user react to the welcome message with the flag of the country they are based in. For example, users from South Africa, reacted to the message with the South African emoji flag which the bot then assigned them the South Africa role.
This feature was implemented using the event handler on_raw_reaction_add to tell when a user had reacted to a message. This was a great place to add the opportunity for fuzzing. We added some code here to look out for users reacting to the welcome message with the emoji
:computer:. People would only find this if they attempted to perform some form of fuzzing by attempting to react with each emoji until they got the right one. This would earn them the
challenge:fuzzer role. Afterwards, the bot cleared the
:computer: reactions so as not to give away the challenge.
The bot didn’t only help us with the verification requirement and the various challenges. We also used the bot to help us with a password cracking challenge that we had running for the duration of SenseCon. This meant that we did not have to code up a web application to take care of user registration and session management. We could simply rely on the user id’s in Discord to track submissions and scores. Users who wanted to download or obtain the files could simply send a direct message to the bot with the following message
!download <challenge number>. To submit cracked passwords, the users had to attach a text file to a direct message to the bot with the text
!submit <challenge number>.
Because we know hackers like to cheat and are sometimes lazy, we also added some logic to prevent users from submitting large wordlists. Users who uploaded a file greater than 200000 bytes, got a stern warning from the bot but were also assigned a
lazy role which was announced to everyone.
Other features were also included in the bot, such as the ability to send a message as the bot to a specific channel and being able to extract role assignments. The bot was also used to assist with the Sconwar challenge registration.
Coding this bot was really fun and it turned out to be a great success for SenseCon. The source code for the bot can be found here: https://github.com/sensepost/sensecon_bot.