October 28, 2026
The 23-Year-Old Flaw at the Heart of CPAN: An RCE in the Perl Infrastructure
When security responders look back at major ecosystem incidents, the names usually stick to the current headline grabbers β the php2shellβ¦

By Imre Rad
4 min read
When security responders look back at major ecosystem incidents, the names usually stick to the current headline grabbers β the php2shell wave, the HTTP2Shell / Node.js / Brotli panics, or PyPI typosquatting campaigns. But while the industry's eyes were fixed on the latest modern stack fires, an ancient footgun was sitting quietly in the bedrock of the internet's software distribution pipeline.
In my free time, I recently identified and ethically disclosed an unauthenticated Remote Code Execution (RCE) vulnerability in PAUSE (the Perl Authors Upload Server) β the automated central engine that ingests, parses, and indexes every module distribution uploaded to CPAN before it reaches global mirrors and CDNs.
What makes this flaw particularly sobering isn't just its severity; it is how deep into the history books it goes and what could have happened if it were found by someone else.
The Footgun Older Than Git History
The vulnerability itself stems from a classic, infamous Perl design trap: the 2-argument open() function.
When passed two arguments without an explicit mode prefix (e.g., open(FH, $filename)), Perl does not treat $filename strictly as a file path. Instead, it parses leading and trailing special characters. If a string starts or ends with a pipe operator (|), Perl interprets the input as a shell command to execute and pipes its input/output to the filehandle.
If an attacker controls the file path string β such as a file or folder name inside an uploaded archive β passing it to a 2-argument open() leads straight to arbitrary command execution on the host system.
While auditing the PAUSE indexer codebase, I discovered that untrusted archive content was fed directly into this legacy function during the automated indexing pass.
When digging into the revision history to trace the origin, the findings were startling:
- The 2-argument
open(FH, $parsefile)implementation in the local parsing file dates back to January 17, 2003, likely pre-dating the version control history of the project altogether.
For over 23 years, an automated pipeline processing millions of untrusted community uploads was exposed to a single-shot command injection vulnerability.
Perl in 2026: Why CPAN Still Matters
It is easy for modern web developers to dismiss Perl as a language of the past. But public perception heavily diverges from infrastructural reality.
According to long-term GitHub analysis metrics (such as GitHut), Perl consistently holds a spot among the top 20 most active programming languages by pull requests globally. In 2014, it sat comfortably at #15, and a decade later, it remains firmly embedded in enterprise systems, cloud platforms, and core infrastructure toolchains.
The footprint of CPAN modules spans the critical systems we rely on every day:
- Operating System Package Managers & Build Chains: Linux distributions (Debian, Ubuntu, Red Hat, Arch) use extensive Perl build scripts, debhelper tools, and RPM spec parsers natively during package maintenance and distribution assembly.
- Security & Email Backbones: Projects like SpamAssassin (anti-spam filtering), Amavis, Request Tracker (RT) (enterprise ticketing used by major CSIRTs and government bodies), and ExifTool (the universal metadata standard used across forensically critical image processing pipelines).
- Enterprise Infrastructure: Critical IT management tools such as Munin, BackupPC, Proxmox VE, and Bugzilla rely heavily on Perl dependencies pulled directly from or structured around CPAN.
If you run a server, manage a build pipeline, or process mail, you are running code built on or verified by the Perl ecosystem.
The Alternate Reality: What an Exploit Path Looks Like
While registering a PAUSE author account has historically been a low-friction process, exploiting this flaw required an authenticated author to upload a distribution whose top-level .pm filename began with a pipe (|) character.
Here is how a full-chain supply-chain compromise could have unfolded:
[Attacker]
β
βΌ (Uploads malformed archive with pipe-injected filename)
[PAUSE Upload Server]
β
βΌ
[PAUSE Indexer Daemon] ββ(Executes 2-arg open)βββΊ [UNAUTHENTICATED RCE]
β
βΌ
[Master Indexer Compromised]
β
βββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
βΌ βΌ
[Silent Index Manipulation] [Source Code Injection]
β’ Overwrite mapping for `JSON::XS`, `LWP::UserAgent`, etc. β’ Modify incoming tarballs before indexing
β’ Point canonical modules to attacker-controlled mirrors β’ Inject backdoors directly into release tarballs
β β
βββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β
βΌ
[Mirrors & CDNs Sync Worldwide]
β
βΌ
[Global Build Pipelines & Systems]
β’ Linux distros building package updates
β’ Enterprise CI/CD pulling latest modules
β’ Security & Mail appliances auto-updating[Attacker]
β
βΌ (Uploads malformed archive with pipe-injected filename)
[PAUSE Upload Server]
β
βΌ
[PAUSE Indexer Daemon] ββ(Executes 2-arg open)βββΊ [UNAUTHENTICATED RCE]
β
βΌ
[Master Indexer Compromised]
β
βββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
βΌ βΌ
[Silent Index Manipulation] [Source Code Injection]
β’ Overwrite mapping for `JSON::XS`, `LWP::UserAgent`, etc. β’ Modify incoming tarballs before indexing
β’ Point canonical modules to attacker-controlled mirrors β’ Inject backdoors directly into release tarballs
β β
βββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β
βΌ
[Mirrors & CDNs Sync Worldwide]
β
βΌ
[Global Build Pipelines & Systems]
β’ Linux distros building package updates
β’ Enterprise CI/CD pulling latest modules
β’ Security & Mail appliances auto-updatingStep 1: Initial RCE as the Service Account
Upon ingestion, the automated indexer processes the crafted distribution. When it parses the malicious .pm filename via the vulnerable 2-argument open() call, it executes arbitrary shell commands in the context of the pause service account on the host.
Step 2: Local Privilege Escalation (LPE)
From the restricted pause service user, an attacker would leverage a local Linux privilege escalationβthe kind of standard kernel that we see every week nowadays β to escalate to full root privileges on the PAUSE host.
Step 3: Root-Level Manipulation & Root Poisoning
With root access secured, the attacker unlocks total control over the repository's core state. Crucially, the indexer itself does not normally rewrite uploaded archives dynamically during standard processing; therefore, executing a full compromise requires direct root capabilities or targeted code modifications:
- Database & Index Alteration: They can directly modify the persistent packages database, which serves as the backing source for
02packages.details.txt.gz. - Archive Substitution: They can replace raw distribution tarballs directly within the
authors/idstorage hierarchy. - Signed Checksum Regeneration: Using root-level access to keys or signing tooling, they can regenerate PAUSE-signed
CHECKSUMSfiles to match any substituted or modified packages.
Step 4: Downstream Propagation
Once the persistent backend database, archives, and signed checksums are manipulated, global mirrors and CDNs syncing from PAUSE automatically pick up the poisoned updates. When downstream build pipelines, corporate networks, and container templates pull dependencies or update their mirrors, they unknowingly ingest the compromised distributions, executing the malicious code at scale.
Disclosure and Remediation
Because this was reported responsibly to the Perl security team, this nightmare scenario never happened.
The response from the team was swift and professional. Upon verifying the vulnerability, the team patched the codebase within hours by refactoring 2-argument open() calls to safe, explicit 3-argument forms (e.g., open(my $fh, '<', $filename)) and replacing shell-sensitive file operations with explicit core library abstractions.
What You Need to Do
- Public CPAN Users: If you pull modules from
cpan.orgor official public CDNs, the central infrastructure was patched immediately. You do not need to take action for standard module usage. - Private / Self-Hosted PAUSE Instances: If your organization operates a private, internal CPAN mirror or runs an enterprise instance of the PAUSE server codebase for proprietary module indexing, you must pull the latest commit from the official
andk/pauserepository immediately.
Conclusion
Vulnerabilities like this are a reminder that the health of global software infrastructure depends on the unglamorous work of maintaining legacy codebases. A 23-year-old single line of code in an indexing script can carry the same systemic blast radius as a zero-day in a modern container runtime.
Huge respect goes to the Perl security team and the PAUSE maintainers for their rapid coordination and immediate deployment of the fix. Thanks to their fast action, responder teams didn't need to add a "perl2shell" incident to their queue this week.