top 5 discord bot vulnerabilities
hello once again. it has been a while since I last posted. in this post, I will explain the 5 most common vulnerabilities that bot owners introduce in discord bots. I will include code, examples, and proof-of-concept exploits. this post is for educational purposes only. be cautious and comply with all applicable rules and laws. the code is in Python.
I. Missing Permissions Check
many bot developers make issues, missing permissions check is one of them. imagine you have a moderation bot, highly customizable through a dashboard. and the bot can ban people with the !ban command, example:
@bot.command()
async def ban(ctx, member: discord.Member):
await member.ban()
await ctx.send(f"{member} has been banned")
in this command you can clearly see that there’s no verification that the user calling the command is admin, which can be exploited meaning ANY user in the discord server can ban anyone without permissions. THIS EXACT BUG LEAD ME TO “HACK” WYVERN, GENERATING MORE THAN 1000 KEYS IN 10 MINUTES.
to fix this error you would need to add a verification of the user that’s running the command, it can be retrieved through the CONTEXT parameter (ctx).
@bot.command()
async def ban(ctx, member: discord.Member):
if not ctx.author.guild_permissions.ban_members:
await ctx.send("No permission")
await member.ban()
await ctx.send(f"{member} has been banned")
II. Rate Limiting
this issue leads to memory exhaust and DoS. if a bot developer doesn’t protect the bot / API from rate limiting an user could spam the commands where the vulnerable API is called and exhaust the memory inside it. an example of a vulnerable command would be:
@bot.command()
async def weather(ctx, city: str):
async with aiohttp.ClientSession() as s:
r = await s.get(f"https://api.weather.com/{city}")
data = await r.json()
await ctx.send(data["temp"])
the bot server doesn’t check if the user is spamming the command, leading to a rate limiting vulnerability. an example of a fix would be adding a built-in discord.py cooldown decorator:
@bot.command()
@commands.cooldown(1, 3, commands.BucketType.user)
async def weather(ctx, city: str):
async with aiohttp.ClientSession() as s:
r = await s.get(f"https://api.weather.com/{city}")
data = await r.json()
await ctx.send(data["temp"])
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandOnCooldown):
await ctx.send(f"Slow down. Retry in {error.retry_after:.1f}s.")
III. SQL / NoSQL injection
this vulnerability is very dangerous and can lead to your database being fully deleted. OWASP contains a very-well known vulnerability called SQL / NoSQL injection. if the bot command doesn’t check the user input it could lead to an unauthorized and dangerous command being executed inside your database. an example of a vulnerable code would be:
@bot.command()
async def profile(ctx, username: str):
cursor.execute(
f"SELECT * FROM users WHERE name = '{username}'"
)
row = cursor.fetchone()
await ctx.send(row["bio"])
as you can see, this code doesn’t check that the user input is legitimate, you could run: !profile ‘ OR ‘1’=’1, this command dumps all users inside the database, with all the rows. a fix would be using the built-in parameter adding inside sqlite3:
@bot.command()
async def profile(ctx, username: str):
cursor.execute(
"SELECT * FROM users WHERE name = ?", (username,)
)
row = cursor.fetchone()
await ctx.send(row["bio"])
IV. eval() / exec() on user input
eval and exec are used to execute a command inside a sandbox / system the bot is running on, running any commands in the user input. an example of a vulnerable code would be:
@bot.command()
async def evaluate(ctx, *, code: str):
result = eval(code)
await ctx.send(str(result))
this code doesn’t verify the code being evaluated so it just runs it with no checks. this could be fixed by using RestrictedPython:
@bot.command()
async def evaluate(ctx, *, code: str):
from RestrictedPython import compile_restricted, safe_globals
try:
byte_code = compile_restricted(code, "<string>", "eval")
result = eval(byte_code, safe_globals)
await ctx.send(str(result))
except Exception as e:
await ctx.send(f"Error: {e}")
V. User-controlled targets
many owners use commands like !dm or !announce to send direct messages to other users without restriction on who the caller can target. scammers use this to promote their scams or use it as a phishing delivery tool. an example of a vulnerable code would be:
@bot.command()
async def dm(ctx, member: discord.Member, *, message: str):
await member.send(message)
await ctx.send("Sent!")
in this command an user would be able to dm anyone with any message they want without restrictions, they could spam it to send phishing or scams to other server members. an example of a fix would be checking if the user has manage messages inside their server permissions, this would verify that the user has permissions and is authorized to dm other members with the bot:
@bot.command()
@commands.has_permissions(manage_messages=True)
async def modmail(ctx, member: discord.Member, *, message: str):
await member.send(message)
await ctx.guild.get_channel(AUDIT_LOG_CHANNEL).send("Sent!")
thank you for reading my post about discord bot vulnerabilities, I hope you devs can make better code and reduce scamming and phishing with discord bots inside the platform.