Detection rules › Panther

GCP Logging Sink Modified

Severity
informational
Log types
GCP.AuditLog
Tags
GCP, Logging, Sink, Infrastructure
Reference
https://cloud.google.com/logging/docs
Source
github.com/panther-labs/panther-analysis

This rule detects modifications to GCP Log Sinks.

Rule body yaml

---
AnalysisType: rule
DedupPeriodMinutes: 60
DisplayName: GCP Logging Sink Modified
Enabled: true
Filename: gcp_logging_sink_modified.py
RuleID: "GCP.Logging.Sink.Modified"
Severity: Info
CreateAlert: false
LogTypes:
  - GCP.AuditLog
Tags:
  - GCP
  - Logging
  - Sink
  - Infrastructure
Description: >
  This rule detects modifications to GCP Log Sinks.
Runbook: >
  Ensure that the modification was valid or expected. Adversaries may do this to exfiltrate logs or evade detection.
Reference: https://cloud.google.com/logging/docs
Tests:
  - Name: logging-sink.modifed-should-alert
    LogType: GCP.AuditLog
    ExpectedResult: true
    Log:
      {
        "insertid": "6ns26jclap",
        "logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "user@domain.com" },
            "authorizationInfo":
              [
                {
                  "granted": true,
                  "permission": "logging.sinks.update",
                  "resource": "projects/test-project-123456/sinks/test-1",
                  "resourceAttributes":
                    {
                      "name": "projects/test-project-123456/sinks/test-1",
                      "service": "logging.googleapis.com",
                    },
                },
              ],
            "methodName": "google.logging.v2.ConfigServiceV2.UpdateSink",
            "request":
              {
                "@type": "type.googleapis.com/google.logging.v2.UpdateSinkRequest",
                "sink":
                  {
                    "description": "test",
                    "destination": "logging.googleapis.com/projects/test-project-123456/locations/global/buckets/testloggingbucket",
                    "exclusions": [{ "filter": "*", "name": "excludeall" }],
                    "name": "test-1",
                  },
                "sinkName": "projects/test-project-123456/sinks/test-1",
                "uniqueWriterIdentity": true,
                "updateMask": "exclusions",
              },
            "requestMetadata":
              {
                "callerIP": "12.12.12.12",
                "callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
                "destinationAttributes": {},
                "requestAttributes":
                  { "auth": {}, "time": "2023-05-23T19:39:07.289670886Z" },
              },
            "resourceName": "projects/test-project-123456/sinks/test-1",
            "serviceName": "logging.googleapis.com",
            "status": {},
          },
        "receiveTimestamp": "2023-05-23 19:39:07.924",
        "resource":
          {
            "labels":
              {
                "destination": "",
                "name": "test-1",
                "project_id": "test-project-123456",
              },
            "type": "logging_sink",
          },
        "severity": "NOTICE",
        "timestamp": "2023-05-23 19:39:07.272",
      }
  - Name: logging-sink.non-modified-should-not-alert
    LogType: GCP.AuditLog
    ExpectedResult: false
    Log:
      {
        "insertid": "6ns26jclap",
        "logname": "projects/test-project-123456/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo": { "principalEmail": "user@domain.com" },
            "authorizationInfo":
              [
                {
                  "granted": true,
                  "permission": "logging.sinks.list",
                  "resource": "projects/test-project-123456/sinks/test-1",
                  "resourceAttributes":
                    {
                      "name": "projects/test-project-123456/sinks/test-1",
                      "service": "logging.googleapis.com",
                    },
                },
              ],
            "methodName": "google.logging.v2.ConfigServiceV2.ListSink",
            "request":
              {
                "@type": "type.googleapis.com/google.logging.v2.ListSinkRequest",
                "sink":
                  {
                    "description": "test",
                    "destination": "logging.googleapis.com/projects/test-project-123456/locations/global/buckets/testloggingbucket",
                    "exclusions": [{ "filter": "*", "name": "excludeall" }],
                    "name": "test-1",
                  },
                "sinkName": "projects/test-project-123456/sinks/test-1",
                "uniqueWriterIdentity": true,
                "updateMask": "exclusions",
              },
            "requestMetadata":
              {
                "callerIP": "12.12.12.12",
                "callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36,gzip(gfe),gzip(gfe)",
                "destinationAttributes": {},
                "requestAttributes":
                  { "auth": {}, "time": "2023-05-23T19:39:07.289670886Z" },
              },
            "resourceName": "projects/test-project-123456/sinks/test-1",
            "serviceName": "logging.googleapis.com",
            "status": {},
          },
        "receiveTimestamp": "2023-05-23 19:39:07.924",
        "resource":
          {
            "labels":
              {
                "destination": "",
                "name": "test-1",
                "project_id": "test-project-123456",
              },
            "type": "logging_sink",
          },
        "severity": "NOTICE",
        "timestamp": "2023-05-23 19:39:07.272",
      }

Detection logic

Rule logic imperative Python

import re

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    method_pattern = r"(?:\w+\.)*v\d\.(?:ConfigServiceV\d\.(?:UpdateSink))"
    match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default=""))
    return match is not None


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    resource = event.deep_get(
        "protoPayload",
        "resourceName",
        default="<RESOURCE_NOT_FOUND>",
    )
    return f"[GCP]: [{actor}] updated logging sink [{resource}]"


def alert_context(event):
    return gcp_alert_context(event)

The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.

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.

FieldSource
projectresource.labels.project_id
principalprotoPayload.authenticationInfo.principalEmail
caller_ipprotoPayload.requestMetadata.callerIP
methodNameprotoPayload.methodName
resourceNameprotoPayload.resourceName
serviceNameprotoPayload.serviceName