Skip to content

Commit

Permalink
feat: framework detection module
Browse files Browse the repository at this point in the history
  • Loading branch information
vmfunc committed Nov 22, 2024
1 parent 9636888 commit 1443721
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Settings struct {
Headers bool
CloudStorage bool
SubdomainTakeover bool
Framework bool
}

const (
Expand Down Expand Up @@ -95,6 +96,7 @@ func Parse() *Settings {
flagSet.BoolVar(&settings.Headers, "headers", false, "Enable HTTP Header Analysis"),
flagSet.BoolVar(&settings.CloudStorage, "c3", false, "Enable C3 Misconfiguration Scan"),
flagSet.BoolVar(&settings.SubdomainTakeover, "st", false, "Enable Subdomain Takeover Check"),
flagSet.BoolVar(&settings.Framework, "framework", false, "Enable framework detection"),
)

flagSet.CreateGroup("runtime", "Runtime",
Expand Down
179 changes: 179 additions & 0 deletions pkg/scan/frameworks/detect.go
Original file line number Diff line number Diff line change
@@ -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`,
`<meta name="csrf-token"`,
},
"Django": {
`csrfmiddlewaretoken`,
`django.contrib`,
`django.core`,
`__admin_media_prefix__`,
},
"Ruby on Rails": {
`csrf-param`,
`csrf-token`,
`ruby-on-rails`,
`rails-env`,
},
"Express.js": {
`express`,
`connect.sid`,
},
"ASP.NET": {
`ASP.NET`,
`__VIEWSTATE`,
`__EVENTVALIDATION`,
},
"Spring": {
`org.springframework`,
`spring-security`,
`jsessionid`,
},
"Flask": {
`flask`,
`werkzeug`,
`jinja2`,
},
}

func DetectFramework(url string, timeout time.Duration, logdir string) (*FrameworkResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Framework Detection") + "..."))

frameworklog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Framework Detection 🔍",
}).With("url", url)

client := &http.Client{
Timeout: timeout,
}

resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
bodyStr := string(body)

var bestMatch string
var highestConfidence float32

for framework, signatures := range frameworkSignatures {
var matches int
for _, sig := range signatures {
if strings.Contains(bodyStr, sig) || containsHeader(resp.Header, sig) {
matches++
}
}

confidence := float32(matches) / float32(len(signatures))
if confidence > 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",

Check failure on line 114 in pkg/scan/frameworks/detect.go

View workflow job for this annotation

GitHub Actions / runner / errcheck

[errcheck] reported by reviewdog 🐶 Error return value of `logger.Write` is not checked (errcheck) Raw Output: pkg/scan/frameworks/detect.go:114:16: Error return value of `logger.Write` is not checked (errcheck) 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
}
11 changes: 11 additions & 0 deletions sif.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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"
)

Expand Down Expand Up @@ -113,6 +114,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 {
Expand Down

0 comments on commit 1443721

Please sign in to comment.