diff --git a/SUMMARY.md b/SUMMARY.md index 008efc3d05..da8525613e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -178,6 +178,7 @@ * [GCP - Workflows Enum](pentesting-cloud/gcp-security/gcp-services/gcp-workflows-enum.md) * [GCP <--> Workspace Pivoting](pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md) * [GCP - Understanding Domain-Wide Delegation](pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/gcp-understanding-domain-wide-delegation.md) + * [GCP - Workspace Add-on Deployment Takeover](pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/gcp-workspace-addons-privesc.md) * [GCP - Unauthenticated Enum & Access](pentesting-cloud/gcp-security/gcp-unauthenticated-enum-and-access/README.md) * [GCP - API Keys Unauthenticated Enum](pentesting-cloud/gcp-security/gcp-unauthenticated-enum-and-access/gcp-api-keys-unauthenticated-enum.md) * [GCP - App Engine Unauthenticated Enum](pentesting-cloud/gcp-security/gcp-unauthenticated-enum-and-access/gcp-app-engine-unauthenticated-enum.md) diff --git a/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md index c6354c2633..d2f07aa60b 100644 --- a/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md +++ b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md @@ -179,9 +179,17 @@ If an attacker has complete access over GWS he will be able to access groups wit ### Google Groups Privilege Escalation -By default users can **freely join Workspace groups of the Organization** and those groups **might have GCP permissions** assigned (check your groups in [https://groups.google.com/](https://groups.google.com/)). +Workspace or Cloud Identity groups might have GCP IAM roles assigned. If a low-privilege user can join one of those groups, request membership, control a nested group, or convince an owner/manager to add them, the inherited group role becomes a Workspace-to-GCP escalation path (check visible groups in [https://groups.google.com/](https://groups.google.com/)). -Abusing the **google groups privesc** you might be able to escalate to a group with some kind of privileged access to GCP. +Do **not** assume every visible group is joinable. The effective options depend on tenant-wide Groups for Business policy and the individual group's **Who can join**, external-member, owner/manager, and nested-membership settings. For example, an administrator can force external members off, while a group can be set to invited users only, organization users only, or organization users can ask. Confirm the exact setting and membership propagation before reporting an exploitable path. + +### Workspace HTTP Add-on Deployment Takeover + +A GCP principal with `gsuiteaddons.deployments.update` can replace an existing HTTP add-on deployment endpoint. An already-installed and authorized user's later invocation can send the replacement endpoint the event and a token limited to scopes that user already granted. This is conditional and is not arbitrary access to every Workspace user. + +{% content-ref url="gcp-workspace-addons-privesc.md" %} +[gcp-workspace-addons-privesc.md](gcp-workspace-addons-privesc.md) +{% endcontent-ref %} ### References diff --git a/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/gcp-workspace-addons-privesc.md b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/gcp-workspace-addons-privesc.md new file mode 100644 index 0000000000..8a133807fe --- /dev/null +++ b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/gcp-workspace-addons-privesc.md @@ -0,0 +1,93 @@ +# GCP - Workspace Add-on Deployment Takeover + +{% hint style="warning" %} +This is a **conditional GCP-to-Workspace pivot**, not a tenant-wide OAuth bypass. The target must be an existing HTTP Google Workspace add-on deployment, a user must already have installed and authorized it, and that user must invoke it after the change. The token is limited to that user's already-authorized scopes. +{% endhint %} + +## `gsuiteaddons.deployments.update` + +Google Workspace HTTP add-on deployments keep their endpoint and OAuth scopes in a GCP project. A principal with only `gsuiteaddons.deployments.update` can replace the deployment definition, including the HTTP function URL. It does not need `gsuiteaddons.deployments.get` or `gsuiteaddons.deployments.list` when the deployment resource name is already known. + +This matters because an HTTP add-on invocation contains an `authorizationEventObject`. For scopes the user already authorized, Google documents a `userOAuthToken` in that object. Repointing a trusted deployment to an attacker-controlled HTTPS endpoint can therefore expose future add-on events and the invoking user's existing scoped token. + +The permission is included in roles such as `roles/gsuiteaddons.admin`, `roles/gsuiteaddons.developer`, and `roles/appmetadata.workspaceMarketplaceAppConfigurationAdmin`. + +### Preconditions + +* A known project number and deployment ID. Listing the deployment is useful but is not required. +* The deployment uses an HTTP endpoint. Apps Script deployments have a different execution boundary. +* The victim already installed and authorized the add-on. +* The victim invokes an affected trigger after the replacement. +* The previously authorized scopes provide useful access. Adding scopes does not silently bypass OAuth consent. + +{% hint style="danger" %} +Do not interpret this permission as access to every user's Drive or Gmail. Impact is per installed/authorized user, per granted scope, and requires a later invocation. +{% endhint %} + +### Enumerate safely + +If listing is allowed: + +```bash +gcloud workspace-add-ons deployments list --project "$PROJECT_ID" +gcloud workspace-add-ons deployments describe "$DEPLOYMENT_ID" \ + --project "$PROJECT_ID" --format=json +``` + +If listing is denied, use deployment IDs recovered from source code, CI configuration, audit logs, documentation, Terraform state, or an application inventory. Test the exact update permission without changing anything: + +```bash +curl -sS -X POST \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + -H 'Content-Type: application/json' \ + "https://cloudresourcemanager.googleapis.com/v3/projects/${PROJECT_ID}:testIamPermissions" \ + -d '{"permissions":["gsuiteaddons.deployments.update"]}' +``` + +### Authorized-lab proof + +First preserve the full original deployment document. A replacement is a full `PUT`, so omitting fields can break the add-on. + +```bash +gcloud workspace-add-ons deployments describe "$DEPLOYMENT_ID" \ + --project "$PROJECT_ID" --format=json > original-deployment.json +``` + +Change only the intended `runFunction` URL in a copy, retain the original name, scopes, add-on sections, and HTTP options, then update it: + +```bash +ACCESS_TOKEN="$(gcloud auth print-access-token)" +PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')" + +curl -sS -X PUT \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + "https://gsuiteaddons.googleapis.com/v1/projects/${PROJECT_NUMBER}/deployments/${DEPLOYMENT_ID}" \ + --data-binary @replacement-deployment.json +``` + +In a disposable lab, make the new endpoint record only whether a token exists, a one-way token hash, the request route, and the returned scope set. Do not read mailbox or Drive content merely to prove delivery. Restore the original deployment immediately after the test. + +The permission boundary was reproduced in September 2026 with a custom role containing only `gsuiteaddons.deployments.update`: + +* deployment `GET`: `403 PERMISSION_DENIED` +* deployment `LIST`: `403 PERMISSION_DENIED` +* deployment `PUT` changing `/victim` to `/attacker`: `200 OK` +* project `testIamPermissions`: returned only `gsuiteaddons.deployments.update` + +The same test also confirmed that a development deployment could not be installed by a user outside the owning Workspace organization, even after granting that user the project-level install permission. This is a useful boundary, but it does not protect already-installed users in the owning organization from an endpoint replacement. + +### Detection and hardening + +* Restrict the three add-on administration roles and custom roles containing `gsuiteaddons.deployments.update`. +* Alert on `UpdateDeployment` activity and unexpected endpoint, OAuth-scope, or manifest changes. +* Inventory add-on endpoints and link them to the Cloud Run, Cloud Functions, or external service that serves them. A runtime takeover can have the same downstream effect even when the deployment object itself is unchanged. +* Require deployment changes through reviewed infrastructure-as-code and compare the complete deployment, not only the endpoint. +* Keep scopes minimal and uninstall unused development deployments. + +### References + +* [Google Workspace Add-ons API: `replaceDeployment`](https://developers.google.com/workspace/add-ons/reference/rest/v1/projects.deployments/replaceDeployment) +* [HTTP add-on authorization event object and `userOAuthToken`](https://developers.google.com/workspace/add-ons/guides/alternate-runtimes#authorization_event_object) +* [Google Workspace Add-ons roles and permissions](https://cloud.google.com/iam/docs/roles-permissions/gsuiteaddons) +* [Deployment resource, triggers, and HTTP options](https://developers.google.com/workspace/add-ons/reference/rest/v1/projects.deployments)