palo alto

    Palo Alto's exploited Python code

    watchTowr Labs has a nice blog post dissecting CVE-2024-3400. It’s very readable. Go check it out.

    The awfulness of Palo Alto’s Python code in this snippet stood out to me:

    def some_function():
        ...
        if source_ip_str is not None and source_ip_str != "": 
            curl_cmd = "/usr/bin/curl -v -H \"Content-Type: application/octet-stream\" -X PUT \"%s\" --data-binary @%s --capath %s --interface %s" \
                         %(signedUrl, fname, capath, source_ip_str)
        else:
            curl_cmd = "/usr/bin/curl -v -H \"Content-Type: application/octet-stream\" -X PUT \"%s\" --data-binary @%s --capath %s" \
                         %(signedUrl, fname, capath)
        if dbg:
            logger.info("S2: XFILE: send_file: curl cmd: '%s'" %curl_cmd)
        stat, rsp, err, pid = pansys(curl_cmd, shell=True, timeout=250)
        ...
    
    def dosys(self, command, close_fds=True, shell=False, timeout=30, first_wait=None):
        """call shell-command and either return its output or kill it
           if it doesn't normally exit within timeout seconds"""
    
        # Define dosys specific constants here
        PANSYS_POST_SIGKILL_RETRY_COUNT = 5
    
        # how long to pause between poll-readline-readline cycles
        PANSYS_DOSYS_PAUSE = 0.1
    
        # Use first_wait if time to complete is lengthy and can be estimated 
        if first_wait == None:
            first_wait = PANSYS_DOSYS_PAUSE
    
        # restrict the maximum possible dosys timeout
        PANSYS_DOSYS_MAX_TIMEOUT = 23 * 60 * 60
        # Can support upto 2GB per stream
        out = StringIO()
        err = StringIO()
    
        try:
            if shell:
                cmd = command
            else:
                cmd = command.split()
        except AttributeError: cmd = command
    
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1, shell=shell,
                 stderr=subprocess.PIPE, close_fds=close_fds, universal_newlines=True)
        timer = pansys_timer(timeout, PANSYS_DOSYS_MAX_TIMEOUT)
    

    It uses string building to create a curl command line. Then it passes that command line down into a function that calls subprocess.Popen(cmd_line, shell=True). What? No! Don’t ever do that!

    I fed that code into the open source bandit static analyzer. It flagged this code with a high severity, high confidence finding:

    ᐅ bandit pan.py
    [main]  INFO    profile include tests: None
    [main]  INFO    profile exclude tests: None
    [main]  INFO    cli include tests: None
    [main]  INFO    cli exclude tests: None
    [main]  INFO    running on Python 3.12.1
    Run started:2024-04-16 17:14:52.240258
    
    Test results:
    >> Issue: [B604:any_other_function_with_shell_equals_true] Function call with shell=True parameter identified, possible security issue.
       Severity: Medium   Confidence: Low
       CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
       More Info: https://bandit.readthedocs.io/en/1.7.8/plugins/b604_any_other_function_with_shell_equals_true.html
       Location: ./pan.py:14:26
    13              logger.info("S2: XFILE: send_file: curl cmd: '%s'" % curl_cmd)
    14          stat, rsp, err, pid = pansys(curl_cmd, shell=True, timeout=250)
    15
    
    --------------------------------------------------
    >> Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified, security issue.
       Severity: High   Confidence: High
       CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
       More Info: https://bandit.readthedocs.io/en/1.7.8/plugins/b602_subprocess_popen_with_shell_equals_true.html
       Location: ./pan.py:49:8
    48              bufsize=1,
    49              shell=shell,
    50              stderr=subprocess.PIPE,
    51              close_fds=close_fds,
    52              universal_newlines=True,
    53          )
    54          timer = pansys_timer(timeout, PANSYS_DOSYS_MAX_TIMEOUT)
    
    --------------------------------------------------
    
    Code scanned:
            Total lines of code: 41
            Total lines skipped (#nosec): 0
    
    Run metrics:
            Total issues (by severity):
                    Undefined: 0
                    Low: 0
                    Medium: 1
                    High: 1
            Total issues (by confidence):
                    Undefined: 0
                    Low: 1
                    Medium: 0
                    High: 1
    Files skipped (0):
    

    From that we can infer that Palo Alto does not use effective static analysis on their Python code. If they did, this code would not have made it to production.