
Detecting Compromised Cloud Credentials
- 1 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Detect compromised AWS, Azure, and GCP credentials by analyzing anomalous API activity, impossible travel, and abuse indicators via GuardDuty and SCC.
About
Detects compromised cloud credentials across AWS, Azure, and GCP by analyzing anomalous API activity, impossible-travel patterns, and unauthorized provisioning. A security team uses it with GuardDuty, Defender for Identity, and SCC Event Threat Detection to surface credential abuse.
- Cross-cloud detection across AWS, Azure, and GCP
- Uses GuardDuty, Defender for Identity, and SCC threat detection
Detecting Compromised Cloud Credentials by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,834 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill detecting-compromised-cloud-credentialsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 27.3k |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Detect compromised AWS, Azure, and GCP credentials by analyzing anomalous API activity, impossible travel, and abuse indicators via GuardDuty and SCC.
Files
Detecting Compromised Cloud Credentials
When to Use
- When investigating alerts about unusual cloud API activity from unfamiliar locations
- When building detection rules for credential theft and abuse across cloud environments
- When responding to notifications from cloud providers about exposed credentials
- When monitoring for credential stuffing or brute force attacks against cloud identities
- When assessing the scope of a credential compromise after initial detection
Do not use for preventing credential compromise (use MFA, credential rotation, and secrets management), for detecting application-level credential theft (use application security monitoring), or for endpoint credential harvesting detection (use EDR tools).
Prerequisites
- AWS GuardDuty enabled across all accounts and regions
- Azure Defender for Identity and Entra ID Protection configured
- GCP Security Command Center with Event Threat Detection enabled
- CloudTrail, Azure Activity Log, and GCP Audit Log centralized for analysis
- SIEM integration for cross-cloud correlation of credential abuse indicators
- Threat intelligence feeds for known malicious IP ranges
Workflow
Step 1: Detect Credential Compromise Indicators in AWS
Monitor GuardDuty findings and CloudTrail anomalies that indicate credential abuse.
# List GuardDuty credential-related findings
aws guardduty list-findings \
--detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
--finding-criteria '{
"Criterion": {
"type": {
"Eq": [
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/TorIPCaller",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B",
"Recon:IAMUser/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"InitialAccess:IAMUser/AnomalousBehavior",
"CredentialAccess:IAMUser/AnomalousBehavior",
"Persistence:IAMUser/AnomalousBehavior"
]
},
"service.archived": {"Eq": ["false"]}
}
}' --output json
# Check for console logins from new locations
aws logs start-query \
--log-group-name cloudtrail-logs \
--start-time $(date -d "7 days ago" +%s) \
--end-time $(date +%s) \
--query-string '
fields @timestamp, userIdentity.userName, sourceIPAddress, responseElements.ConsoleLogin
| filter eventName = "ConsoleLogin"
| filter responseElements.ConsoleLogin = "Success"
| stats count() by userIdentity.userName, sourceIPAddress
| sort count desc
'
# Detect impossible travel (same user from geographically distant IPs within short time)
aws logs start-query \
--log-group-name cloudtrail-logs \
--start-time $(date -d "24 hours ago" +%s) \
--end-time $(date +%s) \
--query-string '
fields @timestamp, userIdentity.arn, sourceIPAddress, eventName
| filter userIdentity.type = "IAMUser"
| stats earliest(@timestamp) as first_seen, latest(@timestamp) as last_seen,
count_distinct(sourceIPAddress) as unique_ips by userIdentity.arn
| filter unique_ips > 3
'Step 2: Detect Credential Abuse in Azure
Monitor Entra ID sign-in logs and Defender for Identity alerts for compromised credentials.
# Check for risky sign-ins
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=riskLevelDuringSignIn ne 'none' and createdDateTime ge 2026-02-16T00:00:00Z&\$top=50" \
--query "value[*].{User:userPrincipalName,Risk:riskLevelDuringSignIn,IP:ipAddress,Location:location.city,App:appDisplayName,Status:status.errorCode}" \
-o table
# Check for sign-ins from anonymous or Tor IPs
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=riskEventTypes_v2/any(r:r eq 'anonymizedIPAddress') and createdDateTime ge 2026-02-22T00:00:00Z" \
--query "value[*].{User:userPrincipalName,IP:ipAddress,Location:location.city}" \
-o table
# List users flagged as compromised by Identity Protection
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?\$filter=riskLevel eq 'high'" \
--query "value[*].{User:userPrincipalName,RiskLevel:riskLevel,RiskState:riskState,LastDetected:riskLastUpdatedDateTime}" \
-o table
# Check for suspicious application consent grants
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=activityDisplayName eq 'Consent to application' and activityDateTime ge 2026-02-16T00:00:00Z" \
--query "value[*].{Activity:activityDisplayName,User:initiatedBy.user.userPrincipalName,App:targetResources[0].displayName}" \
-o tableStep 3: Detect Credential Abuse in GCP
Query GCP audit logs and SCC findings for credential compromise indicators.
# Check SCC Event Threat Detection findings
gcloud scc findings list ORG_ID \
--filter="state=\"ACTIVE\" AND (category=\"ANOMALOUS_CALLER_LOCATION\" OR category=\"SUSPICIOUS_LOGIN\" OR category=\"CREDENTIAL_ACCESS\")" \
--format="table(finding.category, finding.severity, finding.resourceName, finding.eventTime)"
# Query audit logs for service account key usage from unusual IPs
gcloud logging read '
protoPayload.authenticationInfo.principalEmail:*@*.iam.gserviceaccount.com
AND protoPayload.requestMetadata.callerIp!=("10." OR "172." OR "192.168.")
AND timestamp>="2026-02-22T00:00:00Z"
' --limit=100 --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.requestMetadata.callerIp, protoPayload.methodName)"
# Detect API calls from Tor exit nodes
gcloud logging read '
protoPayload.requestMetadata.callerIp:("185." OR "198." OR "45.")
AND protoPayload.authenticationInfo.principalEmail:*@company.com
AND timestamp>="2026-02-22T00:00:00Z"
' --limit=50 --format=json
# Check for new service account keys created (persistence indicator)
gcloud logging read '
protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"
AND timestamp>="2026-02-16T00:00:00Z"
' --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.request.name)"Step 4: Build Cross-Cloud Correlation Rules
Create SIEM rules that correlate credential abuse indicators across cloud providers.
# siem_correlation.py - Cross-cloud credential abuse detection
import json
from datetime import datetime, timedelta
def detect_impossible_travel(events):
"""Detect same identity used from distant locations in short timeframe."""
user_events = {}
for event in events:
user = event.get('principal', '')
ip = event.get('source_ip', '')
ts = event.get('timestamp', '')
cloud = event.get('cloud_provider', '')
key = f"{user}_{cloud}"
if key not in user_events:
user_events[key] = []
user_events[key].append({'ip': ip, 'timestamp': ts, 'cloud': cloud})
alerts = []
for user_key, accesses in user_events.items():
accesses.sort(key=lambda x: x['timestamp'])
for i in range(1, len(accesses)):
time_diff = (datetime.fromisoformat(accesses[i]['timestamp']) -
datetime.fromisoformat(accesses[i-1]['timestamp']))
if time_diff < timedelta(hours=1) and accesses[i]['ip'] != accesses[i-1]['ip']:
alerts.append({
'type': 'IMPOSSIBLE_TRAVEL',
'user': user_key,
'ip_1': accesses[i-1]['ip'],
'ip_2': accesses[i]['ip'],
'time_gap_minutes': time_diff.total_seconds() / 60,
'severity': 'HIGH'
})
return alerts
def detect_credential_stuffing(events, threshold=10):
"""Detect multiple failed logins followed by success."""
user_attempts = {}
for event in events:
user = event.get('principal', '')
success = event.get('success', False)
key = user
if key not in user_attempts:
user_attempts[key] = {'failures': 0, 'success_after_failures': False}
if not success:
user_attempts[key]['failures'] += 1
elif user_attempts[key]['failures'] >= threshold:
user_attempts[key]['success_after_failures'] = True
return [{'user': u, 'failures': d['failures'], 'severity': 'CRITICAL'}
for u, d in user_attempts.items() if d['success_after_failures']]Step 5: Respond to Confirmed Credential Compromise
Execute containment actions when credential compromise is confirmed.
# AWS: Deactivate access key immediately
aws iam update-access-key --user-name COMPROMISED_USER \
--access-key-id AKIA_COMPROMISED --status Inactive
# AWS: Invalidate temporary role credentials by updating role trust policy
aws iam update-assume-role-policy --role-name COMPROMISED_ROLE \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Principal":"*","Action":"sts:AssumeRole"}]}'
# AWS: Revoke all sessions for an IAM user
aws iam put-user-policy --user-name COMPROMISED_USER \
--policy-name RevokeOldSessions \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Deny",
"Action":"*",
"Resource":"*",
"Condition":{"DateLessThan":{"aws:TokenIssueTime":"2026-02-23T10:00:00Z"}}
}]
}'
# Azure: Revoke all sign-in sessions
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/users/COMPROMISED_USER_ID/revokeSignInSessions"
# Azure: Force password reset
az ad user update --id COMPROMISED_USER_ID --force-change-password-next-sign-in true
# GCP: Disable service account
gcloud iam service-accounts disable COMPROMISED_SA_EMAIL
# GCP: Delete service account keys
gcloud iam service-accounts keys delete KEY_ID --iam-account=COMPROMISED_SA_EMAILKey Concepts
| Term | Definition |
|---|---|
| Impossible Travel | Detection of the same credential being used from geographically distant locations within a time period that makes physical travel impossible |
| Credential Stuffing | Attack using stolen username/password combinations from data breaches to attempt login across multiple cloud services |
| Instance Credential Exfiltration | GuardDuty finding indicating EC2 instance role credentials are being used from outside the expected AWS network |
| Anomalous Behavior | Machine learning-based detection of API call patterns that deviate significantly from the established baseline for a principal |
| Session Revocation | Invalidating all active authentication sessions for a compromised principal to force re-authentication with new credentials |
| Persistence Indicator | Attacker actions designed to maintain access after initial compromise, such as creating new access keys or service account keys |
Tools & Systems
- AWS GuardDuty: ML-based threat detection with specific finding types for credential compromise and unauthorized access
- Microsoft Entra ID Protection: Identity risk detection for sign-in anomalies, compromised credentials, and risky user behavior
- GCP Event Threat Detection: SCC component detecting anomalous API usage and credential abuse in GCP environments
- CloudTrail / Activity Log / Audit Log: API audit logs providing the raw data for credential compromise investigation
- SIEM (Splunk, Elastic, Sentinel): Centralized platform for cross-cloud correlation of credential abuse indicators
Common Scenarios
Scenario: Detecting an Access Key Compromised via Phishing
Context: A developer receives a phishing email that harvests their AWS console credentials. The attacker logs in from a foreign IP, creates a new access key, and begins enumerating the account.
Approach: 1. GuardDuty triggers UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B for login from unusual country 2. SOC reviews the finding and correlates with phishing reports from the email security team 3. Query CloudTrail for all actions by the compromised user from the attacker's IP 4. Discover the attacker created new access keys and ran IAM enumeration commands 5. Immediately deactivate all access keys for the user and revoke active sessions 6. Force password reset and re-enroll MFA 7. Check for persistence: new IAM users, roles, Lambda functions, or EC2 instances created 8. Remove any persistence artifacts and document the incident timeline
Pitfalls: Simply changing the password does not invalidate existing access keys or active sessions. All access keys must be rotated and temporary credentials revoked by adding a deny-all policy for tokens issued before the compromise was detected. Attackers may create new IAM users or roles for persistence before the initial credential is revoked.
Output Format
Cloud Credential Compromise Detection Report
===============================================
Detection Date: 2026-02-23
Scope: Multi-cloud (AWS, Azure, GCP)
Period: 2026-02-16 to 2026-02-23
ACTIVE COMPROMISE INDICATORS:
[CRED-001] AWS Console Login from Unusual Location
User: developer@company.com
Source IP: 185.x.x.x (Russia)
Normal Location: US-East
GuardDuty Finding: UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B
Severity: HIGH
Status: Credential deactivated
[CRED-002] Azure Impossible Travel Detection
User: admin@company.onmicrosoft.com
Location 1: New York, US (09:00 UTC)
Location 2: Beijing, CN (09:15 UTC)
Risk Level: HIGH
Status: Sessions revoked, under investigation
DETECTION METRICS (Last 7 Days):
Impossible travel detections: 5
Anomalous API activity alerts: 12
Failed login attempts > threshold: 3
New credentials from unusual IPs: 2
Total compromises confirmed: 2
CONTAINMENT ACTIONS TAKEN:
AWS access keys deactivated: 3
Azure sessions revoked: 2
GCP service accounts disabled: 1
Passwords force-reset: 4
MFA re-enrolled: 4
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Compromised Cloud Credentials Detection API Reference
GuardDuty Credential Findings
| Finding Type | Description |
|---|---|
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS | EC2 instance creds used outside AWS |
UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B | Console login from unusual location |
UnauthorizedAccess:IAMUser/MaliciousIPCaller | API calls from known malicious IP |
Discovery:IAMUser/AnomalousBehavior | Unusual reconnaissance API patterns |
Persistence:IAMUser/AnomalousBehavior | Unusual persistence API calls |
InitialAccess:IAMUser/AnomalousBehavior | Unusual initial access patterns |
CloudTrail - Credential Abuse Investigation
# Lookup events by access key
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAXXXXXXXXXXXXXXXX \
--start-time 2024-01-01T00:00:00Z --end-time 2024-01-02T00:00:00Z
# Lookup by username
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=compromised-user
# Athena query for deep investigation
SELECT eventtime, eventsource, eventname, sourceipaddress,
useridentity.arn, errorcode
FROM cloudtrail_logs
WHERE useridentity.accesskeyid = 'AKIAXXXXXXXXXXXXXXXX'
AND eventtime > '2024-01-01'
ORDER BY eventtime DESCIAM Credential Remediation
# Deactivate access key
aws iam update-access-key --access-key-id AKIAXXXX --user-name user --status Inactive
# Delete access key
aws iam delete-access-key --access-key-id AKIAXXXX --user-name user
# Revoke all sessions (inline deny policy with token age condition)
aws iam put-user-policy --user-name user --policy-name RevokeOldSessions \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*","Condition":{"DateLessThan":{"aws:TokenIssueTime":"2024-01-15T00:00:00Z"}}}]}'
# List all access keys for user
aws iam list-access-keys --user-name userReconnaissance API Calls to Monitor
GetCallerIdentity, ListBuckets, DescribeInstances,
ListUsers, ListRoles, ListAccessKeys, DescribeRegions,
GetAccountAuthorizationDetails, ListFunctions,
DescribeDBInstances, ListSecretsAzure - Compromised Credential Detection
# Query risky sign-ins
az rest --method GET --url "https://graph.microsoft.com/v1.0/identityProtection/riskyUsers"
# Revoke user sessions
az rest --method POST --url "https://graph.microsoft.com/v1.0/users/{id}/revokeSignInSessions"#!/usr/bin/env python3
"""Compromised cloud credential detection agent using AWS CloudTrail and GuardDuty."""
import json
import subprocess
import sys
from datetime import datetime, timedelta
def aws_cli(args):
"""Execute AWS CLI command and return JSON output."""
cmd = ["aws"] + args + ["--output", "json"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0 and result.stdout.strip():
return json.loads(result.stdout)
return {"error": result.stderr.strip()} if result.returncode != 0 else {}
except Exception as e:
return {"error": str(e)}
def get_guardduty_credential_findings():
"""Get GuardDuty findings related to credential compromise."""
det_result = aws_cli(["guardduty", "list-detectors"])
detector_id = det_result.get("DetectorIds", [None])[0]
if not detector_id:
return {"error": "No GuardDuty detector found"}
credential_types = [
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"Discovery:IAMUser/AnomalousBehavior",
"InitialAccess:IAMUser/AnomalousBehavior",
"Persistence:IAMUser/AnomalousBehavior",
]
criteria = {"Criterion": {"type": {"Eq": credential_types}, "service.archived": {"Eq": ["false"]}}}
findings_result = aws_cli([
"guardduty", "list-findings",
"--detector-id", detector_id,
"--finding-criteria", json.dumps(criteria),
])
finding_ids = findings_result.get("FindingIds", [])
if not finding_ids:
return {"findings": [], "count": 0}
details = aws_cli(["guardduty", "get-findings", "--detector-id", detector_id, "--finding-ids"] + finding_ids[:25])
parsed = []
for f in details.get("Findings", []):
parsed.append({
"type": f.get("Type"),
"severity": f.get("Severity"),
"title": f.get("Title"),
"account": f.get("AccountId"),
"region": f.get("Region"),
"resource": f.get("Resource", {}).get("AccessKeyDetails", {}),
"action": f.get("Service", {}).get("Action", {}),
})
return {"count": len(parsed), "findings": parsed}
def query_cloudtrail_for_key(access_key_id, hours=24):
"""Query CloudTrail for all API calls made with a specific access key."""
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=hours)
result = aws_cli([
"cloudtrail", "lookup-events",
"--lookup-attributes", json.dumps([{"AttributeKey": "AccessKeyId", "AttributeValue": access_key_id}]),
"--start-time", start_time.isoformat() + "Z",
"--end-time", end_time.isoformat() + "Z",
"--max-results", "50",
])
events = []
for e in result.get("Events", []):
detail = json.loads(e.get("CloudTrailEvent", "{}"))
events.append({
"time": e.get("EventTime"),
"event_name": e.get("EventName"),
"source_ip": detail.get("sourceIPAddress"),
"user_agent": detail.get("userAgent", "")[:100],
"region": detail.get("awsRegion"),
"resources": e.get("Resources", []),
})
return {"access_key": access_key_id, "events": events, "total": len(events)}
def detect_anomalous_api_calls(access_key_id, hours=24):
"""Detect anomalous API patterns from a potentially compromised key."""
trail = query_cloudtrail_for_key(access_key_id, hours)
events = trail.get("events", [])
regions = set()
ips = set()
api_calls = {}
recon_apis = ["ListBuckets", "DescribeInstances", "ListUsers", "GetCallerIdentity",
"ListRoles", "ListAccessKeys", "DescribeRegions", "ListFunctions"]
recon_count = 0
for e in events:
if e.get("region"):
regions.add(e["region"])
if e.get("source_ip"):
ips.add(e["source_ip"])
name = e.get("event_name", "")
api_calls[name] = api_calls.get(name, 0) + 1
if name in recon_apis:
recon_count += 1
anomaly_score = 0
indicators = []
if len(regions) > 3:
anomaly_score += 30
indicators.append(f"Multi-region activity: {len(regions)} regions")
if len(ips) > 3:
anomaly_score += 25
indicators.append(f"Multiple source IPs: {len(ips)}")
if recon_count > 5:
anomaly_score += 25
indicators.append(f"Reconnaissance APIs: {recon_count} calls")
if any(api in api_calls for api in ["CreateUser", "CreateAccessKey", "AttachUserPolicy"]):
anomaly_score += 40
indicators.append("Persistence API calls detected")
return {
"access_key": access_key_id,
"anomaly_score": min(100, anomaly_score),
"indicators": indicators,
"unique_regions": list(regions),
"unique_ips": list(ips),
"top_apis": sorted(api_calls.items(), key=lambda x: x[1], reverse=True)[:15],
}
def revoke_iam_sessions(username):
"""Revoke all active sessions for an IAM user by adding inline deny policy."""
policy = json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"DateLessThan": {"aws:TokenIssueTime": datetime.utcnow().isoformat() + "Z"}
},
}],
})
return aws_cli([
"iam", "put-user-policy",
"--user-name", username,
"--policy-name", "RevokeOldSessions",
"--policy-document", policy,
])
def deactivate_access_key(access_key_id, username):
"""Deactivate a compromised access key."""
return aws_cli([
"iam", "update-access-key",
"--access-key-id", access_key_id,
"--user-name", username,
"--status", "Inactive",
])
def generate_report():
"""Generate a credential compromise detection report."""
return {
"timestamp": datetime.utcnow().isoformat() + "Z",
"guardduty_findings": get_guardduty_credential_findings(),
}
if __name__ == "__main__":
action = sys.argv[1] if len(sys.argv) > 1 else "report"
if action == "report":
print(json.dumps(generate_report(), indent=2, default=str))
elif action == "findings":
print(json.dumps(get_guardduty_credential_findings(), indent=2, default=str))
elif action == "trail" and len(sys.argv) > 2:
hours = int(sys.argv[3]) if len(sys.argv) > 3 else 24
print(json.dumps(query_cloudtrail_for_key(sys.argv[2], hours), indent=2, default=str))
elif action == "analyze" and len(sys.argv) > 2:
print(json.dumps(detect_anomalous_api_calls(sys.argv[2]), indent=2, default=str))
elif action == "deactivate" and len(sys.argv) > 3:
print(json.dumps(deactivate_access_key(sys.argv[2], sys.argv[3]), indent=2))
elif action == "revoke" and len(sys.argv) > 2:
print(json.dumps(revoke_iam_sessions(sys.argv[2]), indent=2))
else:
print("Usage: agent.py [report|findings|trail <key_id> [hours]|analyze <key_id>|deactivate <key_id> <user>|revoke <user>]")