Generic bpftrace-based RCE/webshell prevention technique for critical Linux network services
Generic bpftrace-based RCE/webshell prevention technique for critical Linux network services. If for whatever reason you can't run it persistently, it could be useful also as a system-wide temporary 'virtual patching' method.
bpftrace is a high-level tracing language and runtime for Linux based on eBPF. It supports static and dynamic tracing for both the kernel and user-space.
This bpftrace script focuses on monitoring and killing certain processes based on their parent process names when they invoke execve(), the system call used to execute new programs.
I've tested in against different flavours of webshells (weevly, p0wny-shell, popen), httpd CVE-2021-41773, MySQL UDF Command Execution, httpd/nginx backdoors, different vuln webapps (PHP/Java), Kafka, Zimbra, Solr to name a few. Check it out, it's just a few lines of code, the magic is done behind bpftrace:
#!/usr/bin/env bpftrace
tracepoint:syscalls:sys_enter_execve
{
@ parent = comm;
}
tracepoint:syscalls:sys_exit_execve
/ @ parent == "httpd" || @ parent == "php-fpm" || @ parent == "mysqld" || @ parent == "java" || @ parent == "postjournal" /
{
printf("Killing execve() process spawned from %s: PID %d\n", @ parent, pid);
signal(9);
}
The link to gist is here https://gist.github.com/cr0nx/e972aac974e1b5c7703ff6de39c07ca8
Short description:
- tracepoint:syscalls:sys_enter_execve block: This part triggers whenever a process calls the execve() system call, which is used to execute a new program.
- tracepoint:syscalls:sys_enter_execve: This tracepoint hooks into the kernel to track when the execve() syscall is invoked.
- @ parent = comm;: This saves the name of the process that invoked execve() into the variable @ parent. comm is a special built-in variable in bpftrace that holds the name of the current process (i.e., the parent process that called execve())
- tracepoint:syscalls:sys_exit_execve block: This part checks for specific parent processes after an execve() call completes and sends a kill signal (SIGKILL)
- tracepoint:syscalls:sys_exit_execve: This tracepoint triggers when the execve() syscall completes.
- / @ parent == ... /: This condition checks if the process that invoked execve() has one of the following names: httpd, php-fpm, mysqld, java, or postjournal. These are common process names for web servers, PHP engines, database systems, Java services, and so on.
- signal(9): This sends a SIGKILL (signal number 9) to the process that was spawned by the execve() call, immediately terminating it.
Some screenshots:
Long story short, the script is designed to monitor any execve() calls that are made by specific high-privilege or sensitive processes such as httpd (Apache), php-fpm (PHP FastCGI Process Manager), mysqld (MySQL server), java, or postjournal. If these processes attempt to spawn a new process using execve(), the script kills the new process immediately. If for whatever reason you can't run it persistently, it could be useful also as a system-wide temporary 'virtual patching' method. Definitely it could be useful during all different types of CTF competitions.
This is POC / research in progress. Some additional ideas:
- Add blocking of access to authorized_keys, id.rsa, and generally everything related to $HOME/.ssh
- Add blocking of lateral movement for outgoing connections from the target to local network addresses
-----
Like this post? Like, re-share, and join the "Linux Attack, Detection, and Live Forensics course + 90 Days PurpleLabs Access" to learn more about Linux Security in the style of Active Defense (offsec vs defsec/forensics).
Currently, there are 232 sections ready to chain. The number of people actively using the materials is constantly growing, and the recommendations we receive are fiery!
For a group of at least 5 users, I can offer a special discount. Drop me a message for details!
#redteam #blueteam #purplelabs #linux #offsec #defsec