Initial Reconnaissance
A quick nmap scan shows only ports 22 (SSH) and 80 (HTTP) open. Nothing interesting in service versions.
We run directory fuzzing and find some interesting paths:
_|. _ _ _ _ _ _|_ v0.4.3 (_||| _) (/_(_|| (_| ) Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 200 | Wordlist size: 11460 Output File: /home/kali/Desktop/HTB/TwoMillion/nmap/reports/http_2million.htb/__25-11-14_17-45-53.txt Target: http://2million.htb/ [17:45:53] Starting: [17:45:53] 301 - 162B - /js -> http://2million.htb/js/ [17:45:58] 200 - 2KB - /404 [17:46:06] 401 - 0B - /api [17:46:07] 401 - 0B - /api/v1 [17:46:07] 403 - 548B - /assets/ [17:46:07] 301 - 162B - /assets -> http://2million.htb/assets/ [17:46:11] 403 - 548B - /controllers/ [17:46:13] 301 - 162B - /css -> http://2million.htb/css/ [17:46:15] 301 - 162B - /fonts -> http://2million.htb/fonts/ [17:46:17] 302 - 0B - /home -> / [17:46:17] 301 - 162B - /images -> http://2million.htb/images/ [17:46:17] 403 - 548B - /images/ [17:46:19] 403 - 548B - /js/ [17:46:20] 200 - 4KB - /login [17:46:21] 302 - 0B - /logout -> / [17:46:29] 200 - 4KB - /register [17:46:37] 301 - 162B - /views -> http://2million.htb/views/ Task Completed
Trying to register asks for an invite code we don’t have.
However, there is a page where we can verify if a code is valid. The validation is done with the following JavaScript:
$(document).ready(function() {
$('#verifyForm').submit(function(e) {
e.preventDefault();
var code = $('#code').val();
var formData = { "code": code };
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
if (response[0] === 200 && response.success === 1 && response.data.message === "Invite code is valid!") {
localStorage.setItem('inviteCode', code);
window.location.href = '/register';
} else {
alert("Invalid invite code. Please try again.");
}
},
error: function(response) {
alert("An error occurred. Please try again.");
}
});
});
});
There is also an obfuscated file inviteapi.min.js:
eval(function(p, a, c, k, e, d) {
e = function(c) { return c.toString(36) };
if (!''.replace(/^/, String)) {
while (c--) { d[c.toString(a)] = k[c] || c.toString(a) }
k = [function(e) { return d[e] }];
e = function() { return '\\w+' };
c = 1
};
while (c--) {
if (k[c]) { p = p.replace(new RegExp('\\b' + e(c) + '\\b','g'), k[c]) }
}
return p
}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}', 24, 24, 'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'), 0, {}))
After deobfuscating it we get:
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 makeInviteCode function is exactly what we need – it tells us how to generate a real invite code.
Intrusion – Getting the Invite Code
We make a POST request to the endpoint it mentions:
curl -L -X POST "http://10.10.11.221/api/v1/invite/how/to/generate"
First response (ROT13 encrypted):
{
"0": 200,
"success": 1,
"data": {
"data": "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr",
"enctype": "ROT13"
},
"hint": "Data is encrypted ... We should probbably check the encryption type in order to decrypt it..."
}
Decode ROT13 → “In order to generate the invite code, make a POST request to /api/v1/invite/generate”
❯ 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
Second POST request returning Base64 code
We get a Base64 string ending with =:
❯ echo "QTBBQ0ktSkY4N1AtNTRNSE0tS1o1NFU=" | base64 -d A0ACI-JF87P-54MHM-KZ54U%
We paste the code, register with dummy credentials and we’re in!
Inside the Application
On hover over the VPN download button we see it calls a special endpoint.
Fuzzing /api/v1/user/ reveals the auth endpoint that returns user info, including is_admin.

We try to see if there is an endpoint for admin in the API making a request to/api/v1/admin and we see that it exists (no 404 error).
As we don't see too much information, we do a request to /api/v1/, where we discover all the endpoints available of the API:

In Burp we modify our user JSON to is_admin: 1:

Now we can generate VPN configs as admin!

RCE → Initial Shell
The VPN generation likely runs something like generate_vpn.sh $username. So we will try a command injection to see if it works:

We get RCE as www-data. We try to send a typical reverse shell but we can't (it might be blocked as we are executing commands as www-data.
Looking around /var/www/html we find .env with DB credentials (which are also valid for SSH as user admin).


Although we are logged in as an admin user, we see that we don't have sudo privileges.

But we obtain the user flag.
Privilege Escalation
linpeas.sh doesn’t show anything obvious. After a while searching and reading files and directories of the machine, I see that at /var/mail we find an interesting email:
From: ch4pTo: admin Cc: g0blin Subject: Urgent: Patch System OS Date: Tue, 1 June 2023 10:45:22 -0700 Message-ID: <9876543210@2million.htb> X-Mailer: ThunderMail Pro 5.2 Hey admin, I'm know you're working as fast as you can to do the DB migration. While we're partially down, can you also upgrade the OS on our web host? There have been a few serious Linux kernel CVEs already this year. That one in OverlayFS / FUSE looks nasty. We can't get popped by that. HTB Godfather
By searching at internet we see that it hints at CVE-2023-0386. We download the exploit from:
https://github.com/sxlmnwb/CVE-2023-0386
Now we just follow the steps of the exploit:

Root flag → machine pwned!
