Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [pull #728] Fix XSS from zero-padded colon entities in link URLs (#726)
- [pull #734] Fix empty list items being parsed as nested lists (#167)
- [pull #735] Fix `smarty-pants` extra converting quotes inside inline HTML tags and comments (#150)
- [pull #737] Fix XSS from code spans that the span gamut doesn't treat as code spans in safe mode (#726)


## python-markdown2 2.5.5
Expand Down
17 changes: 12 additions & 5 deletions lib/markdown2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1448,10 +1448,17 @@ def _is_comment(token):
return re.match(r'(<!--)(.*)(-->)', token)

# protect raw code spans from processing, as they can often contain anything that looks like HTML and
# trips up the regex. These are encoded and processed later on anyway
# trips up the regex. These are encoded and processed later on anyway.
# The span gamut doesn't always agree that these are code spans
# (eg: a tag spanning lines changes how backticks pair up), so swap
# any `<` for a hash that unescapes to `&lt;`. Nothing in here can
# then come out as raw HTML (#726), and real code spans still only
# get escaped once.
code_hashes = {}
lt_hash = _hash_text('&lt;')
self._code_table['&lt;'] = lt_hash
text = self._code_span_re.sub(
lambda m: self._hash_span(m.string[m.start(): m.end()], code_hashes),
lambda m: self._hash_span(m.group(0).replace('<', lt_hash), code_hashes),
text
)

Expand Down Expand Up @@ -1736,7 +1743,8 @@ def _h_sub(self, match: re.Match[str]) -> str:
n = min(n + demote_headers, 6)
header_id_attr = ""
if "header-ids" in self.extras:
header_id = self.header_id_from_text(header_group,
header_id = self.header_id_from_text(
self._unescape_special_chars(header_group),
self.extras["header-ids"].get('prefix'), n)
if header_id:
header_id_attr = ' id="%s"' % header_id
Expand Down Expand Up @@ -3682,8 +3690,7 @@ def _code_block_with_lexer_sub(
formatter_opts = self.md.extras['fenced-code-blocks'] or {}

def unhash_code(codeblock):
for key, sanitized in list(self.md.html_spans.items()):
codeblock = codeblock.replace(key, sanitized)
codeblock = self.md._unhash_html_spans(codeblock, code=True)
replacements = [
("&amp;", "&"),
("&lt;", "<"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="codehilite">
<pre><span></span><code><span class="nb">print</span> <span class="err">`</span><span class="n">a</span><span class="o">&lt;</span><span class="n">b</span><span class="err">`</span>
</code></pre>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{'safe_mode': 'escape', 'extras': ['fenced-code-blocks']}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fenced-code-blocks pygments
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```python
print `a<b`
```
14 changes: 14 additions & 0 deletions test/tm-cases/xss_code_spans_in_tables_issue726.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>`x</td>
<td>&lt;svg onload=alert(origin)&gt;`</td>
</tr>
</tbody>
</table>
1 change: 1 addition & 0 deletions test/tm-cases/xss_code_spans_in_tables_issue726.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"safe_mode": "escape", "extras": ["tables"]}
3 changes: 3 additions & 0 deletions test/tm-cases/xss_code_spans_in_tables_issue726.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| a | b |
|---|---|
| `x | <svg onload=alert(origin)>` |
7 changes: 7 additions & 0 deletions test/tm-cases/xss_code_spans_issue726.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p>&lt;x<img src="code&gt;&lt;a
&gt;&lt;/code" alt="" />&gt;&lt;svg onload=alert(origin)<img src="code&gt;&lt;a
&gt;&lt;/code" alt="" /></p>

<p>Code spans still come out escaped once: <code>a&lt;b</code>, <code>&lt;b&gt;</code> and <code>C:\&lt;folder&gt;</code></p>

<h1 id="header-with-ab-in-it">Header with <code>a&lt;b</code> in it</h1>
1 change: 1 addition & 0 deletions test/tm-cases/xss_code_spans_issue726.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"safe_mode": "escape", "extras": ["header-ids"]}
7 changes: 7 additions & 0 deletions test/tm-cases/xss_code_spans_issue726.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<x![](`<a
>`)><svg onload=alert(origin)![](`<a
>`)

Code spans still come out escaped once: `a<b`, `<b>` and `C:\<folder>`

# Header with `a<b` in it
Loading