Cloud Audit Logs
Cloud platforms generate detailed audit logs for every API call, authentication event, and resource operation. AWS CloudTrail, Azure Activity Log, Microsoft 365 Unified Audit Log, and GCP Cloud Audit Logs are the cloud equivalent of Windows Security event logs — and they're often more comprehensive because cloud providers retain and centralize them by default.
An attacker obtained AWS access keys from a compromised developer workstation. The keys had PowerUser permissions. CloudTrail shows the complete attack sequence: the stolen keys were first used from an unfamiliar IP in a different region at 03:00 UTC. The attacker enumerated IAM permissions, created a new IAM user with admin rights (persistence), then accessed S3 buckets containing customer data. The entire attack chain is preserved in CloudTrail with minute-level precision — no log clearing possible, because the attacker can't delete CloudTrail logs without that action itself being logged.
AWS CloudTrail Investigation
# AWS CloudTrail — logs all AWS API calls
# Default retention: 90 days in CloudTrail console
# Long-term: logs delivered to S3 bucket as gzip JSON files
# Prerequisite: AWS CLI configured with read-only investigation role
PROFILE="--profile forensics-readonly"
REGION="--region us-east-1"
START="2026-09-17T00:00:00Z"
END="2026-09-18T00:00:00Z"
# Step 1: Find all events for a suspicious access key
ACCESS_KEY="AKIA1234567890EXAMPLE"
aws cloudtrail lookup-events \
$PROFILE $REGION \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=$ACCESS_KEY \
--start-time $START --end-time $END \
--output json | jq '.Events[] | {time: .EventTime, name: .EventName, source: .SourceIPAddress}'
# Step 2: Find unusual IAM activity (new users, policy changes)
aws cloudtrail lookup-events \
$PROFILE $REGION \
--lookup-attributes AttributeKey=EventSource,AttributeValue=iam.amazonaws.com \
--start-time $START --end-time $END \
--output json | jq '.Events[] | {time: .EventTime, event: .EventName, user: .Username, ip: .SourceIPAddress}'
# Step 3: Find S3 data access events (requires S3 data event logging)
aws cloudtrail lookup-events \
$PROFILE $REGION \
--lookup-attributes AttributeKey=EventSource,AttributeValue=s3.amazonaws.com \
--start-time $START --end-time $END \
--output json | \
jq '.Events[] | select(.EventName | test("GetObject|ListBucket|PutObject")) |
{time: .EventTime, event: .EventName, bucket: .Resources[0].ResourceName, ip: .SourceIPAddress}'
# Step 4: Query CloudTrail S3 logs with Athena (for large-scale analysis)
# Create Athena table over CloudTrail S3 prefix and run SQL
#!/usr/bin/env python3
"""
Parse CloudTrail JSON logs from S3 bucket (downloaded to local)
Identify: credential theft, privilege escalation, data exfiltration
"""
import json
import gzip
import os
from datetime import datetime
from collections import defaultdict
LOG_DIR = "/cases/CASE-2026-009/cloudtrail/"
SUSPICIOUS_EVENTS = {
"iam.amazonaws.com": [
"CreateUser", "CreateAccessKey", "AttachUserPolicy",
"AttachRolePolicy", "PutUserPolicy", "CreateRole",
"AddUserToGroup", "UpdateLoginProfile"
],
"s3.amazonaws.com": [
"GetObject", "PutObject", "DeleteObject",
"CreateBucket", "PutBucketPolicy"
],
"sts.amazonaws.com": [
"AssumeRole", "AssumeRoleWithWebIdentity",
"GetSessionToken", "GetFederationToken"
],
"ec2.amazonaws.com": [
"RunInstances", "CreateSecurityGroup", "AuthorizeSecurityGroupIngress",
"ModifyInstanceAttribute"
]
}
events = []
for fname in os.listdir(LOG_DIR):
if fname.endswith('.json.gz'):
with gzip.open(os.path.join(LOG_DIR, fname)) as f:
data = json.load(f)
events.extend(data.get('Records', []))
print(f"Total events: {len(events)}")
# Group events by principal + source IP
activity = defaultdict(list)
for e in events:
principal = (e.get('userIdentity', {}).get('arn', 'unknown') +
" from " + e.get('sourceIPAddress', 'unknown'))
activity[principal].append(e)
# Report suspicious principal activity
print("\n=== SUSPICIOUS ACTIVITY SUMMARY ===")
for principal, evts in sorted(activity.items(), key=lambda x: -len(x[1])):
suspicious = []
for e in evts:
src = e.get('eventSource', '')
name = e.get('eventName', '')
if src in SUSPICIOUS_EVENTS and name in SUSPICIOUS_EVENTS[src]:
suspicious.append(f"{e['eventTime']}: {name}")
if suspicious:
print(f"\n Principal: {principal}")
print(f" Total events: {len(evts)}, Suspicious: {len(suspicious)}")
for s in suspicious[:10]:
print(f" {s}")
Microsoft 365 Unified Audit Log
# M365 Unified Audit Log — covers: Exchange, SharePoint, Teams, Azure AD, Defender
# Requires: Exchange Online PowerShell module
# Role required: Audit Log Reader or higher
Connect-ExchangeOnline -UserPrincipalName "admin@corp.com"
$Start = "2026-09-17T00:00:00"
$End = "2026-09-18T00:00:00"
# Search for suspicious inbox rule creation (BEC indicator)
$inboxRules = Search-UnifiedAuditLog \
-StartDate $Start -EndDate $End \
-Operations "New-InboxRule", "Set-InboxRule" \
-ResultSize 500
$inboxRules | ForEach-Object {
$data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Time = $_.CreationDate
User = $_.UserIds
Operation = $_.Operations
RuleName = $data.Parameters | Where-Object {$_.Name -eq "Name"} | Select-Object -Expand Value
ForwardTo = $data.Parameters | Where-Object {$_.Name -like "Forward*"} | Select-Object -Expand Value
}
} | Where-Object { $_.ForwardTo } | Format-Table
# Suspicious file access in SharePoint
$sharePointAccess = Search-UnifiedAuditLog \
-StartDate $Start -EndDate $End \
-RecordType SharePoint \
-Operations "FileAccessed", "FileDownloaded", "FilePreviewed" \
-ResultSize 1000
# Group by user to find mass download
$sharePointAccess |
Group-Object UserIds |
Sort-Object Count -Descending |
Select-Object Name, Count |
Where-Object { $_.Count -gt 50 } | # More than 50 file accesses = suspicious
Format-Table
# OAuth app consent grants (attacker-added persistent app access)
Search-UnifiedAuditLog \
-StartDate $Start -EndDate $End \
-Operations "Consent to application" \
-ResultSize 100 |
Select-Object CreationDate, UserIds, AuditData |
Format-List
Cloud Audit Log Comparison
| Platform | Log source | Default retention | Key investigation areas |
|---|---|---|---|
| AWS | CloudTrail (management + data events) | 90 days (console); unlimited in S3 | IAM credential use, S3 access, EC2 launches, role assumption chains |
| Azure | Activity Log + Entra ID Sign-in + Defender | 90 days (Activity Log); 30 days (Sign-in) | Resource modifications, sign-in from unusual locations, Conditional Access failures |
| M365 | Unified Audit Log (UAL) | 90 days (E3); 365 days (E5) | Inbox rules, mass downloads, OAuth consents, admin changes, Teams messages |
| GCP | Cloud Audit Logs (Admin Activity, Data Access) | 400 days (Admin Activity); 30 days (Data Access) | Service account key creation, IAM policy changes, GCS bucket access |
Q & A
Q: An attacker used a stolen M365 session token rather than credentials — bypassing MFA. What evidence is in the logs?
Token theft attacks (using stolen session tokens/cookies to bypass MFA) leave specific indicators in the Entra ID Sign-in logs: (1) Token replay: the same refresh token used from two different IP addresses or user agents within a short window — the legitimate user from their normal IP, then the attacker from a different IP. (2) Impossible travel: a sign-in from city A, then 10 minutes later from city B — physically impossible. This appears in the Azure AD Sign-in logs and triggers Entra ID Protection risk events. (3) New device / unfamiliar location: the attacker's session appears as a new unregistered device from an unusual location even though the "sign-in" shows MFA-satisfied (because MFA was done by the legitimate user). (4) UserAgent mismatch: the legitimate user accesses M365 via a known browser; the attacker uses a different UserAgent (e.g., Python requests library). Query Sign-in logs for the user and look for multiple UserAgents in the same session timeframe. Command: Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'jsmith@corp.com'" | Select-Object CreatedDateTime, IpAddress, ClientAppUsed, DeviceDetail.