Your service connection credentials are mine
Like with the two previous posts Hacking Azure DevOps
and I am in your pipeline reading all your secrets!
I want to continue to raise awareness and understanding about pipeline security in Azure DevOps. In the previous post I have explained how secure / marked as secret variables are handled during pipeline runtime.
In this post I want to show how an Azure Resource Manager service connection configuration is handled during pipeline runtime. And which sensitive information is exposed through this service connection.
Like I mentioned in the previous posts, without proper security configuration for pipelines this information could be abused by attackers.
But lets start with creating an out of the box Azure Resource Manager service connection.
New Service Connection
In your Azure DevOps project click on the setting icon in the bottom left corner. Next in the setting
menu select the service connection
option. On the new page in the top right corner click on the New Service Connection
button. A list of the available service connection type should be visible on you right hand side. Select the Azure Resource Manager
as shown in the following image and click next.
Next, as shown in the following image select the recommended option Service principal (automatic)
and click next.
On the next page you should be able to see the subscriptions and resource groups to which your current account has access. Select the preferred subscription and resource group and click save. It should look as follows.
When done correctly the newly created service connection should be visible in the list. See following example.
Service connection un the hood
Now that we have our service connection created, lets have a look under the hood. Click on the newly created service connection and the click on the managed service principal option as show in the following image.
You should be redirected to Azure AD page of the underlying App Registration as show in the following image.
To facilitate the service connection an App registration is created under the identity of the currently logged in account. We will ignore the awful looking naming that has been done to the App registration ;-). In addition a secret is also created to facilitate the use of this App registration by the service connection we just created in Azure DevOps. Click on the Certificates and secrets option in the left hand menu, the secret in question should be visible. Naturally the secret value is hidden. This is also show in the following image.
In addition this app registration is also added as contributor to the resource group or any other scope that is selected during the creation process. In the case of this example I have selected the resource group called demo-ado-scraper. Have a look at the following image.
So an App registration with a secret is created and contributor permissions are applied to the selected scope. Meaning that if it would be possible to obtain the App registration details including secret from within a pipeline, it would allow an attacker to at the very least gain contributor permissions over all the resources available under the selected permissions scope. In my case that would be the resource group.
Lets have have a look how we can achieve this.
Getting the App registration secret
Ideally to get any kind of authentication and authorization going the following values would be required.
- Secret set for the the service connection app registration
- App registration Id of the service connection app registration
- Tenant Id of the service connection app registration
Since it is actually possible to get all 3 values using PowerShell a simple pipeline with a Powershell task which exposes the SYSTEM_ACCESSTOKEN
should be more then sufficient to get the job done.
The clue lies within the warning that is given when using the Connect-AzAccount -ServicePrincipal
which reads as following
It tells us that when logging in using an App registration with a secret this secret is stored inside a file on the system. This file can be retrieved and parsed. The following script does exactly that.
Take a closer look at the file we just retrieved, you will notice that each context is stored under its own name. We can get the name of our current context by simply using \$contextName = \$(Get-AzContext).Name
command. With the context name sorted its simply down to sifting through the json properties. And the ones we where interested in are as follows.
- The app registration Id is retrieved by
\$azureRmContextJson.Contexts.$contextName.Account.Id
- The app registration secret is retrieved by
\$azureRmContextJson.Contexts.$contextName.Account.ExtendedProperties.ServicePrincipalSecret
- The tenant Id is retrieved by
\$azureRmContextJson.Contexts.$contextName.Tenant.Id
The full script combined with the yaml task is as follows.
When the above task is executed in a pipeline the pipeline output will contain the Base64 encoded strings for each of the variable values. As show in the following clipping.
After using the decoding script from I am in your pipeline reading all your secrets! post the decoded values are as how in the following image.
With these values exfiltrated an attacker is now able to access your Azure resources at with contributor permissions. For example by using the Connect-AzAccount
Please make sure to secure your pipeline access and use this blog post as an example to grow understanding in your organization about why pipeline security is a crucial security topic. Thank you for taking the time to read this post!