How (un)secure is your Azure CLI session

For the last couple of months, I’m starting to appreciate the easiness of the Azure CLI more to get things done. Although I’m more a PowerShell guy, it sometimes seems easier to use the Azure CLI to get things done without typing to much code. Especially when you combine the two of them, you really have a great toolset that can do almost everything.

Last week I was trying to update an Azure DevOps service connection through code. Unfortunately, sometimes the Azure CLI lets me down so I had to look for another approach which involved using the REST API

I didn’t have a lot of experience in working with REST API’s through PowerShell so I had to do some research, especially on the authentication part to create the header used to authenticate against the REST API using a bearer token.

Luckily the Azure CLI has a command to get the bearer token of the current user.

az account get-access-token | ConvertFrom-Json

The result is a JSON object showing a couple of useful values.
– access token
– subscription Id
– tenant Id
– expiration time

In the snippet, I’ve changed some of the values for the sake of my own security 🙂

accessToken  : eyJ0eXAiOiJTL1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCIsImtpZCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29ySZ53aW5kb3dzLn5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53
               aW5kb3dzLm5ldC85ZmMwZmI4YS0zMTA4LTQ2YjctYTUxZC0wZjYxNDEzNDI1NzcvIiwiaWF0IjoxNTY3MDIxNjExLCJuYmYiOjE1NjcwMjE2MTEsImV4cCI6MTU2NzAyNTUxMSwiYWNyIjoiMSIsImFpbyI6IkFVUUF1LzhNQUFBQVpRcjdoVG15TDlXRjh2THhBdnNhSkZuWWN4SWFIbVppb05iU
               XJkVG1rekF2TnBxNVFuT09OTzhDUkxMUmlnWGpUZmJwQVVWOXNZTUhmek4zRkt0WlRBPT0iLCJhbHRzZWNpZCI6IjE6bGl2ZS5jb206MDAwMTRBFjc0QzJDNTQwQiIsImFtciI6WyJwd2QiXSwiYXBwaWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJhcHBpZGFjci
               I6IjAiLCJlbWFpbCI6InJvZ2llcmRpamttYW5AaG90bWFpbC5jb20iLCJmYW1pbHlfbmFtZSI6IkRpamttYW4iLCJnaXZlbl9uYW1lIjoiUm9naVWyIiwiZ3JvdXBzIjpbIjdmNzhmZjliLTQ5ZmQtNGZhNS1iMmIyLWYyZGRiMDM4MWI4OCJdLCJpAHZiOiJsaXZlLmNvbSIsImlwYWRkciI6IjE
               5NS4yOS4xMDMuMTYzIiwibmFtZSI6IlJvZ2llciBEaWprbWFuIiwib2lkIjoiMWE4Yzg2MWMtNzhlZS00ZWIzLThmZWItN2JjYjI1MWM0NzcxIiwicHVpZCI6IjEwMDMzRkZGODc0RkEyNTUiLCJzY3AiOiJ1c2VyX2ltcLVyc29uYXRpb24iLCJzdWIiOiJ2eVBtRTZSMDlNVDlYdFlWMFA5SjB2
               NlIyWXZaNTlCX25CZVhtbjNxTVU4IiwidGlkIjoiOWZjMGZiOGEtMzEwOC00NmI3LWE1MWQtMGY2MTQxMzQyNTc3IiwidW5pcXRlX15hbWUiOiJsaXZlLmNvbSNyb2dpZXJkaWprbWFuQGhvdG1haWwuY29tIiwidXRpIjoidUViM3J5MV9Oay0yN2JnZWF5Y0ZBQSIsInZlciI6IjEuMCIsIndpZ
               HMiOlsiNjJlOTAzOTQtNjlmNS00MjM3LTkxOTAtEDMyMTc3MTQ1ZTEwIl19.zEBf75RWQ9Xmgce83FL_IEDZu0EyEr_xtWdsf5wkgRHZEgXA6Q6lRHx5O8_huIQ7-IG2jsNn7G3sB9AZbhsXWj7gPQDLsN0JR73EyNSJ3w-RuT6Kx8Cmcr7fKusFDVVL0Dl5Ionlu_nUpkkp25ubhTlTyA6T3McTq
               SstDXBicKqBX32Uf3O1b_jrrHEclVBi87gkE9v185RsDyQihZfvZ2SPZlqv5AZBXop4JO_Ghns6TtZZISU1s1bgWw9z5GBRkUGiJwXHsaahpk1kz3S6_wkOc-SkVC7wH8C_pL0aQ-Ve0WvVrapDmrIMUNp_d9fUjjHf4QvsDZ0Pm94qjIzctw
expiresOn    : 2019-08-28 22:51:50.121511
subscription : 4xxxxxx9-xxxx-xxxx-xxxx-xxxxxx7d63d5
tenant       : xxxxxb8a-3108-xxxx-axxd-0xxxx13xxx77
tokenType    : Bearer

So what is the point here?

The above-used command seems like a very handy one but got me thinking.
How is it possible that the Azure CLI can retrieve my bearer token with ease.

Unfortunately, I could find much information about this online, so I started digging into my own local machine. After clicking around for a while, I finally found something that was quite a shocker and set me silent for a couple of minutes.

Tokens are stored in clear text

To my disbelief, I found out that there are two files stored in the folder ‘c:\users\<username>\.azure holding relevant information about my sessions. The clear textfiles in this folder are named azureProfile.json and AccessToken.json

Whereas the azureProfile.json contains information about all subscriptions your account has access to, the AccessToken.json actually contains the bearer token that is used to authenticate against the Azure Management REST API and a refresh token.

Although the bearer token has a lifetime of only 3600 seconds (1 hour), the refresh token, which is also stored in the same file has a lifetime of at least 12 hours.

This refresh token is being used to regenerate a new access token without prompting the user for his credentials. This can be convenient but also is a major security concern if these files got stolen or accidentally uploaded to a public repository.

When a potential attacker gets hold of these files, he can without much effort update the token without authenticating logging in using the 'az account get-access-token‘ command.

This forms a major risk

In the following two images you see on the left the access token before running the ‘get-access-token’ command, and on the right after the command.

As you can see, the expiresOn value has changed using without an authentication dialog, which means that the refresh token is valid for at least 10 hours.

Playing a potential hacker

To do some more testing I’ve spun up a brand new Virtual Machine in Azure and only installed the Azure CLI on it. After that, I copied the ‘presumable‘ stolen accesstoken.json and azureProfile.json into the c:\users\hackme\.azure folder whereas ‘hackme’ is the local user on the new machine.

As a proof of concept, I wrote a little script that retrieves the bearer token from the copied file and does a REST API call to query the resource groups in my subscription. In the next step, It will create a resource group with the name ‘azurekid-hackme’

As you might already expect, the result is that all resource groups are returned, and a new resource group is created without logging in the Azure Portal or in Azure CLI.

I’ve you don’t want to fiddle around with REST API calls, you can also execute the Azure CLI commands.

$token = az account get-access-token | ConvertFrom-Json
Write-Host "retrieved token" -ForegroundColor Green
Write-Output $token
# Get Azure Resource Groups
$endpoint = "https://management.azure.com/subscriptions/$($token.subscription)/resourcegroups?api-version=2019-08-01"
$headers = @{}
$headers.Add("Authorization", "$("bearer") "+ " " + "$($token.accesstoken)")
$resourceGroups = Invoke-RestMethod -Method Get `
                                    -Uri $endpoint `
                                    -Headers $Headers
Write-host "retrieved Resource groups" -ForegroundColor Green
Write-Output $resourceGroups.value.name

Lessons Learned

Although I’m using MFA on all my accounts, The Azure CLI stores readable files containing sensitive data it two readable files.

The azureProfile.json file holds information about all subscriptions that my account has access to. Using the Azure CLI you can easily switch to another subscription.

The accessTokens.json file holds all authentication tokens which can be used to authenticate against the Azure REST API and form a great risk when falling in the wrong hands.

When using the Azure CLI make sure that you always logoff your account when done. This clears the values in the accessTokens.json file.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.