SEKurity GmbH Logo
adPEAS

adPEAS v2 Episode 9: Tips & Tricks - The Hidden Tools in adPEAS

Hidden productivity boosters in adPEAS v2: Show-Object, KnownSPN discovery, dangerous ACL analysis, EPA testing, account activity filters, certificate analysis, and more.

Alexander Sturz

Founder & Red Team Lead

14 min read
Share:

Introduction

In previous episodes we covered the automated scan, security checks, reports, and offensive operations. But adPEAS has another side that’s easy to overlook: the many small helpers that save enormous amounts of time during day-to-day pentesting.

This post highlights functions and parameters whose usefulness isn’t immediately obvious, but which are invaluable for manual analysis. None of them require external tools — everything is built into adPEAS.

Prerequisite: An active adPEAS session:

Import-Module .\adPEAS.ps1
Connect-adPEAS -Domain "contoso.com" -UseWindowsAuth

Reconnaissance & Enumeration

Understanding AD Objects at a Glance with Show-Object

When you query a user or computer with Get-DomainUser, you get buried in attributes - dozens of properties, most of them irrelevant. Show-Object filters out the noise and displays only the security-relevant attributes, color-coded:

Get-DomainUser -Identity f.sinatra | Show-Object
displayName:                                 Frank Sinatra
sAMAccountName:                              f.sinatra
userPrincipalName:                           f.sinatra@contoso.com
distinguishedName:                           CN=Frank Sinatra,OU=Users,OU=Domain User,DC=contoso,DC=com
objectSid:                                   S-1-5-21-1234567890-1234567890-1234567890-1157
[+] memberOf:                                TerminalServerUsers
                                             LAPSReader
                                             ClientAdmins
                                             ServerAdmins
                                             Server-Operatoren
description:                                 User Help Desk
[+] msDS-KeyCredentialLink:                  DeviceID: 617b9d6fa7b5204c8fa049bebf9da5eb | Created: 2026-02-19 11:20
[+] userAccountControl:                      NORMAL_ACCOUNT
                                             DONT_EXPIRE_PASSWORD
[+] pwdLastSet:                              04/12/2022 11:39:08
lastLogonTimestamp:                          02/19/2026 10:34:36
[+] admincount:                              1

The [+] markers (yellow) highlight attributes that are immediately relevant for a pentester: group memberships, Shadow Credentials, UAC flags like DONT_EXPIRE_PASSWORD, stale passwords, and admincount: 1 as an indicator for privileged accounts.

Works identically for computers:

Get-DomainComputer -Identity DC01 | Show-Object

Instead of sifting through raw LDAP output, you get the overview in seconds - which attributes are set, which ones are interesting, and where it’s worth digging deeper.


Finding High-Value Targets with a Single Parameter

Every pentest starts with the question: where are the interesting systems? Normally that means building LDAP filters, parsing SPNs, filtering output. Or you just use -KnownSPN:

# All SQL servers in AD
Get-DomainComputer -KnownSPN MSSQL

# All Exchange servers
Get-DomainComputer -KnownSPN Exchange

# All ADCS Certificate Authorities
Get-DomainComputer -KnownSPN CA

The beauty of it: adPEAS knows the SPN patterns for each service type. MSSQL matches MSSQLSvc/*, Exchange matches exchangeMDB/*, exchangeRFR/* and exchangeAB/*, CA matches certsvc/*. You don’t need to know the SPN syntax - just specify the service name.

The supported service types:

ParameterSPN PatternTypical Target
MSSQLMSSQLSvc/*SQL Server
ExchangeexchangeMDB/*, exchangeRFR/*, exchangeAB/*Exchange Server
SCCMSMS Site Server/*, CmRcService/*SCCM/MECM
WSUSwsusService/*, WSUS/*Windows Update Services
ADFSadfssrv/*Federation Services
CAcertsvc/*Certificate Authorities
SCOMMSOMHSvc/*, MSOMSdkSvc/*System Center Operations Manager
Backupwbengine/*, veeam*Backup Servers
HyperVMicrosoft Virtual Console Service/*, vmms/*Hyper-V Hosts
RDPTERMSRV/*Remote Desktop
WinRMWSMAN/*WinRM/PSRemoting
HTTPHTTP/*Web Services
FTPFTP/*FTP Servers

Uncovering GPO Scope

GPOs are one of the potential attack vectors in AD. But knowing which GPOs exist is only half the battle — you need to know where they apply:

# Which GPOs apply to the Finance OU?
Get-DomainGPO -AppliedToOU "OU=Finance,DC=contoso,DC=com"

This answers the question: if I can edit a GPO, which systems are affected? Or conversely: which GPOs influence a specific part of the AD structure?


ADCS Templates at a Glance

ADCS vulnerabilities (ESC1-ESC15) have been one of the most popular privilege escalation paths since 2021. adPEAS makes searching for vulnerable templates easy:

# ESC1: Templates that allow arbitrary Subject Names + have Client Auth
Get-CertificateTemplate -EnrolleeSuppliesSubject -ClientAuthentication

# Templates without Manager Approval (faster exploitation)
Get-CertificateTemplate -NoManagerApproval

The filters are combinable. -EnrolleeSuppliesSubject -ClientAuthentication finds templates that meet both conditions — exactly what’s needed for ESC1. Without adPEAS you’d have to manually parse the msPKI-Certificate-Name-Flag and pKIExtendedKeyUsage attributes.


Access Control Analysis

Finding Dangerous ACLs

ACL analysis is normally tedious. Get-ObjectACL takes the pain away:

# All dangerous permissions on privileged objects
Get-DomainUser -AdminCount | Get-ObjectACL -DangerousOnly

-DangerousOnly filters for ACEs that are actually relevant for attacks: GenericAll, GenericWrite, WriteDacl, WriteOwner, and critical ExtendedRights. Everything else — the hundreds of “Read Property” and “List Contents” ACEs that clutter every analysis — gets ignored.

This also works with computers, groups, and GPOs:

# Who can edit GPOs?
Get-DomainGPO | Get-ObjectACL -DangerousOnly

# Who has rights on the Domain Root?
Get-ObjectACL -Identity "DC=contoso,DC=com" -DangerousOnly

Classifying Identities

Who is actually privileged? The question sounds simple, but it isn’t. Domain Admins — obvious. But what about Account Operators? Server Operators? DnsAdmins? Exchange Windows Permissions?

$result = Test-IsPrivileged -Identity "S-1-5-21-3623811015-12345-512"
$result.IsPrivileged   # $true
$result.Category       # "Privileged"
$result.Reason         # "Privileged group (RID suffix -512)"

Test-IsPrivileged classifies SIDs into five categories: Privileged, Operator, BroadGroup, ExchangeService, and Standard. The function doesn’t just check the SID itself against known privileged SIDs and RID suffixes — it also recursively resolves all group memberships with a single LDAP query using LDAP_MATCHING_RULE_IN_CHAIN. A user who is in a group, that is in a group, that is in Domain Admins, will be correctly identified as Privileged. Additionally, the function checks the sIDHistory attribute for privileged SIDs — a classic vector for SID History Injection.


Kerberos Toolbox

Five Auth Methods, One Function

Invoke-KerberosAuth is the Swiss army knife for Kerberos authentication. Five different methods, one unified API:

# Password-based (AES256)
Invoke-KerberosAuth -UserName "admin" -Domain "contoso.com" -Password "P@ssw0rd"

# Overpass-the-Hash (NT-Hash -> RC4 TGT)
Invoke-KerberosAuth -UserName "admin" -Domain "contoso.com" -NTHash "A1B2C3D4E5F6..."

# Pass-the-Key (AES256)
Invoke-KerberosAuth -UserName "admin" -Domain "contoso.com" -AES256Key "4a3b2c1d5e6f..."

# Pass-the-Key (AES128)
Invoke-KerberosAuth -UserName "admin" -Domain "contoso.com" -AES128Key "4a3b2c1d..."

# PKINIT (certificate-based)
Invoke-KerberosAuth -UserName "admin" -Domain "contoso.com" -Certificate "user.pfx"

Two particularly useful parameters:

  • -OutputKirbi "tgt.kirbi" — Saves the TGT directly as a .kirbi file. No manual Base64 decoding, no byte array handling. Just specify the filename.

Network & Lateral Movement

Testing Admin Access — in Parallel

After enumeration, the next question: which systems do I have admin rights on? Testing this manually takes forever, especially in large environments. Test-RemoteAdminAccess solves this with parallel execution:

# Test all servers - 32 at a time (default)
Get-DomainComputer -OperatingSystem "*Server*" | Select-Object -ExpandProperty dNSHostName | Test-RemoteAdminAccess

The function tests via SMB (ADMIN$ share) by default. If SMB is blocked, it automatically falls back to WMI. With -Method you can force a specific method:

# SMB only (no WMI fallback)
Test-RemoteAdminAccess -ComputerName "SERVER01" -Method SMB -NoFallback

# WMI only (when ADMIN$ is blocked)
Test-RemoteAdminAccess -ComputerName "SERVER01" -Method WMI

Important for real-world use: -Timeout sets a per-target timeout. This prevents the entire scan from hanging because a single host isn’t responding. The default is a few seconds — for slow VPN connections you can increase the value.

The function also automatically detects whether Kerberos tickets (PTT) are present and adjusts its behavior accordingly.


NTLM Impersonation — runas /netonly as a Function

Sometimes you need NTLM authentication instead of Kerberos. For example, when you have credentials but want to keep the existing Kerberos tickets in the cache. Invoke-NTLMImpersonation does exactly that — like runas /netonly, but programmatically:

# Create NTLM token
$token = Invoke-NTLMImpersonation -Credential (Get-Credential)

# All network operations now use the new credentials
# SMB, LDAP, etc. — everything over NTLM
# ... do your work ...

# Revert to original identity
Invoke-RevertToSelf -TokenHandle $token

The key point: existing Kerberos tickets remain in the cache. You only switch the NTLM identity for network operations.


Checking EPA Status - Testing NTLM Relay Protection

NTLM Relay remains one of the most effective attack vectors in AD environments. The countermeasure: Extended Protection for Authentication (EPA). But how do you check whether EPA is actually active on Exchange or ADCS? Normally: manually provoke an NTLM Type2 Challenge, analyze the Channel Binding Token, interpret the result. Or you just use -TestEPA:

# Exchange: check EPA status per endpoint
Invoke-HTTPRequest -ScanExchange -Uri "mail.contoso.com" -TestEPA

# ADCS: check EPA status of Web Enrollment (ESC8 relevance!)
Invoke-HTTPRequest -ScanADCS -Uri "ca.contoso.com" -TestEPA

# ADCS with CA name: also test CES endpoints
Invoke-HTTPRequest -ScanADCS -Uri "ca.contoso.com" -CAName "Contoso-CA" -TestEPA

What happens under the hood: adPEAS performs an NTLM handshake without a Channel Binding Token. If the server rejects the authentication, EPA is active - NTLM Relay is blocked. If the server accepts the handshake, EPA is disabled and the endpoint is vulnerable to relay attacks.

Particularly relevant for Exchange: IIS allows different EPA settings per Virtual Directory. OWA might have EPA enabled while EWS does not. That’s why adPEAS tests each endpoint individually:

$scan = Invoke-HTTPRequest -ScanExchange -Uri "mail.contoso.com" -TestEPA

# Query EPA status per endpoint
$scan.Endpoints.OWA.EPAEnabled          # $true or $false
$scan.Endpoints.EWS.EPAEnabled          # Can be different!
$scan.Endpoints.Autodiscover.EPAEnabled

As a bonus, adPEAS extracts useful information about the target server from the NTLM Type2 Challenge — without needing to authenticate:

$scan.NTLMInfo.NbComputerName   # NetBIOS name: "EX01"
$scan.NTLMInfo.NbDomainName     # NetBIOS domain: "CONTOSO"
$scan.NTLMInfo.DnsComputerName  # FQDN: "ex01.contoso.com"
$scan.NTLMInfo.DnsDomainName    # Domain: "contoso.com"
$scan.NTLMInfo.DnsTreeName      # Forest: "contoso.com"
$scan.NTLMInfo.ServerTimestamp   # Server time (helpful for clock skew analysis)

For ADCS, -TestEPA is particularly interesting in the context of ESC8 (NTLM Relay to ADCS Web Enrollment). If /certsrv/ offers NTLM without EPA, an attacker can use coercion methods (PetitPotam, PrinterBug) to relay a Domain Controller’s authentication to the CA and request a certificate on behalf of the DC.


Helper Utilities

Account Analysis with Test-AccountActivity

Test-AccountActivity is a pipeline-based filter for AD accounts — like Where-Object, but with built-in AD logic:

# Accounts inactive for more than 90 days
Get-DomainUser | Test-AccountActivity -IsInactive

# Password older than 3 years
Get-DomainUser | Test-AccountActivity -PasswordAgeDays 1095

# Never logged in
Get-DomainUser | Test-AccountActivity -NeverLoggedIn

# Password set in 2018 or earlier
Get-DomainUser | Test-AccountActivity -PasswordChangedBeforeYear 2019

What makes it special: all filters are combinable. This allows you to build very targeted queries:

# The "ticking time bombs": active accounts + password never expires + password older than 10 years
Get-DomainUser | Test-AccountActivity -IsActive -PasswordNeverExpires -PasswordAgeDays 3650

# Enabled + never logged in (forgotten accounts - perfect for takeover)
Get-DomainUser | Test-AccountActivity -IsEnabled -NeverLoggedIn

# Inactive computers (no login for 6 months - outdated patches?)
Get-DomainComputer | Test-AccountActivity -IsInactive -InactiveDays 180

One detail that sets this function apart from manual filtering: it differentiates between “inactive” and “never logged in.” An account without a lastLogonTimestamp isn’t “inactive” (= was once active, no longer is), but rather “never used.” These are different findings with different implications.

With -IncludeDetails you can attach the computed values as properties:

Get-DomainUser | Test-AccountActivity -IsInactive -IncludeDetails |
    Select-Object sAMAccountName,
        @{N='DaysInactive'; E={$_.ActivityDetails.DaysSinceActivity}},
        @{N='PwdYear';      E={$_.ActivityDetails.PasswordYear}},
        @{N='NeverExpires';  E={$_.ActivityDetails.HasPasswordNeverExpires}}

The complete list of filters:

FilterDescription
-IsActive / -IsInactiveBased on lastLogonTimestamp
-InactiveDays 180Custom threshold (default: 90 days)
-NeverLoggedIn / -HasLoggedInNever logged in vs. logged in at least once
-PasswordAgeDays 1825Password older than N days
-PasswordChangedBeforeYear 2020Password changed before year X
-PasswordChangedInYear 2021Password changed exactly in year X
-PasswordChangedAfterYear 2023Password changed after year X
-PasswordNeverSetPassword was never set
-IsEnabled / -IsDisabledAccount status
-PasswordNeverExpiresDONT_EXPIRE_PASSWORD flag
-PasswordNotRequiredPASSWD_NOTREQD flag

Analyzing Certificates with Get-CertificateInfo

You have a PFX from an ADCS attack, from Shadow Credentials, or from a file share - and want to know what’s inside? Get-CertificateInfo shows you everything important without importing the certificate into the Windows Certificate Store:

Get-CertificateInfo -Certificate ".\admin.pfx" -CertificatePassword "pass"
[?] Certificate Information
[+] General:
    Subject:                                     CN=Administrator
    Issuer:                                      CN=Contoso-CA, DC=contoso, DC=com
    Serial Number:                               1A00000005...
    Thumbprint:                                  3F2B1C...
[+] Validity:
    Not Before:                                  2026-02-19 10:50:21
    Not After:                                   2027-02-19 10:50:21
    Status:                                      Valid (expires in 365 days)
[+] Key Information:
    Has Private Key:                             True
    Key Algorithm:                               RSA (2048 bit)
[+] Extended Key Usage:
    [1] Client Authentication (1.3.6.1.5.5.7.3.2)
    [2] Smartcard Logon (1.3.6.1.4.1.311.20.2.2)
[+] PKINIT Assessment:
    PKINIT Capable:                              True
    PKINIT EKUs:                                 Client Authentication, Smartcard Logon
    Identities:
        [1] UPN:                                 Administrator@contoso.com
    Usage:
    Connect-adPEAS -Domain contoso.com -Certificate '.\admin.pfx' -CertificatePassword 'pass'

Particularly useful: The PKINIT Assessment instantly tells you whether and how you can use the certificate for Kerberos authentication - including a ready-made Connect-adPEAS command for copy-paste.

Works with all common formats:

# PFX/P12 with password
Get-CertificateInfo -Certificate ".\admin.pfx" -CertificatePassword "pass"

# PFX without password (e.g., from Request-ADCSCertificate -NoPassword)
Get-CertificateInfo -Certificate ".\admin.pfx"

# CER/DER (public key only)
Get-CertificateInfo -Certificate ".\server.cer"

# For scripting: -PassThru returns an object
$info = Get-CertificateInfo -Certificate ".\admin.pfx" -PassThru
$info.PKINITCapable    # True/False
$info.Identities       # @("UPN:Administrator@contoso.com")
$info.HasPrivateKey    # True

Tab Completion — Faster Navigation

In large AD environments, tab completion is a real productivity boost. adPEAS offers two modes:

Lazy Loading (Default): The cache is built automatically on the first TAB press - per object type and only on demand. Pressing Get-DomainUser -Identity adm<TAB> triggers the user cache. Computers, Groups, and GPOs are only loaded when actually needed. No overhead at connect time, no unnecessary load.

Eager Loading: If you want to preload all object types, use -BuildCompletionCache at connect time:

Connect-adPEAS -Domain "contoso.com" -UseWindowsAuth -BuildCompletionCache

In both cases, tab completion works identically for all Get-Domain* and Set-Domain* functions:

Get-DomainUser -Identity adm<TAB>        # -> "administrator", "admin.smith", ...
Get-DomainComputer -Identity DC<TAB>     # -> "DC01", "DC02", ...
Get-DomainGroup -Identity Domain<TAB>    # -> "Domain Admins", "Domain Users", ...
Get-DomainGPO -Identity Def<TAB>         # -> "Default Domain Policy", ...

In an environment with 5,000 users, 500 computers, and 200 groups, this saves several seconds per query. Over the course of a full pentest day, that adds up.

The cache persists as long as the PowerShell session is running and can be manually refreshed at any time with Build-CompletionCache.


Checking Session Status

Mid-pentest question: am I still connected? Which auth method? Which DC?

Get-adPEASSession

Shows the current session status: domain, server, authentication method, and whether Kerberos tickets are present. Especially useful after a longer break or when queries suddenly start failing.


Regenerating and Comparing Reports Offline

Two functions that don’t need an active AD connection — just a JSON file from a previous scan:

Report Conversion: When a new adPEAS version is released, you don’t have to re-scan. Convert-adPEASReport takes the JSON file and generates fresh reports with updated templates and scoring:

# Regenerate all formats (HTML + Text + JSON)
Convert-adPEASReport -InputJson ".\audit.json" -OutputPath ".\new_report"

# HTML only for the client
Convert-adPEASReport -InputJson ".\audit.json" -OutputPath ".\new_report" -Format HTML

Report Comparison: After a remediation phase, you want to know what changed. Compare-adPEASReport compares two JSON exports:

# Baseline vs. Current
Compare-adPEASReport -Baseline ".\scan_q1.json" -Current ".\scan_q2.json"

The output shows new, remediated, and changed findings — grouped by check category. Especially useful for quarterly compliance audits or validating remediation efforts.

Details on both functions in Episode 5: Output & Reports.


Searching Attributes with Search-Value

You have 500 users from Get-DomainUser and you’re looking for a specific string — but you don’t know which attribute it’s in? Search-Value searches all properties of every pipeline object:

# Which attribute contains "admin"?
Get-DomainUser | Search-Value "admin"
  administrator
    sAMAccountName: administrator
    description: Built-in account for administering the computer/domain
  f.sinatra
    description: User Help Desk Admin
    memberOf: ClientAdmins
    memberOf: ServerAdmins

For each matching object, Search-Value shows the account name and the properties where the search term was found. This saves you the manual pipeline with ForEach-Object and Where-Object.

The search works with all Get-Domain* functions:

# Computers with "SQL" in any attribute
Get-DomainComputer | Search-Value "SQL"

# Groups with "Exchange" in name or description
Get-DomainGroup | Search-Value "Exchange" -Property name,description

# Regex search: users whose password was set in 2019
Get-DomainUser | Search-Value "^01/..../../2019" -Regex -Property pwdLastSet

# Exact match
Get-DomainUser | Search-Value "DONT_EXPIRE_PASSWORD" -Exact -Property userAccountControl

The three search modes:

ModeSyntaxBehavior
Wildcard (Default)Search-Value "admin"*admin* — matches anywhere in the value
ExactSearch-Value "admin" -ExactExact string comparison
RegexSearch-Value "^adm\d+" -RegexRegular expression

Use -Property to restrict the search to specific attributes. Without it, all attributes are searched.


Previous episode: Episode 8 - PAC Deep-Dive & Ticket Forging

About the Author

Alexander Sturz

Founder & Red Team Lead

Active Directory Ninja and offensive security expert specializing in enterprise infrastructure compromise and post-exploitation techniques.

Related Articles