CVE-2021-29502: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: WarnSystem is a cog (plugin) for the Red discord bot. A vulnerability has been found in the code that allows any user to access sensible informations by setting up a specific template which is not properly sanitized. The problem has been patched in version 1.3.18. Users should update and type `!warnsysteminfo` to check that their version is 1.3.18 or above. As a workaround users may unload the WarnSystem cog or disable the `!warnset description` command globally.
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: 6.5
Published: 2021-05-10T18:15Z
Repository: https://github.com/retke/Laggrons-Dumb-Cogs
Language: Python
Files changed: warnsystem/api.py, warnsystem/warnsystem.py
Commit date: 2021-05-09 18:51:55 +0200
Commit message: [WarnSystem] Safely format embed descriptions

Diff: diff --git a/warnsystem/api.py b/warnsystem/api.py
index db8bad3..170e721 100644
--- a/warnsystem/api.py
+++ b/warnsystem/api.py
@@ -27,10 +27,30 @@ from . import errors
 log = logging.getLogger("red.laggron.warnsystem")
 _ = Translator("WarnSystem", __file__)
 id_pattern = re.compile(r"([0-9]{15,21})$")
 
 
+class SafeMember:
+    def __init__(self, member: discord.Member) -> None:
+        self.name = str(member.name)
+        self.display_name = str(member.display_name)
+        self.nick = str(member.nick)
+        self.id = str(member.id)
+        self.mention = str(member.mention)
+        self.discriminator = str(member.discriminator)
+        self.color = str(member.color)
+        self.colour = str(member.colour)
+        self.created_at = str(member.created_at)
+        self.joined_at = str(member.joined_at)
+
+    def __str__(self):
+        return self.name
+
+    def __getattr__(self, name):
+        return self
+
+
 class FakeRole:
     """
     We need to fake some attributes of roles for the class UnavailableMember
     """
 
@@ -620,13 +640,26 @@ class API:
             today = datetime.utcnow()
         if time:
             duration = self._format_timedelta(time)
         else:
             duration = _("*[No time given]*")
-        format_description = lambda x: x.format(
-            invite=invite, member=member, mod=author, duration=duration, time=today
-        )
+
+        def format_description(text):
+            try:
+                return text.format(
+                    invite=invite,
+                    member=SafeMember(member),
+                    mod=SafeMember(author),
+                    duration=duration,
+                    time=today,
+                )
+            except Exception:
+                log.error(
+                    f"[Guild {guild.id}] Failed to format description in embed", exc_info=True
+                )
+                return "Failed to format field."
+
         link = re.search(r"(https?://)\S+\.(jpg|jpeg|png|gif|webm)", reason)
 
         # embed for the modlog
         log_embed = discord.Embed()
         log_embed.set_author(name=f"{member.name} | {member.id}", icon_url=member.avatar_url)
diff --git a/warnsystem/warnsystem.py b/warnsystem/warnsystem.py
index a2bc74c..2f92706 100644
--- a/warnsystem/warnsystem.py
+++ b/warnsystem/warnsystem.py
@@ -222,11 +222,11 @@ class WarnSystem(SettingsMixin, AutomodMixin, BaseCog, metaclass=CompositeMetaCl
         self.cache = MemoryCache(self.bot, self.data)
         self.api = API(self.bot, self.data, self.cache)
 
         self.task: asyncio.Task
 
-    __version__ = "1.3.17"
+    __version__ = "1.3.18"
     __author__ = ["retke (El Laggron)"]
 
     # helpers
     async def call_warn(self, ctx, level, member, reason=None, time=None):
         """No need to repeat, let's do what's common to all 5 warnings."""

Vulnerable code:         format_description = lambda x: x.format(
            invite=invite, member=member, mod=author, duration=duration, time=today
        )
    __version__ = "1.3.17"
Fixed code: class SafeMember:
    def __init__(self, member: discord.Member) -> None:
        self.name = str(member.name)
        self.display_name = str(member.display_name)
        self.nick = str(member.nick)
        self.id = str(member.id)
        self.mention = str(member.mention)
        self.discriminator = str(member.discriminator)
        self.color = str(member.color)
        self.colour = str(member.colour)
        self.created_at = str(member.created_at)
        self.joined_at = str(member.joined_at)

    def __str__(self):
        return self.name

    def __getattr__(self, name):
        return self



        def format_description(text):
            try:
                return text.format(
                    invite=invite,
                    member=SafeMember(member),
                    mod=SafeMember(author),
                    duration=duration,
                    time=today,
                )
            except Exception:
                log.error(
                    f"[Guild {guild.id}] Failed to format description in embed", exc_info=True
                )
                return "Failed to format field."

    __version__ = "1.3.18"