CVE-2021-29493 (published 2021-05-06T20:15Z)

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.

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
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.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/kennnyshiwa/kennnyshiwa-cogs
commit_details: repo_url: https://github.com/kennnyshiwa/kennnyshiwa-cogs
author: Kenneth
author_date: 2021-05-05 21:13:11-04:00
msg: fix some shit
repository: repo_url: https://github.com/kennnyshiwa/kennnyshiwa-cogs
repo_name: kennnyshiwa/kennnyshiwa-cogs
description: Cogs for Red Discordbot 
repo_language: Python
owner: kennnyshiwa
file_changes: filename: core.py
change_type: ModificationType.MODIFY
diff: @@ -4,8 +4,19 @@ 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
@@ -15,53 +26,78 @@ class TicketsCore:
 
         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:
@@ -75,15 +111,20 @@ class TicketsCore:
 
         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:
@@ -98,65 +139,94 @@ class TicketsCore:
         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:"

programming_language: Python
filename: tickets.py
change_type: ModificationType.MODIFY
diff: @@ -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)

programming_language: Python