On April 18, 2023, I discovered a vulnerability in Cloudflare CASB that enabled me to view sensitive information about other customers’ Microsoft and GitHub organizations. This included employee names/emails, links to SharePoint files, repository names/descriptions and more. View the report on HackerOne.

What is Cloudflare CASB?

I think I have to quickly explain Cloudflare CASB for the rest of the write-up to make any sense, but feel free to skip to the next part if you already know what a Cloud Access Security Broker is and how it works.

Cloudflare CASB (Cloud Access Security Broker) is a product that integrates with your organization’s cloud-hosted applications and services (SaaS, PaaS and IaaS) to scan for and alert you of security issues and possible misconfigurations.

At the time of writing, Cloudflare CASB can integrate with Atlassian Confluence and Jira, Box, Dropbox, GitHub, Google Workspace, Microsoft 365, Salesforce and Slack organization accounts.

Screenshot of the Cloudflare dashboard showing the supported providers

For GitHub integrations, Cloudflare CASB will look for:

  • Organization members without 2FA enabled
  • Vulnerabilities in repository dependencies
  • Public repositories without a security policy
  • Repositories with an unprotected default branch
  • … and more

It works by having you sign into your GitHub account and authorize the Cloudflare CASB application. This grants the application full read access to your GitHub organization, enabling it to perform the checks mentioned above.

Screenshot of the GitHub authorization screen when installing the Cloudflare CASB app

Because of this, the Cloudflare CASB GitHub application has access to all GitHub organizations that have installed it. It is therefore Cloudflare’s responsibility to associate GitHub accounts with Cloudflare accounts and implement access control. Things can go wrong if this is not done properly…

Missing validation of the OAuth code

I was curious about how exactly GitHub and Cloudflare accounts were being linked, and, naturally, whether this process could be exploited. So, I went ahead and created a GitHub integration.

Screenshot of the Cloudflare dashboard when creating a GitHub integration

After authorizing the Cloudflare CASB app to access my GitHub organization, I was redirected to the following URL:


When the /oauth page loads, a POST request is sent to https://dash.cloudflare.com/api/v4/accounts/acc70000000000000000000000000000/casb/integrations with the following payload:

	"name": "My GitHub Integration",
	"policy_id": "3d5f554c-f630-4ffa-ab94-c5047f9d8e3a",
	"credentials": {
		"code": "12345678"
  • name is the user-defined name of the integration
  • policy_id is a UUID indicating the type of integration (e.g. 3d5f554c-f630-4ffa-ab94-c5047f9d8e3a for GitHub integrations)
  • credentials.code is the OAuth code returned by the vendor

But wait, that credentials.code field does not match the OAuth code from the URL above. In fact, it looks like the dashboard is sending the GitHub app installation ID instead of the OAuth code… This made me wonder if I could simply specify the installation ID of another Cloudflare customer to create an integration for their GitHub organization and access their CASB findings.

I tried resending the same POST request in a different Cloudflare account. The request completed successfully. For some reason, however, Cloudflare CASB wasn’t coming up with any findings in either account, so I was unable to verify that it had worked properly.

For this reason, I asked a friend, who uses Cloudflare CASB, for permission to test against their GitHub account. They were happy to help and provided me with the ID of their Cloudflare CASB GitHub app installation. Thank you!

I resent the POST request again with the installation ID that my friend had provided. Shortly after, CASB findings for their GitHub account started appearing in my Cloudflare dashboard!

Screenshot of the Cloudflare dashboard showing the list of GitHub CASB findings (name, severity, number of occurrences, integration name and date detected)

The GitHub app installation ID is just an incrementing integer, and, at the time of writing, there were “only” around 36 million GitHub app installations. A bad actor could simply enumerate all the possible IDs to find those belonging to Cloudflare CASB installations.

CASB findings can contain sensitive information. For example, Cloudflare exposes repository names and descriptions in CASB findings. Also, the “GitHub Organization User 2FA Disabled” finding lists the name of organization members that do not have 2FA enabled. This information would be useful to a bad actor trying to break into the GitHub account of an employee at the target company.

Exploring the dashboard’s JavaScript files

This was bad enough by itself and warranted a vulnerability report, but I wondered if other integration types had the same problem of not validating the OAuth access code. So, I looked at the Zero Trust dashboard’s JavaScript files and found the following function. I have cleaned it up and renamed some variables for clarity.

function getCode(vendor, url) {
	switch(vendor.id) {
		case 'GITHUB':
			return url.searchParams.get('installation_id')
			return url.searchParams.get('tenant')
			return url.searchParams.get('code')

This function is called when the OAuth callback URL is loaded. The result is used as the value of the credentials.code field in the integration creation payload.

The function confirms that the installation ID is used when creating a GitHub integration. It also tells us that Microsoft integrations use the tenant parameter of the callback URL. For any other vendor, the code parameter (OAuth code) is used.

Microsoft integration

I wanted to check if Microsoft integrations were also vulnerable, but I don’t have a Microsoft organization myself. Luckily, my friend also uses Cloudflare CASB with their Microsoft organization! They gave me permission to test this vulnerability against their Microsoft account as well.

If you look at the callback URL for a Microsoft integration, you will see that the tenant parameter is a random UUID. This would obviously not be feasible to guess or enumerate.


However, after looking at the Microsoft OAuth documentation, I found that the tenant can also be the primary domain name of the Microsoft organization. Therefore, I tried creating a Microsoft integration using the domain as the code.

I later discovered it is possible to retrieve the tenant UUID of any domain from https://login.microsoftonline.com/<domain>/.well-known/openid-configuration, so this attack would have still been possible if the API only accepted UUIDs rather than arbitrary strings.

	"name":"My Microsoft Integration",
	"policy_id": "3cd1645e-2b50-4651-afb3-25fc0493bca5",
	"credentials": {
		"code": "example.com"
$ curl https://api.cloudflare.com/client/v4/accounts/acc70000000000000000000000000000/casb/integrations \
	-H 'X-Auth-Email: <EMAIL>' \
	-H 'X-Auth-Key: <API_KEY>' \
	-H 'Content-Type: application/json' \
	-d '{"name":"My Microsoft Integration","credentials":{"code":"example.com"},"policy_id":"3cd1645e-2b50-4651-afb3-25fc0493bca5"}'

The request succeeded and a couple of minutes later these items appeared in my dashboard. They contain sensitive information about users in the organization (names and email addresses), and the “Microsoft File …” findings also contain direct links to the affected SharePoint sites.

Screenshot of the Cloudflare dashboard showing the list of Microsoft CASB findings (name, severity, number of occurrences, integration name and date detected)

It would have been easy for a bad actor to go through e.g. the top 10,000 domains and attempt to create integrations for each of them. If any of them used Cloudflare CASB with their Microsoft organization, the attacker would have gained access to a bunch of sensitive information about the organization and its employees.

The fix

Cloudflare fixed the vulnerability by ensuring that multiple CASB integrations cannot be created for a single Microsoft tenant or GitHub app installation ID.


Following is the report timeline in UTC:

  • 2023-04-18 13:45 - I submit the report to Cloudflare’s HackerOne program
  • 2023-04-18 13:51 - Cloudflare triages the report
  • 2023-04-19 01:06 - Cloudflare confirms a fix has been released and requests a retest
  • 2023-04-19 10:32 - I retest and find that the fix only protects new integrations
  • 2023-04-19 15:00 - Cloudflare awards a bounty of $3300
  • 2023-04-20 09:06 - Cloudflare confirms the fix has been applied retroactively to all integrations
  • 2023-06-07 09:05 - Cloudflare agrees to disclose the report and lets me publish this write-up