对PHP中的mkdir()函数的研究
字数 1522 2025-08-26 22:11:29
PHP中mkdir()函数的深入研究与线程安全机制分析
0x01 背景与问题发现
在分析WordPress Image远程代码执行漏洞时,发现PHP的mkdir()函数在不同参数和环境下的行为存在差异:
- 环境:Windows + PHP-7.0.12-nts
- 现象:当
recursive=false时能成功穿越目录创建文件夹,而recursive=true时创建失败
进一步测试发现不同PHP版本和线程安全模式下行为不一致:
| Windows环境 | thread-safe | non-thread safe |
|---|---|---|
| recursive=false | fail (No error) | success |
| recursive=true | fail (Invalid path) | fail (Invalid path) |
0x02 源码分析与调试
源码位置
mkdir()函数实现在php-7.2.16-src/main/streams/plain_wrapper.c的php_plain_files_mkdir()方法(line 1234)
关键分支逻辑
if (recursive) {
// 递归创建目录的分支
expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND);
} else {
// 非递归创建目录的分支
php_mkdir();
}
0x03 递归模式分析(recursive=true)
无论线程安全(TS)还是非线程安全(NTS)模式,都会进入expand_filepath_with_mode()方法:
- 该方法会判断路径是相对路径还是绝对路径
- 将路径传入
virtual_file_ex() - 关键检查:如果是Windows系统且路径包含
*或?,直接返回错误
结果:两种模式下都会因为路径检查失败而返回"Invalid path"错误
0x04 非递归模式分析(recursive=false)
进入php_mkdir()方法,最终调用VCWD_MKDIR宏,该宏有三种实现:
#ifdef VIRTUAL_DIR
#define VCWD_MKDIR(pathname, mode) virtual_mkdir(pathname, mode)
#elif defined(ZEND_WIN32)
#define VCWD_MKDIR(pathname, mode) php_win32_ioutil_mkdir(pathname, mode)
#else
#define VCWD_MKDIR(pathname, mode) mkdir(pathname, mode)
#endif
线程安全模式(TS)
- 定义了
VIRTUAL_DIR(由ZTS宏控制) - 调用
virtual_mkdir() - 同样会进行
virtual_file_ex()检查,包含*或?则失败
非线程安全模式(NTS)
- 调用
php_win32_ioutil_mkdir() - 直接调用Windows API
CreateDirectoryW - 不进行路径检查,可以成功创建目录
0x05 线程安全机制(TSRM)
PHP使用线程安全资源管理器(Thread Safe Resource Manager)解决线程并发问题:
- 每个线程获取自己独立的资源副本
- 通过
ts_allocate_id()为每个线程分配唯一资源ID - 资源访问通过
TSRMG宏实现:
#define TSRMG(id, type, element) (TSRMG_BULK(id, type)->element)
#define TSRMG_BULK(id, type) ((type) (*((void ***) tsrm_get_ls_cache()))[TSRM_UNSHUFFLE_RSRC_ID(id)])
初始化流程
- 启动CLI或CGI时通过SAPI调用
tsrm_startup() - 模块初始化时分配资源ID
- 线程安全模式下使用"虚拟目录"概念来保证线程安全
0x06 总结与关键点
-
路径检查差异:
- 递归模式始终检查路径有效性
- 非递归模式仅在TS下检查路径
-
线程安全影响:
- TS模式使用
virtual_mkdir()并检查路径 - NTS模式直接调用系统API不检查路径
- TS模式使用
-
Windows特殊字符:
*和?在路径检查时会被拒绝- 可以使用
#等字符绕过限制
-
实际应用:
- 在安全审计时需要注意不同环境下的行为差异
- 文件操作漏洞利用需要考虑PHP的线程安全模式
参考实现流程图
mkdir()调用
│
├── recursive=true → expand_filepath_with_mode()
│ │
│ └── virtual_file_ex()检查 → 拒绝*和? → "Invalid path"
│
└── recursive=false → php_mkdir()
│
├── TS模式 → virtual_mkdir() → virtual_file_ex()检查 → 可能失败
│
└── NTS模式 → php_win32_ioutil_mkdir() → CreateDirectoryW → 直接创建
通过深入分析PHP源码,我们理解了mkdir()函数在不同环境下的行为差异及其背后的线程安全机制,这对安全研究和漏洞分析具有重要意义。