Back to Intelligence

Exposed AWS Access Key Behind Beacon CRM Breach Affecting 1,500+ UK Charities — Detection, Key Hygiene, and Remediation Guide

SA
Security Arsenal Team
August 13, 2026
13 min read

Introduction

Beacon, a UK-based CRM provider serving the non-profit sector, has disclosed that a compromised AWS access key is the likely root cause of a data breach affecting more than 1,500 charities. The exposed data potentially includes donor records, beneficiary information, and operational data belonging to organizations that are, by their nature, some of the least resourced to absorb a security incident.

This is not a novel attack technique — and that is precisely the problem. Long-lived IAM access keys remain one of the most reliably abused credential types in cloud intrusions. Whether the key leaked through source code, a developer workstation, CI/CD logs, or a misconfigured storage artifact, the result is the same: a static credential that authenticates with the full permissions of the IAM principal it belongs to, usable from anywhere on the internet until it is explicitly revoked.

For defenders, this incident is a forcing function. If your organization — or any vendor in your supply chain holding your data — still relies on long-lived access keys for human users or workloads, you are carrying the same risk Beacon was. This post breaks down the attack pattern, provides ready-to-deploy detection content for key misuse, and walks through a concrete remediation plan for AWS credential hygiene.

Technical Analysis

What Happened

Per reporting from Infosecurity Magazine, Beacon's investigation concluded that a compromised AWS access key was the probable entry point. While Beacon has not published full forensic detail, the attack pattern for this class of incident is well understood and maps cleanly to MITRE ATT&CK:

  • Initial Access — T1078.004 (Valid Accounts: Cloud Accounts): The attacker obtains a valid AWS access key ID and secret access key. Common leakage vectors include keys committed to public or private Git repositories, keys hardcoded in application code or configuration files, keys exposed in CI/CD pipeline logs or environment variable dumps, keys harvested from compromised developer endpoints, and keys accidentally published in public S3 objects or AMIs.
  • Discovery — T1580 / T1526 (Cloud Infrastructure & Service Discovery): With a valid key, the attacker's first moves are almost always sts:GetCallerIdentity to confirm the key works, followed by iam:ListUsers, iam:ListAttachedUserPolicies, s3:ListAllMyBuckets, and similar enumeration to map the blast radius of the credential.
  • Collection & Exfiltration — T1530 / T1567.002 (Data from Cloud Storage / Exfiltration to Cloud Storage): For a CRM provider, the highest-value targets are RDS/Aurora snapshots, DynamoDB tables, and S3 buckets holding customer databases, exports, and backups. Bulk s3:GetObject calls, rds:CreateDBSnapshot followed by snapshot sharing, or dynamodb:Scan/ExportTableToPointInTime are the typical collection mechanics.
  • Persistence (optional) — T1098.001 (Additional Cloud Credentials): If the compromised key has IAM write permissions, attackers frequently mint their own keys via iam:CreateAccessKey on existing users to survive revocation of the original credential.

Why Long-Lived Keys Are the Core Weakness

An IAM access key has no concept of device posture, network location, MFA context (unless explicitly conditioned in policy), or expiry. It is a bearer token. AWS's own guidance, the CIS AWS Foundations Benchmark, and every major cloud security framework have pushed for years toward temporary credentials — IAM Roles Anywhere, instance profiles, IRSA (IAM Roles for Service Accounts) on EKS, and SSO-federated access via IAM Identity Center. Every long-lived key that exists is an incident waiting for a leak vector.

Affected Parties and Blast Radius

  • Primary: Beacon CRM and its ~1,500+ UK charity customers.
  • Secondary: Donors, beneficiaries, and service users whose personal data was processed by those charities — likely including sensitive categories given that many Beacon customers work in health, disability, and youth services. UK GDPR and Charity Commission notification obligations apply.
  • Tertiary lesson: Any organization whose SaaS vendors run multi-tenant workloads on AWS. Your vendor's key hygiene is your breach surface. This is a supply-chain data exposure event as much as it is a single-vendor incident.

Exploitation Status

Confirmed real-world compromise — this is an active incident, not a theoretical scenario. No CVE applies; this is a credential-exposure attack pattern, not a software vulnerability. AWS access key theft is consistently among the top initial access vectors observed in cloud IR engagements and in threat intel reporting on cloud-focused actors (e.g., campaigns tracked under Scattered Spider/UNC3944 activity and opportunistic key-scanning botnets that continuously crawl GitHub, Pastebin, and public S3 for leaked keys).

Detection & Response

The detections below focus on the behaviors that matter: anomalous use of access keys, enumeration patterns typical of a newly-compromised credential, and the presence of long-lived keys where they shouldn't exist. These assume CloudTrail is shipped to your SIEM — if it isn't, that is finding number one.

Sigma Rules

YAML
---
title: AWS IAM Access Key Created for Existing User
id: 3f8a2c41-7b9e-4d12-a6f5-8c1e2b4d9a07
status: experimental
description: Detects creation of a new AWS access key via CreateAccessKey, a common persistence technique after initial compromise of an IAM principal. Attackers who gain IAM write permissions mint keys on existing users to survive revocation of the original credential.
references:
  - https://attack.mitre.org/techniques/T1098/001/
  - https://www.infosecurity-magazine.com/news/exposed-aws-key-data-charities/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.persistence
  - attack.t1098.001
logsource:
  product: aws
  service: cloudtrail
detection:
  selection:
    eventSource: iam.amazonaws.com
    eventName: CreateAccessKey
falsepositives:
  - Legitimate key rotation by administrators or automation
  - Onboarding of new service accounts
level: high
---
title: AWS Credential Validation and Enumeration from Unauthenticated Context
id: 9c1d4e72-3a58-4f6b-b2d1-5e7c8a3f1b94
status: experimental
description: Detects the classic reconnaissance sequence of a newly compromised AWS access key - GetCallerIdentity followed by IAM or S3 enumeration. Threat actors validate a leaked key with sts:GetCallerIdentity before mapping permissions. High volume of read-only IAM/S3 API calls from a single identity in a short window is a strong compromise indicator.
references:
  - https://attack.mitre.org/techniques/T1580/
  - https://attack.mitre.org/techniques/T1526/
  - https://www.infosecurity-magazine.com/news/exposed-aws-key-data-charities/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.discovery
  - attack.t1580
  - attack.t1526
logsource:
  product: aws
  service: cloudtrail
detection:
  selection_validation:
    eventSource: sts.amazonaws.com
    eventName: GetCallerIdentity
  selection_enumeration:
    eventName:
      - ListUsers
      - ListRoles
      - ListAttachedUserPolicies
      - ListAccessKeys
      - GetAccountAuthorizationDetails
      - ListBuckets
      - ListAllMyBuckets
      - DescribeDBInstances
      - ListTables
  condition: selection_validation or selection_enumeration
falsepositives:
  - Cloud management tooling, Terraform runs, and CSPM platforms performing inventory
  - Tune by excluding known automation user agents and source IPs
level: medium
---
title: AWS API Activity from Non-Corporate or Tor Exit Network
id: 5b7e9a13-2c48-4d6f-91a3-7d4e6f2c8b50
status: experimental
description: Detects AWS management-plane API calls where the source IP belongs to a known Tor exit node, anonymous VPN provider, or a network with no history of legitimate use by the principal. Compromised access keys are almost always replayed from attacker-controlled infrastructure outside the corporate egress range.
references:
  - https://attack.mitre.org/techniques/T1078/004/
  - https://www.infosecurity-magazine.com/news/exposed-aws-key-data-charities/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.initial_access
  - attack.t1078.004
logsource:
  product: aws
  service: cloudtrail
detection:
  selection:
    eventType: AwsApiCall
  filter_corporate_egress:
    sourceIPAddress|contains:
      - '203.0.113.'
      - '198.51.100.'
  filter_aws_internal:
    sourceIPAddress|endswith: '.amazonaws.com'
  condition: selection and not 1 of filter_*
falsepositives:
  - Administrators working remotely from home ISPs or mobile hotspots
  - Third-party MSP access - baseline and allowlist known external management IPs
level: high

Note on the third rule: Replace the placeholder corporate egress ranges with your actual NAT gateway, VPN concentrator, and office egress IPs. Without that tuning it will be noisy; with it, it is one of the highest-signal cloud detections you can run.

KQL — Microsoft Sentinel (CloudTrail ingested via AWS connector or CEF/Syslog)

This query hunts for the full behavioral pattern of a compromised key: an IAM principal suddenly making validation + enumeration + bulk data-access calls from an IP address with no prior history for that identity, or from an IP flagged by threat intelligence.

KQL — Microsoft Sentinel / Defender
// Hunt: AWS access key misuse - new source IP performing enumeration and bulk reads
let lookback = 14d;
let window = 1d;
let KnownIPs =
    AWSCloudTrail
    | where TimeGenerated > ago(lookback)
    | where EventTypeName == "AwsApiCall"
    | summarize by UserIdentityPrincipalId = tostring(UserIdentityPrincipalId), SourceIpAddress;
AWSCloudTrail
| where TimeGenerated > ago(window)
| where EventTypeName == "AwsApiCall"
| extend EventName = tostring(EventName)
| where EventName in (
    "GetCallerIdentity", "ListUsers", "ListAccessKeys",
    "GetAccountAuthorizationDetails", "ListBuckets",
    "GetObject", "CreateAccessKey", "CreateDBSnapshot",
    "ShareDBSnapshot", "ExportTableToPointInTime", "Scan")
| extend Principal = tostring(UserIdentityPrincipalId)
| join kind=leftanti (KnownIPs | project-rename Principal = UserIdentityPrincipalId, SourceIpAddress)
    on Principal, SourceIpAddress
| summarize
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    EventCount = count(),
    Events = make_set(EventName, 20),
    UserAgents = make_set(tostring(UserAgent), 5)
    by Principal, tostring(UserIdentityArn), SourceIpAddress, AWSRegion
| where EventCount >= 5
| sort by EventCount desc

If your CloudTrail arrives via Syslog/CEF rather than the native AWS connector, adapt the table name (CommonSecurityLog / Syslog) and parse EventName from the message payload — the detection logic is identical. Pair this with Sentinel's built-in Threat Intelligence matching against SourceIpAddress to catch replay from known malicious infrastructure.

Velociraptor VQL — Hunting for Leaked Keys on Endpoints and in Repos

The Beacon root cause was almost certainly a key stored somewhere it should not have been. This artifact hunts developer endpoints for AWS credential material sitting in predictable locations — .aws/credentials, environment files, and shell histories — which is where leaked keys originate before they ever reach an attacker.

VQL — Velociraptor
-- Hunt for exposed AWS access keys and credential files on endpoints
-- Targets: ~/.aws/credentials, .env files, and shell history referencing keys
SELECT
    FullPath,
    Size,
    Mtime,
    read_file(filename=FullPath, length=4096) AS ContentPreview
FROM glob(
    globs=[
        'C:/Users/*/.aws/credentials',
        '/home/*/.aws/credentials',
        '/root/.aws/credentials',
        'C:/Users/*/.aws/config',
        '/home/*/.bash_history',
        '/home/*/.zsh_history',
        'C:/Users/*/.bash_history',
        'C:/Users/*/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt'
    ]
)
WHERE ContentPreview =~ 'AKIA[0-9A-Z]{16}|aws_secret_access_key|aws_access_key_id'

Follow this with a repo-scanning sweep (gitleaks or trufflehog) against all organization repositories — keys committed to version control remain recoverable from git history even after deletion from HEAD.

Remediation Script — Audit and Remediate Long-Lived AWS Access Keys

The following Bash script uses the AWS CLI to enumerate every IAM access key in an account, flag keys older than 90 days, flag keys unused for 90+ days, and generate a rotation/deactivation worklist. Run it with read-only audit credentials first; the deactivation step is commented out by default.

Bash / Shell
#!/usr/bin/env bash
# AWS IAM Access Key Hygiene Audit
# Identifies aged and stale access keys - the Beacon breach root cause class
set -euo pipefail

MAX_AGE_DAYS=90
MAX_UNUSED_DAYS=90
NOW_EPOCH=$(date +%s)
REPORT="aws_key_audit_$(date +%Y%m%d_%H%M%S).csv"

echo "User,KeyId,Status,AgeDays,LastUsedDaysAgo,Finding" > "$REPORT"

echo "[*] Enumerating IAM users and access keys..."
for USER in $(aws iam list-users --query 'Users[].UserName' --output text); do
  for KEY in $(aws iam list-access-keys --user-name "$USER" \
      --query 'AccessKeyMetadata[].AccessKeyId' --output text); do

    META=$(aws iam list-access-keys --user-name "$USER" \
      --query "AccessKeyMetadata[?AccessKeyId=='$KEY']" --output json)
    CREATED=$(echo "$META" | jq -r '.[0].CreateDate')
    STATUS=$(echo "$META" | jq -r '.[0].Status')

    CREATED_EPOCH=$(date -d "$CREATED" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%S" "${CREATED%%.*}" +%s)
    AGE_DAYS=$(( (NOW_EPOCH - CREATED_EPOCH) / 86400 ))

    LAST_USED=$(aws iam get-access-key-last-used --access-key-id "$KEY" \
      --query 'AccessKeyLastUsed.LastUsedDate' --output text 2>/dev/null || echo "None")
    if [ "$LAST_USED" = "None" ] || [ -z "$LAST_USED" ]; then
      UNUSED_DAYS="never-used"
    else
      LAST_EPOCH=$(date -d "$LAST_USED" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%S" "${LAST_USED%%.*}" +%s)
      UNUSED_DAYS=$(( (NOW_EPOCH - LAST_EPOCH) / 86400 ))
    fi

    FINDING=""
    [ "$AGE_DAYS" -gt "$MAX_AGE_DAYS" ] && FINDING="KEY_OLDER_THAN_${MAX_AGE_DAYS}D"
    if [ "$UNUSED_DAYS" != "never-used" ] && [ "$UNUSED_DAYS" -gt "$MAX_UNUSED_DAYS" ]; then
      FINDING="${FINDING:+$FINDING,}STALE_${MAX_UNUSED_DAYS}D_PLUS"
    fi
    [ "$UNUSED_DAYS" = "never-used" ] && FINDING="${FINDING:+$FINDING,}NEVER_USED"

    echo "$USER,$KEY,$STATUS,$AGE_DAYS,$UNUSED_DAYS,${FINDING:-OK}" | tee -a "$REPORT"
  done
done

echo ""
echo "[*] Audit complete: $REPORT"
echo "[!] Flagged keys require rotation or deactivation."

echo ""
echo "[*] Checking for CloudTrail multi-region coverage (required for detection)..."
aws cloudtrail describe-trails --query \
  'trailList[?IsMultiRegionTrail==`false`].[Name,HomeRegion]' --output table

echo ""
echo "[*] Checking whether IAM credential report is current..."
aws iam generate-credential-report >/dev/null 2>&1 || true
echo "    Download with: aws iam get-credential-report --query 'Content' --output text | base64 -d"

# UNCOMMENT ONLY AFTER REVIEW - deactivates flagged stale/aged keys
# while read -r line; do
#   IFS=',' read -r USER KEY STATUS AGE UNUSED FINDING <<< "$line"
#   if [[ "$FINDING" == *"STALE"* || "$FINDING" == *"NEVER_USED"* ]]; then
#     echo "[!] Deactivating $KEY for $USER"
#     aws iam update-access-key --user-name "$USER" --access-key-id "$KEY" --status Inactive
#   fi
# done < <(tail -n +2 "$REPORT")

Remediation

Immediate Actions (First 24–72 Hours)

  1. Inventory every IAM access key in every AWS account. Use the script above plus aws iam get-credential-report. Any key belonging to a human user is a finding. Any key older than 90 days without documented rotation is a finding.
  2. Deactivate, then delete, stale and unused keys. Deactivate first, monitor CloudTrail for failed calls that identify dependent workloads, then delete after a burn-in period (typically 7–14 days).
  3. Rotate all remaining active keys on a defined schedule and record rotation dates. If you cannot answer "when was this key created and last rotated," assume it is compromised-class risk.
  4. Hunt CloudTrail historically. Query the last 90 days (or your retention maximum) for CreateAccessKey, GetCallerIdentity from unknown IPs, console logins without MFA, and bulk GetObject/CreateDBSnapshot/ShareDBSnapshot activity. The KQL above is your starting point.
  5. Scan all source code and CI/CD systems for committed keys using gitleaks or trufflehog, including full git history. Rotate every key found, even if "it was deleted in the next commit" — it is still in history.

Structural Hardening (30–90 Days)

  1. Eliminate long-lived credentials for humans. Migrate all human access to IAM Identity Center (SSO) with short-lived session credentials and enforced MFA. AWS CLI v2 supports SSO natively; there is no remaining justification for human access keys.
  2. Replace workload keys with roles. EC2 instance profiles, ECS task roles, Lambda execution roles, EKS IRSA, and IAM Roles Anywhere for on-premises/hybrid workloads. Target: zero long-lived access keys in the environment.
  3. Enforce with Service Control Policies. Apply an SCP at the AWS Organizations level denying iam:CreateAccessKey except for an explicitly break-glass role. This converts policy into a hard control an attacker (or well-meaning developer) cannot bypass.
  4. Add aws:SourceIp and aws:SecureTransport conditions to sensitive IAM policies where network-restricted access is feasible, so a leaked key fails outside your egress ranges.
  5. Enable and centralize CloudTrail (multi-region, organization trail), GuardDuty, and Security Hub. GuardDuty's UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration and Discovery:IAMUser/AnomalousBehavior finding types are purpose-built for this exact attack class — but only if someone is watching them.
  6. Deploy AWS's automated exposure response. AWS now scans public GitHub commits for AWS key patterns and applies the AWSCompromisedKeyQuarantine policy to exposed keys — verify this is functioning in your accounts, and treat any quarantine event as a full IR trigger, not an inconvenience.

Vendor and Supply-Chain Actions

  1. Beacon customers: Confirm your organization's data exposure status directly with Beacon, review your own UK GDPR Article 33/34 obligations (ICO notification within 72 hours where risk to individuals exists), and assess whether donor/beneficiary notification is required. The ICO and Charity Commission both expect documented decisions.
  2. Every organization: Add cloud credential hygiene to vendor assessments. Ask SaaS providers holding your data: Do you use long-lived IAM access keys? What is your key rotation policy? Do you have organization-level CloudTrail and GuardDuty? A vendor's "we use AWS" answer tells you nothing; these specifics do.
  3. Review DPAs and breach notification SLAs with CRM and data-processing vendors. 1,500 charities discovered their vendor's AWS hygiene problem as their own breach. Contractual notification timelines and security attestations (ISO 27001, SOC 2 Type II with cloud-specific controls) are the leverage you have before an incident, not after.

Reference Guidance

Related Resources

Security Arsenal Incident Response Services AlertMonitor Platform Book a SOC Assessment incident-response Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.