GCP CloudBuild Potential Privilege Escalation


Description

Detects privilege escalation attacks designed to gain access to the Cloud Build Service Account. A user with permissions to start a new build with Cloud Build can gain access to the Cloud Build Service Account and abuse it for more access to the environment.

Query · python

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    if not event.deep_get("protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith(
        "CloudBuild.CreateBuild"
    ):
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    # Get the principal (actor) email
    principal = event.deep_get("protoPayload", "authenticationInfo", "principalEmail", default="")

    # Skip whitelisted service accounts
    if principal.endswith("@gcf-admin-robot.iam.gserviceaccount.com"):
        return False

    # Check if build.create permission was granted
    for auth in authorization_info:
        if auth.get("permission") == "cloudbuild.builds.create" and auth.get("granted") is True:
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    operation = event.deep_get("protoPayload", "methodName", default="<OPERATION_NOT_FOUND>")
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]"


def alert_context(event):
    return gcp_alert_context(event)

Analyst notes

Confirm this was authorized and necessary behavior. To defend against this privilege escalation attack, it is necessary to restrict the permissions granted to the Cloud Build Service Account and to be careful granting the cloudbuild.builds.create permission to any users in your Organization. Most importantly, you need to know that any user who is granted cloudbuild.builds.create, is also indirectly granted all the permissions granted to the Cloud Build Service Account. If that’s alright with you, then you may not need to worry about this attack vector, but it is still highly recommended to modify the default permissions granted to the Cloud Build Service Account.

Raw source GCP CloudBuild Potential Privilege Escalation · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
LogTypes:
  - GCP.AuditLog
Description:
  Detects privilege escalation attacks designed to gain access to the Cloud Build Service Account.
  A user with permissions to start a new build with Cloud Build can gain access to the Cloud Build Service Account
  and abuse it for more access to the environment.
DisplayName: "GCP CloudBuild Potential Privilege Escalation"
RuleID: "GCP.CloudBuild.Potential.Privilege.Escalation"
Enabled: true
Filename: gcp_cloudbuild_potential_privilege_escalation.py
Reference: https://rhinosecuritylabs.com/gcp/iam-privilege-escalation-gcp-cloudbuild/
Runbook:
  Confirm this was authorized and necessary behavior. To defend against this privilege escalation attack,
  it is necessary to restrict the permissions granted to the Cloud Build Service Account and to be careful granting
  the cloudbuild.builds.create permission to any users in your Organization. Most importantly, you need to know that
  any user who is granted cloudbuild.builds.create, is also indirectly granted all the permissions granted to the
  Cloud Build Service Account. If that’s alright with you, then you may not need to worry about this attack vector,
  but it is still highly recommended to modify the default permissions granted to the Cloud Build Service Account.
Reports:
  MITRE ATT&CK:
    - TA0004:T1548 # Abuse Elevation Control Mechanism
Severity: High
DedupPeriodMinutes: 60
Threshold: 1
Tests:
  - Name: GCP CloudBuild - Build with Potentially Privileged Access
    ExpectedResult: true
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "operation":
          {
            "first": true,
            "id": "operations/build/some-project/YzNhZWI0YWYtNjAwNi00YzM5LTgxYmUtMjhmMjc1YzJkOGEz",
            "producer": "cloudbuild.googleapis.com",
          },
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo":
              {
                "principalEmail": "whodoneit@some-project.iam.gserviceaccount.com",
                "principalSubject": "serviceAccount:whodoneit@some-project.iam.gserviceaccount.com",
                "serviceAccountKeyName": "//iam.googleapis.com/projects/some-project/serviceAccounts/whodoneit@some-project.iam.gserviceaccount.com/keys/123er456788",
              },
            "authorizationInfo":
              [
                {
                  "granted": true,
                  "permission": "cloudbuild.builds.create",
                  "resource": "projects/some-project",
                  "resourceAttributes": {},
                },
              ],
            "methodName": "google.devtools.cloudbuild.v1.CloudBuild.CreateBuild",
            "request":
              {
                "@type": "type.googleapis.com/google.devtools.cloudbuild.v1.CreateBuildRequest",
                "build": {},
                "projectId": "some-project",
              },
            "requestMetadata":
              {
                "callerIP": "189.163.74.177",
                "callerSuppliedUserAgent": "(gzip),gzip(gfe),gzip(gfe)",
                "destinationAttributes": {},
                "requestAttributes":
                  { "auth": {}, "time": "2024-01-25T11:55:09.740095Z" },
              },
            "resourceLocation": { "currentLocations": ["global"] },
            "resourceName": "projects/some-project/builds",
            "serviceName": "cloudbuild.googleapis.com",
          },
        "receiveTimestamp": "2024-01-25 11:55:09.854909113",
        "resource":
          {
            "labels":
              {
                "build_id": "c3aeb4ap-6006-4c39-81be-28f275c2d8a3",
                "build_trigger_id": "",
                "project_id": "some-project",
              },
            "type": "build",
          },
        "severity": "NOTICE",
        "timestamp": "2024-01-25 11:55:08.919358000",
      }
  - Name: GCP CreateBrand - No Privileged Access
    ExpectedResult: false
    Log:
      {
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo":
              { "principalEmail": "john.doe@some-project.com" },
            "authorizationInfo":
              [
                {
                  "granted": true,
                  "permission": "clientauthconfig.brands.create",
                  "resource": "brands/1028347248702",
                  "resourceAttributes": {},
                },
              ],
            "methodName": "CreateBrand",
            "request":
              {
                "brand":
                  {
                    "displayName": "NewBrand",
                    "projectNumbers": ["1028345275902"],
                    "supportEmail": "john.doe@some-project.com",
                  },
                "visibility": "INTERNAL",
              },
            "requestMetadata":
              {
                "callerIP": "189.163.74.177",
                "destinationAttributes": {},
                "requestAttributes":
                  { "auth": {}, "time": "2024-01-24T14:30:05.891336Z" },
              },
            "resourceName": "brands/1028347275902",
            "response":
              {
                "brandId": "1028347248702",
                "creationTime": "2024-01-24T14:30:05.400Z",
                "displayName": "NewBrand test",
                "projectNumbers": ["1028347248702"],
                "supportEmail": "some.user@company.com",
                "updateTime": "2024-01-24T14:30:05.866002Z",
              },
            "serviceName": "clientauthconfig.googleapis.com",
            "status": {},
          },
        "receiveTimestamp": "2024-01-24 14:30:06.741210353",
        "resource":
          {
            "labels":
              { "brand_id": "1028347248702", "project_id": "some-project" },
            "type": "client_auth_config_brand",
          },
        "severity": "NOTICE",
        "timestamp": "2024-01-24 14:30:05.207884000",
      }


# ------ paired body: gcp_cloudbuild_potential_privilege_escalation.py ------

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    if not event.deep_get("protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith(
        "CloudBuild.CreateBuild"
    ):
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    # Get the principal (actor) email
    principal = event.deep_get("protoPayload", "authenticationInfo", "principalEmail", default="")

    # Skip whitelisted service accounts
    if principal.endswith("@gcf-admin-robot.iam.gserviceaccount.com"):
        return False

    # Check if build.create permission was granted
    for auth in authorization_info:
        if auth.get("permission") == "cloudbuild.builds.create" and auth.get("granted") is True:
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    operation = event.deep_get("protoPayload", "methodName", default="<OPERATION_NOT_FOUND>")
    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]"


def alert_context(event):
    return gcp_alert_context(event)

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.