133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
gotestwafTimeout = 120 * time.Second
|
|
targetURL = "https://git.sechpoint.app"
|
|
)
|
|
|
|
// RunGoTestWAF executes GoTestWAF scan against the target server.
|
|
// The report is saved to reports/ directory with the given reportName.
|
|
// Returns the raw output for AI analysis.
|
|
func RunGoTestWAF(ctx context.Context, projectRoot string, reportName string) (string, error) {
|
|
binaryPath := filepath.Join(projectRoot, "gotestwaf")
|
|
|
|
// Verify binary exists
|
|
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
|
return "", fmt.Errorf("GoTestWAF binary not found at %s", binaryPath)
|
|
}
|
|
|
|
reportsDir := filepath.Join(projectRoot, "reports")
|
|
if err := os.MkdirAll(reportsDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create reports directory: %w", err)
|
|
}
|
|
|
|
// Create context with timeout
|
|
scanCtx, cancel := context.WithTimeout(ctx, gotestwafTimeout)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(scanCtx, binaryPath,
|
|
"--url", targetURL,
|
|
"--configPath", "gotestwaf-config.yaml",
|
|
"--testCasesPath", "testcases",
|
|
"--maxIdleConns", "2",
|
|
"--maxRedirects", "10",
|
|
"--reportPath", reportsDir,
|
|
"--reportName", reportName,
|
|
"--reportFormat", "html",
|
|
"--wafName", "generic",
|
|
"--skipWAFBlockCheck",
|
|
"--nonBlockedAsPassed",
|
|
"--tlsVerify",
|
|
"--noEmailReport",
|
|
"--quiet",
|
|
)
|
|
|
|
cmd.Dir = projectRoot
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
outputStr := string(output)
|
|
|
|
if err != nil {
|
|
if scanCtx.Err() == context.DeadlineExceeded {
|
|
return outputStr, fmt.Errorf("GoTestWAF scan timed out after %v", gotestwafTimeout)
|
|
}
|
|
return outputStr, fmt.Errorf("GoTestWAF scan failed: %w", err)
|
|
}
|
|
|
|
return outputStr, nil
|
|
}
|
|
|
|
// FindMostCriticalSubdomain selects the most security-critical subdomain
|
|
// from a list of discovered subdomains. Prioritizes:
|
|
// 1. Subdomains with "api", "admin", "dashboard" in name
|
|
// 2. Subdomains vs root domain
|
|
// 3. First subdomain alphabetically as tiebreaker
|
|
func FindMostCriticalSubdomain(subdomains []string, originalDomain string) string {
|
|
if len(subdomains) == 0 {
|
|
return originalDomain
|
|
}
|
|
|
|
// Score each subdomain for "criticality"
|
|
type scored struct {
|
|
domain string
|
|
score int
|
|
}
|
|
|
|
var candidates []scored
|
|
for _, s := range subdomains {
|
|
score := 0
|
|
lower := strings.ToLower(s)
|
|
if strings.Contains(lower, "api") {
|
|
score += 10
|
|
}
|
|
if strings.Contains(lower, "admin") {
|
|
score += 8
|
|
}
|
|
if strings.Contains(lower, "dashboard") {
|
|
score += 8
|
|
}
|
|
if strings.Contains(lower, "gateway") {
|
|
score += 6
|
|
}
|
|
if strings.Contains(lower, "auth") {
|
|
score += 6
|
|
}
|
|
if strings.Contains(lower, "console") {
|
|
score += 5
|
|
}
|
|
if strings.Contains(lower, "dev") || strings.Contains(lower, "staging") {
|
|
score += 4
|
|
}
|
|
if strings.Contains(lower, "app") {
|
|
score += 2
|
|
}
|
|
// Prefer subdomains over root
|
|
if strings.HasPrefix(s, "www.") {
|
|
score += 1
|
|
}
|
|
if s != originalDomain && !strings.HasPrefix(s, "www.") {
|
|
score += 3
|
|
}
|
|
candidates = append(candidates, scored{s, score})
|
|
}
|
|
|
|
// Find highest score
|
|
best := candidates[0]
|
|
for _, c := range candidates[1:] {
|
|
if c.score > best.score || (c.score == best.score && c.domain < best.domain) {
|
|
best = c
|
|
}
|
|
}
|
|
|
|
return best.domain
|
|
}
|