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/)