internal/functions/Invoke-XdrTemporaryAccessPassAuthentication.ps1
|
function Invoke-XdrTemporaryAccessPassAuthentication { <# .SYNOPSIS Performs Temporary Access Pass authentication against Entra ID and returns the ESTSAUTH cookie value. .DESCRIPTION Implements the Entra ID TAP web sign-in flow used by mysignins.microsoft.com, then extracts the resulting ESTSAUTH cookie so it can be passed to Connect-XdrByEstsCookie. TAP sign-in is tenant-scoped. The same TenantId is used for the Entra authorize request and is typically passed on to Connect-XdrByEstsCookie so the Defender XDR portal opens the intended tenant. This is an internal function used by Connect-XdrByTemporaryAccessPass. .PARAMETER Username The user principal name (e.g., admin@contoso.com). .PARAMETER TemporaryAccessPass The Temporary Access Pass as a SecureString. .PARAMETER TenantId The Entra tenant ID used for the TAP authorize request. .PARAMETER UserAgent User-Agent string for HTTP requests. .OUTPUTS String - the ESTSAUTH cookie value suitable for passing to Connect-XdrByEstsCookie. .EXAMPLE $tap = ConvertTo-SecureString 'ABC12345' -AsPlainText -Force Invoke-XdrTemporaryAccessPassAuthentication -Username 'admin@contoso.com' -TemporaryAccessPass $tap -TenantId '8612f621-73ca-4c12-973c-0da732bc44c2' Performs the internal TAP sign-in flow and returns the ESTSAUTH cookie value. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Username, [Parameter(Mandatory)] [SecureString]$TemporaryAccessPass, [Parameter(Mandatory)] [string]$TenantId, [string]$UserAgent = (Get-XdrDefaultUserAgent) ) $clientId = '19db86c3-b2b9-44cc-b339-36da233a3be2' $redirectUri = 'https://mysignins.microsoft.com' $tokenScope = "$clientId/.default openid profile offline_access" $session = [Microsoft.PowerShell.Commands.WebRequestSession]::new() $session.UserAgent = $UserAgent $verifierBytes = [byte[]]::new(32) [System.Security.Cryptography.RandomNumberGenerator]::Fill($verifierBytes) $codeVerifier = ConvertTo-XdrBase64Url -Bytes $verifierBytes $challengeBytes = [System.Security.Cryptography.SHA256]::HashData([System.Text.Encoding]::ASCII.GetBytes($codeVerifier)) $codeChallenge = ConvertTo-XdrBase64Url -Bytes $challengeBytes $authUrl = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/authorize?" + "client_id=$([uri]::EscapeDataString($clientId))" + "&redirect_uri=$([uri]::EscapeDataString($redirectUri))" + "&scope=$([uri]::EscapeDataString($tokenScope))" + "&response_type=code&response_mode=fragment&prompt=login" + "&login_hint=$([uri]::EscapeDataString($Username))" + "&code_challenge=$([uri]::EscapeDataString($codeChallenge))&code_challenge_method=S256&state=test" Write-Verbose "Initiating TAP authentication flow for $Username in tenant $TenantId" $loginPage = Invoke-WebRequest -Uri $authUrl -Method Get -UseBasicParsing -MaximumRedirection 10 -WebSession $session -Verbose:$false $config = Get-XdrAuthStateFromResponse -Response $loginPage if (-not $config) { throw 'Unexpected response from Entra TAP authentication endpoint.' } $tapHandle = [IntPtr]::Zero $plainTap = $null try { $tapHandle = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($TemporaryAccessPass) $plainTap = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($tapHandle) $loginBody = [ordered]@{ login = $Username loginfmt = $Username accesspass = $plainTap ps = '56' psRNGCDefaultType = '1' psRNGCEntropy = '' psRNGCSLK = [string]$config.sFT canary = [string]$config.canary ctx = [string]$config.sCtx hpgrequestid = [string]$(if ($config.sessionId) { $config.sessionId } else { $config.correlationId }) flowToken = [string]$config.sFT PPSX = '' NewUser = '1' FoundMSAs = '' fspost = '0' i21 = '0' CookieDisclosure = '0' IsFidoSupported = '1' isSignupPost = '0' DfpArtifact = '' i19 = '10000' } $encodedLoginBody = ConvertTo-XdrFormUrlEncodedBody -Data $loginBody } finally { $plainTap = $null if ($tapHandle -ne [IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($tapHandle) } } $currentUrl = Resolve-XdrAuthAbsoluteUri -Uri $(if ($config.urlPost) { $config.urlPost } else { 'https://login.microsoftonline.com/common/login' }) -BaseUri 'https://login.microsoftonline.com/' $currentMethod = 'POST' $currentBody = $encodedLoginBody for ($step = 0; $step -lt 15; $step++) { try { $requestParams = @{ Uri = $currentUrl Method = $currentMethod WebSession = $session UseBasicParsing = $true MaximumRedirection = 0 Verbose = $false } if ($currentMethod -eq 'POST' -and $null -ne $currentBody) { $requestParams['Body'] = $currentBody $requestParams['ContentType'] = 'application/x-www-form-urlencoded' } $response = Invoke-WebRequest @requestParams -ErrorAction Stop $parsedState = Get-XdrAuthStateFromResponse -Response $response $formPost = Get-XdrHtmlFormPost -Response $response if ($response.StatusCode -eq 200 -and $formPost) { $currentUrl = Resolve-XdrAuthAbsoluteUri -Uri $formPost.Action -BaseUri $currentUrl $currentMethod = 'POST' $currentBody = $formPost.Body continue } if ($parsedState -and $parsedState.sErrorCode) { throw "TAP authentication failed ($($parsedState.sErrorCode)): $($parsedState.sErrTxt)" } break } catch [Microsoft.PowerShell.Commands.HttpResponseException] { $response = $_.Exception.Response $statusCode = [int]$response.StatusCode if ($statusCode -lt 300 -or $statusCode -ge 400) { throw } $location = Get-XdrResponseLocation -Response $response if (-not $location) { throw 'TAP authentication redirected without a Location header.' } $resolvedLocation = Resolve-XdrAuthAbsoluteUri -Uri $location -BaseUri $currentUrl if ($resolvedLocation -match '[#?&]code=') { break } if ($resolvedLocation -match 'error=') { throw "TAP authentication failed: $resolvedLocation" } $currentUrl = $resolvedLocation $currentMethod = 'GET' $currentBody = $null continue } } $bestCookie = Get-XdrBestEstsCookieValue -Session $session if (-not $bestCookie) { throw 'No ESTSAUTH cookie found after TAP authentication.' } Write-Verbose "Obtained ESTS cookie (length: $($bestCookie.Length))" return $bestCookie } |