Serverless Execution
This detection identifies adversary abuse of serverless computing platforms — including AWS Lambda, Azure Functions, and Microsoft Power Automate — to execute arbitrary code or automate malicious workflows within cloud environments. Adversaries create or modify serverless functions to run cryptomining payloads, establish persistent backdoors triggered by cloud events, escalate privileges by attaching overprivileged IAM roles (via IAM:PassRole or iam.serviceAccounts.actAs), and exfiltrate data through automated workflows. Key indicators include unexpected serverless function creation by identities with no prior deployment history, attachment of administrative IAM roles to functions, event source mappings that enable persistent trigger-based execution, and Power Automate flows containing email forwarding or external HTTP connector actions. Real-world examples include the Denonia cryptominer (first Lambda-specific malware), Pacu framework Lambda deployment, and adversary-created Power Automate flows forwarding executive email to external addresses.
What is T1648 Serverless Execution?
Serverless Execution (T1648) maps to the Execution tactic — the adversary is trying to run malicious code in MITRE ATT&CK.
This page provides production-ready detection logic for Serverless Execution, covering the data sources and telemetry it touches: AWS CloudTrail (Sentinel Connector), Azure Activity Logs, Microsoft Defender for Cloud Apps / CloudAppEvents. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Execution
- Technique
- T1648 Serverless Execution
- Canonical reference
- https://attack.mitre.org/techniques/T1648/
let lookback = 1d;
// AWS Lambda abuse via CloudTrail
let LambdaAbuse = AWSCloudTrail
| where TimeGenerated > ago(lookback)
| where EventSource == "lambda.amazonaws.com"
| where EventName in~ (
"CreateFunction20150331",
"UpdateFunctionCode20150331v2",
"AddPermission20150331v2",
"CreateEventSourceMapping",
"UpdateFunctionConfiguration20150331v2"
)
| extend Actor = coalesce(UserIdentityArn, UserIdentityUserName)
| extend SourceIP = SourceIpAddress
| extend FunctionName = tostring(RequestParameters.functionName)
| extend FunctionRole = tostring(RequestParameters.role)
| extend Runtime = tostring(RequestParameters.runtime)
| extend RiskScore = case(
EventName == "AddPermission20150331v2", 80,
EventName == "CreateEventSourceMapping", 75,
EventName == "UpdateFunctionCode20150331v2", 70,
FunctionRole has_any ("Admin", "PowerUser", "FullAccess"), 90,
65
)
| project TimeGenerated, Platform="AWS Lambda", Actor, SourceIP,
Action=EventName, ResourceName=FunctionName,
ExtraDetail=strcat("Role: ", FunctionRole, " | Runtime: ", Runtime),
RiskScore, TenantOrAccount=RecipientAccountId, Region=AWSRegion;
// Azure Functions creation/modification
let AzureFuncAbuse = AzureActivity
| where TimeGenerated > ago(lookback)
| where OperationNameValue has_any (
"microsoft.web/sites/write",
"microsoft.web/sites/functions/write",
"microsoft.web/sites/config/write"
)
| where ActivityStatusValue == "Success"
| where ResourceProvider == "MICROSOFT.WEB"
| extend Actor = Caller
| extend SourceIP = CallerIpAddress
| extend RiskScore = 70
| project TimeGenerated, Platform="Azure Functions", Actor, SourceIP,
Action=OperationNameValue, ResourceName=ResourceId,
ExtraDetail=tostring(Properties), RiskScore,
TenantOrAccount=SubscriptionId, Region=ResourceGroup;
// Power Automate suspicious flow activity (M365 / CloudAppEvents)
let PowerAutomate = CloudAppEvents
| where TimeGenerated > ago(lookback)
| where Application == "Microsoft Power Automate"
| where ActionType in ("CreateFlow", "UpdateFlow", "EnableFlow", "ShareFlow")
| extend Actor = AccountUpn
| extend SourceIP = IPAddress
| extend FlowName = tostring(RawEventData.flowName)
| extend TriggerType = tostring(RawEventData.triggerType)
| extend RiskScore = case(
ActionType == "ShareFlow", 75,
ActionType == "EnableFlow", 70,
ActionType == "CreateFlow", 65,
60
)
| project TimeGenerated, Platform="Power Automate", Actor, SourceIP,
Action=ActionType, ResourceName=FlowName,
ExtraDetail=strcat("Trigger: ", TriggerType),
RiskScore, TenantOrAccount=tostring(AccountObjectId), Region="M365";
// Union all platforms and surface highest risk events
union LambdaAbuse, AzureFuncAbuse, PowerAutomate
| order by RiskScore desc, TimeGenerated desc
| project TimeGenerated, Platform, Actor, SourceIP, Action, ResourceName, ExtraDetail, RiskScore, TenantOrAccount, Region Detects creation, modification, and permission changes to serverless functions and automation workflows across AWS Lambda (via AWSCloudTrail), Azure Functions (via AzureActivity), and Microsoft Power Automate (via CloudAppEvents). Applies risk scoring based on operation type and known high-risk patterns such as AddPermission, CreateEventSourceMapping, and admin role attachment. Results are ordered by risk score to surface highest-priority events first.
Data Sources
Required Tables
False Positives
- Legitimate DevOps CI/CD pipelines (GitHub Actions, Jenkins, AWS CodePipeline) using service accounts to regularly deploy Lambda or Azure Function updates as part of normal SDLC workflows
- Infrastructure-as-code tooling (Terraform, AWS CDK, Pulumi, Bicep) creating or updating serverless resources during planned deployments — these typically originate from known CI/CD source IPs with consistent timing patterns
- IT or business teams creating Power Automate flows for approved process automation such as SharePoint approval workflows, Teams notifications, or internal HR onboarding processes
Sigma rule & cross-platform mapping
The detection logic for Serverless Execution (T1648) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
product: azure Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for T1648
References (10)
- https://attack.mitre.org/techniques/T1648/
- https://www.cadosecurity.com/cado-discovers-denonia-the-first-malware-specifically-targeting-lambda/
- https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
- https://rhinosecuritylabs.com/gcp/privilege-escalation-google-cloud-platform-part-1/
- https://github.com/RhinoSecurityLabs/pacu
- https://www.varonis.com/blog/power-automate-data-exfiltration
- https://www.microsoft.com/en-us/security/blog/2020/03/09/real-life-cybercrime-stories-dart-microsoft-detection-and-response-team/
- https://cloud.hacktricks.xyz/pentesting-cloud/gcp-security/gcp-services/gcp-apps-script-abuse
- https://docs.aws.amazon.com/lambda/latest/dg/security-iam.html
- https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-record-contents.html
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Create Malicious AWS Lambda Function via CLI
Expected signal: AWS CloudTrail: EventName=CreateFunction20150331, EventSource=lambda.amazonaws.com with requestParameters.functionName set to the test function name and requestParameters.role containing the execution role ARN. Appears in AWSCloudTrail Sentinel table within 5-15 minutes of ingestion.
- Test 2Attach EventBridge Scheduled Rule to Lambda Function (Persistence)
Expected signal: AWS CloudTrail: EventName=PutRule and EventName=PutTargets from EventSource=events.amazonaws.com. The PutTargets event requestParameters.targets will contain the Lambda function ARN. Visible in AWSCloudTrail table in Sentinel.
- Test 3Create Email-Forwarding Power Automate Flow via Graph API (M365)
Expected signal: CloudAppEvents table in Microsoft Sentinel: Application='Microsoft Power Automate', ActionType='CreateFlow', AccountUpn identifying the test user, with RawEventData.flowName matching the created flow display name.
Response Playbook
Triage
- 1. Identify the complete actor identity: extract the full IAM ARN (including account ID, role/user, and assumed-session name) or Azure service principal from the alert. Query CloudTrail/AzureActivity for this actor's last 30 days of Lambda/Function operations to determine if this is a regular deployer or a novel actor.
- 2. Check the function's IAM execution role (AWS): extract the role ARN from the alert's ExtraDetail field and run `aws iam list-attached-role-policies --role-name <name>` and `aws iam list-role-policies --role-name <name>`. Flag any AdministratorAccess, PowerUserAccess, iam:PassRole with wildcard resource, or policies granting sts:AssumeRole to external accounts.
- 3. Inspect the deployed function code: for Lambda, run `aws lambda get-function --function-name <name> --query 'Code.Location' --output text` to get the pre-signed S3 URL for the deployment package. Download and extract the ZIP. Look for: mining pool connection strings (stratum+tcp://), hardcoded IP:port C2 addresses, base64-encoded payloads decoded at runtime, subprocess calls to curl/wget/python, or credential harvesting from environment variables.
- 4. Enumerate trigger/persistence mechanisms: run `aws lambda list-event-source-mappings --function-name <name>` and check EventBridge rules targeting this function via `aws events list-rules-by-target --target-arn <lambda-arn>`. Scheduled or DynamoDB/S3 triggers indicate attempts to establish persistent execution.
- 5. Correlate with surrounding cloud activity: query CloudTrail for the same actor identity in the ±2 hour window. Look for: IAM:CreateAccessKey (credential creation), S3:PutBucketPolicy (exfiltration staging), EC2:CreateInstance (lateral movement), and sts:AssumeRole chains that may indicate identity escalation.
- 6. For Power Automate alerts: access the Microsoft 365 compliance portal (compliance.microsoft.com) or query the flow definition via `GET https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/<env>/flows/<id>`. Examine all connectors and actions — specifically flag: SendEmail with external recipients, HTTP POST to non-Microsoft URLs, SharePoint file sharing with external users, and OneDrive file copy operations to unknown destinations.
Containment
- Immediately throttle the suspicious Lambda function to zero concurrent executions to stop ongoing execution: `aws lambda put-function-concurrency --function-name <name> --reserved-concurrent-executions 0`. This prevents invocations without deleting evidence.
- Revoke the execution role's temporary credentials if the function was recently invoked: identify the assumed-role session ARN from CloudTrail's responseElements.functionArn context and revoke via `aws iam put-role-policy --role-name <name> --policy-name DenyAll --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*"}]}'`.
- Disable the triggering actor's access key or service principal: for AWS run `aws iam update-access-key --access-key-id <key> --status Inactive`; for Azure revoke the service principal credential in Entra ID (formerly Azure AD) portal under App Registrations → Certificates & Secrets.
- For Power Automate flows: disable the flow immediately via the Power Automate admin center (admin.powerplatform.microsoft.com → Environments → Flows → Disable). Additionally, revoke the OAuth connections used by the flow (e.g., Office 365 Outlook connector) to prevent reconnection by the adversary.
- Delete or quarantine suspicious event source mappings and EventBridge rules targeting the function before full remediation: `aws lambda delete-event-source-mapping --uuid <mapping-uuid>` and `aws events remove-targets --rule <rule-name> --ids <target-id>`.
Evidence Collection
- Export the full CloudTrail event JSON for the triggering operation including requestParameters, responseElements, userIdentity, and errorCode/errorMessage fields. Archive to an S3 bucket with object lock (COMPLIANCE mode) enabled to ensure immutability.
- Download the Lambda deployment package before deletion: use the pre-signed URL from `aws lambda get-function --function-name <name>`. Compute SHA-256 hash (`sha256sum function.zip`) and store with the incident ticket. Unzip and preserve all source files including requirements.txt/package.json for dependency analysis.
- Capture all CloudWatch Logs for the function execution history if the function was invoked: `aws logs filter-log-events --log-group-name /aws/lambda/<function-name> --start-time <epoch_ms> --end-time <epoch_ms>`. These logs reveal what code actually executed and any runtime output including exfiltrated data previews.
- Collect all CloudTrail events for the actor identity in the 72-hour window: `aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<key> --start-time <ISO8601> --end-time <ISO8601>`. Export as JSON and import to SIEM for timeline reconstruction.
- For Azure Functions: export the Function App deployment history via ARM API (`GET https://management.azure.com/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<name>/deployments`) and capture the app settings (environment variables) which may contain exfiltration endpoints or embedded credentials.
- For Power Automate: export the complete flow run history (including input/output of each action) from the Power Automate admin center. Screenshot or export the full flow diagram showing all triggers, conditions, and actions. Preserve the raw flow JSON definition via the Power Platform API.
Escalation Criteria
- ! Escalate immediately if the Lambda execution role has AdministratorAccess, iam:* permissions, or sts:AssumeRole on * — this represents full account takeover potential and requires incident commander engagement.
- ! Escalate if CloudWatch Logs or function code analysis confirms active cryptomining workloads (XMRig, T-Rex, lolMiner binaries, or stratum+tcp:// connection strings) — AWS will typically begin account suspension within hours of detecting this, requiring urgent business impact management.
- ! Escalate if the serverless function has been successfully invoked and logs show data exfiltration: HTTP POST to external IPs with large payloads, S3 cross-account PutObject to unknown buckets, or email transmission to external addresses.
- ! Escalate if the actor identity was an EC2 instance profile, ECS task role, or Lambda execution role from another function — this indicates intra-cloud lateral movement and suggests the initial compromise vector is a separate already-compromised compute resource.
- ! Escalate if a Power Automate flow is confirmed to have forwarded emails from an executive (C-suite), finance team member, IT security staff, or HR system account to an external address — this constitutes active Business Email Compromise (BEC) or insider threat incident.
Investigation Guide
Forensic Artifacts
- >
AWS CloudTrail logs: lambda.amazonaws.com events (CreateFunction20150331, UpdateFunctionCode20150331v2, AddPermission20150331v2, CreateEventSourceMapping) with full requestParameters and userIdentity - >
Lambda deployment package ZIP archive containing function source code, runtime dependencies (requirements.txt, package.json, go.mod), and any embedded binaries - >
CloudWatch Logs group /aws/lambda/<function-name> containing execution output, errors, and any data the function printed to stdout/stderr - >
IAM role trust policy and attached/inline permission policies for the function's execution role - >
EventBridge (CloudWatch Events) rules and targets JSON showing what triggers the function and under what schedule or event pattern - >
Azure Activity Log entries for microsoft.web/sites/write and microsoft.web/sites/functions/write including the Properties field with function code metadata - >
Azure Function App application settings (appsettings) which may contain hardcoded credentials, C2 URLs, or exfiltration endpoints embedded as environment variables - >
Power Automate flow definition JSON including all connectors, trigger configurations, conditions, and action parameters (accessible via Power Platform admin center or Graph API) - >
Power Automate flow run history with input/output payloads for each action execution - >
Google Workspace Apps Script revision history and execution transcripts (Apps Scripts console → My Projects → <script> → Executions)
Tuning Guidance
Reduce false positives by building allowlists of authorized CI/CD service account ARNs, Azure DevOps service principals, and M365 Power Platform environment service accounts that regularly perform serverless deployments. Tag these identities in your SIEM with a 'devops-deployer' classification and suppress alerts when the actor matches AND the source IP belongs to known CI/CD infrastructure CIDR ranges. For Power Automate, maintain a whitelist of approved flow templates and suppress CreateFlow alerts from IT-approved environment administrators. For AWS Lambda, consider increasing alert priority when the source IP is external (not from your corporate egress ranges or known CI/CD NAT gateways), the actor has no Lambda deployment history in the past 90 days, or the deployment occurs outside business hours. Suppress CreateEventSourceMapping alerts when the event source ARN belongs to a known internal DynamoDB table or Kinesis stream used by documented applications.
Hunting Queries
Hunts for newly created Lambda functions where the execution role name or ARN contains high-privilege indicators (Admin, PowerUser, FullAccess). These deployments are high-risk because a compromised or malicious function inherits full admin access to the AWS account.
// Hunt for Lambda functions with newly attached overprivileged IAM roles
AWSCloudTrail
| where TimeGenerated > ago(30d)
| where EventSource == "lambda.amazonaws.com"
| where EventName == "CreateFunction20150331"
| extend FunctionRole = tostring(RequestParameters.role)
| extend FunctionName = tostring(RequestParameters.functionName)
| extend Actor = coalesce(UserIdentityArn, UserIdentityUserName)
| extend Runtime = tostring(RequestParameters.runtime)
| where FunctionRole has_any ("Admin", "PowerUser", "FullAccess", "AdministratorAccess", "Root")
or FunctionRole matches regex @"arn:aws:iam::[0-9]+:role/.*[Aa]dmin.*"
| project TimeGenerated, Actor, FunctionName, FunctionRole, Runtime, AWSRegion, RecipientAccountId
| order by TimeGenerated desc index=* sourcetype="aws:cloudtrail" eventSource="lambda.amazonaws.com" eventName="CreateFunction20150331"
| eval function_name='requestParameters.functionName'
| eval function_role='requestParameters.role'
| eval actor='userIdentity.arn'
| eval runtime='requestParameters.runtime'
| where match(function_role, "(?i)(admin|poweruser|fullaccess|administrator|root)")
| table _time, actor, function_name, function_role, runtime, awsRegion, recipientAccountId
| sort -_time Hunts specifically for EventBridge (CloudWatch Events) rules that target Lambda functions — the persistence mechanism adversaries use to re-invoke malicious functions on schedule or in response to cloud events. This finds the trigger linkage rather than the function creation itself, catching cases where the adversary modifies an existing function and adds a new trigger separately.
// Hunt for EventBridge rules that create persistent Lambda triggers (persistence backdoor pattern)
AWSCloudTrail
| where TimeGenerated > ago(30d)
| where EventSource == "events.amazonaws.com"
| where EventName in ("PutRule", "PutTargets")
| extend RuleName = tostring(RequestParameters.name)
| extend ScheduleExpression = tostring(RequestParameters.scheduleExpression)
| extend Targets = tostring(RequestParameters.targets)
| extend Actor = coalesce(UserIdentityArn, UserIdentityUserName)
| where Targets has "lambda" or Targets has "arn:aws:lambda"
| join kind=leftouter (
AWSCloudTrail
| where TimeGenerated > ago(30d)
| where EventSource == "lambda.amazonaws.com"
| where EventName == "CreateFunction20150331"
| extend FunctionName = tostring(RequestParameters.functionName)
| project TimeGenerated, FunctionName, FunctionCreatedBy=coalesce(UserIdentityArn, UserIdentityUserName)
) on $left.Actor == $right.FunctionCreatedBy
| project TimeGenerated, Actor, RuleName, ScheduleExpression, Targets, FunctionName, AWSRegion
| order by TimeGenerated desc index=* sourcetype="aws:cloudtrail" eventSource="events.amazonaws.com" eventName IN ("PutRule", "PutTargets")
| eval actor='userIdentity.arn'
| eval rule_name='requestParameters.name'
| eval schedule='requestParameters.scheduleExpression'
| eval targets=mvjoin('requestParameters.targets{}', ",")
| where match(targets, "(?i)(lambda|arn:aws:lambda)")
| table _time, actor, rule_name, schedule, targets, awsRegion
| sort -_time Hunts for Power Automate flows created or updated with actions containing email forwarding, external HTTP POST calls, webhook triggers, or anonymous SharePoint sharing link generation. Adversaries use these flows to silently exfiltrate email content or document data to external destinations from within compromised M365 tenants.
// Hunt for Power Automate flows containing external connector actions (email forwarding / data exfiltration)
CloudAppEvents
| where TimeGenerated > ago(30d)
| where Application == "Microsoft Power Automate"
| where ActionType in ("CreateFlow", "UpdateFlow")
| extend FlowDefinition = tostring(RawEventData)
| extend Actor = AccountUpn
| extend FlowName = tostring(RawEventData.flowName)
// Flag flows with suspicious external action patterns
| where FlowDefinition has_any (
"ForwardEmail", "sendEmail", "Send_an_email",
"http", "webhook", "POST",
"externalRecipient", "Anonymous", "sharingLink",
"smtp.gmail", "smtp.yahoo"
)
| extend SuspiciousIndicators = strcat(
iff(FlowDefinition has "ForwardEmail", "[EmailForward] ", ""),
iff(FlowDefinition has "http", "[ExternalHTTP] ", ""),
iff(FlowDefinition has "sharingLink", "[AnonymousShare] ", "")
)
| project TimeGenerated, Actor, FlowName, SuspiciousIndicators, FlowDefinition
| order by TimeGenerated desc index=* sourcetype="o365:management:activity" Workload="MicrosoftFlow" Operation IN ("CreateFlow", "UpdateFlow")
| eval actor=UserId
| eval flow_name=mvindex('FlowDetails.displayName', 0)
| eval raw_json=_raw
| where match(raw_json, "(?i)(ForwardEmail|sendEmail|Send_an_email|http|webhook|smtp|externalRecipient|sharingLink|Anonymous)")
| eval suspicious_indicators=mvappend(
if(match(raw_json, "(?i)ForwardEmail"), "EmailForward", null()),
if(match(raw_json, "(?i)http"), "ExternalHTTP", null()),
if(match(raw_json, "(?i)sharingLink|Anonymous"), "AnonymousShare", null())
)
| table _time, actor, flow_name, suspicious_indicators, ClientIP
| sort -_time Atomic Red Team Tests
Creates a minimal AWS Lambda function using Python runtime, simulating adversary deployment of a cryptomining or C2 beacon payload. Validates that CloudTrail CreateFunction events are captured and the detection query fires.
Command
#!/bin/bash
# Prerequisites: AWS CLI configured with lambda:CreateFunction and iam:PassRole permissions
AWS_REGION="us-east-1"
FUNCTION_NAME="argus-atomic-test-$(date +%s)"
ROLE_ARN="arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/lambda-basic-execution-role"
# Create minimal benign simulation payload
cat > /tmp/argus_test_lambda.py << 'PYEOF'
import os
import json
def handler(event, context):
# Atomic test: simulates adversary function (no real malicious action)
env_vars = dict(os.environ)
return {'statusCode': 200, 'body': json.dumps({'test': 'argus-atomic-t1648'})}
PYEOF
cd /tmp && zip argus_test_lambda.zip argus_test_lambda.py
aws lambda create-function \
--function-name "$FUNCTION_NAME" \
--runtime python3.11 \
--role "$ROLE_ARN" \
--handler argus_test_lambda.handler \
--zip-file fileb:///tmp/argus_test_lambda.zip \
--region "$AWS_REGION"
echo "[+] Lambda function created: $FUNCTION_NAME"
echo "[+] Expected CloudTrail event: CreateFunction20150331 from lambda.amazonaws.com" Cleanup
aws lambda delete-function --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
rm -f /tmp/argus_test_lambda.py /tmp/argus_test_lambda.zip
echo "[+] Cleanup complete" Expected Telemetry
AWS CloudTrail: EventName=CreateFunction20150331, EventSource=lambda.amazonaws.com with requestParameters.functionName set to the test function name and requestParameters.role containing the execution role ARN. Appears in AWSCloudTrail Sentinel table within 5-15 minutes of ingestion.
Expected Detection
Alert: Serverless Execution (T1648) — Platform: AWS Lambda, Action: CreateFunction20150331. Risk score 65 for new function creation, elevated to 90 if role ARN contains Admin in the name.
Creates a CloudWatch Events/EventBridge scheduled rule that targets an existing Lambda function, simulating the adversary persistence mechanism where a malicious function is configured to execute on a recurring schedule.
Command
#!/bin/bash
# Prerequisites: AWS CLI configured, an existing Lambda function
AWS_REGION="us-east-1"
FUNCTION_NAME="argus-atomic-test-lambda" # Must exist; create first using Atomic Test 1
RULE_NAME="argus-atomic-persistence-rule-$(date +%s)"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
LAMBDA_ARN="arn:aws:lambda:${AWS_REGION}:${ACCOUNT_ID}:function:${FUNCTION_NAME}"
# Create a scheduled CloudWatch Events rule (every 5 minutes)
aws events put-rule \
--name "$RULE_NAME" \
--schedule-expression "rate(5 minutes)" \
--state ENABLED \
--region "$AWS_REGION"
# Attach the Lambda function as the target
aws events put-targets \
--rule "$RULE_NAME" \
--targets "Id=1,Arn=${LAMBDA_ARN}" \
--region "$AWS_REGION"
echo "[+] EventBridge rule created: $RULE_NAME targeting $LAMBDA_ARN"
echo "[+] Expected CloudTrail events: PutRule and PutTargets from events.amazonaws.com" Cleanup
aws events remove-targets --rule "$RULE_NAME" --ids 1 --region "$AWS_REGION"
aws events delete-rule --name "$RULE_NAME" --region "$AWS_REGION"
echo "[+] EventBridge rule removed" Expected Telemetry
AWS CloudTrail: EventName=PutRule and EventName=PutTargets from EventSource=events.amazonaws.com. The PutTargets event requestParameters.targets will contain the Lambda function ARN. Visible in AWSCloudTrail table in Sentinel.
Expected Detection
Alert fires from the hunting query detecting EventBridge rules targeting Lambda functions. If the same actor previously created the Lambda function (Atomic Test 1), the join will confirm actor correlation indicating persistence establishment.
Creates a Power Automate flow using the Microsoft Flow REST API that simulates an adversary-created email exfiltration flow. Tests detection of M365 serverless workflow abuse via CloudAppEvents.
Command
# Prerequisites: PowerShell, M365 account with Power Automate access, MSAL.PS module or OAuth token
# Install-Module MSAL.PS -Force
$TenantId = "<your-tenant-id>"
$ClientId = "<your-registered-app-client-id>" # App registered with Flow.ReadWrite.All
$Credential = Get-Credential # Enter M365 credentials
# Acquire delegated access token
$Token = Get-MsalToken -TenantId $TenantId -ClientId $ClientId -Interactive -Scopes "https://service.flow.microsoft.com/.default"
$AccessToken = $Token.AccessToken
# Get default environment ID
$EnvResponse = Invoke-RestMethod -Uri "https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments" `
-Headers @{Authorization = "Bearer $AccessToken"} -Method GET
$EnvId = $EnvResponse.value[0].name
# Create minimal flow definition with HTTP action (simulates exfil endpoint call)
$FlowBody = @{
properties = @{
displayName = "ArgusT1648-AtomicTest-$(Get-Date -Format 'HHmmss')"
definition = @{
'$schema' = "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#"
contentVersion = "1.0.0.0"
triggers = @{
manual = @{ type = "Request"; kind = "Http"; inputs = @{ schema = @{} } }
}
actions = @{
HTTP_Beacon = @{
type = "Http"
inputs = @{
method = "GET"
uri = "https://httpbin.org/get" # Benign test endpoint
}
}
}
}
state = "Started"
}
} | ConvertTo-Json -Depth 15 -Compress
$FlowResult = Invoke-RestMethod `
-Uri "https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/$EnvId/flows" `
-Method POST `
-Headers @{Authorization = "Bearer $AccessToken"; 'Content-Type' = 'application/json'} `
-Body $FlowBody
Write-Host "[+] Flow created: $($FlowResult.properties.displayName) (ID: $($FlowResult.name))" Cleanup
$FlowId = "<flow-id-from-creation-output>"
Invoke-RestMethod -Uri "https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/$EnvId/flows/$FlowId" `
-Method DELETE -Headers @{Authorization = "Bearer $AccessToken"}
Write-Host "[+] Flow deleted" Expected Telemetry
CloudAppEvents table in Microsoft Sentinel: Application='Microsoft Power Automate', ActionType='CreateFlow', AccountUpn identifying the test user, with RawEventData.flowName matching the created flow display name.
Expected Detection
Alert fires on Power Automate CreateFlow action. Hunting query for flows with external HTTP actions will also surface this test due to the HTTP_Beacon action targeting an external URL.