CVE-2021-29493: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: Kennnyshiwa-cogs contains cogs for Red Discordbot. An RCE exploit has been found in the Tickets module of kennnyshiwa-cogs. This exploit allows discord users to craft a message that can reveal sensitive and harmful information. Users can upgrade to version 5a84d60018468e5c0346f7ee74b2b4650a6dade7 to receive a patch or, as a workaround, unload tickets to render the exploit unusable.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 8.8
Published: 2021-05-06T20:15Z
Repository: https://github.com/kennnyshiwa/kennnyshiwa-cogs
Language: Python
Files changed: tickets/core.py, tickets/tickets.py
Commit date: 2021-05-05 21:13:11 -0400
Commit message: fix some shit


Diff: diff --git a/tickets/core.py b/tickets/core.py
index c02e880..c2c8665 100644
--- a/tickets/core.py
+++ b/tickets/core.py
@@ -2,68 +2,104 @@ from redbot.core import Config
 from datetime import datetime
 import discord
 import random
 
 
-class TicketsCore:
+class SafeMember:
+    def __init__(self, member: discord.Member):
+        self.name = member.name
+        self.mention = member.mention
+
+    def __str__(self):
+        return self.name
+
+    def __getattr__(self, name):
+        return ""
 
+
+class TicketsCore:
     async def red_delete_data_for_user(self, **kwargs):
         """ Nothing to delete """
         return
 
     def __init__(self, bot):
         self.bot = bot
 
         self.config = Config.get_conf(self, identifier=2134287593)
         default_guild = {
-            'category': None,
-            'closed_category': None,
-            'ticket_role': None,
-            'default_message_ticket_channel': None,
-            'sessions': {}
+            "category": None,
+            "closed_category": None,
+            "ticket_role": None,
+            "default_message_ticket_channel": None,
+            "sessions": {},
         }
         self.config.register_guild(**default_guild)
 
-        self.ticket_info_format = '\n\n**[{datetime}]** [{author}]\n{information}'
+        self.ticket_info_format = "\n\n**[{datetime}]** [{author}]\n{information}"
 
     async def create_ticket(self, context):
         guild = context.guild
         author = context.author
 
-        ticket_role = [role for role in guild.roles if await self.config.guild(guild).ticket_role() == role.id]
+        ticket_role = [
+            role
+            for role in guild.roles
+            if await self.config.guild(guild).ticket_role() == role.id
+        ]
 
         if ticket_role:
             ticket_role = ticket_role[0]
         category_channel = await self.config.guild(guild).category()
-        default_message_ticket_channel = await self.config.guild(guild).default_message_ticket_channel()
-
-        if category_channel and category_channel in [category.id for category in guild.categories]:
-            n1 = 10**10
+        default_message_ticket_channel = await self.config.guild(
+            guild
+        ).default_message_ticket_channel()
+
+        if category_channel and category_channel in [
+            category.id for category in guild.categories
+        ]:
+            n1 = 10 ** 10
             n2 = n1 * 10 - 1
             ticket_id = int(random.randint(n1, n2))
-            ticket_channel = await guild.create_text_channel('{}-{}'.format(author.display_name, ticket_id),
-                                                             category=self.bot.get_channel(category_channel))
-
-            await ticket_channel.set_permissions(author, read_messages=True, send_messages=True)
-            await ticket_channel.set_permissions(guild.me, read_messages=True, send_messages=True, manage_channels=True)
-
-            await ticket_channel.edit(topic=self.ticket_info_format.format(ticket=ticket_id,
-                                      datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
-                                      author=author.display_name,
-                                      information='Ticket opened'))
+            ticket_channel = await guild.create_text_channel(
+                "{}-{}".format(author.display_name, ticket_id),
+                category=self.bot.get_channel(category_channel),
+            )
+
+            await ticket_channel.set_permissions(
+                author, read_messages=True, send_messages=True
+            )
+            await ticket_channel.set_permissions(
+                guild.me, read_messages=True, send_messages=True, manage_channels=True
+            )
+
+            await ticket_channel.edit(
+                topic=self.ticket_info_format.format(
+                    ticket=ticket_id,
+                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
+                    author=author.display_name,
+                    information="Ticket opened",
+                )
+            )
 
             if default_message_ticket_channel:
-                await ticket_channel.send(default_message_ticket_channel.format(member=author,
-                                                                                channel=ticket_channel,
-                                                                                origin=context.channel,
-                                                                                ticket_role=ticket_role))
+                try:
+                    await ticket_channel.send(
+                        default_message_ticket_channel.format(
+                            member=SafeMember(author),
+                            channel=ticket_channel,
+                            origin=context.channel,
+                            ticket_role=ticket_role,
+                        )
+                    )
+                except:
+                    return "Oops there has been an unexpected error with your new ticket message. Please contact the bot owner for assistance"
 
             async with self.config.guild(guild).sessions() as session:
-                    session.update({ticket_channel.id: author.id})
+                session.update({ticket_channel.id: author.id})
 
         else:
-            return 'Naughty! You need to run the setup first.'
+            return "Naughty! You need to run the setup first."
 
     async def update_ticket(self, context, status):
         try:
             await context.message.delete()
         except discord.Forbidden:
@@ -73,19 +109,24 @@ class TicketsCore:
         channel = context.channel
         author = context.author
 
         sessions = await self.config.guild(guild).sessions()
 
-        if str(channel.id) in sessions and await self.config.guild(guild).ticket_role() in [role.id for role in author.roles]:
-
-            ticket_id = str(channel.name).split('-')[1]
-            await channel.edit(topic=channel.topic+self.ticket_info_format.format(
-                                ticket=ticket_id,
-                                datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
-                                author=author.display_name,
-                                information=status)
-                               )
+        if str(channel.id) in sessions and await self.config.guild(
+            guild
+        ).ticket_role() in [role.id for role in author.roles]:
+
+            ticket_id = str(channel.name).split("-")[1]
+            await channel.edit(
+                topic=channel.topic
+                + self.ticket_info_format.format(
+                    ticket=ticket_id,
+                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
+                    author=author.display_name,
+                    information=status,
+                )
+            )
 
     async def close_ticket(self, context):
         try:
             await context.message.delete()
         except discord.Forbidden:
@@ -96,67 +137,96 @@ class TicketsCore:
         author = context.author
 
         sessions = await self.config.guild(guild).sessions()
 
         if str(channel.id) not in sessions:
-            return await channel.send("Make sure you are doing this within the ticket channel that you want to close.")
-        if await self.config.guild(guild).ticket_role() not in [role.id for role in author.roles]:
-            return await channel.send("You do not have the proper role to manage tickets")
+            return await channel.send(
+                "Make sure you are doing this within the ticket channel that you want to close."
+            )
+        if await self.config.guild(guild).ticket_role() not in [
+            role.id for role in author.roles
+        ]:
+            return await channel.send(
+                "You do not have the proper role to manage tickets"
+            )
         else:
             member = guild.get_member(sessions[str(channel.id)])
-            ticket_id = str(channel.name).split('-')[1]
+            ticket_id = str(channel.name).split("-")[1]
 
             closed_category = await self.config.guild(guild).closed_category()
             closed_category = self.bot.get_channel(closed_category)
 
-            await channel.set_permissions(member, read_messages=True, send_messages=False)
-            await channel.edit(category=closed_category,
-                               topic=channel.topic+self.ticket_info_format.format(
-                                    ticket=ticket_id,
-                                    datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
-                                    author=author.display_name,
-                                    information='Ticket closed'))
+            await channel.set_permissions(
+                member, read_messages=True, send_messages=False
+            )
+            await channel.edit(
+                category=closed_category,
+                topic=channel.topic
+                + self.ticket_info_format.format(
+                    ticket=ticket_id,
+                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
+                    author=author.display_name,
+                    information="Ticket closed",
+                ),
+            )
 
             async with self.config.guild(guild).sessions() as session:
-                    session.pop(channel.id, None)
+                session.pop(channel.id, None)
 
     async def purge_tickets(self, context):
         try:
             guild = context.guild
-            closed_channels = [channel for channel in guild.channels if channel.category_id == await self.config.guild(guild).closed_category()]
+            closed_channels = [
+                channel
+                for channel in guild.channels
+                if channel.category_id
+                == await self.config.guild(guild).closed_category()
+            ]
             for channel in closed_channels:
                 await channel.delete()
 
-            return 'All closed tickets removed!'
+            return "All closed tickets removed!"
         except discord.Forbidden:
-            return 'I need permissions to manage channels.'
+            return "I need permissions to manage channels."
 
     async def set_default_message_ticket_channel(self, context, message):
         guild = context.guild
 
         await self.config.guild(guild).default_message_ticket_channel.set(message)
 
-        return 'Your default message has been set.'
+        return "Your default message has been set."
 
     async def automatic_setup(self, context):
         guild = context.guild
 
         try:
             overwrites = {
-                guild.default_role: discord.PermissionOverwrite(send_messages=False, read_messages=False),
+                guild.default_role: discord.PermissionOverwrite(
+                    send_messages=False, read_messages=False
+                ),
             }
 
-            category_channel = await guild.create_category('Tickets', overwrites=overwrites)
-            closed_category_channel = await guild.create_category('Closed Tickets', overwrites=overwrites)
+            category_channel = await guild.create_category(
+                "Tickets", overwrites=overwrites
+            )
+            closed_category_channel = await guild.create_category(
+                "Closed Tickets", overwrites=overwrites
+            )
 
-            ticket_role = await guild.create_role(name='Ticket')
+            ticket_role = await guild.create_role(name="Ticket")
 
-            await category_channel.set_permissions(ticket_role, read_messages=True, send_messages=True)
-            await closed_category_channel.set_permissions(ticket_role, read_messages=True, send_messages=True)
+            await category_channel.set_permissions(
+                ticket_role, read_messages=True, send_messages=True
+            )
+            await closed_category_channel.set_permissions(
+                ticket_role, read_messages=True, send_messages=True
+            )
 
             await self.config.guild(guild).category.set(category_channel.id)
-            await self.config.guild(guild).closed_category.set(closed_category_channel.id)
+            await self.config.guild(guild).closed_category.set(
+                closed_category_channel.id
+            )
             await self.config.guild(guild).ticket_role.set(ticket_role.id)
 
-            return ':tada: Fabulous! You\'re all done! Now add the `Ticket` role to anyone who you deem good enough to handle tickets. And if you care, you can change the name of the role and category if you _really_ want to.'
+            return ":tada: Fabulous! You're all done! Now add the `Ticket` role to anyone who you deem good enough to handle tickets. And if you care, you can change the name of the role and category if you _really_ want to."
         except discord.Forbidden:
-            return 'That didn\'t go well... I need permissions to manage channels and manage roles. :rolling_eyes:'
+            return "That didn't go well... I need permissions to manage channels and manage roles. :rolling_eyes:"
diff --git a/tickets/tickets.py b/tickets/tickets.py
index 3d4dc22..2d6c5e1 100644
--- a/tickets/tickets.py
+++ b/tickets/tickets.py
@@ -1,71 +1,73 @@
 from redbot.core import commands
 from .core import TicketsCore
+import re
 
 BaseCog = getattr(commands, "Cog", object)
 
+
 class Tickets(BaseCog):
     def __init__(self, bot):
         self.bot = bot
         self.core = TicketsCore(bot)
 
-    @commands.group(name='ticket')
+    @commands.group(name="ticket")
     async def ticket(self, context):
-        '''
+        """
         Tickets!
-        '''
+        """
 
-    @ticket.command(name='new')
+    @ticket.command(name="new")
     async def ticket_new(self, context):
-        '''
+        """
         Create a new ticket
-        '''
+        """
         if context.invoked_subcommand is None:
             message = await self.core.create_ticket(context)
             if message:
                 await context.send(message)
 
-    @ticket.command(name='update')
+    @ticket.command(name="update")
     async def ticket_update(self, context, *, status: str):
-        '''
+        """
         Update the status of a ticket
-        '''
+        """
         await self.core.update_ticket(context, status)
 
-    @ticket.command(name='close')
+    @ticket.command(name="close")
     async def ticket_close(self, context):
-        '''
+        """
         Close a ticket, must be run in the ticket channel you want to close
-        '''
+        """
         await self.core.close_ticket(context)
 
-    @ticket.group(name='set')
+    @ticket.group(name="set")
     @commands.has_permissions(administrator=True)
     async def ticket_set(self, context):
-        '''
+        """
         Settings
-        '''
+        """
 
-    @ticket_set.command(name='purge')
+    @ticket_set.command(name="purge")
     async def ticket_set_purge(self, context):
-        '''
+        """
         Delete all closed tickets
-        '''
+        """
         message = await self.core.purge_tickets(context)
         await context.send(message)
 
-    @ticket_set.command(name='message')
+    @ticket_set.command(name="message")
     @commands.has_permissions(administrator=True)
     async def ticket_set_message(self, context, *, message: str):
-        '''
+        """
         Set the default message when a new ticket has been created (markdown safe)
-        '''
+        """
         message = await self.core.set_default_message_ticket_channel(context, message)
         await context.send(message)
 
-    @ticket_set.command(name='setup')
+    @ticket_set.command(name="setup")
     async def ticket_setup(self, context):
-        '''
+        """
         Automatic setup, will create two categories for open and closed tickets, and a ticket role for people to be able to manage tickets.
-        '''
+        """
         message = await self.core.automatic_setup(context)
         await context.send(message)

Vulnerable code: class TicketsCore:
            'category': None,
            'closed_category': None,
            'ticket_role': None,
            'default_message_ticket_channel': None,
            'sessions': {}
        self.ticket_info_format = '\n\n**[{datetime}]** [{author}]\n{information}'
        ticket_role = [role for role in guild.roles if await self.config.guild(guild).ticket_role() == role.id]
        default_message_ticket_channel = await self.config.guild(guild).default_message_ticket_channel()

        if category_channel and category_channel in [category.id for category in guild.categories]:
            n1 = 10**10
            ticket_channel = await guild.create_text_channel('{}-{}'.format(author.display_name, ticket_id),
                                                             category=self.bot.get_channel(category_channel))

            await ticket_channel.set_permissions(author, read_messages=True, send_messages=True)
            await ticket_channel.set_permissions(guild.me, read_messages=True, send_messages=True, manage_channels=True)

            await ticket_channel.edit(topic=self.ticket_info_format.format(ticket=ticket_id,
                                      datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
                                      author=author.display_name,
                                      information='Ticket opened'))
                await ticket_channel.send(default_message_ticket_channel.format(member=author,
                                                                                channel=ticket_channel,
                                                                                origin=context.channel,
                                                                                ticket_role=ticket_role))
                    session.update({ticket_channel.id: author.id})
            return 'Naughty! You need to run the setup first.'
        if str(channel.id) in sessions and await self.config.guild(guild).ticket_role() in [role.id for role in author.roles]:

            ticket_id = str(channel.name).split('-')[1]
            await channel.edit(topic=channel.topic+self.ticket_info_format.format(
                                ticket=ticket_id,
                                datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
                                author=author.display_name,
                                information=status)
                               )
            return await channel.send("Make sure you are doing this within the ticket channel that you want to close.")
        if await self.config.guild(guild).ticket_role() not in [role.id for role in author.roles]:
            return await channel.send("You do not have the proper role to manage tickets")
            ticket_id = str(channel.name).split('-')[1]
            await channel.set_permissions(member, read_messages=True, send_messages=False)
            await channel.edit(category=closed_category,
                               topic=channel.topic+self.ticket_info_format.format(
                                    ticket=ticket_id,
                                    datetime=datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S'),
                                    author=author.display_name,
                                    information='Ticket closed'))
                    session.pop(channel.id, None)
            closed_channels = [channel for channel in guild.channels if channel.category_id == await self.config.guild(guild).closed_category()]
            return 'All closed tickets removed!'
            return 'I need permissions to manage channels.'
        return 'Your default message has been set.'
                guild.default_role: discord.PermissionOverwrite(send_messages=False, read_messages=False),
            category_channel = await guild.create_category('Tickets', overwrites=overwrites)
            closed_category_channel = await guild.create_category('Closed Tickets', overwrites=overwrites)
            ticket_role = await guild.create_role(name='Ticket')
            await category_channel.set_permissions(ticket_role, read_messages=True, send_messages=True)
            await closed_category_channel.set_permissions(ticket_role, read_messages=True, send_messages=True)
            await self.config.guild(guild).closed_category.set(closed_category_channel.id)
            return ':tada: Fabulous! You\'re all done! Now add the `Ticket` role to anyone who you deem good enough to handle tickets. And if you care, you can change the name of the role and category if you _really_ want to.'
            return 'That didn\'t go well... I need permissions to manage channels and manage roles. :rolling_eyes:'
    @commands.group(name='ticket')
        '''
        '''
    @ticket.command(name='new')
        '''
        '''
    @ticket.command(name='update')
        '''
        '''
    @ticket.command(name='close')
        '''
        '''
    @ticket.group(name='set')
        '''
        '''
    @ticket_set.command(name='purge')
        '''
        '''
    @ticket_set.command(name='message')
        '''
        '''
    @ticket_set.command(name='setup')
        '''
        '''
Fixed code: class SafeMember:
    def __init__(self, member: discord.Member):
        self.name = member.name
        self.mention = member.mention

    def __str__(self):
        return self.name

    def __getattr__(self, name):
        return ""

class TicketsCore:
            "category": None,
            "closed_category": None,
            "ticket_role": None,
            "default_message_ticket_channel": None,
            "sessions": {},
        self.ticket_info_format = "\n\n**[{datetime}]** [{author}]\n{information}"
        ticket_role = [
            role
            for role in guild.roles
            if await self.config.guild(guild).ticket_role() == role.id
        ]
        default_message_ticket_channel = await self.config.guild(
            guild
        ).default_message_ticket_channel()

        if category_channel and category_channel in [
            category.id for category in guild.categories
        ]:
            n1 = 10 ** 10
            ticket_channel = await guild.create_text_channel(
                "{}-{}".format(author.display_name, ticket_id),
                category=self.bot.get_channel(category_channel),
            )

            await ticket_channel.set_permissions(
                author, read_messages=True, send_messages=True
            )
            await ticket_channel.set_permissions(
                guild.me, read_messages=True, send_messages=True, manage_channels=True
            )

            await ticket_channel.edit(
                topic=self.ticket_info_format.format(
                    ticket=ticket_id,
                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
                    author=author.display_name,
                    information="Ticket opened",
                )
            )
                try:
                    await ticket_channel.send(
                        default_message_ticket_channel.format(
                            member=SafeMember(author),
                            channel=ticket_channel,
                            origin=context.channel,
                            ticket_role=ticket_role,
                        )
                    )
                except:
                    return "Oops there has been an unexpected error with your new ticket message. Please contact the bot owner for assistance"
                session.update({ticket_channel.id: author.id})
            return "Naughty! You need to run the setup first."
        if str(channel.id) in sessions and await self.config.guild(
            guild
        ).ticket_role() in [role.id for role in author.roles]:

            ticket_id = str(channel.name).split("-")[1]
            await channel.edit(
                topic=channel.topic
                + self.ticket_info_format.format(
                    ticket=ticket_id,
                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
                    author=author.display_name,
                    information=status,
                )
            )
            return await channel.send(
                "Make sure you are doing this within the ticket channel that you want to close."
            )
        if await self.config.guild(guild).ticket_role() not in [
            role.id for role in author.roles
        ]:
            return await channel.send(
                "You do not have the proper role to manage tickets"
            )
            ticket_id = str(channel.name).split("-")[1]
            await channel.set_permissions(
                member, read_messages=True, send_messages=False
            )
            await channel.edit(
                category=closed_category,
                topic=channel.topic
                + self.ticket_info_format.format(
                    ticket=ticket_id,
                    datetime=datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"),
                    author=author.display_name,
                    information="Ticket closed",
                ),
            )
                session.pop(channel.id, None)
            closed_channels = [
                channel
                for channel in guild.channels
                if channel.category_id
                == await self.config.guild(guild).closed_category()
            ]
            return "All closed tickets removed!"
            return "I need permissions to manage channels."
        return "Your default message has been set."
                guild.default_role: discord.PermissionOverwrite(
                    send_messages=False, read_messages=False
                ),
            category_channel = await guild.create_category(
                "Tickets", overwrites=overwrites
            )
            closed_category_channel = await guild.create_category(
                "Closed Tickets", overwrites=overwrites
            )
            ticket_role = await guild.create_role(name="Ticket")
            await category_channel.set_permissions(
                ticket_role, read_messages=True, send_messages=True
            )
            await closed_category_channel.set_permissions(
                ticket_role, read_messages=True, send_messages=True
            )
            await self.config.guild(guild).closed_category.set(
                closed_category_channel.id
            )
            return ":tada: Fabulous! You're all done! Now add the `Ticket` role to anyone who you deem good enough to handle tickets. And if you care, you can change the name of the role and category if you _really_ want to."
            return "That didn't go well... I need permissions to manage channels and manage roles. :rolling_eyes:"
import re

    @commands.group(name="ticket")
        """
        """
    @ticket.command(name="new")
        """
        """
    @ticket.command(name="update")
        """
        """
    @ticket.command(name="close")
        """
        """
    @ticket.group(name="set")
        """
        """
    @ticket_set.command(name="purge")
        """
        """
    @ticket_set.command(name="message")
        """
        """
    @ticket_set.command(name="setup")
        """
        """