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 Access

Rule body

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;

let GitLabFailedLogins is inlined into the numbered stages below.

Stages 1 to 4 define let GitLabFailedLogins (the rule's main pipeline source); stages 5 to 11 run on it.

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

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

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
EventTimeproject
FailedLoginsCountInRunTimeproject
IpAddressproject
LearningThresholdproject
Userproject