2026DASCTF夏季赛
本文最后更新于 2026年6月1日 下午
crypto
three_friends
题目
1 | |
题目将 flag 分成三段,分别用 RSA 加密。关键点在于三个模数 共享质因子:
1 | |
三个模数两两之间都有一个公共质因子,这就是题目名 “three friends” 的含义。
1. 利用 GCD 分解模数
由于 n1 和 n2 共享质因子 q:
1 | |
验证:p*q == n1, q*r == n2, p*r == n3 ✓
2. 分别计算私钥并解密
1 | |
3. 拼接得到 flag
1 | |
解密脚本
1 | |
1 | |
lattice_oracle
Flag
1 | |
题目分析
题目实现了一个 Learning With Errors (LWE) 问题,参数极小:
- 维度
n = 6 - 模数
q = 97 - 样本数
m = 30 - 秘密向量
s的每个分量在{0, 1, 2, 3}中 - 误差
e_i ∈ {-1, 0, 1}
加密方式为 AES-CBC,密钥由 sha256(str(s)) 的前 16 字节派生。
LWE 方程组
其中 



解题思路
由于参数极小(n=6,s[i] ∈ {0,1,2,3}),秘密空间仅有 
对每个候选秘密 



找到正确的 
sha256(str(s)) 派生 AES 密钥,解密得到 flag。
解题脚本
1 | |
关键点
- LWE 参数(
n=6,q=97, 误差范围{-1,0,1})过小,暴力搜索空间仅 - 实际 CTF 中的 LWE 问题通常需要格基归约(LLL/BKZ),但本题参数故意设小以降低难度
- AES 密钥由
sha256(str(s))派生,注意 Python 的str(list)格式
phantom_sign
Flag
1 | |
题目分析
题目实现了 ECDSA 签名(secp256k1 曲线),使用 40 条已知消息的签名。关键弱点在于 nonce 的生成方式:
1 | |
而曲线阶 n 为 256 位。这意味着 k_i < 2^248,即 nonce 比 n 少 8 位(高位为 0)。这是一个典型的 biased nonce 攻击场景。
ECDSA 签名方程
可改写为:
其中 

解题思路
Hidden Number Problem (HNP)
由于 




Kannan 嵌入法
将 CVP(最近向量问题)转化为 SVP(最短向量问题):
- 构造格
,基向量为
和
- 目标向量为
- 使用 Kannan 嵌入,添加目标行
其中
- LLL 归约后的最短向量即为
参数分析
- 格维度:
(40 条签名 + 1 个 Kannan 嵌入列)
- 目标向量范数:
- 高斯启发式:
- 目标范数
高斯启发式,LLL 可以找到
解题脚本
需要 pycryptodome 和 python-flint 库
解题脚本:
1 | |
关键点
os.urandom(31)生成的 nonce 为 248 位,比曲线阶n(256 位)少 8 位- 这 8 位的 bias 足以通过 Hidden Number Problem + 格基归约恢复私钥
- 40 条签名提供了足够的方程数(需要约 33+ 条)
- 使用 Kannan 嵌入将 CVP 转化为 SVP,用 flint 的快速 LLL 求解
- 核心公式:
,其中
web
InkVerse
题目描述
1 | |
http://ca62fb0b.http-ctf2.dasctf.com/api/docs暴露了接口文档
1 | |
注册账户test/test

访问/bulletin提示Access denied. Only featured article authors, reviewers, and admins can view the bulletin board.
Reputation达到50权限提升至Reviewer
通过打赏文章可以获取Reputation,打赏消耗Balance

10 Balance == 2 Reputation
200/10=20*2=40 Reputation,不够50提升到Reviewer
看cookie格式可以知道是Flask session cookie 格式:base64_payload.timestamp.hmac_signature
1 | |
尝试伪造user_id
1 | |
使用rockyou字典爆破,均未成功。密钥强度足够,此路不通。
测试文章flask SSTI

用户名处也没有

正常打赏流程:
1 | |
问题:步骤 1 的余额检查和步骤 2 的余额扣除之间存在 TOCTOU (Time-of-Check-Time-of-Use) 竞态窗口。如果同时发送多个请求,所有请求可能在余额扣除前都通过了检查。
1 | |
- 并发请求全部在余额扣除前通过了
balance >= 10检查 - 30 个请求 × 2 声望 = 60 声望(超过 50 的 reviewer 阈值)
- 余额变为 -100(透支),但声望已经足够
- 系统自动将角色从
user晋升为reviewer
再次访问/bulletin

| 角色 | 权限 | 获取方式 |
|---|---|---|
user |
创建文章、打赏、查看公告(public) | 注册即获得 |
reviewer |
审核文章、导出文章、查看审核面板 | 声望 ≥ 50 自动晋升 |
admin |
管理员权限、导入文章、晋升用户 | 仅限 user_id=1 |
featured_author |
查看分级公告中的特殊内容 | 文章被精选后获得 |
查看api文档中有文章导出接口

查看任务状态

下载文件

1 | |
得到Feature-Token
文章要经过/review才能公开,但是不能审核自己的文章,所以要注册另一个账户通过toctou提权到reviewer然后审核通过
Feature-Token是根据文章动态生成的,每个文章不同,所以要导出自己的文章

用Feature-Token将自己的文章精选,则权限提升至featured_author
1 | |


CorpGate
技术栈: Node.js + Express + EJS + JWT + Cookie Auth
CorpGate 是一套企业员工门户系统,主要模块包括:
| 路由文件 | 功能 |
|---|---|
routes/auth.js |
用户注册、登录、登出 |
routes/user.js |
Dashboard、通讯录、设置、搜索、健康检查 |
routes/admin.js |
管理面板(需要 admin 角色)、诊断令牌生成 |
routes/diagnostic.js |
诊断报告执行(运行 /readflag) |
漏洞一:deepMerge 原型链污染(Prototype Pollution)
漏洞位置: utils/merge.js
1 | |
关键缺陷分析:
BLOCKED_KEYS** 过滤不全**:constructor和prototype不在BLOCKED_KEYS中,只在BLOCKED_ROOTS中- 深度限制可绕过:
BLOCKED_ROOTS的检查条件是depth < 3,当depth >= 3时,constructor、prototype等危险键名不再被阻止 - 函数类型也可递归: 当
target[key]是函数时(如构造函数),代码仍然递归进入合并
漏洞二:JWT 签名密钥可被外部轮换
漏洞位置: config.js + routes/user.js
1 | |
漏洞三:诊断端点执行系统命令
漏洞位置: routes/diagnostic.js
1 | |
攻击链验证
Step 1:注册用户
服务器返回 302 重定向到 /dashboard,并在 Set-Cookie 中返回 JWT token:
1 | |
Step 2:触发原型链污染
利用 POST /api/settings 的 deepMerge 漏洞。请求体结构经过精心设计:
1 | |
1 | |

污染路径详解:
| 深度 | 键名 | BLOCKED_KEYS 检查 | BLOCKED_ROOTS 检查 | 结果 |
|---|---|---|---|---|
| 0 | notifications |
未命中 | - | 递归进入 |
| 1 | digest |
未命中 | - | 递归进入 |
| 2 | channels |
未命中 | - | 递归进入 |
| 3 | constructor |
未命中 | depth < 3 为 false,放行 |
递归进入(target 是函数 Object) |
| 4 | prototype |
未命中 | depth < 3 为 false,放行 |
递归进入(target 是 Object.prototype) |
| 5 | pending |
未命中 | 未命中 | ✅ Object.prototype.pending = "passwd" |
Step 3:触发 JWT 签名密钥轮换
/api/system/healthcheck 端点无需认证,直接调用 configRefresh():

关键原理:
1 | |
响应确认密钥已轮换:
1 | |
Step 4:伪造 Admin JWT

使用伪造的 admin JWT 访问 /admin 管理面板:
1 | |
页面返回:

Step 5:执行诊断获取 Flag
1 | |
响应:

TaxManager
代码审计总结
1 | |
漏洞1: 反射型权限提升 (Privilege Escalation via Reflection)
文件: AuthController.java:122-134
1 | |

使用Java反射设置User对象的任意字段。只禁止了 role=admin,但允许 role=reviewer,实现权限提升
1 | |

/api/profile验证

漏洞2: 不安全的Java反序列化 (Unsafe Deserialization)
文件: com.tax.util.SerializeUtil:20-24
1 | |

使用原始 ObjectInputStream.readObject(),无任何类型白名单或过滤。
找调用

com.tax.controller.ExportController

参数是voucherData,由/api/export/generate接口传入,这个接口访问需要reviewer权限,并且需要从/api/export/prepare获取exportToken
漏洞3: 自定义反序列化Gadget链
com.tax.util.ScheduledTaskHandler
1 | |
反序列化时会自动执行 taskQueue 里的所有 Runnable 对象的run()方法。
找Runnable+Serializable的类并且有run()方法
得到com.tax.job.ReportJob
1 | |
这里是模板渲染,参数可控。generator是PdfReportGenerator的对象
查看com.tax.report.PdfReportGenerator
1 | |
直接将用户控制的内容作为FreeMarker模板执行,且使用 VERSION_2_3_31 默认的 UNSAFE_RESOLVER,允许使用 ObjectConstructor 等危险内置函数。
Gadget链:
1 | |
漏洞4: XXE (XML External Entity Injection)
文件: ImportController.java:21-23
1 | |

这里waf不许读flag,由于freemarker的ssti没回显,所以可以ssti执行mv /flag /tmp/xxx来通过xxe读取
漏洞利用
首先找source,也就是voucherData是怎么传入的。
1 | |
找setVoucherData()
在com.tax.service.RefundService
1 | |
通过attachmentData参数传入
跟进approveRefund

com.tax.controller.ReviewController
1 | |
attachmentData来自/api/review接口请求的body
这个接口除了验证role为reviewer,还调用了verifySignature
1 | |
api.signing.secret硬编码在BOOT-INF/classes/application.properties
1 | |
ssti payload,waf还会检测flag的内容,所以要base64编码
1 | |
gen_payload.js
1 | |
执行时需要将应用的类加入classpath:
1 | |
1 | |
使用获取到的密钥对payload进行HMAC-SHA256签名:
1 | |
创建退税申请
1 | |

提交审核请求,将恶意payload作为attachmentData,并带上X-Signature头
1 | |

此时恶意payload已作为voucherData存储在数据库中
导出接口使用两步验证:先prepare获取token,再generate使用token
1 | |

导出触发反序列化
1 | |

xxe读flag
