package frameworks import ( "fmt" "io" "net/http" "os" "regexp" "strings" "time" "github.com/charmbracelet/log" "github.com/dropalldatabases/sif/internal/styles" "github.com/dropalldatabases/sif/pkg/logger" ) type FrameworkResult struct { Name string `json:"name"` Version string `json:"version"` Confidence float32 `json:"confidence"` CVEs []string `json:"cves,omitempty"` Suggestions []string `json:"suggestions,omitempty"` } type FrameworkSignature struct { Pattern string Weight float32 HeaderOnly bool } var frameworkSignatures = map[string][]FrameworkSignature{ "Laravel": { {Pattern: `laravel_session`, Weight: 0.4, HeaderOnly: true}, {Pattern: `XSRF-TOKEN`, Weight: 0.3, HeaderOnly: true}, {Pattern: ` highestConfidence { highestConfidence = confidence bestMatch = framework } } if highestConfidence > 0 { version := detectVersion(bodyStr, bestMatch) result := &FrameworkResult{ Name: bestMatch, Version: version, Confidence: highestConfidence, } if logdir != "" { logger.Write(url, logdir, fmt.Sprintf("Detected framework: %s (version: %s, confidence: %.2f)\n", bestMatch, version, highestConfidence)) } frameworklog.Infof("Detected %s framework (version: %s) with %.2f confidence", styles.Highlight.Render(bestMatch), version, highestConfidence) if cves, suggestions := getVulnerabilities(bestMatch, version); len(cves) > 0 { result.CVEs = cves result.Suggestions = suggestions for _, cve := range cves { frameworklog.Warnf("Found potential vulnerability: %s", styles.Highlight.Render(cve)) } } return result, nil } frameworklog.Info("No framework detected") return nil, nil } func containsHeader(headers http.Header, signature string) bool { for _, values := range headers { for _, value := range values { if strings.Contains(strings.ToLower(value), strings.ToLower(signature)) { return true } } } return false } func detectVersion(body string, framework string) string { version := extractVersion(body, framework) if version == "Unknown" { return version } parts := strings.Split(version, ".") var normalized string if len(parts) >= 3 { normalized = fmt.Sprintf("%05s.%05s.%05s", parts[0], parts[1], parts[2]) } return normalized } func exp(x float64) float64 { if x > 88.0 { return 1e38 } if x < -88.0 { return 0 } sum := 1.0 term := 1.0 for i := 1; i <= 20; i++ { term *= x / float64(i) sum += term } return sum } func getVulnerabilities(framework, version string) ([]string, []string) { // TODO: Implement CVE database lookup if framework == "Laravel" && version == "8.0.0" { return []string{ "CVE-2021-3129", }, []string{ "Update to Laravel 8.4.2 or later", "Implement additional input validation", } } return nil, nil } func extractVersion(body string, framework string) string { versionPatterns := map[string]string{ "Laravel": `Laravel\s+[Vv]?(\d+\.\d+\.\d+)`, "Django": `Django\s+[Vv]?(\d+\.\d+\.\d+)`, "Ruby on Rails": `Rails\s+[Vv]?(\d+\.\d+\.\d+)`, "Express.js": `Express\s+[Vv]?(\d+\.\d+\.\d+)`, "ASP.NET": `ASP\.NET\s+[Vv]?(\d+\.\d+\.\d+)`, "Spring": `Spring\s+[Vv]?(\d+\.\d+\.\d+)`, "Flask": `Flask\s+[Vv]?(\d+\.\d+\.\d+)`, } if pattern, exists := versionPatterns[framework]; exists { re := regexp.MustCompile(pattern) matches := re.FindStringSubmatch(body) if len(matches) > 1 { return matches[1] } } return "Unknown" }