Encrypting buckets for compliance and ransom - How Attackers Can Use KMS to Ransomware S3 Buckets

A successful ransomware attack is the culmination of numerous steps by a determined attacker: gaining initial access to the victim’s environment, reaching enumerating level of privilege to identify se

Intro

A successful ransomware attack is the culmination of numerous steps by a determined attacker: gaining initial access to the victim’s environment, reaching enumerating level of privilege to identify sensitive data, escalating the privileges to gain access to those data, exfiltrating, encrypting and destroying the original data and finally asking for the ransom.

The encryption process itself is straightforward. After the data is accessed, a cryptographic key (most of the time an asymmetric key) is used and the original data is destroyed in a way that cannot be recovered. That requires the attacker to have the resources to store the plaintext data and the encrypted one, download and upload the files without triggering any bandwidth limit alert before asking for the ransom. What if that was not necessary and the attacker could only use what was within the victim’s infrastructure, while nullifying them any access to the data? Remember, cloud is someone else’s computer, so every resource is hosted, managed and given access to you by the cloud provider.

Creativity is key when finding new ways to attack a target. One should find new ways to achieve the goal with little detection and the least usage of resources. While researching, Permiso found several ways to abuse features allowed by AWS KMS Keys to encrypt the data on a target's buckets, making them inaccessible. There are some ways to achieve it and all of them have methods on how to defend or prevent the attack. This blog will try to go through all of them, from an attacker’s perspective, giving the attack methodology and all the ways a defender can mitigate them.

Setting the stage

Let's put our self in the shoes of a Threat Actor. Actually, let's raise the stakes. Let's play a game of chess in between a team of Threat Actors and a team of Security Engineers. And to do that, we need to set some standards.

Threat Actors:

  • Are financially motivated: so any outcome should involve some payment from a specific party

  • Are not financed by anybody: meaning no nation state funded team, no competitor funded. This means, the resources are thin. We are talking about a couple laptops and probably some free-tier cloud resources

Security Engineers:

  • The company is hosting its resources on AWS

  • Work at a company with a normalish Cybersecurity Investment, meaning they are using services and features that AWS is offering, like CloudTrail, GuardDuty, Resource/IAM/Service Control Policies, but do not have any other security system that monitors malicious activity, as well as detects/prevents attacks.

  • The company's data on S3 Buckets are important to the business and touching them will impact on the company's business continuation.

The whole blog will go into what a cat and mouse game, showing how the Threat Actors will try to lock/encrypt the data and how the target has/can prevent them.

Targeting S3 Bucket Data

For many organizations, some of the most sensitive data resides in S3 buckets. AWS S3 (Amazon Simple Storage Service) provides storage, access, backup and encryption of the objects inside it. Encrypting the data ensures denial of unauthorized access, since whoever has the key has the the access to the data. If this key is controlled by an unauthorized party, this encryption can result in data being held ransom by a threat actor.

The chart below illustrates the most basic Ransomware attack that can happen on an S3 Bucket:

Presumably, at this point, the threat actor has full access to the data. Also, assuming the victim organization doesn’t have backups that couldn’t be accessed by the threat actors, they might need to comply with the threat actor.

If the ransom goes unpaid, the data can either get public exposed, sold or even destroyed. The problem the attackers have is the storage. They are dealing with hundreds of Terabytes of data, which which makes the process of downloading, encrypting and uploading very time consuming. Besides that, the sheer volume of s3:GetObject calls, which though data plane objects are rarely logged, could be enough to draw attention to the attacker.

One way to reduce the number of steps would be to skip the process of downloading the bucket objects, and re-uploading encrypted objects, is to simply not do them, but use AWS KMS (Key Management Service) instead.

KMS Key Policy

KMS is a service provided by AWS, which allows for an ease of the Encryption Process for different data stored or provided by other services. This includes S3 Bucket Objects too. For S3 Objects, KMS will encrypt them inside the bucket, skipping the need for download.

Like many AWS resources, a KMS Key can contain a Resource Policy which defines the Users, Roles, or Services that can access the key, considered as Principals in the policy. A Resource Policy, differently from an IAM Policy, will only be applied to the specific resource and will define the access that different identities will have on the resource.

During testing, I found out that KMS Policy will only be applied to the identities added on the Policy, meaning, everyone else will be denied. Even root account. This means the attacker can abuse this feature to encrypt using a KMS Key and then deny access to all other identities.

The policy below only best describes how only allowing testuser access to the key and blocking any other identity from accessing it.

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
		    "arn:aws:iam::012345678912:user/testuser"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        }
    ]
}

This means, KMS offers an Privilege-based Access Control on the data, aside from also encrypting them. If an identity with no access to the KMS Key, tries to access the data, they will not be allowed to:

The privileges needed on the key are based on what the activity is:

  • If the identity wants to download files, the identity should have access to kms:Decrypt besides the s3:GetObject or s3:CopyObject, so they can download a decrypted file

  • If the identity wants to upload files, the identity should have access to kms:Encrypt besides the s3:PutObject or s3:CopyObject, so they can download a Encrypted file

  • The KMS Key does not affect deleting an object, meaning if an identity has access to run s3:DeleteObject and not have access to the KMS Key, they will still be able to delete the object

Encrypting S3 Buckets

S3 Bucket Objects can be encrypted using KMS Keys to offer a layer of protection against unauthorized access. There are 2 ways to achieve this:

  • By configuring the encryption on the bucket (which is the better way), which will prevent uploading or downloading objects unless the identity requesting has access to the Key. In this case, s3:PutBucketEncryptionConfiguration permission is needed to attach the KMS key to the bucket. After that, any s3:GetObject s3:PutObject, s3:DeleteObject or s3:CopyObject will require access to the key.

  • By configuring each object encryption individually, which will allow unencrypted files upload to the bucket, as well as download of files without encryption configuration, but will require access to the key in case the identity will require upload or download of encrypted files. That means, any s3:GetObject s3:PutObject, s3:DeleteObject or s3:CopyObject will require access to the key.

Attack Scenario 1: Locking the data using a target owned KMS Key with a locked down Policy

Considering everything, all the attacker need to do is to just create a KMS Key only accessible by the identity they have compromised, or modify the policy of a current KMS Key and lock the data.

To do that, they will need at least access to these Permissions:

  • kms:CreateKey or kms:PutKeyPolicy

  • s3:PutBucketEncryptionConfiguration

  • (s3:GetObject and s3:PutObject) or s3:CopyObject

While this scenario works, it requires that KMS Key to exists in the same account and region as the S3 Bucket. The victim by default would have access to the KMS Key, since the key is inside their own infrastructure. The attacker would need a way to lock access to the key too. And they can utilize KMS Key Policy for this.

Bottleneck nr.1: Identities permissions on a key can be bruteforced to find the one with access

If an identity does not have access to a KMS Key when viewing the KMS Dashboard in AWS Management Console, they will be given a warning that they do not have access to it. The error has the KeyID and the Key ARN, both information that is needed to bruteforce the permissions of identities.

After the information gathered, an administrator can create a script that will:

  • Create an IAM policy that allows kms:DescribeKey on the specific key

  • Get a list of all the identities (users and roles)

  • Attach the policy to each user and role

  • Modify each role’s Trust Policy to allow the identity that is running the script to Assume them

  • If the user has 1 set of credentials (AccessKey and SecretKey), since by default each user can have 2, create a 2nd. If the user has 2 sets of credentials, delete the 1st and create a new one. As for the roles, they can use sts:AssumeRole to get a set of temporary credentials

  • Using the credentials of the identities, run kms:DescribeKey

  • Lastly detach the policy on the users and roles, detach the policy and delete the key, detach the policy, delete the users’ credentials, and delete the policy

Word of caution: This might break a lot of stuff on the infrastructure (especially the deletion of the user’s one credential to recreate a new one), as the credentials might be used elsewhere.

Bypass nr.1: Creating an AWS IAM identity and giving it access to the key

One way to bypass this bottleneck from an attacker's perspective, is to create an IAM Identity, give access to the KMS key and delete it. Then, if the target pays, the attacker can just give them the identity name and type to create.

Since nobody else except for the identities defined in the policy will have access to the key, it will be impossible for the target to run kms:DescribeKey or kms:GetKeyPolicy to get the identity of the owner of the key. The attacker will have to create an identity with a complex naming convention. The name will have to be very hard to bruteforce, even with character bruteforce. By default, an IAM user name can have up to 64 characters with valid characters being: A-Z, a-z, 0-9, and + = , . @ _ - (hyphen). A really long complicated name like qQ5ybE3L05eS.=8d6MuWN@wTSyl.OCs.@.=+UTbS90ECRJoNYu would be hard to brute-force.

They can then create the key for the new user or assume the new role using the compromised identity, make the created user/role the sole owner of the key and encrypt the bucket. Then delete the identity.

Bottleneck nr.2: Ransom Events are History

CloudTrail, provided it is enabled, will log all the requests configured on the trail in the specified region. Assuming that the region in which the bucket is located has logging enabled and control plane events are also being logged, the target organization will have log data that would capture much of the attacker’s behavior up to this point. Such log activity would include iam:CreateUser, iam:AttachUserPolicy, kms:PutKeyPolicy (or kms:CreateKey if the attacker put the policy when they created the key), or iam:DeleteUser. This way, even a deleted user will not be able to be hidden, since the defender will know the name of the identity from the logs.

The attacker may stop logging for a trail or potentially delete the trail altogether, which will change the process to:

CloudTrail Trails are not the only place where events are stored though. A defender can utilize Event History to look for the events, even if the CloudTrail Trail is Stopped or Deleted.

Event History is an AWS Cloudtrail Feature that keeps a record of all the events that captured in a region over the last 90 days. So what differentiates Event History from Cloudtrail Trails ? Even if all Cloudtrail logging is stopped/deleted, Event History will not stop recording activity.

This isn’t good news for the attacker. Using this feature, the security team of the victim organization will know the user that was compromised, see the user they then created, the key they created, and the key policy they attached to the key:

Bottleneck nr.3: Deleting the user deletes the privilege

Another finding I found is that deleting an identity that is allowed to access a key and recreating the identity with the same name will nullify its access to the key.

Now, even if the target creates the identity with the correct name, they cannot access the key, making the bucket objects encrypted and inaccessible. This will result in data destruction, as neither attacker, not defender can regain access to the key.

Bypass nr.2: Locking Source IP

On his presentation on DefCON, Spencer Gietzen talks about a way to lock a key policy by having an Elastic IP (or any equivalent of any cloud provider) and limit the KMS Access to only that IP. In that case, the KMS Policy would look something like this:

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
		    "arn:aws:iam::012345678912:user/testuser"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        "Condition": {
            "aws:SourceIp": [
                "192.0.2.0/24",
                "203.0.113.0/24"
            ]
        }
    ]
}

This way, an attacker can have a server running, which can limit the access to the key and therefore can limit the defender on accessing the resources encrypted by it.

Bypass nr.3: Using Attacker Controlled AWS Account

We will see this a bit later on Scenario 2 and on, but KMS are a Cross-Account Resource, meaning an identity from one account can access a key from another account. So, an attacker with their own Attacker Account, can access a key on the target's account and be the only one allowed to do so.

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                     "arn:aws:iam::<ATTACKER Account>:user/admin"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        }
    ]
}

We can even combine both of them and only allow the attacker identity from the attacker account to access the key:

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::<ATTACKER Account>:user/admin"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        "Condition": {
            "aws:SourceIp": [
                "192.0.2.0/24",
                "203.0.113.0/24"
            ]
        }
    ]
}

Bottleneck nr.3: Still on the same boat

The previous scenarios are an improvement, but not necessarily the best way, as both suffer from the same issues this attack suffers altogether:

  1. The key is inside the target's account. Worst case scenario, the attacker can ask for access to the key from AWS, due to the key being misused for an attack.

  2. The target identity resides inside the target's account, meaning the target can look into the CloudTrail logs (including Event History) to know which is the compromised identity and from where the request came.

  3. All cloud providers have defined IP spaces, so if the target has the event (which they do from the Event History), they can find out which provider the attacker is using, which can lead to easier capture of the attacker.

Attack Scenario 1 Conclusions

In the end, as an ransomware attempt, this attack has some drawbacks that do not allow it to be the best Ransomware Scenario:

  1. The key, though locked, is still part of the target's account. It's like creating a island on a lake inside a city and claim it as not part of the city. The water makes it harder, but not impossible to access.

  2. Allowing only one identity to access the key can result in finding by bruteforce and deleting the identity with access to it can result in key and data lock

  3. The logs cannot be stopped as Event History will not allow it.

  4. Worst case scenario, AWS itself will help in unlocking the key policy.

Attack Scenario nr.2: Ransomware using Cross-Account KMS Keys

Key Sharing

AWS KMS offers a feature called Key Sharing (you might also find it as Cross Account Key Use). The idea behind it is simple, an account can encrypt their data using a key from another account. This is done through Key Policy, which enables a user on the account with the data to access the key from the other account. So, an account is allowed access to a key until the account that owns the key revokes that access.

At this point, if the attacker wants to lock the S3 Data on a S3 Bucket, they will only need an AWS Account (even a free trial one).

Key Granting

KMS Key Granting works the same as Cross Account Access, but in this case, the attacker will grant the target access and give them a grant token, with which the target will use the key. The idea is still the same, aside from the fact that on the Policy Usage, the key policy will need to be updated, while for key granting, the tokens will need to be revoked.

Ransomware Process

In this scenario we will again assume that the attacker has gained access to the environment, their privileges have been escalated and they now have access to an Identity that has permissions to perform s3:PutBucketEncryption, s3:GetObject, kms:Encrypt, kms:Decrypt, kms:DescribeKey, s3:PutObject including KMS for all resources on all accounts. If the actions are limited to only the current account for example, this attack wouldn’t work. For the sake of simplicity, let’s assume that the compromised user has AdministratorAccess, which has access to everything on all accounts.

The attacker’s next steps are as follows:

  • The threat actor creates the KMS Key on the attacker controlled account

  • They will need to add the policy that allows the compromised account to access the key. The policy will also allow root access of the attacker’s account so as not to lock themselves out of the account (we will presume 987654321012 is the attacker’s account and 012345678912 is the target’s account).

    {
        "Version": "2012-10-17",
        "Id": "key-consolepolicy-3",
        "Statement": [
            {
                "Sid": "Allow attachment of persistent resources",
                "Effect": "Allow",
                "Principal": {
                    "AWS": [
    			"arn:aws:iam::012345678912:user/compromisedUser",
    			"arn:aws:iam::987654321012:user/root"
                    ]
                },
                "Action": "kms:*",
                "Resource": "*"
            }
        ]
    }
  • Now the compromised can access the key, even though it’s associated with other account. Using the permissions they’ve been granted, the target account can access the key. The attacker can check this by running kms:DescribeKey as the compromised identity.

  • A bucket can have Bucket Encryption Configuration, meaning it can have a KMS Key attached to it to encrypt the objects. An attacker can check the configuration using s3:GetBucketConfiguration. If a bucket does not have a key attached, the output would be as below:

Contrary to the output after the key is attached:

  • To add the configuration, the attacker can run s3:PutBucketEncryption using the key created on the attacker’s account and then encrypt the bucket.

  • Due to how S3 Buckets operate, the objects that were inside the bucket prior to the attachment of the KMS Key are not encrypted. For the encryption, the attacker needs to download the object, encrypt it and upload it back. An easier way is to use s3:Copy, which for this case is a combination of s3:GetObject, kms:Encrypt and s3:PutObject. To encrypt the whole bucket, the attacker uses aws s3 cp —recursive on the bucket.

  • To limit the access from the target account, the attacker changes the KMS Policy in order to prohibit the compromised identity from accessing the key. If the identity tries to download the file, they are unable to, since they won’t have access to the key.

    If the attacker changes the policy again to allow the victim access to the key, the files can be downloaded.

Bottleneck nr.1: Bucket Versioning will keep an old copy of the object

Bucket Versioning is a feature of S3 Buckets that allows one or more versions of an object to be saved on the bucket in the case of an update/delete of such object. The object count is infinite and is only limited by the cost of them being stored, but they are never deleted even if several thousand versions of an object are created.

This in itself poses an issue to the attackers, as the previous, non-encrypted object will still be allowed to be retrieved, leading to a nullification of the attack, and as such, will need to be bypassed.

Bypass nr.1: Bypassing Bucket Versioning by and by not disabling it

One way attackers “bypass” bucket versioning is to remove it, which can be done using s3:PutBucketVersioning. If bucket versioning is set from Enabled to Suspended, the versioning will be disabled. The previous versions will still remain, but no new versions will be created.

Another way an attacker with no rights to change Versioning, but with the rights to delete the versions and the bucket versioning, can do that before encrypting the bucket. So, the flow of work is:

In this instance, the only version left on the bucket is the one that is encrypted.

Bottleneck nr.2: MFADelete to the rescue

We should mention that deleting S3 bucket version configuration or deleting object versions will both require MFA if S3 MFADelete is enabled. MFADelete is a part of bucket versioning which requires MFA for tasks like s3:DeleteObject or s3:PutBucketVersioning. This requires extra work for the attacker, because they would need to get the identity that can execute the tasks, breach it, get MFA Access and continue with the rest.

MFADelete also ensures that another identity will not accidentally delete objects from the bucket as well as unauthorized access to the bucket.

Can MFADelete by bypassed?

Theoretically yes, practically maybe. An attacker with access to the MFA Device, access to the identity that has the device can do that. Of course this requires a lot of work, but theoretically can be done. It’s a pain to do it though and we will not go through with this approach.

Bottleneck nr.3: Stopping Ransomware using Service Control Policies

Amazon Service Control Policies (SCPs) are a feature of AWS that limit the usage of certain services based on certain conditions. They work the same way as normal resource policies, but on a service level. They can be attached to accounts, roots and organization units.

In this case, the victim organization would need to specify the ResourceOrgId policy condition. This condition will deny all actions on resources (KMS Keys on our case) outside the organization that the account is in. You can also use ResourceAccount to filter on the account level (for example if you only have one account and no organization). The policy below is an example the victim organization can use:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictKMSOutsideOrg",
      "Effect": "Deny",
      "Action": [
        "kms:*"
      ],
      "Resource": [
        "*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:ResourceOrgID": "o-xxxxxxxxx"
        }
      }
    }
  ]
}

In the image below, we see before and after the SCP is applied on the organization. The key, being on the attacker’s account, is not allowed to be used by the targeted identity.

This poses a problem to the attacker, as they cannot just create a key on their account and use it. They would need to find a way to do it using a key that the target account does not have access to and they can use it.

Attack Scenario nr.3: An object a day keeps data access away

One problem with the previous attack, is the fact that the bucket will essentially be locked from future files being uploaded. That is because if the bucket has the encryption configured, each new uploaded object will need to be encrypted. Since the target's other identities will not have access to the key, they will not be able to do it. This leads to issues when the attacker needs to encrypt as many data as they can.

When looking for different scenarios, one thing that stands out is the fact that each object seems to have its own an encryption configuration that was the same as the bucket’s config, but why was it there?

This is a scenario that from Rhino Security's (then) Researcher Spencer Gietzen wrote on 2 articles about. They touch upon this technique as a good way to lock the data on a bucket:

As seen before, when the encryption configuration is changed on a bucket, the encryption config of the previous objects do not change. So, that means each object has its own encryption configuration.

So, can a bucket without an encryption configuration accept objects that are encrypted with a KMS Key? It should be possible since the object in itself, albeit encrypted, is just some bytes put in a specific order.

If we take our bucket without a KMS encryption configuration and try to upload a file with a custom KMS Key, the bucket’s encryption config will not change.

The object is encrypted using KMS and the bucket’s config is not changed.

That means, other objects can be uploaded on the bucket and the previous ones will be encrypted. That way, even if the breach is found, the attacker would still have files locked with the key that is on its own account. The attacker now can just create a scheduled script that will do the job for him.

All in all, the attack thus becomes:

  • The attacker creates a KMS Key on their account and allow the compromised user on the victim account to access that key

  • The attacker would have compromised an identity that can run s3:ListObjects and s3:GetObject to get each object’s configuration. If the object is encrypted, the object will have a config key called SSEKMSKeyId that has the KMS Key Arn.

  • The user will run s3:Copy, which utilizes s3:GetObject, kms:Encrypt, s3:PutObject. s3:Copy will copy the object from the bucket to the bucket again, which encrypt the file in the process.

  • If the object does not have a KMS Key, meaning the object is not encrypted, the identity can run s3:Copy, which utilizes s3:GetObject, kms:Encrypt, s3:PutObject to automatically encrypt the object. s3:Copy will copy the object from the bucket to the bucket again, which will be encrypting the file in the process.

  • The object will be encrypted and inaccessible to download. The bucket on the other hand will be functional.

We should be also note that this attack scenario can be achieved using a key on the target’s account by locking the key as we did in the first scenario.

Attack Scenario nr.4: Keeping it simple works too

In a more rudimentary ransomware campaign, an attacker gets access to the important files, encrypts them with a key they own, and then puts them back on the storage. This can be simulated by using a key on the attacker’s account:

This attack, while being the most “generic” of them, is very effective because it doesn’t require many privileges. The bucket encryption and bucket versioning will present some issues that we will look at later. If bucket encryption configuration is set, the attacker will need access to the key that has encrypted the files. Even if they were to delete the bucket encryption configuration, files will still maintain the previous encrypted state.

Notes for the attack

  • For this attack, the threat actor would need a compromised identity with only s3:ListObjects, s3:GetObject and s3:PutObject.

  • Same as on the previous attack, if the bucket does not have encryption configuration enabled, the attacker doesn’t need KMS access on the target’s account since the key they are using is on the their account. They also don’t need to change the encryption configuration or the key policy, and there’s no need to lock a key.

  • If the defender has configured the bucket to have a bucket encryption configuration, they’ll need to have access to the KMS Key.

  • SCPs do not help on this attack, since the key is on the attacker’s account, accessed only by the attacker and only requested by an identity on the attackers account.

  • Bucket Versioning and especially MFA Delete is the best way to help on this scenario. If the bucket objects are not allowed to be deleted or modified unless MFA is bypassed, the attacker will not be able to encrypt them.

Attack Scenario nr.5: When encryption fails the first time, Try Harder

One issue with these attacks is that an attacker might come in contact with a target S3 Bucket that already has encrypted objects. As we mentioned on Attack Scenario nr.3, an attacker can use the fact that an object by itself has a KMS Key

Another way to achieve the same thing is to use kms:ReEncrypt to allow a text which was previously to be encrypted using another KMS Key. The rest of the attack is the same as the previous one, where we use the key locally. Only this time, the key is provided by KMS:

Attack Scenario nr.6: Using External Keys

In 2022, AWS announced AWS KMS External Key Store, which is a feature that allows connection of an external Key Management System to the AWS Account and thus using a key not created and managed by AWS to encrypt AWS Resources.

Harsh Varagiya has a blog and a tool written about using this technique to encrypt AWS Resources using an external KMS Key. Then, the attacker can either use kms:ConnectCustomKeyStore or kms:UpdateCustomKeyStore to either connect the new external key store cluster or update the current one (provided there is one that allows that currently exists).

Bottleneck nr.1: SCPs to the rescue

SCPs come to the rescue again, as to create or connect the external key store, one would need to be able to use kms:CreateCustomKeyStore, kms:UpdateCustomKeyStore, or kms:ConnectCustomKeyStore on the target's account.

If the target is not using XKS as a feature, they can configure the Organization's SCP to prevent XKS related privileges. The example below is taken directly from Harsh Varagiya blog:

{
  "Version": "2012–10–17",
  "Statement": [
    {
      "Sid": "PreventAllCKSActions",
      "Effect": "Deny",
      "Action": [
        "kms:CreateCustomKeyStore",
        "kms:UpdateCustomKeyStore",
        "kms:ConnectCustomKeyStore",
        "kms:DisconnectCustomKeyStore",
        "kms:DeleteCustomKeyStore"
      ],
      "Resource": "*"
    }
  ]
}

Bypass nr.1: Keeping it simple might still work

One bypass for it by an attacker would be to use the example from Attack nr.2, Bypass nr.3, but instead of using a local key, they will be using an XKS Cluster inside an attacker owned AWS Account. That way they can manage the key, while bypassing SCPs on the target. The question is, is it worth it?

Bottleneck nr.2: Is it really important to use KMS XKS to achieve this?

On the previous bypass, we saw that we can utilize the example from Attack nr.2, Bypass nr.3, where we are using the XKS Key to locally encrypt the objects. While this allows for a better management of the key, is it really important to do it?

For one, the setup is complex enough, and to go from a key or an external KMS, to an attacker owned AWS Account, just to be able to encrypt locally seems like an overkill (and it is).

Then, there is also the matter of detection. While the attacker can use this technique, it is still easier for a target to reach them, due to them providing information when they create the attacker's account, making the whole attempt redundant at best.

Attack Scenario nr.7: Stopping CloudTrail using KMS Ransomware

One of the impacts of the S3 Bucket Ransomware is abusing KMS Keys to tamper with CloudTrail Logging. This will lead to CloudTrail Logs not being stored on the CloudTrail S3 Bucket. This attack will NOT tamper with the logs saved on the Event History and accessible through the CloudTrail API, neither will it encrypt previous logs. Just prevent the new logs from being forwarded and stored on the S3 Bucket. Only the ones being forwarded and stored on AWS S3 Buckets.

To achieve this, the attacker will need to tamper with the KMS Key of the CloudTrail Bucket, by using the API call kms:PutKeyPolicy, which will modify the KMS key policy and thus limiting the CloudTrail service from accessing the bucket to upload the new logs.

Bottleneck nr.1: Removing KMS Key will allow logs to be stored again

Two of the drawbacks of this scenario is that:

  1. The logging is not stopped. Just log forwarding is. That means, the target will still have 90 days worth of logs stored on the Event History

  2. The target can just remove the KMS Key from the trail using cloudtrail:UpdateTrial with an empty KMS Key and the logs will continue to be forwarded:

RansomWhen?

This all lead to the creation of RansomWhen, a Python based, customizable tool able to enumerate identities with access to lock data on a bucket using a KMS Key, as well as detect Ransomware Attempts on the account.

Enumerating Identities

To enumerate the identities, the tool will use the JSON Blob on path scenarios/scenarios.json containing the privileges needed for different attacks. That JSON is configurable with the below format. Each scenario will have a name and a list of events to check.

{
    "Create Locked Key and Encrypt Bucket using CopyObject": [
        "kms:CreateKey",
        "s3:PutBucketEncryptionConfiguration",
        "s3:CopyObject"
    ]
}

Each scenario will be listed as:

  • allowed, for cases when all the events in a scenario are allowed

  • partially, for cases when at least 1 event in the scenario is allowed

  • denied, for cases when no event on the scenario is allowed

Finding malicious events

As far as finding identities with malicious events, the tool will look into the JSON Blob scenarios/events.json. That file can also be configured, with a format as below, where each event will have its information set to either null or a value, just as it is supposed to be saved on the CloudTrail Logs.

{
  "CreateKey": {
    "UserAgent": null,
    "Identity": null,
    "RequestParameters": null,
    "ResponseElements": null,
    "ErrorCode": null,
    "ErrorMessage": null,
    "EventSource": "kms.amazonaws.com"
  }
}

Then, running the tool, would list all the identities (IAM Users and Roles) and the events they have ran over the last 90 days.

References

  1. S3 Ransomware Part 1: Attack Vector - Rhino Security Labs (https://rhinosecuritylabs.com/aws/s3-ransomware-part-1-attack-vector/)

  2. S3 Ransomware Part 2: Prevention and Defense - Rhino Security Labs (https://rhinosecuritylabs.com/aws/s3-ransomware-part-2-prevention-and-defense/)

  3. Perfecting Ransomware on AWS — Using ‘keys to the kingdom’ to change the locks - (https://medium.com/@harsh8v/redefining-ransomware-attacks-on-aws-using-aws-kms-xks-dea668633802)

  4. amazon-s3-encryption-client-go - (https://github.com/aws/amazon-s3-encryption-client-go)

  5. RansomWhen??? I never even notice it… (https://permiso.io/blog/ransomwhen-i-did-not-even-notice-it)

Conclusions

If there’s one thing to learn from this article, it’s that just because a vendor or cloud service provider has offered functionality for their customers, doesn’t mean that same functionality can’t be used nefariously by threat actors. Attackers are becoming smarter and finding new ways to attack their targets.

As a defender, there are some things that need to be aware of the best practices to apply on the infrastructure to mitigate the attacks:

  • Be vigilant and continuously check who has access to sensitive data or sensitive services like KMS.

  • Always enable bucket versioning + MFA delete with least privilege in mind for the identities that have access to MFA.

  • If your account is part of an organization, the account and organization level access should be limited.

Last updated

Was this helpful?