Wednesday, March 20, 2024
New-ADComputer : A required attribute is missing
Sunday, August 20, 2023
Splitting AD integrated reverse DNS zones
When it comes to splitting the zone, there's a few things to remember with AD integrated DNS. All records are objects under a zone object. Making changes in the dns management console doesn't mean objects in AD will automatically be deleted or migrated for you. You can end up in situations where hidden old records still exist in ldap, but don't show up in the dns management console. This code example below will help guide you in extracting the records you want and putting them in a new ldap dns zone object, along with preserving the data, timestamps, and permissions on the objects.
This example below is splitting of all 10.1.x.x records from a 10.x.x.x reverse zone. Before running your modified code, create your new reverse dns zone, then stop the dns server on the domain controller that you are making this change on.
get-adobject -searchbase "DC=10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com" -ldapfilter "(objectclass=dnsnode)" |
where {$_.name -match "\.1$"} |
move-adobject -targetpath "DC=1.10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com"
get-adobject -searchbase "DC=1.10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com" -ldapfilter "(objectclass=dnsnode)" |
where {$_.name -match "\.1$"} | %{
$newname = $_.name.replace(".1","")
$_ | rename-adobject -newname $newname
}
Thursday, June 23, 2022
PetitPotam Defenses
Protection against coerced authentication on domain controllers:
Print Spooler:
Disable the service via group policy on all DC's
EFS RPC attack:
Create the two RPC filters by putting this in a text file (source):
rpc
filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=c681d488-d850-11d0-8c52-00c04fd90f7e
add filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=df1941c5-fe89-4e79-bf10-463657acf44d
add filter
quit
Save the file and use "netsh -f filename.txt" to apply it
DFS RPC attack:
Create one RPC filter by putting this in a text file (source)
rpc
filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=4fc742e0-4a10-11cf-8273-00aa004ae673
add filter
quit
Save the file and use "netsh -f filename.txt" to apply it
This dfs filter has some impact on creation of new dfs namespaces. Otherwise it doesn't seem to cause other issues that I can tell.
Certificate Authority hardening:
Follow the MS guidance on hardening the CA against NTLM relay. Essentially you configure the web interfaces to allow kerberos only with extended protection. If possible, disable NTLM auth completely on the server. Beyond that, limiting access to client certificates and not allowing user supplied SAN's on them should be done.
Tuesday, September 28, 2021
Capturing unique simple bind or unsigned ldap queries from a domain controller
$query = @"
<QueryList>
<Query Id="0" Path="Directory Service">
<Select Path="Directory Service">*[System[(EventID=2889)]]</Select>
</Query>
</QueryList>
"@
$somelistofdomaincontrollers | %{
$serv = $_
$hashes = @();
get-winevent -filterxml $query | select @{n="dc";e={$_.machinename}},
@{n="source";e={($_.properties.value[0].split(":"))[0]}},
@{n="user";e={$_.properties.value[1]}},
@{n='connhash';e={$str = ($_.machinename +
$_.properties.value[0].split(":"))[0] +
$_.properties.value[1]; $str.gethashcode()}} | %{
if ($hashes.contains($_.connhash)) {} else {$hashes += $_.connhash; $_|
select dc,source,user}
}
Thursday, August 5, 2021
Quick way to find all OU's in a domain that block gpo inheritence
get-adobject -ldapfilter "(&(objectclass=organizationalunit)(gpoptions:1.2.840.113556.1.4.803:=1))"
Wednesday, May 8, 2019
AD groups - setting group owner and delegating permission with powershell
[CmdletBinding()]
param(
[parameter(helpmessage="samaccountname of group owner",mandatory=$true)]$owner,
#defaults to current user's domain if not specified
[parameter(helpmessage="netbios domain name")]$domain=$env:userdomain,
[parameter(parametersetname='pipe', valuefrompipeline=$true)]
[Microsoft.ActiveDirectory.Management.ADGroup]$group,
[parameter(parametersetname='normal',valuefrompipeline=$false, helpmessage="AD group name")]$identity,
#for cross domain? needs testing and what if the group and owner are in different domains?
[parameter(helpmessage="domain controller to do object lookups")]$server
)
begin{
import-module activedirectory
if ($server -eq $null) { $server = (get-addomaincontroller -Discover -Writable).hostname}
}
process {
#allow for both a single group name to be passed as $identity, or take pipeline get-adgroup output
if ($pscmdlet.parametersetname -eq 'normal') {
try {
[array]$group = get-adgroup $identity -property managedby -ea stop -server $server
} catch {
throw "Unable to find AD group"
}
}
#cast the group as an array for the foreach loop
if (!($group -is [array])) { $group = [array]$group}
#hardcode guid for the write members permission
$guid =[guid]'bf9679c0-0de6-11d0-a285-00aa003049e2'
#get the group owner's sid - this could be replaced with get-aduser
$user = New-Object System.Security.Principal.NTAccount("$domain\$owner")
$sid =$user.translate([System.Security.Principal.SecurityIdentifier])
$ctrl =[System.Security.AccessControl.AccessControlType]::Allow
$rights =[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
$intype =[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
try {
try {
$aduserobj = get-aduser $owner -server $server
} catch {
#if no user was found try looking for a group owner
$aduserobj = get-aduser $owner -server $server
}
} catch {
throw "Unable to find owner's account"
}
foreach ($groupobj in $group) {
$acl = Get-Acl ad:"$($groupobj.distinguishedname)"
#if the group was passed without the managedby property, look it up again
if (($groupobj |gm |where {$_.name -eq "managedby"}) -eq $null) {
#if there was a previous manager listed that doesn't match the one being passed to this script
# remove any existing rights for that user
$groupobj = get-adgroup $groupobj.distinguishedname -property managedby -server $server
}
if ($groupobj.managedby -ne $null) {
try {
$previousownersid = (get-aduser $groupobj.managedby -property sid -server $server).sid } catch { $previousownersid = (get-adgroup $groupobj.managedby -property sid -server $server).sid
if ($previousownersid.value -ne $sid.value) {
#$prevownerID = new-object system.security.principal.securityidentifier($previousownersid)
$previousownerID = $previousownersid.Translate([System.security.principal.ntaccount]).Value
$acl.access | where {$_.identityreference -eq $previousownerID} | % {
$acl.RemoveAccessRule($_)
}
}
}
#set the ManagedBy property to the new owner
set-adgroup $groupobj -managedby $aduserobj -server $server
#add new owner's right to manage members
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid,$rights,$ctrl,$guid)
$acl.AddAccessRule($rule)
Set-Acl -acl $acl -path ad:"$($groupobj.distinguishedname)"
}
}
Thursday, October 5, 2017
Master list of Domain Join errors
Main Error Message on client: "An Active Directory Domain Controller (AD DC) for the domain 'test.local' could not be contacted. Ensure that the domain name is typed correctly"
Sub Error Message when Details are expanded:
Situation: a RODC is accessible, however a RW domain controller is not accessible. Your machine may be at a branch office with a local RODC that is handling dns queries, however the link connecting back to a writable domain controller is down. Additionally this error could come up if the client has a functioning dns server to query that does provide answers, but due to some connectivity problem, the machine can't connect to a domain controller.
Sub Error Message when Details are expanded:
DNS was successfully querie for the service location (SRV) resource record used to locate a domain controller for domain "testforest.local":
The query was for the SRV record _ldap._tcp.dc._msdcs.testforest.local
The following domain controllers were identified by the query:
forest1dc1.testforest.local
However no domain controllers could be contacted.
Steps to Perform: 1) Ensure that the name typed in for the domain name on the client is the correct name, 2) check DNS infrastructure to find a server that is capable of resolving the active directory domain's dns zone.
Sub Error Message when Details are expanded:
Error: the RPC Server is unavailable
Error: Extremely slow domain join and everything else (boot up, logon, etc)
Error: none
Situation: Clock time of machine doesn't match domain controller (large skew >5min)
What is seen: No problem in domain join. System reboot, logon are all fine. Clock time sync's after domain join reboot.
Error: "An Active Directory Domain Controller (AD DC) for the domain 'test.local' could not be contacted. Ensure that the domain name is typed correctly"
Sub error message in Details:
Monday, July 31, 2017
Testing connectivity to your domain controller
To begin with, you should know what domain controller your workstation has logged into. This machine logon establishes the "secure channel" between your machine and the domain. You can use an old tool that is still around called nltest.
C:\Windows>nltest /sc_query:contoso.com
Flags: 30 HAS_IP HAS_TIMESERV
Trusted DC Name \\DC1.contoso.com
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully
This output shows the status of your secure channel, and the name of the domain controller you are querying. You will need to provide the name of the domain you are connected to. FQDN domain name or NETBIOS domain name should work fine.
This script will provide two functions, one port checker and one function to run to test your connection. Run Test-DomainControllerPorts with your domain name (or leave it blank for auto detect). The script returns the name of the DC that you are connected to, along with 2 arrays of ports that are open and another of ports that aren't responding.
function tcpt ([string]$serv, [string]$p) {
$result = $false
try {
$conn = new-object system.net.sockets.tcpclient($serv,$p)
if ($conn.connected) { $result = $true } else { result = $false }
$conn.close()
} catch {
$result = $false
}
$conn = $null
return $result
}
function test-DomainControllerPorts {
param (
$domainname = (gwmi win32_computersystem).domain
)
$secureChannelDC = (nltest /sc_query:$domainname |
where {$_ -match "Trusted DC Name"}).split("\\") |
where {$_ -match $domainname}
$secureChanneldc = $securechanneldc.trim()
$functionalports = @()
$nonFunctionalPorts = @()
$portsToCheck = ("53", "88", "135", "137", "139", "389", "445", "464", "3268", "636", "3269")
foreach ($port in $portsToCheck) {
$portstat = tcpt $secureChannelDC $port
if ($portstat) {
$functionalports += $port
} else {
$nonfunctionalPorts += $port
}
}
$result = new-object PSObject
add-member -inp $result NoteProperty DomainController $secureChannelDC
add-member -inp $result NoteProperty OpenPorts $functionalports
add-member -inp $result NoteProperty UnOpenPorts $nonfunctionalports
out-default -inp $result
}
Sunday, July 2, 2017
AD: Simple way to remove all members of a group
Set-adgroup -identity "name of group" -clear member
The time required to execute will vary depending on number of people in the group.
Wednesday, March 29, 2017
Password change failed: Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied
Tuesday, December 27, 2016
Fixing dns record permissions for dynamic dns
param (
$computername,
$dnsrecord
)
$script:computernameSam = $computername + "$"
try {
import-module activedirectory
} catch {
write-error "This script requires the AD powershell module"
exit
}
while ( (test-path -path Ad:) -ne $true )
{
start-sleep -seconds 2
}
#Standard ACL for a dynamic dns entry
#ActiveDirectoryRights : CreateChild, DeleteChild, ListChildren, ReadProperty, DeleteTree, ExtendedRight, Delete,
# GenericWrite, WriteDacl, WriteOwner
#InheritanceType : None
#ObjectType : 00000000-0000-0000-0000-000000000000
#InheritedObjectType : 00000000-0000-0000-0000-000000000000
#ObjectFlags : None
#AccessControlType : Allow
#IdentityReference : Domain\machine$
#IsInherited : False
#InheritanceFlags : None
#PropagationFlags : None
#
function get-partition {
param ( $record )
#need to split off everything after first name to find longest zone match
#using get-dnsserverzone ($name)
$dnsrecordparts = $record.split(".")
for ($i = 1; $i -lt $dnsrecordparts.length; $i++) {
$zonenameTest = $dnsrecordparts[$i..($dnsrecordparts.length -1)] -join "."
$zoneObj = get-dnsserverzone $zonenameTest -ea 0
if ($zoneObj -ne $null) {
write-output -inputobject $zoneobj
$i = $dnsrecordparts.length + 1
}
}
}
function get-dnsobject {
param ($record)
$zoneObject = get-partition -record $record
if ($zoneObject -ne $null) {
$zonename = $zoneObject.zonename
$record -match "(.*)(\.$zonename)"
$dnsRecordDN = "dc=" + $matches[1] + "," + $zoneobject.distinguishedname
try {
get-adobject $dnsRecordDN
} catch { Throw "Unable to find dns record for this machine"}
} else { throw "DNS zone not found"}
}
try {
try {
$guid = [guid]'00000000-0000-0000-0000-000000000000'
$adcomputer = get-adcomputer $computername -property objectsid
$sid = $adcomputer.objectsid
$ctrl = [System.Security.AccessControl.AccessControlType]::Allow
$rights = 983423
$intype =[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid,$rights,$ctrl,$guid)
} catch { throw "Unable to get computer account SID" }
try {
#find record
$dnsDN = get-dnsObject -record $dnsrecord
} catch { throw $_ }
try {
$acl = get-acl ad:"$($dnsDN.distinguishedname)"
$acl.setowner([system.security.principal.ntaccount]"$script:computernameSam")
$acl.AddAccessRule($rule)
Set-Acl -acl $acl -path ad:"$($dnsDN.distinguishedname)"
} catch { throw $_ }
} catch { $_}
Thursday, July 28, 2016
MS - Certificate autoenrollment behind a firewall
Client to domain controller:
Ldap (TCP 389)
RPC (tcp 135)
RPC on dynamic port (>1023 TCP)
Client to certificate server(s) with the template available
Dynamic RPC (TCP > 1023) for CA servers on windows 2003 and earlier
Dynamic RPC (TCP > 49151) for CA servers on newer windows OS's
ProtSeq:ncacn_ip_tcp
Endpoint:52775
NetOpt:
Annotation:
IsListening:YES
StringBinding:ncacn_ip_tcp:computername[52775]
UUID:91ae6020-9e3c-11cf-8d7c-00aa00c091be
ComTimeOutValue:RPC_C_BINDING_DEFAULT_TIMEOUT
VersMajor 0 VersMinor 0
Once you have obtained the port, you can use any port testing tool from the client to the CA server, like test-netconnection to see if that port, and port 135 is accessible to the client.
Tuesday, June 21, 2016
Tips for AD group membership managment in powershell
Fails: Get-adgroupmember "LargeGroup"
error: Get-ADGroupMember : The size limit for this request was exceeded
Works: Add-adgroupmember and remove-adgroupmember
Work Around: get-adgroup "LargeGroup" -properties members | select -expand members
This will get the distinguishednames of all members as an array.
-----------------------------------------------
Piping groups or users into a group membership cmdlet to change the group memberships.
1) When you are piping groups into a cmdlet where the user(s) are static. Pipe to Add-ADGroupMember.
Ex: get-adgroup -filter {name -like "HelpDesk*"}| add-adgroupmember -members $userdn
2) When you are piping users into a cmdlet where the group(s) are static. Pipe to Add-ADPrincipalGroupMembership
Ex: get-aduser bob | Add-ADPrincipalGroupMembership -memberof $groupdn
NOTE: Add-ADPrincipalGroupMembership will generate successful security audit events (Directory Service Change) for the addition of the group member, even if they were already a member of the group
-----------------------------------------------
When using Add-ADGroupMember with an array of members, if any of them are part of the group already, the whole operation will fail. Its best to try adding one at a time.
Thursday, June 16, 2016
Piping get-aduser output through several custom powershell functions
What I wanted:
Take an OU, run get-aduser on the OU -> Pipe to an analysis function to check password expiration for different types of accounts and password policies, then decide if an email notice needed to be sent -> Pipe (if needed) to an email function -> Pipe the results of all of the above to logging function.
At each stage, different bits of calculated data or additional properties needed to be added to the original get-aduser object. This was possible by using custom PSObjects after the initial analysis function. The basics of the code is below:
function process-OU {
param(
[parameter(mandatory=$true)][string]$searchbase,
[string]$type="standard"
)
Get-ADUser -Filter {(enabled -eq $True) -and (mail -like "*") } `
-SearchBase $SearchBase `
-Properties mail, PasswordLastSet, sn, PasswordNeverExpires |
analyze-user -type $type |email-user |log-result
}
function Analyze-User{
[CmdletBinding()]
param (
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
[Microsoft.ActiveDirectory.Management.ADAccount]$user,
[string]$Type
)
begin {}
process {
#do some analysis and decide if you want to
#continue with write-output $user
#
#Add any additional pieces of information to the user object with
# add-member -input $user -force NoteProperty Expired $False
if ($proceedtoEmail) { write-output $user }
}
}
function Email-User {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
[PSobject]$emailuser
)
#Notice the parameter type is a generic
#[psobject] as it is no long conforming
#to the [Microsoft.ActiveDirectory.Management.ADAccount] type
Begin{}
Process {
#handle email creation and sending.
#Check if it was sent without error,
#add email status as another property
}
}
function log-result {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
[PSObject]$user
)
begin {}
process {
#do some logging here
}
}
process-OU -searchbase "ou=myusers,dc=contoso,dc=com" -type "regular"
Thursday, April 14, 2016
Tuesday, April 12, 2016
Active Directory ACL's explained
Permission set in GUI: "Apply to: All Descendant objects, create/delete Conference Site objects"
ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType : Descendents
ObjectType : msExchConferenceContainer
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Allow
IdentityReference : TEST.LOCAL\Nathan
IsInherited : False
InheritanceFlags : ContainerInherit
PropagationFlags : InheritOnly
Permission set in GUI: "Apply to: This object and all descendant objects, create/delete Contact objects"
ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType : All
ObjectType : contact
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Allow
IdentityReference : TEST.LOCAL\Nathan
IsInherited : False
InheritanceFlags : ContainerInherit
PropagationFlags : None
Permission set in GUI: "Apply to: This object only, create/delete Computer Objects"
ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType : None
ObjectType : computer
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Allow
IdentityReference : TEST.LOCAL\Nathan
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
Permission set in GUI: "Apply to: Descendent Computer objects, Modify Owner"
ActiveDirectoryRights : WriteOwner
InheritanceType : Descendents
ObjectType : 00000000-0000-0000-0000-000000000000
InheritedObjectType : computer
ObjectFlags : InheritedObjectAceTypePresent
AccessControlType : Allow
IdentityReference : BHI-MASTER\adminlinlnat
IsInherited : False
InheritanceFlags : ContainerInherit
PropagationFlags : InheritOnly
InheritedObjectType: Notice this will be all zero's when the permission is for creating a child object in a container. When it is permissions being set on a specific type of child objects, then it will be set that that object type, and the ObjectType value will be all zero's. When setting a permission on a specific property of a specific type of child object, you will get both fields filled in with the ObjectType being the specified property, and InheritedObjectType being the AD object's type.
PropagationFlags: InheritOnly exists when applying to something other than the current OU. (https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags(v=vs.110).aspx)
InheritenceFlags: ContainerInherit when applying to anything below the current level, ObjectInherit when applying to child objects (https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags(v=vs.110).aspx)
InheritanceType: All (everything from this level down), Descendents (children and descendants, not the current object), None (current level only) (https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurityinheritance(v=vs.110).aspx)
Wednesday, December 16, 2015
Tis the season for vacation clearing (and password expiration)
import-module activedirectory
#grab the domain wide password policy and extract a # of days integer
$passwordage = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge |select -exp days
#define your start and end filter dates and subtract the max Password age value.
#We need to calculate using passwordlastset timestamps
$startdate = ([datetime]"12-21-2015").adddays(-$passwordage)
$enddate = ([datetime]"1-4-2016").adddays(-$passwordage)
#Filter as much as possible on the LDAP side with the date ranges.
#The Select statement includes a calculated
#expression to convert the passwordlastset value to an actual expiration date.
#Convert to CSV and output to file.
#Zip it and mail it out.
get-aduser -filter {(enabled -eq $true) -and (passwordlastset -ge $startdate) -and (passwordlastset -le $enddate)} -Properties passwordlastset, mail | select samaccountname, name, mail, @{name="ExpirationDate"; exp={$_.passwordlastset.adddays($passwordage)}} | convertto-csv -notypeinfo | out-file .\expiringholidays.csv
Monday, August 3, 2015
Encouraging domain controller to advertise time
param(
[parameter(mandatory=$true)]$computer
)
$sb = {
set-itemproperty -path `
HKLM:\system\currentcontrolset\services\w32time\timeproviders\ntpserver `
-name "Enabled" -value "1" restart-service w32time w32tm /resync } invoke-command -script $sb -computer $computer
2023 - Update to this article. It is possible for a server to have a value of 1 on the Enabled dword
for ntserver and the server still reports that it is not advertising. There is a group policy setting
that can prevent the machine from acting as an NTP server which will block the service from working and won't
present anything obvious in the registry.
Checking NTP time offsets with powershell - parsing dos utility output
The number of samples in the stripchart command will greatly impact the run time if you are running it against a large number of computers. I put it at 3 due to false non-responsive alerts when it was set to 1. If your network is repsonsive, 1 may be sufficient.
function get-timeoffset {
param([parameter(mandatory=$true)]$computer)
write-verbose "working on server $computer"
$resultval = new-object PSobject
add-member -input $resultval NoteProperty Computer $computer
$a = w32tm /stripchart /computer:$computer /dataonly /samples:3 /ipprotocol:4
if (-not ($a -is [array])) {
add-member -input $resultval NoteProperty Status "Offline"
add-member -input $resultval NoteProperty Offset $null
} else {
$foundtime=$false
#go through the 5 samples to find a response with timeoffset.
for ($i = 3; $i -lt 8; $i++) {
if (-not $foundtime) {
if ($a[$i] -match ", ([-+]\d+\.\d+)s") {
$offset = [float]$matches[1]
add-member -input $resultval NoteProperty Status "Online"
add-member -input $resultval NoteProperty Offset $offset
$foundtime=$true
}
}
}
#if no time samples were found, check for error
if (-not $foundtime) {
if ($a[3] -match "error") {
#0x800705B4 is not advertising/responding
add-member -input $resultval NoteProperty Status "NTP not responding"
} else {
add-member -input $resultval NoteProperty Status $a[3]
}
add-member -input $resultval NoteProperty Offset $null
}
}
$resultval
}
Thursday, July 23, 2015
Finding domain controllers replicating from a particular server
get-adobject -searchbase `
("CN=Sites," + (get-adforest).partitionscontainer.substring(14)) `
-ldapfilter "(objectclass=nTDSConnection)" `
-Properties distinguishedname,fromServer |select distinguishedname,fromServer |
where {$_.fromserver -match "servername"}