CVE-2021-43818 (Python, published 2021-12-13T18:15Z)
Description: 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.
Severity: MEDIUM (CVSS v3 base score: 7.1)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
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.
Repository: https://github.com/lxml/lxml
Files changed:
src/lxml/html/clean.py
src/lxml/html/tests/test_clean.py
Commit message: Cleaner: Remove SVG image data URLs since they can embed script content.

Reported as GHSL-2021-1038


Vulnerable code:
_is_image_dataurl = re.compile(
    r'^data:image/.+;base64', re.I).search
    r'(?:javascript|jscript|livescript|vbscript|data|about|mocha):',
    re.I).search
    if _is_image_dataurl(s):
        return None
    return _is_possibly_malicious_scheme(s)
# FIXME: should data: be blocked?

Fixed code:
_find_image_dataurls = re.compile(
    r'^data:image/(.+);base64,', re.I).findall
    r'(javascript|jscript|livescript|vbscript|data|about|mocha):',
    re.I).findall
# SVG images can contain script content
_is_unsafe_image_type = re.compile(r"(xml|svg)", re.I).findall

    is_image_url = False
    for image_type in _find_image_dataurls(s):
        is_image_url = True
        if _is_unsafe_image_type(image_type):
            return True
    if is_image_url:
        return False
    return bool(_is_possibly_malicious_scheme(s))
import base64
import gzip
    def test_svg_data_links(self):
        # Remove SVG images with potentially insecure content.
        svg = b'<svg onload="alert(123)" />'
        svgz = gzip.compress(svg)
        svg_b64 = base64.b64encode(svg).decode('ASCII')
        svgz_b64 = base64.b64encode(svgz).decode('ASCII')
        urls = [
            "data:image/svg+xml;base64," + svg_b64,
            "data:image/svg+xml-compressed;base64," + svgz_b64,
        ]
        for url in urls:
            html = '<img src="%s">' % url
            s = lxml.html.fragment_fromstring(html)

            cleaned = lxml.html.tostring(clean_html(s))
            self.assertEqual(
                b'<img src="">',
                cleaned,
                "%s  ->  %s" % (url, cleaned))

    def test_image_data_links(self):
        data = b'123'
        data_b64 = base64.b64encode(data).decode('ASCII')
        urls = [
            "data:image/jpeg;base64," + data_b64,
            "data:image/apng;base64," + data_b64,
            "data:image/png;base64," + data_b64,
            "data:image/gif;base64," + data_b64,
            "data:image/webp;base64," + data_b64,
            "data:image/bmp;base64," + data_b64,
            "data:image/tiff;base64," + data_b64,
            "data:image/x-icon;base64," + data_b64,
        ]
        for url in urls:
            html = '<img src="%s">' % url
            s = lxml.html.fragment_fromstring(html)

            cleaned = lxml.html.tostring(clean_html(s))
            self.assertEqual(
                html.encode("UTF-8"),
                cleaned,
                "%s  ->  %s" % (url, cleaned))