Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

Successful brute force attack on S3 Bucket

Back
Id31b9e94b-0df6-4a3d-a297-3457b53c5d86
RulenameSuccessful brute force attack on S3 Bucket.
DescriptionA successful brute force attack on an S3 bucket was detected. Verify these actions, and if needed, remediate the compromise.
SeverityHigh
TacticsDefenseEvasion
TechniquesT1562
Required data connectorsAWS
KindScheduled
Query frequency1h
Query period1h
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Amazon Web Services/Analytic Rules/AWS_S3BruteForce.yaml
Version1.0.1
Arm template31b9e94b-0df6-4a3d-a297-3457b53c5d86.json
Deploy To Azure
let timeframe = 1h;
let failed_attempts = AWSCloudTrail
| where TimeGenerated >= ago(timeframe)
| where EventName == "GetObject" and isnotempty(ErrorMessage) and isnotempty(ErrorCode)
| where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
| summarize time_min_failed=arg_min(TimeGenerated, *), failed_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
| where failed_keys > 20;
let success_attempts = AWSCloudTrail
| where TimeGenerated >= ago(timeframe)
| where EventName == "GetObject" and isempty(ErrorMessage) and isempty(ErrorCode)
| where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
| summarize time_min_success=arg_min(TimeGenerated, *), success_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
| where success_keys >= 1;
failed_attempts
| join kind=inner success_attempts on SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, bucketName
| where time_min_success > time_min_failed
| project-away keyName
| extend timestamp = time_min_success
kind: Scheduled
status: Available
triggerThreshold: 0
relevantTechniques:
- T1562
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Amazon Web Services/Analytic Rules/AWS_S3BruteForce.yaml
requiredDataConnectors:
- dataTypes:
  - AWSCloudTrail
  connectorId: AWS
queryPeriod: 1h
tactics:
- DefenseEvasion
severity: High
triggerOperator: gt
description: |
    'A successful brute force attack on an S3 bucket was detected. Verify these actions, and if needed, remediate the compromise.'
query: |
  let timeframe = 1h;
  let failed_attempts = AWSCloudTrail
  | where TimeGenerated >= ago(timeframe)
  | where EventName == "GetObject" and isnotempty(ErrorMessage) and isnotempty(ErrorCode)
  | where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
  | extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
  | summarize time_min_failed=arg_min(TimeGenerated, *), failed_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
  | where failed_keys > 20;
  let success_attempts = AWSCloudTrail
  | where TimeGenerated >= ago(timeframe)
  | where EventName == "GetObject" and isempty(ErrorMessage) and isempty(ErrorCode)
  | where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
  | extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
  | summarize time_min_success=arg_min(TimeGenerated, *), success_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
  | where success_keys >= 1;
  failed_attempts
  | join kind=inner success_attempts on SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, bucketName
  | where time_min_success > time_min_failed
  | project-away keyName
  | extend timestamp = time_min_success  
name: Successful brute force attack on S3 Bucket.
version: 1.0.1
id: 31b9e94b-0df6-4a3d-a297-3457b53c5d86
queryFrequency: 1h
entityMappings:
- entityType: Account
  fieldMappings:
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
  - columnName: RecipientAccountId
    identifier: CloudAppAccountId
- entityType: IP
  fieldMappings:
  - columnName: SourceIpAddress
    identifier: Address
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2023-02-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/31b9e94b-0df6-4a3d-a297-3457b53c5d86')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/31b9e94b-0df6-4a3d-a297-3457b53c5d86')]",
      "properties": {
        "alertRuleTemplateName": "31b9e94b-0df6-4a3d-a297-3457b53c5d86",
        "customDetails": null,
        "description": "'A successful brute force attack on an S3 bucket was detected. Verify these actions, and if needed, remediate the compromise.'\n",
        "displayName": "Successful brute force attack on S3 Bucket.",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "AccountName",
                "identifier": "Name"
              },
              {
                "columnName": "AccountUPNSuffix",
                "identifier": "UPNSuffix"
              },
              {
                "columnName": "RecipientAccountId",
                "identifier": "CloudAppAccountId"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "SourceIpAddress",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Amazon Web Services/Analytic Rules/AWS_S3BruteForce.yaml",
        "query": "let timeframe = 1h;\nlet failed_attempts = AWSCloudTrail\n| where TimeGenerated >= ago(timeframe)\n| where EventName == \"GetObject\" and isnotempty(ErrorMessage) and isnotempty(ErrorCode)\n| where UserIdentityAccountId == \"ANONYMOUS_PRINCIPAL\" or UserIdentityAccessKeyId <> RecipientAccountId\n| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)\n| extend UserName = tostring(split(UserIdentityArn, '/')[-1])\n| extend AccountName = case( UserIdentityPrincipalid == \"Anonymous\", \"Anonymous\", isempty(UserIdentityUserName), UserName, UserIdentityUserName)\n| extend AccountName = iif(AccountName contains \"@\", tostring(split(AccountName, '@', 0)[0]), AccountName),\n  AccountUPNSuffix = iif(AccountName contains \"@\", tostring(split(AccountName, '@', 1)[0]), \"\")\n| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)\n| summarize time_min_failed=arg_min(TimeGenerated, *), failed_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName\n| where failed_keys > 20;\nlet success_attempts = AWSCloudTrail\n| where TimeGenerated >= ago(timeframe)\n| where EventName == \"GetObject\" and isempty(ErrorMessage) and isempty(ErrorCode)\n| where UserIdentityAccountId == \"ANONYMOUS_PRINCIPAL\" or UserIdentityAccessKeyId <> RecipientAccountId\n| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)\n| extend UserName = tostring(split(UserIdentityArn, '/')[-1])\n| extend AccountName = case( UserIdentityPrincipalid == \"Anonymous\", \"Anonymous\", isempty(UserIdentityUserName), UserName, UserIdentityUserName)\n| extend AccountName = iif(AccountName contains \"@\", tostring(split(AccountName, '@', 0)[0]), AccountName),\n  AccountUPNSuffix = iif(AccountName contains \"@\", tostring(split(AccountName, '@', 1)[0]), \"\")\n| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)\n| summarize time_min_success=arg_min(TimeGenerated, *), success_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName\n| where success_keys >= 1;\nfailed_attempts\n| join kind=inner success_attempts on SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, bucketName\n| where time_min_success > time_min_failed\n| project-away keyName\n| extend timestamp = time_min_success\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "PT1H",
        "severity": "High",
        "status": "Available",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "DefenseEvasion"
        ],
        "techniques": [
          "T1562"
        ],
        "templateVersion": "1.0.1",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}