Set an arbitrary upper limit on ReDoS-vulnerable regexps

This commit is contained in:
Adrián Chaves 2023-11-29 11:54:08 +01:00
parent 5fccf370b8
commit c947f51077
3 changed files with 37 additions and 7 deletions

View File

@ -3,6 +3,22 @@
Release notes
=============
.. _release-2.11.1:
Scrapy 2.11.1 (unreleased)
--------------------------
**Security bug fix:**
- The regular expressions of the ``iternodes`` node iterator of
:class:`~scrapy.spiders.XMLFeedSpider` are no longer susceptible to a
`ReDoS attack`_. Please, see the `cc65-xxvf-f7r9 security
advisory`_ for more information.
.. _ReDoS attack: https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
.. _cc65-xxvf-f7r9 security advisory: https://github.com/scrapy/scrapy/security/advisories/GHSA-cc65-xxvf-f7r9
.. _release-2.11.0:
Scrapy 2.11.0 (2023-09-18)
@ -2869,6 +2885,18 @@ affect subclasses:
(:issue:`3884`)
.. _release-1.8.4:
Scrapy 1.8.4 (unreleased)
-------------------------
**Security bug fix:**
- The regular expressions of the ``iternodes`` node iterator of
:class:`~scrapy.spiders.XMLFeedSpider` are no longer susceptible to a
`ReDoS attack`_. Please, see the `cc65-xxvf-f7r9 security
advisory`_ for more information.
.. _release-1.8.3:

View File

@ -40,10 +40,10 @@ def xmliter(
"""
nodename_patt = re.escape(nodename)
DOCUMENT_HEADER_RE = re.compile(r"<\?xml[^>]+>\s*", re.S)
DOCUMENT_HEADER_RE = re.compile(r"<\?xml[^>]{1,1024}>\s*", re.S)
HEADER_END_RE = re.compile(rf"<\s*/{nodename_patt}\s*>", re.S)
END_TAG_RE = re.compile(r"<\s*/([^\s>]+)\s*>", re.S)
NAMESPACE_RE = re.compile(r"((xmlns[:A-Za-z]*)=[^>\s]+)", re.S)
END_TAG_RE = re.compile(r"<\s*/([^\s>]{1,1024})\s*>", re.S)
NAMESPACE_RE = re.compile(r"((xmlns[:A-Za-z]{,1024})=[^>\s]+)", re.S)
text = _body_or_str(obj)
document_header_match = re.search(DOCUMENT_HEADER_RE, text)
@ -57,13 +57,15 @@ def xmliter(
for tagname in reversed(re.findall(END_TAG_RE, header_end)):
assert header_end_idx
tag = re.search(
rf"<\s*{tagname}.*?xmlns[:=][^>]*>", text[: header_end_idx[1]], re.S
rf"<\s*{tagname}.{{,1024}}?xmlns[:=][^>]{{,1024}}>",
text[: header_end_idx[1]],
re.S,
)
if tag:
for x in re.findall(NAMESPACE_RE, tag.group()):
namespaces[x[1]] = x[0]
r = re.compile(rf"<{nodename_patt}[\s>].*?</{nodename_patt}>", re.DOTALL)
r = re.compile(rf"<{nodename_patt}[\s>].{{,1024}}?</{nodename_patt}>", re.DOTALL)
for match in r.finditer(text):
nodetext = (
document_header

View File

@ -91,8 +91,8 @@ def open_in_browser(
if isinstance(response, HtmlResponse):
if b"<base" not in body:
repl = rf'\1<base href="{response.url}">'
body = re.sub(b"<!--.*?-->", b"", body, flags=re.DOTALL)
body = re.sub(rb"(<head(?:>|\s.*?>))", to_bytes(repl), body)
body = re.sub(b"<!--.{,1024}?-->", b"", body, flags=re.DOTALL)
body = re.sub(rb"(<head(?:>|\s.{,1024}?>))", to_bytes(repl), body)
ext = ".html"
elif isinstance(response, TextResponse):
ext = ".txt"