Detect and Block Post-Exploitation Attempts
CrowdSec Security Engine 1.5 in Action — Part III
A few months ago, we announced the release of the CrowdSec Security Engine 1.5 — our biggest release since v1.0! Version 1.5 came packed with new features, and major enhancements, and offers more control over your security management.
In this blog series, I am walking you through some of the most interesting features and exploring a few use cases. In the previous two parts of this series we covered:
- How to detect successful SSH brute force attacks
- How to detect impossible travel and other suspicious IP behaviors
In this part, I will show you how you can detect and block an attacker running a backdoor after exploiting a vulnerability in a web application.
The CrowdSec Security runs in two modes — the service mode (aka default mode) and the replay mode. In this article, I'm running the CrowdSec Security Engine in replay mode. If you want to learn more about the two CrowdSec modes and how to configure acquisition for the service mode, check out the Requirements section in the first part of this series.
Install the auditd collection
You can find the collection here.
Note: If you want those rules to be persistent, you have to write them in /etc/audit/audit.rules.
There are two ways of detecting a post-exploitation attempt:
- When the attacker downloads a backdoor via a vulnerability and then executes it
- When a user prints a base64 blob, decodes it, and pipes it to an interpreter (Python, PHP, Perl, etc.)
In this tutorial, I will explore the first option.
To detect a post-exploitation attempt, we need a conditional bucket that will check in the queue:
- that there is an event where curl or wget has been executed
- and another event with the same PPID where the executable is not located in /usr/, /bin, or /sbin (the backdoor will often be placed in /tmp) and that a shell interpreter (sh, bash, dash) has been executed
Here is the condition that we will use for our conditional bucket:
This checks that:
- the bucket contains one event where the executable is wget or curl
- there is no event in the bucket where the executable path starts with /usr, /bin, or /sbin (because they might be legitimate binaries)
- one event is a call to a shell interpreter
And here is the full scenario that detects post-exploitation attempts from the internet:
You can see that the scope of this scenario is of type PID and the value is evt.Meta.pid. This means that if you want to trigger remediation for this scenario, the value of the Decision will be the PID of the last event poured into the bucket. This will be useful later on when you want to automatically block those post-exploitation attempts.
Note: Don’t forget to change remediation: false to remediation: true if you use the scenario from the CrowdSec Hub and if you want to automatically remediate against this type of attack.
Test with CrowdSec Replay mode
To check that everything works properly, you can now test this scenario with the CrowdSec Replay mode.
Here are some logs that represent a post-exploitation attempt from the net with an attacker calling wget, chmod, and running a backdoor called blitz64:
Run can run the CrowdSec Replay mode with the following command:
And there you have it — CrowdSec detected the post-exploitation attempt!
Going further: Automatic remediation
Now that you have the capacity to detect this attack, it would be great to be able to block it automatically.
So, how can you do that?
Since CrowdSec can generate remediation with a PID, you need to be able to kill this process when the scenario is triggered. Thankfully, with the crowdsec-custom-bouncer you can call a custom binary with each Decision as an argument of the script.
First, install the crowdsec-custom-bouncer and configure it to work with our local API and to get only Decisions where the scope is pid.
Second, create a custom script that receives a Decision in argument and will kill the PID in the Decision.
Install the CrowdSec custom bouncer
You can find all the information about the CrowdSec custom bouncer in our documentation.
If the bouncer is installed on the same machine as CrowdSec, then the bouncer will automatically be registered to the local API during the installation.
Create your custom script
As seen in the the custom bouncer documentation, the custom binary will receive the information about a new Decision in the following format:
And in this format when the Decision has to be removed:
Before creating your script, let's create a virtual environment:
And install the needed dependencies:
And here is your script:
Add this script in /tmp/kill_process_bouncer/kill_process_bouncer.py and it will log any action in tmp/kill_process_bouncer/logs/bouncer.log. You must give it the right to be executed with:
Note: For this tutorial, I am using /tmp/ but if you want to use it in production, put it in another folder.
So basically, this script will receive as arguments:
- the value field of the Decision
- the duration of the Decision (you don’t need it here)
- the reason of the Decision (you don’t need it here)
- the full JSON object of the Decision: you need it to get the scope and the scenario
If the scope of the Decision it received is pid and the process is currently running, then the script will try to kill it.
Let's now configure the custom bouncer to call the script when it receives a new Decision, and to filter the Decision with a scope pid directly at the local API level.
In the bouncer configuration file (/etc/crowdsec/bouncers/crowdsec-custom-bouncer.yaml), edit the bin_path parameter with the script path and add the scopes array with the pid value:
And restart the bouncer:
Configure the CrowdSec profile
In order to generate a Decision for alerts with the pid scope, you need to add this section at the beginning of the profile configuration:
Now it is time to check if your new scenario works in real-time. For this demo, I have a basic PHP website that asks a user to run a PING command. This functionality is vulnerable to command injection, and this is where I will try to upload and execute the backdoor.
Note: Before you get started with this step, please make sure add the acquisition as shown in the requirements and restart the Security Engine.
To create the backdoor and the listener, I will use Metasploit.
You can run the following command to create the backdoor binary where attacker_ip is the IP of the listener and attacker_port is the port of the listener:
Let’s now create a basic Python HTTP server to host this file, so it can be downloaded from the defender server.
And now you can configure and run the listener:
To download and execute the backdoor, you need to exploit the command injection vulnerability in the ping input of the website. The payload will download the backdoor in /tmp/backdoor.sh, give it the right to be executed and, finally, execute it:
Congrats, you now have a shell on the defender machines!
CrowdSec to the rescue
Just after the attacker submits its input to download and execute the backdoor, you can see that CrowdSec is triggering a Decision with the PID of the process running the backdoor:
And you can check in the logs of the custom script that the PID has been killed:
And finally, on the attacker machine, you can see that the Meterpreter session has been closed!
Let’s go even further!
You now have the capability to kill the PID of the process that is running the backdoor. But it would be cool to also block the IP address of the attacker, wouldn’t it?
Install the CrowdSec firewall bouncer
To install the CrowdSec firewall bouncer, we just need to run the apt install command:
Get the IP address connected to the backdoor
Here is a Python function that can retrieve the remote address to which a process is connecting (via /proc/[pid]/net/tcp):
Ban the aggressive IP address
Now that you have installed the firewall bouncer and that you have the IP you want to ban, you can simply add a CrowdSec Decision on this IP in your Python script:
After exploiting the website again, the meterpreter session has been closed again, but this time, it is not possible to exploit the website again since the IP address of the attacker has been added to the CrowdSec Decisions list and is now blocked by the firewall bouncer:
Here is the full Python script:
The conditional bucket feature introduced in CrowdSec Security Engine 1.5 is a truly exceptional tool that helps you detect more advanced suspicious and aggressive behaviors. This was the last part of our CrowdSec Security Engine 1.5 in Action series — I hope you enjoyed it!
Don’t forget to visit the CrowdSec Blog as we are publishing new and interesting tutorials for you every week.