CVE-2021-43818: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: lxml is a library for processing XML and HTML in the Python language. Prior to version 4.6.5, the HTML Cleaner in lxml.html lets certain crafted script content pass through, as well as script content in SVG files embedded using data URIs. Users that employ the HTML cleaner in a security relevant context should upgrade to lxml 4.6.5 to receive a patch. There are no known workarounds available.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 7.1
Published: 2021-12-13T18:15Z
Repository: https://github.com/lxml/lxml
Language: Python
Files changed: src/lxml/html/clean.py, src/lxml/html/tests/test_clean.py
Commit date: 2021-11-11 13:19:30 +0100
Commit message: Cleaner: Prevent "@import" from re-occurring in the CSS after replacements, e.g. "@@importimport".

Reported as GHSL-2021-1037

Diff: diff --git a/src/lxml/html/clean.py b/src/lxml/html/clean.py
index 0494357e5..25844e873 100644
--- a/src/lxml/html/clean.py
+++ b/src/lxml/html/clean.py
@@ -539,10 +539,12 @@ class Cleaner(object):
         style = style.lower()
         if 'javascript:' in style:
             return True
         if 'expression(' in style:
             return True
+        if '@import' in style:
+            return True
         if '</noscript' in style:
             # e.g. '<noscript><style><a title="</noscript><img src=x onerror=alert(1)>">'
             return True
         if _looks_like_tag_content(style):
             # e.g. '<math><style><img src=x onerror=alert(1)></style></math>'
diff --git a/src/lxml/html/tests/test_clean.py b/src/lxml/html/tests/test_clean.py
index 45c2e83ab..d395d5141 100644
--- a/src/lxml/html/tests/test_clean.py
+++ b/src/lxml/html/tests/test_clean.py
@@ -121,10 +121,30 @@ class CleanerTest(unittest.TestCase):
 
         self.assertEqual(
             b'<math><style>/* deleted */</style></math>',
             lxml.html.tostring(clean_html(s)))
 
+    def test_sneaky_import_in_style(self):
+        # Prevent "@@importimport" -> "@import" replacement.
+        style_codes = [
+            "@@importimport(extstyle.css)",
+            "@ @  import import(extstyle.css)",
+            "@ @ importimport(extstyle.css)",
+            "@@  import import(extstyle.css)",
+            "@ @import import(extstyle.css)",
+            "@@importimport()",
+        ]
+        for style_code in style_codes:
+            html = '<style>%s</style>' % style_code
+            s = lxml.html.fragment_fromstring(html)
+
+            cleaned = lxml.html.tostring(clean_html(s))
+            self.assertEqual(
+                b'<style>/* deleted */</style>',
+                cleaned,
+                "%s  ->  %s" % (style_code, cleaned))
+
     def test_formaction_attribute_in_button_input(self):
         # The formaction attribute overrides the form's action and should be
         # treated as a malicious link attribute
         html = ('<form id="test"><input type="submit" formaction="javascript:alert(1)"></form>'
         '<button form="test" formaction="javascript:alert(1)">X</button>')

Fixed code:         if '@import' in style:
            return True
    def test_sneaky_import_in_style(self):
        # Prevent "@@importimport" -> "@import" replacement.
        style_codes = [
            "@@importimport(extstyle.css)",
            "@ @  import import(extstyle.css)",
            "@ @ importimport(extstyle.css)",
            "@@  import import(extstyle.css)",
            "@ @import import(extstyle.css)",
            "@@importimport()",
        ]
        for style_code in style_codes:
            html = '<style>%s</style>' % style_code
            s = lxml.html.fragment_fromstring(html)

            cleaned = lxml.html.tostring(clean_html(s))
            self.assertEqual(
                b'<style>/* deleted */</style>',
                cleaned,
                "%s  ->  %s" % (style_code, cleaned))