Dans cet article nous verrons comment programmer avec PowerShell des fonctions d’exploitation, ce qui permettra d’introduire plusieurs notions qui seront très utilisées dans le future, de mieux comprendre comment PowerView fonctionne, et apprendre à interagir avec un Active Directory à l’aide des classes .NET et ADSI. Si PowerShell vous est totalement étranger, je vous recommande la série d’article sur ce dernier présente sur le blog.
Get-Domain
Une fonction élémentaire; tous les programmes qui interagissent avec un annuaire LDAP y ont recours. Elle permet d’obtenir les informations sur le contexte actuel de l’éventuel domaine ou forêt à laquelle est jointe la machine: le nom du domaine, les contrôleurs de domaine et leurs rôles. Pour permettre cette énumération, l’espace de nom System.DirectoryServices.ActiveDirectory
est dédié, on y retrouve la méthode
et ::GetCurrentDomain()
. La première ne nécessite pas d’argument on l’appelle comme ceci:::GetDomain()
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
l’intérêt de la deuxième est qu’elle prend en argument un DirectoryContext
. Un DirectoryContext
est une classe appartenant au même espace de nom, qui prend en arguments plusieurs choses: un domaine alternatif, et des identifiants, on l’utilise comme ceci:
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DomainContext('Domain', 'contoso.local') $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DomainContext('Domain', 'contoso.local', 'UserName', 'Password') # ce qui permet d’utiliser GetDomain() [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
Ainsi la fonction
retourne un objet .NET spécialement conçu en fonction des paramètres donnés, des identifiants alternatifs, ou bien un domaine cible. La fonction utilisée dans les scripts complet est une version légèrement modifiée de celle proposée par @Harmj0y (pour la simple raison qu’elle est parfaite).Get-Domain
Invoke-DomainSearcher
Cette fonction permet la recherche d’objet au sein de l’annuaire LDAP en utilisant System.DirectoryServices.DirectorySearcher
. Cette fonction à besoin de
pour pouvoir obtenir le contrôleur de domaine possédant le rôle “PDC Emulator”, qu’il va intégrer dans une base de recherche, qui est généralement à la racine du domaine. Elle ressemblera ainsi à Get-Domain
LDAP://DC.domain.local/
. Un filtre est appliqué grâce à la propriété d’objet Filter
. le résultat de la recherche est retourné. De manière grossière la partie de recherche est donc composée des étapes suivantes:
$domainObj = Get-Domain $PDC = ($domainObj.PdcRoleOwner).Name $SearchString = "LDAP://" $SearchString += $PDC + "/" $DistinguishedName = "DC=$($domainObj.Name.Replace('.',’DC='))" $SearchString += $DistinguishedName $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) $objDomain = New-Object System.DirectoryServices.DirectoryEntry $Searcher.SearchRoot = $objDomain $Searcher.Filter = $Filter $result = $Searcher.FindAll()
Get-DomainObject
Cette fonction à pour but de retourner les propriétés d’un objet donné par l’utilisateur en argument. Le but est d’obtenir le chemin ADS qui permettra d’effectuer des modifications sur l’objet. Un filtre LDAP est constitué à partir de l’identification qu’il est possible de faire avec l’identité de l’objet. Par exemple, si la taille du tableau formé de la chaîne de caractère de l’identité séparé tous les “-” vaut 8, il s’agira du SID de l’objet, ainsi de suite. le code ressemble donc à cela:
$filter = @("(|") if ($Identity.Contains("LDAP://") -and $Identity.Contains("DC=")) { $filter += "(distinguishedName=$($Identity.Replace("LDAP://")))" } elseif ($Identity.Contains("DC=")) { $filter += "(distinguishedName=$Identity)" } elseif ($Identity.Split("-").Count -eq 8) { $filter += "(objectSid=$Identity)" } elseif ($Identity.Split(".") -ge 3) { $filter += "(dnshostname=$Identity)" } else { $filter += "(|(samAccountName=$Identity)(name=$Identity)(cn=$Identity))" } $finalFilter = (-join $filter) + ")" $results = Invoke-DomainSearcher -Filter $finalFilter
Ensuite, un psObject
est créé à partir des objets présents dans le résultat de la recherche.
Get-DomainObjectAcl
Cette fonction permet d’obtenir le contenu de la DACL d’un objet. Elle renvoie à ce titre un PSCustomObject
comprenant plusieurs propriétés. Pour obtenir ces dernières, il faut monter le chemin ADS de l’objet, et accéder au champ ObjectSecurity
. Puis utiliser la méthode
.GetAccessRules()
$ADSIObject = New-Object System.DirectoryServices.DirectoryEntry($Object.path) $aclObject = $ADSIObject.PSBase.ObjectSecurity $aclList = $aclObject.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
Add-DomainObjectAcl
La première fonction d’exploitation permet d’ajouter une ACL dans le cas de l’utilisation du droit WriteDACL
. On commence par accéder à l’objet avec une DirectoryEntry
:
$ADSIObject = New-Object System.DirectoryServices.DirectoryEntry($Object.Path)
Puis on précise que l’on modifie la DACL,
$ADSIObject.PSBase.Options.SecurityMasks = 'Dacl'
Puis on reproduit les étapes suivantes pour ajouter une ACE (si vous avez lu l’article sur l’abus d’ACL, elles devraient vous être familière). Il faut d’abord obtenir le SID du référent du droit et on en créer un objet .NET de type System.Security.Principal.IdentityReference
. Puis, on précise le droit Active Directory en utilisant l’énumération System.DirectoryServices.ActiveDirectoryRights
, ainsi que le type d’héritage. Enfin on crée une ACE objet avec la classe System.DirectoryServices.ActiveDirectoryAccessRule
. On ajoute l’ACE avec la méthode
..AddAccessRule()
$sid = [System.Security.Principal.SecurityIdentifier] $Principal.sid $identity = [System.Security.Principal.IdentityReference] $SID $adRights = [System.DirectoryServices.ActiveDirectoryRights] $Access $type = [System.Security.AccessControl.AccessControlType] "Allow" $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$inheritanceType) $ADSIObject.PSBase.ObjectSecurity.AddAccessRule($ACE)
Si on ajoute un droit étendu les étapes sont différentes mais cependant très proches.
$FinalGUID = New-Object System.Guid("00299570-246d-11d0-a768-00aa006e0529") # guid pour ForceChangePassword $sid = [System.Security.Principal.SecurityIdentifier] $Principal.sid $identity = [System.Security.Principal.IdentityReference] $SID $adRights = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight $type = [System.Security.AccessControl.AccessControlType] "Allow" $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$FinalGUID,$inheritanceType) $ADSIObject.PSBase.ObjectSecurity.AddAccessRule($ACE)
Enfin, on finit par appliquer les modifications $ADSIObject.PSBase.CommitChanges()
.
Set-DomainUserPassword
Cette fonction permet comme son nom l’indique de changer le mot de passe d’un utilisateur. Pour ce faire, il faut monter l’objet avec une DirectoryEntry
puis on invoque la méthode .SetPassword()
avec en argument le nouveau mot de passe en clair. On applique enfin les changements.
$ADSIObject = New-Object System.DirectoryServices.DirectoryEntry($user.adspath) $ADSIObject.PSBase.Invoke("SetPassword", $AccountPassword) $ADSIObject.CommitChanges()
Set-DomainObjectOwner
Cette fonction permet de changer le propriétaire d’un objet, on rappelle que ce dernier possède implicitement tous les droits (GENERIC_ALL
) sur l’objet dont il a l’acquisition. Après avoir monté le chemin LDAP de l’objet cible avec une DirectoryEntry
, on doit créer un System.Security.Principal.NTAccount
avec en argument le domaine, et le nom SAM de l’objet référent. Puis on utilise la méthode .SetOwner()
pour enfin appliquer les changements.
$IdentityReference = New-Object System.Security.Principal.NTAccount($domainName, $OwnerIdentity.SamAccountName) $ADSIObject.PSBase.ObjectSecurity.SetOwner($IdentityReference) $ADSIObject.CommitChanges()
Les scripts PowerShell sont disponibles sur github ici et je vous invite à les lire. J’espère que cet article vous a plu, écrire ses propres outils est très formateur, j’espère que vous vous y essayerez !