PowerView is probably my favorite bit of code I’ve written, and definitely the one I most regularly use (as evidenced by my recent posts). My team also heavily utilizes the toolkit, and we’ve come up with some cool uses for it over the past several years. For a long time I’ve wanted to share some of the real “power” uses of PowerView, like the PowerView “tricks” highlighted here.
My intention for this series is to demonstrate how you can use PowerView to solve interesting problems and the thought process we put behind each solution. These posts should be short-and-sweet, less complicated (and subsequently more frequent) than my normal posts, but no promises as I’m not intending to stick to any standard release timeline :) Also, I’d like to point out that PowerView is not an inherently “offensive” or “defensive” toolset- it’s a tool to help solve Active Directory problems, no matter the color.
Everything in this series will be based on real-world scenarios and each article will feature at least one function from PowerView. As these scenarios are based on real life problems, the solutions may not always be the most elegant, but they should hopefully be useful. I’m convinced that a large number of people use a relatively small part of PowerView’s functionality, and I hope to demonstrate its full capabilities to everyone.
The posts in this series will conform to this semi-standard format:
- The Scenario: highlight an operational problem we encountered on an engagement
- The Solution: provide the complete PowerView-based solution I or my team came up with
- The Explanation: break the solution down piece by piece, and explain our thought process step by step
I’m hoping to rope some additional coworkers into the effort, and will update this post with the subsequent posts as they surface:
- The PowerView PowerUsage Series #1 – Mass User Profile Enumeration
- The PowerView PowerUsage Series #2 – Mapping Computer Shortnames With the Global Catalog
- The PowerView PowerUsage Series #3 – Enumerating GPO edit rights in a foreign domain
- The PowerView PowerUsage Series #4 – Finding cross-trust ACEs
- The PowerView PowerUsage Series #5 – Performing username -> workstation mappings
The Scenario
You’re on an engagement with elevated domain rights, and want to map out who has RDP’ed (or otherwise interactively logged into) the systems you have access to. You’re also aware that the domain may already be compromised, so you want to limit credential exposure as best as you can. And it’d be nice if we have a filterable CSV to deliver the data to the client as well, as well a minimizing any “noise” if possible.
The Solution
Get-DomainComputer -LDAPFilter '(dnshostname=*)' -Properties dnshostname -UACFilter NOT_TRUSTED_FOR_DELEGATION -Ping | % { try { $ComputerName = $_.dnshostname Get-WmiObject -Class Win32_UserProfile -Filter "NOT SID = 'S-1-5-18' AND NOT SID = 'S-1-5-19' AND NOT SID = 'S-1-5-20'" -ComputerName $_.dnshostname -ErrorAction SilentlyContinue | % { if ($_.SID -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$') { $LocalPath, $Time = '', '' if ($_.LastUseTime) { $Time = ([WMI]'').ConvertToDateTime($_.LastUseTime) } if ($_.LocalPath) { $LocalPath = $_.LocalPath.Split('\')[-1] } New-Object PSObject -Property @{'ComputerName'=$ComputerName ; 'SID'=$_.SID; 'LocalPath'=$LocalPath; 'LastUseTime'=$Time} } } } catch {} } | Export-Csv -NoTypeInformation user_profiles.csv
The Explanation
First, we’re using Get-DomainComputer to retrieve Active Directory computer objects for our current domain. To reduce a bit of noise, we implement a custom LDAPFilter that forces the function to only return computers that have a dnsHostName set in AD. Since we also only care about the dnshostname of the machine’s returned, we can set -Properties to only ask the associated domain controller to return that information. This helps reduce the amount of traffic between us and the DC.
Since we know the domain might be compromised, we want to be as careful as we can with our elevated credentials when making remote network connections. Sean Metcalf has a great post on the dangers of unconstrained delegation, which I don’t have time to get into here, but we can at least only return machines that DON’T have the TRUSTED_FOR_DELEGATION flag set. This will return systems that should be OK to touch through things like WMI. Finally, we also only want machines we can reach from our current network stance- the -Ping parameter will only return machines that respond to a standard ICMP ping.
So now we have the set of hostnames we’re interested in returning on the pipeline. For this particular scenario, where we wanted to see who had ever logged in on a machine, we settled on enumerating the Win32_UserProfile class through Get-WmiObject to see what interactive users had a profile generated on the machine. To reduce some noise, we can “optimize to the left” and filter out some common SIDs we don’t care about as well.
Finally, we only care about domain SIDs, hence the ‘S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$’ filter. We then convert the LastUseTime for the profile to a readable format, and split the profile path returned so we can get an idea about the user name for the profile. We cap it all off by using New-Object PSObject to create a new custom object in the middle of the pipeline that contains the final output elements we care about, piping everything to Export-Csv for easily parsable output.
Nice example. Why not use Protected Users group or if older than 2012R2 DCs, at least use “account is sensitive and cannot be delegated” flag on your account?
Good point! Slipped my mind for sure