Skip to content

This tool is not affiliated with, endorsed by or sponsored by Google LLC. Google Cloud and Google Cloud Platform are trademarks of Google LLC. Other names are trademarks of their respective owners.

SetIamPolicy abuse: finding owner grants and outsiders

Read SetIamPolicy audit entries like an investigator: bindingDeltas, Owner granted to Gmail accounts, unknown domains, allUsers, Token Creator, org policies.

Published on 5 min read

TL;DR. Every change to an IAM policy is an Admin Activity entry, and the useful part is not the full policy in the request but the diff: serviceData.policyDelta.bindingDeltas (or metadata.policyDelta, metadata.datasetChange for BigQuery), with action, role and member per change. Look for ADD of roles/owner, roles/editor or IAM admin roles, of consumer accounts (@gmail.com), of domains that never appear among your callers, of allUsers, and of the Token Creator / Service Account User roles.

Once an attacker has a credential with setIamPolicy permission, the first thing they usually do is give themselves a durable identity they control. In MITRE ATT&CK terms this is T1098.003, Additional Cloud Roles. It is also the easiest step to spot, because Admin Activity logs are always on and kept 400 days.

What a SetIamPolicy entry contains

gcloud projects add-iam-policy-binding looks like a small change, but the API is read-modify-write: the client calls GetIamPolicy, edits the policy locally and sends the whole thing back with SetIamPolicy. The request therefore contains the entire new policy. Reading it tells you the result, not what changed.

The change is in the policy delta:

"serviceData": {
  "@type": "type.googleapis.com/google.iam.v1.logging.AuditData",
  "policyDelta": {
    "bindingDeltas": [
      { "action": "ADD", "role": "roles/owner", "member": "user:someone@gmail.com" }
    ]
  }
}

Depending on the service and API version, the same structure appears under protoPayload.metadata.policyDelta, or, for BigQuery datasets and tables, under metadata.datasetChange.bindingDeltas / metadata.tableChange.bindingDeltas (BigQuery audit logs).

The method name also varies by resource:

ResourceTypical methodName
Project, folder, organizationSetIamPolicy (service cloudresourcemanager.googleapis.com)
Cloud Storage bucketstorage.setIamPermissions
Service accountgoogle.iam.admin.v1.SetIAMPolicy
Compute image, snapshot, diskv1.compute.images.setIamPolicy, …snapshots.setIamPolicy
BigQuery dataset or tableDataset update or table IAM methods; the delta sits in metadata.datasetChange / metadata.tableChange

So search on the delta, not only on one method name.

The five grants that matter

1. Owner, Editor and IAM administration roles

roles/owner and roles/editor are the basic roles with (near) full control (roles overview). roles/resourcemanager.projectIamAdmin, roles/resourcemanager.organizationAdmin and roles/iam.securityAdmin are just as dangerous because they allow granting anything else. Any ADD of these during an incident window is a lead.

2. Consumer accounts

A member ending with @gmail.com or @googlemail.com is a personal Google account. Attackers like them: free, anonymous, and they survive the rotation of every credential in your organisation. ADD roles/owner user:x@gmail.com is, unless it is a documented break-glass account, a takeover.

3. Unknown domains

Less obvious: a user: or group: member from a domain that never appears as a caller in your logs. It can be a contractor; it can be the attacker's own Workspace tenant. The domain restricted sharing constraint (iam.allowedPolicyMemberDomains) prevents both.

4. allUsers and allAuthenticatedUsers

These special principals make a resource public. On a bucket, it means anyone on the Internet can read the objects. See allUsers and the exfiltration article.

5. Impersonation roles

roles/iam.serviceAccountTokenCreator, roles/iam.serviceAccountUser, roles/iam.serviceAccountKeyAdmin and roles/iam.workloadIdentityUser let the member act as service accounts. They are quieter than Owner and often go unnoticed. See service account impersonation.

Changes around the policy

Two more Admin Activity events belong in the same review:

  • Organization policy changes (google.cloud.orgpolicy.v2.OrgPolicy.CreatePolicy, UpdatePolicy, DeletePolicy, or the older SetOrgPolicy / ClearOrgPolicy). An attacker who relaxes iam.allowedPolicyMemberDomains before adding a Gmail account, or iam.disableServiceAccountKeyCreation before creating keys, is preparing the next step (T1484).
  • Custom roles (google.iam.admin.v1.CreateRole, UpdateRole, UndeleteRole). A role called "Viewer (legacy)" can contain iam.serviceAccountKeys.create. Rhino Security Labs' research on GCP privilege escalation lists iam.roles.update among the escalation paths.

Audit configuration changes (auditConfigDeltas) also travel through SetIamPolicy; they are covered in defense evasion.

Investigating a suspicious grant

For each suspicious delta, answer:

  1. Who made the call? authenticationInfo.principalEmail, and serviceAccountKeyName or serviceAccountDelegationInfo if present. A grant made by a CI service account with a key, from a VPS, is not a human mistake.
  2. From where? requestMetadata.callerIp and user agent. Compare with the principal's usual IPs.
  3. What happened next? Search for the new member as principalEmail. An account that receives Owner and then acts from the same IP within minutes closes the loop.
  4. Is it still there? The latest SetIamPolicy responses, or a current gcloud projects get-iam-policy, tell you whether the binding was removed.
  5. What else changed? Look for REMOVE deltas too: attackers sometimes remove legitimate owners.

A Logs Explorer query that catches most project-level changes:

logName:"cloudaudit.googleapis.com%2Factivity"
protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD"

What the analyzer flags

The rules in the analyzer evaluate every delta separately, whatever the resource:

RuleSeverityCondition on a delta
owner_to_consumerCriticalADD of roles/owner or roles/editor to a @gmail.com / @googlemail.com member
public_access_grantedCriticalADD of allUsers or allAuthenticatedUsers
owner_editor_grantedHighADD of Owner, Editor, Organization Admin, Project IAM Admin or Security Admin
consumer_account_grantedHighADD of any consumer account
token_creator_grantedHighADD of Token Creator, Service Account User, Key Admin or Workload Identity User
new_external_domain (analytic)MediumADD of a member from a domain never seen among the callers
org_policy_changedMediumOrganization policy created, updated or deleted
custom_role_changedLowCustom role created, updated or undeleted

Targets are shown as readable deltas (ADD roles/owner user:…), and the Entities tab marks consumer accounts as "personal Gmail".

Remediation

Remove the bindings, then check the whole hierarchy (project, folders, organization) for other additions; enforce domain restricted sharing; review custom roles; and restore any organization policy that was relaxed. Google's compromised credentials guide covers the identity side.

Frequently asked questions

How do I see who granted Owner on a Google Cloud project?

Query Admin Activity audit logs for protoPayload.methodName="SetIamPolicy" and read protoPayload.serviceData.policyDelta.bindingDeltas: each delta has an action (ADD or REMOVE), a role and a member. The caller is protoPayload.authenticationInfo.principalEmail.

Is a gmail.com account in an IAM policy always malicious?

No, some small organisations use personal accounts, and break-glass accounts exist. But a consumer account receiving roles/owner during an incident is the classic takeover move, and the iam.allowedPolicyMemberDomains policy exists to prevent it.

Further reading

Related articles

This tool is not affiliated with, endorsed by or sponsored by Google LLC. Google Cloud and Google Cloud Platform are trademarks of Google LLC. Other names are trademarks of their respective owners.