SecurityFever.psm1
<#
.SYNOPSIS List the current audit policy setting on the local system. .DESCRIPTION This command uses the auditpol.exe command to get the current audit policy setting for the local system and parses the output into a custom object. .INPUTS None. .OUTPUTS SecurityFever.AuditPolicy. Array of custom audit policy objects. .EXAMPLE PS C:\> Get-SecurityAuditPolicy Return all local security audit policies. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-SecurityAuditPolicy { [CmdletBinding()] param () # Because the auditpol.exe cmdlet need administration permission, verify if # the current session is startet as administrator. if (-not (Test-AdministratorRole)) { throw 'Access denied. Please start this functions as an administrator.' } # Use the helper functions to execute the auditpol.exe queries. $csvAuditCategories = Invoke-AuditPolListSubcategoryAllCsv | ConvertFrom-Csv $csvAuditSettings = Invoke-AuditPolGetCategoryAllCsv | ConvertFrom-Csv foreach ($csvAuditCategory in $csvAuditCategories) { # If the Category/Subcategory field starts with two blanks, it is a # subcategory entry - else a category entry. if ($csvAuditCategory.'GUID' -like '{*-797A-11D9-BED3-505054503030}') { $lastCategory = $csvAuditCategory.'Category/Subcategory' $lastCategoryGuid = $csvAuditCategory.GUID } else { $csvAuditSetting = $csvAuditSettings | Where-Object { $_.'Subcategory GUID' -eq $csvAuditCategory.GUID } # Return the result object [PSCustomObject] @{ PSTypeName = 'SecurityFever.AuditPolicy' ComputerName = $csvAuditSetting.'Machine Name' Category = $lastCategory CategoryGuid = $lastCategoryGuid Subcategory = $csvAuditSetting.'Subcategory' SubcategoryGuid = $csvAuditSetting.'Subcategory GUID' AuditSuccess = $csvAuditSetting.'Inclusion Setting' -like '*Success*' AuditFailure = $csvAuditSetting.'Inclusion Setting' -like '*Failure*' } } } } <# .SYNOPSIS Get one audit policy setting on the local system. .DESCRIPTION This command uses the auditpol.exe command to get the current audit policy setting for the local system and parses the output for the target setting. .INPUTS None. .OUTPUTS System.Boolean. Return true if the audit policy is enabled, false if not. .EXAMPLE PS C:\> Get-SecurityAuditPolicySetting -Category 'Object Access' -Subcategory 'File System' -Setting 'Success' Returns the current setting for success audit on the file system object access audit policy. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-SecurityAuditPolicySetting { [CmdletBinding()] [OutputType([System.Boolean])] param ( # Audit policy category [Parameter(Mandatory = $true)] [System.String] $Category, # Audit policy subcategory [Parameter(Mandatory = $true)] [System.String] $Subcategory, # Audit policy setting [Parameter(Mandatory = $true)] [ValidateSet('Success', 'Failure')] [System.String] $Setting ) $auditPolicies = Get-SecurityAuditPolicy foreach ($auditPolicy in $auditPolicies) { if ($auditPolicy.Category -eq $Category -and $auditPolicy.Subcategory -eq $Subcategory) { switch ($Setting) { 'Success' { return $auditPolicy.AuditSuccess } 'Failure' { return $auditPolicy.AuditFailure } } } } throw "No audit policy found for category $Category and subcategory $Subcategory." } <# .SYNOPSIS Generate a Time-Base One-Time Password based on RFC 6238. .DESCRIPTION This command uses the reference implementation of RFC 6238 to calculate a Time-Base One-Time Password. It bases on the HMAC SHA-1 hash function to generate a shot living One-Time Password. .INPUTS None. .OUTPUTS System.String. The one time password. .EXAMPLE PS C:\> Get-TimeBasedOneTimePassword -SharedSecret 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' Get the Time-Based One-Time Password at the moment. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever https://tools.ietf.org/html/rfc6238 #> function Get-TimeBasedOneTimePassword { [CmdletBinding()] [Alias('Get-TOTP')] param ( # Base 32 formatted shared secret (RFC 4648). [Parameter(Mandatory = $true)] [System.String] $SharedSecret, # The date and time for the target calculation, default is now (UTC). [Parameter(Mandatory = $false)] [System.DateTime] $Timestamp = (Get-Date).ToUniversalTime(), # Token length of the one-time password, default is 6 characters. [Parameter(Mandatory = $false)] [System.Int32] $Length = 6, # The hash method to calculate the TOTP, default is HMAC SHA-1. [Parameter(Mandatory = $false)] [System.Security.Cryptography.KeyedHashAlgorithm] $KeyedHashAlgorithm = (New-Object -TypeName 'System.Security.Cryptography.HMACSHA1'), # Baseline time to start counting the steps (T0), default is Unix epoch. [Parameter(Mandatory = $false)] [System.DateTime] $Baseline = '1970-01-01 00:00:00', # Interval for the steps in seconds (TI), default is 30 seconds. [Parameter(Mandatory = $false)] [System.Int32] $Interval = 30 ) # Generate the number of intervals between T0 and the timestamp (now) and # convert it to a byte array with the help of Int64 and the bit converter. $numberOfSeconds = ($Timestamp - $Baseline).TotalSeconds $numberOfIntervals = [Convert]::ToInt64([Math]::Floor($numberOfSeconds / $Interval)) $byteArrayInterval = [System.BitConverter]::GetBytes($numberOfIntervals) [Array]::Reverse($byteArrayInterval) # Use the shared secret as a key to convert the number of intervals to a # hash value. $KeyedHashAlgorithm.Key = Convert-Base32ToByte -Base32 $SharedSecret $hash = $KeyedHashAlgorithm.ComputeHash($byteArrayInterval) # Calculate offset, binary and otp according to RFC 6238 page 13. $offset = $hash[($hash.Length-1)] -band 0xf $binary = (($hash[$offset + 0] -band '0x7f') -shl 24) -bor (($hash[$offset + 1] -band '0xff') -shl 16) -bor (($hash[$offset + 2] -band '0xff') -shl 8) -bor (($hash[$offset + 3] -band '0xff')) $otpInt = $binary % ([Math]::Pow(10, $Length)) $otpStr = $otpInt.ToString().PadLeft($Length, '0') Write-Output $otpStr } <# .SYNOPSIS Runs the specified command in an elevated context. .DESCRIPTION Runs the specified command in an elevated context. This is useful on Windows systems where the user account control is enabled. Input object and result objects are serialized using XML. It's important, the command does use the current user context. This means, the current user needs administrative permissions on the local system. If no file path or script block is specified, the current running process will be run as administrator. .INPUTS None. .OUTPUTS Output of the invoked script block or command. .EXAMPLE PS C:\> Invoke-Elevated Will start the current process, e.g. PowerShell Console or ISE, in an elevated session as Administrator. .EXAMPLE PS C:\> Invoke-Elevated -FilePath 'C:\Temp\script.ps1' Start the script in an elevated session and return the result. .EXAMPLE PS C:\> Invoke-Elevated -ScriptBlock { Get-DscLocalConfigurationManager } Start the script in an elevated session and return the result. .EXAMPLE PS C:\> Invoke-Elevated -ScriptBlock { param ($Path) Remove-Item -Path $Path } -ArgumentList 'C:\Windows\test.txt' Delete a file from the program files folder with elevated permission, beacuse a normal user account has no permissions. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Invoke-Elevated { [CmdletBinding(DefaultParameterSetName = 'None')] [Alias('sudo')] param ( # The path to an executable program. [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'FilePath')] [ValidateScript({Test-Path -Path $_})] [System.String] $FilePath, # The script block to execute in an elevated context. [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ScriptBlock')] [System.Management.Automation.ScriptBlock] $ScriptBlock, # Optional argument list for the program or the script block. [Parameter(Mandatory = $false, Position = 1)] [System.Object[]] $ArgumentList ) if ($PSCmdlet.ParameterSetName -eq 'None') { # If no file path and script block was specified, just elevate the # current session for interactive use. For this, use the start info # object of the current process and start an elevated new one. $currentProcess = Get-Process -Id $PID $processStart = $currentProcess.StartInfo $processStart.FileName = $currentProcess.Path $processStart.Verb = 'RunAs' $process = New-Object -TypeName System.Diagnostics.Process $process.StartInfo = $processStart $process.Start() | Out-Null } if ($PSCmdlet.ParameterSetName -eq 'FilePath') { # If a file path instead of a script block was specified, just load the # file content and parse it as script block. $ScriptBlock = [System.Management.Automation.ScriptBlock]::Create((Get-Content -Path $FilePath -ErrorAction Stop -Raw)) } if ($PSCmdlet.ParameterSetName -eq 'FilePath' -or $PSCmdlet.ParameterSetName -eq 'ScriptBlock') { try { # To transport the parameters, script outputs and the errors, we use # the CliXml object serialization and temporary files. This is # necessary because the elevated process runs in an elevated context $scriptBlockFile = [System.IO.Path]::GetTempFileName() + '.xml' $argumentListFile = [System.IO.Path]::GetTempFileName() + '.xml' $commandOutputFile = [System.IO.Path]::GetTempFileName() + '.xml' $commandErrorFile = [System.IO.Path]::GetTempFileName() + '.xml' $ScriptBlock | Export-Clixml -Path $scriptBlockFile $ArgumentList | Export-Clixml -Path $argumentListFile # Get the current PowerShell drive. If this does not exist, it has # to be created in the session. Switch to the system drive, to # prevent errors for missing drives. $drive = Get-Location | Select-Object -ExpandProperty 'Drive' Push-Location -Path "$Env:SystemDrive\" # Create a command string which contains all command executed in the # elevated session. The wrapper of the script block is needed to # pass the parameters and return all outputs objects and errors. $commandString = '' $commandString += 'if ($null -eq (Get-PSDrive -Name "{0}" -ErrorAction SilentlyContinue)) {{ New-PSDrive -Name "{0}" -PSProvider "{1}" -Root "{2}" }};' -f $drive.Name, $drive.Provider.Name, $drive.Root $commandString += 'Set-Location -Path "{0}:\{1}";' -f $drive.Name, $drive.CurrentLocation $commandString += '$scriptBlock = [System.Management.Automation.ScriptBlock]::Create((Import-Clixml -Path "{0}"));' -f $scriptBlockFile $commandString += '$argumentList = [System.Object[]] (Import-Clixml -Path "{0}");' -f $argumentListFile $commandString += '$output = Invoke-Command -ScriptBlock $scriptBlock -ArgumentList $argumentList;' $commandString += '$error | Export-Clixml -Path "{0}";' -f $commandErrorFile $commandString += '$output | Export-Clixml -Path "{0}";' -f $commandOutputFile $commandEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($commandString)) $processStart = New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe' $processStart.Arguments = '-NoProfile -NonInteractive -EncodedCommand {0}' -f $commandEncoded $processStart.Verb = 'RunAs' $processStart.WindowStyle = 'Hidden' $process = New-Object -TypeName System.Diagnostics.Process $process.StartInfo = $processStart $process.Start() | Out-Null Write-Verbose "Elevated powershell.exe process started with id $($process.Id)." $process.WaitForExit() Write-Verbose "Elevated powershell.exe process stopped with exit code $($process.ExitCode)." if ((Test-Path -Path $commandErrorFile)) { Import-Clixml -Path $commandErrorFile | ForEach-Object { Write-Error $_ } } if ((Test-Path -Path $commandOutputFile)) { Import-Clixml -Path $commandOutputFile | Write-Output } } catch { throw $_ } finally { if ($null -ne $process) { $process.Dispose() } Pop-Location Remove-Item -Path $scriptBlockFile -Force -ErrorAction SilentlyContinue Remove-Item -Path $argumentListFile -Force -ErrorAction SilentlyContinue Remove-Item -Path $commandOutputFile -Force -ErrorAction SilentlyContinue Remove-Item -Path $commandErrorFile -Force -ErrorAction SilentlyContinue } } } <# .SYNOPSIS Start a new PowerShell Console session. .DESCRIPTION Start a new PowerShell Console session with alternative credentials. It uses the Start-Process cmdlet and use the system drive as a working directory. .INPUTS None. .OUTPUTS None. .EXAMPLE PS C:\> Invoke-PowerShell -Credential 'DOMAIN\user' Start a new PowerShell Console session with alternative credentials. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Invoke-PowerShell { [CmdletBinding()] [Alias('posh')] param ( # Alternative credentials to start a PowerShell Console session. [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential ) Start-Process -FilePath "$PSHOME\powershell.exe" -WorkingDirectory $Env:SystemDrive -Credential $Credential } <# .SYNOPSIS Convert a string into a secure string. .DESCRIPTION Uses the Windows build-in data protection API (DPAPI) to convert the string to a secure string. Only the current user, on the current computer with the current profile can decrypt the secure string. .INPUTS System.String. The String to protect. .OUTPUTS System.Security.SecureString. The protected string. .EXAMPLE PS C:\> Protect-String -String 'Passw0rd' Protect the password a secure string. .EXAMPLE PS C:\> 'Text A', 'Text B' | Protect-String Protect both strings as a secure string. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Protect-String { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function', Target='Protect-String')] [OutputType([System.Security.SecureString])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [System.String[]] $String ) process { foreach ($currentString in $String) { $currentSecureString = ConvertTo-SecureString -String $currentString -AsPlainText -Force Write-Output $currentSecureString } } } <# .SYNOPSIS Test the provided credentials with the choosen test method against the local system or Active Directory. .DESCRIPTION Test the provided credentials against the local system by starting a simple process or against Active Directory by binding to the root via ADSI. .INPUTS System.Management.Automation.PSCredential .OUTPUTS System.Management.Automation.PSCredential System.Boolean .EXAMPLE PS C:\> Test-Credential -Credential 'DOMAIN\user' Test the interactive provided credentials against the local system. If the credential are not valid, an exception is thrown. .EXAMPLE PS C:\> Test-Credential -Credential 'DOMAIN\user' -Quiet Test the interactive provided credentials against the local system and return $true if the credentials are valid, else return $false. .EXAMPLE PS C:\> Test-Credential -Username $Username -Password $Password -Method ActiveDirectory Test the provided username and password pair against the Active Directory. .EXAMPLE PS C:\> $cred = Get-Credential 'DOMAIN\user' | Test-Credential Request the user to enter the password for DOMAIN\user and test it immediately with Test-Credential against the local system. If the credentials are valid, they are returned and stored in $cred. If not, an terminating exception is thrown. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Test-Credential { [CmdletBinding()] [OutputType([System.Boolean])] [OutputType([System.Management.Automation.PSCredential])] param ( # PowerShell credentials object to test. [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Credential')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # The username to validate. Specify password too. [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')] [System.String] $Username, # The password to validate. Specify username too. [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')] [System.Security.SecureString] $Password, # Validation method. [Parameter(Mandatory = $false)] [ValidateSet('StartProcess', 'ActiveDirectory')] [System.String] $Method = 'StartProcess', # Return a boolean value which indicates if the credentials are valid. [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $Quiet ) begin { if ($PSCmdlet.ParameterSetName -eq 'UsernamePassword') { $Credential = New-Object -TypeName PSCredential -ArgumentList $Username, $Password } } process { $exception = $null if ($Method -eq 'StartProcess') { Write-Verbose "Test credentials $($Credential.UserName) by starting a local cmd.exe process" try { # Create a new local process with the given credentials. This # does not validate the credentials against an external target # system, but tests if they are valid locally. Of courese, it's # possible to validate domain credentials too. $startInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo $startInfo.FileName = 'cmd.exe' $startInfo.WorkingDirectory = $env:SystemRoot $startInfo.Arguments = '/C', 'echo %USERDOMAIN%\%USERNAME%' $startInfo.Domain = $Credential.GetNetworkCredential().Domain $startInfo.UserName = $Credential.GetNetworkCredential().UserName $startInfo.Password = $Credential.GetNetworkCredential().SecurePassword $startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $startInfo.CreateNoWindow = $true $startInfo.UseShellExecute = $false $process = New-Object -TypeName System.Diagnostics.Process $process.StartInfo = $startInfo $process.Start() | Out-Null # If the process start does not throw an exception, the # credentials are valid. } catch { # Hide the $process.Start() method call exception, by expanding # the inner exception. if ($_.Exception.Message -like 'Exception calling "Start" with "0" argument(s):*') { $exception = $_.Exception.InnerException } else { $exception = $_.Exception } } } if ($Method -eq 'ActiveDirectory') { Write-Verbose "Test credentials $($Credential.UserName) by binding the default domain with ADSI" try { # We use an empty path, because we just test the credential # binding and not any object access in Active Directory. $directoryEntryArgs = @{ TypeName = 'System.DirectoryServices.DirectoryEntry' ArgumentList = '', # Bind to the local default domain $Credential.GetNetworkCredential().UserName, $Credential.GetNetworkCredential().Password } $directoryEntry = New-Object @directoryEntryArgs -ErrorAction Stop if ($null -eq $directoryEntry -or [String]::IsNullOrEmpty($directoryEntry.distinguishedName)) { throw 'Unable to create an ADSI connection.' } } catch { $exception = $_.Exception } } # Check the exception variable if an exception occured and return a # boolean or the credentials. In case of an exception, throw a custom # exception. if ($Quiet.IsPresent) { Write-Output ($null -eq $exception) } else { if ($null -eq $exception) { Write-Output $Credential } else { $errorRecordArgs = $exception, '0', [System.Management.Automation.ErrorCategory]::AuthenticationError, $Credential $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList $errorRecordArgs $PSCmdlet.ThrowTerminatingError($errorRecord) } } } } <# .SYNOPSIS Convert a secure string into a string. .DESCRIPTION Uses the Windows build-in data protection API (DPAPI) to convert the secure string back to a string. Only the user which has protected the original string can decrypt it. .INPUTS System.Security.SecureString. The protected secure string. .OUTPUTS System.String. The unprotected string. .EXAMPLE PS C:\> Unprotect-SecureString -SecureString $password Get the plain text password. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Unprotect-SecureString { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [System.Security.SecureString[]] $SecureString ) process { foreach ($currentSecureString in $SecureString) { $currentCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList 'Dummy', $currentSecureString $currentString = $currentCredential.GetNetworkCredential().Password Write-Output $currentString } } } <# .SYNOPSIS Show security relevant activities on a system. .DESCRIPTION Show security relevant activities on a system. This includes: - Startup / Shutdown - Awake / Sleep - Logon / Logoff .INPUTS None. .OUTPUTS SecurityFever.ActivityRecord. Array of custom activity records. .EXAMPLE PS C:\> Get-SecurityActivity Get all available security activity records on the system. .EXAMPLE PS C:\> Get-SecurityActivity -Activity Startup, Shutdown Get only the startup and shutdown activity records on the system. .EXAMPLE PS C:\> Get-SecurityActivity -Recommended Get all available security activity records on the system but show just the recommended records and hide verbose records. .EXAMPLE PS C:\> Get-SecurityActivity -ComputerName 'COMPUTER' -Credential 'DOMAIN\User' Get all security activity records on the remote system 'COMPUTER'. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-SecurityActivity { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Scope='Function', Target='Get-SecurityActivity')] [CmdletBinding(DefaultParameterSetName = 'Local')] param ( # Define the activity action(s). [Parameter(Mandatory = $false)] [ValidateSet('Startup', 'Shutdown', 'Awake', 'Sleep', 'Logon', 'Logoff')] [System.String[]] $Activity, # Specify a remote computer to query. [Parameter(Mandatory = $true, ParameterSetName = 'Remote')] [System.String] $ComputerName, # Specify credentials for the remote computer. [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # Specify a time limit for the records. [Parameter(Mandatory = $false)] [System.DateTime] $After = ([DateTime]::MinValue), # Show only filtered recommended events. [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $Recommended ) # The security event log is protected, therefore the cmdlet need elevated # permission. Verify if the current session is startet as administrator. if ($PSCmdlet.ParameterSetName -eq 'Local' -and -not (Test-AdministratorRole)) { throw 'Access denied. Please start this functions as an administrator.' } # If no activity is specified, initialize the input with all possible # activities. if ($null -eq $Activity -or $Activity.Count -eq 0) { $Activity = (Get-Command -Name 'Get-SecurityActivity').Parameters['Activity'].Attributes.ValidValues } # Static event log map to get the event log id's for each activity. $eventLogMap = @{ 4608 = @{ Activity = 'Startup'; Log = 'Security'; Event = 'LSASS Started' } # Windows is starting up. 6005 = @{ Activity = 'Startup'; Log = 'System'; Event = 'Event Log Started' } # The Event log service was started. 6009 = @{ Activity = 'Startup'; Log = 'System'; Event = 'OS Initialization' } # Microsoft (R) Windows (R) <Version>. <Build> Multiprocessor Free. 1074 = @{ Activity = 'Shutdown'; Log = 'System'; Event = 'Requested Shutdown' } # The process <Process> has initiated the power off of computer <Computer> on behalf of user <Domain>\<User> for the following reason: ... 6006 = @{ Activity = 'Shutdown'; Log = 'System'; Event = 'Event Log Stopped' } # The Event log service was stopped. 6008 = @{ Activity = 'Shutdown'; Log = 'System'; Event = 'Unexpected Shutdown' } # The previous system shutdown at <Time> on <Date> was unexpected. 1 = @{ Activity = 'Awake'; Log = 'System'; Event = 'Leaving Sleep' } # The system has returned from a low power state. 42 = @{ Activity = 'Sleep'; Log = 'System'; Event = 'Entering Sleep' } # The system is entering sleep. 4624 = @{ Activity = 'Logon'; Log = 'Security'; Event = 'Logon Successful' } # An account was successfully logged on. 4625 = @{ Activity = 'Logon'; Log = 'Security'; Event = 'Logon Failed' } # An account failed to log on. 4634 = @{ Activity = 'Logoff'; Log = 'Security'; Event = 'Logoff Successful' } # An account was logged off. 4647 = @{ Activity = 'Logoff'; Log = 'Security'; Event = 'Logoff Request' } # User initiated logoff. # 4648 = @{ Activity = 'Logon' } # A logon was attempted using explicit credentials # 4778 = @{ Activity = 'SessionReconnected' } # 4779 = @{ Activity = 'SessionDisconnected' } # 4800 = @{ Activity = 'WorkstationLocked' } # 4801 = @{ Activity = 'WorkstationUnlocked' } # 4802 = @{ Activity = 'ScreensaverInvoked' } # 4803 = @{ Activity = 'ScreensaverDismissed' } } # Warning messages, if the audit policy is disabled for the requested # activity. if ($PSCmdlet.ParameterSetName -eq 'Local') { if ($Activity -contains 'Startup') { if(-not (Get-SecurityAuditPolicySetting -Category 'System' -Subcategory 'Security State Change' -Setting 'Success')) { Write-Warning "System audit policy for 'System' > 'Security State Change' > 'Success' is not enabled." } } if ($Activity -contains 'Logon') { if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logon' -Setting 'Success')) { Write-Warning "System audit policy for 'Logon/Logoff' > 'Logon' > 'Success' is not enabled." } if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logon' -Setting 'Failure')) { Write-Warning "System audit policy for 'Logon/Logoff' > 'Logon' > 'Failure' is not enabled." } } if ($Activity -contains 'Logoff') { if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logoff' -Setting 'Success')) { Write-Warning "System audit policy for 'Logon/Logoff' > 'Logoff' > 'Success' is not enabled." } } } if ($PSCmdlet.ParameterSetName -eq 'Remote') { Write-Warning "Unable to verify the audit policy settings for $ComputerName." } # Build the xml filter to query the system and security log. $filterSystem = '' $filterSecurity = '' $filterSystemIds = $eventLogMap.GetEnumerator() | Where-Object { $_.Value.Activity -in $Activity -and $_.Value.Log -eq 'System' } | ForEach-Object Name $filterSecurityIds = $eventLogMap.GetEnumerator() | Where-Object { $_.Value.Activity -in $Activity -and $_.Value.Log -eq 'Security' } | ForEach-Object Name if ($filterSystemIds.Count -gt 0) { $filterSystem = '<Select Path="System">*[System[(({0}) and TimeCreated[@SystemTime>=''{1}''])]]</Select>' -f ([String]::Join(' or ', ($filterSystemIds | ForEach-Object { "EventID=$_" }))), $After.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') } if ($filterSecurityIds.Count -gt 0) { $filterSecurity = '<Select Path="Security">*[System[(({0}) and TimeCreated[@SystemTime>=''{1}''])]]</Select>' -f ([String]::Join(' or ', ($filterSecurityIds | ForEach-Object { "EventID=$_" }))), $After.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') } $filter = '<QueryList><Query Id="0" Path="Security">{0}{1}</Query></QueryList>' -f $filterSystem, $filterSecurity # Execute query against local or remote system. if ($PSCmdlet.ParameterSetName -eq 'Local') { $events = Get-WinEvent -FilterXml $filter -ErrorAction Stop } if ($PSCmdlet.ParameterSetName -eq 'Remote') { $invokeCommandParam = @{ ComputerName = $ComputerName ScriptBlock = { param ($filter) Get-WinEvent -FilterXml $filter -ErrorAction Stop | Select-Object Id, MachineName, TimeCreated, Properties } ArgumentList = $filter } if ($null -ne $Credential) { $invokeCommandParam['Credential'] = $Credential } $events = Invoke-Command @invokeCommandParam -ErrorAction Stop } # Security activities $activities = @() foreach ($event in $events) { switch ($event.Id) { 4608 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap } 6005 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap } 6009 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap } 1074 { $activities += Convert-EventLogObjectId1074 -Record $event -Map $eventLogMap } 6006 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap } 6008 { $activities += Convert-EventLogObjectId6008 -Record $event -Map $eventLogMap } 1 { if ($event.ProviderName -eq 'Microsoft-Windows-Power-Troubleshooter') { $activities += Convert-EventLogObjectId1 -Record $event -Map $eventLogMap } } 42 { $activities += Convert-EventLogObjectId42 -Record $event -Map $eventLogMap } 4624 { $activities += Convert-EventLogObjectId4624 -Record $event -Map $eventLogMap } 4625 { $activities += Convert-EventLogObjectId4625 -Record $event -Map $eventLogMap } 4634 { $activities += Convert-EventLogObjectId4634 -Record $event -Map $eventLogMap } 4647 { $activities += Convert-EventLogObjectId4647 -Record $event -Map $eventLogMap } } } # Filter for recommended activities if ($Recommended.IsPresent) { $activities = $activities | Where-Object { -not ( $_.Id -eq 6006 -or $_.Id -eq 6005 -or $_.Id -eq 4608 -or ($_.Id -eq 4624 -and 'Network', 'Batch', 'Service', 'Unlock', 'NetworkCleartext', 'NewCredentials' -contains $_.Type) -or ($_.Id -eq 4624 -and $_.Username -like 'Window Manager\DWM-*') -or ($_.Id -eq 4634 -and 'Network', 'Batch', 'Service', 'Unlock', 'NetworkCleartext', 'NewCredentials' -contains $_.Type) -or ($_.Id -eq 4634 -and $_.Username -like 'Window Manager\DWM-*') ) } } $activities | Sort-Object TimeCreated -Descending | Write-Output } <# .SYNOPSIS Get the current impersonation context and the active windows identity. .DESCRIPTION Returns the current impersonation context and the active windows identity available on the GetCurrent() method on the WindowsIdentity .NET class. .INPUTS None. .OUTPUTS The current impersonation context. .EXAMPLE PS C:\> Get-ImpersonationContext Return the current impersonation context. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-ImpersonationContext { [CmdletBinding()] param () Initialize-ImpersonationContext $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() [PSCustomObject] @{ ImpersonationLevel = $windowsIdentity.ImpersonationLevel ImpersonationStack = $Script:ImpersonationContext.Count WindowsIdentity = $windowsIdentity.Name AuthenticationType = $windowsIdentity.AuthenticationType IsAuthenticated = $windowsIdentity.IsAuthenticated IsGuest = $windowsIdentity.IsGuest IsSystem = $windowsIdentity.IsSystem IsAnonymous = $windowsIdentity.IsAnonymous } } <# .SYNOPSIS Leave the current impersonation context. .DESCRIPTION If the current session was impersonated with Push-ImpersonationContext, this command will leave the impersonation context. .INPUTS None. .OUTPUTS None. .EXAMPLE PS C:\> Pop-ImpersonationContext Leave the current impersonation context. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Pop-ImpersonationContext { [CmdletBinding()] param () Initialize-ImpersonationContext if ($Script:ImpersonationContext.Count -gt 0) { # Get the latest impersonation context $popImpersonationContext = $Script:ImpersonationContext.Pop() # Undo the impersonation $popImpersonationContext.Undo() # Reset the PSReadline history save style if ($null -ne (Get-Module -Name 'PSReadline') -and $Script:ImpersonationContext.Count -eq 0) { Set-PSReadlineOption -HistorySaveStyle $Script:PSReadlineHistorySaveStyle -ErrorAction SilentlyContinue } } } <# .SYNOPSIS Create a new impersonation context by using the specified credentials. All following commands will be executed as the specified user until the context is closed. .DESCRIPTION Use the Win32 unmanaged API in the AdvApi32.dll to logon the user with the specified credentials. With this logon token, the user can be impersonated in the current session. .INPUTS None. .OUTPUTS None. .EXAMPLE PS C:\> Push-ImpersonationContext -Credential 'CONTOSO\Operator' Create a new impersonation context for the Contoso Operator user. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Push-ImpersonationContext { [CmdletBinding()] param ( # Specifies a user account to impersonate. [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # The logon type. [Parameter(Mandatory = $false)] [ValidateSet('Interactive', 'Network', 'Batch', 'Service', 'Unlock', 'NetworkClearText', 'NewCredentials')] $LogonType = 'Interactive', # The logon provider. [Parameter(Mandatory = $false)] [ValidateSet('Default', 'WinNT40', 'WinNT50')] $LogonProvider = 'Default' ) Initialize-ImpersonationContext # Handle for the logon token $tokenHandle = [IntPtr]::Zero # Now logon the user account on the local system $logonResult = [Win32.AdvApi32]::LogonUser($Credential.GetNetworkCredential().UserName, $Credential.GetNetworkCredential().Domain, $Credential.GetNetworkCredential().Password, ([Win32.Logon32Type] $LogonType), ([Win32.Logon32Provider] $LogonProvider), [ref] $tokenHandle) # Error handling, if the logon fails if (-not $logonResult) { $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Failed to call LogonUser() throwing Win32 exception with error code: $errorCode" } # Update the PSReadline history save style if ($null -ne (Get-Module -Name 'PSReadline') -and $Script:ImpersonationContext.Count -eq 0) { Set-PSReadlineOption -HistorySaveStyle 'SaveNothing' -ErrorAction SilentlyContinue } # Go to the system root drive, to prevent access denied on user paths Set-Location -Path "$Env:SystemDrive\" # Now, impersonate the new user account $newImpersonationContext = [System.Security.Principal.WindowsIdentity]::Impersonate($tokenHandle) $Script:ImpersonationContext.Push($newImpersonationContext) # Finally, close the handle to the token [Win32.Kernel32]::CloseHandle($tokenHandle) | Out-Null } <# .SYNOPSIS Add an entry to the trusted host list. .DESCRIPTION Append the entry to the trusted host list separated by a comma and store it in the path WSMan:\localhost\Client\TrustedHosts. .INPUTS System.String. Trusted host list entry. .OUTPUTS None. .EXAMPLE PS C:\> Add-TrustedHosts -ComputerName 'SERVER', '10.0.0.1', '*.contoso.com' Add the three entries to the trusted host list. .EXAMPLE PS C:\> '10.0.0.1', '10.0.0.2', '10.0.0.3' | Add-TrustedHosts Add the list of IP addresses to the trusted host list. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Add-TrustedHost { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.String[]] $ComputerName ) begin { # The trusted hosts list can only be changed as an administrator. if (-not (Test-AdministratorRole)) { throw 'Access denied. Please start this functions as an administrator.' } # Get the WSMan trusted hosts item, ensure its a string $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value } process { # Add all new entries foreach ($computer in $ComputerName) { $trustedHosts = '{0},{1}' -f $trustedHosts, $computer $trustedHosts = $trustedHosts.Trim(',') } } end { if ($PSCmdlet.ShouldProcess($trustedHosts, "Set")) { # Finally, set the item Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $trustedHosts -Force } } } <# .SYNOPSIS Get trusted host list entries. .DESCRIPTION Return the WSMan:\localhost\Client\TrustedHosts item as string array separated by the comma. .INPUTS None. .OUTPUTS System.String. Array of trusted host list entries. .EXAMPLE PS C:\> Get-TrustedHosts Get trusted host list entries. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-TrustedHost { [CmdletBinding()] param () # Get the WSMan trusted hosts item, ensure its a string $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value # Split the list by comma if (-not [String]::IsNullOrWhiteSpace($trustedHosts)) { Write-Output $trustedHosts.Split(',') } } <# .SYNOPSIS Remove an entry from the trusted host list. .DESCRIPTION Remove an entry from the trusted host list and regenerate a new list with all the remaining entries, separated by a comma, and store it in the path WSMan:\localhost\Client\TrustedHosts. .INPUTS System.String. Trusted host list entry. .OUTPUTS None. .EXAMPLE PS C:\> Remove-TrustedHosts -ComputerName 'SERVER', '10.0.0.1', '*.contoso.com' Remove three entries from the trusted host list. .EXAMPLE PS C:\> '10.0.0.1', '10.0.0.2', '10.0.0.3' | Remove-TrustedHosts Remove the list of IP addresses from the trusted host list. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Remove-TrustedHost { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.String[]] $ComputerName ) begin { # The trusted hosts list can only be changed as an administrator. if (-not (Test-AdministratorRole)) { throw 'Access denied. Please start this functions as an administrator.' } # Get the WSMan trusted hosts item, ensure its a string $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value # Create an array list $trustedHostsList = New-Object -TypeName 'System.Collections.ArrayList' $trustedHostsList.AddRange($trustedHosts.Split(',')) } process { # Remove the entries foreach ($computer in $ComputerName) { if ($trustedHostsList.Contains($computer)) { $trustedHostsList.Remove($computer) } } } end { # Join the remaining entries $trustedHosts = [String]::Join(',', @($trustedHostsList)) if ($PSCmdlet.ShouldProcess($trustedHosts, "Set")) { # Finally, set the item Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $trustedHosts -Force } } } <# .SYNOPSIS Get the PSCredential objects from the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to retrieve all entries from the Windows Credential Manager vault. The entries are of type PSCredential. To get the full credential entries with all properties like target name, use the Get-VaultEntry cmdlet. .INPUTS None. .OUTPUTS System.Management.Automation.PSCredential. .EXAMPLE PS C:\> Get-VaultCredential -TargetName 'MyUserCred' Return the PSCredential objects with the target name 'MyUserCred'. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-VaultCredential { [CmdletBinding()] [Alias('Get-VaultEntryCredential')] [OutputType([System.Management.Automation.PSCredential])] param ( # Filter the credentials by target name. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $TargetName, # Filter the credentials by type. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialType] $Type, # Filter the credentials by persist location. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialPersist] $Persist, # Filter the credentials by username. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Username ) $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username) foreach ($credentialEntry in $credentialEntries) { Write-Output $credentialEntry.Credential } } <# .SYNOPSIS Get the credential entries from the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to retrieve all entries from the Windows Credential Manager vault. The entries are not objects of type PSCredential. The PSCredential is available on the Credential property or with the Get-VaultCredential cmdlet or you can get a secure string with the Get-VaultSecureString cmdlet. .INPUTS None. .OUTPUTS SecurityFever.CredentialManager.CredentialEntry. .EXAMPLE PS C:\> Get-VaultEntry Returns all available credential entries. .EXAMPLE PS C:\> Get-VaultEntry -TargetName 'MyUserCred' Return the credential entry with the target name 'MyUserCred'. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-VaultEntry { [CmdletBinding()] [OutputType([SecurityFever.CredentialManager.CredentialEntry])] param ( # Filter the credentials by target name. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $TargetName, # Filter the credentials by type. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialType] $Type, # Filter the credentials by persist location. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialPersist] $Persist, # Filter the credentials by username. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Username ) $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username) foreach ($credentialEntry in $credentialEntries) { Write-Output $credentialEntry } } <# .SYNOPSIS Get the secure string objects from the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to retrieve all entries from the Windows Credential Manager vault. The entries are of type secure string. To get the full credential entries with all properties like target name, use the Get-VaultEntry cmdlet. .INPUTS None. .OUTPUTS System.Security.SecureString. .EXAMPLE PS C:\> Get-VaultSecureString -TargetName 'MyUserCred' Return the secure string objects with the target name 'MyUserCred'. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Get-VaultSecureString { [CmdletBinding()] [Alias('Get-VaultEntrySecureString')] [OutputType([System.Security.SecureString])] param ( # Filter the credentials by target name. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $TargetName, # Filter the credentials by type. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialType] $Type, # Filter the credentials by persist location. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialPersist] $Persist, # Filter the credentials by username. Does not support wildcards. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Username ) $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username) foreach ($credentialEntry in $credentialEntries) { Write-Output $credentialEntry.Credential.Password } } <# .SYNOPSIS Create a new entry in the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to create a new entry in the Windows Credential Manager vault. The credential type and persist location can be specified. By default, a generic entry with no special purpose is created on the local machine persist location. Use one of the following persist locations: - Session The credential persists for the life of the logon session. It will not be visible to other logon sessions of this same user. It will not exist after this user logs off and back on. - LocalMachine The credential persists for all subsequent logon sessions on this same computer. It is visible to other logon sessions of this same user on this same computer and not visible to logon sessions for this user on other computers. - Enterprise The credential persists for all subsequent logon sessions on this same computer. It is visible to other logon sessions of this same user on this same computer and to logon sessions for this user on other computers. Use on of the following types: - Generic The credential is a generic credential. The credential will not be used by any particular authentication package. The credential will be stored securely but has no other significant characteristics. - DomainPassword The credential is a password credential and is specific to Microsoft's authentication packages. The NTLM, Kerberos, and Negotiate authentication packages will automatically use this credential when connecting to the named target. - DomainCertificate The credential is a certificate credential and is specific to Microsoft's authentication packages. The Kerberos, Negotiate, and Schannel authentication packages automatically use this credential when connecting to the named target. - DomainVisiblePassword This value is no longer supported. The credential is a password credential and is specific to authentication packages from Microsoft. The Passport authentication package will automatically use this credential when connecting to the named target. - GenericCertificate The credential is a certificate credential that is a generic authentication package. - DomainExtended The credential is supported by extended Negotiate packages. - Maximum The maximum number of supported credential types. - MaximumEx The extended maximum number of supported credential types that now allow new applications to run on older operating systems. .INPUTS None. .OUTPUTS SecurityFever.CredentialManager.CredentialEntry. .EXAMPLE PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Credential $credential Create a new entry in the Credential Manager vault with the name MyUserCred and the credentials specified in the variable. .EXAMPLE PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Username 'DOMAIN\user' -Password $secretPassword Create a new entry in the Credential Manager vault with the name MyUserCred, the username user and the password specified in the variable. .EXAMPLE PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -Persist 'Session' -Credential $credential Create a new entry in the Credential Manager vault with a custom type and persist options. Check the description for detailed information about the types and persist locations. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function New-VaultEntry { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([SecurityFever.CredentialManager.CredentialEntry])] param ( # The entry target name. [Parameter(Mandatory = $true)] [System.String] $TargetName, # The entry type. [Parameter(Mandatory = $false)] [SecurityFever.CredentialManager.CredentialType] $Type = 'Generic', # The entry persist location. [Parameter(Mandatory = $false)] [SecurityFever.CredentialManager.CredentialPersist] $Persist = 'LocalMachine', # The credential object to store in the vault. [Parameter(Mandatory = $true, ParameterSetName = 'Credential')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # The username to store in the vault. Specify password too. [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')] [System.String] $Username, # The password to store in the vault. Specify username too. [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')] [System.Security.SecureString] $Password, # Override any existing entry. [Parameter(Mandatory = $false)] [Switch] $Force ) if ((-not $Force.IsPresent) -and ([SecurityFever.CredentialManager.CredentialStore]::ExistCredential($TargetName, $Type))) { throw "Entry with target name $TargetName and type $Type already exists!" } if ($PSCmdlet.ParameterSetName -eq 'UsernamePassword') { $Credential = New-Object -TypeName PSCredential -ArgumentList $Username, $Password } if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$TargetName ($Type)", "Create Entry")) { $credentialEntry = [SecurityFever.CredentialManager.CredentialStore]::CreateCredential($TargetName, $Type, $Persist, $Credential) Write-Output $credentialEntry } } <# .SYNOPSIS Removes an existing entry in the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to remove a existing entry in the Windows Credential Manager vault. .INPUTS None. .OUTPUTS None. .EXAMPLE PS C:\> Get-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -Persist 'Session' | Remove-VaultEntry Remove the Credential Manager vault entry which was piped to the cmdlet. .EXAMPLE PS C:\> Remove-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' Remove the Credential Manager vault entry with the specified target name and type. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Remove-VaultEntry { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([void])] param ( # The target entry to delete as objects. [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Object')] [SecurityFever.CredentialManager.CredentialEntry[]] $InputObject, # The name of the target entry to delete. [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [System.String] $TargetName, # The type of the target entry to delete. [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [SecurityFever.CredentialManager.CredentialType] $Type, # Force the removal. [Parameter(Mandatory = $false)] [Switch] $Force ) process { if ($PSCmdlet.ParameterSetName -eq 'Properties') { [SecurityFever.CredentialManager.CredentialStore]::RemoveCredential($TargetName, $Type) } else { foreach ($object in $InputObject) { if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$($object.TargetName) ($($object.Type))", "Remove Entry")) { [SecurityFever.CredentialManager.CredentialStore]::RemoveCredential($object.TargetName, $object.Type) } } } } } <# .SYNOPSIS Update an existing entry in the Windows Credential Manager vault. .DESCRIPTION This cmdlet uses the native unmanaged Win32 api to update an existing entry in the Windows Credential Manager vault. Use a entry object or the target name and type combination to identity the entry to update. It is possible to update the persist location, the credentials, the username and the password. If you specify new credentials, new username and password will be ignored. .INPUTS None. .OUTPUTS SecurityFever.CredentialManager.CredentialEntry. .EXAMPLE PS C:\> Update-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -NewCredential $cred .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Update-VaultEntry { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([SecurityFever.CredentialManager.CredentialEntry])] param ( # The target entry to update as objects. [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Object')] [SecurityFever.CredentialManager.CredentialEntry[]] $InputObject, # The name of the target entry to update. [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [System.String] $TargetName, # The type of the target entry to update. [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [SecurityFever.CredentialManager.CredentialType] $Type, # The entry persist location. [Parameter(Mandatory = $false)] [AllowNull()] [SecurityFever.CredentialManager.CredentialPersist] $NewPersist, # The credential object to store in the vault. [Parameter(Mandatory = $false)] [AllowNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $NewCredential, # The username to store in the vault. Specify password too. [Parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $NewUsername, # The password to store in the vault. Specify username too. [Parameter(Mandatory = $false)] [AllowNull()] [System.Security.SecureString] $NewPassword, # Force the update. [Parameter(Mandatory = $false)] [Switch] $Force ) process { if ($PSCmdlet.ParameterSetName -eq 'Properties') { $InputObject = [SecurityFever.CredentialManager.CredentialStore]::GetCredential($TargetName, $Type) } foreach ($object in $InputObject) { if (([SecurityFever.CredentialManager.CredentialStore]::ExistCredential($object.TargetName, $object.Type))) { # If no new persist was specified, use the current value. if (-not $PSBoundParameters.ContainsKey('NewPersist')) { $NewPersist = $object.Persist } # If no new credentials were specified, check if the username or # password was updated and create a new credentials object. if (-not $PSBoundParameters.ContainsKey('NewCredential')) { $NewCredential = $object.Credential if ($PSBoundParameters.ContainsKey('NewUsername')) { $NewCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $NewUsername, $NewCredential.Password } if ($PSBoundParameters.ContainsKey('NewPassword')) { $NewCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $NewCredential.Username, $NewPassword } } if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$($object.TargetName) ($($object.Type))", "Update Entry")) { # Finally, invoke the NewCredential() method to update the # credential entry. $credentialEntry = [SecurityFever.CredentialManager.CredentialStore]::CreateCredential($object.TargetName, $object.Type, $NewPersist, $NewCredential) Write-Output $credentialEntry } } else { throw "Entry with target name $($object.TargetName) and type $($object.Type) already exists!" } } } } <# .SYNOPSIS Get the PSCredential object from the Windows Credential Manager vault or query the caller to enter the credentials. These credentials will be stored in the vault. .DESCRIPTION This cmdlet will load the target PSCredential object from the Windows Credential Manager vault by using Get-VaultCredential. If the vault entry does not exist, it will query the credential from the interactive user by using Get-Credential. If this was successful, the credential will be stored in the Windows Credential Manager vault by using the New-VaultEntry command and then returned to the pipeline. Else an exception will be thrown. .INPUTS None. .OUTPUTS System.Management.Automation.PSCredential. .EXAMPLE PS C:\> Use-VaultCredential -TargetName 'MyUserCred' -Credential 'MyUsername' Return the PSCredential objects with the target name 'MyUserCred' from the vault or if it does not exist, query the user. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/SecurityFever #> function Use-VaultCredential { [CmdletBinding()] [OutputType([System.Management.Automation.PSCredential])] param ( # The vault credential entry name. [Parameter(Mandatory = $true)] [System.String] $TargetName, # The username to search in the vault or query the credential. [Parameter(Mandatory = $false)] [System.String] $Username ) # Get all entries matching the parameters. $entries = @(Get-VaultEntry @PSBoundParameters) if ($entries.Count -eq 1) { # Exactly one entry found, return it Write-Output $entries[0].Credential } elseif ($entries.Count -gt 1) { # Multiple entries found, throw an exception throw 'Multiple entries found in the Credential Manager valut matching the parameters.' } else { # Get the credentials from the user if ($PSBoundParameters.ContainsKey('Username')) { $credential = Get-Credential -Message $TargetName -UserName $Username } else { $credential = Get-Credential -Message $TargetName } # If no credentials were specified, throw an exception if ($null -eq $credential) { throw 'No entry found in the Credential Manager and no credentials entered-' } # Add the credentials to the Credential Manager vault New-VaultEntry -TargetName $TargetName -Credential $credential | Out-Null Write-Output $credential } } function Invoke-AuditPolGetCategoryAllCsv { [CmdletBinding()] param () (auditpol.exe /get /category:* /r) | Where-Object { -not [String]::IsNullOrEmpty($_) } } function Invoke-AuditPolListSubcategoryAllCsv { [CmdletBinding()] param () (auditpol.exe /list /subcategory:* /r) | Where-Object { -not [String]::IsNullOrEmpty($_) } } function Convert-Base32ToByte { param ( [Parameter(Mandatory = $true)] [System.String] $Base32 ) # RFC 4648 Base32 alphabet $rfc4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' $bits = '' # Convert each Base32 character to the binary value between starting at # 00000 for A and ending with 11111 for 7. foreach ($char in $Base32.ToUpper().ToCharArray()) { $bits += [Convert]::ToString($rfc4648.IndexOf($char), 2).PadLeft(5, '0') } # Convert 8 bit chunks to bytes, ignore the last bits. for ($i = 0; $i -le ($bits.Length - 8); $i += 8) { [Byte] [Convert]::ToInt32($bits.Substring($i, 8), 2) } } function Test-AdministratorRole { [CmdletBinding()] param () # Check against the generic administrator role (language neutral). $AdministratorRole = [Security.Principal.WindowsBuiltInRole]::Administrator # Get the current user identity $CurrentWindowsPrincipal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() return $CurrentWindowsPrincipal.IsInRole($AdministratorRole) } function Convert-EventLogObject { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = New-Object -TypeName PSObject -Property @{ ComputerName = $Record.MachineName.Split('.')[0] TimeCreated = $Record.TimeCreated Id = $Record.Id Activity = $Map[$Record.Id].Activity Event = $Map[$Record.Id].Event Type = '' Reason = '' Username = '' Computer = '' Process = '' Comment = '' } $activity.PSTypeNames.Insert(0, 'SecurityFever.ActivityRecord') Write-Output $activity } function Convert-EventLogObjectId1 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordReason = $Record.Properties[12].Value # Set default values $activity.Reason = "Unknown ($recordReason)" # Populate the awake source switch ($recordReason) { 1 { $activity.Reason = 'Power Button' } 3 { $activity.Reason = 'S4 Doze to Hibernate' } 5 { $activity.Reason = 'Device - ACPI Lid' } } Write-Output $activity } function Convert-EventLogObjectId1074 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordType = $Record.Properties[4].Value $recordUser = $Record.Properties[6].Value $recordComputer = $Record.Properties[1].Value $recordProcess = $Record.Properties[0].Value $recordReason = $Record.Properties[2].Value $recordComment = $Record.Properties[5].Value # Set default values $activity.Type = $recordType $activity.Reason = $recordReason $activity.Username = $recordUser $activity.Computer = $recordComputer $activity.Process = $recordProcess $activity.Comment = $recordComment Write-Output $activity } function Convert-EventLogObjectId42 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordReason = $Record.Properties[2].Value # Set default values $activity.Reason = "Unknown ($recordReason)" # Populate the sleep reason switch ($recordReason) { 0 { $activity.Reason = 'Button or Lid' } 2 { $activity.Reason = 'Battery' } 4 { $activity.Reason = 'Application API' } 6 { $activity.Reason = 'Hibernate from Sleep - Fixed Timeout' } 7 { $activity.Reason = 'System Idle' } } Write-Output $activity } function Convert-EventLogObjectId4624 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) # Definition: Types $typeMap = @{ '2' = 'Interactive' '3' = 'Network' '4' = 'Batch' '5' = 'Service' '7' = 'Unlock' '8' = 'NetworkCleartext' '9' = 'NewCredentials' '10' = 'RemoteInteractive' '11' = 'CachedInteractive' } $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordType = $Record.Properties[8].Value.ToString().Trim() $recordUser = $Record.Properties[6].Value + '\' + $Record.Properties[5].Value $recordComputer = $Record.Properties[11].Value $recordProcess = $Record.Properties[9].Value.Trim() $recordAuth = $Record.Properties[10].Value $recordAuth2 = $Record.Properties[14].Value # Set default values $activity.Type = "Unknown ($recordType)" $activity.Username = $recordUser $activity.Computer = $recordComputer $activity.Process = $recordProcess $activity.Comment = "$recordAuth ($recordAuth2)" # Populate the type if ($typeMap.ContainsKey($recordType)) { $activity.Type = $typeMap[$recordType] } # Cleanup comment $activity.Comment = $activity.Comment.Replace(' (-)', '') Write-Output $activity } function Convert-EventLogObjectId4625 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) # Definition: Types $typeMap = @{ '2' = 'Interactive' '3' = 'Network' '4' = 'Batch' '5' = 'Service' '7' = 'Unlock' '8' = 'NetworkCleartext' '9' = 'NewCredentials' '10' = 'RemoteInteractive' '11' = 'CachedInteractive' } # Definition: Reasons $reasonMap = @{ 0xC0000064 = 'Username does not exist' 0xC000006A = 'Username is correct but the password is wrong' 0xC0000234 = 'User is currently locked out' 0xC0000072 = 'Account is currently disabled' 0xC000006F = 'User tried to logon outside his day of week or time of day restrictions' 0xC0000070 = 'Workstation restriction, or authentication policy silo violation' 0xC0000193 = 'Account expiration' 0xC0000071 = 'Expired password' 0xC0000133 = 'Clocks between DC and other computer too far out of sync' 0xC0000224 = 'User is required to change password at next logon' 0xC0000225 = 'Evidently a bug in Windows and not a risk' 0xc000015b = 'The user has not been granted the requested logon type (aka logon right) at this machine' } $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordType = $Record.Properties[10].Value.ToString().Trim() $recordUser = $Record.Properties[6].Value + '\' + $Record.Properties[5].Value $recordComputer = $Record.Properties[13].Value $recordReason = $Record.Properties[7].Value $recordReason2 = $Record.Properties[9].Value $recordProcess = $Record.Properties[11].Value.Trim() $recordAuth = $Record.Properties[12].Value.ToString().Trim() $recordAuth2 = $Record.Properties[15].Value.ToString().Trim() # Set default values $activity.Type = "Unknown ($recordType)" $activity.Reason = "$recordReason ($recordReason2)" $activity.Username = $recordUser $activity.Computer = $recordComputer $activity.Process = $recordProcess $activity.Comment = "$recordAuth ($recordAuth2)" # Populate the type if ($typeMap.ContainsKey($recordType)) { $activity.Type = $typeMap[$recordType] } # Cleanup comment $activity.Comment = $activity.Comment.Replace(' (-)', '') # Populate reason if ($reasonMap.ContainsKey($recordReason)) { $recordReason = $reasonMap[$recordReason] } if ($reasonMap.ContainsKey($recordReason2)) { $recordReason2 = $reasonMap[$recordReason2] } $activity.Reason = "$recordReason ($recordReason2)" Write-Output $activity <# try { $reason1 = $reasonMap[$reason1] } catch { } try { $reason2 = $reasonMap[$reason2] } catch { } $reason = @() if (-not [String]::IsNullOrEmpty($reason1) -and $reason1 -ne '-') { $reason += $reason1 } if (-not [String]::IsNullOrEmpty($reason2) -and $reason2 -ne '-') { $reason += $reason2 } #4625 = @{ Type = 'Logon'; Log = 'Security'; Event = 'Logon Failed' }# An account failed to log on. $activity.Detail = $activity.Detail -f $type, $user, $computer, ($reason -join ' / '), $process, $auth #> } function Convert-EventLogObjectId4634 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) # Definition: Types $typeMap = @{ '2' = 'Interactive' '3' = 'Network' '4' = 'Batch' '5' = 'Service' '7' = 'Unlock' '8' = 'NetworkCleartext' '9' = 'NewCredentials' '10' = 'RemoteInteractive' '11' = 'CachedInteractive' } $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordType = $Record.Properties[4].Value.ToString().Trim() $recordUser = $Record.Properties[2].Value + '\' + $Record.Properties[1].Value # Set default values $activity.Type = "Unknown ($recordType)" $activity.Username = $recordUser # Populate the type if ($typeMap.ContainsKey($recordType)) { $activity.Type = $typeMap[$recordType] } Write-Output $activity } function Convert-EventLogObjectId4647 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = Convert-EventLogObject -Record $Record -Map $Map # Grab record properties $recordUser = $Record.Properties[2].Value + '\' + $Record.Properties[1].Value # Set default values $activity.Username = $recordUser Write-Output $activity } function Convert-EventLogObjectId6008 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Record, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Map ) $activity = Convert-EventLogObject -Record $Record -Map $Map try { $lrMark = [char]8206 $time = $Record.Properties[0].Value $date = $Record.Properties[1].Value.Replace("$lrMark", "") $activity.TimeCreated = [DateTime]::Parse("$date $time") } catch { Write-Warning "TimeCreated value parsing error for event 6008: $_" } Write-Output $activity } function Initialize-ImpersonationContext { [CmdletBinding()] param () # Add Win32 native API methods to call to LogonUser() if (-not ([System.Management.Automation.PSTypeName]'Win32.AdvApi32').Type) { Add-Type -Namespace 'Win32' -Name 'AdvApi32' -MemberDefinition ' [DllImport("advapi32.dll", SetLastError = true)] public static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); ' } # Add Win32 native API methods to call to CloseHandle() if (-not ([System.Management.Automation.PSTypeName]'Win32.Kernel32').Type) { Add-Type -Namespace 'Win32' -Name 'Kernel32' -MemberDefinition ' [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr handle); ' } # Define enumeration for the logon type if (-not ([System.Management.Automation.PSTypeName]'Win33.Logon32Type').Type) { Add-Type -TypeDefinition ' namespace Win32 { public enum Logon32Type { Interactive = 2, Network = 3, Batch = 4, Service = 5, Unlock = 7, NetworkClearText = 8, NewCredentials = 9 } } ' } # Define enumeration for the logon provider if (-not ([System.Management.Automation.PSTypeName]'Win33.Logon32Type').Type) { Add-Type -TypeDefinition ' namespace Win32 { public enum Logon32Provider { Default = 0, WinNT40 = 2, WinNT50 = 3 } } ' } # Global variable to hold the impersonation context if ($null -eq $Script:ImpersonationContext) { $Script:ImpersonationContext = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]' } # Global variable to hold the PSReadline preference if ($null -ne (Get-Module -Name 'PSReadline') -and $null -eq $Script:PSReadlineHistorySaveStyle) { $Script:PSReadlineHistorySaveStyle = Get-PSReadlineOption | Select-Object -ExpandProperty 'HistorySaveStyle' } } New-Variable -Name 'ModulePath' -Value $PSScriptRoot |