Public/Test-SecurityState.ps1

#Requires -Version 5.1
#Requires -PSEdition Core, Desktop
#Requires -Modules @{ ModuleName="PKI"; ModuleVersion="1.0.0.0" }

using module PKI
using namespace System.Security.Cryptography.X509Certificates

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

class SicherheitInfoCheck {

    [int]    $Id
    [string] $Status
    [string] $Kategorie
    [string] $Test
    [Object] $IST
    [Object] $SOLL

    SicherheitInfoCheck([int]$Id, [string]$Status, [string]$Kategorie, [string]$Test, [Object]$IST, [Object]$SOLL) {
        $this.Id = $Id
        $this.Status = $Status
        $this.Kategorie = $Kategorie
        $this.Test = $Test
        $this.IST = $IST
        $this.SOLL = $SOLL
    }
}

function IstZertifikatExportierbar {
    param([X509Certificate2]$Cert)
    try {
        $Cert.Export(([X509ContentType]::Pfx)) | Out-Null
        return $true
    }
    catch {
        return $false
    }
}

function Test-SecurityState {

    <#
    .SYNOPSIS
    Analysiert das aktuelle System auch Sicherheitsschwachstellen in Verbindung mit PowerShell.
 
    .OUTPUTS
    Analyseergebnis wird per [SicherheitInfoCheck]-Objekt zurückgegeben. Erklärende Informationen erhältst Du per -Verbose-Meldung
 
    .EXAMPLE
    Test-SecurityState
 
    .EXAMPLE
    Test-SecurityState -Verbose
 
    .EXAMPLE
    Test-SecurityState -SkipModuleVersionTest -Verbose
 
    .EXAMPLE
    Test-SecurityState -SkipAlternateDataStreamTest -Verbose
 
    .EXAMPLE
    Test-SecurityState -SkipModuleVersionTest -SkipAlternateDataStreamTest -Verbose
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock','')]
    [CmdletBinding()]
    param(
        # Überspringt die zeitaufwändige Modul-Versions-Test zu der eine Internet-Verbindung bestehen muss.
        [switch]$SkipModuleVersionTest,

        # Überspringt die zeitaufwändige Alternate-Data-Stream-Test.
        [switch]$SkipAlternateDataStreamTest
    )

    $My = [HashTable]::Synchronized(@{})
    $My.ESC = [char]0x1b
    $My.Status = $null
    $My.Result = $null
    $My.TargetResult = $null
    $My.Id = 1
    $My.Neutral = "Neutral"
    $My.Passed = "$($My.ESC)[92mBestanden$($My.ESC)[0m"
    $My.Failed = "$($My.ESC)[91mDurchgefallen$($My.ESC)[0m"
    $My.Skip = "$($My.ESC)[95mÜbersprungen$($My.ESC)[0m"
    $my.IsAdminRights = (New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentLIST ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::AdminISTrator)

    #region Allgemein

    [SicherheitInfoCheck]::new($My.Id++, $My.Neutral, 'Allgemein', 'PSEdition', $PSVersionTable.PSEdition, '')
    "Zu $($My.Id - 1)) Weitere Details siehe about_PowerShell_Editions" | Write-Verbose

    if ($PSVersionTable.PSVersion -le [Version]"2.0.0.0") { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++ , $My.Status, 'Allgemein', 'PSVersion', $PSVersionTable.PSVersion, '-gt 2.0')
    "Zu $($My.Id - 1)) Schutzmechaniken können in Windows PowerShell 2.0 umgangen werden, daher diese Versionen unter Windows-Feature-/Optionen deaktivieren." | Write-Verbose

    [SicherheitInfoCheck]::new($My.Id++, $My.Neutral, 'Allgemein', 'Platform', $PSVersionTable.Platform, '')

    [SicherheitInfoCheck]::new($My.Id++, $My.Neutral, 'Allgemein', 'CurrentPSHost', (Get-Host | Select-Object -ExpandProperty Name), '')
    "Zu $($My.Id - 1)) weitere PowerShell-Hosts können sein: ConsoleHost, Visual Studio Code Host, Windows PowerShell ISE Host, etc." | Write-Verbose

    if ($ExecutionContext.SessionState.LanguageMode -ieq "FullLanguage") { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Allgemein', 'PSSessionLanguageMode', ($ExecutionContext.SessionState.LanguageMode), 'ConstrainedLanguage')
    "Zu $($My.Id - 1)) Weitere Details siehe about_Language_Modes" | Write-Verbose

    if ((Get-ExecutionPolicy) -ine "AllSigned") { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Allgemein', 'ExecutionPolicy', (Get-ExecutionPolicy), 'AllSigned')
    "Zu $($My.Id - 1)) *Policy sollte auf AllSigned stehen. Für PSEntwickler empfiehlt sich RemoteSigned" | Write-Verbose

    if ($my.IsAdminRights) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Allgemein', 'Erhöhte Admin-Rechte', $my.IsAdminRights, $false)

    #endregion

    #region PROFILE

    $my.Result = Test-Path -Path $PROFILE.AllUsersAllHosts -PathType Leaf
    if ($My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Profile-Datei vorhanden', 'AllUsersAllHosts', $My.Result, $false)

    $my.Result = Test-Path -Path $PROFILE.AllUsersCurrentHost -PathType Leaf
    if ($My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Profile-Datei vorhanden', 'AllUsersCurrentHost', $My.Result, $false)
    "Zu $($My.Id - 1)) Auch PFade in anderen PowerShell-Hosts (s.o.) testen." | Write-Verbose

    $my.Result = Test-Path -Path $PROFILE.CurrentUserAllHosts -PathType Leaf
    if ($My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Profile-Datei vorhanden', 'CurrentUserAllHosts', $My.Result, $false)

    $my.Result = Test-Path -Path $PROFILE.CurrentUserCurrentHost -PathType Leaf
    if ($My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Profile-Datei vorhanden', 'CurrentUserCurrentHost', $My.Result, $false)
    "Zu $($My.Id - 1)) Auch PFade in anderen PowerShell-Hosts (s.o.) testen." | Write-Verbose

    #endregion

    #region Microsoft Defender Antivirus

    $my.Result = Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
    if ($null -ne $My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status , 'Defender', 'ExclusionPath', $My.Result, 'No Exclusion Paths')

    #endregion

    #region Module Version Check

    $My.Status = $my.Skip ; $My.Result = "Test übersprungen" ; $My.TargetResult = "Test übersprungen"
    if (-not $SkipModuleVersionTest) {
        $My.Status = $My.Passed ; $My.Result = "unnötig in PowerShell 7" ; $My.TargetResult = "unnötig in PowerShell 7"
        if (-not $PSVersionTable.PSVersion -ge 6) {
            $My.Result = (Get-PackageProvider -Name NuGet).Version
            $My.TargetResult = (Find-PackageProvider -Name NuGet).Version
            if ($My.Result -le $My.TargetResult) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
        }
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'PackageProvider', 'NuGet', $My.Result, $My.TargetResult)
    "Zu $($My.Id - 1)) Nur in Windows PowerShell 5.1 wichtig." | Write-Verbose

    #endregion

    #region Module Version Check

    $My.Status = $my.Skip ; $My.Result = "Test übersprungen" ; $My.TargetResult = "Test übersprungen"
    if (-not $SkipModuleVersionTest) {
        $My.Result = (Get-Module -Name PackageManagement -LISTAvailable -Verbose:$false).Version | Sort-Object -Descending | Select-Object -First 1
        $My.TargetResult = [Version](Find-Module -Name PackageManagement).Version
        if ($My.Result -lt $My.TargetResult) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Module', 'PackageManagement', $My.Result, $My.TargetResult)

    $My.Status = $my.Skip ; $My.Result = "Test übersprungen" ; $My.TargetResult = "Test übersprungen"
    if (-not $SkipModuleVersionTest) {
        $My.Result = (Get-Module -Name PowerShellGet -LISTAvailable -Verbose:$false).Version | Sort-Object -Descending | Select-Object -First 1
        $My.TargetResult = [Version](Find-Module -Name PowerShellGet).Version
        if ($My.Result -lt $My.TargetResult) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Module', 'PowerShellGet', $My.Result, $My.TargetResult)

    $My.Status = $my.Skip ; $My.Result = "Test übersprungen" ; $My.TargetResult = "Test übersprungen"
    if (-not $SkipModuleVersionTest) {
        $My.Result = (Get-Module -Name PSScriptAnalyzer -LISTAvailable -Verbose:$false).Version | Sort-Object -Descending | Select-Object -First 1
        $My.TargetResult = [Version](Find-Module -Name PSScriptAnalyzer).Version
        if ($My.Result -lt $My.TargetResult) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Module', 'PSScriptAnalyzer', $My.Result, $My.TargetResult)

    $My.Status = $my.Skip ; $My.Result = "Test übersprungen" ; $My.TargetResult = "Test übersprungen"
    if (-not $SkipModuleVersionTest) {
        $My.Result = (Get-Module -Name Pester -LISTAvailable -Verbose:$false).Version | Sort-Object -Descending | Select-Object -First 1
        $My.TargetResult = [Version](Find-Module -Name Pester).Version
        if ($My.Result -lt $My.TargetResult) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Module', 'Pester', $My.Result, $My.TargetResult)

    #endregion

    #region ScriptBlockLogging

    $My.Result = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'EnableScriptBlockLogging' -ErrorAction SilentlyContinue
    if ($My.Result -eq 1) { $My.Status = $My.Passed } else { $My.Status = $My.Failed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'ScriptBlockLogging', 'Windows PowerShell 64bit', $My.Result, 1)
    "Zu $($My.Id - 1)) ScriptBlockLogging sollte inkl. Verschlüsselung aktiviert werden. Für weitere Details siehe about_Logging_Windows." | Write-Verbose
    
    $My.Result = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'EnableScriptBlockLogging' -ErrorAction SilentlyContinue
    if ($My.Result -eq 1) { $My.Status = $My.Passed } else { $My.Status = $My.Failed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'ScriptBlockLogging', 'Windows PowerShell 32bit', $My.Result, 1)
    "Zu $($My.Id - 1)) ScriptBlockLogging sollte inkl. Verschlüsselung aktiviert werden. Für weitere Details siehe about_Logging_Windows." | Write-Verbose

    $My.Result = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'EnableScriptBlockLogging' -ErrorAction SilentlyContinue
    if ($My.Result -eq 1) { $My.Status = $My.Passed } else { $My.Status = $My.Failed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'ScriptBlockLogging', 'PowerShell 6+ 64bit', $My.Result, 1)
    "Zu $($My.Id - 1)) ScriptBlockLogging sollte inkl. Verschlüsselung aktiviert werden. Für weitere Details siehe about_Logging_Windows." | Write-Verbose
    
    #endregion

    #region Just Enough Administration

    if($my.IsAdminRights) {
        $My.Result = Get-PSSessionConfiguration -Verbose:$false | Where-Object -Property Name -NotMatch -Value 'PowerShell' | Select-Object -ExpandProperty Name; $sc -join ', '
    } else {
        "Zu $($My.Id - 1)) ACHTUNG! Keine Admin-Rechte zum testen vorhanden. Führe diesen Test erneut mit Admin-Rechten aus." | Write-Warning
        $My.Result = "n/v"
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Neutral, 'JEA', 'SessionConfigurations', $my.Result, '')
    "Zu $($My.Id - 1)) Das Einrichten von JEA für Nicht-Administratoren/Konten mit administrativen Aufgaben anstreben." | Write-Verbose

    #endregion

    #region Alternate Data Stream

    $My.Status = $My.Skip; $My.Result = "Test übersprungen"
    if (-not $SkipAlternateDataStreamTest) {
        $My.CountFiles = Get-ChildItem -Path 'C:\' -File -Force -ErrorAction Ignore -Recurse | Measure-Object | Select-Object -ExpandProperty Count
        $My.CurrentCounter = 1
        $My.Status = $My.Neutral
        $My.Result = Get-ChildItem -Path c:\ -File -Force -Recurse -ErrorAction Ignore | ForEach-Object -Process {
            Write-Progress -Activity 'Scanne Dateien nach Alternate Data Stream''s' -Status $My.CurrentCounter -PercentComplete ([System.Math]::Floor(($My.CurrentCounter++) / $My.CountFiles * 100))
            try { Get-Item -Path $_.FullName -Stream * -ErrorAction Ignore | Where-Object -Property Stream -ne ':$DATA' } catch {  }
        } | Group-Object -Property Stream | Sort-Object -Descending Count | Select-Object -Property Count, Name, @{Name = 'Sum'; Expression = { ($_.Group | Measure-Object -Property Length -Sum | Select-Object -ExpandProperty SumLengthKB) / 1KB } }, Group
        Write-Progress -Activity 'Scanne Dateien nach Alternate Data Stream''s' -Completed
    }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'Alternate Data Stream', 'Gruppiert IST', $My.Result, 'Zero ADS Strategy')
    "Zu $($My.Id - 1)) Auffällige / Große ADS-Dateien manuell prüfen. Siehe das gruppierte Ergebnis der IST-Eigenschaft." | Write-Verbose

    #endregion

    #region PKI

    $My.Result = Get-ChildItem -Path 'Cert:\' -Recurse -Force | Where-Object -Property 'HasPrivateKey' | ForEach-Object -Process {
        if ((IstZertifikatExportierbar $_)) {
            return "SUBJECT: $($_.Subject) THUMBPRINT: $($_.Thumbprint)"
        }
    }
    if ($null -ne $My.Result) { $My.Status = $My.Failed } else { $My.Status = $My.Passed }
    [SicherheitInfoCheck]::new($My.Id++, $My.Status, 'PKI', 'ExportablePrivateCertificate', $My.Result, 'Zero Strategie')
    "Zu $($My.Id - 1)) Zertifikate mit privatem Schlüssel sollten mit diesem nicht exportierbar sein." | Write-Verbose

    #endregion

    Remove-Variable -Name My -Force -ErrorAction Ignore
}

<# KOMPONENTEN TEST
Update-FormatData -PrependPath '.\Modules\PowerShellBuddy\Public\Test-SecurityState.Format.ps1xml'
Test-SecurityState
Test-SecurityState -Verbose
Test-SecurityState -SkipModuleVersionTest -Verbose
Test-SecurityState -SkipAlternateDataStreamTest -Verbose
Test-SecurityState -SkipModuleVersionTest -SkipAlternateDataStreamTest -Verbose
#>