Detection rules › Kusto

GitLab - Brute-force Attempts

Status
available
Severity
medium
Time window
1h
Group by
User
Source
github.com/Azure/Azure-Sentinel

'This query relies on GitLab Application Logs to get failed logins to highlight brute-force attempts from different IP addresses in a short space of time.'

MITRE ATT&CK coverage

TacticTechniques
Credential AccessT1110 Brute Force

Rule body kusto

id: 2238d13a-cf05-4973-a83f-d12a25dbb153
name: GitLab - Brute-force Attempts
description: |
  'This query relies on GitLab Application Logs to get failed logins to highlight brute-force attempts from different IP addresses in a short space of time.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes: 
      - Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let LearningPeriod = 7d; 
  let EndLearningTime = now();
  let BinTime = 1h; 
  let RunTime = 1h; 
  let MinThreshold = 3.0; 
  let GitLabFailedLogins = (GitLabApp
  | where FailedLogin == 1
  | parse kind=regex Message with "Failed Login: username=" User "ip=" IpAddress 
  | project TimeGenerated, EventTime, Computer, User, HostName, HostIP, IpAddress);
  GitLabFailedLogins 
  | where EventTime between (ago(LearningPeriod) .. EndLearningTime) 
  | summarize FailedLoginsCountInBinTime = count() by User, bin(EventTime, BinTime) 
  | summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by User 
  | extend LearningThreshold = max_of(AvgOfFailedLoginsInLearning, MinThreshold) 
  | join kind=innerunique ( GitLabFailedLogins 
  | summarize FailedLoginsCountInRunTime = count() by User, IpAddress, EventTime = bin(EventTime, BinTime) ) on User 
  | where FailedLoginsCountInRunTime >= LearningThreshold
  | project User, IpAddress, EventTime, FailedLoginsCountInRunTime, LearningThreshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let LearningPeriod = 7d;
let EndLearningTime = now();
let BinTime = 1h;
let RunTime = 1h;
let MinThreshold = 3.0;

The stages below define let GitLabFailedLogins (the rule's main pipeline source).

Stage 1: source

GitLabApp

Stage 2: where

| where FailedLogin == 1

Stage 3: parse

| parse kind=regex Message with "Failed Login: username=" User "ip=" IpAddress

Stage 4: project

| project TimeGenerated, EventTime, Computer, User, HostName, HostIP, IpAddress

The stages below run on GitLabFailedLogins (the outer pipeline).

Stage 5: where

GitLabFailedLogins
| where EventTime between (ago(LearningPeriod) .. EndLearningTime)

Stage 6: summarize

| summarize FailedLoginsCountInBinTime = count() by User, bin(EventTime, BinTime)

Stage 7: summarize

| summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by User

Stage 8: extend

| extend LearningThreshold = max_of(AvgOfFailedLoginsInLearning, MinThreshold)

Stage 9: join

| join kind=innerunique ( GitLabFailedLogins 
| summarize FailedLoginsCountInRunTime = count() by User, IpAddress, EventTime = bin(EventTime, BinTime) ) on User

Stage 10: where

| where FailedLoginsCountInRunTime >= LearningThreshold

Stage 11: project

| project User, IpAddress, EventTime, FailedLoginsCountInRunTime, LearningThreshold

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
FailedLogineq
  • 1 transforms: cased
FailedLoginsCountInRunTimege
  • LearningThreshold transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
EventTimeproject
FailedLoginsCountInRunTimeproject
IpAddressproject
LearningThresholdproject
Userproject