Cloud Security Office Hours Banner

Codefinger S3 Ransomware 2025

Step-by-step kill chain mapped to MITRE ATT&CK Cloud, sourced from official post-mortems and primary technical analyses.

January 2025 Critical AWS S3

Codefinger / S3 Ransomware - Stolen AWS Keys → Valid Access → Bucket Enum → SSE-C Encryption → 7-Day Delete + BTC Ransom

A financially motivated actor tracked as Codefinger used compromised or publicly exposed AWS access keys to encrypt victims' S3 buckets using AWS's own Server-Side Encryption with Customer-Provided Keys (SSE-C). Because the attacker generates and holds the AES-256 key locally, and AWS stores only an HMAC of that key, victims cannot decrypt their data without paying. Codefinger applied S3 Lifecycle policies to delete the encrypted objects within seven days as ransom pressure and demanded payment in Bitcoin. No AWS vulnerability was exploited - the entire campaign relied on abusing valid credentials and native AWS features. Halcyon, which disclosed the campaign on January 13, 2025, observed at least two victim organisations.

SSE-C abused as the encryption primitive
7 days until S3 Lifecycle deletes the data
AES-256 attacker-held key - no server-side copy
Threat actor: Codefinger (financially motivated, Bitcoin ransom)
📄 Halcyon - Abusing AWS Native Services (SSE-C) ↗ 📄 Arctic Wolf - Ransomware Encrypting S3 with SSE-C ↗
🕵️ Credential Acquisition - Exposed AWS Keys
01
Attacker obtained AWS access keys from public exposure or prior compromise
T1552 - Unsecured Credentials

Codefinger sourced valid AWS access key IDs and secret access keys that had been publicly disclosed or otherwise compromised. Long-lived IAM access keys routinely leak through committed source code, exposed config files, CI logs, and public buckets, and are harvested by scanners within minutes of exposure. Halcyon assessed the actor used keys with permissions to read and write S3 objects, so no privilege escalation or AWS vulnerability was needed - possession of a working key pair was sufficient.

Credential type: Long-lived AWS IAM access key ID + secret access key
Common leak paths: Git commits, public S3 buckets, CI/CD logs, hardcoded app config
Why it worked: Keys never rotated · No expiry · No detection of key use from new sources
Exposed KeysAWS Access KeyCredential LeakT1552
Initial Access - Valid AWS Credentials
02
Attacker authenticated to the victim AWS account with the stolen keys
T1078.004 - Valid Accounts: Cloud Accounts

Using the compromised keys, Codefinger authenticated to the victim's AWS account as a legitimate principal via the AWS SDK, CLI, or API. Because the request came from a valid IAM identity, it passed authentication with no anomaly. The keys carried s3:GetObject and s3:PutObject permissions - enough to read existing objects and overwrite them with encrypted copies. AWS CloudTrail recorded the API activity, but by default no alert fires on legitimate-looking S3 calls from a new IP or region.

Access method: AWS CLI / SDK / S3 API calls signed with the stolen key pair
Required IAM perms: s3:GetObject, s3:PutObject on target buckets
Detection gap: Valid-credential access from a new IP raised no default alert
Valid AccountsIAMNo MFA on KeysT1078.004
🔍 Discovery - Bucket and Object Enumeration
03
Attacker enumerated buckets and objects to identify high-value targets
T1619 - Cloud Storage Object Discovery

With valid access, Codefinger enumerated the account's S3 footprint using native API calls to list buckets and objects, mapping which buckets held valuable data and were reachable with the key's permissions. These are ordinary read operations that generate no exploitation signal. The output told the attacker exactly which objects to target for encryption in the impact phase.

Enumeration calls: s3:ListAllMyBuckets, s3:ListBucket (list objects per bucket)
Goal: Identify readable/writable buckets holding business-critical data
Typical targets: Backups, application data, customer records, media assets
Bucket EnumerationListBucketDiscoveryT1619
💰 Impact - SSE-C Encryption
04
Objects re-uploaded encrypted with an attacker-held AES-256 SSE-C key
T1486 - Data Encrypted for Impact

Codefinger encrypted each target object by re-writing it with Server-Side Encryption using Customer-Provided Keys (SSE-C). The attacker generated an AES-256 key locally and supplied it on the PutObject request via the x-amz-server-side-encryption-customer-algorithm and x-amz-server-side-encryption-customer-key headers. AWS performs the encryption but does not store the key - CloudTrail logs only an HMAC of it, which is insufficient to reconstruct the key. Once complete, the data is unrecoverable without the attacker's key, even by AWS. This turns a native, legitimate encryption feature into a ransomware primitive with no malware deployed.

Header 1: x-amz-server-side-encryption-customer-algorithm: AES256
Header 2: x-amz-server-side-encryption-customer-key: <attacker AES-256 key>
Key custody: Generated and stored locally by attacker - AWS keeps only an HMAC
Result: Objects unrecoverable without the attacker key - AWS cannot decrypt them
SSE-CAES-256PutObjectT1486
📤 Extortion - Lifecycle Deletion + Bitcoin Ransom
05
7-day S3 Lifecycle deletion set as pressure, ransom note demands Bitcoin
T1657 - Financial Theft

To force fast payment, Codefinger applied an S3 Lifecycle policy configured to permanently delete the encrypted objects within seven days. The attacker left a ransom note in each affected bucket with a Bitcoin address and payment instructions, and warned victims not to alter account permissions or attempt recovery. Because AWS never held the SSE-C key, victims who did not pay before the lifecycle timer expired lost the data outright. Halcyon reported at least two organisations were hit.

Pressure mechanism: S3 Lifecycle policy - permanent object deletion within 7 days
Ransom demand: Bitcoin payment for the AES-256 SSE-C decryption key
Ransom note: Left in-bucket · warned against changing IAM permissions
Confirmed victims: At least 2 organisations (Halcyon, Jan 2025)
Lifecycle DeletionBitcoin RansomExtortionT1657

🛡 How to Defend Against This Chain

Restrict or disable SSE-C where you do not need it. Most workloads use SSE-S3 or SSE-KMS, not SSE-C. Add an S3 bucket policy or SCP that denies s3:PutObject when the request carries the x-amz-server-side-encryption-customer-algorithm header, so an attacker cannot encrypt your objects with a key you do not control. This closes the exact primitive Codefinger abused.
Eliminate long-lived IAM access keys and rotate what remains. Prefer short-lived credentials via IAM roles, SSO, and STS. Where keys are unavoidable, rotate them regularly, scope them tightly, and scan your repos, CI logs, and buckets for exposed keys. Nearly every cloud ransomware case starts with a leaked long-lived IAM key.
Apply least privilege to S3 permissions. Grant s3:PutObject and s3:GetObject only to the principals and buckets that require them. A leaked key with narrow, read-only scope cannot re-encrypt your data. Audit for over-permissioned keys and wildcard S3 policies.
Enable S3 Versioning and Object Lock, and keep isolated backups. Versioning preserves prior object versions so an SSE-C overwrite does not destroy the original, and Object Lock in compliance mode blocks deletion within a retention window - defeating the 7-day lifecycle pressure. Maintain backups in a separate account the leaked key cannot reach.
Alert on SSE-C usage and lifecycle changes in CloudTrail. Monitor CloudTrail for PutObject calls carrying the customer-key headers, for PutBucketLifecycleConfiguration events, and for unfamiliar source IPs on existing keys. Route these to your SIEM so unusual S3 encryption or new deletion policies trigger an immediate response.

Related defense topics