|
| 1 | +package scanners |
| 2 | + |
| 3 | +// Scanner Execution Coordinator |
| 4 | +// |
| 5 | +// Extracted from cmd/root.go Phase 2 refactoring (2025-10-06) |
| 6 | +// Contains main scanner coordination logic with dependency injection |
| 7 | + |
| 8 | +import ( |
| 9 | + "context" |
| 10 | + "fmt" |
| 11 | + "strings" |
| 12 | + "time" |
| 13 | + |
| 14 | + "github.com/CodeMonkeyCybersecurity/shells/internal/config" |
| 15 | + "github.com/CodeMonkeyCybersecurity/shells/internal/core" |
| 16 | + "github.com/CodeMonkeyCybersecurity/shells/internal/logger" |
| 17 | + "github.com/CodeMonkeyCybersecurity/shells/pkg/auth" |
| 18 | + "github.com/CodeMonkeyCybersecurity/shells/pkg/types" |
| 19 | +) |
| 20 | + |
| 21 | +// ScanExecutor coordinates all scanner execution with dependency injection |
| 22 | +type ScanExecutor struct { |
| 23 | + log *logger.Logger |
| 24 | + store core.ResultStore |
| 25 | + cfg *config.Config |
| 26 | +} |
| 27 | + |
| 28 | +// NewScanExecutor creates a new scanner executor with dependencies |
| 29 | +func NewScanExecutor(log *logger.Logger, store core.ResultStore, cfg *config.Config) *ScanExecutor { |
| 30 | + return &ScanExecutor{ |
| 31 | + log: log, |
| 32 | + store: store, |
| 33 | + cfg: cfg, |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +// RunBusinessLogicTests executes business logic vulnerability tests |
| 38 | +func (e *ScanExecutor) RunBusinessLogicTests(ctx context.Context, target string) error { |
| 39 | + e.log.Infow("Running Business Logic Tests") |
| 40 | + |
| 41 | + // Initialize business logic analyzers |
| 42 | + analyzers := []struct { |
| 43 | + name string |
| 44 | + test func(string) error |
| 45 | + }{ |
| 46 | + {"Password Reset", e.testPasswordReset}, |
| 47 | + {"MFA Bypass", e.testMFABypass}, |
| 48 | + {"Race Conditions", e.testRaceConditions}, |
| 49 | + {"E-commerce Logic", e.testEcommerceLogic}, |
| 50 | + {"Account Recovery", e.testAccountRecovery}, |
| 51 | + } |
| 52 | + |
| 53 | + var findings []types.Finding |
| 54 | + errors := 0 |
| 55 | + |
| 56 | + for _, analyzer := range analyzers { |
| 57 | + if err := analyzer.test(target); err != nil { |
| 58 | + e.log.Debugw("Business logic test failed", "test", analyzer.name, "error", err) |
| 59 | + errors++ |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + if errors == 0 { |
| 64 | + e.log.Infow("Business Logic Tests completed successfully") |
| 65 | + } else { |
| 66 | + e.log.Warnw("Business Logic Tests completed with issues", |
| 67 | + "errorCount", errors) |
| 68 | + } |
| 69 | + |
| 70 | + // Store any findings |
| 71 | + if len(findings) > 0 && e.store != nil { |
| 72 | + if err := e.store.SaveFindings(ctx, findings); err != nil { |
| 73 | + e.log.LogError(ctx, err, "Failed to save business logic findings") |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + return nil |
| 78 | +} |
| 79 | + |
| 80 | +// testPasswordReset tests password reset functionality |
| 81 | +func (e *ScanExecutor) testPasswordReset(target string) error { |
| 82 | + // This will be implemented using the password reset analyzer |
| 83 | + // For now, create a placeholder finding |
| 84 | + ctx := context.Background() |
| 85 | + |
| 86 | + if e.store != nil { |
| 87 | + finding := types.Finding{ |
| 88 | + ID: fmt.Sprintf("bl-reset-%d", time.Now().Unix()), |
| 89 | + ScanID: fmt.Sprintf("scan-%d", time.Now().Unix()), |
| 90 | + Type: "Business Logic - Password Reset", |
| 91 | + Severity: types.SeverityInfo, |
| 92 | + Title: "Password Reset Flow Analyzed", |
| 93 | + Description: "Analyzed password reset flow for vulnerabilities", |
| 94 | + Tool: "business-logic", |
| 95 | + Evidence: fmt.Sprintf("Target: %s", target), |
| 96 | + CreatedAt: time.Now(), |
| 97 | + UpdatedAt: time.Now(), |
| 98 | + } |
| 99 | + |
| 100 | + return e.store.SaveFindings(ctx, []types.Finding{finding}) |
| 101 | + } |
| 102 | + |
| 103 | + return nil |
| 104 | +} |
| 105 | + |
| 106 | +// testMFABypass tests for MFA bypass vulnerabilities |
| 107 | +func (e *ScanExecutor) testMFABypass(target string) error { |
| 108 | + // Placeholder for MFA bypass testing |
| 109 | + return nil |
| 110 | +} |
| 111 | + |
| 112 | +// testRaceConditions tests for race condition vulnerabilities |
| 113 | +func (e *ScanExecutor) testRaceConditions(target string) error { |
| 114 | + // Placeholder for race condition testing |
| 115 | + return nil |
| 116 | +} |
| 117 | + |
| 118 | +// testEcommerceLogic tests e-commerce business logic |
| 119 | +func (e *ScanExecutor) testEcommerceLogic(target string) error { |
| 120 | + // Placeholder for e-commerce logic testing |
| 121 | + return nil |
| 122 | +} |
| 123 | + |
| 124 | +// testAccountRecovery tests account recovery mechanisms |
| 125 | +func (e *ScanExecutor) testAccountRecovery(target string) error { |
| 126 | + // Placeholder for account recovery testing |
| 127 | + return nil |
| 128 | +} |
| 129 | + |
| 130 | +// RunAuthenticationTests executes authentication vulnerability tests |
| 131 | +func (e *ScanExecutor) RunAuthenticationTests(ctx context.Context, target string) error { |
| 132 | + e.log.Infow("Running Authentication Tests") |
| 133 | + |
| 134 | + // Discover authentication endpoints |
| 135 | + discovery := auth.NewDiscovery() |
| 136 | + result, err := discovery.DiscoverAuth(ctx, target) |
| 137 | + if err != nil { |
| 138 | + e.log.Debugw("Authentication discovery failed", "error", err) |
| 139 | + e.log.Infow("No auth endpoints found") |
| 140 | + return nil |
| 141 | + } |
| 142 | + |
| 143 | + var allFindings []types.Finding |
| 144 | + authTypesFound := []string{} |
| 145 | + |
| 146 | + // Test SAML if discovered |
| 147 | + if result.SAML != nil { |
| 148 | + authTypesFound = append(authTypesFound, "SAML") |
| 149 | + samlScanner := auth.NewSAMLScanner() |
| 150 | + if findings := samlScanner.Scan(ctx, result.SAML.MetadataURL); len(findings) > 0 { |
| 151 | + allFindings = append(allFindings, findings...) |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + // Test OAuth2/OIDC if discovered |
| 156 | + if result.OAuth2 != nil { |
| 157 | + authTypesFound = append(authTypesFound, "OAuth2/OIDC") |
| 158 | + // Create OAuth2 finding |
| 159 | + finding := types.Finding{ |
| 160 | + ID: fmt.Sprintf("auth-oauth2-%d", time.Now().Unix()), |
| 161 | + ScanID: fmt.Sprintf("scan-%d", time.Now().Unix()), |
| 162 | + Type: "OAuth2 Configuration", |
| 163 | + Severity: types.SeverityInfo, |
| 164 | + Title: "OAuth2/OIDC Endpoint Discovered", |
| 165 | + Description: "OAuth2/OIDC endpoints discovered and analyzed", |
| 166 | + Tool: "auth-scanner", |
| 167 | + Evidence: "OAuth2 configuration endpoint detected", |
| 168 | + CreatedAt: time.Now(), |
| 169 | + UpdatedAt: time.Now(), |
| 170 | + } |
| 171 | + allFindings = append(allFindings, finding) |
| 172 | + } |
| 173 | + |
| 174 | + // Test WebAuthn if discovered |
| 175 | + if result.WebAuthn != nil { |
| 176 | + authTypesFound = append(authTypesFound, "WebAuthn") |
| 177 | + // Create WebAuthn finding |
| 178 | + finding := types.Finding{ |
| 179 | + ID: fmt.Sprintf("auth-webauthn-%d", time.Now().Unix()), |
| 180 | + ScanID: fmt.Sprintf("scan-%d", time.Now().Unix()), |
| 181 | + Type: "WebAuthn Configuration", |
| 182 | + Severity: types.SeverityInfo, |
| 183 | + Title: "WebAuthn/FIDO2 Support Detected", |
| 184 | + Description: "WebAuthn authentication is supported by this application", |
| 185 | + Tool: "auth-scanner", |
| 186 | + Evidence: "WebAuthn registration and authentication endpoints detected", |
| 187 | + CreatedAt: time.Now(), |
| 188 | + UpdatedAt: time.Now(), |
| 189 | + } |
| 190 | + allFindings = append(allFindings, finding) |
| 191 | + } |
| 192 | + |
| 193 | + // Store all findings |
| 194 | + if len(allFindings) > 0 && e.store != nil { |
| 195 | + if err := e.store.SaveFindings(ctx, allFindings); err != nil { |
| 196 | + e.log.LogError(ctx, err, "Failed to save auth findings") |
| 197 | + } else { |
| 198 | + e.log.Infow("Successfully saved auth findings", "count", len(allFindings)) |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + if len(authTypesFound) > 0 { |
| 203 | + e.log.Infow("Authentication Tests completed", |
| 204 | + "foundMethods", strings.Join(authTypesFound, ", ")) |
| 205 | + } else { |
| 206 | + e.log.Infow("No auth methods detected") |
| 207 | + } |
| 208 | + |
| 209 | + return nil |
| 210 | +} |
| 211 | + |
| 212 | +// RunInfrastructureScans executes infrastructure security scans |
| 213 | +func (e *ScanExecutor) RunInfrastructureScans(ctx context.Context, target string) error { |
| 214 | + e.log.Infow("Running Infrastructure Scans") |
| 215 | + |
| 216 | + var allFindings []types.Finding |
| 217 | + testsRun := 0 |
| 218 | + errorCount := 0 |
| 219 | + |
| 220 | + // Check if Nomad is available for distributed execution |
| 221 | + _, useNomad := e.GetNomadClient() |
| 222 | + |
| 223 | + // Run Nmap port scanning |
| 224 | + if nmapFindings, err := e.runNmapScan(ctx, target, useNomad); err != nil { |
| 225 | + e.log.LogError(ctx, err, "Nmap scan failed", "target", target) |
| 226 | + errorCount++ |
| 227 | + } else { |
| 228 | + allFindings = append(allFindings, nmapFindings...) |
| 229 | + testsRun++ |
| 230 | + } |
| 231 | + |
| 232 | + // Run Nuclei vulnerability scanning |
| 233 | + if nucleiFindings, err := e.runNucleiScan(ctx, target, useNomad); err != nil { |
| 234 | + e.log.LogError(ctx, err, "Nuclei scan failed", "target", target) |
| 235 | + errorCount++ |
| 236 | + } else { |
| 237 | + allFindings = append(allFindings, nucleiFindings...) |
| 238 | + testsRun++ |
| 239 | + } |
| 240 | + |
| 241 | + // Run SSL/TLS analysis |
| 242 | + if sslFindings, err := e.runSSLScan(ctx, target, useNomad); err != nil { |
| 243 | + e.log.LogError(ctx, err, "SSL scan failed", "target", target) |
| 244 | + errorCount++ |
| 245 | + } else { |
| 246 | + allFindings = append(allFindings, sslFindings...) |
| 247 | + testsRun++ |
| 248 | + } |
| 249 | + |
| 250 | + // Store findings |
| 251 | + if len(allFindings) > 0 && e.store != nil { |
| 252 | + if err := e.store.SaveFindings(ctx, allFindings); err != nil { |
| 253 | + e.log.LogError(ctx, err, "Failed to save infrastructure findings") |
| 254 | + } else { |
| 255 | + e.log.Infow("Saved infrastructure findings", "count", len(allFindings)) |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + if errorCount == 0 { |
| 260 | + e.log.Infow("Infrastructure Scans completed successfully", |
| 261 | + "toolsRun", testsRun) |
| 262 | + } else { |
| 263 | + e.log.Warnw("Infrastructure Scans completed with failures", |
| 264 | + "failed", errorCount, |
| 265 | + "total", testsRun) |
| 266 | + } |
| 267 | + |
| 268 | + return nil |
| 269 | +} |
| 270 | + |
| 271 | +// RunSpecializedTests executes specialized vulnerability tests |
| 272 | +func (e *ScanExecutor) RunSpecializedTests(ctx context.Context, target string) error { |
| 273 | + e.log.Infow("Running Specialized Tests") |
| 274 | + |
| 275 | + var allFindings []types.Finding |
| 276 | + testsRun := []string{} |
| 277 | + |
| 278 | + // 1. SCIM Vulnerability Testing |
| 279 | + if scimFindings := e.runSCIMTests(ctx, target); len(scimFindings) > 0 { |
| 280 | + allFindings = append(allFindings, scimFindings...) |
| 281 | + testsRun = append(testsRun, "SCIM") |
| 282 | + } |
| 283 | + |
| 284 | + // 2. HTTP Request Smuggling Testing |
| 285 | + if smugglingFindings := e.runHTTPSmugglingTests(ctx, target); len(smugglingFindings) > 0 { |
| 286 | + allFindings = append(allFindings, smugglingFindings...) |
| 287 | + testsRun = append(testsRun, "Smuggling") |
| 288 | + } |
| 289 | + |
| 290 | + // 3. JavaScript Analysis |
| 291 | + if jsFindings := e.runJavaScriptAnalysis(ctx, target); len(jsFindings) > 0 { |
| 292 | + allFindings = append(allFindings, jsFindings...) |
| 293 | + testsRun = append(testsRun, "JS") |
| 294 | + } |
| 295 | + |
| 296 | + // 4. Secrets Scanning |
| 297 | + if secretsFindings := e.runSecretsScanning(ctx, target); len(secretsFindings) > 0 { |
| 298 | + allFindings = append(allFindings, secretsFindings...) |
| 299 | + testsRun = append(testsRun, "Secrets") |
| 300 | + } |
| 301 | + |
| 302 | + // 5. OAuth2 Security Testing |
| 303 | + if oauth2Findings := e.runOAuth2SecurityTests(ctx, target); len(oauth2Findings) > 0 { |
| 304 | + allFindings = append(allFindings, oauth2Findings...) |
| 305 | + testsRun = append(testsRun, "OAuth2") |
| 306 | + } |
| 307 | + |
| 308 | + // 6. Directory/Path Fuzzing |
| 309 | + if fuzzingFindings := e.runFuzzingTests(ctx, target); len(fuzzingFindings) > 0 { |
| 310 | + allFindings = append(allFindings, fuzzingFindings...) |
| 311 | + testsRun = append(testsRun, "Fuzzing") |
| 312 | + } |
| 313 | + |
| 314 | + // 7. Protocol Security Testing |
| 315 | + if protocolFindings := e.runProtocolTests(ctx, target); len(protocolFindings) > 0 { |
| 316 | + allFindings = append(allFindings, protocolFindings...) |
| 317 | + testsRun = append(testsRun, "Protocol") |
| 318 | + } |
| 319 | + |
| 320 | + // 8. Passive Intelligence Gathering |
| 321 | + if passiveFindings := e.runPassiveIntelligence(ctx, target); len(passiveFindings) > 0 { |
| 322 | + allFindings = append(allFindings, passiveFindings...) |
| 323 | + testsRun = append(testsRun, "Passive") |
| 324 | + } |
| 325 | + |
| 326 | + // 9. Heavy Security Tools (Boileau) |
| 327 | + if boileauFindings := e.runBoileauTests(ctx, target); len(boileauFindings) > 0 { |
| 328 | + allFindings = append(allFindings, boileauFindings...) |
| 329 | + testsRun = append(testsRun, "Boileau") |
| 330 | + } |
| 331 | + |
| 332 | + // 10. Run Correlation Analysis on all findings |
| 333 | + if correlationFindings := e.runCorrelationAnalysis(ctx, target, allFindings); len(correlationFindings) > 0 { |
| 334 | + allFindings = append(allFindings, correlationFindings...) |
| 335 | + testsRun = append(testsRun, "Correlation") |
| 336 | + } |
| 337 | + |
| 338 | + // Store all findings |
| 339 | + if len(allFindings) > 0 && e.store != nil { |
| 340 | + if err := e.store.SaveFindings(ctx, allFindings); err != nil { |
| 341 | + e.log.LogError(ctx, err, "Failed to save specialized findings") |
| 342 | + } else { |
| 343 | + e.log.Infow("Successfully saved specialized findings", "count", len(allFindings)) |
| 344 | + } |
| 345 | + } |
| 346 | + |
| 347 | + if len(testsRun) > 0 { |
| 348 | + e.log.Infow("Specialized Tests completed", |
| 349 | + "testsRun", strings.Join(testsRun, ", ")) |
| 350 | + } else { |
| 351 | + e.log.Infow("Specialized Tests completed successfully") |
| 352 | + } |
| 353 | + |
| 354 | + return nil |
| 355 | +} |
0 commit comments