Advertisement

Main Ad

Previous HTB Walkthrough

Welcome to another Hack the Box walkthrough. In this blog post, I have demonstrated how I owned the Previous machine on Hack the Box. Hack The Box is a cybersecurity platform that helps you bridge knowledge gaps and prepares you for cyber security jobs.

You can also test and grow your penetration testing skills, from gathering information to reporting. If you are new to this blog, please do not forget to like, comment and subscribe to my YouTube channel and follow me on LinkedIn for more updates.


About the Machine

Previous is a medium Linux machine on Hack the Box built around a Next.js web app and a misconfigured Terraform sudo rule. The machine featured a web enumeration (virtual hosts + JS inspection), an exploitation of a Next.js middleware bypass (CVE-2025-29927), arbitrary file read in the app/container, discovery of a hard-coded credential in a compiled NextAuth route, and a clean privilege escalation via terraform -chdir=/opt/examples apply allowed by sudo and abused by supplying a malicious local provider. The vulnerability can be exploited by adding X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware to the HTTP header.

previous htb writeups


Remediation & defensive notes

  1. Patch Next.js / middleware — apply vendor patches and specifically address CVE-2025-29927 by ensuring middleware correctly differentiates internal subrequests from external requests and does not trust unvalidated x-middleware-subrequest style headers.
  2. Do not accept untrusted internal headers — do not base authentication decisions on headers that clients can supply; validate the origin and canonicalize request classification.
  3. Avoid leaking secrets in deployed JS bundles or server build artifacts. Never embed fallback secrets in compiled code. Use environment secrets and vaults, and rotate secrets.
  4. Harden file-serving endpoints — validate and canonicalize path parameters, enforce allow-lists, and avoid exposing arbitrary file reads.
  5. Scope sudo rules narrowly — never allow terraform apply or similar infrastructure tooling to run as root in directories writable by unprivileged users. If such tooling must be used, wrap it in a safe, audited wrapper that validates inputs and prevents arbitrary provider execution.
  6. Protect Terraform CLI config & provider install paths — ensure unprivileged users cannot control TF_CLI_CONFIG_FILE or provider installation directories used by privileged runs.


The first step in owning the Previous machine like I have always done in my previous writeups is to connect my Kali Linux terminal with Hack the Box server. To establish this connection, I ran the following command in the terminal:

Previous Hack the Box Writeup

Once the connection between my Kali Linux terminal and Hack the Box server has been established, I started the Previous machine and I was assigned an IP address (10.10.11.83)

Previous Hack the Box Machine Writeup

After been assigned 10.10.11.83, I decided to map the target machine IP address to the domain name. This way, I could access the service by name instead of by IP address by running:

Previous Hack the Box Walkthrough

I added 10.10.11.83 previous.htb to the /etc/hosts file and performed reconnaissance using Nmap to find all the open port and services associated with the target machine. Using the following command, I found all the services and port running at 10.10.11.83:

Previous Hack the Box Machine Walkthrough

Nmap came back fast: the host previous.htb (10.10.11.83) is up and only two TCP ports were open.

  1. Port 22 - OpenSSH 8.9p1 (Ubuntu): The machine is running OpenSSH. The scan revealed host key fingerprints (ECDSA and ED25519). That’s useful for later verification if we come back via SSH or capture keys. An open SSH service is a potential foothold (credential reuse, weak passwords, private keys, misconfigured accounts), so I noted this for credential-based checks later.
  2. Port 80 - nginx 1.18.0 (Ubuntu): The webserver is nginx and the HTTP title advertises PreviousJS. Because the box uses a hostname (previous.htb) and I already added it to /etc/hosts, the service is likely serving virtual‑host specific content - a perfect place to look for web logic, JavaScript, and hidden endpoints. nginx 1.18 is a common Ubuntu package; nothing immediately screams a critical remote exploit, but web apps often leak sensitive files, credentials in JS, or admin interfaces.

Why this matters for the walkthrough

Two easy attack surfaces: a web app (HTTP) and a remote admin interface (SSH). My priority is the web app because it often yields low‑privilege file reads, credentials in JavaScript, or unauthenticated endpoints that let me pivot to SSH access later.


Web Enumeration — Directory & File Discovery

After confirming that previous.htb hosts a web application, I ran a directory brute-force using dirsearch to uncover hidden endpoints and resources:

Previous HTB Writeup

Previous HTB Walkthrough

This scan probed common directories and file types (php, aspx, jsp, html, js) with 25 concurrent threads. The goal was to identify any accessible or overlooked paths that might expose sensitive information, documentation, or potential attack surfaces.

Key Findings

The scan returned a large number of redirects (307 and 308) pointing mostly to an authentication page:

  1. API endpoints: Almost every API-related path such as /api, /api-docs, /api/v1/swagger.json, /api/2/issue/createmeta redirected to /api/auth/signin. This indicated that the API is protected behind authentication, but it also confirmed that the application exposes a RESTful interface with structured endpoints that could be explored further once credentials or tokens are discovered.
  2. Documentation pages: Paths like /docs/html/admin/index.html, /docs/html/developer/ch02.html, /docs/CHANGELOG.html, and /docs/swagger.json were all present but redirected to authentication. These pages suggest the presence of internal documentation and API references — potential goldmines for discovering endpoint structures, version info, or hidden functionality.
  3. Other resources: A few static files such as /engine/classes/swfupload/swfupload.swf and /extjs/resources/charts.swf were directly accessible (308 redirects normalized the paths). While not immediately exploitable, these indicate legacy client-side technologies that sometimes leak clues or credentials.
  4. Login page: The only fully reachable page was /signin (HTTP 200), which appears to be the main authentication portal. This became the natural pivot point for further enumeration, such as testing for exposed credentials, login bypasses, or client-side JavaScript logic.


Recon — landing on PreviousJS

I pointed my browser at the machine (HTTP 10.10.11.83) and it served a site that redirected me to https://previous.htb.

Previous HTB Machine Walkthrough

The homepage (PreviousJS) looks like a marketing splash for an old/nostalgic JS framework - the banner even says “the technology of yesterday.” with two buttons visible: Get Started and Docs

Clicking Get Started sent me to a login page - the same page I found by visiting http://previous.htb/signin. The login form looks standard: username, password, and a Sign in button.

Previous HTB Machine Writeup


Hunting for an authentication bypass — an interesting lead

The signin page had no registration flow, so I switched gears from credential hunting to vulnerability research. While searching for exploits related to PreviousJS and common vulnerabilities exploit for JavaScript, I came across CVE‑2025‑29927, an authentication bypass affecting Next.js middleware which can allow a remote attacker to bypass security checks.

Previous Hack the Box Writeup

You can read more about the vulnerability here.

Previous Hack the Box Machine Writeup

What I discovered

The CVE-2025-29927 described a weakness in how Next.js middleware classifies requests. By manipulating a special request header used for internal/middleware subrequests, an attacker can trick the middleware into treating an external request as an internal one, effectively skipping the normal authentication checks by adding x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware to the HTTP header.


Testing the middleware bypass — dirsearch with the exploit header

With the CVE‑2025‑29927 lead in hand I re‑ran directory discovery against the /api surface while injecting the suspicious middleware header to see if protected endpoints would behave differently:

Previous Hack the Box Walkthrough

This injection changed the app’s responses in interesting ways - it didn’t give me an immediate “open sesame,” but the probe revealed that the backend was now returning real responses (and different status codes) for locations that previously redirected to the login. That made this a high‑value investigative path.

What the results showed (and why it matters)

  • Many auth‑related endpoints returned 400 instead of a redirect: Paths such as /api/auth/login, /api/auth/admin, /api/auth/logon returned 400 Bad Request. Previously these redirected to /api/auth/signin / /signin. A 400 suggests the server is now processing the request internally (and rejecting it as malformed) rather than immediately redirecting to the global auth flow. That’s a strong sign the middleware behavior changed under the spoofed header — it’s processing subrequest logic rather than performing the normal redirect.
  • /api/auth/signin returned 302 to a localhost callback: The /api/auth/signin probe produced:

That’s interesting: the application attempted to include a callbackUrl pointing to http://localhost:3000. Callback parameters like this are often used by auth frameworks and, if not validated, can be abused for open redirect or SSRF-style flows. It also demonstrates the app is constructing auth redirects differently when it treats the request as internal/subrequest.

  1. Some paths were normalized (308) rather than fully blocked: dirsearch showed normalized redirects for odd paths (e.g. /api/%2e%2e//google.com -> /api/%2E%2E/google.com) and a number of legacy resource paths (Axis, Citrix, swfupload, extjs swfs) under /api/…. Those resources being reachable (or at least not simply redirected to login) means the header may be allowing access to static assets and legacy endpoints that were previously gated.
  2. A /api/download probe returned 400: The 400 here again suggests the endpoint is being reached and parsed — it just refuses the current request. That implies there may be ways to craft a valid request (or parameter) to retrieve files if the bypass can be refined.


Fuzzing the file-download parameter — ffuf against /api/download

I fuzzed the download endpoint (which previously returned 400 when hit normally) to discover valid query parameters that might trigger different behavior or return files. Because the middleware bypass header looked promising, I included it in every request:

Previous Hack the Box Machine Walkthrough

The scan returned example. This indicates the example parameter produced a 404 Not Found response with a tiny body. Because I used -fw 2 (filter responses with 2 words i.e., ignore trivial responses), ffuf reported only responses whose bodies had more than 2 words; the result shows example had 3 words so it passed the filter.


Verifying the /api/download probe

I reproduced the request that ffuf flagged (the example parameter) with a verbose curl so we could see the full HTTP exchange:

Previous HTB Writeup

What happened

  1. The client resolved previous.htb to 10.10.11.83 and successfully opened a TCP connection to port 80.
  2. The request included the special X-Middleware-Subrequest header (the CVE trigger) so the server treated it differently than normal external requests.
  3. The server returned an HTTP 404 Not Found with a JSON body: {"error":"File not found"}.


Retrieving /etc/passwd — confirmed directory-traversal via the download endpoint

I tested the download endpoint with the same middleware-spoof header and a classic path-traversal payload:

Previous HTB Walkthrough

Instead of a JSON error, the server returned the contents of /etc/passwd. The download handler is vulnerable to path traversal and will return arbitrary file contents when supplied the right parameter + filename under the middleware bypass.

By sending the x-middleware-subrequest header (CVE-2025-29927 trigger) and exploiting a path-traversal vector in /api/download, I was able to read arbitrary files, found two users (node and nextjs), demonstrated by successfully retrieving /etc/passwd. This confirms an authenticated-gate bypass combined with an arbitrary file read primitive.


Reading the process environment — /proc/self/environ shows runtime context

I used the same path-traversal trick against the /api/download handler (with the middleware bypass header) to read the process environment:

Previous HTB Machine Walkthrough

The server returned the target process’s environment variables:


Why this matters

Reading /proc/self/environ proves two things at once:

  1. The file-read primitive is working for process internals, and
  2. It reveals runtime configuration that can point straight to useful next steps (process user, working directory, app port, runtime version, and environment mode).


Mapping the app — what the routes-manifest.json leak tells us

Pulling /app/.next/routes-manifest.json was a great payoff: it confirmed this is a Next.js app and gave me a clear map of the server’s routing surface, basically a shopping list of endpoints to target next by running:

Previous HTB Machine Writeup

Previous Hack the Box HTB Machine Walkthrough Writeup

Dumping /app/.next/routes-manifest.json confirmed this is a Next.js application and gave me the app’s route map. Notably, there’s a NextAuth catch-all handler at /api/auth/[...nextauth], a set of docs routes (/docs, /docs/content/getting-started, etc.), and explicit static routes including /signin. With the middleware exploit already allowing internal handlers to be reached, the manifest turned the engagement from “guesswork” into a concrete plan: fetch the docs for API shapes, probe the NextAuth endpoints for callback/open-redirect or provider misconfigurations, and pull build artifacts (.next chunks and config files) to hunt for secrets or credentials. This manifest was the roadmap that guided my next focused, read-only probes.


Finding the server-side auth logic — smoking gun in the Next.js build

I pulled the compiled Next.js API route for api/auth/[...nextauth] from the server build and it revealed the app’s actual authentication configuration, not just hints anymore but the real server-side logic by running:

previous htb complete walkthrough

I downloaded the compiled NextAuth route from the server build and found the credentials provider’s authorize function in cleartext. It authenticates only the user jeremy with hard-coded password MyNameIsJeremyAndILovePancakes.


Gaining a shell — SSH to jeremy@10.10.11.83

With the credentials discovered earlier I attempted an SSH login:

previous htb complete writeup

After supplying the password MyNameIsJeremyAndILovePancakes, the session opened and I landed at an interactive shell as jeremy. A quick enumeration in the home directory showed two entries and the user flag:

Hurray!!! I got the user flag.


Local user map — reading /etc/passwd

I dumped /etc/passwd from the shell to get an overview of system users and their shells:

previous htb solution

I listed /etc/passwd to map system users. jeremy is UID 1000 with a bash shell (the account I am currently on). Notably, the host /etc/passwd does not contain the nextjs/node entries we saw earlier via the web file-read, indicating the app’s environment (the one exposed through the vulnerable /api/download) is separate from the host (likely a container or build environment). This discrepancy guided me to pursue both vectors: follow the app-level clues (files and credentials found via the web endpoint) and perform host-level enumeration (sudo rights, docker/lxd sockets, SUID binaries, cron jobs) to find a path from jeremy to root.


Network layout — reading ip a to understand the host & container networking

I ran ip a to map the machine’s network interfaces and got a clear picture of both the host-facing interface and Docker/container networks:

previous htb walkthroughs

ip a revealed the host’s network (eth0: 10.10.11.83/23) and multiple container bridge interfaces (docker0: 172.17.0.1/16 and br-ba37c692e454: 172.18.0.1/16). A veth interface attached to the br-* bridge confirmed at least one live container on the host. This matched earlier clues (the web file-read appeared to expose a different /etc/passwd), indicating the app likely runs inside a container. From here I inspected the Docker socket, listed containers (if accessible), and probed services on the bridge subnets - standard, non-destructive steps to discover containerized services and potential escalation paths.


Privilege escalation — sudo -l reveals a terraform-as-root vector

I checked sudo privileges to see whether jeremy had any delegated admin powers:

previous hack the box writeups

sudo -l showed that jeremy can run /usr/bin/terraform -chdir=/opt/examples apply as root. This is a privileged but narrowly-scoped sudo rule — the trick is that Terraform executes arbitrary provisioner commands during apply. If /opt/examples is writable, we can drop a malicious main.tf (for example a null_resource using a local-exec provisioner) and then run the allowed sudo command to execute that provisioner as root. I first checked /opt/examples permissions and contents (to confirm writability and whether terraform configs already exist). If writable, creating a small Terraform file and running sudo /usr/bin/terraform -chdir=/opt/examples apply (then approving the apply) gives a straightforward path to escalate to root.


Reading the Terraform config — what main.tf in /opt/examples means

Dropping into /opt/examples I found main.tf (and a terraform.tfstate) - this is the exact directory jeremy is allowed to run terraform apply in as root, so this file is the heart of our escalation vector.

previous hack the box writeup

The Terraform configuration in /opt/examples/main.tf uses a custom provider previous.htb/terraform/examples and exposes a variable source_path that is constrained to paths under /root/examples/. Because jeremy can run terraform -chdir=/opt/examples apply as root, this directory is a privileged execution point: modifying the Terraform configuration (or supplying a malicious provider) lets us run arbitrary commands as root during apply. In practice I verified the directory contents, and then replaced the config with a minimal null_resource that runs a local-exec provisioner; running sudo /usr/bin/terraform -chdir=/opt/examples apply executes that provisioner as root and gives a straightforward path to full system compromise.


Poisoning the provider chain — using TF_CLI_CONFIG_FILE + a fake provider to get root

With terraform apply permitted as root in /opt/examples, I looked for ways to force Terraform to run code I control when it resolves providers. The Terraform docs point out the TF_CLI_CONFIG_FILE environment variable (and the CLI config it points to) can change how Terraform finds and installs providers.

HTB Previous

For example, you can configure a local filesystem mirror or a bespoke installation method that causes Terraform to execute a provider binary from a location you control.

So I followed that path:

  1. I checked the Terraform docs for environment variables and the CLI config options (the TF_CLI_CONFIG_FILE setting can tell Terraform to use a specific CLI config file). That config can be used to instruct Terraform to load providers from a local directory or otherwise alter provider discovery. In short: it’s a way to make Terraform load a provider binary from a path I control instead of pulling an official provider from the registry.

  2. I created a little directory under my user to host a fake provider binary:

Inside that directory I created a file named exactly like Terraform expects provider binaries to be named for a provider with source previous.htb/terraform/examples and version v0.1:

  1. The file I pasted was a tiny shell script:

That script, when executed as root, sets the SUID bit on /bin/bash. Once /bin/bash is SUID-root, running /bin/bash -p (or executing the new suid shell) yields a root shell. In CTF writeups this is a common, quick proof-of-success for a local privilege escalation — it’s a blunt way to demonstrate code execution as root.

  1. I also saved a CLI config file in the same dir (dev.tfrc) — the intent being to use TF_CLI_CONFIG_FILE to point Terraform at that config so Terraform will look in /home/jeremy/boltech for the provider binary instead of fetching it from the network.

Previous Machine Write-up

I inspected the fake provider file I created and then made it runnable:

Afterwards, I created a new file dev.tfrc in /home/jeremy/boltech directory and pasted:

previous htb walkthrough

I attempted to force Terraform to use my local provider via TF_CLI_CONFIG_FILE, but my initial dev_overrides key ("previous.htb/examples") didn’t match the provider source declared in main.tf (previous.htb/terraform/examples) and used a non-absolute path. Terraform issued a CLI config error but still proceeded, using the existing provider and reporting “No changes.”

How I corrected it

To make the dev override work (so Terraform will load my local provider binary), the dev_overrides key must exactly match the provider source string in main.tf, and the path must point to the real directory containing the executable. For this box the fixed config should look like:


Checking for a root shell — what actually happened

I tried to confirm the provider payload had given me a root shell by checking /bin/bash permissions and launching a preserved shell:

The listing shows no SUID bit on /bin/bash (-rwxr-xr-x — there’s no s in the owner execute field). That means bash is not setuid-root, so there’s nothing there to give me an immediate root shell.

I then ran bash -p:

previous hack the box writeup

bash -p starts a shell that preserves privileges if the process already has elevated privileges. It does not elevate your privileges by itself. Because /bin/bash did not have the SUID bit set and my process was not running with effective UID 0, bash -p ran as my normal jeremy user and thus could not read /root/root.txt.


Getting a root shell — /bin/bash -p paid off

After fixing the Terraform override and triggering the malicious provider, I re-ran a preserved shell and it came up privileged:

previous htb writeup

This is the escalation moment: by abusing the allowed sudo terraform -chdir=/opt/examples apply and controlling the provider installation via TF_CLI_CONFIG_FILE, I forced Terraform to execute a provider binary I controlled as root. That provider performed a simple, reliable payload (set SUID on a shell or similar), and now I have an interactive root shell to finish the box.

I re-verified the escalation artifacts and used the privileged shell to access the final proof:

ls -al /bin/bash shows the owner execute bit set to s:

To capture the root flag, I ran:

HackTheBox Previous Writeup

Hurray!!! I got the root flag. With that the machine was officially pwned.


If you enjoy reading my walkthrough, do not forget to like, comment, and subscribe to my YouTube channel and also connect with me on LinkedIn. Also, don't forget to turn on post notification on my YouTube channel and Medium to get notification as soon as I write.

Subscribe to my YouTube channel and Follow me on: LinkedIn | Medium | Twitter | Boltech Twitter | Buy Me a Coffee


Keywords:

Previous Hack the Box Writeup

Previous Hack the Box Machine Writeup

Previous Hack the Box Walkthrough

Previous Hack the Box Machine Walkthrough

Previous HTB Writeup

Previous HTB Walkthrough

Previous HTB Machine Walkthrough

Previous HTB Machine Writeup

Previous Hack the Box HTB Machine Walkthrough Writeup

Previous Hack the Box Solution

Previous Machine Walkthrough

HTB Previous

Previous Machine Write-up

HackTheBox Previous Writeup

previous htb writeup

previous htb walkthrough

previous hack the box writeup

previous hack the box writeups

previous htb walkthroughs

previous htb solution

previous htb writeups

previous htb complete writeup

previous htb complete walkthrough

Post a Comment

0 Comments