Per GCP IAM documentation, Google Groups are valid principals for IAM policy bindings in Google Cloud. Google also recommends using Groups when granting roles in GCP, as opposed to users. Groups can include groups outside of organizations like devs@googlegroups.com or groups in an Organization like admins@yourorg.com. Google Groups can be managed via https://groups.google.com/ and optionally through the Google Cloud Console.

Google Groups can be configured with various access settings at both the specific group and organization level. One important access setting is “Who can join group”.  Most organizations will have anonymous internet access disabled by default, which leaves three common organization level settings: Only invited users, Anyone in the organization can ask, and Anyone in the organization can join.

This blog will detail how an attacker can escalate their privileges in Google Cloud by leveraging weak group join settings for groups that have been granted roles in GCP. Opportunities for Hunting and Detection are provided towards the end of the blog.

TL;DR

  • A user that is a member of the organization can potentially escalate their privileges into Google Cloud if:
    • A Google Group has been specified as a principal member in an IAM policy AND
    • the Google Group has been configured with open access permissions that allow any member of the organization to join the group.
  • Google Groups created via the main Groups console can be granted permissions within Google Cloud IAM Policy, even when the group has been configured with “Entire organization – can join group” access settings.
  • There does not appear to be any explicit, default guardrails in place to prevent administrators from assigning roles in GCP to Groups with open join settings.
  • This was reported to Google as a potential Privilege Escalation vector via Bug Hunters VRP. The report resulted in a classification of Type “Bug” and a Status of “Won’t Fix (Intended Behavior)”.

Previous Research

Finding vulnerable Groups

As noted in the TL;DR, we need to find open groups that also have been granted roles in Google Cloud. This process is made a lot easier if you already have read access to list IAM Policy, so that you can target only the groups that actually have permissions. Without this extra information, you will be stuck with attempting to list and check every group.

From an offensive perspective, the main Google Groups console is probably the easiest way to quickly identify open groups. A member user, Developer Tools, and a little background research are all that is needed to get started.

Browsing to the Google Groups page with browser Dev Tools open shows requests related to a batchexecute endpoint.

Decoded URL
https://groups.google.com/u/2/_/GroupsFrontendUi/data/batchexecute?rpcids=rCA4W&source-path=/u/2/recent…

Decoded POST body
f.req=[[["rCA4W","[]",null,"generic"]]]…

Response body
)]}'

104
[["wrb.fr","rCA4W","[]",null,null,null,"generic"],…

Ryan Kovatch’s blog does a great job of explaining the format of the requests and responses for this endpoint. The main takeaway here is that we want to identify the particular rpcid that returns group settings information. This can be identified by browsing to the All Groups section and paginating through as many pages as possible to view every group in the organization. A batchexecute request containing the zx9ptd rpcid should be present for every group listed.

Decoded URL
https://groups.google.com/u/2/_/GroupsFrontendUi/data/batchexecute?rpcids=zx9ptd&source-path=/u/2/all-groups&…

Decoded POST body
f.req=[[["zx9ptd","[\"demo-open-join@thisisnotarealorg.com\"]",null,"generic"]]]…

Response body
)]}'

192
[["wrb.fr","zx9ptd","[[\"110157945035653151646\",\"demo-open-join@thisisnotarealorg.com\"],[true,false,true],0]",null,null,null,"generic"],…

This request looks promising and the rpcid can be looked up by doing a quick search in the Dev Tools console.

…
_.gBa = new _.Oe("zx9ptd",_.fu,_.hu,[{
    key: _.Cj,
    value: !0
}, {
    key: _.Ej,
    value: "/GroupsFrontendService.GetJoinPermissions"
}]);
…

Looking back at the responses for the zx9ptd request, the access settings of the Group can be determined by comparing an open group vs a closed group. The open group contains the following highlighted string in the response. This can then be used to identify open groups at scale by searching for a common group keyword and then paging through the groups. This string can then be searched via the Browser Developer Tools to find open groups.

Entire organization – can join group

…,\"demo-open-join@thisisnotarealorg.com\"],[true,false,true],0]"…

Entire organization – can ask to join group

…\"test1@thisisnotarealorg.com\"],[false,true,true],0]"…

Invited users – can join group

…\"test2@thisisnotarealorg.com\"],[false,false,true],0]"…

While extremely rudimentary, this method can work at scale when you have thousands of groups to review. Manual checks can also be done easily by checking for a Join icon when browsing through the list of groups. A group with open join permissions will look like below.

Escalating into Google Cloud

The following example assumes that the demo-user is a normal member organization user with no permissions in Google Cloud. This is confirmed by browsing to the Cloud Console and attempting to list Projects in the Project chooser.

The demo-open-join Group does have permissions in Google Cloud and has been previously configured with open join settings (see the last screenshot in the previous section). This group is a standard group created via the Groups UI and has been granted the Storage Admin Role on the entire prj-demo project in GCP.

The demo-user can join the demo-open-join group by clicking into the group’s settings and clicking on the “Join group” button. The normal member user has successfully joined the group.

The demo-user can then refresh their session to the Cloud Console after a few minutes and will see a new project. Digging further into the console, the user will also be able to see buckets. Since the Storage Admin role has been granted to the demo-open-join group, any user in this group inherits these permissions.

This is a very simple example, but it demonstrates the risk around groups with open join permissions when they are granted roles in Google Cloud. Not every scenario will be this straightforward and impact depends entirely on the role and the scope of the permissions granted to the group.

Prevention and Remediation

The best way to prevent accidental role grants to open join groups is to always validate the group’s access settings and members before making a role assignment. NetSPI was able to grant roles in GCP to a group with open join permissions (“Entire organization – can join group”). The test Organization was using default configurations and there were no explicit guardrails in place that prevented this. Google does support Security Groups, which have some additional protections in place, but also allow open join permissions.

Google does provide some Group specific guardrails around Group visibility. Hiding certain groups from general members could be an effective way to prevent users from enumerating sensitive groups.

General recommendations for Groups can be found here. Google Cloud also provides additional features for controlling IAM permissions via IAM Deny policies that may be useful for your organization.

While this issue has been classified as Intended Behavior, there is a risk for customer misconfiguration. Google’s Security Command Center in GCP has a check (Open group IAM member) that can find these scenarios. Google’s recommendations for remediating the issue can be found here.

Hunting and Detection

The following may be helpful to organizations that want to actively search or detect on Open join groups. Note that these opportunities are meant to provide a starting point and may not work as written for every organization’s use case. At a high level, the detection below will alert to existing open groups in the environment while the hunting opportunities offer a more broad approach using IAM and Group metadata.

Detection Opportunity #1

Data Source: IAM Policy bindings creation
Detection Strategy: Behavior
Detection Concept: Detect when an IAM policy is created where the principal is an open group. The finding OPEN_GROUP_IAM_MEMBER exists in Security Command Center for this detection opportunity.

state="ACTIVE" AND NOT mute="MUTED" AND category="OPEN_GROUP_IAM_MEMBER"

Detection Reasoning: A member user of the Organization could join the open group and inherit any permissions granted to the group.
Known Detection Consideration: Detection relies on the cadence of scanning in Security Command Center.

Hunting Opportunity #1 – Identify permissions for any groups in GCP

Data Source: IAM Policy Metadata
Detection Strategy: Behavior
Hunting Concept: Review all IAM Policies where the principal is a group using Asset Inventory. Will include ALL groups. This data should be cross-referenced with Hunting Opp #2.
Gcloud CLI command

gcloud asset search-all-iam-policies \
    --scope='...' \
    --query='memberTypes:group'

Console command

Hunting Reasoning: A member user of the Organization could join the open group and inherit any permissions granted to the group.

Hunting Opportunity #2 – Identify any open groups

Data Source: Group Metadata
Detection Strategy: Behavior
Hunting Concept: Review all groups that have open join access, even those that are not assigned permissions in GCP.

  1. Go to the Groups dashboard in the Admin console.
  2. Click the Manage Columns option.
  3. Add a new column “Who can join the group”.
  4. Click Save.
  5. Review this column for the setting “Anyone in the organization can join”.

Hunting Reasoning: A member user of the Organization could join the open group and inherit any permissions granted to the group.

Coordinated Disclosure Timeline

NetSPI worked with Google on coordinated disclosure.

  • 12/11/2023 – Report submitted
  • 12/11/2023 – Report triaged and assigned
  • 12/12/2023 – Status changed to Won’t Fix (Infeasible)
  • 12/14/2023 – Status changed to Won’t Fix (Intended Behavior)
  • 12/28/2023 – Status changed to Assigned (reopened)
  • 01/03/2024 – Status changed to Won’t Fix (Intended Behavior)
  • 01/16/2024 – Status changed to In Progress (Accepted) (reopened). Type changed to Bug.
  • 01/18/2024 – Status changed to Won’t Fix (Intended Behavior)
  • 01/21/2024 – Coordinated Disclosure process begins
  • 03/15/2024 – Coordinated Disclosure process completed

Thanks to Karl Fosaaen, Nick Lynch, and Ben Lister for their review.