Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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)