Tools · AWS detection and investigation · 01 / 04
GuardDuty triage: read a finding from type name to next action
Generate GuardDuty sample findings in a sandbox account, decode a finding type name, and work through resource, actor, and timing fields with the AWS CLI to decide whether to contain or close.
Practice on sample findings
GuardDuty analyses CloudTrail management events, VPC flow logs, DNS logs, and optional data sources such as S3 data events and EKS audit logs. In a sandbox account, create-sample-findings writes realistic findings marked as samples, so the triage steps can be learned without an incident. Enable GuardDuty only in accounts you control; it bills by analysed volume after a 30-day trial.
DET=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty create-sample-findings --detector-id "$DET" \
--finding-types 'UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS' 'CryptoCurrency:EC2/BitcoinTool.B!DNS'
aws guardduty list-findings --detector-id "$DET" \
--finding-criteria '{"Criterion":{"severity":{"Gte":7}}}'Decoding the type name
Every type follows ThreatPurpose:ResourceType/ThreatFamily.Variant!Artifact. UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS says: unauthorised access, the resource is an IAM principal, and credentials issued to an EC2 instance role were used from outside AWS. That one string already tells you which credentials to look at.
Severity is a number from 1 to 10 grouped into bands: Low below 4, Medium from 4, High from 7, and Critical from 9, which GuardDuty reserves for multi-stage attack sequences. Agree a response time per band before the first real finding, for example the next business day for Low and immediate paging for High and Critical.
The fields that drive the decision
Resource names the principal or instance. Service.Action shows the API call, its caller address, and whether it came from a known malicious list. Count, EventFirstSeen, and EventLastSeen show whether it is ongoing.
aws guardduty get-findings --detector-id "$DET" --finding-ids <id> \
--query 'Findings[0].{Type:Type,Sev:Severity,Role:Resource.AccessKeyDetails.UserName,Key:Resource.AccessKeyDetails.AccessKeyId,Api:Service.Action.AwsApiCallAction.Api,Ip:Service.Action.AwsApiCallAction.RemoteIpDetails.IpAddressV4,Count:Service.Count,First:Service.EventFirstSeen,Last:Service.EventLastSeen}'
{"Type":"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS","Sev":8,
"Role":"app-web-role","Key":"ASIAEXAMPLE...","Api":"ListBuckets",
"Ip":"198.51.100.200","Count":14,
"First":"2026-11-02T03:10:11Z","Last":"2026-11-02T03:41:52Z"}Worked decision
If this were real: temporary credentials for app-web-role were used 14 times from an outside address over half an hour. Containment is to revoke the role’s active sessions with an inline deny conditioned on aws:TokenIssueTime, then replace the instance, because the credentials came from that instance’s metadata. The investigation question is how they left the instance, which usually points to an SSRF bug or a compromised process, and the next lesson’s CloudTrail Lake queries show what else the key did.
Archive the samples when finished so they do not mix with real findings.
aws guardduty archive-findings --detector-id "$DET" --finding-ids $(aws guardduty list-findings --detector-id "$DET" \
--finding-criteria '{"Criterion":{"service.additionalInfo.sample":{"Eq":["true"]}}}' --query 'FindingIds' --output text)Diagram
Triage of one GuardDuty finding
- Name
Decode purpose, resource, and family.
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS
- Actor
Which key and role, from where?
Resource.AccessKeyDetails, RemoteIpDetails
- Timing
Is it still happening?
Service.Count, EventFirstSeen, EventLastSeen
- Contain
Revoke sessions and replace the instance.
Deny if aws:TokenIssueTime < now
Check yourself
1. What does InstanceCredentialExfiltration.OutsideAWS indicate?
2. Which fields show whether activity is ongoing?
3. Why replace the instance as well as revoke sessions?