题解作者:tl2cents
出题人、验题人、文案设计等:见 Hackergame 2024 幕后工作人员。
-
题目分类:math
-
题目分值:Hello? Admin!(250)+ Super Talent!(350)
传统的加密系统饱受 Padding Oracle Attack 的困扰,因此需要相当小心地处理数据填充逻辑。如果我们有一种方式能够有效检测到数据篡改,一切都迎刃而解了。于是小 A 决定设计一个超安全的认证加密系统,既然是超安全的系统,那么就用超安全的 Rust 语言、超标准的 AES-GCM 算法、以及超严格的限制来实现吧。另外,小 A 还超贴心地增加了一个认证恢复码,以防用户注册后不仅忘记了密码还忘记了自己的用户名,为了防止这个功能被滥用,小 A 严格限制了其使用次数。
最后,小 A 想到在这么有限的次数的访问下,密钥的熵值其实小一些也未尝不可,于是使用了 PIN 初始化了超级管理员的密钥。但是听说妮可神童口算 RSA 分解,相信对数穷举也不在话下,比如
提示:点击下面的「打开/下载题目」按钮会为你创建一个独立的题目环境,有效时间一小时。如果环境遇到问题,可以 关闭环境 后再试。
注记:本题给出了项目源码,可以在本地运行 cargo run --release
调试项目,请尽量在本地求解后再尝试远程。
这题的出题思路是现有带关联数据的认证加密(AEAD:Authenticated Encryption with Associated Data)的两个安全性问题:nonce 复用和 Partitioning Oracle Attack。与传统加密不同的是,认证加密会检测密文是否合法,即 MAC 的正确性,是存在解密失败的可能性的。对目前标准化的认证加密算法而言,解密失败的信息不能泄露,虽然大部分情况下不足以利用产生漏洞,但仍会削弱安全强度。
第二问源自于 Shadowsocks VPN 中存在的 Partitioning Oracle Attack (简记为 POA),POA 通过构造多密钥碰撞密文,使得单次 Oracle 可以确定一组密钥的正确性,而不是传统意义上的单个密钥,这也是 Partitioning 的意义。值得一提的是,目前所有标准化的认证加密算法比如 TLS 1.2/1.3 中广泛使用的 AES-CGM、ChaCha20-Poly1305,都是 non-committing AEAD,所谓 non-committing AEAD 意味着密文不与密钥严格绑定,因此,一个由攻击者精心构造的密文和 MAC 值,可以让其在特定的一组密钥下解密成功,容易受到 POA 的威胁。因此,committing AEAD 也是近年来认证加密设计的热点,比如 22 年欧密 Efficient Schemes for Committing Authenticated Encryption 提了 AES-GCM 一个非常简单的改进方案,使得 AES-GCM 可以成为 committing AEAD,据笔者目前来看,标准化还没有部署这个方案,其安全性证明也还待进一步验证。
第一问只需要任意登陆一个具有 admin 权限的用户即可获得 flag。首先我们重点关注认证恢复码的逻辑:
- 系统初始化一个超级管理员和随机的 32 字节密钥 KEY,根据超级管理员的 username 和 PIN 生成一个 SUPER KEY。
- 用户注册逻辑:每个用户注册时,会根据其 username 和 password 生成一个 12 字节的
Nonce = HMAC-SHA256(key=username, data=password)
,然后 Server 加密 username 生成密文Cipher = AES-GCM(KEY, Nonce, username, AD="admin=false")
,Server 存储凭证信息Credential = (Nonce, Cipher, AD)
,并且将其序列号化后以 BASE64 编码返回给用户,即认证恢复码。 - 登陆认证逻辑:双重验证,根据用户的 username 和 password,首先验证
HMAC-SHA256
,即 Nonce 的正确性,然后验证AES-GCM
解密的正确性。 - 认证恢复码逻辑:用户忘记密码或者用户名后,可以使用认证恢复码找回,Server 会根据用户提供的认证恢复码解密出 USERNAME,然后根据认证恢复码重置用户相关信息。
值得注意的,认证恢复码生成时使用 username 作为 HMAC 的密钥,从而我们可以构造 HMAC 伪碰撞,注意到一般的 HMAC 框架如下:
我们在 username 后面添加若干个 0 字节(username 长度不超过 64),得到的 Hmac 值是一样的,比如用户名 tl2cents\x00
和 tl2cents
。这样可以导致认证恢复码复用 nonce 加密不同的消息,通过 GHASH 的相关构造,我们可以求解有限域上的多项式方程恢复完整性密钥,从而通过 Recover 功能修改现有用户的权限(AD),进而认证为 admin。
简单介绍 AES-GCM 中 GHASH 的相关构造:
- 伽罗瓦域:
$\mathbb{F}_{2^{128}}$ ,模多项式为$P(x) = x^{128} + x^7 + x^2 + x + 1$ . - MAC 密钥:
$H = \textsf{AES}_K(0) \in \mathbb{F}_{2^{128}}$ . - 常数系数:
$C = \textsf{AES}_K(\textsf{Nonce||1})$ .
记由密文和关联数据组成的填充消息 pad(AD) || pad(CT) || len(AD) || len(CT)
表示为 GHASH
函数可以表示为一个简单的多项式求值:
一个简单的图示如下:
如果 nonce 不变,那么
解上述方程组,可以恢复出
第二问需要登陆系统中初始超级管理员的账号获得 flag。认证恢复码恢复账户的逻辑需要 nonce 与 server 端现有的 nonce 相同,我们不能通过第一问恢复出来的 nonce 对应的密钥信息,尝试修改系统中已有的账户。但是 Recover 中有另一个逻辑可以绕过 nonce 的匹配,直接修改用户的密码,那就是使用超级管理员的密钥(super key)认证。super key 是典型的弱密钥,使用超级管理员的 username 和 6 位的 PIN 值生成,因此在完成第一问后,我们可以通过查看 users 页面得到超级管理员的 username,从而减少 super key 的熵值,未知的是仅 6 位的 PIN(为了减少运行时间,实际熵为
预期解是利用 Partitioning Oracle Attack 进行近似对数穷举,每次生成特殊的密文消息(multi-key collision ciphertext),对一半的密钥而言,它能够正确解密(但是解密为随机的数据),对于另一半的密钥而言,它们不能解密成功,解密是否成功的信息可以从 http response 返回的状态码中得到。从而每次我们都能筛选掉一半的密钥,在理想状态下可以在 19 次以内得到密钥。但是,考虑到生成特殊的密文消息(multi-key collision ciphertext),需要进行拉格朗日多项式插值,复杂度与需要碰撞的密钥的个数
下面简要介绍 Partitioning Oracle Attack 的基本原理。记
上述等式可以重写为 len(AD) || len(CT)
,其中 len(AD)
是关联数据的长度,len(CT)
是密文的长度。因此,对于
其中
对
- Sagenmath 多项式环内置的
lagrange_polynomial
方法。 - PARI 库实现的
polinterpolate
方法。 - C++ NTL 库实现的拉格朗日插值算法。
上述插值算法在我本机上测试的效率依次递增。EXP 中最终使用了 C++ 的 NTL 库进行拉格朗日插值,对于
最开始这个题的 demo 是使用 ChaCha20-Poly1305
实现的,由于 Poly1305
中有双模数和明文块的填充特征,会比现有的这题更难一些,Poly1305
中 POA 的多密钥碰撞密文的构造需要求解 CVP 问题,本地测试发现,随着格维度的增大,有限时间内最多生成
关于 EXP:
-
运行环境:Sagemath 10.2,一些低版本的 SageMath 比如 9.5 不兼容。比如
gf2e.from_integer
和gf2e.to_integer
方法。 -
NTL 依赖:如果需要运行 EXP,需要首先安装 NTL 库,然后编译 binary:
$ g++ -O2 -std=c++11 -pthread -march=native -o interpolate poly_interpolate.cpp -lntl -lgmp -lm
参考文献:
- Partitioning Oracle Attacks: https://eprint.iacr.org/2020/1491.pdf
- Partition Oracles from Weak Key Forgeries: https://eprint.iacr.org/2021/1296
- Galois/Counter Mode Wiki: https://en.wikipedia.org/wiki/Galois/Counter_Mode