本次2023 Zer0ptsCTF 是我们SU第一次正式组织参加国际赛,获得全球榜第二十七名,大陆第三名,我们会继续努力,感谢队里师傅们的辛苦付出!同时我们也在持续招人,只要你拥有一颗热爱 CTF 的心,都可以加入我们!欢迎发送个人简介至:suers_xctf@126.com或直接联系书鱼(QQ:381382770)
以下是我们 SU 本次 Zer0ptsCTF writeup
web
Warmuprofile
POST /user/:username/delete 路由存在条件竞争
1 | app.post('/user/:username/delete', needAuth, async (req, res) => { |
假设有两个请求,第一个请求正常执行,但其中await User.findOne会有一定的操作延迟
在这个时间内,第二个请求跟进来,sesssion.username 跟 req.param.username 还是一样的,执行到await User.findOne的时候,用户已经被删除了,User.findOne 会返回为 null ,然后 执行 await User.destroy 操作会把所有用户都删了
知晓原理后用BurpSuite扩展 Turbo Intruder 进行条件竞争,删除所有用户
再利用 POST /register 路由创建admin用户,就可以获得flag
参考链接:
https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/
https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html
jqi
jq_wiki: https://github.com/jqlang/jq/wiki
nodejs调用jq二进制文件解析json,命令注入或者jq的表达式可以利用?
jq官方手册: https://jqlang.github.io/jq/manual/#Formatstringsandescaping$ENV可以表示环境变量,有字符串插值\(exp)但是\(被过滤了
官方文档还提供base64之类的东西,也许有用吧【 不用管了
利用 \ 逃逸,然后通过data.json中各个字段凑齐base64的标准码表,利用if来盲注环境变量里面的flag即可。
由于双引号被过滤了,单引号又不能表示字符,因此在data.json中把base64中包含的字符串集齐
1 | def getstr1(): |
最终得出
1 | list1 = { |
然后利用halt_error来导致不一样的回显,配合if语句进行盲注即可
1 | import string |
Pwn
qjail
看qiling源码
在loader/elf.py里是他的elf加载逻辑
根据经验AT_RANDOM的值指向canary
往上翻可以看见randstraddr的来源
能够猜+试出来canary是7个a和一个00
elf加载地址在qiling的profile里的linux profile
1 | [OS64] |
1 | from pwn import * |
但是目前发现pop rdi会报unicorn的READ error
不太确定libc是不是固定的,还没从源码中看到这部分
重回main好像也不太得劲
qiling启动的是ld,所以之前看到的pie地址其实是ld的,elf在profile里的mmap地址范围
这个结论是通过qiling的gdb server调试观察到的,https://docs.qiling.io/en/latest/debugger/
然后就是直接看出其他段的地址,应该都是不变的
重新算地址,pop rdi,重回main都没问题了
system会有点问题,所以最后orw读flag.txt
1 | from pwn import * |
Brainjit
Brainfxxk jit编译器
有个功能是为多个连续的操作独立生成汇编,length标识重复长度
但是这个32位的length如果最高byte是有符号似乎就会有点问题
不知道是不是漏洞
但是经过调试后面这种方法py里check了write写入不能超过0x8000,emmm
貌似只有 ‘ [ ‘ 没有 ‘ ] ‘的时候会出现点问题,可以跳到jz指令的0xff上,跟后面的指令偏一下,但我找了半天没有找到可以合理的偏出想要的指令的
就是上面这种方法做的
但是偏起来挺jb的。。
1 | from pwn import * |
Re
decompile me
hook了比较函数,把数据替换了,rc4解一下即可
mimikyu
程序的核心是用到了libc.so.6、libgmp.so两个so库中的导出函数,寻找指定函数是用到了ResolveModuleFunction函数,其中是采用hash校验的方式获取指定函数。
1 | __int64 ResolveModuleFunction(void *a1, int a2, ...) |
首先是定义三个大数空间与初始化随机数种子
接着判断是输入字符是否是可打印字符
然后是获取随机数,并以此使用__gmpz的计算函数计算出两个结果v14,v13,最后将输入以4字节为一组与得到的两个结果进行pow运算,最后与指定密文对比。
因为是指定的确切的随机数种子,所以这个随机数每次都是确定的,另外程序中计算v14、v13的过程稍显复杂,直接调试得到10组v14与v13
得到密文与10组v14与v13后便考虑开始解密了,其实程序用到的就是10组rsa加密,但这个n都不能直接分解为2个质数,
因此考虑爆破。
1 |
|
但是这样很慢,但可以将其拆成10组分别爆破,相当于手动实现了多进程爆破。
比如爆破第二组:
1 |
|
结果:
最后所有结果拼接起来
zer0pts{L00k_th3_1nt3rn4l_0f_l1br4r13s!}
Cry
easy_factoring
关系是N = pow(p, 2) + pow(q, 2),类似CCTF2022的SOT。
1 | from Crypto.Util.number import * |
Misc
NetFS 1
guest/guest账户可看/home/ctf/public/welcome.txt但
File: /home/ctf/secret/flag.txt
Permission denied.
用侧信道脚本
1 | import socket |
NetFS2
增加了延时,不能手动挡了,也是侧信道
1 | import socket |