PowerShell ist eines der mächtigsten Tools auf Geräten mit Microsoft Betriebssystem. Aufgrund verschiedener administrativer Funktionen ist die PowerShell aber auch nicht mehr wegzudenken. Ein bei Premium Audits oft angetroffes Szenario ist, dass die PowerShell ohne Einschränkungen auch für normale Benutzer zur Verfügung steht. Aus Sicht der IT-Sicherheit ist dies zu vermeiden.

Mittels PowerShell kann beispielsweise auch eine Rückverbindung zu einem Command & Control Server bzw. zum Rechner eines Angreifers hergestellt werden.

Windows Sicht:

JEA 2

Angreifer Sicht:

JEA 1

Dieses Beispiel lässt bei den meisten Windows Administratoren die Alarmglocken läuten. Der einfachste Weg dies zu verhindern? PowerShell & cmd auf Clients sperren. Dies bringt aber den Nachteil, dass alle die administrativen Vorteile wie Logon Scripts, welche im User-Kontext ausgeführt werden oder andere Scripts, nicht mehr funktionieren. Wie also kann der Zugriff auf die PowerShell so eingeschränkt werden, dass normale User nur noch die nötigen Commands ausführen können und nicht mehr Zugriff auf alle verfügbaren Befehle haben?

Microsoft selber liefert da eins zurzeit noch eher unbekannten und kaum verbreiteten Tools namens JEA (Just Enough Administration). Mit Hilfe diese Tools kann bis auf User Ebene die PowerShell eingeschränkt werden.

Dass JEA optimal funktionieren kann muss aber folgendes Patchlevel bei Servern und Clients vorhanden sein.

  • Server
    Mindestens Windows Server 2012 mit WMF (Windows Management Framework) 5.1
  • Client
    Windows 10 ab Version 1607 mit WMF (Windows Management Framework) 5.1

Falls sie unsicher sind, welche PowerShell Version auf Ihren Servern bzw Clients installiert ist, können Sie dies mit folgendem Befehl überprüfen:

$PSVersionTable.PSVersion

Wenn Ihr Netzwerk diese Anforderungen erfüllt steht der Einführung von JEA nichts mehr im Wege. Nachfolgendes Tutorial zeigt auf, wie eine einfach Einschränkung auf eine gewisse Usergruppe in einem Active Directory Netzwerk realisiert werden kann.

Falls das PowerShell Remoting auf Ihren Systemen nicht aktiviert ist, kann dieses mittels folgendem Befehl aktiviert bzw. reaktiviert werden. Auf aktuellen Systemen dieses Feature aber standardmässig aktiviert.

Enable-PSRemoting

JEA verwendet zur Kontrolle der Berechtigungen zwei verschiedene Dateien. Im sogenannten RoleCapability File wird definiert was das dem zukünftigen JEA Nutzer erlaubt wird. Im Session Configuration File hingegen wird definiert, wer Die JEA Konfiguration bzw. Rollen verwenden darf. Beide dieser Files werden schlussendlich lokal auf den entsprechenden Computern bzw. Servern abgelegt. Die Verteilung in grossen Netzwerken mit entsprechend vielen Hosts kann mittels Gruppenrichtlinien realisiert werden.

Nun werde ich etwas genauer auf den Inhalt des RoleCapability File eingehen und welche Konfigurationsmöglichkeiten zur Verfügung stehen. Ich werde nicht alle Teile des Files erläutern, da die Beschreibungen für sich sprechen. Folgend sehen Sie ein "leeres" RoleCapability File.

@{

# ID zur eindeutigen Kennzeichnung dieses Dokuments
GUID = 'be7fe637-4fdf-4656-9826-07bf4b475a54'

# Autor dieses Dokuments
Author = 'Administrator'

# Beschreibung der von diesen Einstellungen bereitgestellten Funktionen
# Description = ''

# Company associated with this document
CompanyName = 'Unbekannt'

# Urheberrechtserklärung für dieses Dokument
Copyright = '(c) 2018 Administrator. Alle Rechte vorbehalten.'

# Module, die importiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# ModulesToImport = 'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }

# Aliase, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleAliases = 'Item1', 'Item2'

# Cmdlets, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleCmdlets = 'Invoke-Cmdlet1', @{ Name = 'Invoke-Cmdlet2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }

# Funktionen, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleFunctions = 'Invoke-Function1', @{ Name = 'Invoke-Function2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }

# Externe Befehle (Skripts und Anwendungen), die sichtbar gemacht werden sollen, wenn Sie auf eine Sitzung angewendet werden.
# VisibleExternalCommands = 'Item1', 'Item2'

# Anbieter, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleProviders = 'Item1', 'Item2'

# Skripts, die ausgeführt werden sollen, wenn sie auf eine Sitzung angewendet werden.
# ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1'

# Aliase, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# AliasDefinitions = @{ Name = 'Alias1'; Value = 'Invoke-Alias1'}, @{ Name = 'Alias2'; Value = 'Invoke-Alias2'}

# Funktionen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# FunctionDefinitions = @{ Name = 'MyFunction'; ScriptBlock = { param($MyInput) $MyInput } }

# Variablen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VariableDefinitions = @{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, @{ Name = 'Variable2'; Value = 'StaticInitialValue' }

# Umgebungsvariablen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# EnvironmentVariables = @{ Variable1 = 'Value1'; Variable2 = 'Value2' }

# Typdateien (PS1XML), die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# TypesToProcess = 'C:\ConfigData\MyTypes.ps1xml', 'C:\ConfigData\OtherTypes.ps1xml'

# Formatdateien (PS1XML), die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# FormatsToProcess = 'C:\ConfigData\MyFormats.ps1xml', 'C:\ConfigData\OtherFormats.ps1xml'

# Assemblys, die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# AssembliesToLoad = 'System.Web', 'System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

}

Für uns sind in diesem Blog bzw. zur ersten Einrichtung von JEA nur folgende Abschnitte wichtig:

  • VisibleCmdlets
  • VisibleFunctions
  • VisibleExternalCommands

Im Bereich VisibleCmdlets werden alle Cmdlets erfasst, welche der Nutzer der JEA Session später zur Verfügung haben soll. Standardmässig hat der Nutzer folgende Grund-Cmdlets zur Verfügung:

  • Clear-Host
  • Exit-PSSession
  • Get-Command
  • Get-FormatData
  • Get-Help
  • Measure-Object
  • Out-Default
  • Select-Object

Alle weiteren Cmdlets müssen im Bereich VisibleCmdlets definiert werden. Hier können zum einen Cmdlets generell erlaubt oder mittels Variablendefinition teils erlaubt werden.

Um Cmdlets generell zu erlauben muss dieses wie folgt definiert werden:

VisibleCmdlets = 'Cmdlet', 'Cmdlet2'

Wie oben erwähnt, können die Variablen eingeschränkt werden. Dies ist nützlich, falls der betroffene User beispielsweise nur einen gewissen Dienst neustarten darf. Im Falle eines DHCP Servers würde dies wie folgt aussehen:

VisibleCmdlets = @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'DHCP'}}

VisibleFunctions verhält sich gleich wie VisibleCmdlets, nur, dass hier die Funktionen definiert werden. Ob es sich beim gewünschten Befehl um eine Funktion oder ein Cmdlet handelt, kann mittels Get-Command herausgefunden werden:

Get-Command 'Befehl'

JEA 3

 Unter CommandType ist nun ersichtlich, ob es sich um ein Cmdlet oder eine Funktion handelt.

Unter VisibleExternalCommands kann dem User das Ausführen von speziefischen externen Programmen oder Powershell Scripts erlaubt werden.

VisibleExternalCommands = 'C:\Windows\System32\whoami.exe', 'C:\Program Files\Scripts\Logon.ps1'

Um nun mit kleinem Aufwand ein solches Role Capatibility File erstellen muss folgendes gemacht werden.

Noch vor dem Erstellen des Role Capability Files muss die entsprechende Filestruktur inklusive Module Manifest angelegt werden:

New-Item -Path "C:\Program Files\WindowsPowerShell\Modules\DHCP_Module" -ItemType Directory

New-ModuleManifest -Path "C:\Program Files\WindowsPowerShell\Modules\DHCP_Module\DHCP_Module.psd1"
New-Item -Path "C:\Program Files\WindowsPowerShell\Modules\DHCP_Module\RoleCapabilities" -ItemType Directory

Nun wollen wir einem User erlauben den Befehl, welcher benötigt wird, um den DHCP Service neu zu starten, ausführen zu können. Als erstes werden aber die Parameter definiert, welche abgefüllt werden sollen:

$DHCPRoleCapabilityParams = @{
Author = 'goSecurity Tutorial'
Description = 'DHCP Service Administration'
CompanyName = 'goSecurity Tutorial'
VisibleCmdlets = @{Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'DHCPServer'}}}

Nun kann mit folgendem Befehl ein neues Role Capability File erstellt werden. Der Speicherort soll gemäss Microsoft keine Rolle spielen. Der Einfachheit halber wurde in diesem Beispiel aber der von Microsoft vorgeschlagene Pfad verwendet.

New-PSRoleCapabilityFile -Path "$ENV:ProgramFiles\WindowsPowerShell\Modules\DHCP_Module\RoleCapabilities\DHCPRoleCapability.psrc" @DHCPRoleCapabilityParams

Mittels PSEdit kann das File nun begutachtet werden. Falls alles geklappt hat, sollte dieses wie folgt aussehen (ACHTUNG: psedit funktioniert nur im Powershell ISE)

psedit "$ENV:ProgramFiles\WindowsPowerShell\Modules\Demo_Module\RoleCapabilities\DHCPRoleCapability.psrc
@{

# ID zur eindeutigen Kennzeichnung dieses Dokuments
GUID = '1f7c4406-cd4a-49c8-952a-a713ceb500a2'

# Autor dieses Dokuments
Author = 'goSecurity Tutorial'

# Beschreibung der von diesen Einstellungen bereitgestellten Funktionen
Description = 'DHCP Service Administration'

# Company associated with this document
CompanyName = 'goSecurity Tutorial'

# Urheberrechtserklärung für dieses Dokument
Copyright = '(c) 2018 goSecurity Tutorial. Alle Rechte vorbehalten.'

# Module, die importiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# ModulesToImport = 'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }

# Aliase, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleAliases = 'Item1', 'Item2'

# Cmdlets, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
VisibleCmdlets = @{
'Name' = 'Restart-Service'
'Parameters' = @{
'Name' = 'Name'
'ValidateSet' = 'DHCPServer' } }

# Funktionen, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleFunctions = 'Invoke-Function1', @{ Name = 'Invoke-Function2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }

# Externe Befehle (Skripts und Anwendungen), die sichtbar gemacht werden sollen, wenn Sie auf eine Sitzung angewendet werden.
# VisibleExternalCommands = 'Item1', 'Item2'

# Anbieter, die sichtbar gemacht werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VisibleProviders = 'Item1', 'Item2'

# Skripts, die ausgeführt werden sollen, wenn sie auf eine Sitzung angewendet werden.
# ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1'

# Aliase, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# AliasDefinitions = @{ Name = 'Alias1'; Value = 'Invoke-Alias1'}, @{ Name = 'Alias2'; Value = 'Invoke-Alias2'}

# Funktionen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# FunctionDefinitions = @{ Name = 'MyFunction'; ScriptBlock = { param($MyInput) $MyInput } }

# Variablen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# VariableDefinitions = @{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, @{ Name = 'Variable2'; Value = 'StaticInitialValue' }

# Umgebungsvariablen, die definiert werden sollen, wenn sie auf eine Sitzung angewendet werden.
# EnvironmentVariables = @{ Variable1 = 'Value1'; Variable2 = 'Value2' }

# Typdateien (PS1XML), die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# TypesToProcess = 'C:\ConfigData\MyTypes.ps1xml', 'C:\ConfigData\OtherTypes.ps1xml'

# Formatdateien (PS1XML), die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# FormatsToProcess = 'C:\ConfigData\MyFormats.ps1xml', 'C:\ConfigData\OtherFormats.ps1xml'

# Assemblys, die geladen werden sollen, wenn sie auf eine Sitzung angewendet werden.
# AssembliesToLoad = 'System.Web', 'System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

}

Da wir nun definiert haben, was gemacht werden darf, müssen wir nun definieren wer unseren DHCP Server neustarten darf.

In diesem Beispiel erstellen wir eine neue Neue Gruppe namens 'DHCP_Operators' und einen User namens 'DHCPOperator', welcher dieser Gruppe angehört. Als Passwort wird 'saKcFFh3i2e8' gewählt.

$DHCPOperatorGroup = New-ADGroup -Name "DHCP_Operators" -GroupScope DomainLocal -PassThru
$OperatorUser = New-ADUser -Name "DHCPOperator" -AccountPassword (ConvertTo-SecureString 'saKcFFh3i2e8' -AsPlainText -Force) -PassThru
Enable-ADAccount -Identity $OperatorUser
Add-ADGroupMember -Identity $DHCPOperatorGroup -Members $OperatorUser

Hier wird eine Variable für unsere DHCP Operatorgruppe erstellt. Im Beispiel handelt es sich um die Domäne 'golab.ch'. Dieser Wert muss bei Ihnen natürlich auf den Namen Ihrer Domäne angepasst werden.

$NonAdministrator = "golab.ch\DHCP_Operators"

Nun setzen wir die Parameter für das Session Configuration File.

$ConfParams = @{
SessionType = 'RestrictedRemoteServer'
RunAsVirtualAccount = $true
RoleDefinitions = @{
$NonAdministrator = @{ RoleCapabilities = 'DHCPRoleCapability'}
}
TranscriptDirectory = "$env:ProgramData\JEAConfiguration\Transcripts"
}

Es wird das Verzeichnis JEAConfiguration im Ordner 'C:\ProgramData' erstellt.

New-Item -Path "$env:ProgramData\JEAConfiguration" -ItemType Directory

Nun wird das Session Configuration File im oben erstellten Ordner generiert. Es werden die unter $ConfParams definierten Parameter abgefüllt.

New-PSSessionConfigurationFile -Path "$env:ProgramData\JEAConfiguration\JEADHCP.pssc" @ConfParams

Mittels psedit kann nun das erstellte File angezeigt werden. Falls alles funktioniert hat, sollte es wie folgt aussehen:

psedit "$env:ProgramData\JEAConfiguration\JEADHCP.pssc"
@{

# Versionsnummer des Schemas, das für dieses Dokument verwendet wird
SchemaVersion = '2.0.0.0'

# ID zur eindeutigen Kennzeichnung dieses Dokuments
GUID = 'f41b8764-b560-442b-ac8f-270d28c32eaf'

# Autor dieses Dokuments
Author = 'Administrator'

# Beschreibung der von diesen Einstellungen bereitgestellten Funktionen
# Description = ''

# Standardwerte für den Sitzungstyp, die auf diese Sitzungskonfiguration angewendet werden sollen. Mögliche Werte: RestrictedRemoteServer (empfohlen), Empty oder Default
SessionType = 'RestrictedRemoteServer'

# Verzeichnis, in dem Sitzungsaufzeichnungen für diese Sitzungskonfiguration gespeichert werden sollen.
TranscriptDirectory = 'C:\ProgramData\JEAConfiguration\Transcripts'

# Gibt an, ob diese Sitzungskonfiguration als (virtuelles) Administratorkonto des Computers ausgeführt werden soll.
RunAsVirtualAccount = $true

# Skripts, die ausgeführt werden sollen, wenn sie auf eine Sitzung angewendet werden.
# ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1'

# Benutzerrollen (Sicherheitsgruppen) und die Rollenfunktionen, die darauf angewendet werden sollen, wenn sie auf eine Sitzung angewendet werden.
RoleDefinitions = @{
'golab.ch\DHCP_Operators' = @{
'RoleCapabilities' = 'DHCPRoleCapability' } }

}

Als nächstes muss noch das erstellte Session Configuration File regisrtiert werden.

$SessionName = 'JEA_DHCP_OperatorSession'
Register-PSSessionConfiguration -Name $SessionName -Path "$env:ProgramData\JEAConfiguration\JEADHCP.pssc"

Nun ist soweit alles bereit für unseren DHCP Operator. Hinterlegen Sie auf dem Client mit folgendem Command die Credentials des Operators.

$credential = Get-Credential
Golab.ch\DHCPOperator

Das Passwort ist 'saKcFFh3i2e8' (oben gesetzt)

Mittels Enter-PSSession kann nun eine JEA Powershell Session gestartet werden.

Enter-PSSession -ComputerName DC1.golab.ch -ConfigurationName JEA_DHCP_OperatorSession -Credential $credential

Nun können Sie mittels Get-Command überprüfen ob alles funktioniert hat. Es sollte nebst den Standard-Commands noch Restart Service vorhanden sein. Tippen Sie Restart-Service ein und schalten Sie mittels TAB durch die Möglichkeiten. Dies sollte nicht möglich sein und als einzige Auswahlmöglichkeit sollte DHCP erscheinen.

Sie haben nun erfolgreich Benutzer in ihren Möglichkeiten innerhalb von Powershell eingeschränkt. So können Sie genau steuern, welcher Benutzer bzw. welche Benutzergruppe welche Befehle auf welchen Geräten ausführen darf. Wie Sie sicher bemerkt haben, ist das Einrichten von JEA mit relativ grossem Aufwand verbunden. Zurzeit ist JEA aber die einzig komplett implementierte Möglichkeit Benutzer oder Gruppen auf PowerShell-Ebene innerhalb eines Active Directories einzuschränken. Aus meiner Sicht lohnt sich der Aufwand für die Konfiguration von JEA, da so der Zugriff für Benutzer auf PowerShell eingeschränkt werden kann, ohne direkt die gesamte PowerShell zu sperren.

Informationen zum Autor
Michel Hennet
Autor: Michel Hennet
Schon während meines Studiums an der ZHAW in Winterthur faszinierte mich die IT-Security. Unbedingt wollte ich darum die Stelle als Junior Security Consultant bei der Firma goSecurity bekommen. Schon während meinen ersten Kundenprojekten wurde mir klar: Genau das habe ich gesucht. Aus der Faszination entwickelt sich bei mir eine Leidenschaft. Die Leidenschaft als technischer Spezialist die Geschäftsanforderungen perfekt zu unterstützen. Nur so ist der Begriff des Experten für mich erst legitim. Mein voller Einsatz für Sie, befriedigt meine Leidenschaft und meinen Anspruch noch vor den ersten grauen Haaren zum Senior zu werden.