|
2 | 2 |
|
3 | 3 | {{#include ../../../../banners/hacktricks-training.md}} |
4 | 4 |
|
5 | | -**Important note:** |
| 5 | +`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`. |
6 | 6 |
|
7 | | - |
| 7 | +This is a **real** primitive, but on modern targets it is far less common than older writeups suggest. |
8 | 8 |
|
9 | | -**`dl`** is a PHP function that can be used to load PHP extensions. It the function isn't disabled it could be abused to **bypass `disable_functions` and execute arbitrary commands**.\ |
10 | | -However, it has some strict limitations: |
| 9 | +## Why this bypass is uncommon today |
11 | 10 |
|
12 | | -- The `dl` function must be **present** in the **environment** and **not disabled** |
13 | | -- The PHP Extension **must be compiled with the same major version** (PHP API version) that the server is using (you can see this information in the output of phpinfo) |
14 | | -- The PHP extension must be **located in the directory** that is **defined** by the **`extension_dir`** directive (you can see it in the output of phpinfo). It's very unprobeable that an attacker trying to abuse the server will have write access over this directory, so this requirement probably will prevent you to abuse this technique). |
| 11 | +The main blockers are: |
15 | 12 |
|
16 | | -**If you meet these requirements, continue reading the post** [**https://antichat.com/threads/70763/**](https://antichat.com/threads/70763/) **to learn how to bypass disable_functions**. Here is a summary: |
| 13 | +- `dl()` must exist and must not be disabled |
| 14 | +- `enable_dl` must still allow dynamic loading |
| 15 | +- The target SAPI must support `dl()` |
| 16 | +- The payload must be a valid PHP extension compiled for the **same target ABI** |
| 17 | +- The extension must be reachable from the configured `extension_dir` |
17 | 18 |
|
18 | | -The [dl function](http://www.php.net/manual/en/function.dl.php) is used to load PHP extensions dynamically during script execution. PHP extensions, typically written in C/C++, enhance PHP's functionality. The attacker, upon noticing the `dl` function is not disabled, decides to create a custom PHP extension to execute system commands. |
| 19 | +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. |
19 | 20 |
|
20 | | -### Steps Taken by the Attacker: |
| 21 | +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. |
21 | 22 |
|
22 | | -1. **PHP Version Identification:** |
| 23 | +If `dl()` is not viable, go back to the broader list of [module/version dependent bypasses](README.md). |
23 | 24 |
|
24 | | - - The attacker determines the PHP version using a script (`<?php echo 'PHP Version is '.PHP_VERSION; ?>`). |
| 25 | +## Fast triage from a foothold |
25 | 26 |
|
26 | | -2. **PHP Source Acquisition:** |
| 27 | +Before building anything, collect the exact parameters that the module must match: |
27 | 28 |
|
28 | | - - Downloads the PHP source from the official [PHP website](http://www.php.net/downloads.php) or the [archive](http://museum.php.net) if the version is older. |
| 29 | +```php |
| 30 | +<?php |
| 31 | +phpinfo(); |
| 32 | +echo "PHP_VERSION=" . PHP_VERSION . PHP_EOL; |
| 33 | +echo "PHP_SAPI=" . php_sapi_name() . PHP_EOL; |
| 34 | +echo "ZTS=" . (PHP_ZTS ? "yes" : "no") . PHP_EOL; |
| 35 | +echo "INT_BITS=" . (PHP_INT_SIZE * 8) . PHP_EOL; |
| 36 | +echo "enable_dl=" . ini_get("enable_dl") . PHP_EOL; |
| 37 | +echo "extension_dir=" . ini_get("extension_dir") . PHP_EOL; |
| 38 | +echo "disabled=" . ini_get("disable_functions") . PHP_EOL; |
| 39 | +?> |
| 40 | +``` |
29 | 41 |
|
30 | | -3. **Local PHP Setup:** |
| 42 | +What you care about: |
31 | 43 |
|
32 | | - - Extracts and installs the specific PHP version on their system. |
| 44 | +- `PHP_SAPI`: if this is `fpm-fcgi` or `apache2handler`, `dl()` is usually a dead end for web exploitation |
| 45 | +- `extension_dir`: the payload must be loaded from here |
| 46 | +- `PHP Version`, architecture, debug/non-debug, and ZTS/non-ZTS: your module must match them |
| 47 | +- `disable_functions`: confirm whether `dl` is absent because it is disabled or because the SAPI does not support it |
33 | 48 |
|
34 | | -4. **Extension Creation:** |
35 | | - - Studies [creating PHP extensions](http://www.php.net/manual/en/zend.creating.php) and inspects the PHP source code. |
36 | | - - Focuses on duplicating the functionality of the [exec function](http://www.php.net/manual/en/function.exec.php) located at `ext/standard/exec.c`. |
| 49 | +## Practical exploitation constraints |
37 | 50 |
|
38 | | -### Notes for Compiling the Custom Extension: |
| 51 | +### 1. You normally need write access to `extension_dir` |
39 | 52 |
|
40 | | -1. **ZEND_MODULE_API_NO:** |
| 53 | +This is the biggest bottleneck. |
41 | 54 |
|
42 | | - - The `ZEND_MODULE_API_NO` in `bypass.c` must match the current Zend Extension Build, retrievable with: |
43 | | - ```bash |
44 | | - php -i | grep "Zend Extension Build" |awk -F"API4" '{print $2}' | awk -F"," '{print $1}' |
45 | | - ``` |
| 55 | +`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. |
46 | 56 |
|
47 | | -2. **PHP_FUNCTION Modification:** |
48 | | - - For recent PHP versions (5, 7, 8), `PHP_FUNCTION(bypass_exec)` may need adjustment. The provided code snippet details this modification. |
| 57 | +Realistic situations where this becomes exploitable: |
49 | 58 |
|
50 | | -### Custom Extension Files: |
| 59 | +- CTFs or intentionally weak labs where `extension_dir` is writable |
| 60 | +- Shared-hosting or container mistakes that expose a writable extension path |
| 61 | +- A separate arbitrary file write primitive that already reaches `extension_dir` |
| 62 | +- Post-exploitation scenarios where you already escalated enough to drop files there |
51 | 63 |
|
52 | | -- **bypass.c**: |
53 | | - - Implements the core functionality of the custom extension. |
54 | | -- **php_bypass.h**: |
55 | | - - Header file, defining extension properties. |
56 | | -- **config.m4**: |
57 | | - - Used by `phpize` to configure the build environment for the custom extension. |
| 64 | +### 2. The module must match the target build |
58 | 65 |
|
59 | | -### Building the Extension: |
| 66 | +Matching only `PHP_VERSION` is not enough. The extension also needs to match: |
60 | 67 |
|
61 | | -1. **Compilation Commands:** |
| 68 | +- OS and CPU architecture |
| 69 | +- libc/toolchain expectations |
| 70 | +- `ZEND_MODULE_API_NO` |
| 71 | +- debug vs non-debug build |
| 72 | +- ZTS vs NTS |
62 | 73 |
|
63 | | - - Uses `phpize`, `./configure`, and `make` to compile the extension. |
64 | | - - Resulting `bypass.so` is then located in the modules subdirectory. |
| 74 | +If those do not match, `dl()` will fail or crash the process. |
65 | 75 |
|
66 | | -2. **Cleanup:** |
67 | | - - Runs `make clean` and `phpize --clean` after compilation. |
| 76 | +### 3. `open_basedir` is not the main defense here |
68 | 77 |
|
69 | | -### Uploading and Executing on the Victim Host: |
| 78 | +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. |
70 | 79 |
|
71 | | -1. **Version Compatibility:** |
| 80 | +## Building the malicious extension |
72 | 81 |
|
73 | | - - Ensures PHP API versions match between the attacker's and victim's systems. |
| 82 | +The classic route is still valid: |
74 | 83 |
|
75 | | -2. **Extension Loading:** |
| 84 | +1. Recreate the victim build as closely as possible |
| 85 | +2. Use `phpize`, `./configure`, and `make` to build a shared extension |
| 86 | +3. Export a PHP function such as `bypass_exec($cmd)` that wraps native command execution |
| 87 | +4. Upload the compiled module into `extension_dir` |
| 88 | +5. Load it with `dl()` and call the exported function |
76 | 89 |
|
77 | | - - Utilizes the `dl` function, circumventing restrictions by using relative paths or a script to automate the process. |
| 90 | +The attack is old, but still relevant because PHP 8.x changelogs continue to include `dl()`-specific crash fixes. The primitive still exists; the hard part is finding a deployment where it is reachable and where you can land a matching module. |
78 | 91 |
|
79 | | -3. **Script Execution:** |
80 | | - - The attacker uploads `bypass.so` and a PHP script to the victim's server. |
81 | | - - The script uses `dl_local` function to dynamically load `bypass.so` and then calls `bypass_exec` with a command passed via the `cmd` query parameter. |
| 92 | +## Minimal workflow |
82 | 93 |
|
83 | | -### Command Execution: |
| 94 | +### On the attacker box |
84 | 95 |
|
85 | | -- The attacker can now execute commands by accessing: `http://www.example.com/script.php?cmd=<command>` |
| 96 | +```bash |
| 97 | +mkdir bypass && cd bypass |
| 98 | +phpize |
| 99 | +./configure |
| 100 | +make |
| 101 | +``` |
86 | 102 |
|
87 | | -This detailed walkthrough outlines the process of creating and deploying a PHP extension to execute system commands, exploiting the `dl` function, which should ideally be disabled to prevent such security breaches. |
| 103 | +The resulting shared object will usually be under `modules/`. |
88 | 104 |
|
89 | | -{{#include ../../../../banners/hacktricks-training.md}} |
| 105 | +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. |
| 106 | + |
| 107 | +## Loading and using the extension |
| 108 | + |
| 109 | +If the target really supports `dl()` and the module is inside `extension_dir`, the runtime side is simple: |
| 110 | + |
| 111 | +```php |
| 112 | +<?php |
| 113 | +if (!extension_loaded('bypass')) { |
| 114 | + dl('bypass.so'); // use the correct filename for the target platform |
| 115 | +} |
| 116 | +echo bypass_exec($_GET['cmd']); |
| 117 | +?> |
| 118 | +``` |
90 | 119 |
|
| 120 | +On Windows the filename will typically be a `.dll`, while on Unix-like targets it will usually be a `.so`. |
91 | 121 |
|
| 122 | +## Attacker notes |
92 | 123 |
|
| 124 | +- Do not assume this works remotely just because `function_exists("dl")` returns true in some documentation or old writeup; validate the live SAPI |
| 125 | +- A failed `dl()` attempt may kill the PHP worker if the module is incompatible |
| 126 | +- From PHP 8 onward, disabled functions are removed from the function table, so userland enumeration may differ from older posts |
| 127 | +- 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 |
| 128 | + |
| 129 | +## References |
| 130 | + |
| 131 | +- [PHP manual: dl](https://www.php.net/manual/en/function.dl.php) |
| 132 | +- [Tarlogic: A deep dive into disable_functions bypass and PHP exploitation](https://www.tarlogic.com/blog/disable_functions-bypasses-php-exploitation/) |
| 133 | + |
| 134 | +{{#include ../../../../banners/hacktricks-training.md}} |
0 commit comments