Detection rules › Panther
GCP KMS Cross-Project Encryption
Detects when a GCS service account in one project uses a KMS encryption key from a different project. This could indicate potential ransomware activity where an attacker is using their own KMS key to encrypt data in a victim's project, making it inaccessible without the attacker's key.
Rule body yaml
AnalysisType: rule
Filename: gcp_kms_cross_project_encryption.py
RuleID: "GCP.KMS.CrossProjectEncryption"
DisplayName: "GCP KMS Cross-Project Encryption"
Enabled: true
LogTypes:
- GCP.AuditLog
Tags:
- GCP
- KMS
- Encryption
- Ransomware
Severity: High
Description: >
Detects when a GCS service account in one project uses a KMS encryption key from a different project.
This could indicate potential ransomware activity where an attacker is using their own KMS key to encrypt
data in a victim's project, making it inaccessible without the attacker's key.
Runbook: |
1. Query GCP audit logs for all KMS operations by the principal email in the 24 hours before and after this alert to understand the scope of encryption activity
2. Check if the cross-project KMS key access is documented in approved service integrations or has been used by this service account in the past 90 days
3. Find all storage operations (GCS object writes, rewrites) by this service account in the 1 hour window around the alert to identify potentially affected data
Reference: >
https://cloud.google.com/kms/docs/encrypt-decrypt
https://cloud.google.com/storage/docs/encryption/customer-managed-keys
SummaryAttributes:
- protoPayload:authenticationInfo:principalEmail
- protoPayload:resourceName
- resource:labels:project_id
Tests:
- Name: Cross-Project KMS Encryption Detected
ExpectedResult: true
Log:
{
"p_log_type": "GCP.AuditLog",
"insertId": "test-insert-id-001",
"logName": "projects/victim-project/logs/cloudaudit.googleapis.com%2Fdata_access",
"protoPayload": {
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": {
"principalEmail": "service-111111111111-gs-project-accounts.iam.gserviceaccount.com"
},
"authorizationInfo": [
{
"granted": true,
"permission": "cloudkms.cryptoKeyVersions.useToEncrypt",
"permissionType": "DATA_READ",
"resource": "projects/victim-project/locations/us/keyRings/test-keyring/cryptoKeys/test-key"
}
],
"methodName": "Encrypt",
"request": {
"@type": "type.googleapis.com/google.cloud.kms.v1.EncryptRequest",
"name": "projects/victim-project/locations/us/keyRings/test-keyring/cryptoKeys/test-key"
},
"resourceName": "projects/attacker-project/locations/us/keyRings/malicious-keyring/cryptoKeys/ransomware-key",
"serviceName": "cloudkms.googleapis.com"
},
"resource": {
"labels": {
"crypto_key_id": "test-key",
"key_ring_id": "test-keyring",
"location": "us",
"project_id": "victim-project"
},
"type": "cloudkms_cryptokey"
},
"severity": "INFO",
"timestamp": "2025-12-02 19:41:27.745108384"
}
- Name: Same-Project KMS Encryption (No Alert)
ExpectedResult: false
Log:
{
"p_log_type": "GCP.AuditLog",
"insertId": "test-insert-id-002",
"logName": "projects/legitimate-project/logs/cloudaudit.googleapis.com%2Fdata_access",
"protoPayload": {
"at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": {
"principalEmail": "service-111111111111-gs-project-accounts.iam.gserviceaccount.com"
},
"authorizationInfo": [
{
"granted": true,
"permission": "cloudkms.cryptoKeyVersions.useToEncrypt",
"permissionType": "DATA_READ",
"resource": "projects/legitimate-project/locations/us/keyRings/test-keyring/cryptoKeys/test-key"
}
],
"methodName": "Encrypt",
"request": {
"@type": "type.googleapis.com/google.cloud.kms.v1.EncryptRequest",
"name": "projects/legitimate-project/locations/us/keyRings/test-keyring/cryptoKeys/test-key"
},
"resourceName": "projects/legitimate-project/locations/us/keyRings/test-keyring/cryptoKeys/test-key",
"serviceName": "cloudkms.googleapis.com"
},
"resource": {
"labels": {
"crypto_key_id": "test-key",
"project_id": "legitimate-project"
},
"type": "cloudkms_cryptokey"
},
"severity": "INFO",
"timestamp": "2025-12-02 19:41:27.745108384"
}
Detection logic
Condition
not (protoPayload.serviceName ne "cloudkms.googleapis.com" or protoPayload.methodName ne "Encrypt" or protoPayload.authenticationInfo.principalEmail not contains "gs-project-accounts.iam.gserviceaccount.com")
This rule also runs imperative logic the parser cannot express as a filter; the conditions above are the structured part it could extract.
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
protoPayload.authenticationInfo.principalEmail | contains | gs-project-accounts.iam.gserviceaccount.com |
protoPayload.methodName | ne | Encrypt |
protoPayload.serviceName | ne | cloudkms.googleapis.com |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
protoPayload.authenticationInfo.principalEmail | contains |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
project | resource.labels.project_id |
principal | protoPayload.authenticationInfo.principalEmail |
caller_ip | protoPayload.requestMetadata.callerIP |
methodName | protoPayload.methodName |
resourceName | protoPayload.resourceName |
serviceName | protoPayload.serviceName |