daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

disable-functions-bypass-dl-function.md (6680B)


      1 ---
      2 title: "Disable Functions Bypass - dl Function"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-dl-function.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-dl-function.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Disable Functions Bypass - dl Function
     14 
     15 `dl()` lets PHP load a shared extension at runtime. If you can make it load an attacker-controlled module, you can register a new PHP function that internally calls `execve`, `system`, or any other native primitive and therefore bypass `disable_functions`.
     16 
     17 This is a **real** primitive, but on modern targets it is far less common than older writeups suggest.
     18 
     19 ## Why this bypass is uncommon today
     20 
     21 The main blockers are:
     22 
     23 - `dl()` must exist and must not be disabled
     24 - `enable_dl` must still allow dynamic loading
     25 - The target SAPI must support `dl()`
     26 - The payload must be a valid PHP extension compiled for the **same target ABI**
     27 - The extension must be reachable from the configured `extension_dir`
     28 
     29 The official PHP manual is the most important reality check here: **`dl()` is only available for CLI and embed SAPIs, and for the CGI SAPI when run from the command line**. That means the technique is **usually not available in normal PHP-FPM/mod_php web requests**, so check the SAPI before spending time building a payload.<sup>[[1]](#references)</sup>
     30 
     31 Also note that `enable_dl` is an `INI_SYSTEM` setting and, as of **PHP 8.3.0**, PHP documents it as **deprecated**, so you usually cannot flip it at runtime from attacker-controlled PHP code.
     32 
     33 If `dl()` is not viable, go back to the broader list of [module/version dependent bypasses](/hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable-functions-open-basedir-bypass/overview).
     34 
     35 ## Fast triage from a foothold
     36 
     37 Before building anything, collect the exact parameters that the module must match:
     38 
     39 ```php
     40 <?php
     41 phpinfo();
     42 echo "PHP_VERSION=" . PHP_VERSION . PHP_EOL;
     43 echo "PHP_SAPI=" . php_sapi_name() . PHP_EOL;
     44 echo "ZTS=" . (PHP_ZTS ? "yes" : "no") . PHP_EOL;
     45 echo "INT_BITS=" . (PHP_INT_SIZE * 8) . PHP_EOL;
     46 echo "enable_dl=" . ini_get("enable_dl") . PHP_EOL;
     47 echo "extension_dir=" . ini_get("extension_dir") . PHP_EOL;
     48 echo "disabled=" . ini_get("disable_functions") . PHP_EOL;
     49 ?>
     50 ```
     51 
     52 What you care about:
     53 
     54 - `PHP_SAPI`: if this is `fpm-fcgi` or `apache2handler`, `dl()` is usually a dead end for web exploitation
     55 - `extension_dir`: the payload must be loaded from here
     56 - `PHP Version`, architecture, debug/non-debug, and ZTS/non-ZTS: your module must match them
     57 - `disable_functions`: confirm whether `dl` is absent because it is disabled or because the SAPI does not support it
     58 
     59 ## Practical exploitation constraints
     60 
     61 ### 1. You normally need write access to `extension_dir`
     62 
     63 This is the biggest bottleneck.
     64 
     65 `dl()` takes the **extension filename**, and PHP loads it from `extension_dir`. In practice, this means that a normal arbitrary file upload to `/var/www/html/uploads` is not enough. You still need a path to place a `.so`/`.dll` where PHP will actually load extensions from.
     66 
     67 Realistic situations where this becomes exploitable:
     68 
     69 - CTFs or intentionally weak labs where `extension_dir` is writable
     70 - Shared-hosting or container mistakes that expose a writable extension path
     71 - A separate arbitrary file write primitive that already reaches `extension_dir`
     72 - Post-exploitation scenarios where you already escalated enough to drop files there
     73 
     74 ### 2. The module must match the target build
     75 
     76 Matching only `PHP_VERSION` is not enough. The extension also needs to match:
     77 
     78 - OS and CPU architecture
     79 - libc/toolchain expectations
     80 - `ZEND_MODULE_API_NO`
     81 - debug vs non-debug build
     82 - ZTS vs NTS
     83 
     84 If those do not match, `dl()` will fail or crash the process.
     85 
     86 ### 3. `open_basedir` is not the main defense here
     87 
     88 Once you can place the module in `extension_dir` and call `dl()`, the extension code executes inside the PHP process. At that point the relevant barrier was not `open_basedir`, but the ability to land a valid shared object in the extension loading path.
     89 
     90 ## Building the malicious extension
     91 
     92 The classic route is still valid in the constrained environments described above:<sup>[[2]](#references)</sup>
     93 
     94 1. Recreate the victim build as closely as possible
     95 2. Use `phpize`, `./configure`, and `make` to build a shared extension
     96 3. Export a PHP function such as `bypass_exec($cmd)` that wraps native command execution
     97 4. Upload the compiled module into `extension_dir`
     98 5. Load it with `dl()` and call the exported function
     99 
    100 The primitive still exists; the hard part is finding a supported SAPI where it is reachable and landing a matching module in `extension_dir`.<sup>[[1]](#references)[[2]](#references)</sup>
    101 
    102 ## Minimal workflow
    103 
    104 ### On the attacker box
    105 
    106 ```bash
    107 mkdir bypass && cd bypass
    108 phpize
    109 ./configure
    110 make
    111 ```
    112 
    113 The resulting shared object will usually be under `modules/`.
    114 
    115 If you are building on a different environment than the target, treat the produced file as a draft until you verify that the ABI matches the victim.
    116 
    117 ## Loading and using the extension
    118 
    119 If the target really supports `dl()` and the module is inside `extension_dir`, the runtime side is simple:
    120 
    121 ```php
    122 <?php
    123 if (!extension_loaded('bypass')) {
    124     dl('bypass.so'); // use the correct filename for the target platform
    125 }
    126 echo bypass_exec($_GET['cmd']);
    127 ?>
    128 ```
    129 
    130 On Windows the filename will typically be a `.dll`, while on Unix-like targets it will usually be a `.so`.
    131 
    132 ## Attacker notes
    133 
    134 - Do not assume this works remotely just because `function_exists("dl")` returns true in some documentation or old writeup; validate the live SAPI
    135 - A failed `dl()` attempt may kill the PHP worker if the module is incompatible
    136 - From PHP 8 onward, disabled functions are removed from the function table, so userland enumeration may differ from older posts
    137 - If you cannot write to `extension_dir`, this technique is usually less practical than FPM/FastCGI, `LD_PRELOAD`, or module-specific bypasses already covered in this section
    138 
    139 ## References
    140 
    141 - [1] [PHP manual: dl](https://www.php.net/manual/en/function.dl.php)
    142 - [2] [Tarlogic: A deep dive into disable_functions bypass and PHP exploitation](https://www.tarlogic.com/blog/disable_functions-bypasses-php-exploitation/)