diff --git a/pkg/config/config.go b/pkg/config/config.go index 3f8286c..676775d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -44,6 +44,7 @@ type Settings struct { Shodan bool SQL bool LFI bool + Framework bool } const ( @@ -89,6 +90,7 @@ func Parse() *Settings { flagSet.BoolVar(&settings.Shodan, "shodan", false, "Enable Shodan lookup (requires SHODAN_API_KEY env var)"), flagSet.BoolVar(&settings.SQL, "sql", false, "Enable SQL reconnaissance (admin panels, error disclosure)"), flagSet.BoolVar(&settings.LFI, "lfi", false, "Enable LFI (Local File Inclusion) reconnaissance"), + flagSet.BoolVar(&settings.Framework, "framework", false, "Enable framework detection"), ) flagSet.CreateGroup("runtime", "Runtime", diff --git a/pkg/scan/frameworks/detect.go b/pkg/scan/frameworks/detect.go new file mode 100644 index 0000000..c96638a --- /dev/null +++ b/pkg/scan/frameworks/detect.go @@ -0,0 +1,179 @@ +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"` +} + +var frameworkSignatures = map[string][]string{ + "Laravel": { + `laravel_session`, + `XSRF-TOKEN`, + ` 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) + + // Add CVEs and suggestions based on version + 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 { + patterns := map[string]*regexp.Regexp{ + "Laravel": regexp.MustCompile(`Laravel[/\s+]?([\d.]+)`), + "Django": regexp.MustCompile(`Django/([\d.]+)`), + "Ruby on Rails": regexp.MustCompile(`Rails/([\d.]+)`), + "Express.js": regexp.MustCompile(`express/([\d.]+)`), + "ASP.NET": regexp.MustCompile(`ASP\.NET[/\s+]?([\d.]+)`), + "Spring": regexp.MustCompile(`spring-(core|framework)/([\d.]+)`), + "Flask": regexp.MustCompile(`Flask/([\d.]+)`), + } + + if pattern, exists := patterns[framework]; exists { + matches := pattern.FindStringSubmatch(body) + if len(matches) > 1 { + return matches[1] + } + } + return "Unknown" +} + +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 +} diff --git a/sif.go b/sif.go index 628e0e4..cc13467 100644 --- a/sif.go +++ b/sif.go @@ -28,6 +28,7 @@ import ( "github.com/dropalldatabases/sif/pkg/config" "github.com/dropalldatabases/sif/pkg/logger" "github.com/dropalldatabases/sif/pkg/scan" + "github.com/dropalldatabases/sif/pkg/scan/frameworks" jsscan "github.com/dropalldatabases/sif/pkg/scan/js" ) @@ -125,6 +126,16 @@ func (app *App) Run() error { scansRun = append(scansRun, "Basic Scan") } + if app.settings.Framework { + result, err := frameworks.DetectFramework(url, app.settings.Timeout, app.settings.LogDir) + if err != nil { + log.Errorf("Error while running framework detection: %s", err) + } else if result != nil { + moduleResults = append(moduleResults, ModuleResult{"framework", result}) + scansRun = append(scansRun, "Framework Detection") + } + } + if app.settings.Dirlist != "none" { result, err := scan.Dirlist(app.settings.Dirlist, url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir) if err != nil {