Skip to content

Google Workspace Enumeration

WebbinRoot edited this page Jul 10, 2026 · 1 revision

Google Workspace Enumeration

Enumerate a Google Workspace / Cloud Identity tenant (groups, users, admin roles, org units, domains, mobile devices, 3rd‑party OAuth grants) from GCPwn, and surface the GCP → Workspace takeover path (service‑account domain‑wide delegation) in OpenGraph.

Naming note: GCPwn's own workspace is your local session/credential container (see Workspace Instructions). This page is about Google Workspace, the SaaS tenant — a different thing that happens to share the word.


Why it's separate from GCP enumeration

Google Workspace and GCP IAM are two independent privilege systems:

  • A credential's GCP IAM role (e.g. roles/viewer at the org) governs GCP resources — it has no bearing on Workspace.
  • Workspace access comes from either a Workspace admin user or a service account with domain‑wide delegation (DWD) impersonating an admin.

So a service account that is only a GCP viewer can, via DWD, act as a Workspace super‑admin — a takeover path that normal GCP IAM enumeration never shows. GCPwn models exactly this (see OpenGraph below).

Workspace is tenant‑scoped (by directoryCustomerId, e.g. C0xxxxxxx), not per‑project — so these modules run once, not per project.


Access model — pick one

Path Credential Setup
Admin user OAuth creds for a Workspace super‑admin user None — works directly
Service account + DWD A service account authorized for domain‑wide delegation The steps below
A Workspace user's own OAuth Any user's own token (incl. a Workspace‑only user with no GCP access) --token-file (load a user token) — see below

The service‑account path is the common offensive scenario (you rarely hold an admin's live token). What a DWD service account can do is (the OAuth scopes authorized for it) × (the privileges of the user it impersonates). Read‑only scopes ⇒ read‑only; impersonate a super‑admin ⇒ full‑tenant reach.

Using a user's own token (no service account). Google has no username/password API — you cannot POST an email+password and get a token (ClientLogin/ROPC don't exist for Google identities). A built-in interactive creds login browser flow is planned but not currently shipped; for now, mint an authorized_user token out-of-band (e.g. gcloud auth application-default login in a browser) and load it directly. This is the path for a Workspace‑only identity (e.g. a super‑admin with no GCP roles):

creds add CRED_NAME --type oauth2 --token-file token.json   # authorized-user json (refresh token, auto-renews)
creds add CRED_NAME --type oauth2 --token ya29.<...>        # bare access token (expires ~1h, no refresh)

Whatever scopes the token consented to are what you get — request the Drive / Directory / Groups scopes when you mint it if you plan to run those modules.


Setting up service‑account domain‑wide delegation

You do this once, in the Google Workspace Admin console (only a Workspace super‑admin can). GCPwn cannot do this step for you.

  1. Get the service account's OAuth client ID — the numeric client_id in the SA key JSON ("client_id" field), or GCP Console → IAM & Admin → Service Accounts → the SA → Unique ID.

  2. Authorize that client ID at admin.google.com → Security → Access and data control → API controls → Domain‑wide delegation → Manage Domain Wide Delegation → Add new:

    • Client ID: the numeric client ID from step 1
    • OAuth scopes (comma‑separated — the read‑only scopes GCPwn's modules request):
      https://www.googleapis.com/auth/cloud-identity.groups.readonly,
      https://www.googleapis.com/auth/admin.directory.user.readonly,
      https://www.googleapis.com/auth/admin.directory.group.readonly,
      https://www.googleapis.com/auth/admin.directory.group.member.readonly,
      https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly,
      https://www.googleapis.com/auth/admin.directory.domain.readonly,
      https://www.googleapis.com/auth/admin.directory.orgunit.readonly,
      https://www.googleapis.com/auth/admin.directory.device.mobile.readonly,
      https://www.googleapis.com/auth/admin.directory.user.security,
      https://www.googleapis.com/auth/apps.groups.settings,
      https://www.googleapis.com/auth/admin.datatransfer.readonly,
      https://www.googleapis.com/auth/drive.readonly
      

    drive.readonly is only needed for the Drive module (enum_drive, list + download) and also requires the Drive API enabled in step 3; omit it if you're not touching Drive. DWD is all‑or‑nothing on the token request: if GCPwn requests a scope you did not authorize here, that call fails. Authorize the whole list. Each directory sub‑API needs its own scope (e.g. domains ⇒ admin.directory.domain.readonly), and GCPwn requests them per module.

  3. Enable the APIs in the service account's GCP project: Cloud Identity API and Admin SDK API.

  4. Choose a Workspace admin to impersonate — the subject. DWD makes the SA act as this user, so for tenant‑wide directory listing it must be an admin (a super‑admin covers everything). Find one at admin.google.com → Directory → Users, filter by Admin role → Super Admin.


Configuring GCPwn

Load the service‑account key as a credential (creds add), then set the two Workspace settings:

configs set workspace_admin_subject admin@DOMAIN       # the admin to impersonate (SA DWD)
configs set workspace_customer_id   C0xxxxxxx          # optional — GCPwn auto-resolves it from the org
  • workspace_admin_subject — the persistent DWD subject. Equivalent per‑run flag: --impersonate admin@DOMAIN (accepted by every Workspace module; overrides the config for that run). For a user credential this is a no‑op — an admin user already works directly.
  • workspace_customer_id — the directoryCustomerId. GCPwn resolves it automatically from the GCP organization (organizations.get) when the SA has resourcemanager.organizations.get; set it explicitly if it can't be derived.

Resolution order for the subject: --impersonateconfigs set workspace_admin_subject → none.


Running it

Run everything once:

modules run enum_google_workspace

enum_google_workspace prints an up‑front status line (customer=..., impersonating=...) and, if no tenant resolves, one actionable pointer instead of a wall of 403s. Or run modules individually:

Module Surfaces Attack‑path value
enum_cloud_identity Groups, group memberships, users Membership graph; who's in privileged groups
enum_admin_roles Admin roles + who holds each (assignments) Highest — finds super‑admins (full‑tenant control)
enum_oauth_tokens Per‑user 3rd‑party OAuth app grants Over‑privileged app grants (e.g. an app with Gmail/Drive scopes = data exfil)
enum_group_settings Per‑group access/posting settings Externally‑open / anyone‑can‑post / anyone‑can‑join groups
enum_data_transfers Data‑ownership transfer requests Offboarding data movement (who inherited a departed user's data)
enum_org_units Organizational units Tenant structure / policy scoping
enum_domains Verified domains Tenant footprint
enum_mobile_devices Enrolled mobile devices Device inventory

Useful flags:

modules run enum_cloud_identity --groups --memberships --users        # pick components
modules run enum_cloud_identity --query "…" --transitive              # groups.search + nested memberships
modules run enum_admin_roles                                          # roles + assignments (run --users first for readable emails)
modules run enum_oauth_tokens --user-key user@DOMAIN                   # one user (default: all cached workspace_users)
modules run enum_google_workspace --impersonate admin@DOMAIN             # per-run subject

Ordering note: enum_oauth_tokens scans the cached workspace_users table — run enum_cloud_identity --users first (or pass --user-key). enum_admin_roles resolves assignee IDs to emails from workspace_users, so --users first makes "who holds super‑admin" readable.

Results land in the workspace‑scoped tables (workspace_groups, workspace_users, workspace_admin_roles, workspace_role_assignments, workspace_domains, workspace_org_units, workspace_mobile_devices, workspace_oauth_tokens) — query/export them via the data commands.


Google Drive: enumerate exposure, then exfiltrate content

Drive is a per‑user data plane — you read one user's Drive at a time. With a service account that means DWD impersonating the target user directly (--caller-email is the DWD subject, not an admin subject); with a user's own OAuth credential you read that user's own Drive. Needs the drive.readonly scope (above) and the Drive API enabled. enum_drive is deliberately not part of enum_google_workspace (it's per‑user and can be large) — run it explicitly.

1) Enumerate files + sharing (enum_drive) — records file metadata and an exposure label (public / anyone_with_link / external / domain / private) to workspace_drive_files, and every ACL entry to workspace_drive_permissions:

modules run enum_drive --caller-email user@DOMAIN            # one user's Drive
modules run enum_drive --all-users                           # every cached workspace_users Drive (run enum_cloud_identity --users first)
modules run enum_drive --caller-email user@DOMAIN --query "mimeType!='application/vnd.google-apps.folder'" --max-files 500

2) Download content (enum_drive --download) — a modern, logged take on GD‑Thief, folded into enum_drive. The same run lists the target Drive(s) and then downloads from what it cached, so you target by exposure. Two switches:

  • --download-public — download only public / anyone‑with‑link files (readable by any identity, so they pull without special access).
  • --download — download every listed candidate file.

Three download tiers (with --download):

Tier Flag Behavior
Everything (none) download every candidate file
Focused --focused only interesting extensions/types by NAME (docs, configs, keys, archives, …)
Secret‑scan --secret-regex [PATTERN…] download, scan CONTENT, keep only files that hit a secret pattern — no value = built‑in set (AWS/GCP/Slack/GitHub keys, private keys, password= …); values add custom patterns

Candidate filters: --only-external, --exposure <level>, --owner <email>, --limit <n>.

modules run enum_drive --caller-email user@DOMAIN --download-public          # only anyone/anyone-with-link files
modules run enum_drive --all-users --download --focused --limit 50
modules run enum_drive --caller-email user@DOMAIN --download --secret-regex   # keep only files containing a secret
modules run enum_drive --caller-email user@DOMAIN --download --secret-regex "AcmeCorp-[A-Z0-9]{20}"   # + a custom pattern

Download as a different user. Google has no raw email/password auth, so an "email/password user" is always a stored OAuth token (creds add --type oauth2 --token-file). By default files are pulled as the identity you listed under (your current user, or the --caller-email user via DWD). To use another identity:

modules run enum_drive --all-users --download --downloader-cred alice_login   # download AS a stored credential
modules run enum_drive --all-users --download --downloader-creds-all          # try EVERY stored credential per file

--downloader-cred <credname> downloads as one stored credential; --downloader-creds-all tries every stored credential per file until one can read it (and the [DOWNLOAD] line reports which one worked). Need a raw token instead? Store it first — creds add <name> --type oauth2 --token ya29.<...> — then reference it.

Blue‑team note. Every download emits a greppable [DOWNLOAD] downloader=… file=… id=… exposure=… bytes=… -> <path> line, and --throttle <seconds> paces requests so the Drive audit‑log pattern (a burst of drive.file.download / view events from one actor) is easy to spot. Files land under the workspace downloads/drive/… output directory. Use this in a lab to generate and study those detections.


OpenGraph: the DWD attack path

When a service account successfully impersonates an admin, GCPwn records that it holds domain‑wide delegation to the tenant (the workspace_delegations table). OpenGraph then emits:

  • a GoogleWorkspaceTenant node (workspace_tenant:C0xxxxxxx),
  • DOMAIN_WIDE_DELEG edges from the service‑account node to every user it can impersonate,
  • DELEGATES_INTO (SA → tenant) and WORKSPACE_MEMBER (user → tenant) edges,

on top of the existing workspace principal edges (GOOGLE_MEMBER_OF, and CAN_IMPERSONATE/CAN_RESET_PASSWORD from super‑admin role assignments). Build it with modules run process_og_gcpwn_data after enumerating.

Two more Workspace exposure paths land in the same graph:

  • Self‑join‑open groups (enum_group_settings) → a CAN_JOIN edge into each open group from GCPAllUsers (anyone‑can‑join) or a per‑tenant PrincipalsInOrg node (all‑in‑domain‑can‑join) — a principal who can self‑join inherits everything the group holds.
  • Public / anyone‑with‑link Drive files (enum_drive) → a GoogleDriveFile node with a CAN_READ edge from GCPAllUsers (anonymous), tying Drive data exposure into the same graph as public buckets.

This makes the takeover chain a first‑class path in BloodHound: service account (maybe only a GCP viewer) → DOMAIN_WIDE_DELEG → super‑admin user → GCP resources — a pivot that never appears in GCP IAM alone. These additions are purely additive and only appear once a delegation is proven (graphs without Workspace data are unchanged).


Troubleshooting

  • Every module 403s / empty tables — the SA isn't authorized for DWD (or the wrong subject). Re‑check step 2 (client ID + scopes) and that the subject is a real admin. enum_google_workspace prints a prerequisites pointer when no tenant resolves.
  • unauthorized_client on the token request — a requested scope isn't authorized for the client ID; authorize the full scope list in step 2.
  • Some modules work, others 403 — you authorized a partial scope set; each directory sub‑API needs its own admin.directory.*.readonly scope.
  • No directoryCustomerId — set it explicitly: configs set workspace_customer_id C0xxxxxxx.
  • Admin‑role assignees show numeric IDs — run enum_cloud_identity --users first so IDs resolve to emails (group/SA assignees stay numeric).
  • OAuth tokens denied for every useradmin.directory.user.security is a separately elevated scope; authorize it for the DWD client (GCPwn stops after the first denial instead of spamming).

See also: Authentication Reference · CLI Module Reference · OpenGraph Overview

Clone this wiki locally