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

Stage 0: let

let LearningPeriod = 7d; 
let EndLearningTime = now();
let BinTime = 1h; 
let RunTime = 1h; 
let MinThreshold = 3.0; 
let GitLabFailedLogins = (GitLabApp <inlined as stages below>;

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

| 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 EventTime, FailedLoginsCountInRunTime, IpAddress, LearningThreshold, User

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