Disable Functions Bypass - dl Function

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

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.

这是一个 真实 的原语,但在现代目标上,它远不像早期写作所暗示的那么常见。

Why this bypass is uncommon today

主要阻碍因素有:

  • dl() 必须存在且不能被禁用
  • enable_dl 必须仍然允许动态加载
  • 目标 SAPI 必须支持 dl()
  • payload 必须是为 相同目标 ABI 编译的有效 PHP 扩展
  • 扩展必须可以从配置的 extension_dir 访问到

官方 PHP 手册是这里最重要的现实检查:dl() 仅对 CLI 和 embed SAPIs 可用,并且仅在从命令行运行时对 CGI SAPI 可用。这意味着该技术通常在普通 PHP-FPM/mod_php web 请求中不可用,所以在花时间构建 payload 之前请先检查 SAPI。

另请注意,enable_dl 是一个 INI_SYSTEM 设置,并且从 PHP 8.3.0 起,PHP 将其记录为 已弃用,因此你通常无法在运行时通过攻击者控制的 PHP 代码将其打开。

如果 dl() 不可行,回到更广泛的列表 module/version dependent bypasses

Fast triage from a foothold

在构建任何东西之前,收集模块必须匹配的确切参数:

<?php
phpinfo();
echo "PHP_VERSION=" . PHP_VERSION . PHP_EOL;
echo "PHP_SAPI=" . php_sapi_name() . PHP_EOL;
echo "ZTS=" . (PHP_ZTS ? "yes" : "no") . PHP_EOL;
echo "INT_BITS=" . (PHP_INT_SIZE * 8) . PHP_EOL;
echo "enable_dl=" . ini_get("enable_dl") . PHP_EOL;
echo "extension_dir=" . ini_get("extension_dir") . PHP_EOL;
echo "disabled=" . ini_get("disable_functions") . PHP_EOL;
?>

What you care about:

  • PHP_SAPI:如果这是 fpm-fcgiapache2handlerdl() 通常在 web 利用场景中是死胡同
  • extension_dir:payload 必须从这里加载
  • PHP Version、CPU 架构、debug/non-debug,以及 ZTS/non-ZTS:你的模块必须与之匹配
  • disable_functions:确认 dl 的缺失是因为被禁用,还是因为 SAPI 不支持它

实际利用限制

1. 通常你需要对 extension_dir 有写权限

这是最大的瓶颈。

dl() 接收 extension filename,PHP 从 extension_dir 加载它。实际上,这意味着普通的任意文件上传到 /var/www/html/uploads 不足够。你仍然需要一个可将 .so/.dll 放到 PHP 实际从中加载扩展的路径。

实际可利用的场景示例:

  • CTFs 或有意设置弱点的实验环境,extension_dir 可写
  • 共享托管或容器配置错误,暴露了可写的 extension 路径
  • 一个能写任意文件的独立原语,已经能写入 extension_dir
  • 在后利用场景中,已经提权到可以把文件放到那里

2. 模块必须与目标构建匹配

仅匹配 PHP_VERSION 不够。扩展还需要匹配:

  • 操作系统和 CPU 架构
  • libc/工具链的期望
  • ZEND_MODULE_API_NO
  • debug 与 non-debug 构建
  • ZTS vs NTS

如果这些不匹配,dl() 会失败或导致进程崩溃。

3. open_basedir 并不是这里的主要防线

一旦你能把模块放到 extension_dir 并调用 dl(),扩展代码就会在 PHP 进程内执行。那时相关的障碍并不是 open_basedir,而是能否把一个有效的共享对象放到扩展加载路径中。

构建恶意扩展

经典流程仍然有效:

  1. 尽可能复现受害者的构建环境
  2. 使用 phpize./configuremake 构建共享扩展
  3. 导出一个 PHP 函数,例如 bypass_exec($cmd),用来封装原生命令执行
  4. 将编译好的模块上传到 extension_dir
  5. dl() 加载它并调用导出的函数

这个攻击手法虽旧但仍相关,因为 PHP 8.x 的变更日志仍持续包含针对 dl() 的崩溃修复。该原语仍然存在;难点在于找到一个可达的部署环境,以及能投放匹配模块的位置。

最小化工作流程

在攻击者主机上

mkdir bypass && cd bypass
phpize
./configure
make

生成的共享对象通常位于 modules/ 下。

如果你在与目标不同的环境上构建,请将生成的文件视为草稿,直到你确认 ABI 与 victim 匹配。

加载并使用扩展

如果目标确实支持 dl() 且模块位于 extension_dir 中,运行时部分很简单:

<?php
if (!extension_loaded('bypass')) {
dl('bypass.so'); // use the correct filename for the target platform
}
echo bypass_exec($_GET['cmd']);
?>

在 Windows 上,文件名通常为 .dll,而在类 Unix 的目标上通常为 .so

攻击者注意事项

  • 不要仅因为某些文档或旧文章中 function_exists("dl") 返回 true 就假设这在远程环境中可用;务必验证实时 SAPI
  • 如果模块不兼容,失败的 dl() 调用可能会导致 PHP worker 崩溃
  • 从 PHP 8 开始,被禁用的函数会从函数表中删除,因此用户态的枚举可能与较早的帖子不同
  • 如果你无法写入 extension_dir,相比于本节已介绍的 FPM/FastCGI、LD_PRELOAD 或模块特定的绕过方法,该技术通常不太实用

参考资料

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks