Come primo articolo in questo nuovo anno, vorrei trattare un argomento a mio avviso interessante per i sostenitori delle tematiche di sicurezza IT, ovvero le GPO (Group Policy Object). Non vorrei soffermarmi su cosa siano e a cosa servono le GPO , ma vorrei fare focus su come molto spesso vengono utilizzate.
In ambienti enterprise, dove sono spesso distribuiti grandi domini e/o foreste Active Directory, l’utilizzo di configurazioni distribuite tramite GPO è una prassi ormai consolidata tra gli amministratori. Utilizzare le politiche di gruppo, è infatti un metodo rapido ed efficiente per poter configurare in maniera massiva le risorse interne al dominio e mantenere nello stesso tempo un governo su queste. Di contropartita a mio avviso c’è purtroppo il problema che spesso il numero delle GPO tende a crescere in maniera proporzionale all’aumento della complessità operativa dei contesti funzionali.
Se per questa tecnologia non viene inoltre messo a punto un Piano di Maintenance che comprenda processi strutturati di deployment, documentazione e attività di consolidamento, con il tempo si potrà perdere il controllo sulle configurazioni distribuite arrivando a scenari estremi di situazioni di conflitto anche con possibili blocchi operativi. Di fronte poi a questi casi estremi, implementare in corsa un Piano di Maintence implicherebbe la necessità di un assessment di base magari finanziando un attività progettuale con un relativo impegno economico.
A mio avviso la cosa potrebbe essere anche gestibile in maniera rapida, a maggior ragione se sono poi gli amministratori ad essere impegnati in prima persona nelle attività di analisi. Ricordiamoci però che siamo in ambiente enterprise e che spesso in questi contesti, dove magari si gestiscono dati particolarmente delicati, potrebbero esserci delle strutture di controllo con il mandato di evidenziare situazioni di non conformatità legislativa e/o di sicurezza. Se per esempio un ufficio come l’audit volesse avviare un attività di analisi (e credetemi che questo spesso succede in maniera nascosta) potrebbe riscontrare delle criticità, non tanto sul numero e/o sul disordine delle GPO, ma bensì sul loro contenuto. Spesso infatti succede di utilizzare questa tecnologia per riconfigurare gli account locali su server e parco client, definire dei task schedulati, configurare share ecc.
Attività di questo tipo implica il dover specificare delle password all’interno dei File XML delle GPO stesse. Per chi amministra Active Directory e per chi conosce, anche in maniera superficiale la tecnologia che ne sta alla base ovvero Directory Service, sa che per distribuire le GPO si usa semplicemente una cartella condivisa sui Domain Controller. Se quindi da un qualunque client apriamo un Explorer digitando “\\[FQDN_DEL_DOMINIO]\SYSVOL\[FQDN_DEL_DOMINIO]\Policies” avremmo accesso a tutte le cartelle che contengono le GPO e a loro volta i file di configurazione.L’accesso ai file XML e il loro relativo contenuto sarà quindi garantito, pur non avendo dei privilegi speciali. A questo punto ci si potrebbe chiedere: “Ma se nelle GPO possono essere specificate delle Password, queste potranno essere recuperate cosi facilmente?”. Diciamo che Microsoft non ha fatto inizialmente un grande sforzo per mettere in sicurezza questo genere di informazione. Tanto è vero che nel tempo ha pure rilasciato delle FIX per correggere questa cosa. In altri termini le password, non sono ovviamente in chiaro nei file di configurazione, ma sono semplicemente cifrata con protocollo AES256, quindi facilmente decifrabili.
Fatta questa doverosa premessa e per tornare alla questione legata alle analisi da parte delle strutture di controllo, ho definito un codice che potrà in qualche maniera salvare in parte gli amministratori da un possibile attacco. Lo script che segue agisce infatti su tutto il contesto Active Directory processando l’insieme dei file XML, trovando le password definite al loro interno. Quello che viene poi restituito è il nome della GPO, la password decifrata e il file XML vulnerabile.
Riporto il codice per poi analizzare come lavora:
Import-module grouppolicy
$ItemsPath = "C:\Windows\SYSVOL\domain\Policies\"
#Extract the all XML files in the Folders
$Items = Get-ChildItem $ItemsPath -recurse -Filter *.xml
foreach ($XMLFileName in $Items){
#Convert XML in a String file
[string]$XMLContent = Get-content ($XMLFileName.FullName)
#Check if Cpassword Exist in the file
if($XMLContent.Contains("cpassword")){
#Take the Cpassword Value from XML String file
[string]$Cpassword = [regex]::matches($XMLContent,'(cpassword=).+?(?=\")')
$Cpassword = $Cpassword.split('(\")')[1]
#Check if Cpassword has a value
if($Cpassword){
$Mod = ($Cpassword.length % 4)
switch ($Mod) {
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
'2' {$Cpassword += ('=' * (4 - $Mod))}
'3' {$Cpassword += ('=' * (4 - $Mod))}
}
$Base64Decoded = [Convert]::FromBase64String($Cpassword)
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
#Use th AES Key
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
#Convert Hash variable in a String valute
$Password = [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
[string]$GPOguid = [regex]::matches($XMLFileName.DirectoryName,'(?<=\{).+?(?=\})')
$GPODetail = Get-GPO -guid $GPOguid
Write-Host "I find a Password [ " $Password " ] The GPO named:" $GPODetail.DisplayName" and th file is:" $XMLFileName
}
} #if($XMLContent.Contains("cpassword")
}
Per praticità assumiamo di far girare lo script direttamente su uno dei DC nel dominio. Si potrebbe adattare il codice e lanciando una sensione remota, ma il parcing delle GPO potrebbe diventare particolarmente lungo. La cartella dove sono pubblicate le GPO è C:\Windows\SYSVOL\domain\Policies
. Il comando Get-ChildItem esegue una ricerca ricorsiva per trovare tutti i file XML, che ricordo contengono le password.
Successivamente viene definito un ciclo per analizzare uno ad uno tutti i file nell’array $Items. Il primo step del ciclo recupera il contenuto del file XML. Se il file contiene una password cifrata, questa viene identificata con il capo cpassoword (es. cpassword=”LEuQsR9WVTorlBWCUzjxnUMACu6KSdMRTlLydJM6fD8″). Come si può vedere, la password viene visualizzata da un codice alfanumerico di 43 Caratteri. Se questa è quindi presente e è popolata, vengono eseguiti due comandi sul testo per isolare la stringa cifrata popolando la variabile $Cpassword. Viene successivamente eseguito il blocco core di codice per le attività di decodifica. Proverò a dettagliarlo:
- Viene calcolata la lunghezza del Hash il quale numero dei caratteri dovrà essere pari a 43.
- Il codice viene poi convertito mediante la classe Conver utilizzando il metodo FromBase64String per trasformare la stringa in base 64 in un array equivalmente a 8 bit https://msdn.microsoft.com/en-us/library/system.convert(v=vs.110).aspx.
- A questo punto viene instanziato un oggetto System.Security.Cryptography.AesCryptoServiceProvider per gestire dati cifrati AES.
- All’oggetto instanziato, vengono passate due proprietà: la Lunghezza del Vettore ($AesIV) e la Chiave Crittografica ($AesKey). Per il primo parametro sarà definito un nuovo oggettodi tipo Byte per il secondo sarà sufficiente recuperare il valore della chiave dal MSDN all’indirizzo https://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be.aspx
- Invocando infine il metodo CreateDecryptor() sarà posissibile procedere con la conversione
- La risultante sarà un blocco di tipo Byte che sarà infine convertito in Unicode con il metodo Unicode.GetString
In coda lo script si chiude recuperando il nome della GPO e il nome del file XML contenente la password.
Anche in questo caso l’impegno è stato massimo nel cercare di descrivere in maniere concreta il codice sviluppato. Se qualcosa comunque non è stato chiaro….beh sono qui.
Dieri che è tutto. Hasta siempre.j@ck