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