Codegate Final 2019

FinalScoreboard

前言

這場初賽和Balsn一起打,雖然初賽題目和環境各種被罵爆,不過最後還是打進決賽了XD

決賽最後的結果蠻可惜的,前半場我們都維持在前3,後面可能大家都想睡覺就沒力惹


行程

比賽時間是在3/26 10:00 ~ 3/27 10:00,共24小時

我們搭3/25早上6點的飛機,抵達當地時間10:20

然後離開是3/29晚上7點的飛機

中間除了兩天比賽時間外,大概就是排了一堆各種韓國吃吃喝喝的行程


這次一樣行程和住宿也都我負責的

不過這次不像上次去羅馬尼亞一樣順利,預定行程大概只走了50% ~ 70%

去的時間有點小尷尬,不夠冷,已經沒下雪惹,但也不夠暖,櫻花都還沒開

簡略行程:

  • 3/25
    • 明洞
    • 首爾塔
  • 3/26
    • 比賽
  • 3/27
    • 比賽
    • 弘大
  • 3/28
    • 景福宮
    • 北村
  • 3/29
    • 廣藏市場
    • 樂天超市

換錢

有聽聞在韓國當地換錢匯率爆幹好

所以出發時,我只在台灣換了10000韓元(約300台幣),夠搭車就行

然後是在大家都很推的明洞大使館旁邊的換錢所換的

用台幣或美金換,匯率都還不錯


交通

韓國首爾基本上到處都有地鐵,有點類似台灣的捷運

我們在出發前,先買了T-money卡(類似台灣的悠遊卡),在那邊只要先儲值,就能爽搭各種交通工具

另外,從仁川機場到首爾站,可以搭AREX直通車,速度蠻快的,不過要注意班次,間隔時間蠻長的,櫃台也有點難找

AREX車票除了這個外,還有一個類似收據的東西

那個很重要,那上面會寫你的座位在哪

我們第一次搭時不知道,就一個坐錯位置


行李寄放

我覺得韓國最難搞的大概就是行李寄放問題

有時候沒辦法提早check-in,就要拖著行李跑行程

通常大的地鐵站都有行李寄放櫃

用法很簡單,但很多要注意的地方

基本上就先選你要的位置,然後付款

接著他就會解鎖給你放

但因為這是一次性的置物櫃

如果你不小心關上,那就掰掰,只能重新付款 (我們發生不少次這種悲劇…

這個是幫我們解決置物櫃關不起來的工作人員

沒錯,這個櫃子超容易出Bug


住宿

3/25 ~ 3/26

比賽官方提供我們25, 26兩天的住宿,住在一個不太高級的飯店

不過就在會場旁邊而已,還算挺方便的

附近雖然晚上有點荒涼,只剩按摩店和金拱門,不過認真找還是有一些賣吃的小店 (小巷內有炸雞店,讚)


27, 28兩天則是訂Airbnb,分別住弘大和北村那附近

3/27

27號住的那間,就在弘大地鐵正上方,交通超方便,走路就能到弘大商圈

不過房東有點小雷,他好像忘記清前個房客的東西,所以下午自己跑進來清冰箱

然後就把我們的東西順便清走惹,慘XDD

3/28

28號住的則是,景福宮旁邊的一間傳統韓屋(我超想體驗看看傳統韓屋XD)

這間價錢算便宜,CP值很高

但缺點就是隔音很差,講話一直被抗議 (但隔壁妹子一直吵都沒被抗議,黑人問號)

不過房東人很好,宵夜還幫我們訂橋村炸雞

原本Uber eat訂不到,想說要直接衝去東大門買,還好有在地人幫忙叫XD


景點

首爾塔

上首爾塔要先搭這個斜的電梯

然後再搭纜車才會到最上面的塔底

上面晚上可能因為在高山上,所以很冷

愛情的枷鎖

出現了!首爾塔

上面會標註各大城市的方位和距離,台北也在上面XD

首爾塔夜景,很猛

可是反光很嚴重,超難拍XD

明洞聖堂

據說是韓國最早的哥德式教堂

窗戶很有感覺

裡面大家都很安靜

給人一種神聖的感覺XD

我們在裡面坐了一下,有種被淨化的感覺

Line Friends Store

就是一個很多人會和熊大一起拍照的地方

Retro Game Bar

弘大最有名的就是夜生活

所以我就找了一家適合肥宅的game bar來體驗一下XD

只要付一杯酒的錢,就能爽玩店裡頭的各種機台、PS4、Switch、…等

我們玩到關門才被老闆趕走XDD

不過老闆人很好,一開始忘記收錢,我們最後要付錢給他時,直接給我們超優惠折扣

特調Menu

上面的酒名幾乎都是遊戲角色名字XD

居然還有快打旋風機台

最後我們這天半夜5點才睡

隔天睡過頭,行程直接砍一半XDD

景福宮

古代的宮殿

裡頭其實蠻大的,很難全部走完

大家都會租韓服來這邊拍照

樂天超市

其實也不算景點,就是一個買各種伴手禮或零食的好地方XD

首爾站旁邊的樂天超市

我買了一堆零食帶回台灣吃


吃吃

對於資工肥宅來說,韓國比較有吸引力的地方,大概就是到處都是滿滿的美食惹

王妃家

來韓國的第一餐

燒肉太貴惹,只能點這種已經烤好的XD

可是店員很雷,我們點的東西整個忘記上

明洞小吃

其實就是洋芋片

只是口感比較特別一點

不知名炸雞

云計算啤酒(?

土俗村蔘雞湯

雞裡面包飯

每個人都有一小罐山蔘

好像要加進去吃的(?

不過我把他當紀念品帶走惹

他們也有賣人蔘酒,我後來就買了一罐帶回台灣

豬骨湯+血腸

血腸味道有點獨特,裡面好像是包內臟之類的東西(?

Booyas烤腸

這家店面超隱密,很難找

感覺是在地人才比較知道的一家店

橋村炸雞

蜂蜜口味,好吃!

不愧是傳說中的炸雞,真的讚讚

不知名的燒肉

滿滿的肉

生肉

生牛肉

活章魚

章魚超黏,在口中會微微蠕動XD

還不錯吃,可是很多人不敢吃

廣藏市場裡面超多家活章魚,我們就挑了這家似乎評價不錯的來吃

三清洞摩西年糕鍋

裡面加了起司、方便麵、火腿、海鮮、雞蛋

Isaac

聽說是韓國美而美

甜點

住宿旁邊的小咖啡廳買的巧克力蛋糕

很厲害的香蕉巧克力鬆餅,但被壓扁惹

爆幹好吃的Fuhaha爆漿奶油麵包

韓國除了燒酒必喝之外,另一個必喝的就是馬格利

馬格利其實就是韓國米酒,酒味沒燒酒那麼強烈,很像汽水XD

soju

Hacking for soju!

長壽馬格利

還不錯喝,有一點碳酸氣泡感

這個也是馬格利,但味道沒上面那個那麽適合大眾XD

妹酒



比賽

codegate

在出發前,就有聽老前輩說Codegate的Web題都很水

所以看到比賽前半場,只出了一題Web: coingate,雖然沒到非常簡單,但整體沒有感到很意外

(聽說中途好像因為資料庫沒擋外連之類的,直接關掉題目維修…跟初賽差不多雷)


但後半場半夜主辦方不知道吃錯啥藥,一口氣丟了無數題Web,而且難度都不低

原本預期可能只有一題Web的我,幾乎沒保留繼續解題的體力,整個被虐爆QQ

果然應該趁沒題目時,趕快去睡覺的

結果Codegate根本Web場……

只有一個會打Web的,有點吃力XDD


下面補一下前半場這題Web的Writeup:

Coingate

題目點開來是一個加密貨幣交易網站

基本功能:

  • 註冊
  • 登入
  • 大頭貼

快速翻了一下,可以容易發現到,在參數/?p=profile的部分可以LFI

/?p=profile引用的是profile.php

所以可以用php://filter/convert.base64-encode/resource=去讀Source code

以下列出幾個比較重要的php file

admin.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
#from Crypto.Cipher import AES
#from Crypto import Random
#import base64

#block_size = 16
#pad = lambda s: s + (block_size - len(s) % block_size) * chr(block_size - len(s) % block_size)
#print 'what you need is just to use python aes'

#plain = "flag"
#plain = pad(plain)

#iv = Random.new().read(AES.block_size)
#key = 'coingate'

#cipher = AES.new(key,AES.MODE_CBC, iv)
#encrypted_text = base64.encodestring(cipher.encrypt(iv+plain))

#print encrypted_text
?>

config.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
//	error_reporting(0);
    $host = "coin_db_1";
    $user = "coin";
    $db_schema = "coin";
	$port = 3306;
    $mysql = new mysqli($host, $user, "", $db_schema,$port);
    $mysql->query("SET NAMES utf8");


?>

lib.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
	ini_set('phar.readonly',0);
    class Requests{
        public $url;

        function __construct($url){
            $this->url = $url;
        }
        function __destruct(){
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $this->url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $output = curl_exec($ch);
            echo '<div class="description">'.$output.'</div>';
        }
    }
?>

uploadThumb.php:

 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
<?php
    if($_SESSION['is_login'] !==1 ) die("<script>alert('Login please.');history.back();</script>");
    chdir('uploads');
    $allowExt = Array('jpg','jpeg','png','gif');
    $fname = $_FILES['thumb']['name'];
    $fname = array_pop(explode('./',$fname));
    if(file_exists(urldecode($fname))){
        
        echo "<script>alert('Already uploaded file.\\nPlease change filename.');history.back();</script>";
    }else{
        $ext = strtolower(array_pop(explode('.',$fname)));
        if($_FILES['thumb']['error'] !== 0){
            die("<script>alert('Upload Error!');history.back();</script>");
        }
        if(!in_array($ext, $allowExt)){
            die("<script>alert('Sorry, not allow extension.');history.back();</script>");
        }

        $contents = file_get_contents($_FILES['thumb']['tmp_name']);
        if($ext=="jpg"){
            if(substr($contents,0,4)!="\xFF\xD8\xFF") die("<script>alert('JPG is corrupted.\\nSorry.');history.back();</script>");
        }else if($ext=="jpeg"){
            if(substr($contents,0,4)!="\xFF\xD8\xFF") die("<script>alert('JPEG is corrupted.\\nSorry.');history.back();</script>");
	}else if($ext=="png"){
            if(substr($contents,0,4)!="\x89PNG") die("<script>alert('PNG is corrupted.\\nSorry.');history.back();</script>");
        }else if($ext=="gif"){
            if(substr($contents,0,4)!="GIF8") die("<script>alert('GIF is corrupted.\\nSorry.');history.back();</script>");
        }else{
            die("<script>alert('Something error.\\nSorry.');history.back();</script>");
        }
	@move_uploaded_file($_FILES['thumb']['tmp_name'], $fname);
	$id = $mysql->real_escape_string($_SESSION['id']);
	$sql = "UPDATE users SET thumb='".$mysql->real_escape_string($fname)."' WHERE id='".$id."'";
	$result = $mysql->query($sql);
	if($result===TRUE){
            $_SESSION['avatar'] = $fname;
            echo("<script>alert('Successfully Avatar Change!');history.back();</script>");    
        }else{
            echo("<script>alert('Upload failed!');history.back();</script>");    
        }
    }
?>

看完以上幾個檔案後,其實大概可以猜得到題目想考啥

就是Phar反序列化,串SSRF偽造MySQL query去撈FLAG

Payload在這: https://github.com/w181496/CTF/blob/master/codegate2019-final/coingate/payload.gif

最後撈出來是: key{J3qBeP1N9q2w0Pja7kh7Mkh51F6dVdzUcW3eIV4pQwCDgiQqx4N9HnJWBKvF1nGBFoza+AZmW0q9/WKLYeSrVw==}

很明顯就是要我們用admin.php撈出來的key去解AES

(這裡卡了一小段時間,因為舊版code給的key是錯的= =)

成功解出來,就是FLAG惹: FLAG{Lif2_i5_n0t_alw4ys_what_One_lIke}

p.s. 賽後看twitter,有人說這題還是抄襲某場CTF,科科


OAO

一開始走進會場,看到這場景,差點以為走錯場惹

韓國文化跟台灣果然不太一樣(?


codegate2

韓國版金盾