Achieve security excellence without breaking the budget!

Download guide

detect suspicious ip behavior and impossible travel

Detecting Suspicious IP Behavior and Impossible Travel

Welcome to the second part of our CrowdSec Security Engine 1.5 in Action series!

In part one, we saw how to detect a successful SSH brute force attack with the new Security Engine v1.5 feature, conditional scenarios.

In this installment, I’ll demonstrate how you can leverage the same feature to detect impossible travel — aka an account connecting from two different locations, far from each other, in a short amount of time. This method, known as impossible travel detection, is crucial for identifying suspicious IP behavior and ensuring account security.

We will use the SSHD service again as an example for this article, but this feature can be used for any service logging authentication.

What is impossible travel?

Unusual or anomalous activities associated with an IP address are considered suspicious and may indicate malicious intent or unauthorized access.

Impossible travel, in particular, refers to a user or system that is observed accessing services or networks from geographically distant locations within a time frame that makes such movement physically impossible (e.g., logging in from New York and then from Tokyo an hour later). These behaviors often trigger security alerts, as they may signal account compromise, use of proxy networks, or other unauthorized actions.

Requirements

Setup the acquisition (optional)

Same as in part one, in this article, I’m running the CrowdSec Security Engine in replay mode. If you want to use service mode, you need to set up the acquisition.

#/etc/crowdsec/acquis.yaml
---
filenames:
  - /var/log/auth.log
labels:
  type: syslog
---

Install the SSHD collection

You can find the collection here.


sudo cscli collections install crowdsecurity/sshd

Parse successful authentication

As already seen in part one, this is the parser node to parse successful SSH authentication:


#/etc/crowdsec/parsers/s01-parse/sshd-logs.yaml
---
- grok:
      pattern: 'Accepted password for %{USERNAME:sshd_valid_user} from %{IP_WORKAROUND:sshd_client_ip} port d+'
      apply_on: message
      statics:
        - meta: log_type
          value: ssh_success-auth
        - meta: target_user
          expression: "evt.Parsed.sshd_valid_user"
          

Enrich event with IP geolocation

To detect that an account connected from two different locations in a short amount of time, we need information on the geolocation of the source IP address.

We can get this info by installing the geoip-enrich parser.


sudo cscli parsers install crowdsecurity/geoip-enrich

Note: You can skip this step if you have already installed this parser.

The scenario

Now we need to check that the IPs filled in our buckets are more than 1000km away from each other.

Here is the condition that we will use for our conditional bucket:


condition: |
  len(queue.Queue) >= 2 
  and Distance(queue.Queue[-1].Enriched.Latitude, queue.Queue[-1].Enriched.Longitude,
  queue.Queue[-2].Enriched.Latitude, queue.Queue[-2].Enriched.Longitude) > 1000
  

This condition checks that the buckets contain at least two events and that the distance between the IPs in those events is more than 1000km away. We can achieve that using the Distance() helper, which uses the Haversine formula to determine the distance between two points with their longitude and latitude.

Here is the full scenario that detects an account connecting from more than 1000km away since its last connection in 4 hours:


#/etc/crowdsec/scenarios/impossible-travel.yaml
---
type: conditional
name: crowdsecurity/impossible-travel
description: "Detect impossible travel"
filter: "evt.Meta.log_type == 'ssh_success-auth'"
leakspeed: "4h"
distinct: evt.Meta.source_ip
capacity: -1
condition: |
  len(queue.Queue) >= 2 
  and Distance(queue.Queue[-1].Enriched.Latitude, queue.Queue[-1].Enriched.Longitude,
  queue.Queue[-2].Enriched.Latitude, queue.Queue[-2].Enriched.Longitude) > 1000
groupby: evt.Meta.target_user
blackhole: 1m
labels:
  service: ssh
  type: bruteforce
  remediation: true
scope:
  type: user
  expression: evt.Meta.target_user
  

To make this scenario work, we must group the events by targeted user and only look at distinct IP addresses in the bucket.

Note: we set user as the scope for this scenario. If your profile configuration generates a decision only for alert with scope IP, triggering this scenario will not lead to any decision.  

Bonus: Add context to the alert

With this scenario, the CrowdSec Security Engine will only log the user targeted by the attack. But it is also interesting to know from which IPs and countries the user logged in. To do that, I will use the new CrowdSec feature, called Alert Context. See more about this feature here.

To add the source IP addresses and the source countries, we need to run the following commands:


sudo cscli lapi context add --key source_ip --value evt.Meta.source_ip
sudo cscli lapi context add --key country --value evt.Enriched.IsoCode

When you inspect an alert, you will be able to see all the source IPs and countries that have been poured into the bucket. 

Test with CrowdSec replay mode

We can test this scenario with the CrowdSec replay mode to ensure everything works properly.

Here are some logs that represent two successful authentications from two different IPs, in 2 different countries, and in 2 hours intervals:


#ssh_auth.log
---
Apr 11 15:53:54 myvps sshd[835241]: Accepted password for root from 1.2.3.4 port 1042 ssh2
Apr 11 17:53:54 myvps sshd[835241]: Accepted password for root from 8.8.8.8 port 1042 ssh2

We can run the CrowdSec replay mode with the following command:


#$ sudo crowdsec -dsn file://ssh_auth.log -type syslog -no-api
---
INFO[14-04-2023 18:14:24] single file mode : log_media=stdout daemonize=false 
INFO[14-04-2023 18:14:24] Enabled feature flags: [none]                
INFO[14-04-2023 18:14:24] Crowdsec v1.5.0-rc3-6-g169b8442-linux-169b84421227a4a2cab1c680863df958862bc615 
WARN[14-04-2023 18:14:24] MaxOpenConns is 0, defaulting to 100         
INFO[14-04-2023 18:14:24] Loading prometheus collectors                
WARN[14-04-2023 18:14:24] Exprhelpers loaded without database client.  
INFO[14-04-2023 18:14:24] Loading grok library /etc/crowdsec/patterns  
INFO[14-04-2023 18:14:25] Loading enrich plugins                       
INFO[14-04-2023 18:14:25] Successfully registered enricher 'GeoIpCity' 
INFO[14-04-2023 18:14:25] Successfully registered enricher 'GeoIpASN'  
INFO[14-04-2023 18:14:25] Successfully registered enricher 'IpToRange' 
INFO[14-04-2023 18:14:25] Successfully registered enricher 'reverse_dns' 
INFO[14-04-2023 18:14:25] Successfully registered enricher 'ParseDate' 
INFO[14-04-2023 18:14:25] Successfully registered enricher 'UnmarshalJSON' 
INFO[14-04-2023 18:14:25] Loading parsers from 5 files                 
INFO[14-04-2023 18:14:25] Loaded 2 parser nodes                         file=/etc/crowdsec/parsers/s00-raw/syslog-logs.yaml stage=s00-raw
INFO[14-04-2023 18:14:25] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s01-parse/sshd-logs.yaml stage=s01-parse
INFO[14-04-2023 18:14:25] Loaded 1 parser nodes                         file=/etc/crowdsec/parsers/s02-enrich/dateparse-enrich.yaml stage=s02-enrich
INFO[14-04-2023 18:14:25] Loaded 1 parser nodes
…
INFO[14-04-2023 18:14:26] user root performed 'crowdsecurity/impossible-travel' (2 events over 2h0m0s) at 2023-04-11 17:53:54 +0000 UTC     

Tada! The impossible travel from the user root has been detected by CrowdSec!

Demo

To test this scenario in real life, we will connect to our server via SSH from France for the first time, and then, a few minutes later, we will use a VPN to connect to our server from Spain.

Note: First, don’t forget to restart CrowdSec since we have edited some parsers and scenarios and added context to alerts.

So, let’s connect for the first time on our server with the demo account:


ssh demo@[DEMO_IP]

Then we use our VPN to connect to the server in Spain:


VPN: Connected
Current network: [DEMO_NETWORK]
Gateway: Spain

And we connect again to our server with the same command.


ssh demo@[DEMO_IP]

The CrowdSec Security Engine has successfully detected this impossible travel:


time="25-04-2023 14:59:37" level=info msg="user demo performed 'crowdsecurity/impossible-travel' (2 events over 52.655606946s) at 2023-04-25 14:59:37.249468308 +0000 UTC"
time="25-04-2023 14:59:38" level=info msg="(ff7bfc7d3d8043188b90b8ca80edd29cEo2BtePh5aKyvESP) alert : crowdsecurity/impossible-travel by user demo"

With the Alert Context feature that we enabled before, we can see the two IP addresses and countries in the alert:


sudo cscli alerts inspect 15107

################################################################################################

 - ID           : 15107
 - Date         : 2023-04-25T14:59:38Z
 - Machine      : [machine_id]
 - Simulation   : false
 - Reason       : crowdsecurity/impossible-travel
 - Events Count : 2
 - Scope:Value  : user:demo
 - Country      : 
 - AS           : 
 - Begin        : 2023-04-25 14:58:44.593879707 +0000 UTC
 - End          : 2023-04-25 14:59:37.249486653 +0000 UTC
 - UUID         : 1f89849e-94d5-490c-9bde-ea9ffaa171f3


 - Context  :
╭───────────┬────────────────╮
│    Key    │     Value      │
├───────────┼────────────────┤
│ country   │ FR             │
│ country   │ ES             │
│ source_ip │ [demo_ip]      │
│ source_ip │ [vpn_ip]       │
╰───────────┴────────────────╯

Summing up

The conditional feature in CrowdSec Security Engine 1.5 is a powerful tool!

We already saw how to use it in detecting successful brute force attacks and impossible travel. 

But did you know you can also use the conditional feature to detect post-exploitation? Come back to read part three of CrowdSec Security Engine 1.5 in Action series to learn how to do it.

WRITTEN BY

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.