Skip to content

Commit eacff47

Browse files
b-c-dswaylan
authored andcommitted
Fix cubic ReDoS in fenced code and references
Two regular expressions were vulerable to Regular Expression Denial of Service (ReDoS). Crafted strings containing a long sequence of spaces could cause Denial of Service by making markdown take a long time to process. This represents a vulnerability when untrusted user input is processed with the markdown package. ReferencesProcessor: https://github.com/Python-Markdown/markdown/blob/4acb949256adc535d6e6cd8/markdown/blockprocessors.py#L559-L563 e.g.: ```python import markdown markdown.markdown('[]:0' + ' ' * 4321 + '0') ``` FencedBlockPreprocessor (requires fenced_code extension): https://github.com/Python-Markdown/markdown/blob/a11431539d08e14b0bd821c/markdown/extensions/fenced_code.py#L43-L54 e.g.: ```python import markdown markdown.markdown('```' + ' ' * 4321, extensions=['fenced_code']) ``` Both regular expressions had cubic worst-case complexity, so doubling the number of spaces made processing take 8 times as long. The cubic behaviour can be seen as follows: ``` $ time python -c "import markdown; markdown.markdown('[]:0' + ' ' * 1000 + '0')" python -c "import markdown; markdown.markdown('[]:0' + ' ' * 1000 + '0')" 1.25s user 0.02s system 99% cpu 1.271 total $ time python -c "import markdown; markdown.markdown('[]:0' + ' ' * 2000 + '0')" python -c "import markdown; markdown.markdown('[]:0' + ' ' * 2000 + '0')" 9.01s user 0.02s system 99% cpu 9.040 total $ time python -c "import markdown; markdown.markdown('[]:0' + ' ' * 4000 + '0')" python -c "import markdown; markdown.markdown('[]:0' + ' ' * 4000 + '0')" 74.86s user 0.27s system 99% cpu 1:15.38 total ``` Both regexes had three `[ ]*` groups separated by optional groups, in effect making the regex `[ ]*[ ]*[ ]*`. Discovered using [regexploit](https://github.com/doyensec/regexploit).
1 parent 4acb949 commit eacff47

File tree

2 files changed

+8
-8
lines changed

2 files changed

+8
-8
lines changed

markdown/blockprocessors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ def run(self, parent, blocks):
559559
class ReferenceProcessor(BlockProcessor):
560560
""" Process link references. """
561561
RE = re.compile(
562-
r'^[ ]{0,3}\[([^\]]*)\]:[ ]*\n?[ ]*([^\s]+)[ ]*\n?[ ]*((["\'])(.*)\4|\((.*)\))?[ ]*$', re.MULTILINE
562+
r'^[ ]{0,3}\[([^\]]*)\]:[ ]*\n?[ ]*([^\s]+)[ ]*(?:\n[ ]*)?((["\'])(.*)\4[ ]*|\((.*)\)[ ]*)?$', re.MULTILINE
563563
)
564564

565565
def test(self, parent, block):

markdown/extensions/fenced_code.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ def extendMarkdown(self, md):
4242
class FencedBlockPreprocessor(Preprocessor):
4343
FENCED_BLOCK_RE = re.compile(
4444
dedent(r'''
45-
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # opening fence
46-
((\{(?P<attrs>[^\}\n]*)\})?| # (optional {attrs} or
47-
(\.?(?P<lang>[\w#.+-]*))?[ ]* # optional (.)lang
48-
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?) # optional hl_lines)
49-
[ ]*\n # newline (end of opening fence)
50-
(?P<code>.*?)(?<=\n) # the code block
51-
(?P=fence)[ ]*$ # closing fence
45+
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # opening fence
46+
((\{(?P<attrs>[^\}\n]*)\})| # (optional {attrs} or
47+
(\.?(?P<lang>[\w#.+-]*)[ ]*)? # optional (.)lang
48+
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot)[ ]*)?) # optional hl_lines)
49+
\n # newline (end of opening fence)
50+
(?P<code>.*?)(?<=\n) # the code block
51+
(?P=fence)[ ]*$ # closing fence
5252
'''),
5353
re.MULTILINE | re.DOTALL | re.VERBOSE
5454
)

0 commit comments

Comments
 (0)