426b23
From 725268a12dacbb153dacf9d8cc22cfe37ff230ff Mon Sep 17 00:00:00 2001
426b23
From: Lumir Balhar <lbalhar@redhat.com>
426b23
Date: Wed, 6 Jan 2021 10:20:09 +0100
426b23
Subject: [PATCH] CVE-2020-27783
426b23
426b23
---
426b23
 src/lxml/html/clean.py             | 29 ++++++++++++++++++++---------
426b23
 src/lxml/html/tests/test_clean.py  | 20 ++++++++++++++++++++
426b23
 src/lxml/html/tests/test_clean.txt | 12 ++++++++++--
426b23
 3 files changed, 50 insertions(+), 11 deletions(-)
426b23
426b23
diff --git a/src/lxml/html/clean.py b/src/lxml/html/clean.py
426b23
index adc3f45..0492fca 100644
426b23
--- a/src/lxml/html/clean.py
426b23
+++ b/src/lxml/html/clean.py
426b23
@@ -4,8 +4,9 @@ Removes unwanted tags and content.  See the `Cleaner` class for
426b23
 details.
426b23
 """
426b23
 
426b23
-import re
426b23
 import copy
426b23
+import re
426b23
+import sys
426b23
 try:
426b23
     from urlparse import urlsplit
426b23
 except ImportError:
426b23
@@ -61,12 +62,16 @@ __all__ = ['clean_html', 'clean', 'Cleaner', 'autolink', 'autolink_html',
426b23
 
426b23
 # This is an IE-specific construct you can have in a stylesheet to
426b23
 # run some Javascript:
426b23
-_css_javascript_re = re.compile(
426b23
-    r'expression\s*\(.*?\)', re.S|re.I)
426b23
+_replace_css_javascript = re.compile(
426b23
+    r'expression\s*\(.*?\)', re.S|re.I).sub
426b23
 
426b23
 # Do I have to worry about @\nimport?
426b23
-_css_import_re = re.compile(
426b23
-    r'@\s*import', re.I)
426b23
+_replace_css_import = re.compile(
426b23
+    r'@\s*import', re.I).sub
426b23
+
426b23
+_looks_like_tag_content = re.compile(
426b23
+    r'</?[a-zA-Z]+|\son[a-zA-Z]+\s*=',
426b23
+    *((re.ASCII,) if sys.version_info[0] >= 3 else ())).search
426b23
 
426b23
 # All kinds of schemes besides just javascript: that can cause
426b23
 # execution:
426b23
@@ -292,8 +297,8 @@ class Cleaner(object):
426b23
             if not self.inline_style:
426b23
                 for el in _find_styled_elements(doc):
426b23
                     old = el.get('style')
426b23
-                    new = _css_javascript_re.sub('', old)
426b23
-                    new = _css_import_re.sub('', new)
426b23
+                    new = _replace_css_javascript('', old)
426b23
+                    new = _replace_css_import('', new)
426b23
                     if self._has_sneaky_javascript(new):
426b23
                         # Something tricky is going on...
426b23
                         del el.attrib['style']
426b23
@@ -305,9 +310,9 @@ class Cleaner(object):
426b23
                         el.drop_tree()
426b23
                         continue
426b23
                     old = el.text or ''
426b23
-                    new = _css_javascript_re.sub('', old)
426b23
+                    new = _replace_css_javascript('', old)
426b23
                     # The imported CSS can do anything; we just can't allow:
426b23
-                    new = _css_import_re.sub('', old)
426b23
+                    new = _replace_css_import('', new)
426b23
                     if self._has_sneaky_javascript(new):
426b23
                         # Something tricky is going on...
426b23
                         el.text = '/* deleted */'
426b23
@@ -509,6 +514,12 @@ class Cleaner(object):
426b23
             return True
426b23
         if 'expression(' in style:
426b23
             return True
426b23
+        if '
426b23
+            # e.g. '">'
426b23
+            return True
426b23
+        if _looks_like_tag_content(style):
426b23
+            # e.g. '<math><style></style></math>'
426b23
+            return True
426b23
         return False
426b23
 
426b23
     def clean_html(self, html):
426b23
diff --git a/src/lxml/html/tests/test_clean.py b/src/lxml/html/tests/test_clean.py
426b23
index 3bcaaf5..451eec2 100644
426b23
--- a/src/lxml/html/tests/test_clean.py
426b23
+++ b/src/lxml/html/tests/test_clean.py
426b23
@@ -69,6 +69,26 @@ class CleanerTest(unittest.TestCase):
426b23
         s = lxml.html.fromstring('<invalid tag>child</another>')
426b23
         self.assertEqual('child', clean_html(s).text_content())
426b23
 
426b23
+    def test_sneaky_noscript_in_style(self):
426b23
+        # This gets parsed as ..."</style>
426b23
+        # thus passing the  through into the output.
426b23
+        html = '">'
426b23
+        s = lxml.html.fragment_fromstring(html)
426b23
+
426b23
+        self.assertEqual(
426b23
+            b'',
426b23
+            lxml.html.tostring(clean_html(s)))
426b23
+
426b23
+    def test_sneaky_js_in_math_style(self):
426b23
+        # This gets parsed as <math> -> <style>"..."</style>
426b23
+        # thus passing any tag/script/whatever content through into the output.
426b23
+        html = '<math><style></style></math>'
426b23
+        s = lxml.html.fragment_fromstring(html)
426b23
+
426b23
+        self.assertEqual(
426b23
+            b'<math><style>/* deleted */</style></math>',
426b23
+            lxml.html.tostring(clean_html(s)))
426b23
+
426b23
 
426b23
 def test_suite():
426b23
     suite = unittest.TestSuite()
426b23
diff --git a/src/lxml/html/tests/test_clean.txt b/src/lxml/html/tests/test_clean.txt
426b23
index c78ab4f..c901871 100644
426b23
--- a/src/lxml/html/tests/test_clean.txt
426b23
+++ b/src/lxml/html/tests/test_clean.txt
426b23
@@ -104,7 +104,11 @@
426b23
 >>> print(Cleaner(page_structure=False, safe_attrs_only=False).clean_html(doc))
426b23
 <html>
426b23
   <head>
426b23
-    <style>/* deleted */</style>
426b23
+    <style>
426b23
+      body {background-image: url()};
426b23
+      div {background-image: url()};
426b23
+      div {color: };
426b23
+    </style>
426b23
   </head>
426b23
   <body>
426b23
     a link
426b23
@@ -168,7 +172,11 @@
426b23
     <link rel="alternate" type="text/rss" src="evil-rss">
426b23
     <link rel="alternate" type="text/rss" href="http://example.com">
426b23
     <link rel="stylesheet" type="text/rss" href="http://example.com">
426b23
-    <style>/* deleted */</style>
426b23
+    <style>
426b23
+      body {background-image: url()};
426b23
+      div {background-image: url()};
426b23
+      div {color: };
426b23
+    </style>
426b23
   </head>
426b23
   <body>
426b23
     a link
426b23
-- 
426b23
2.29.2
426b23