GCP GCS Ransom Note Upload


Description

Detects when a file with a name matching common ransomware note patterns is uploaded to a Google Cloud Storage bucket. Ransomware attackers often leave ransom notes with distinctive filenames to provide victims with payment instructions.

Query · python

import re

# Common ransomware note filename patterns
RANSOM_NOTE_PATTERNS = [
    # Explicit ransomware-related terms
    # RANSOM_NOTE.txt, PAYMENT_INFO.html
    r"(?i)(ransom|payment)[_-]?(note|info|instructions?).*\.(txt|html?)$",
    # Decrypt/restore with specific action words
    # HOW_TO_DECRYPT_FILES.txt
    r"(?i)how[_-]?to[_-]?(decrypt|restore|recover)[_-]?(your[_-]?)?files.*\.(txt|html?)$",
    # DECRYPT_INSTRUCTIONS.txt
    r"(?i)decrypt[_-]?(instructions?|guide|info|your[_-]?files).*\.(txt|html?)$",
    # RESTORE_INSTRUCTIONS.txt
    r"(?i)restore[_-]?(instructions?|guide|info|your[_-]?files).*\.(txt|html?)$",
    # RECOVERY_INSTRUCTIONS.txt
    r"(?i)recovery[_-]?(instructions?|key|guide).*\.(txt|html?)$",
    # Files encrypted/locked messages
    # FILES_ENCRYPTED.txt, ALL_FILES_HAVE_BEEN_ENCRYPTED.txt
    r"(?i)(all[_-]?)?files?[_-]?(have[_-]?been[_-]?)?(encrypted|locked).*\.(txt|html?)$",
    # YOUR_FILES_ARE_ENCRYPTED.txt
    r"(?i)your[_-]?files?[_-]?(are|have[_-]?been)[_-]?(encrypted|locked).*\.(txt|html?)$",
    # DATA_ENCRYPTED.txt
    r"(?i)data[_-]?(has[_-]?been[_-]?)?(encrypted|locked).*\.(txt|html?)$",
    # Unlock-related (common in ransomware)
    # UNLOCK_INSTRUCTIONS.txt
    r"(?i)unlock[_-]?(instructions?|guide|your[_-]?files).*\.(txt|html?)$",
    # Help decrypt/restore (specific to ransomware)
    # HELP_DECRYPT_YOUR_FILES.txt
    r"(?i)help[_-]?(restore|decrypt|recover)[_-]?(your[_-]?)?files.*\.(txt|html?)$",
]

COMPILED_PATTERNS = [re.compile(pattern) for pattern in RANSOM_NOTE_PATTERNS]


def rule(event):
    if event.deep_get("protoPayload", "serviceName") != "storage.googleapis.com":
        return False

    # Focus on the create operation (the actual re-encryption)
    method = event.deep_get("protoPayload", "methodName", default="<UNKNOWN_METHOD")
    if method != "storage.objects.create":
        return False

    # Check for filename
    resource = event.deep_get("protoPayload", "resourceName", default="")
    obj_name = resource.split("/objects/")[-1] if "/objects/" in resource else "<UNKNOWN_FILE>"
    return any(pattern.match(obj_name) for pattern in COMPILED_PATTERNS)


def title(event):
    resource = event.deep_get("protoPayload", "resourceName", default="")
    obj_name = resource.split("/objects/")[-1] if "/objects/" in resource else "<UNKNOWN_FILE>"
    bucket = event.deep_get("resource", "labels", "bucket_name", default="<UNKNOWN_BUCKET>")
    user = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<UNKNOWN_USER>"
    )
    return (
        f"[GCP] Potential ransomware note uploaded to GCS bucket: "
        f"[{obj_name}] in bucket [{bucket}] by user [{user}]"
    )

Analyst notes

  1. Query GCP Audit logs for all bucket operations by the principal email in the 24 hours before and after this alert
  2. Check if the source IP is associated with known cloud provider IP ranges, VPN endpoints, or matches the user's typical access patterns
  3. Search for related KMS key changes or IAM policy modifications on the same bucket in the past 6 hours
  4. Look for bulk object operations (rewrite, copy, delete) on this bucket in the past 24 hours that could indicate file encryption
  5. Verify backups are intact and check for any bucket configuration changes (versioning, retention, encryption) in the past 24 hours
Raw source GCP GCS Ransom Note Upload · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
DisplayName: "GCP GCS Ransom Note Upload"
LogTypes:
  - GCP.AuditLog
RuleID: "GCP.GCS.Ransom.Note.Upload"
Enabled: true
Filename: gcp_gcs_ransom_note_upload.py
Description: >
  Detects when a file with a name matching common ransomware note patterns is uploaded to a Google Cloud Storage bucket.
  Ransomware attackers often leave ransom notes with distinctive filenames to provide victims with payment instructions.
Runbook: |
  1. Query GCP Audit logs for all bucket operations by the principal email in the 24 hours before and after this alert
  2. Check if the source IP is associated with known cloud provider IP ranges, VPN endpoints, or matches the user's typical access patterns
  3. Search for related KMS key changes or IAM policy modifications on the same bucket in the past 6 hours
  4. Look for bulk object operations (rewrite, copy, delete) on this bucket in the past 24 hours that could indicate file encryption
  5. Verify backups are intact and check for any bucket configuration changes (versioning, retention, encryption) in the past 24 hours
Reference: https://attack.mitre.org/techniques/T1486/
Tags:
  - GCP
  - Google Cloud Storage
  - Impact:Data Encrypted for Impact
  - Ransomware
Reports:
  MITRE ATT&CK:
    - TA0040:T1486
SummaryAttributes:
  - severity
  - p_any_ip_addresses
  - p_any_emails
Severity: High
Tests:
  - Name: Ransomware note uploaded
    ExpectedResult: true
    Log:
      insertId: 1vfv897ejxgc2
      logName: projects/example-project/logs/cloudaudit.googleapis.com%2Fdata_access
      p_any_ip_addresses:
        - 1.2.3.4
      p_any_emails:
        - denethor@lotr.com
      p_any_usernames:
        - user
      p_event_time: "2025-12-12 22:05:35.878912806"
      p_log_type: GCP.AuditLog
      p_parse_time: "2025-12-12 22:06:21.141743969"
      p_row_id: 000000000013a23f82a982753925d2c2
      p_schema_version: 0
      p_source_id: bd7da315-647e-4eca-bcfe-083fab18f3f1
      p_source_label: gcp-audit-logs
      protoPayload:
        at_sign_type: type.googleapis.com/google.cloud.audit.AuditLog
        authenticationInfo:
          oauthInfo:
            oauthClientId: 111111111111-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com
          principalEmail: denethor@lotr.com
        authorizationInfo:
          - granted: true
            permission: storage.objects.delete
            resource: projects/_/buckets/data-bucket/objects/HOW_TO_DECRYPT_FILES.txt
            resourceAttributes: {}
          - granted: true
            permission: storage.objects.create
            resource: projects/_/buckets/data-bucket/objects/HOW_TO_DECRYPT_FILES.txt
            resourceAttributes: {}
        methodName: storage.objects.create
        requestMetadata:
          callerIP: 1.2.3.4
          callerIp: 1.2.3.4
          callerSuppliedUserAgent: google-cloud-sdk gcloud/548.0.0 command/gcloud.storage.cp invocation-id/abc123def456 environment/devshell environment-version/None client-os/LINUX client-os-ver/6.6.111 client-pltf-arch/x86_64 interactive/False from-script/False python/3.13.7,gzip(gfe)
          destinationAttributes: {}
          requestAttributes:
            auth: {}
            time: "2025-12-12T22:05:35.887374247Z"
        resourceLocation:
          currentLocations:
            - us
        resourceName: projects/_/buckets/data-bucket/objects/HOW_TO_DECRYPT_FILES.txt
        serviceData:
          "@type": type.googleapis.com/google.iam.v1.logging.AuditData
          at_sign_type: type.googleapis.com/google.iam.v1.logging.AuditData
          policyDelta: {}
        serviceName: storage.googleapis.com
        status: {}
      receiveTimestamp: "2025-12-12 22:05:36.474700595"
      resource:
        labels:
          bucket_name: data-bucket
          location: us
          project_id: example-project
        type: gcs_bucket
      severity: INFO
      timestamp: "2025-12-12 22:05:35.878912806"
  - Name: Normal file upload
    ExpectedResult: false
    Log:
      insertId: 2abc123xyz789
      logName: projects/example-project/logs/cloudaudit.googleapis.com%2Fdata_access
      p_any_ip_addresses:
        - 10.0.0.200
      p_any_emails:
        - admin@example.com
      p_any_usernames:
        - admin
      p_event_time: "2025-12-12 22:10:15.123456789"
      p_log_type: GCP.AuditLog
      p_parse_time: "2025-12-12 22:11:01.987654321"
      p_row_id: 11d269250d7ba7e2b399f8e83bbb9915
      p_schema_version: 0
      p_source_id: bd7da315-647e-4eca-bcfe-083fab18f3f1
      p_source_label: gcp-audit-logs
      protoPayload:
        at_sign_type: type.googleapis.com/google.cloud.audit.AuditLog
        authenticationInfo:
          oauthInfo:
            oauthClientId: 123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com
          principalEmail: admin@example.com
        authorizationInfo:
          - granted: true
            permission: storage.objects.create
            resource: projects/_/buckets/data-bucket/objects/quarterly_report_2025.pdf
            resourceAttributes: {}
        methodName: storage.objects.create
        requestMetadata:
          callerIP: 10.0.0.200
          callerIp: 10.0.0.200
          callerSuppliedUserAgent: google-cloud-sdk gcloud/548.0.0 command/gcloud.storage.cp invocation-id/def789ghi012 environment/devshell environment-version/None client-os/LINUX client-os-ver/6.6.111 client-pltf-arch/x86_64 interactive/True from-script/False python/3.13.7,gzip(gfe)
          destinationAttributes: {}
          requestAttributes:
            auth: {}
            time: "2025-12-12T22:10:15.234567890Z"
        resourceLocation:
          currentLocations:
            - us
        resourceName: projects/_/buckets/data-bucket/objects/quarterly_report_2025.pdf
        serviceData:
          "@type": type.googleapis.com/google.iam.v1.logging.AuditData
          at_sign_type: type.googleapis.com/google.iam.v1.logging.AuditData
          policyDelta: {}
        serviceName: storage.googleapis.com
        status: {}
      receiveTimestamp: "2025-12-12 22:10:16.345678901"
      resource:
        labels:
          bucket_name: data-bucket
          location: us
          project_id: example-project
        type: gcs_bucket
      severity: INFO
      timestamp: "2025-12-12 22:10:15.123456789"

# ------ paired body: gcp_gcs_ransom_note_upload.py ------

import re

# Common ransomware note filename patterns
RANSOM_NOTE_PATTERNS = [
    # Explicit ransomware-related terms
    # RANSOM_NOTE.txt, PAYMENT_INFO.html
    r"(?i)(ransom|payment)[_-]?(note|info|instructions?).*\.(txt|html?)$",
    # Decrypt/restore with specific action words
    # HOW_TO_DECRYPT_FILES.txt
    r"(?i)how[_-]?to[_-]?(decrypt|restore|recover)[_-]?(your[_-]?)?files.*\.(txt|html?)$",
    # DECRYPT_INSTRUCTIONS.txt
    r"(?i)decrypt[_-]?(instructions?|guide|info|your[_-]?files).*\.(txt|html?)$",
    # RESTORE_INSTRUCTIONS.txt
    r"(?i)restore[_-]?(instructions?|guide|info|your[_-]?files).*\.(txt|html?)$",
    # RECOVERY_INSTRUCTIONS.txt
    r"(?i)recovery[_-]?(instructions?|key|guide).*\.(txt|html?)$",
    # Files encrypted/locked messages
    # FILES_ENCRYPTED.txt, ALL_FILES_HAVE_BEEN_ENCRYPTED.txt
    r"(?i)(all[_-]?)?files?[_-]?(have[_-]?been[_-]?)?(encrypted|locked).*\.(txt|html?)$",
    # YOUR_FILES_ARE_ENCRYPTED.txt
    r"(?i)your[_-]?files?[_-]?(are|have[_-]?been)[_-]?(encrypted|locked).*\.(txt|html?)$",
    # DATA_ENCRYPTED.txt
    r"(?i)data[_-]?(has[_-]?been[_-]?)?(encrypted|locked).*\.(txt|html?)$",
    # Unlock-related (common in ransomware)
    # UNLOCK_INSTRUCTIONS.txt
    r"(?i)unlock[_-]?(instructions?|guide|your[_-]?files).*\.(txt|html?)$",
    # Help decrypt/restore (specific to ransomware)
    # HELP_DECRYPT_YOUR_FILES.txt
    r"(?i)help[_-]?(restore|decrypt|recover)[_-]?(your[_-]?)?files.*\.(txt|html?)$",
]

COMPILED_PATTERNS = [re.compile(pattern) for pattern in RANSOM_NOTE_PATTERNS]


def rule(event):
    if event.deep_get("protoPayload", "serviceName") != "storage.googleapis.com":
        return False

    # Focus on the create operation (the actual re-encryption)
    method = event.deep_get("protoPayload", "methodName", default="<UNKNOWN_METHOD")
    if method != "storage.objects.create":
        return False

    # Check for filename
    resource = event.deep_get("protoPayload", "resourceName", default="")
    obj_name = resource.split("/objects/")[-1] if "/objects/" in resource else "<UNKNOWN_FILE>"
    return any(pattern.match(obj_name) for pattern in COMPILED_PATTERNS)


def title(event):
    resource = event.deep_get("protoPayload", "resourceName", default="")
    obj_name = resource.split("/objects/")[-1] if "/objects/" in resource else "<UNKNOWN_FILE>"
    bucket = event.deep_get("resource", "labels", "bucket_name", default="<UNKNOWN_BUCKET>")
    user = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<UNKNOWN_USER>"
    )
    return (
        f"[GCP] Potential ransomware note uploaded to GCS bucket: "
        f"[{obj_name}] in bucket [{bucket}] by user [{user}]"
    )

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.