This post documents the complete walkthrough of Book, a retired vulnerable VM created by MrR3boot, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post


Book is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 --rate=500

Starting masscan 1.0.5 ( at 2020-02-23 02:12:57 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on
Discovered open port 22/tcp on

Nothing extraordinary. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p22,80 -A --reason -oN nmap.txt
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 f7:fc:57:99:f6:82:e0:03:d6:03:bc:09:43:01:55:b7 (RSA)
|   256 a3:e5:d1:74:c4:8a:e8:c8:52:c7:17:83:4a:54:31:bd (ECDSA)
|_  256 e3:62:68:72:e2:c0:ae:46:67:3d:cb:46:bf:69:b9:6a (ED25519)
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
|   /:
|_      httponly flag not set
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: LIBRARY - Read | Learn | Have Fun

This is a shit show man. This is how the site looks like.

Directory/File Enumeration

Before we begin fuzzing, note that we can sign up a new account and log in to the site.

Also, notice that there’s a validateForm() JavaScript.

Basically it does nothing other than telling us the length requirement of 10 and 20 characters respectively for the name and email fields. :wink:

This is what the site looks like after logging in.

The purpose of that is to get a valid session cookie for fuzzing.

gobuster dir -w dirbuster.txt -c "PHPSESSID=2abemapmnqa92r1nq42261m2fq" -t 20 -e -x php,pdf,txt -u                                                                                                                                                                           
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:  
[+] Threads:        20
[+] Wordlist:       dirbuster.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] Cookies:        PHPSESSID=2abemapmnqa92r1nq42261m2fq
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     pdf,txt,php
[+] Expanded:       true
[+] Timeout:        10s
2020/02/23 04:32:17 Starting gobuster
=============================================================== (Status: 301) (Status: 200) (Status: 200) (Status: 200) (Status: 200) (Status: 302) (Status: 200) (Status: 301) (Status: 200) (Status: 200) (Status: 301) (Status: 200) (Status: 302) (Status: 302) (Status: 302)
Progress: 34144 / 81630 (41.83%)^C
[!] Keyboard interrupt detected, terminating.
2020/02/23 04:48:57 Finished

On top of that, casual browsing of the site reveals the email address of admin.

Bypass admin’s Authentication with SQL Truncation Attack

Notice from above that there’s an /admin present? This is how it looks like.

If I had to guess, I would say that the backend uses prepared statement from MySQLi extension for authentication. Why? Because I can tease out existing username check with the admin’s email address alone.

Which means that somewhere in index.php there’s code that does something like this:

$stmt=$conn->prepare("select email from users where email=?");
$result = $stmt->get_result();
if($num_rows > 0)
	echo '<script>alert("User Exits!");window.location="/index.php";</script>';
	$stmt=$conn->prepare("insert into users values(?,?,?)");
	header('location: index.php');

Armed with this hypothesis, let’s see if we can bypass admin’s authentication with a SQL truncation attack like this.

We noted that name has a 10 characters limit while email has a 20 characters limit. In Burp’s Repeater above, we input the name field with admin_____1 (11 characters, and where _ represents the space character) and the email field with [email protected]_______1 (21 characters, and where _ represents the space character).

This will allow us to bypass the existing username check based on email because there’s no username [email protected]______1. Right after this, the INSERT query strips the 1 because of the 20 character limit and truncates the rest of the whitespaces and insert [email protected] into the database with my chosen password (letmein). After some testing, the character limit of the name field was found to be 11 instead of 10. Sneaky!

Now let’s see if we can log in to /admin.

I notice that I can’t log in if I try it again at /admin/index.php. I have to re-insert the entry and then it works. Looks like some kind of database job is scheduled to aggressively remove the inserted entry from the database.

SSRF attack

Here’s where I found something interesting at the Collections page.

Opening the Collections PDF reveals something that looks like a generated PDF from a HTML.

With that in mind, let’s upload something with a litte something extra peppered in the Title and the Author columns like so.

xhr = new XMLHttpRequest();
xhr.onload = function() {
};"GET", "file:///etc/passwd");

The PDF doesn’t even have to be a real PDF :smiling_imp: Now, let’s see what do we get.

Low-Privilege Shell

Maybe we can grab hold of reader’s RSA private key with this little nugget?

xhr = new XMLHttpRequest();
xhr.onload = function() {
};"GET", "file:///home/reader/.ssh/id_rsa");

Awesome. After some massaging of the text, we should be able to log in as reader.

The file user.txt is at reader’s home directory.

Privilege Escalation

During enumeration of reader’s account, I notice that logrotate executes every 5 seconds.

I also notice the presence of a backups directory containing two log files.

Putting two and two together, I figure we might have a logrotten exploit at hand.

logrotate <= 3.15.1 - Privilege Escalation

If I had to guess, I would say that that /root/log.cfg may well look something like this.

/home/reader/backups/access.log {
        rotate 12
        size 1k

Let’s go ahead and put in a RSA public key I control into /root/.ssh/authorized_keys as the payload.

Bombs away.

We should get our root shell right after we re-login to reader’s account.

Bam. Getting root.txt should be a piece of cake now.