我很少打CTF比赛,这次比赛又加深了我对CTF比赛的不明所以的印象。可能是题目的问题吧。
有一些题目,我感觉就是很无厘头,没有逻辑,如果你和出题人想到一块去了,那你就做出来了,不然你就只能看运气了。(我认为一道好的CTF题不是这样的)在我看来最有意义的CTF题目是你可以从中学到新知识,而不是简单地重复尝试你知道的。
Misc
这道题目在我看来就是缝合怪,只要叠够多层,这道题就变得有意思了(?)
观察pcap包发现有很多DNS请求,看字符集像是base64编码的。用wireshark提取域名再decode之后得到一个压缩包,解压得到一个png文件。
这道题被ChatGPT坑了,ChatGPT使用了([\w\.]+)
来匹配Base64,然而Base64还有其他字符。
(此处细节用Windows打开)
首先发现文件的最后有很多无用的字节。计算它们的长度为10000,猜想是100x100的隐藏图片,转换后得到一个二维码,扫描之后得到m/wchhlbt/THUCTF2021
。
还没有完全得到flag,用pngcheck
检查这个文件,发现CRC error in chunk IHDR。一般来说这种情况是图片宽度/高度被修改了导致的,于是写一个脚本爆破正确的参数:
1 | import binascii |
打开之后得到:
是一个GitHub仓库,从记录里一个字符一个字符地提取flag即可。
最后一步我都找到仓库了,非要恶心人一个字符一个字符地找。没有难度,只有浪费时间。
Web_1
这道题还挺有意思,学到新知识就是好题。
这道题给出了code.php
的源码。观察源码发现只要使得代码运行到这一行即可获得 flag:
1 | case 'login': |
仔细观察,发现首先要满足这个条件:
1 | if ($_GET['action'] == 'login' && $_POST['cb_user'] == 'admin' && $_SERVER['REMOTE_ADDR'] != '127.0.0.1') |
我最开始尝试的方向是修改HTTP请求头来假冒$_SERVER['REMOTE_ADDR']
,例如X-Forwarded-For
,但是都失败了。后来发现它是直接读取的IP包的来源。
陷入困难,发现提示是GET, POST, REQUEST
,搜索后发现REQUEST是GET和POST的集合,对于上面的if
语句,只要$_GET['action']
设置了但不是login
即可。$_REQUEST['action']
可以在POST里设置。这样请求可以得到try harder
。
然后观察有关md5的条件,要求cb_pass
不含a
,且md5(cb_pass) == md5(cb_salt + 'a')
。这很难找到,但是php的==
是弱比较(万恶之源),如果两个的md5值形如0exx...x
其中x
为数字,将科学计数法转换为数字后即可得到0==0
。这样的例子是比较好找的。我们已经有一个著名的例子:md5(QNKCDZO)=0e830400451993494058024219903391
,接下来再写程序找到另一个以a
结尾的字符串满足要求即可。最终代码:
1 | import requests |
Web_2
根据提示,我们的目标是读取flag1所在的文件,即lib/flag.php
,我们可以利用的操作是save_item
(保存任意文件到主机)和list_item
。save-item
中会调用SQL语句,观察是否可能SQL注入。发现name有比较严格的审查,而uuid一项实际上仅限制了\S
,这允许任意非空白字符输入,所以可以从这里注入。于是我们将filename覆盖为lib/flag.php
。保存后再执行list_item
即可得到文件内容,文件中同时还有flag3的提示:
1 | <?php\nnamespace lib;\n\nclass Flag {\n const FLAG1 = 'flag{simple_php_bypass_f4ffbe58}';\n const FLAG2 = 'flag{simple_sql_injection_41278e35}';\n // FLAG3 is in /flag3, call /readflag3 to read it;\n}\n |
1 | import requests |
Pwn_1
发现可以telnet连接到实例的端口,请求用户输入,估计是栈溢出。
直接用IDA反编译:
那么目标就很明确了,劫持返回地址到back
即可。返回地址是rbp+8
,因此得到代码:
1 | from pwnlib.util.packing import p64 |
pwnlib
ban 掉了import *
,我的评价是装什么孙子。实用是第一位的。