.*Index of|Parent Directory|Directory Listing|.*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 "
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 "
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 "
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 "
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 = "" },
@{ 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 "
]>
&xxe;
"@
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 -- 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 = @"
CyberArk Security Audit Report
$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)
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.
"@
# 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 += @"
| Control |
Description |
Findings |
Status |
"@
# 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 += @"
| $controlId |
$($script:CISControls[$controlId]) |
$controlCount |
$controlStatus |
"@
}
$html += @"
Click on any finding row to expand and view detailed information including evidence, remediation steps, and business impact analysis.
|
ID |
Severity |
Component |
Category |
Finding |
Resource |
CVSS |
"@
# 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 += @"
|
$($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)
|
"@
}
$html += @"
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 += @"
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
|
Status |
Control |
Category |
Check Name |
Reason |
Follow-Up |
"@
$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 += @"
|
$statusIcon |
$($skipped.CISControl) |
$($skipped.Category) |
$($skipped.CheckName) |
$($skipped.Reason) |
$followUpIcon |
$($skipped.CheckID)
$($skipped.CISDescription)
$($skipped.Prerequisites)
$($skipped.RiskIfNotChecked)
$($skipped.ManualVerificationSteps)
$(if ($skipped.AlternativeEvidence) { "$($skipped.AlternativeEvidence) " })
|
"@
}
$html += @"
"@
}
$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