Back
Id796a45ee-220b-42be-8415-c8c933cf3b6d
RulenameAWSCloudTrail - Creation of Lambda policy and then privilege escalation
DescriptionIdentifies creation of IAM policies that grant broad AWS Lambda permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to add execution capabilities that support privilege escalation.
SeverityMedium
TacticsDefenseEvasion
PrivilegeEscalation
Persistence
TechniquesT1484
T1098.003
Required data connectorsAWS
KindScheduled
Query frequency1d
Query period1d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Amazon%20Web%20Services/Analytic%20Rules/AWS_CreatedLambdaPolicytoPrivilegeEscalation.yaml
Version1.0.3
Arm template796a45ee-220b-42be-8415-c8c933cf3b6d.json
Deploy To Azure
let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy =  dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;
// Creating Master table with all the events to use with materialize for better performance
let EventInfo = materialize(AWSCloudTrail
| where TimeGenerated >= ago(lookback)
| where EventName in (EventNameList) or EventName in (createPolicy)
| 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]), ""));
//Checking for Policy creation event with Full Admin Privileges since lookback period.
let FullAdminPolicyEvents =  EventInfo
| where TimeGenerated >= ago(lookback)
| where EventName in (createPolicy)
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
| mvexpand Statement
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
| extend Action = tostring(Action)
| where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
| project-rename StartTime = TimeGenerated;
let PolicyAttach = EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix,  PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;
// Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
// These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName
| project-away PolicyName1
entityMappings:
- entityType: Account
  fieldMappings:
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
  - columnName: RecipientAccountId
    identifier: CloudAppAccountId
- entityType: IP
  fieldMappings:
  - columnName: SourceIpAddress
    identifier: Address
query: |
  let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
  let createPolicy =  dynamic(["CreatePolicy", "CreatePolicyVersion"]);
  let timeframe = 1d;
  let lookback = 14d;
  // Creating Master table with all the events to use with materialize for better performance
  let EventInfo = materialize(AWSCloudTrail
  | where TimeGenerated >= ago(lookback)
  | where EventName in (EventNameList) or EventName in (createPolicy)
  | 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]), ""));
  //Checking for Policy creation event with Full Admin Privileges since lookback period.
  let FullAdminPolicyEvents =  EventInfo
  | where TimeGenerated >= ago(lookback)
  | where EventName in (createPolicy)
  | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
  | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
  | mvexpand Statement
  | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
  | project-rename StartTime = TimeGenerated;
  let PolicyAttach = EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix,  PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
  | project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;
  // Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
  // These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
  FullAdminPolicyEvents
  | join kind=leftouter
  (
      PolicyAttach
  )
  on PolicyName
  | project-away PolicyName1
id: 796a45ee-220b-42be-8415-c8c933cf3b6d
queryFrequency: 1d
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Lambda privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.
status: Available
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Amazon%20Web%20Services/Analytic%20Rules/AWS_CreatedLambdaPolicytoPrivilegeEscalation.yaml
version: 1.0.3
severity: Medium
relevantTechniques:
- T1484
- T1098.003
name: AWSCloudTrail - Creation of Lambda policy and then privilege escalation
kind: Scheduled
tactics:
- DefenseEvasion
- PrivilegeEscalation
- Persistence
requiredDataConnectors:
- dataTypes:
  - AWSCloudTrail
  connectorId: AWS
description: |
  Identifies creation of IAM policies that grant broad AWS Lambda permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to add execution capabilities that support privilege escalation.
customDetails:
  RecipientAccountId: RecipientAccountId
  EventName: EventName
  UserIdentityArn: UserIdentityArn
  PolicyName: PolicyName
triggerOperator: gt
triggerThreshold: 0
queryPeriod: 1d
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/796a45ee-220b-42be-8415-c8c933cf3b6d')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/796a45ee-220b-42be-8415-c8c933cf3b6d')]",
      "properties": {
        "alertDetailsOverride": {
          "alertDescriptionFormat": "Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.",
          "alertDisplayNameFormat": "AWS Lambda privilege escalation policy activity: {{PolicyName}} by {{AccountName}}"
        },
        "alertRuleTemplateName": "796a45ee-220b-42be-8415-c8c933cf3b6d",
        "customDetails": {
          "EventName": "EventName",
          "PolicyName": "PolicyName",
          "RecipientAccountId": "RecipientAccountId",
          "UserIdentityArn": "UserIdentityArn"
        },
        "description": "Identifies creation of IAM policies that grant broad AWS Lambda permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to add execution capabilities that support privilege escalation.\n",
        "displayName": "AWSCloudTrail - Creation of Lambda policy and then privilege escalation",
        "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%20Web%20Services/Analytic%20Rules/AWS_CreatedLambdaPolicytoPrivilegeEscalation.yaml",
        "query": "let EventNameList = dynamic([\"AttachUserPolicy\",\"AttachRolePolicy\",\"AttachGroupPolicy\"]);\nlet createPolicy =  dynamic([\"CreatePolicy\", \"CreatePolicyVersion\"]);\nlet timeframe = 1d;\nlet lookback = 14d;\n// Creating Master table with all the events to use with materialize for better performance\nlet EventInfo = materialize(AWSCloudTrail\n| where TimeGenerated >= ago(lookback)\n| where EventName in (EventNameList) or EventName in (createPolicy)\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//Checking for Policy creation event with Full Admin Privileges since lookback period.\nlet FullAdminPolicyEvents =  EventInfo\n| where TimeGenerated >= ago(lookback)\n| where EventName in (createPolicy)\n| extend PolicyName = tostring(parse_json(RequestParameters).policyName)\n| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement\n| mvexpand Statement\n| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)\n| extend Action = tostring(Action)\n| where Effect =~ \"Allow\" and ((((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:*\") or ((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:CreateFunction\" and Action contains \"lambda:InvokeFunction\") or ((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:Create*\" and Action contains \"lambda:Invoke*\")) or ((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:*\" and Action contains \"dynamodb:*\") or ((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:CreateFunction\" and Action contains \"lambda:CreateEventSourceMapping\" and Action contains \"dynamodb:PutItem\" and Action contains \"dynamodb:CreateTable\") or ((Action contains \"iam:*\" or Action contains \"iam:PassRole\") and Action contains \"lambda:Create*\" and Action contains \"dynamodb:Put*\" and Action contains \"dynamodb:Create*\")) and Resource == \"*\" and Condition == \"\"\n| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix\n| project-rename StartTime = TimeGenerated;\nlet PolicyAttach = EventInfo\n| where TimeGenerated >= ago(timeframe)\n| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)\n| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),\"/\")[1])\n| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix,  PolicyName\n| extend AttachEvent = pack(\"StartTime\", StartTime, \"EndTime\", EndTime, \"EventName\", EventName, \"UserIdentityType\",   UserIdentityType, \"SourceIpAddress\", SourceIpAddress, \"AccountName\", AccountName, \"AccountUPNSuffix\", AccountUPNSuffix, \"RecipientAccountId\", RecipientAccountId, \"UserIdentityArn\", UserIdentityArn)\n| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;\n// Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.\n// These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.\nFullAdminPolicyEvents\n| join kind=leftouter\n(\n    PolicyAttach\n)\non PolicyName\n| project-away PolicyName1\n",
        "queryFrequency": "P1D",
        "queryPeriod": "P1D",
        "severity": "Medium",
        "status": "Available",
        "subTechniques": [
          "T1098.003"
        ],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "DefenseEvasion",
          "Persistence",
          "PrivilegeEscalation"
        ],
        "techniques": [
          "T1098",
          "T1484"
        ],
        "templateVersion": "1.0.3",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}