From 3b96ec1c9e6974229796bc36aa9bc6fad6829b29 Mon Sep 17 00:00:00 2001 From: Crozzers Date: Thu, 14 May 2026 22:23:12 +0100 Subject: [PATCH 1/3] Fix XSS fromn HTML encoded colons in hrefs --- lib/markdown2.py | 4 +++- test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 2 ++ test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/markdown2.py b/lib/markdown2.py index dc698970..6683df7c 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -1537,8 +1537,10 @@ def _safe_href(self): safe = r'-\w' # omitted ['"<>] for XSS reasons less_safe = r'#/\.!#$%&\(\)\+,/:;=\?@\[\]^`\{\}\|~' + # html encoded colon in a URL still functions as a normal colon, so need to detect those + protocol_seperators = [':', ':', ':', ':'] # dot seperated hostname, optional port number, not followed by protocol seperator - domain = r'(?:[{}]+(?:\.[{}]+)*)(?:(?<code>" onerror="alert(1)//</code>

A

+ +

x

diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text index 4a5c25a8..12d54edb 100644 --- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text +++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text @@ -2,4 +2,6 @@ ![`" onerror="alert(1)//`]() -![A](B "") \ No newline at end of file +![A](B "") + +[x](javascript:alert(origin)) \ No newline at end of file From a11ce82fbb99c3f8b72711a141ee1a511a90846b Mon Sep 17 00:00:00 2001 From: Crozzers Date: Thu, 14 May 2026 22:24:25 +0100 Subject: [PATCH 2/3] Fix XSS from making javascript: hrefs look like domains with ports --- lib/markdown2.py | 2 +- test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 2 ++ test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/markdown2.py b/lib/markdown2.py index 6683df7c..745d91f6 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -1540,7 +1540,7 @@ def _safe_href(self): # html encoded colon in a URL still functions as a normal colon, so need to detect those protocol_seperators = [':', ':', ':', ':'] # dot seperated hostname, optional port number, not followed by protocol seperator - domain = r'(?:[{}]+(?:\.[{}]+)*)(?:(?A

x

+ +

x

diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text index 12d54edb..26edae4e 100644 --- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text +++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text @@ -4,4 +4,6 @@ ![A](B "") -[x](javascript:alert(origin)) \ No newline at end of file +[x](javascript:alert(origin)) + +[x](javascript:1/alert(origin)) \ No newline at end of file From 82b4482b70a1718eef9a4d4fb2449c059949a5f0 Mon Sep 17 00:00:00 2001 From: Crozzers Date: Thu, 14 May 2026 22:39:11 +0100 Subject: [PATCH 3/3] Fix onerror XSS in image title attr --- lib/markdown2.py | 2 ++ test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 7 +++++++ test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/markdown2.py b/lib/markdown2.py index 745d91f6..4ba78a4f 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -3271,6 +3271,8 @@ def run(self, text: str): .replace('*', self.md._escape_table['*']) .replace('_', self.md._escape_table['_']) ) + if self.md.safe_mode: + title = self.md._hash_span(title) title_str = f' title="{title}"' else: title_str = '' diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.html b/test/tm-cases/xss_smuggling_spans_in_image_attrs.html index 20e0cd4d..ccd398e7 100644 --- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.html +++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.html @@ -7,3 +7,10 @@

x

x

+ +
    +
  • +
      +
    • onerror=alert(origin) )
    • +
  • +
diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text index 26edae4e..3f025a00 100644 --- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text +++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text @@ -6,4 +6,7 @@ [x](javascript:alert(origin)) -[x](javascript:1/alert(origin)) \ No newline at end of file +[x](javascript:1/alert(origin)) + +- +- ![](x '`![](`') onerror=alert(origin) ) \ No newline at end of file