第七届中科大信息安全大赛部分题解
今年再次参与了好玩有趣又能学到知识的 Hacker game 比赛,题目还是熟悉的味道,很好的延续了寓教于乐的传统。相比之下,某著名二次元宅舞站的题目就……
下面是我的部分题解:
签到
随便尝试提交后,提示需要输入整数个 flag 数量,F12 里看到是 get 传参,直接将地址栏的数字修改成 1
再提交即可。
2048
直接翻看 JS 源码,找到了游戏成功时的请求地址
if (won) {
url = "/getflxg?my_favorite_fruit=" + ('b'+'a'+ +'a'+'a').toLowerCase();
}
尝试手工拼出 URL /getflxg?my_favorite_fruit=baaa
,提交却失败了。我以为是有什么 override toLowerCase 之类的骚操作,没有在意,直接刷新页面准备重试,却发现游戏状态还在。意识到状态应该是保存到本地了。随后在 localStorage 中找到个 won 字段,直接修改成 true
后刷新页面,拿到 flag。
此时再回过头来看之前的 toLowerCase 魔法,在 js 里 eval 一下,发现 ('b'+'a'+ +'a'+'a').toLowerCase()
的输出是 banana
。我才意识到原来是其它的 JS 魔法,具体可以看这里。
从零开始的记账工具人
开始尝试用 Excel 本身来解决,到网上找到阿拉伯数字转大写数字的方法,但没有便捷的反向转换方式。于是将文件导出成 csv 后,自己写了个简单的 Python 脚本来转成阿拉伯数字。其中碰到一些与“拾”、“整”相关的 edge case,导致第一遍算出来结果不对。用 Excel 的阿拉伯数字转大写功能又校对了一遍才发现,修正之后拿到 flag。
超简单的世界模拟器
直接搜“生命游戏”,找到几种可以无限复制的初始状态。用一个不断向右移动的“飞船”结构解决了第一题,再加上一个不断扩大繁殖的结构解决了第二题。
自复读的复读机
一个经典的代码自举题,被称之为Quine。我到网上找到一个 C 语言的版本后,简单修改解决。
i="i=%c%s%c;print((i%%(34,i,34))[::-1],end='')";print((i%(34,i,34))[::-1],end='')
不过由于 Python 的 %r
功能,其实还有更简单的实现方式。
s='s=%r;print((s%%s)[::-1],end="")';print((s%s)[::-1],end="")
233 同学的字符串工具
第一小题:我一开始想着是有什么特殊注音字母或 Mark 字符能够躲过正则检查的同时,还能再 upper 过程中丢失。直接写了段代码尝试爆破,然而并没有结果。
for i in range(0x10FFFF):
test(chr(i) + 'FLAG')
test('FL%sG' % chr(i))
后来意识到会不会是一个字符变多个字符的情况,又写代码找了一番,发现了 fl
字符的存在(注意不是 fl
)。
for i in range(0x10FFFF):
c = chr(i)
if len(c) != len(c.upper()):
print(c, c.upper())
提交 flag
后通过。
第二小题:先简单尝试了一下 'a'.encode('utf-7')
,果然得到的还是 a
(题目怎么可能这么简单嘛)。于是跑去看 UTF-7 的规范,发现它是一种类似 URL-Encoded 和 base64 的混合体。手动构造出 fl+AGE-g
后通过。
233 同学的 Docker
docker pull xxx
下来之后,用 docker inspect xxx
看 LowerDir
的路径,直接去本机的 /var/lib/docker/overlay2
目录里对应的路径看文件内容。
所以说同学们都要多多留意 docker 的 layers 特性呀。
从零开始的 HTTP 链接
先尝试了用 curl 直接访问,果然不行呢(150分难道会白送你吗)
抄起 socat 转发端口搞定。
socat tcp-listen:8000,fork,reuseaddr tcp-connect:x.x.x.x:0
来自一教的图片
在知乎找到一篇回答,讲如何通过傅里叶变换的方式给图片加水印,参考着处理了图片后,得到答案。
超简陋的 OpenGL 小程序
真的简陋,到网上找了段 OpenGL 例子,复制过来随便改改就过了,我也还不明白怎么回事。
生活在博弈树上
很好的一道 C/C++ 指针内存安全科普题。
看到源码里的 gets
调用而非 gets_s
就知道下手点在哪了。
用反汇编工具看下栈的分布情况,input 距离 success 有 0x8F 个字节,所以输入 0x8F 个字符再加上一个 '\1'
即可覆盖掉 success。
注意要正确输入之后才会检查 success,所以输入内容的前几个字符还是要保证为正确的坐标输入格式。
来自未来的信笺
手动尝试解码了几张二维码之后,意识到可能存在 binary 内容,应该是要将所有二维码中的内容拼接成个压缩包文件。
然后尝试了N个库,终于有个 zbar 能“正常”解码出内容。发现是个损坏的 tar 文件,其中有个 repo.tar.xz 无法正常解压。对照着 xz file format 手工修复半天后,依旧无法正常解压,遂放弃。
最后看官方题解,原来 tar 文件是正常的。我用的库解码二进制内容时产生了错误,导致文件损坏……
狗狗银行
开始看到公告:
每个用户最多 1000 张卡,每张卡最多 100000 条交易
以为是并发问题,写了脚本“压测”服务器之后,并没能成功。然后注意到利息计算是存在小数的,可能和取整有关。经过计算,得出卡里余额为 167 时,效益最高。简单改了下我的“压测”脚本,创建200张卡,第一天用信用卡给每张卡转 167 元,之后每天从储蓄卡里转出1元利息还给信用卡,成功解决。
普通的身份认证器
首先题目暗示某个依赖库版本老旧存在安全问题,然后身份认证是通过 JWT 实现的,就先搜搜 JWT 的安全问题。找到这篇文章。看来下手点需要拿到网站用于签名的公钥。
通过网页代码注释可以知道后端使用的 FastAPI 框架,于是跑去看文档。发现其非常方便的提供了 /docs
endpoint 来查阅 API。访问网站的文档后很明显能发现一个可以的 /debug
接口,直接尝试用页面自带的调试功能请求,拿到 public key
。最后手工拼一下数据结构,用CyberChef 生成 JWT Token 解决了。
超简易的网盘服务器
开始还以为是老旧版本 h5ai 的漏洞,但通过 dockerfile 可以看出用的是当前最新版,应该是没戏了。最后发现直接访问 PHP 的话,可以绕过根目录的身份认证机制。于是直接发请求调用 h5ai 的打包下载功能,将 flag 下载回来,从而拿到 flag。
超安全的代理服务器
直接访问页面,提示已经 push 了最新的 secret key,意识到是 HTTP2 的 Server Push 功能。看了下 curl 的文档,然而并不支持 HTTP2 Server Push 功能。于是选择了 go 的 http2 库附带的 h2i 工具。
$ ~/go/bin/h2i -insecure x.x.x.x
Connecting to x.x.x.x:443 ...
Connected to x.x.x.x:443
Negotiated protocol "h2"
Sending: []
...
h2i> headers
(as HTTP/1.1)> GET / HTTP/1.1
(as HTTP/1.1)>
Opening Stream-ID 1:
:authority =
:method = GET
:path = /
:scheme = https
[FrameHeader PUSH_PROMISE flags=END_HEADERS stream=1 len=35]
:method = "GET"
:scheme = "https"
:authority = ""
:path = "/4a3fc104-33c3-440e-bbf4-a282b1ad66ec"
然后手动访问 push 的页面,拿到第一小题的 flag。
接下来看第二小题,通过页脚的链接可以猜出,管理页面需要通过代理访问远端 localhost。但域名白名单和黑名单的存在,告诉了我们需要想办法绕过 ACL。
经过简单尝试,可以发现只要域名中包含 ustc.edu.cn
即可,后面加上其他字符也不会 403。于是给自己的域名添加了一条 A 记录,让 ustc.edu.cn.example.com
指向 127.0.0.1
。等待片刻,确认 DNS 记录生效后,使用上面得到的代理信息访问 ustc.edu.cn.example.com
,成功获得 flag。
结果官方解就只是用 IPv6 地址……
总结
总体做下来,感觉今年的题目相比以往,稍微少了些趣味性,多了些做题感。(也可能是我太菜了,Orz)
最终排名 43,也算是不错的名次了。相比我以往的成绩掉下来不少。
个人猜测可能是因为这个比赛更加有名气了,因此吸引了更多的大佬参与吧。
这也达成了主办方宣传和培养大家的信息安全知识的诉求,成功在中国信息安全史上画下了重重的一笔(
哈哈哈哈哈好有趣呀,我和朋友也一起玩过呢✌🏻
中科大的大佬嘛
并不是哦