$7.5k Google services mix-up

Before getting to the bug itself, I need to explain a few concepts. You can jump directly to it if you want.

Key concepts
Google has a service called Google Service Management, which provides a way to manage several aspects of services (APIs) that use Google's systems (Such as Google's own public and internal APIs, and services deployed by users using Google Cloud Endpoints).
This service allows users to enable/disable services on their Google Cloud Platfom projects, list available services (Such as Maps API, Gmail API, private APIs you have access to, etc.), and manage the settings of their own services through service configurations.
Developers will rarely have to use this service directly, mostly interacting with it through the Google Cloud Console or command line (For enabling/disabling services), or through Google Cloud Endpoints (For managing settings), but it also has a very interesting API.

This API can not only do everything stated above and in its official documentation, but it also has some hidden capabilities only accessible for Google services that use the API.
These hidden capabilities can be found in several ways, but the simplest and easiest one is by enabling the Service Management API on a Google Cloud Platform project and opening the combo box used for filtering the traffic your project produces:
Enable the Service Management API
Open the combo box
See a list of all the methods (Including hidden ones)

In the last image there is a list of all the methods available through the API, including hidden ones (Framed in red) which are not accessible for non-Google clients. If a non-Google client attempts to access them, they will see a 404 error (Funny enough, the error will be in JSON, instead of the well known HTML page with the broken robot saying "That's an error." and "That's all we know." that an inexistent method would throw).

Also, the same way there are hidden methods in the API, there are hidden parameters some public methods accept, but those a bit more tricky to find.

Both, hidden methods and hidden parameters, are use a feature of Google services called "Visibility" (Documented externally, though only used internally by Google).

Note: Hidden parts of a Google-owned API can be found using several ways, and most of the time they have hidden documentation as well, Google does not consider finding hidden API capabilities or hidden API documentation a security vulnerability (I already tried reporting those to them).
But, there are some hidden capabilities that, if used successfully, are considered a security vulnerability (Like the "usage.dependsOnServices" field in this bug).

Knowing this, I tried finding a way to access this hidden stuff, which was not very difficult to accomplish just by looking at the HTTP requests the Google Cloud Console makes when visited.
Google Cloud Console uses several public and private Google APIs for accessing information about a project, it does so with its own client, which has the API key "AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g".

An usual request with this client looks like this:
GET /v1/services?key=AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g HTTP/1.1
Host: servicemanagement.clients6.google.com
Authorization: SAPISIDHASH <SAPISIDHASH>
X-Origin: https://console.cloud.google.com
Cookie: <GOOGLE COOKIES>

Let's see what each part is:
- "clients6.google.com" is just another way of saying "googleapis.com", which is needed since the cookies can only reach google.com subdomains.
- "SAPISIDHASH" is a value in the form "TIMESTAMP_HASH" as explained in this nice StackOverflow answer, there are some other ways to make a SAPISIDHASH, but they are irrelevant for this write-up.
- The "X-Origin", or just "Origin", header is needed both for the SAPISIDHASH and to verify that it is being called by an authorized client from a trusted website.
- The needed cookies are: SID, HSID, SSID, APISID and SAPISID. In order to obtain those, a Google user just has to log-in.

Forging a request with the Google Cloud Console client is very easy, and since it is a Google-owned client, it has access to some internal capabilities of several Google APIs (Including some private Google APIs), including the Service Management API.

One of several capabilities this client has is creating a service with a configuration attached since the beginning (Normal clients will just ignore the "serviceConfig" parameter, since it is hidden,and create a service with no initial configuration), a simple request like this:
POST /v1/services?key=AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g HTTP/1.1
Host: servicemanagement.clients6.google.com
Authorization: SAPISIDHASH <SAPISIDHASH>
X-Origin: https://console.cloud.google.com
Cookie: <GOOGLE COOKIES>
Content-Length: <CONTENT-LENGTH>

{
  "serviceName": "<SERVICE NAME>",
  "producerProjectId": "<PROJECT>",
  "serviceConfig": {
    "name": "<SERVICE NAME>",
    "producerProjectId": "<PROJECT>",
    "configVersion": 3
}

The bug

Normally, the "serviceName" and the "serviceConfig.name" parameters must match in any request where both are specified, but during creation this constraint was not being checked when the "configVersion" was set to either 1, 2, or a value between 2147483648 and 4294967295 (An integer overflow occurs in the back-end), therefore any user could create a service with a real name (Such as "the-expanse.appspot.com") but with a configuration that claims it is a different service (Such as "my-private-secure-api.appspot.com").

Note: This bug did not work for a few old Google services due to a special setting they have for compatibility reasons.

This was a very big deal, a few important processes use the name found in the configuration of a service for performing any action, except for checking permissions, in that case they use the service name itself. So, with a different name in the configuration, an attacker could be permitted to perform important actions on a different service.

Among this actions were:
- Enabling the other service:
If I have my service "the-expanse.appspot.com" with "very-important-api.example.com" in its configuration, when enabling "the-expanse.appspot.com" for a project, Google will proceed because I have permission to enable "the-expanse.appspot.com", but the action will really be performed on "very-important-api.example.com", so I end up having enabled "very-important-api.example.com".
If a customer sets up an API to use Google API keys and/or Google Auth tokens to authenticate legitimate clients, this could be bypassed by an attacker.
Google itself uses this method for authenticating legitimate clients, so an attacker could be able to use private Google APIs that are in development, are meant to be used only internally, or that are accessible for a few white-listed users (Trusted Testers, Google My Business API, etc.).

- Getting access to hidden capabilities:
A hidden method in the Service Management API is "PatchProjectSettings", this allows a service owner to set some hidden settings of a specific service consumer (Project). Among this settings is the option for setting visibility labels (Which manage access to hidden capabilities).
For instance, if I have my service "the-expanse.appspot.com" with "cloudresourcemanager.googleapis.com" in its configuration, I can send the following request and get access to capabilities being tested by a select few (Trusted Testers) in the Cloud Resource Manager API on my project (the-expanse):
PATCH /v1/services/the-expanse.appspot.com/projectSettings/the-expanse?updateMask=visibilitySettings&key=AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g HTTP/1.1
<..SAME HEADERS AS BEFORE...>

{
  "visibilitySettings": {
    "visibilityLabels": [
      "TRUSTED_TESTER"
    ]
  }
}


- Disabling the service on someone else's project:
Using the same method as before, we can set another setting that controls whether a project has enabled or not the service. Note that you cannot enable your service on a project you do not own, only disable it.
For instance, if I have my service "the-expanse.appspot.com" with "cloudresourcemanager.googleapis.com" in its configuration, I can send the following request and disable the Cloud Resource Manager API on Cloud SDK (Which uses the project "google.com:cloudsdktool"):
PATCH /v1/services/the-expanse.appspot.com/projectSettings/google.com:cloudsdktool?updateMask=usageSettings&key=AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g HTTP/1.1
<..SAME HEADERS AS BEFORE...>

{
  "usageSettings": {
    "consumerEnableStatus": "DISABLED"
  }
}

This bug could lead to lots of issues, enabling private APIs, getting access to hidden capabilities and disabling services for others could probably lead to several issues arising for lots of people very easily. I have not tested most of these scenarios and maybe they were never possible, but I am very confident they could be done:
- Accessing several Google APIs in development to use features not yet released to the general public
- Accessing billed Google APIs for free by enabling them using this bug (The billing constraint is enforced using the real service name)
- Accessing private APIs developed by Google users using Google Cloud Endpoints
- Accessing hidden features not yet released for the general public in public Google APIs
- Bypassing quota limits (They are stored in the project settings too)
- Exploiting bugs not available without the use of this one
- Disabling key APIs for some projects that lead to service outages (Such as the Cloud SDK not being able to access projects, Android's YouTube app not being able to retrieve videos' metadata, etc.).

Timeline (UTC-3)
  • 2018-01-27 afternoon - Bug found
  • 2018-01-27, 06:45 PM - Initial submission
  • 2018-01-27, around 7:45 PM - Google Service Management API development team discovered the bug independently (Note it was a Saturday!)
  • 2018-01-29 - Bug during creation fixed by the development team
  • 2018-01-29, 12:53 PM - Bug report triaged
  • 2018-01-30 - All services with mismatched serviceName/serviceConfig.name pairs were completely purged from Google's systems, thus this bug could no longer be exploited
  • 2018-01-30, 06:46 PM - Security team cannot reproduce scenario #3, the security engineer keeps getting 401 errors
  • 2018-01-30, 08:21 PM - It is confirmed that the development team discovered the "hack" and patched an emergency fix during the weekend
  • 2018-01-31, 12:42 PM - I am told the development team independently discovered the bug an hour after my report, nevertheless my report is sent to the security panel to check if a reward can be issued
  • 2018-02-14, 06:12 AM - A reward of 7500 dollars is issued