|
| 1 | +[metadata] |
| 2 | +creation_date = "2026/04/21" |
| 3 | +integration = ["aws"] |
| 4 | +maturity = "production" |
| 5 | +updated_date = "2026/04/21" |
| 6 | + |
| 7 | +[rule] |
| 8 | +author = ["Elastic"] |
| 9 | +description = """ |
| 10 | +Detects AWS access keys that are used from both GitHub Actions CI/CD infrastructure and non-CI/CD infrastructure. |
| 11 | +This pattern indicates potential credential theft where an attacker who has stolen AWS credentials configured as GitHub |
| 12 | +Actions secrets and is using them from their own infrastructure. |
| 13 | +""" |
| 14 | +false_positives = [ |
| 15 | + """ |
| 16 | + AWS credentials legitimately shared between GitHub Actions and another Microsoft/Azure service |
| 17 | + may trigger this rule. Verify whether the non-CI/CD source IP is expected for the workload. |
| 18 | + """, |
| 19 | + """ |
| 20 | + GitHub Actions self-hosted runners running on non-Microsoft/Amazon/Google infrastructure will |
| 21 | + appear as suspicious. Add the ASN of your self-hosted runner infrastructure to the is_cicd_infra |
| 22 | + allowlist. |
| 23 | + """, |
| 24 | +] |
| 25 | +from = "now-7d" |
| 26 | +interval = "1h" |
| 27 | +language = "esql" |
| 28 | +license = "Elastic License v2" |
| 29 | +name = "AWS Credentials Used from GitHub Actions and Non-CI/CD Infrastructure" |
| 30 | +note = """## Triage and analysis |
| 31 | +
|
| 32 | +### Investigating AWS Credentials Used from GitHub Actions and Non-CI/CD Infrastructure |
| 33 | +
|
| 34 | +This rule detects when an AWS access key appears in CloudTrail from both GitHub Actions runners |
| 35 | +(identified by Microsoft ASN or the `github-actions` user agent string) and from infrastructure |
| 36 | +outside the expected CI/CD provider ASNs. This is a strong indicator that AWS credentials stored |
| 37 | +as GitHub repository or organization secrets have been exfiltrated and are being used by an |
| 38 | +attacker from their own infrastructure. |
| 39 | +
|
| 40 | +### Possible investigation steps |
| 41 | +
|
| 42 | +- Identify which GitHub repository owns the credential by cross-referencing the access key ID with |
| 43 | + your GitHub Actions workflow configurations and AWS IAM user/role assignments. |
| 44 | +- Review the suspicious source IPs and ASNs — residential ISPs, VPN providers, or budget hosting |
| 45 | + providers are high-confidence indicators of credential theft. |
| 46 | +- Check the actions performed from the suspicious source — `sts:GetCallerIdentity` followed by |
| 47 | + enumeration calls (`ListBuckets`, `DescribeInstances`, `ListUsers`) is a common attacker recon |
| 48 | + pattern after credential theft. |
| 49 | +- Review the user agent strings from the suspicious source — `aws-cli` or `boto3` from a non-runner |
| 50 | + IP confirms manual/scripted usage outside CI/CD. |
| 51 | +- Check GitHub audit logs for recent workflow changes, new collaborators, or secret access events |
| 52 | + that could indicate how the credential was stolen. |
| 53 | +- Determine if the credential is a long-lived IAM user key or a temporary STS session — temporary |
| 54 | + credentials from `AssumeRoleWithWebIdentity` (OIDC) are less likely to be exfiltrated but still |
| 55 | + possible. |
| 56 | +
|
| 57 | +### Response and remediation |
| 58 | +
|
| 59 | +- Immediately rotate the compromised AWS access key in IAM and update the GitHub repository/org secret. |
| 60 | +- Review and revoke any resources created or modified by the suspicious source IP using CloudTrail |
| 61 | + event history filtered by the access key ID. |
| 62 | +- Audit the GitHub repository for signs of compromise — check for unauthorized workflow modifications, |
| 63 | + new secrets, or suspicious pull requests that could have exfiltrated the credential. |
| 64 | +- Implement OIDC-based authentication (`aws-actions/configure-aws-credentials` with `role-to-assume`) |
| 65 | + instead of long-lived access keys to eliminate the credential theft vector entirely. |
| 66 | +- If using OIDC, add IP condition policies to the IAM role trust policy to restrict |
| 67 | + `AssumeRoleWithWebIdentity` to known GitHub runner IP ranges. |
| 68 | +- Enable GitHub's secret scanning and push protection to detect accidental credential exposure in |
| 69 | + code or logs. |
| 70 | +""" |
| 71 | +references = [ |
| 72 | + "https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services", |
| 73 | + "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html", |
| 74 | +] |
| 75 | +risk_score = 73 |
| 76 | +rule_id = "b8c7d6e5-f4a3-4b2c-9d8e-7f6a5b4c3d2e" |
| 77 | +severity = "high" |
| 78 | +tags = [ |
| 79 | + "Domain: Cloud", |
| 80 | + "Data Source: AWS", |
| 81 | + "Data Source: Amazon Web Services", |
| 82 | + "Data Source: AWS CloudTrail", |
| 83 | + "Data Source: AWS IAM", |
| 84 | + "Use Case: Threat Detection", |
| 85 | + "Tactic: Initial Access", |
| 86 | + "Tactic: Lateral Movement", |
| 87 | + "Resources: Investigation Guide", |
| 88 | +] |
| 89 | +timestamp_override = "event.ingested" |
| 90 | +type = "esql" |
| 91 | + |
| 92 | +query = ''' |
| 93 | +from logs-aws.cloudtrail-* metadata _id, _version, _index |
| 94 | +
|
| 95 | +| WHERE event.dataset == "aws.cloudtrail" |
| 96 | + AND aws.cloudtrail.user_identity.access_key_id IS NOT NULL |
| 97 | + AND @timestamp >= NOW() - 7 days |
| 98 | + AND source.as.organization.name IS NOT NULL |
| 99 | +
|
| 100 | +// AWS API key used from github actions |
| 101 | +| EVAL is_aws_github = user_agent.original LIKE "*aws-credentials-for-github-actions" |
| 102 | +
|
| 103 | +// non CI/CD related ASN |
| 104 | +| EVAL is_not_cicd_infra = not source.as.organization.name IN ("Microsoft Corporation", "Amazon.com, Inc.", "Amazon Technologies Inc.", "Google LLC") |
| 105 | +
|
| 106 | +| STATS Esql.is_github_aws_key = MAX(CASE(is_aws_github, 1, 0)), |
| 107 | + Esql.has_suspicious_asn = MAX(CASE(is_not_cicd_infra, 1, 0)), |
| 108 | + Esql.last_seen_suspicious_asn = MAX(CASE(is_not_cicd_infra, @timestamp, NULL)), |
| 109 | + Esql.source_ip_values = VALUES(source.address), |
| 110 | + Esql.source_asn_values = VALUES(source.as.organization.name) BY aws.cloudtrail.user_identity.access_key_id, user.name, cloud.account.id |
| 111 | +
|
| 112 | +// AWS API key tied to a GH action used from unusual ASN (non CI/CD infra) |
| 113 | +| WHERE Esql.is_github_aws_key == 1 AND Esql.has_suspicious_asn == 1 |
| 114 | +
|
| 115 | + // avoid alert duplicates within 1h interval |
| 116 | + AND Esql.last_seen_suspicious_asn >= NOW() - 1 hour |
| 117 | +
|
| 118 | +| KEEP user.name, aws.cloudtrail.user_identity.access_key_id, Esql.* |
| 119 | +''' |
| 120 | + |
| 121 | +[rule.investigation_fields] |
| 122 | +field_names = [ |
| 123 | + "aws.cloudtrail.user_identity.access_key_id", |
| 124 | + "user.name" |
| 125 | +] |
| 126 | + |
| 127 | +[[rule.threat]] |
| 128 | +framework = "MITRE ATT&CK" |
| 129 | + |
| 130 | +[[rule.threat.technique]] |
| 131 | +id = "T1078" |
| 132 | +name = "Valid Accounts" |
| 133 | +reference = "https://attack.mitre.org/techniques/T1078/" |
| 134 | + |
| 135 | +[[rule.threat.technique.subtechnique]] |
| 136 | +id = "T1078.004" |
| 137 | +name = "Cloud Accounts" |
| 138 | +reference = "https://attack.mitre.org/techniques/T1078/004/" |
| 139 | + |
| 140 | +[rule.threat.tactic] |
| 141 | +id = "TA0001" |
| 142 | +name = "Initial Access" |
| 143 | +reference = "https://attack.mitre.org/tactics/TA0001/" |
| 144 | + |
| 145 | + |
| 146 | +[[rule.threat]] |
| 147 | +framework = "MITRE ATT&CK" |
| 148 | + |
| 149 | +[[rule.threat.technique]] |
| 150 | +id = "T1550" |
| 151 | +name = "Use Alternate Authentication Material" |
| 152 | +reference = "https://attack.mitre.org/techniques/T1550/" |
| 153 | + |
| 154 | +[[rule.threat.technique.subtechnique]] |
| 155 | +id = "T1550.001" |
| 156 | +name = "Application Access Token" |
| 157 | +reference = "https://attack.mitre.org/techniques/T1550/001/" |
| 158 | + |
| 159 | +[rule.threat.tactic] |
| 160 | +id = "TA0008" |
| 161 | +name = "Lateral Movement" |
| 162 | +reference = "https://attack.mitre.org/tactics/TA0008/" |
0 commit comments