idps-escape

Suspicious login

Objectives

Example Scenario: A user logs in at 03:00 from a foreign IP address, deviating from their normal location and login schedule. Another user who typically logs in from Luxembourg starts showing login activity from multiple countries within a short time frame. These behaviors deviate from the user’s normal pattern and may indicate account compromise, credential theft, or malicious automation.

The objective is to detect such deviations in login behavior by modeling typical login patterns (time, location, frequency) per user and identifying outliers indicative of suspicious or unauthorized access.

Categorical Features: To improve detection accuracy, categorize anomalies by username or user ID. This ensures the model builds a separate baseline per user. Optional dimensions include user department, user role, or device ID if such metadata exists in the log source. Data should be sliced per user to model unique behavioral patterns.

Hybrid detection (AD + rules): Alongside anomaly detection, this scenario also enables rule-based (signature) detection in Wazuh. Rules cover high-confidence patterns such as failed-login bursts, impossible travel, and a correlation of both. This hybrid approach gives fast, deterministic alerts (rules) while Anomaly Detector could handle subtle, behavior-based deviations.

Signature-based approach

This scenario implements a signature-based detection pipeline for suspicious SSH logins. It combines local log enrichment with Wazuh decoders, rules, and active responses.

Log source

The primary log source for this scenario is the Linux authentication log: /var/log/auth.log. This file contains raw SSH authentication events produced by sshd (both successful and failed login attempts).

Log enrichment

The enrichment step is performed by the radar-helper/radar-helper.py script located in the radar directory. The helper does:

This file is the Wazuh-monitored log source for the signature-based detection.

Wazuh decoders

The enriched entries in /var/log/suspicious_login.log are parsed by Wazuh using decoders defined in /radar/scenarios/decoders/suspicious_login/0310-ssh.xml. At the end of this file, specific decoders are defined to handle enriched SSH events:

Role of these decoders: Parse the enriched log format produced by radar-helper.py. These extract both standard SSH fields (e.g., user, IP, port, result) and enrichment fields (ASN, country, geo-velocity, etc.).

Detection logic

The parsed events are then evaluated by rules defined in /radar/scenarios/rules/suspicious_login/, namely rules starting with 210XXX. These rules implement the signature-based detection logic, including:

When rule conditions are satisfied, Wazuh generates an alert for the corresponding suspicious login behavior.

Active Response

Once a rule from /radar/scenarios/rules/suspicious_login fires, Wazuh triggers the configured active response defined in /radar/scenarios/ossec/radar-suspicious-login-ossec-snippet.xml. This snippet binds the rule IDs starting with 210XXX to email active-response command.

The email active response uses /radar/scenarios/active_responses/radar_ar.py to:

End-to-end flow

  1. Raw logs are generated by sshd and written to /var/log/auth.log.
  2. radar-helper.py tails /var/log/auth.log, enriches SSH events with GeoIP data (ASN, country, geo-velocity), and writes them to /var/log/suspicious_login.log.
  3. Wazuh monitors /var/log/suspicious_login.log.
  4. Decoders in 0310-ssh.xml (sshd-success-with-radar, ssh-failed-with-radar, ssh-invfailed-with-radar) parse enriched SSH events into structured fields.
  5. Rules in /radar/scenarios/rules/suspicious_login/ apply signature-based logic to detect failed-login bursts and impossible travel patterns.
  6. Active responses configured in /radar/scenarios/ossec/radar-suspicious-login-ossec-snippet.xml are executed when rules fire, triggering email notifications via radar_ar.py script.

This completes the signature-based suspicious login detection path for the suspicious_login scenario.

Manual setup

This section describes how to manually deploy the signature-based suspicious login scenario on Wazuh.

We distinguish between:


Prerequisites


Agent-side setup

  1. Copy /radar/radar-helper.py to the target host into /opt/radar/radar-helper.py:
    mkdir -p /opt/radar
    mkdir -p /opt/radar/venv
    chown user:user /opt/radar -R
    chmod 755 /opt/radar
    
  2. Ensure required Python packages are installed (paths must match what radar-helper.py expects):
    apt-get update
    apt-get install -y \
      python3 \
      python3-venv \
      python3-pip
    python3 -m venv /opt/radar/venv
    /opt/radar/venv/bin/pip install --upgrade pip
    /opt/radar/venv/bin/pip install maxminddb
    
  3. Ensure required GeoIP databases installed in the required paths:
    mkdir -p /usr/share/GeoIP
    chown user:user /usr/share/GeoIP
    chmod 755 /usr/share/GeoIP
    cp ../geoip/GeoLite2-City.mmdb /usr/share/GeoIP/GeoLite2-City.mmdb
    cp GeoLite2-ASN.mmdb  /usr/share/GeoIP/GeoLite2-ASN.mmdb
    
  4. Copy radar-helper/radar-helper.service service configurations and run it as a service so it continuously:
    cp ../radar-helper/radar-helper.service /etc/systemd/system/radar-helper.service
    systemctl daemon-reload
    systemctl enable radar-helper.service
    systemctl start radar-helper.service
    
  5. Configure Wazuh agent to monitor /var/log/suspicious_login.log:
    nano /var/ossec/etc/ossec.conf
    

    And paste the content of /radar/scenarios/ossec/radar-suspicious-login-ossec-snippet.xml into the end of file before the tag </ossec_config>

  6. Save the file and restart the agent:
    systemctl restart wazuh-agent
    

Manager-side Setup

  1. Copy /radar/scenarios/decoders/suspicious_login/0310-ssh.xml to the manager and ensure that it has the needed permissions root:wazuh:
    cp /radar/scenarios/decoders/suspicious_login/0310-ssh.xml /var/ossec/etc/decoders/0310-ssh.xml
    chmod 640 /var/ossec/etc/decoders/0310-ssh.xml
    chown root:wazuh /var/ossec/etc/decoders/0310-ssh.xml
    
  2. Ensure that the default 0310 SSH decoders are excluded from the configurations of manager:
    nano /var/ossec/etc/ossec.conf
    

    And add this line into the ruleset tag: ```

0310-ssh_decoders.xml
3. Copy the content of `/radar/scenarios/rules/suspicious_login` into the `/var/ossec/etc/rules/`
4. Copy the `/radar/scenarios/active_responses/radar_ar.py` script into `/var/ossec/active-response/bin/` and ensure that it has proper permissions: 

cp /radar/scenarios/active_responses/radar_ar.py /var/ossec/active-response/bin/radar_ar.py chmod 750 /var/ossec/active-response/bin/.py chown root:wazuh /var/ossec/active-response/bin/.py

5. Add the content of `/radar/scenarios/ossec/radar-suspicious-login-ossec-snippet.xml` inside `<ossec_config>` in `/var/ossec/etc/ossec.conf`.
6. Restart Wazuh manager:

/var/ossec/bin/wazuh-control restart



## Behavior-based Approach

> This approach should be tested in experimental environment. Improvements are needed for production environment usage.

### Data Preparation & Ingestion

#### Dataset Ingestion

To simulate “today” data and feed Wazuh’s AD plugin, we shift each file’s dates into the last three days. The script is located in suspicious_login/wazuh_ingest.py. Run it from suspicious_login:

```bash
python3 /radar/archives/suspicious_login/wazuh_ingest.py

Index Pattern & Wazuh Integration

  1. In Dashboards Management, create an Index Pattern for wazuh-ad-suspicious-login-*.
  2. Confirm documents appear in Discover with fields:
    • @timestamp (date)
    • User ID (string)
    • Country, IP Address, event_hour, etc.

SSO system configuration

  1. In agent endpoint, run Keycloak:
docker run -d --name keycloak -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=secret \
  quay.io/keycloak/keycloak:24.0.1 start-dev
  1. In Keycloak Admin Console (http://localhost:8080):
    • Create Realm: demo
    • Create Confidential Client: wazuh (enable Service Accounts)
    • Assign roles from realm-management: view-users, manage-users
  2. In agent endpoint, run script for adding users from dataset. For this, first set environment variables:
export KC_BASE_URL=http://127.0.0.1:8080
export KC_REALM=demo
export KC_ADMIN_USER=admin
export KC_ADMIN_PASSWORD=secret

Run the script to bulk create users:

python3 bulk_create_keycloak_users.py dataset/rba-dataset+0.csv

Detection

Anomaly Detector & Feature Configuration

Create the Anomaly Detector
  1. Navigate in Wazuh Dashboards to OpenSearch Plugins ➔ Anomaly Detection.
  2. Click Create detector and fill out:
    • Name: suspicious-login-detector
    • Description: “Monitor per-user login”
    • Index: wazuh-ad-suspicious-login-*
    • Time field: @timestamp
    • Detection interval: 5m (with 1m window delay)
    • Detector type: Real-time (continuous)
    • Custom result index: opensearch-ad-plugin-result-suspicious_login (!important)

Define Features
Feature name Method Field Notes
login_count value_count User ID.keyword Counts total login events per user
distinct_geo_country Custom expression See “Workaround for Cardinality” below
login_hour_cardinality Custom expression See “Workaround for Cardinality” below

Workaround for Cardinality

OpenSearch’s anomaly-detection UI doesn’t directly expose a cardinality() aggregation in the simple “Field value” mode, so we inject our two cardinality features via custom JSON expressions:

{
  "distinct_geo_country": {
    "cardinality": {
      "field": "Country.keyword"
    }
  }
}
{
  "login_hour_cardinality": {
    "cardinality": {
      "field": "event_hour"
    }
  }
}

Each of these goes into the “Custom expression” section when you add a feature.

Enable Categorical Field (per-user modelling)

Under Categorical field, select the user identifier User ID.keyword.

This ensures each user gets its own statistical model, preventing Alice’s behavior from obscuring Bob’s anomalies.


Saving & Validation

Click Next to Review.

Monitor, Webhook & Wazuh Rule Integration

Create an OpenSearch Monitor

In suspicious-login-detector Anomaly overview, set up alert:

  1. This will create a monitor suspicious-login-detector-Monitor, which will create an alert when an anomaly is detected.
  2. Trigger Configuration: Add trigger
    • Trigger name: Suspicious-Login-Detected
    • Severity: High
    • Condition:

    When choosing thresholds for firing alerts, you must balance sensitivity (catching real threats) against precision (avoiding false positives). A balanced strategy is to require:

    • anomaly_grade ≥ 0.8: captures the upper quintile of deviations without triggering on mild fluctuations, and
    • confidence ≥ 0.85: ensures the model has seen enough data to trust its grade.

    Starting here helps minimize alerts on spikes. Particularly important in high-cardinality, per-user detectors where data volume per user can vary widely. Tuning can then adjust these up or down based on observed false-positive rates during the analysis.

  3. Before following with an action, create a Notification Channel in Wazuh. Go to Menu, navigate to Notifications under Explore. And create a Channel:
    • Name: RADAR
    • Channel type: Custom webhook
    • Method: POST
    • Webhook URL: http://<wazuh-manager>:8080/notify
  4. Action
    • Action name: RADAR
    • Channel: RADAR
    • Message (must be JSON)

        {
          "monitor": {
            "name": ""
          },
          "trigger": {
            "name": ""
          },
          "entity": "",
          "periodStart": "",
          "periodEnd":   ""
        }
      

When the condition is met, this monitor will send structured JSON to the webhook.


Webhook Script (ad_alerts_webhook.py)

This webhook is a simple Flask application that receives the monitor’s payload and appends a single line to /var/log/ad_alerts.log. To deploy the webhook in the Wazuh manager:

  1. Copy the ad_alerts_webhook.py file from this repository into the Wazuh manager to a custom wazuh_webhook directory.

  2. Ensure execution permissions: chmod +x

  3. Run under a python3:
    python3 ad_alerts_webhook.py
    
  4. The resulted log file should be monitored by Wazuh, thus /var/ossec/etc/ossec.conf needs to be configured:
<localfile>
    <log_format>syslog</log_format>
    <location>/var/log/ad_alerts.log</location>
</localfile>

Wazuh Decoder & Rule

Local Decoder

Add the content of the file /radar/archives/suspicious_login/local_decoder.xml in this repository into the file /var/ossec/etc/decoders/local_decoder.xml at the Wazuh manager.

Local Rules

Add the content of the file /radar/archives/suspicious_login/local_rules.xml in this repository into the file /var/ossec/etc/rules/local_rules.xml at the Wazuh manager.


Binding the Manager-Side Active Response

  1. In ossec.conf on the manager, register and bind only the ad_context_susplog_active_response.py script. Script can be found in Active Response directory.
<ossec_config>
  <!-- 1) Command declaration -->
  <command>                                                                                                             
    <name>ad_enrich_suspicious_login</name>                                                                             
    <executable>ad_context_susplog_active_response.py</executable>                                                        
    <timeout_allowed>yes</timeout_allowed>                                                                              
  </command>

  <!-- 2) Active-response binding -->
  <active-response>
    <disabled>no</disabled>
    <command>ad_enrich_suspicious_login</command>
    <location>server</location>
    <rules_id>100302</rules_id>
  </active-response>
</ossec_config>
  1. Place the script itself in active-response directory to /var/ossec/active-response/bin in wazuh manager.
  2. Give permissions for execution:
    chmod 750 /var/ossec/active-response/bin/ad_context_susplog_active_response.py
    chown root:wazuh /var/ossec/active-response/bin/ad_context_susplog_active_response.py
    
  3. Install dependencies into Wazuh manager
python3 -m pip install requests

Binding the Agent-side Active Responses

  1. Install jq for JSON parsing:
sudo apt update
sudo apt install -y jq
  1. Prepare enrichment log file:
sudo touch /var/ossec/logs/ad_pc_enriched.log
sudo chown root:wazuh /var/ossec/logs/ad_pc_enriched.log
sudo chmod 664 /var/ossec/logs/ad_pc_enriched.log
  1. Deploy contextual logging script:
sudo cp write_contextual_logs_susplog_active_response.sh /var/ossec/active-response/bin/
sudo chown root:wazuh /var/ossec/active-response/bin/write_contextual_logs_susplog_active_response.sh
sudo chmod 750 /var/ossec/active-response/bin/write_contextual_logs_susplog_active_response.sh
  1. Register command in ossec.conf:
<command>
  <name>write_contextual_logs_susplog_active_response.sh</name>
  <executable>write_contextual_logs_susplog_active_response.sh</executable>
  <timeout_allowed>yes</timeout_allowed>
</command>
  1. Deploy Keycloak AR script:
sudo cp disable_sso_user.py /var/ossec/active-response/bin/
sudo chmod 750 /var/ossec/active-response/bin/disable_sso_user.py
sudo chown root:wazuh /var/ossec/active-response/bin/disable_sso_user.py
  1. Set environment variables:
export KC_BASE_URL=http://127.0.0.1:8080
export KC_REALM=demo
export KC_CLIENT_ID=wazuh
export KC_CLIENT_SECRET=REPLACE_ME
  1. Register disable-sso-user command in ossec.conf:
<command>
  <name>disable_sso_user.py</name>
  <executable>disable_sso_user.py</executable>
  <timeout_allowed>no</timeout_allowed>
</command>
  1. Enable remote command execution.

Edit /var/ossec/etc/local_internal_options.conf:

wazuh_command.remote_commands=1
  1. Restart the Wazuh agent:
sudo systemctl restart wazuh-agent

Active Response Analysis (Suspicious Login)

In a production environment, we recommend a two-tier response strategy for login anomalies:

Tier 1: Alert Only (For Early Anomalies)

This tier gives visibility to analysts while avoiding premature blocking.

Tier 2A: Disable SSO Account (User-Based Threats)

Use case:

Impact: Prevents any future login attempts using the user’s SSO identity across all systems federated with Keycloak.

Recovery: Admins can re-enable accounts manually after validation.

Tier 2B: IP Firewall Block (Network-Based Threats)

Use case:

Impact: Temporarily drops packets from the attacker’s IP using IPTables (default expiration ~10 mins).

Recovery: IP block expires automatically unless re-enforced.

Considerations

This two-tier model balances visibility with rapid containment, reserving automated blocks for the highest-confidence scenarios and minimizing collateral disruption.


Context Extraction & Active Response Flow

Below is the end-to-end sequence when a suspicious login anomaly triggers Tier 2 containment:


False-Positive Safeguards

OpenCTI Enrichment

For Contextual Enrichment and Threat Intelligence, corresponding Active Response can be triggered on every Anomaly detection. The instruction is in Automated OpenCTI Enrichment.

Dataset

The dataset originates from Kaggle - RBA-dataset.

Generalizing Suspicious Login Detection Beyond Keycloak

While this setup uses Keycloak as the default SSO provider for demonstration purposes, the detection logic is fully generalizable to other authentication systems such as:

The anomaly detection system is designed to be identity provider–agnostic, relying only on normalized login event data.


Key Concepts for Generalization

Component Adaptation Notes
Log Source Replace or augment Keycloak logs with logs from SSH, Azure AD, Okta, etc.
Ingest Format Normalize logs to include fields like User ID, timestamp, Country, IP.
Anomaly Features Maintain behavior-based indicators (geo changes, login hours, frequency).
Categorical Field Always slice data per user (e.g., User ID.keyword, username.keyword).

Feature Mapping for Other Authentication Systems

Common Field SSH Azure AD / Okta
User ID username userPrincipalName
Timestamp timestamp createdDateTime
IP Address src_ip ipAddress
Country Derived from src_ip Derived from ipAddress
Login Time Derived from timestamp Derived from timestamp

Use Logstash, Filebeat modules, or ingestion scripts to transform and map fields before indexing to OpenSearch.

Risk Analysis

In the case of suspicious login activity,such as a user accessing the system at 03:00 from a foreign IP or from multiple countries in a short timeframe, the associated risk is again modelled using:

R = C × I

Here, C represents the confidence score output by the anomaly detection system, reflecting the likelihood that the login behavior deviates from established user-specific baselines. This use of model confidence as a proxy for likelihood is standard in behavior-based intrusion detection systems. The impact score I is derived from CVSS, adapted to represent behavioral anomalies such as unauthorized or suspicious access events.

In this context, potential consequences include moderate confidentiality loss (e.g., exposure of personal or customer data), but typically no direct integrity or availability compromise, assuming the attacker has not escalated privileges or performed destructive actions.

According to our tiered thresholding automated response mechanism, we set:

This framework ensures that anomalous login behavior is escalated only when both the confidence is high and the potential business impact is non-trivial. It also allows for consistent application of response policies across users and login patterns, making the model robust for account compromise detection.