0CTF 2023 Writeup
Lysithea
又是一次大型比赛,又是单刷团队副本,又是发着烧答题(怎么这么似曾相识呢)。
这次难度感觉很高,我总共只花了半天(不到6个小时),misc的ctar做出来了,math exam做出了前两个flag,在比赛结束后两天把后两个flag也补了。感觉有些知识点值得记录
ctar (misc, chacha20, tar format)
代码分析
题目代码有160多行(很长,但是在这个比赛里算短的了)。每次连接后,首先要过一个proof of work, 即爆破一个四位的sha256 hash。
通过PoW后,是一个基于ChaCha20算法和tar file的文件系统。实际的物理存储在一个临时目录里,并且有一个self.data
字典来记录上传的文件名、类型。结合这些,我们来看看暴露的几个接口:
- add secret,可以上传一个文件,然后随机生成一个8位hex作为文件名。然后以
self.data[name] = 1
的形式存储。 - upload ctar,是download ctar的逆过程,上传的本身是Chacha20加密的密文,前8个字节是IV(nonce),解密后应该是一个tar文件,然后先把
self.data
对应名字改为1,然后把tar文件解压(extractall
)到当前目录
for fname in f.getnames():
self.data[fname] = 1
f.extractall(path=self.dir)
os.unlink(tname)
self.request.sendall(b"[OK] upload succeeded\n")
不过加入不是合法的ctar文件,则会把解密后的明文打印出来。
- read secret,假的,没有功能
- download ctar,把目前
self.data
包括的所有文件打包成tar文件,然后进行ChaCha20加密(密钥是固定且未知的)。但是只要包含了真正的flag(self.data
有项目为0)在返回的密文中不包含IV。 - add flag,类似add secret,但是添加的是flag内容,并且会设置对应
self.data[name]=0
然后会有一些上限,上传文件总数最大为9(包括ctar解压出来的文件数),上传文件大小最大为100,tar文件最大为100000。没有报错,不过因为基本会有特征输出,如果没有看到可以反推可能发生了报错。
解题思路
很明显我们需要add flag之后通过某种方式拿到flag的具体位置,但是ctar在没有key的情况下加解密只能在云端进行。
首先一个知识点:Chacha20作为一种基于模加、循环移位、异或的流密码算法,已知一组明文密文对,可以伪造任意密文(non-authority):c1 ^ m1 ^ m2 = c2
。
另一方面,当不添加任何数据时,我们可以download获得完整的IV和cipher,同时我们非常清楚明文是什么,就是空tar,即:
with tarfile.open("a.tar", 'w') as f:
pass
因而我们可以做到在upload流程中上传任何tar文件走解压流程或解密流程。然而,upload流程是需要IV的,绕过的方法是这样一个TOCTOU:
for fname in f.getnames():
self.data[fname] = 1
# make above succeed and below fail
f.extractall(path=self.dir)
即需要在f.getnames()
流程中成功,但是extractall
流程中失败。一个可行的方案是,如果我们在tar里打包一个同名的文件夹,因为Linux文件系统不允许同名的文件和文件夹,所以解压过程会失败,flag文件不会被覆盖,但是self.data
已经改了,我们就可以正常在download
过程中获得IV了。最后我们通过异或破坏掉tar结构,走一遍解密流程,通过报错信息就可以获取到原始明文了。(原始明文是包含flag文件的tar)
flag{s0_....___wHat_hApPeneD_w1Th_My_t4rfI1E?_:/}