A security tool named `picklescan`, designed to find dangerous Python pickle files, had a critical command injection flaw. When scanning a ZIP file, it used an unsanitized internal filename to build a shell command, allowing an attacker who controls the filename to achieve remote code execution. The fix was a one-line change to properly quote the filename. Anyone using `picklescan` versions before 1.0.1 should upgrade immediately.
The picklescan library, a tool designed to safely inspect Python pickle files for malicious content without the risks of deserialization, ironically contained a critical command injection vulnerability. By crafting a malicious ZIP archive with a specially named file, an attacker could execute arbitrary commands on the machine running the scan. This vulnerability turns a security tool into a weapon, achieving remote code execution by exploiting the very mechanism meant to provide safety.
Python's pickle module is the digital equivalent of a mystery box from a stranger. It's a powerful serialization format, but with great power comes the terrifying ability to execute arbitrary code upon deserialization. The official documentation itself screams warnings at you, advising to never, ever unpickle data from an untrusted source. It's the boogeyman of Python security, responsible for countless RCEs over the years.
Enter picklescan, our supposed hero. Its mission, which it chose to accept, was to tame this beast. Instead of recklessly unpickling data, picklescan performs static analysis, scanning the raw byte stream for dangerous opcodes like os.system without actually executing anything. It was designed to be the security guard that checks for weapons before letting anyone into the building.
The irony, it seems, is a cruel mistress. The very tool built on the premise of 'safe scanning' harbored a vulnerability just as old and treacherous as the one it was designed to fight. The guard wasn't just asleep at his post; he was actively helping the intruder build a bomb, all because he trusted a piece of paper the intruder handed him.
The fatal flaw wasn't in the pickle analysis logic itself, but in a seemingly innocuous feature: scanning inside compressed archives. To inspect a file within a ZIP archive, picklescan cleverly uses a shell pipeline. It calls the unzip command to extract the file's contents to standard output and pipes it directly into grep to look for malicious opcodes. This is efficient, avoiding the need to write temporary files to disk.
Herein lies the original sin of so many vulnerabilities: building shell commands with untrusted strings. The developer correctly used shlex.quote on the main archive's path, protecting it from injection. However, they forgot to apply the same protection to the filename of the member inside the archive. An attacker has complete control over the filenames of files they place in a ZIP archive.
This oversight creates a classic command injection vulnerability (CWE-78). The unsanitized filename is concatenated directly into the command string passed to the shell. A filename is no longer just a name; it's a potential command payload. The developer locked the front door (file_path) but left a gaping hole in the wall (member.filename).
Code tells a story, and this one is a tragedy in a single line. Let's look at the vulnerable piece of code from the scan_zip_file function in picklescan/scanner.py. The evidence is damning.
# The vulnerable line of code
scan_archive_cmd = f"unzip -p {shlex.quote(file_path)} {member.filename} | grep -aoE '({dangerous_opcode_re})'"Notice the beautiful, secure shlex.quote(file_path). The developer was so close. But right next to it, member.filename sits naked and exposed, ready to be manipulated. The shell sees this string and interprets any special characters ($, ;, |, `) as instructions, not as part of a literal filename. It's a rookie mistake, but one that appears with depressing regularity.
The fix is as simple as it is obvious. It's the kind of change that makes you stare at the ceiling at 3 AM, wondering about the fragility of it all. One small addition was all it took to close the hole.
# The patched line of code
import shlex
scan_archive_cmd = f"unzip -p {shlex.quote(file_path)} {shlex.quote(member.filename)} | grep -aoE '({dangerous_opcode_re})'"By wrapping member.filename in shlex.quote(), the string is properly escaped. A filename like pwn.pkl; id becomes 'pwn.pkl; id', which the shell now correctly interprets as a single, albeit weirdly named, file. The attack is completely neutralized. This patch highlights a critical lesson: sanitize all external input, especially when it's destined for a command interpreter.
Exploiting this is trivially easy, which is what makes it so severe. An attacker doesn't need complex memory corruption skills; they just need to know how a shell works and how to create a ZIP file. It's a low-skill, high-impact affair.
Here is the attack plan, step-by-step:
unzip command and inject our own. A filename like exploit.pkl; whoami > /tmp/proof; # is perfect. The semicolon ends the previous command, whoami executes, its output is redirected, and the # comments out the rest of the original command string to avoid syntax errors.payload.txt for now.touch payload.txt
zip malicious.zip "exploit.pkl; whoami > /tmp/proof; #"=payload.txtmalicious.zip file is now the weapon. It needs to be sent to a service that uses a vulnerable version of picklescan. This could be a file upload endpoint, a machine learning model repository, or any automated analysis pipeline.When picklescan processes this ZIP file, the vulnerable code will construct and execute the following shell command:
unzip -p '/path/to/malicious.zip' exploit.pkl; whoami > /tmp/proof; # | grep -aoE '...'
The shell executes unzip, then whoami, and the grep command is ignored. The system is now compromised.
The impact of this vulnerability is catastrophic, precisely because of the context in which picklescan is used. This isn't a client-side bug in some desktop application; it's a flaw in a server-side security tool. Systems running this code are explicitly processing untrusted, potentially malicious files. They are on the front lines.
A successful exploit grants the attacker Remote Code Execution on the server. From there, the playbook is wide open. An attacker can steal sensitive data, deploy ransomware, install persistent backdoors, or pivot deeper into the victim's network. The server running the scanner becomes a beachhead for a full-scale invasion.
What's worse is the betrayal of trust. Developers use a tool named picklescan because they are trying to improve their security posture. They are doing the right thing. This vulnerability turns their diligence against them, making the security measure the very vector of compromise. It's a painful reminder that every dependency, even a security-focused one, is a potential attack surface.
Fortunately, the fix is straightforward and should be applied yesterday. There is no good reason to be running a vulnerable version. The path to redemption is a single command.
[!IMPORTANT] The primary remediation is to upgrade to version
1.0.1or newer ofpicklescan.
pip install --upgrade picklescanFor those in bizarre corporate environments where patching is a multi-year bureaucratic process, other mitigation strategies exist, but they are poor substitutes. You could, for instance, run picklescan in a heavily restricted Docker container with no network access and minimal permissions. This contains the blast but doesn't defuse the bomb. Another option is to sanitize archive filenames before passing them to picklescan, but this just moves the problem and risks implementing a faulty sanitization routine yourself.
Ultimately, the lesson is clear and has been for decades: Never, ever build commands by concatenating strings with untrusted data. Use argument arrays or shell-safe quoting libraries (shlex in Python) religiously. Treat every piece of external data, including metadata like filenames, as if it were crafted by your worst enemy, because one day, it will be.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
picklescan mmaitre314 | < 1.0.1 | 1.0.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 |
| CWE Name | Improper Neutralization of Special Elements used in a Command ('Command Injection') |
| Attack Vector | Network / File Upload |
| CVSS 3.1 Score | 9.8 (Critical) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Impact | Remote Code Execution |
| Exploit Status | Proof-of-Concept Available |
| KEV Status | Not Listed |
Get the latest CVE analysis reports delivered to your inbox.