Detection rules › Kusto

GitLab - External User Added to GitLab

Status
available
Severity
medium
Time window
1d
Source
github.com/Azure/Azure-Sentinel

'This queries GitLab Application logs to list external user accounts (i.e.: account not in allow-listed domains) which have been added to GitLab users.'

MITRE ATT&CK coverage

TacticTechniques
PersistenceT1136 Create Account

Rule body kusto

id: c1544d8f-cbbd-4e35-8d32-5b9312279833
name: GitLab - External User Added to GitLab
description: |
  'This queries GitLab Application logs to list external user accounts (i.e.: account not in allow-listed domains) which have been added to GitLab users.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes: 
      - Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  // List of allow-listed domains
  let allowedDomain = pack_array("mydomain.com");
  GitLabAudit
  | where AddAction == "user"
  | project AuthorName, IPAddress, User = EntityName
  | join (GitLabApp 
  | where UserAdded == 1
  | parse kind=regex Message with "User \"" User "\"" EmailAddress " was created"
  | project tostring(User), EmailAddress = substring(EmailAddress,2,strlen(EmailAddress)-3)) on User
  | project  AuthorName, IPAddress, User, EmailAddress, DomainName = tostring(extract("@(.*)$", 1, EmailAddress))
  | where allowedDomain !contains DomainName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AuthorName        
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainName        
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let allowedDomain = pack_array("mydomain.com");

Stage 1: source

GitLabAudit

Stage 2: where

| where AddAction == "user"

Stage 3: project

| project AuthorName, IPAddress, User = EntityName

Stage 4: join

| join (GitLabApp 
| where UserAdded == 1
| parse kind=regex Message with "User \"" User "\"" EmailAddress " was created"
| project tostring(User), EmailAddress = substring(EmailAddress,2,strlen(EmailAddress)-3)) on User

Stage 5: project

| project  AuthorName, IPAddress, User, EmailAddress, DomainName = tostring(extract("@(.*)$", 1, EmailAddress))

Stage 6: where

| where allowedDomain !contains DomainName

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
allowedDomaincontainsDomainName

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
AddActioneq
  • user transforms: cased
UserAddedeq
  • 1 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
AuthorNameproject
DomainNameproject
EmailAddressproject
IPAddressproject
Userproject