Pentest AD
Sources
https://wadcoms.github.io https://book.hacktricks.xyz/windows-hardening/active-directory-methodology https://github.com/nyxgeek/o365recon https://github.com/emiliensocchi/azurehound-queries/blob/main/customqueries.json
Bloodhound
Queries
detect esc15
MATCH p=(:Base)-[:MemberOf*0..]->()-[:Enroll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(:EnterpriseCA)-[:TrustedForNTAuth]->(:NTAuthStore)-[:NTAuthStoreFor]->(:Domain)
WHERE ct.enrolleesuppliessubject = True
AND ct.authenticationenabled = False
AND ct.requiresmanagerapproval = False
AND ct.schemaversion = 1
RETURN p
File size too large to import
Sometimes in large AD environments the bloodhound files become waaaayy too large. This tool splits the data and allows an easy import into bloodhound https://github.com/bitsadmin/chophound/
Azurehound
Dealing with Multi-Factor Auth and Conditional Access Policies
If a user has MFA or CAP restrictions applied to them, you will not be able to authenticate with just a username and password with AzureHound. In this situation, you can acquire a refresh token for the user and supply the refresh token to AzureHound.
The most straight-forward way to accomplish this is to use the device code flow. In this example I will show you how to perform this flow using PowerShell, but this example can be very easiliy ported to any language, as we are simply making calls to Azure APIs.
Open a PowerShell window on any system and paste the following:
$body = @{
"client_id" = "1950a258-227b-4e31-a9cf-717495945fc2"
"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
$authResponse
The output will contain a user_code and device_code. Now, open a browser where your AzureAD user either already logged on or can log on to Azure. In this browser, navigate to https://microsoft.com/devicelogin
Enter the code you generated from the above PowerShell script. Follow the steps in the browser to authenticate as the AzureAD user and approve the device code flow request. When done, the browser page should display a message similar to “You have signed in to the Microsoft Azure PowerShell application on your device. You may now close this window.”
Now go back to your original PowerShell window and paste this:
$body=@{
"client_id" = "1950a258-227b-4e31-a9cf-717495945fc2"
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
"code" = $authResponse.device_code
}
$Tokens = Invoke-RestMethod `
-UseBasicParsing `
-Method Post `
-Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" `
-Headers $Headers `
-Body $body
$Tokens
The output will include several tokens including a refresh_token. It will start with characters similar to “0.ARwA6Wg…”. Now you are ready to run AzureHound! Take the refresh token and supply it to AzureHound using the -r switch:
./azurehound -r "0.ARwA6Wg..." list --tenant "contoso.onmicrosoft.com" -o output.json
This will attempt to list all possible data from that particular tenant, but you can ALSO use that same refresh token to target any other tenant your user has access to!
Azure AD Queries
Find users who have high privileges on a subscription with their normal account
Goal: Find all paths starting from a user that has high/owner privileges on a subscription. Exclude admin accounts.
Option 1: Find all non-admin users that have a path to subscription ownership. Show only the first 1000 paths.
MATCH p = (n:AZUser)-[*]->()-[r:AZOwns]->(g:AZSubscription)
WHERE NOT(tolower(n.userprincipalname) STARTS WITH 'admin_')
RETURN p
LIMIT 1000
Option 2: Find the shortest path for all non-admin users with a path to high privileges on a subscription.
MATCH p = shortestpath((n:AZUser)-[*]->(g:AZSubscription))
WHERE NOT(tolower(n.userprincipalname)
STARTS WITH 'admin_')
RETURN p
Option 3: List all non-admin users, ordered by the number of subscriptions they have high privileges on.
MATCH p = shortestpath((n:AZUser)-[*]->(g:AZSubscription))
WHERE NOT(tolower(n.userprincipalname)
STARTS WITH 'admin_')
RETURN n.userprincipalname,
COUNT(g) ORDER BY COUNT(g) DESC
Find apps with interesting delegated permissions
Goal: Find all Azure apps that have access to resources which might be unexpected, due to nested group memberships.
Option 1: Find all apps that have a path which ends in another resource with a relationship that is not “RunAs” and not “MemberOf”. These relationships can be on the path, but not at the end of the path.
MATCH p = (n:AZApp)-[*]->()-[r]->(g)
WHERE any(r in relationships(p)
WHERE NOT(r:AZRunsAs)) and NOT(r:AZMemberOf)
RETURN p
LIMIT 3000
Option 2: The same as option 1, but for all service principals (not all apps are necessarily a service principal).
MATCH p = (n:AZServicePrincipal)-[*]->()-[r]->(g)
WHERE any(r in relationships(p)
WHERE NOT(r:AZRunsAs)) and NOT(r:AZMemberOf)
RETURN p
LIMIT 3000
Find all VMs with a managed identity that has access to interesting resources
Goal: Find VMs which can have a high impact in case they get compromized.
MATCH p=(v:AZVM)-->(s:AZServicePrincipal)
MATCH q=shortestpath((s)-[*]->(r))
WHERE s <> r
RETURN q
Find shortest path from a compromized device to interesting resources
Goal: Find a path from a compromized Azure-joined device to a resource.
MATCH p = shortestpath((n:AZDevice)-[*]->(g))
WHERE n <> g
RETURN p
LIMIT 100
Find external user with odd permissions on Azure objects
Goal: Find users from an external directory which have odd permissions.
Option 1: Find external users with directly assigned permissions.
MATCH p = (n:AZUser)-[r]->(g)
WHERE n.name contains "#EXT#" AND NOT(r:AZMemberOf)
RETURN n.name, COUNT(g.name), type(r), COLLECT(g.name)
ORDER BY COUNT(g.name) DESC
Option 2: Find external users with Owner or Contributor permissions on a subscription.
MATCH (n:AZUser) WHERE n.name contains "#EXT#"
MATCH p = (n)-[*]->()-[r:AZOwns]->(g:AZSubscription)
RETURN p
Option 3: Find external users with generic high privileges in Azure.
MATCH p = (n:AZUser)-[*]->(g)
WHERE n.name contains "#EXT#" AND any(r in relationships(p) WHERE NOT(r:AZMemberOf))
RETURN p
Find objects with the user administrator role
Goal: Find any object that has indirect access to the user administrator role. Note that you can modify this role in the below queries to any other role you deem relevant.
Option 1: Find all objects with a path to a specific role. In this case “user administrator”.
MATCH p = (n)-[*]->(g:AZRole)
WHERE n<>g and NOT(n:AZTenant) AND g.name starts with "USER ADMINISTRATOR"
RETURN p
Option 2: Find all objects, which are not users, with an indirect role assignment.
MATCH p = shortestpath((n)-[*]->(g:AZRole))
WHERE n<>g and NOT(n:AZTenant) AND NOT(n:AZManagementGroup) and NOT(n:AZUser)
RETURN p
Option 3: The same as option 2, except we exclude directory readers, since this is a common role to have.
MATCH p = shortestpath((n)-[*]->(g:AZRole))
WHERE n<>g and NOT(n:AZTenant) AND NOT(n:AZManagementGroup) and NOT(n:AZUser) and NOT(g.name starts with "DIRECTORY READERS")
RETURN p
Find users with high privileges on most objects
Goal: Identify users with high privileges (owner/contributor) on most objects (non-transitive). Find the top 100 users with most direct contributor permissions. The permission can be changed for AZOwner as well.
MATCH p = (n)-[r:AZContributor]->(g)
RETURN n.name, COUNT(g)
ORDER BY COUNT(g) DESC
LIMIT 100