Posts in "python"

I wrote a little Python web chat thingy ages ago, Seshat. I haven’t touched it again in over 15 years. Someone asked me for permission to re-use that name on PyPI. I’d refuse under other circumstances — supply chain attack, anyone? — except that I strongly doubt there’s a single user of my package anywhere. The new person wants to rename their legitimate, long-lived project in a completely different niche so there’s no chance of confusing the two, either.

Because of all that, I’m agreeing to it. They’re starting the transfer process and I’ll approve it. If you’re the one person in the world using my abandoned project 15 years on, please consider vendoring my code. In fact, you can flat-out have it. Call it your own. Put it under your own license for all I care.

In short: some time after March 2026, the Seshat name on PyPI will start pointing at something else.

If you use Python 3.14’s venv module to create a virtualenv, it adds an Easter egg.

ᐅ python -m venv my-venv
ᐅ lsd -1 my-venv/bin
 activate
 activate.csh
 activate.fish
 Activate.ps1
 pip
 pip3
 pip3.14
 python ⇒ python3
 python3 ⇒ /Users/kirk/.local/bin/python3
 python3.14 ⇒ python3
 𝜋thon ⇒ python3

We wrote a pretty powerful AWS infra tool in Python, and haven’t done much with it in a couple years except to update its dependencies.

I’m kinda thinking about porting it to Rust to get some extra type constraints (it’s already mypy-clean, but…) and make it a single executable deployment.

Python 3.13 launched today. I’ve done it. I’ve lived long enough to see a less-GIL’ed Python released to the public. Until now there’s been an unvirtuous cycle:

Python isn’t good at running CPU-intensive threaded code. → No one writes code like that. → There was no pressure to remove the GIL because no one writes code that would benefit from it. → Repeat.

I hope this is the first giant step toward good Python multithreading.

Python 3.13 is removing more Amiga “dead batteries” modules, like chunk:

The chunk module provides support for reading and writing Electronic Arts’ Interchange File Format. IFF is an old audio file format originally introduced for Commodore and Amiga. The format is no longer relevant.

I’m sure that’s the right thing to do. It still saddens me.

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.