CVE-2021-41250: Other (NVD-CWE-Other)
value: Python discord bot is the community bot for the Python Discord community. In affected versions when a non-blacklisted URL and an otherwise triggering filter token is included in the same message the token filter does not trigger. This means that by including any non-blacklisted URL moderation filters can be bypassed. This issue has been resolved in commit 67390298852513d13e0213870e50fb3cff1424e0
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 4.3
Published: 2021-11-05T23:15Z
Repository: https://github.com/python-discord/bot
Language: Python
Files changed: bot/exts/filters/filtering.py, tests/bot/exts/filters/test_filtering.py
Commit date: 2021-11-05 12:31:05 +0000
Commit message: Merge pull request from GHSA-j8c3-8x46-8pp6

* Don't Exit Token Filtering Early On URLs

The token filtering function would exit early if it detected a URL
within the message, but it made no extra checks to ensure there weren't
other tokens within that message that would trigger it. This made
sense when the filtering logic was written, but it's been modified since
to introduce this bug.

Regression tests included.

Signed-off-by: Hassan Abouelela <hassan@hassanamr.com>

* Links Advisory In Token Filter Tests

Adds a link to the advisory with reasoning for the existence of the
 test.

Signed-off-by: Hassan Abouelela <hassan@hassanamr.com>
Diff: diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py
index 022b4ab0..f05b1d00 100644
--- a/bot/exts/filters/filtering.py
+++ b/bot/exts/filters/filtering.py
@@ -494,14 +494,10 @@ class Filtering(Cog):
         if SPOILER_RE.search(text):
             text = self._expand_spoilers(text)
 
         text = self.clean_input(text)
 
-        # Make sure it's not a URL
-        if URL_RE.search(text):
-            return False, None
-
         watchlist_patterns = self._get_filterlist_items('filter_token', allowed=False)
         for pattern in watchlist_patterns:
             match = re.search(pattern, text, flags=re.IGNORECASE)
             if match:
                 return match, self._get_filterlist_value('filter_token', pattern, allowed=False)['comment']
diff --git a/tests/bot/exts/filters/test_filtering.py b/tests/bot/exts/filters/test_filtering.py
new file mode 100644
index 00000000..8ae59c1f
--- /dev/null
+++ b/tests/bot/exts/filters/test_filtering.py
@@ -0,0 +1,40 @@
+import unittest
+from unittest.mock import patch
+
+from bot.exts.filters import filtering
+from tests.helpers import MockBot, autospec
+
+
+class FilteringCogTests(unittest.IsolatedAsyncioTestCase):
+    """Tests the `Filtering` cog."""
+
+    def setUp(self):
+        """Instantiate the bot and cog."""
+        self.bot = MockBot()
+        with patch("bot.utils.scheduling.create_task", new=lambda task, **_: task.close()):
+            self.cog = filtering.Filtering(self.bot)
+
+    @autospec(filtering.Filtering, "_get_filterlist_items", pass_mocks=False, return_value=["TOKEN"])
+    async def test_token_filter(self):
+        """Ensure that a filter token is correctly detected in a message."""
+        messages = {
+            "": False,
+            "no matches": False,
+            "TOKEN": True,
+
+            # See advisory https://github.com/python-discord/bot/security/advisories/GHSA-j8c3-8x46-8pp6
+            "https://google.com TOKEN": True,
+            "https://google.com something else": False,
+        }
+
+        for message, match in messages.items():
+            with self.subTest(input=message, match=match):
+                result, _ = await self.cog._has_watch_regex_match(message)
+
+                self.assertEqual(
+                    match,
+                    bool(result),
+                    msg=f"Hit was {'expected' if match else 'not expected'} for this input."
+                )
+                if result:
+                    self.assertEqual("TOKEN", result.group())

Vulnerable code:         # Make sure it's not a URL
        if URL_RE.search(text):
            return False, None

Fixed code: import unittest
from unittest.mock import patch

from bot.exts.filters import filtering
from tests.helpers import MockBot, autospec


class FilteringCogTests(unittest.IsolatedAsyncioTestCase):
    """Tests the `Filtering` cog."""

    def setUp(self):
        """Instantiate the bot and cog."""
        self.bot = MockBot()
        with patch("bot.utils.scheduling.create_task", new=lambda task, **_: task.close()):
            self.cog = filtering.Filtering(self.bot)

    @autospec(filtering.Filtering, "_get_filterlist_items", pass_mocks=False, return_value=["TOKEN"])
    async def test_token_filter(self):
        """Ensure that a filter token is correctly detected in a message."""
        messages = {
            "": False,
            "no matches": False,
            "TOKEN": True,

            # See advisory https://github.com/python-discord/bot/security/advisories/GHSA-j8c3-8x46-8pp6
            "https://google.com TOKEN": True,
            "https://google.com something else": False,
        }

        for message, match in messages.items():
            with self.subTest(input=message, match=match):
                result, _ = await self.cog._has_watch_regex_match(message)

                self.assertEqual(
                    match,
                    bool(result),
                    msg=f"Hit was {'expected' if match else 'not expected'} for this input."
                )
                if result:
                    self.assertEqual("TOKEN", result.group())