Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .tests/xray-brute-force/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: xulianh/xray-brute-force-test
description: "Integration test for X-ray brute-force detection providing full stage pipeline"
scenarios:
- xulianh/xray-brute-force
parsers:
# Crucial baseline ingestion parser to populate evt.Parsed.message
- crowdsecurity/non-syslog
- xulianh/xray-logs
# Timeline refinement component
- crowdsecurity/dateparse-enrich
log_file: xray_sample.log
log_type: xray
labels:
program: xray
562 changes: 562 additions & 0 deletions .tests/xray-brute-force/parser.assert

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions .tests/xray-brute-force/scenario.assert
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

len(results) == 2
"192.0.2.20" in results[0].Overflow.GetSources()
results[0].Overflow.Sources["192.0.2.20"].IP == "192.0.2.20"
results[0].Overflow.Sources["192.0.2.20"].Range == ""
results[0].Overflow.Sources["192.0.2.20"].GetScope() == "Ip"
results[0].Overflow.Sources["192.0.2.20"].GetValue() == "192.0.2.20"
results[0].Overflow.Alert.Events[0].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[0].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[0].GetMeta("timestamp") == "2026-06-30T16:43:10Z"
results[0].Overflow.Alert.Events[1].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[1].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[1].GetMeta("timestamp") == "2026-06-30T16:43:11Z"
results[0].Overflow.Alert.Events[2].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[2].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[2].GetMeta("timestamp") == "2026-06-30T16:43:12Z"
results[0].Overflow.Alert.Events[3].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[3].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[3].GetMeta("timestamp") == "2026-06-30T16:43:13Z"
results[0].Overflow.Alert.Events[4].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[4].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[4].GetMeta("timestamp") == "2026-06-30T16:43:14Z"
results[0].Overflow.Alert.Events[5].GetMeta("action") == "rejected"
results[0].Overflow.Alert.Events[5].GetMeta("source_ip") == "192.0.2.20"
results[0].Overflow.Alert.Events[5].GetMeta("timestamp") == "2026-06-30T16:43:15Z"
results[0].Overflow.Alert.GetScenario() == "xulianh/xray-brute-force"
results[0].Overflow.Alert.Remediation == true
results[0].Overflow.Alert.GetEventsCount() == 6
"192.0.2.10" in results[1].Overflow.GetSources()
results[1].Overflow.Sources["192.0.2.10"].IP == "192.0.2.10"
results[1].Overflow.Sources["192.0.2.10"].Range == ""
results[1].Overflow.Sources["192.0.2.10"].GetScope() == "Ip"
results[1].Overflow.Sources["192.0.2.10"].GetValue() == "192.0.2.10"
results[1].Overflow.Alert.Events[0].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[0].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[0].GetMeta("timestamp") == "2026-06-30T16:43:00Z"
results[1].Overflow.Alert.Events[1].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[1].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[1].GetMeta("timestamp") == "2026-06-30T16:43:01Z"
results[1].Overflow.Alert.Events[2].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[2].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[2].GetMeta("timestamp") == "2026-06-30T16:43:02Z"
results[1].Overflow.Alert.Events[3].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[3].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[3].GetMeta("timestamp") == "2026-06-30T16:43:03Z"
results[1].Overflow.Alert.Events[4].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[4].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[4].GetMeta("timestamp") == "2026-06-30T16:43:04Z"
results[1].Overflow.Alert.Events[5].GetMeta("action") == "rejected"
results[1].Overflow.Alert.Events[5].GetMeta("source_ip") == "192.0.2.10"
results[1].Overflow.Alert.Events[5].GetMeta("timestamp") == "2026-06-30T16:43:05Z"
results[1].Overflow.Alert.GetScenario() == "xulianh/xray-brute-force"
results[1].Overflow.Alert.Remediation == true
results[1].Overflow.Alert.GetEventsCount() == 6

15 changes: 15 additions & 0 deletions .tests/xray-brute-force/xray_sample.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
2026/06/30 16:43:00.000000 from 192.0.2.10:61149 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:01.000000 from 192.0.2.10:61150 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:01.500000 from 198.51.100.55:62204 accepted tcp:www.gstatic.com:443 [direct]
2026/06/30 16:43:02.000000 from 192.0.2.10:61151 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:03.000000 from 192.0.2.10:61152 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:03.500000 from 192.0.2.10:62264 accepted tcp:1.1.1.1:443 [direct]
2026/06/30 16:43:04.000000 from 192.0.2.10:61153 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:05.000000 from 192.0.2.10:61154 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:10.000000 from 192.0.2.20:61200 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:11.000000 from 192.0.2.20:61201 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:11.500000 from 198.51.100.60:62504 accepted tcp:torrentclaw.com:443 [direct]
2026/06/30 16:43:12.000000 from 192.0.2.20:61202 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:13.000000 from 192.0.2.20:61203 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:14.000000 from 192.0.2.20:61204 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
2026/06/30 16:43:15.000000 from 192.0.2.20:61205 rejected proxy/vless/encoding: invalid request user id: 00000000-0000-0000-0000-000000000000
71 changes: 71 additions & 0 deletions parsers/s01-parse/xulianh/xray-logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---

author: xulianh
classification:

* AppSec
* Proxy
tags:
* xray
* brute-force

---
Comment on lines +1 to +12

# 🛡️ X-ray Log Parser

## 📝 Description

This parser is designed to extract relevant security events from X-ray proxy service logs. It targets incoming log strings to identify connection outcomes and safely isolate structural metadata such as source IP addresses and destination ports.

## ⚙️ Acquisition Configuration

To allow this parser to ingest logs in a production environment, the following configuration must be provisioned in the acquisition file (e.g., `/etc/crowdsec/acquis.d/xray.yaml`):

filenames:

* /var/log/xray/access.log
labels:
type: xray
Comment on lines +24 to +28

## 🔍 Extraction Logic

The component utilizes optimized Grok patterns to process the `message` field of raw incoming lines matching the `xray` program identifier.

* **Target Variables:**
* `evt.Parsed.action`: Captures the connection state outcome (e.g., `rejected`, `accepted`).
* `evt.Parsed.source_ip`: Isolates the originating IPv4 or IPv6 address.
* `evt.Parsed.source_port`: Extracts the source ephemeral port for granular logging.


* **Chronological Synchronization:**
* The parser formats and concatenates raw time components (`year`, `month`, `day`, `hour`, `minute`, `second`) into the `evt.StrTime` variable. This allows the downstream `crowdsecurity/dateparse-enrich` component to shift the event clock from the current system time to the actual historical log timestamp, unlocking flawless forensic and time-machine execution.



### 📊 Architecture Flow

[Raw X-ray Log] --> [s00-raw (non-syslog Ingestion)] --> [s01-parse (xulianh/xray-logs)]
[Available for Scenarios] <── [s02-enrich (dateparse-enrich)] <── [Extracted Fields & StrTime]

## 🧪 Validation and Testing

To validate the extraction logic within an isolated development environment without risking production services, utilize the integrated `hubtest` framework rather than global binary commands.

Run the ordinary simulation test using:

cscli hubtest run xray-brute-force

To inspect the synchronized step-by-step state transitions, Grok pattern matches, and variable updates (the strict equivalent to `cscli explain`), execute the detailed interactive diagnostic:

cscli hubtest explain xray-brute-force --verbose

### 🔒 Verification and Best Practices

A successful validation is achieved when all lines are labeled with a green success indicator (`🟢`), and the computed variables match the frozen signatures inside the local `parser.assert` file.

Security Requirements for Test Vectors:
When populating or updating the testing vector file `xray_sample.log` under `.tests/xray-brute-force/`, it is strictly forbidden to include real, un-anonymized operational production IP addresses. All network data must undergo an obfuscation process.

It is mandatory to use standard documentation IP networks defined by RFC 5737 (such as `192.0.2.0/24` or `198.51.100.0/24`). This prevents the leakage of private server topology to public community repositories and ensures consistent, portable integration testing across continuous integration (CI) environments.
16 changes: 16 additions & 0 deletions parsers/s01-parse/xulianh/xray-logs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: xulianh/xray-logs
description: "Syntactic analyzer for X-ray access records"
#filter: "evt.Parsed.program == 'xray' || evt.Meta.program == 'xray'"
filter: "evt.Line.Labels.type == 'xray'"
onsuccess: next_stage
nodes:
- grok:
pattern: '^(?P<time>%{YEAR:year}/%{MONTHNUM:month}/%{MONTHDAY:day} %{HOUR:hour}:%{MINUTE:minute}:%{INT:second}\.%{INT:micro}) from %{IP:source_ip}:%{INT:source_port} %{WORD:action} %{GREEDYDATA:reason}'
apply_on: message
statics:
- meta: source_ip
expression: "evt.Parsed.source_ip"
- meta: action
expression: "evt.Parsed.action"
- target: evt.StrTime
expression: "evt.Parsed.year + '-' + evt.Parsed.month + '-' + evt.Parsed.day + 'T' + evt.Parsed.hour + ':' + evt.Parsed.minute + ':' + evt.Parsed.second + 'Z'"
Comment on lines +15 to +16
47 changes: 47 additions & 0 deletions scenarios/xulianh/xray-brute-force.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
author: xulianh
classification:

AppSec
Proxy
tags:
xray
brute-force

🛡️ X-ray Brute-Force Detection
📝 Description
This scenario is designed to detect brute-force authentication attempts or port scanning targeting an X-ray proxy service. It actively monitors connection events that the X-ray parser explicitly classifies with a rejected action type.

⚙️ Detection Logic
The scenario implements a leaky bucket algorithm to control the frequency of failed access attempts per source IP address, handled in an isolated and concurrent manner:
Comment on lines +1 to +15

Capacity: 5 events (the 6th event within the time window triggers the bucket overflow).

Leak Speed: 20 seconds.

Trigger: If a single source IP address accumulates more than 5 rejected connection attempts within a 20-second sliding window, the bucket overflows, triggering a security alert and requesting a remediation decision (blocking).

Resilience to Mixed Traffic: In accordance with best practices for signature design, the correlation logic discriminates concurrent malicious bursts without being affected by the presence of interleaved legitimate traffic (accepted). Successful connections from an IP do not clear or reset the failure counter for that same IP, neutralizing common signature evasion techniques via legitimate noise injection.

📊 Architecture Flow
[Incoming Connection] --> [X-ray Service] --> [Log Entry (rejected)]
|
v
[Alert / Decision] <--- [Bucket Overflowed] <--- [Scenario Engine]

🧪 Validation and Testing
To ensure the scenario operates correctly before distribution, the pipeline behavior must be validated using the integrated hubtest development framework on a controlled injection vector, isolating tests from the global server installation.

The execution of the standard simulation is performed via the local test suite using the following command:
cscli hubtest run xray-brute-force

To interactively inspect the complete state transition tree and the line-by-line evaluation of syntactic expressions (equivalent to the explain command in a production environment), the detailed diagnostic directive is invoked:
cscli hubtest explain xray-brute-force --verbose

🔍 Verification and Best Practices
The framework confirms simulation success when processing the lines generates the corresponding overflow alerts for the evaluated IP addresses, matching the signatures saved in local assertion files (parser.assert and scenario.assert).

Security Standards for Test Vector Generation:

When building or updating the xray_sample.log simulation file located in the .tests/xray-brute-force/ path, it is strictly forbidden to include operational or private IP addresses extracted directly from production infrastructure. If real logs are captured from the service using filtering tools such as tail, a mandatory anonymization process must be performed before delivery.

It is recommended to replace nodes with address blocks reserved exclusively for documentation in accordance with RFC 5737 (such as the 192.0.2.0/24 or 198.51.100.0/24 networks). This ensures code portability in continuous integration (CI) environments and prevents unwanted leakage of server network topology into public community repositories.
12 changes: 12 additions & 0 deletions scenarios/xulianh/xray-brute-force.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type: leaky
name: xulianh/xray-brute-force
description: "Detect X-ray brute-force or scanning attempts"
filter: evt.Meta.action == 'rejected'
groupby: evt.Meta.source_ip
leakspeed: 20s
capacity: 5
blackhole: 1m
labels:
service: xray
type: brute-force
remediation: true
Loading