Enumération des sites et sous réseaux Active Directory
Introduction
Dans un domaine Active Directory, vous pouvez configurer de nombreux paramètres pour les petites ou les grandes organisations. Dans les grands domaines AD, il est assez courant d’avoir de nombreuses infrastructures informatiques réparties sur différents emplacements géographiques. Afin de gérer ces configurations, ADDS fournit un outil appelé sites et services Active Directory permettant aux administrateurs de gérer de grands domaines sur de nombreux sites.
Du point de vue d’un attaquant, c’est une partie très importante de la phase de reconnaissance d’un pentest de comprendre quelles machines sont accessibles dans quels sous-réseaux. Avec ces informations, un attaquant peut alors choisir les meilleures prochaines cibles parmi les machines déjà compromises.
Nous allons voir comment fonctionnent les sites et services d’Active Directory et écrire un script pour les énumérer à partir de Windows avec Powershell. Ensuite, nous verrons un module crackmapexec pour l’utiliser à partir de Linux.
Active Directory Sites and Services
Du point de vue de l’administrateur, la console Sites et services Active Directory ressemble à ceci :
Toutes ces informations sont stockées dans le LDAP :
- Les AD Sites sont stockés dans:
CN=Configuration,DC=LAB,DC=local
- Les Subnets sont stockés dans:
CN=Sites,CN=Configuration,DC=LAB,DC=local
- Les Serveurs sont stockés dans le
distinguishedName
du site, par exemple:%s,DC=LAB,DC=local
Énumération
Pour énumérer ces propriétés à partir d’un Active Directory, nous avons besoin de :
- Avoir des identifiants valides pour le domaine
- Pouvoir interroger le LDAP
Dans cette optique, nous pouvons construire un pseudo-code pour extraire ces informations. Premièrement, nous devons extraire les sites de la forêt. Ensuite, nous extrairons les sous-réseaux du site, et les serveurs déclarés à l’intérieur du site. Cela nous donne le pseudo-code suivant :
- Extraire les sites de la forêt
- Pour chaque site dans sites :
- Extraire les sous-réseaux du site
- Pour chaque sous-réseau dans les sous-réseaux :
- Extraire les serveurs du site
- Pour chaque serverur dans les serveurs :
- Afficher le serveur trouvé
Dans Powershell à partir de Windows
Il ne nous reste plus qu’à traduire ce pseudo-code en Powershell, et maintenant vous pouvez énumérer les sites, sous-réseaux et serveurs en powershell avec ces quelques lignes :
$sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
foreach ($site in $sites) {
write-host "[>] (site) $site"
foreach ($subnet in $site.Subnets) {
write-host " └─> (subnet) $subnet"
foreach ($server in $site.Servers) {
write-host " └─> (server) $server"
}
}
}
Cela nous donne une sortie d’arborescence similaire à la console Sites et services Active Directory :
Ou en une seule ligne :
foreach ($s in [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites ){write-host "[>] (site) $s";foreach ($r in $s.Subnets){write-host " └─> (subnet) $r";foreach ($m in $s.Servers){write-host " └─> (server) $m"}}}
Utilisation de crackmapexec de Linux
J’ai écrit un module crackmapexec pour l’utiliser depuis Linux, basé sur le même pseudo-code. Voici un exemple de la sortie du module :
Pour ce faire, j’ai utilisé des requêtes LDAP pour obtenir tous les sites, puis une autre requête LDAP pour obtenir tout le sous-réseau de ce site et une requête LDAP pour obtenir tous les serveurs du site. C’est fondamentalement la même chose que ce que nous avons fait précédemment dans Powershell, mais sans les fonctions intégrées de Windows.
Ce module a été proposé en tant que pull request #509, en attendant voici le code final du module :
from impacket.ldap import ldapasn1 as ldapasn1_impacket
def searchResEntry_to_dict(results):
data = {}
for attr in results['attributes']:
key = str(attr['type'])
value = str(attr['vals'][0])
data[key] = value
return data
class CMEModule:
'''
Retrieves the different Sites and Subnets of an Active Directory
Authors:
Podalirius: @podalirius_
'''
def options(self, context, module_options):
pass
name = 'subnets'
description = 'Retrieves the different Sites and Subnets of an Active Directory'
supported_protocols = ['ldap']
opsec_safe = True
multiple_hosts = False
def on_login(self, context, connection):
dn = ','.join(["DC=%s" % part for part in context.domain.split('.')])
context.log.info('Getting the Sites and Subnets from domain')
list_sites = connection.ldapConnection.search(
searchBase="CN=Configuration,%s" % dn,
searchFilter='(objectClass=site)',
attributes=['distinguishedName', 'name', 'description'],
sizeLimit=999
)
for site in list_sites:
if isinstance(site, ldapasn1_impacket.SearchResultEntry) is not True:
continue
site = searchResEntry_to_dict(site)
site_dn = site['distinguishedName']
site_name = site['name']
site_description = ""
if "description" in site.keys():
site_description = site['description']
# Getting subnets of this site
list_subnets = connection.ldapConnection.search(
searchBase="CN=Sites,CN=Configuration,%s" % dn,
searchFilter='(siteObject=%s)' % site_dn,
attributes=['distinguishedName', 'name', 'description'],
sizeLimit=999
)
for subnet in list_subnets:
if isinstance(subnet, ldapasn1_impacket.SearchResultEntry) is not True:
continue
subnet = searchResEntry_to_dict(subnet)
subnet_dn = subnet['distinguishedName']
subnet_name = subnet['name']
subnet_description = ""
if "description" in subnet.keys():
site_description = subnet['description']
context.log.highlight(" │ Site (Name:%s) (Subnet:%s)" % (site_name, subnet_name))
if len(site_description) != 0:
context.log.highlight(' │ │ Site description: \"%s\"' % (site_description))
if len(subnet_description) != 0:
context.log.highlight(' │ │ Subnet description: \"%s\"' % (subnet_description))
# Getting machines in these subnets
list_servers = connection.ldapConnection.search(
searchBase=site_dn,
searchFilter='(objectClass=server)',
attributes=['cn'],
sizeLimit=999
)
for server in list_servers:
if isinstance(server, ldapasn1_impacket.SearchResultEntry) is not True:
continue
server = searchResEntry_to_dict(server)['cn']
context.log.highlight(" │ ├───> Server: %s" % server)