1 Einleitung
2 ActiveDirectory Accounts
   2.1 SecurityIdentifier (SIDs)
         Beispiel 1: Auslesen von SIDs (User, Computer, Gruppen)
         Beispiel 1b: Auslesen der SID und der UAC mit LDAP
         Beispiel 2: Auslesen von SIDs (Dienste)
   2.2 Auffinden unbenutzter Accounts
         Beispiel 1: Übersicht über die Attribute "LastLogontimeStamp" und "pwdlastset" aller Accounts
   2.3 Angriffe auf Passwörter
        2.3.1 Wörterbuchangriff
         Beispiel 1: einfacher Wörterbuchangriff
        2.3.2 Secure storage of passwords in Keypass
   2.4 RunAs
         Beispiel 1: Prüfen, ob ein Skript unter priviligierten Rechten läuft

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1 Einleitung

Zu Beginn zwei lesenswerte Artikel von Microsoft

Microsoft Downloadcenter: Mitigating Pass-the-Hash (PtH) Attacks and Other Credential Theft Techniques

 

Der obere Artikel beschreibt das Vorgehen von Angreifern und speziell, wie diese in den Besitz von privilegierten Accountinformationen gelangen können.

Der untere Artikel beschreibt, wie ein wirksames Securtiykonzept gegen derartige Angriffe aussehen kann.

 

Einen meiner Meinung nach ganz wichtigen Satz aus dem Vorwort des zweiten Artikels möchte ich an dieser Stelle zitieren:

"... a preventative attack strategy is not enough, assuming breach and preparing for internal attackers will provide the best level of defense to organizations.." 

Mit anderen Worten: Geht davon aus  -egal wie gut eure SecurityKonzepte sein mögen- es gibt immer Lücken! Neben dem vorbeugenden Schutz gehört daher auch das Erkennen eines erfolgreichen Abgriffs, sowie vorbereitete Prozesse für diesen Fall zu einer guten SecurityStrategie.

 

Beim Erkennen von Angriffen, sowie bei der Sicherung und Auswertung (=Forensic) relevanter Daten können Powershellskripte wieder mal mehr als nur gute Dienste leisten.  

Dazu ein passender Webcast der Technet 2014 von Paula Januszkiewicz: TWC: CSI: Windows - Techniques for Finding the Cause of the Unexpected System Takeovers

 

2 ActiveDirectory Accounts

User- und vor allem AdministratorAccounts (=privilegierte Accounts) sind einer der wichtigsten und gleichzeitig schwächsten Angriffspunkte einer Attacke (siehe die Artikle zu Pth oben). Umso mehr lohnt es sich diese Objekte genauer zu betrachten.    

 

2.1 SecurityIdentifier (SIDs)

Jedem Objekt im AD, dem eine Berechtigung auf etwas gegeben werden kann, ist ein sogenannter SecurityPrincipal und besitzt damit eine eindeutige SID, wie beispielweise

S-1-5-21-234337726-2083412868-3867772844-500

eine solche SID ist eindeutig einem bestimmten SecurityPrincipal (User, Computer, Gruppe oder Dienst) zugeordnet und weltweit einzigartig.

Die SID besteht aus einem ersten, zumindest in der Windowswelt für selbst erstellte Objekte fast immer gleichen ersten Teil (hier: orange). 
Der zweite (hier grüne Teil) stellt den eindeutigen Identifier der Domäne dar. Dieser Wert ist für jede Domäne weltweit einzigartig.

Der dritte und letzte gelbe Wert ist der sogenannte relative Identifer. Selbst erstellte User erhalten fortlaufende Nummer ab 1000. Werte unter 1000 belegen Objekte wie das Administrator- oder das Gastkonto.
 

Es gibt bei Microsoft und in vielen anderen Quellen tiefergehende Erklärungen zum Aufbau und zur Verwendung der SID, so etwa

Sicherheitstechnisch ist hervorzuheben, dass ab Windows Server 2008 / Windows Vista auch Dienste eine eigene SID bekommen haben. Dadurch können Diensten Berechtigungen zugewiesen werden. Auf die lokale Firewall kann man damit dem ausgehenden Netzwerkverkehr eines bestimmten Dienstes selbst erlauben oder verbieten.
Davor musste diese Berechtigung dem UserKonto gegeben werden, unter dem der Dienst lief. Wie im übernächsten Skript "Beispiel 2" gleich zu sehen ist, besitzt eine Dienste SID einen anderen Aufbau als eine UserSID und wird ausschließlich aus dem Dienstnamen hergeleitet. Die SID eines Dienstes ist also auf jedem Rechner dieser Welt gleich, sogar wenn es einen Dienst dieses Namen gar nicht gibt.

 

Beispiel 1: Auslesen von SIDs (User, Computer, Gruppen)

###########################################################
# AUTHOR  : K.Y.
# COMMENT : How to get a SID for an object
#                        How to translate a SID to an objectname
# VERSION : 1.0
###########################################################
 
# CHANGELOG

Set-StrictMode -Version "2.0"
Clear-Host  
 
Function Main{
   Get-DomainSID
   Get-ObjectSID -ObjectName administrator
   Translate-Sid2Account
   Get-SIDofService
}

Function Get-DomainSID{
  <#
  .Synopsis
  Get the SID of the current or a given domain (sufficient access is a precondition)

  .Example
  Get-DomainSID mytest.intern
  #>
  Param(
    $Domain = $(Get-ADDomain).Name
    )
  $DomainSID = $(Get-ADDomain -Identity $Domain).DomainSID
  write "DomainName: $Domain"
  write "DomainSID = $DomainSID"
  Write ""
}

Function Get-ObjectSID{
  <#
  .Synopsis
  Get SID of the current user or a given object (sufficient access is a precondition)
  objects can be users, computers, groups
  #>
  Param(
    $ObjectName = $env:username,
    $DomainName = $(Get-ADDomain).Name
  )
  #use this syntax with $Domainname and $Objectname for users, computers, groups
  $Object = New-Object System.Security.Principal.NTAccount($DomainName,$ObjectName)
 
  #usage that syntax if the current domain is fixed. Use this for "Service Names"
  #$Object = New-Object System.Security.Principal.NTAccount($Objectname)
 
  $SID = $Object.Translate([System.Security.Principal.SecurityIdentifier])  #usage of .Net
  #$SID = $($(Get-ADObject -Name $ObjectName).SID)                          #usage of AD-cmdets
 
  "SID of {0}: {1}" -f $ObjectName, $SID.Value
  Write ""
}

Function Translate-Sid2Account{
  <#
  .Synopsis
  Translate a SID or a relative SID to an accountname (user, computer, group)
  e.g."S-1-5-21-234337726-2083412868-3867772844-501
  #>
 
  Param(
    $RID = 501,
    #Current domain
    $DomainName = $(Get-ADDomain).Name
  )  
  $DomainSID = $(Get-ADDomain -Identity $DomainName).DomainSID   
  $AccountSID = New-Object System.Security.Principal.SecurityIdentifier("$DomainSID-$RID")
  $User = $AccountSID.Translate( [System.Security.Principal.NTAccount])
  "The SID {0} is used by {1}" -f "$DomainSID-$RID'",$User.Value
  Write ""
}

Function Get-SIDofService{
  <#
  .Synopsis
  Get a SID of a Domain Service
  #>   
   Param(
    $ServiceName = "Network Service"
  )  
  $NetWorkService = New-Object System.Security.Principal.NTAccount($ServiceName)
  $SID = $NetWorkService.Translate([System.Security.Principal.SecurityIdentifier])
  "SID of {0}: {1}" -f $ServiceName,$SID.Value
  Write ""

  }

Main

Beispiel 1b: Auslesen der SID und der UAC mit LDAP

Clear-Host
Set-StrictMode -Version "2.0"

$SamAccountName = "administrator"

$User = ([ADSISearcher]"((SamaccountName=$SamAccountName))").FindOne()
[String]$ObjectSid = (new-object System.Security.Principal.SecurityIdentifier $User.Properties.objectsid[0],0).Value

$useraccountcontrol = $($User.Properties.UserAccountcontrol)

$ObjectSid
$UserAccountControl

Weitere Beispiele und Erklärungen zu LDAPQueries findet ihr unter: "LDAPQueries mit .Net"

Beispiel 2: Auslesen von SIDs (Dienste)

Set-StrictMode -Version "2.0"
Clear-Host

Write-Host "`nSID von Diensten" -backgroundcolor darkyellow
$ServiceSID = SC.Exe ShowSid DHCP
"SID des DHCP-Dienstes : {0}" -f $ServiceSID[2]

$ServiceSID = CMD /C "SC ShowSid MickyMaus"
"SID des Dienstes MickyMaus': {0}" -f $ServiceSID[2]
#Ausgabe

SID von Diensten
SID des DHCP-Dienstes : SERVICE SID: S-1-5-80-2940520708-3855866260-481812779-327648279-1710889582
SID des Dienstes MickyMaus': SERVICE SID: S-1-5-80-3431518564-4051124637-3124641216-3451993315-2416480639

Mit reinen Powershellmitteln konnte ich die DiensteSID nicht auslesen.
 

2.2 Auffinden unbenutzter Accounts

Bei der Sicherheit von Accounts denken die Meisten wahrscheinlich an Passwortsicherheit oder zertifikatsbasierte Anmeldung. Daneben ist es meiner Meinung nach auch wichtig, sich um die Ordnung in seinem AD zu kümmern, wozu unter anderem schon lange nicht mehr oder noch nie benutzte Accounts gehören. Eine größerer Anzahl solcher -nicht deaktivierter- Accounts kann ein Hinweis auf unsaubere Prozesse bei der Userverwaltung sein. 
Unbenutzte Accounts sind nicht direkt ein Risiko, aber in einer unsauber gepflegten Umgebung würden aktive, aber unberechtigte, Accounts wahrscheinlich eher nicht auffallen. Ein Bereinigen des ActiveDirectories von unbenutzten Accoount, sowie ein regelmäßiges Monitoring auf Useraccounts mit veralteten Passwörtern sollte man in einem Sicherheitskonzept mit einplanen.

 

Beispiel 1: Übersicht über die Attribute "LastLogontimeStamp" und "pwdlastset" aller Accounts
Weitere Beispiele und Erklärungen zu LDAPQueries findet ihr unter: "LDAPQueries mit .Net"

Set-StrictMode -Version "2.0"
Clear-Host

Function Main {
  $Searchroot = "LDAP://DC=Domain,DC=Intern"
  $SearchScope = "Subtree"
  $CreatedAfter = "2010-03-26"       #ISO  YYYY-MM-DD
  $EnabledUsers = $True #$True, $False or $Null

  #Fielddeklaration
  $Users = @()
  $PSUsers = @()
 
  #ConvertTime
  $CreatedAfter = [Management.ManagementDateTimeConverter]::ToDmtfDateTime($CreatedAfter)  
  $CreatedAfter = $CreatedAfter.Split(".")[0]+".0Z"
 
  #Creating filter
  $Filter = "(&(objectcategory=user)(objectclass=user))"
  If($EnabledUsers -eq $True){
    $Filter += "(!UserAccountControl:1.2.840.113556.1.4.803:=2)"  #enabled Users
  }Elseif($EnabledUsers -eq $False){
    $Filter += "(UserAccountControl:1.2.840.113556.1.4.803:=2)"  #disabled Users
  }
  $Filter += "(!UserAccountControl:1.2.840.113556.1.4.803:=2)"  #enabled Users
  $Filter += "(whencreated>=$CreatedAfter)"
  [string]$Filter = "(&$Filter)"
 
  #definig DirectorySearcher
  $DirectorySearcher=([ADSISearcher]"GC://")
  $DirectorySearcher.Filter=$Filter
  $DirectorySearcher.SearchScope = $SearchScope
  $DirectorySearcher.Searchroot = $SearchRoot
  $DirectorySearcher.SearchScope = "subtree"
  $DirectorySearcher.PageSize = 1000
  $DirectorySearcher.Asynchronous = $True
  $DirectorySearcher.CacheResults = $False
  $DirectorySearcher.PropertiesToLoad.AddRange(('samaccountname','distinguishedname','pwdlastset','lastLogonTimestamp'))

  #perform LDAPSearch
  $Users = $DirectorySearcher.FindAll()
  $Count=0
 
  Foreach($User in $Users){
    $SamAccountName = $User.properties.samaccountname
    $DistinguishedName = $User.properties.distinguishedname
    
    Try{
     $pwdlastset = [DateTime]::FromFileTime($($User.properties.pwdlastset))
     $pwdlastsetInDays = $(([System.Datetime]::Now) - $pwdlastset).TotalDays
     }Catch{$pwdlastset = "#"}
 
     Try{
     $lastlogontimestamp = [DateTime]::FromFileTime($($User.properties.lastlogontimestamp))
     $lastlogontimestampInDays = $(([System.Datetime]::Now) - $lastlogontimestamp).TotalDays
     }Catch{$lastlogontimestampindays = "0"}

    #Group
    $AgeStatus_pwdlastset = &$GetAgeStatus $pwdlastsetInDays
    $AgeStatus_lastlogontimestamp = &$GetAgeStatus $LastlogontimestampInDays

    $PSUser = New-Object -TypeName Psobject -Property @{
      DistinguishedName = $Distinguishedname
      pwdlastset = $pwdlastset
      AgeStatus_pwdlastset = $AgeStatus_pwdlastset
      AgeStatus_lastlogontimestamp = $AgeStatus_lastlogontimestamp
     }#psUser

       $PSUsers += $PSUser
     }#foreach

     write-host "passwordlastset-numbers"
     $PSUsers | Sort AgeStatus_pwdlastset | Group-Object AgeStatus_pwdlastset |  Ft Name,count -auto
      write-host "lastlogontimestamp-numbers"
     $PSUsers | Sort AgeStatus_lastlogontimestamp | Group-Object AgeStatus_lastlogontimestamp |  Ft name,count -auto
}# Function main

  #ScriptBlock
  $GetAgeStatus = {
  Param($AgeInDays)
  If( $AgeInDays -lt 30) {
    [int32]$AgeStatus = "30"
  }ElseIf ( $AgeInDays -lt 90){
    [int32]$AgeStatus = "90"
  }ElseIf ( $AgeInDays -lt 180){
    [int32]$AgeStatus = "180"
  }ElseIf ( $AgeInDays -lt 360){
    [int32]$AgeStatus = "360"
  }ElseIf ( $AgeInDays -lt 720){
    [int32]$AgeStatus = "720"
  }ElseIf ( $AgeInDays -lt 7200){
    [int32]$AgeStatus = "7200"
  }ElseIf ( $AgeInDays -lt 720000){
    [int32]$AgeStatus = "720000"
  }#endif

  Try{
  Return $AgeStatus
  }catch{
    $AgeStatus = "unknown"
    Return $AgeStatus
  }
} #Skriptblock  

Main
#mögliche Ausgabe


passwordlastset-numbers

Name Count
---- -----
30      19
90      25
180      6
360      2
720      1
7200     7


lastlogontimestamp-numbers

Name Count
---- -----
30      50
90       4
180      3
720      1
7200     5

Das heisst also, dass in dieser Domäne

  • 19 Accounts ihr Passwort in den letzten 29 Tagen
  • 25 Accounts ihr Passwort zwischen den letzten 30 und 89 Tagen,
  • 6 Accounts ihr Passwort zwischen den letzten 90 und 180 Tagen,
  • ...
  • 7 Accounts seit mehr als 720 Tagen

das Passwort nicht gewechselt haben (passwordlastset-numbers)

und dass in dieser Domäne

  • 50 Accounts ihr Passwort in den letzten 29 Tagen
  • 4 Accounts ihr Passwort zwischen den letzten 30 und 89 Tagen,
  • 3 Accounts ihr Passwort zwischen den letzten 90 und 180 Tagen,
  • ...
  • 5 seit mehr als 720 Tagen

sich nicht mehr angemeldet haben (lastlogontimestamp-numbers). Es werden nur aktive Accounts betrachtet, die nach dem 26.3.2010 neu erstellt wurden.

Ein gewisser Handlungsbedarf sowohl zum Bereinigen einiger Accounts als auch zur Überprüfung der Prozesse ist deutlich zu erkennen.

 

2.3 Angriffe auf Passwörter

2.3.1 Wörterbuchangriff

 

Beispiel 1: einfacher Wörterbuchangriff

Set-StrictMode -Version "2.0"
Clear-Host
 
$Passwordlist = @("Password123","Heidi","Claudia","Naomi","Gisele","Cindy")
#$PasswordList = Get-Content -Path C:\temp\passwords.txt
 
$Users = dsquery user "OU=Finance,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern" -limit 0
 
$Users | ForEach {
  $FQDN=$_
  $SamId = $(dsget user $_ -samid)[1]
  $Samid = $Samid.Replace(" ","")
  $Passwordlist | foreach{
     $Password=$_.Replace(" ","")
         
     dsget user $FQDN -u $SamID -p $Password >$null
     if($?) {
       "Username: {0}  Passwort: {1}" -f $Samid,$Password
       }
    }
  }

#mögliche Ausgabe

 

Username: UserOMA003  Passwort: Password123

Schon mit Hilfe dieses kleinen Skripts könnte ein Angreifer alle Accounts einer Domäne oder einer OU auslesen (dsquery user) und mit dsget gegen eine Liste von Passwörtern überrprüfen (=Wörterbuchangriff). Sofern in der Accountpolicy der DomainController-GPO kein "Account Lockout Threshhold" gesetzt ist, kann dies beliebig of geschehen. Auf der anderen Seite würde ein gesetzter "Account Lockout Threshhold" bei einem solchen Szenario alle Accounts zumindest temporär unbrauchbar machen, was auch nicht viel besser als eventuell gebrochene Passwörter wäre.  

Die von Hackern verwendeten Wörterbücherangriffe sind auch so raffiniert, das beispielsweise neben dem gelisteten Wort "Heidi" auch sämtliche Groß- und Kleinschreibvarianten wie "HEIDI" oder "HeIDi" mit diversen Suffixen wie "Heidi1" bis "Heidi10000" durchprobiert werden. Sollte das Passwort eines Users so geknackt werden, wird dieses Passwort auch gleich in die neuesten Wörterbücher mit wiederum weiteren Kombinationen eingearbeitet. Hört man sich die Nachrichten an, wie oft schon Millionen von Accounts samt Passwörtern bei Freemailern oder Spieleherstellern gestohlen wurden, dann kann man sich ausmalen, welche Vielzahl möglicher Passwörter in solchen frei zugänglichen Listen enthalten ist.

Der beste Schutz gegen Passwortangriffe sind alternative Authentisierungsmechanismen wie Smartcards oder Fingerprints (Meinungen zu biometrischen Verfarhen sind allerdings geteilt). Solltet ihr auf Passwörter angewiesen sein, so generiert euch zumindest für eure priviligierten Accounts mit einem Passwortgenerator ein mindestens 20-stelliges zufälliges Passwort und speichert es euch in einem Passwortsafe wie das kostenlose Keypass (siehe nächstes Kapitel) ab. Keypass enthält selbst auch einen Passwortgenerator.

Wenn eure Anwender ebenfalls gute und komplexe Passwörter benutzen und diese regelmäßig wechseln, braucht ihr den "Account Lockout Threshhold" nicht und vermeidet so "Denial of Service" - Attacken.

 

2.3.2 Secure storage of passwords in Keypass

It can be easily realized from the preceding chapter that the use of strong passwords is essential for security. In keeping with the current security recommendations strong passwords should consist of at least 16 more or less random characters to achieve a good level of security. 

Just as decisive for a good security is both a regular password change and an exclusive use of a password for each single service. Do not use duplicate passwords for Google, Amazon and so on, but take completely different ones! 

Study: Half of passwords are over five years old

A very valuable and easy and free tool to manage all strong passwords is KeePass Password Safe, which allows to save and access as many passwords as needed with only one master password. Only this master password must be kept in mind. I think, Keypass or a similar tool is a must-have for everyone!

 

To increase the security of the Keypass file, I like to recommend the following two settings in your Keypass. Please follow the links below to get deeper information.

 

a) Protection against Dictionary Attacks

             

 

If an attacker could steal a keypass file, but this setting had been set before for this file, then only one attempt per second to find out the master password will be possible. This step makes it much more harder for the thief to find out the master password of the Keypass database and consequently to get access to the precious passwords inside the database.

 

b) Enter Master Key on Secure Desktop (Protection against Keyloggers)

This setting makes it harder to spy out your master password with a keylogger, because the login will take place on secure desktop.

 

2.4 RunAs

Viele Zugriffe auf Resourcen erfordern, dass ein Skript unter "Run as Administrator" ausgeführt wird. Für solche Scripte ist es sinnvoll eine entsprechende Abfrage einzubauen

Beispiel 1: Prüfen, ob ein Skript unter priviligierten Rechten läuft

Function Main{
   $IsRunAsAdmin = Check-IsRunAsAdmin
   If ($IsRunAsAdmin -eq $False){
      Write-Warning "this is not running with privileged rights"
      #exit
   }else{
     #privileged rights and Powershell > 4.0 are required
     Get-Process -IncludeUserName  
   }
} #end  Main
 
Function Check-IsRunAsAdmin{
   $SID = New-Object System.Security.Principal.SecurityIdentifier ("S-1-5-32-544")
   $LocalAdminGroup = $SID.Translate( [System.Security.Principal.NTAccount])
   $isRunAsAdmin = ([System.Security.Principal.WindowsIdentity]::GetCurrent() -as  `
      [System.Security.Principal.WindowsPrincipal]).IsInRole("$LocalAdminGroup")
   Return $isRunAsAdmin
} #end Check-IsRunAsAdmin
 
Main

 

eine ähnliches Skript liegt bei heise.de: http://www.heise.de/ct/hotline/PowerShell-Skript-mit-Admin-Rechten-1045393.html