CASE-2026-0415-K · CLOUD HUNT · 5 AWS accounts, 30 days Confirmed FOCUSED HUNT · CLOUD API ABUSE 82% CONFIDENCE

The accounts turning off the logging
had read-only permissions.

A hunt across all five AWS accounts for cloud API abuse. The worker found ReadOnlyAccess and SecurityAudit roles creating IAM users, attaching policies, and stopping the CloudTrail logging that would have recorded it. Systematic privilege boundary violations across every account, 82% confidence, four detections shipped.

The hypothesis
Are compromised credentials or over-permissioned roles running administrative actions through cloud management APIs? Hunt AWS CloudTrail and Azure Activity across all accounts over 30 days.
What the worker found
Privilege boundary violations in all 5 AWS accounts. Audit and read-only roles executing StopLogging, CreateUser, AttachRolePolicy, and DeleteAlarm. Four findings, four detections shipped.
The open question
Misconfiguration or attack? CloudTrail confirms the violations but not the cause. Both need the same fix now. The worker recommends; the analyst decides which.
12
Queries executed
5 / 5
AWS accounts hit
82%
Confidence
4
Findings
4
Detections shipped
$0.3-0.7K
Analyst cost saved
Act 1 · The Hunt Brief

Cloud API abuse hides inside legitimate management calls.

An attacker who lands a cloud credential does not drop malware. They call the same management APIs an administrator calls: create a user, attach a policy, stop the logging. Each call is valid on its own. The abuse shows only in who made the call and whether that identity should have been able to. That is a question signature-based detection does not ask.

The worker’s brief was a 30-day hunt across AWS CloudTrail and Azure Activity, testing two techniques: account manipulation (T1098) and disabling cloud logs (T1562.008). Phase one checked which connectors actually carried data before hunting in them.

# Phase 1 baseline, verify each connector carries data before hunting QUERY api_activity.api.operation, api_activity.actor.user.name, api_activity.time, api_activity.status_id FROM ‘ATB – CloudTrail Subset’ AFTER 30d LIMIT 10 → 10 records. Rich. StopLogging by rodneyspencer and CreateUser by johnfernandez in the first sample alone.

Two connectors came back empty (CloudTrail via Security Lake, EKS audit logs), and Cribl carried operations with no user attached. The worker recorded each as a gap and hunted where the data was: the CloudTrail Subset connector, which had full operation and actor detail.

Act 2 · One Query Broke It Open

Thirteen sensitive operations, one filter.

The worker asked CloudTrail for thirteen high-consequence operations in one query: the calls that create identities, grant permissions, and turn off monitoring. It returned the actor and the assumed role on every record, so a dangerous operation and the identity behind it arrived together.

QUERY api_activity.api.operation, api_activity.actor.user.name, api_activity.time, api_activity.status_id, api_activity.actor.session.uid FROM ‘ATB – CloudTrail Subset’ WITH api_activity.api.operation IN ‘StopLogging’, ‘DeleteTrail’, ‘CreateUser’, ‘AttachUserPolicy’, ‘AttachRolePolicy’, ‘CreateAccessKey’, ‘CreateLoginProfile’, ‘DeleteAlarm’, ‘PutBucketPolicy’ AFTER 30d LIMIT 25 → 25 records (capped). Sensitive operations across 5 AWS accounts, and the assumed role on each was the problem.

The pattern was not one rogue account. It was a class of roles doing things their names say they cannot. Read the actor column and the finding writes itself: an identity labeled read-only, calling StopLogging.

Act 3 · Four Findings, One Shape

Roles doing what their names forbid.

FindingRiskRoles involvedCount
CloudTrail StopLogging CRITICAL ReadOnlyAccess (stephenroberts), SecurityAudit (gwendolynmcneil, christymartin, ashleygibbs, ericastokes), plus DeveloperAccess and PowerUserAccess 15+ calls, 5 accounts
IAM User Creation HIGH ReadOnlyAccess (michaelharris, melodykelley), SecurityAudit (jamessmith, tomcohen, angelajones, michaelcooper) 10+ users, 4 accounts
Mass Policy Attachment HIGH Non-admin SSO roles attaching IAM policies to roles (privilege escalation, T1098) 25+ events, 5 accounts
CloudWatch Alarm Deletion MEDIUM Non-admin roles deleting monitoring alarms (monitoring evasion, T1562) 10+ deletions

One finding stacks on the next. The alarm deletions remove the monitoring that would flag the policy attachments. The StopLogging calls remove the record that would prove the user creations. An attacker running this playbook is clearing the evidence as they go, and the roles doing it are the ones an auditor would trust least to look at twice.

Act 4 · Misconfiguration or Attack

The worker confirmed the what. It would not guess the why.

Two readings fit the same evidence. Either the SSO permission sets are misconfigured, so read-only and audit roles were granted admin permissions they should never hold, or an attacker is exploiting role trust policies to escalate from audit into admin. CloudTrail shows the operations and the roles. It does not show which policies those permission sets actually carry, so it cannot settle the cause on its own.

The worker said so, and named the one query it could not run: the SSO permission-set contents live in IAM configuration, not in CloudTrail. Rather than pick a story the data did not support, it reported both and pointed to the review that would decide between them.

  • If misconfiguration: the fix is to strip cloudtrail:StopLogging, iam:CreateUser, and iam:Attach*Policy from every non-admin permission set.
  • If attack: the same permissions come off, and an incident opens on the roles and sessions involved.

The distinction changes the incident classification. It does not change the urgency or the first action, so the worker recommended pulling the permissions today and running the IAM review in parallel. Recommend, not declare. The analyst owns the call on cause.

Receipts

Every query. Every source. Every gap.

Key queries across the hunt

Q7Sensitive AWS operations sweep, the core findingCORE FINDING
QUERY api_activity.api.operation, api_activity.actor.user.name, api_activity.time, api_activity.status_id, api_activity.actor.session.uid FROM ‘ATB – CloudTrail Subset’ WITH api_activity.api.operation IN ‘StopLogging’, ‘DeleteTrail’, ‘CreateUser’, ‘AttachUserPolicy’, ‘AttachRolePolicy’, ‘CreateAccessKey’, ‘CreateLoginProfile’, ‘DeleteAlarm’, ‘PutBucketPolicy’ AFTER 30d LIMIT 25
25 records (capped), ~15 KB. StopLogging, CreateUser, DeleteAlarm, and AttachRolePolicy from ReadOnlyAccess and SecurityAudit roles across 5 AWS accounts.
Q1Schema discovery, api_activity field pathsSCHEMA
Search_FSQL_SCHEMA “api_activity actor user email operation action resource cloud provider”
30 schema entries, ~53 KB. Confirmed the OCSF api_activity paths: api.operation, actor.user.name, actor.session.uid, cloud.account.uid, status_id.
Q2Baseline, CloudTrail Subset connector (30d)HIT
QUERY api_activity.api.operation, api_activity.actor.user.name, api_activity.time, api_activity.status_id FROM ‘ATB – CloudTrail Subset’ AFTER 30d LIMIT 10
10 records. Full operation and actor detail. StopLogging and CreateUser present in the first sample. This became the primary hunt source.
Q3Baseline, CloudTrail via Security Lake (30d)EMPTY
QUERY api_activity.api.operation, api_activity.actor.user.name, api_activity.time, api_activity.status_id FROM ‘CT_MGMT_APIACT’ AFTER 30d LIMIT 10
0 records. Security Lake CloudTrail pipeline carried nothing in 30 days. Logged as a gap, single-source dependency flagged.
Q8Azure role assignments, complex OR filterCONNECTOR LIMIT
QUERY api_activity.message, api_activity.actor.user.name, api_activity.time, api_activity.status_id FROM ‘ATB – AzureActivity’ WITH api_activity.message CONTAINS ‘ROLEASSIGNMENTS’ OR api_activity.message CONTAINS ‘RUNCOMMAND’ AFTER 30d LIMIT 25
ERROR. The Azure connector fails on complex OR filters. Azure-side role-assignment hunting deferred and logged as a gap.

Showing 5 of 12 queries. Full audit trail in queries.md.

Sources queried · with status

SourceStatusNotes
ATB – CloudTrail SubsetHITPrimary source. Full operation and actor detail across 5 accounts.
CT_MGMT_APIACT (Security Lake)EMPTY0 records in 30d. No backup if CloudTrail Subset retention expires.
EKS Audit Logs (Security Lake)EMPTY0 records. Kubernetes API abuse is a blind spot.
ATB – AzureActivityPARTIALData present, but complex filters error. Azure hunting limited.
Cribl Audit LogsPARTIALHTTP-verb operations, user.name null on every record. No attribution.

Gaps explicitly catalogued

  • SSO permission-set contents unavailable from CloudTrail. Root cause (misconfiguration vs exploitation) needs an IAM configuration review, named as the deciding query.
  • EKS audit logs empty, zero visibility into Kubernetes API abuse, RBAC manipulation, or secrets access. Flagged High.
  • Security Lake CloudTrail pipeline empty, the hunt runs on a single connector with no backup source.
  • Azure Activity errors on complex filters, Azure IAM abuse cannot be hunted systematically until per-operation queries are validated.
  • Cribl audit logs carry no user attribution, actions through the Cribl API cannot be tied to a person.

Detections shipped from the hunt

DetectionTechniqueLevel
CloudTrail logging disabled via SSO roleT1562.008CRITICAL
IAM user creation from read-only or audit roleT1136.003, T1098HIGH
Policy attachment from non-admin roleT1098, T1078.004HIGH
CloudWatch alarm deletion (monitoring evasion)T1562MEDIUM
Lessons

What this hunt teaches the next hunt.

01

The finding was a permission boundary, not an indicator.

No known-bad IP, no malware hash. The signal was an identity doing something its role should forbid. Hunting cloud API abuse means reading the actor on every sensitive operation, because the operation alone is always legitimate.

02

A hunt should end in coverage, not just a report.

The worker left four detections behind, one per finding, so the next StopLogging from a read-only role trips an alert instead of waiting for the next hunt. The output of a hunt is standing detection, not a document.

03

An empty connector is a finding, not silence.

EKS audit logs and the Security Lake CloudTrail pipeline both returned zero. The worker recorded them as blind spots rather than reading zero as clean. An audit that trusts missing data is a false pass.

04

Cause undetermined is an answer, not a failure.

The worker confirmed the violations at 82% and stopped short of naming misconfiguration or attack, because CloudTrail could not settle it. It named the review that would. Stating the limit of the evidence is what makes the confirmed part trustworthy.

What this case proves

Three claims, made on the Workers page, tested here.

→ Hunt mode, not just triage.

The case starts from a hypothesis, not an alert. The worker tested two techniques across five accounts and confirmed a pattern no signature was watching for, then shipped the detections to catch it next time.

→ Recommend, don’t declare.

The violations are confirmed. The cause is not, and the worker refused to guess. It handed the analyst both readings, the same first action for either, and the one review that decides between them.

→ Every gap named.

Two empty connectors, one that errors, one with no attribution. The worker catalogued each, so the confidence number is bounded by what it could actually see, not inflated past it.