对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.cphp_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()方法:

  1. 该方法会判断路径是相对路径还是绝对路径
  2. 将路径传入virtual_file_ex()
  3. 关键检查:如果是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)解决线程并发问题:

  1. 每个线程获取自己独立的资源副本
  2. 通过ts_allocate_id()为每个线程分配唯一资源ID
  3. 资源访问通过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)])

初始化流程

  1. 启动CLI或CGI时通过SAPI调用tsrm_startup()
  2. 模块初始化时分配资源ID
  3. 线程安全模式下使用"虚拟目录"概念来保证线程安全

0x06 总结与关键点

  1. 路径检查差异

    • 递归模式始终检查路径有效性
    • 非递归模式仅在TS下检查路径
  2. 线程安全影响

    • TS模式使用virtual_mkdir()并检查路径
    • NTS模式直接调用系统API不检查路径
  3. Windows特殊字符

    • *?在路径检查时会被拒绝
    • 可以使用#等字符绕过限制
  4. 实际应用

    • 在安全审计时需要注意不同环境下的行为差异
    • 文件操作漏洞利用需要考虑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()函数在不同环境下的行为差异及其背后的线程安全机制,这对安全研究和漏洞分析具有重要意义。

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) 关键分支逻辑 0x03 递归模式分析(recursive=true) 无论线程安全(TS)还是非线程安全(NTS)模式,都会进入 expand_filepath_with_mode() 方法: 该方法会判断路径是相对路径还是绝对路径 将路径传入 virtual_file_ex() 关键检查:如果是Windows系统且路径包含 * 或 ? ,直接返回错误 结果 :两种模式下都会因为路径检查失败而返回"Invalid path"错误 0x04 非递归模式分析(recursive=false) 进入 php_mkdir() 方法,最终调用 VCWD_MKDIR 宏,该宏有三种实现: 线程安全模式(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 宏实现: 初始化流程 启动CLI或CGI时通过SAPI调用 tsrm_startup() 模块初始化时分配资源ID 线程安全模式下使用"虚拟目录"概念来保证线程安全 0x06 总结与关键点 路径检查差异 : 递归模式始终检查路径有效性 非递归模式仅在TS下检查路径 线程安全影响 : TS模式使用 virtual_mkdir() 并检查路径 NTS模式直接调用系统API不检查路径 Windows特殊字符 : * 和 ? 在路径检查时会被拒绝 可以使用 # 等字符绕过限制 实际应用 : 在安全审计时需要注意不同环境下的行为差异 文件操作漏洞利用需要考虑PHP的线程安全模式 参考实现流程图 通过深入分析PHP源码,我们理解了 mkdir() 函数在不同环境下的行为差异及其背后的线程安全机制,这对安全研究和漏洞分析具有重要意义。