gitex2026/AttackSurface/src/internal/scanner/gotestwaf.go
2026-04-24 19:17:01 +00:00

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
}