Skip to content

Commit 1443721

Browse files
committed
feat: framework detection module
1 parent 9636888 commit 1443721

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed

pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Settings struct {
5353
Headers bool
5454
CloudStorage bool
5555
SubdomainTakeover bool
56+
Framework bool
5657
}
5758

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

100102
flagSet.CreateGroup("runtime", "Runtime",

pkg/scan/frameworks/detect.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package frameworks
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"os"
8+
"regexp"
9+
"strings"
10+
"time"
11+
12+
"github.com/charmbracelet/log"
13+
"github.com/dropalldatabases/sif/internal/styles"
14+
"github.com/dropalldatabases/sif/pkg/logger"
15+
)
16+
17+
type FrameworkResult struct {
18+
Name string `json:"name"`
19+
Version string `json:"version"`
20+
Confidence float32 `json:"confidence"`
21+
CVEs []string `json:"cves,omitempty"`
22+
Suggestions []string `json:"suggestions,omitempty"`
23+
}
24+
25+
var frameworkSignatures = map[string][]string{
26+
"Laravel": {
27+
`laravel_session`,
28+
`XSRF-TOKEN`,
29+
`<meta name="csrf-token"`,
30+
},
31+
"Django": {
32+
`csrfmiddlewaretoken`,
33+
`django.contrib`,
34+
`django.core`,
35+
`__admin_media_prefix__`,
36+
},
37+
"Ruby on Rails": {
38+
`csrf-param`,
39+
`csrf-token`,
40+
`ruby-on-rails`,
41+
`rails-env`,
42+
},
43+
"Express.js": {
44+
`express`,
45+
`connect.sid`,
46+
},
47+
"ASP.NET": {
48+
`ASP.NET`,
49+
`__VIEWSTATE`,
50+
`__EVENTVALIDATION`,
51+
},
52+
"Spring": {
53+
`org.springframework`,
54+
`spring-security`,
55+
`jsessionid`,
56+
},
57+
"Flask": {
58+
`flask`,
59+
`werkzeug`,
60+
`jinja2`,
61+
},
62+
}
63+
64+
func DetectFramework(url string, timeout time.Duration, logdir string) (*FrameworkResult, error) {
65+
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Framework Detection") + "..."))
66+
67+
frameworklog := log.NewWithOptions(os.Stderr, log.Options{
68+
Prefix: "Framework Detection 🔍",
69+
}).With("url", url)
70+
71+
client := &http.Client{
72+
Timeout: timeout,
73+
}
74+
75+
resp, err := client.Get(url)
76+
if err != nil {
77+
return nil, err
78+
}
79+
defer resp.Body.Close()
80+
81+
body, err := io.ReadAll(resp.Body)
82+
if err != nil {
83+
return nil, err
84+
}
85+
bodyStr := string(body)
86+
87+
var bestMatch string
88+
var highestConfidence float32
89+
90+
for framework, signatures := range frameworkSignatures {
91+
var matches int
92+
for _, sig := range signatures {
93+
if strings.Contains(bodyStr, sig) || containsHeader(resp.Header, sig) {
94+
matches++
95+
}
96+
}
97+
98+
confidence := float32(matches) / float32(len(signatures))
99+
if confidence > highestConfidence {
100+
highestConfidence = confidence
101+
bestMatch = framework
102+
}
103+
}
104+
105+
if highestConfidence > 0 {
106+
version := detectVersion(bodyStr, bestMatch)
107+
result := &FrameworkResult{
108+
Name: bestMatch,
109+
Version: version,
110+
Confidence: highestConfidence,
111+
}
112+
113+
if logdir != "" {
114+
logger.Write(url, logdir, fmt.Sprintf("Detected framework: %s (version: %s, confidence: %.2f)\n",
115+
bestMatch, version, highestConfidence))
116+
}
117+
118+
frameworklog.Infof("Detected %s framework (version: %s) with %.2f confidence",
119+
styles.Highlight.Render(bestMatch), version, highestConfidence)
120+
121+
// Add CVEs and suggestions based on version
122+
if cves, suggestions := getVulnerabilities(bestMatch, version); len(cves) > 0 {
123+
result.CVEs = cves
124+
result.Suggestions = suggestions
125+
for _, cve := range cves {
126+
frameworklog.Warnf("Found potential vulnerability: %s", styles.Highlight.Render(cve))
127+
}
128+
}
129+
130+
return result, nil
131+
}
132+
133+
frameworklog.Info("No framework detected")
134+
return nil, nil
135+
}
136+
137+
func containsHeader(headers http.Header, signature string) bool {
138+
for _, values := range headers {
139+
for _, value := range values {
140+
if strings.Contains(strings.ToLower(value), strings.ToLower(signature)) {
141+
return true
142+
}
143+
}
144+
}
145+
return false
146+
}
147+
148+
func detectVersion(body string, framework string) string {
149+
patterns := map[string]*regexp.Regexp{
150+
"Laravel": regexp.MustCompile(`Laravel[/\s+]?([\d.]+)`),
151+
"Django": regexp.MustCompile(`Django/([\d.]+)`),
152+
"Ruby on Rails": regexp.MustCompile(`Rails/([\d.]+)`),
153+
"Express.js": regexp.MustCompile(`express/([\d.]+)`),
154+
"ASP.NET": regexp.MustCompile(`ASP\.NET[/\s+]?([\d.]+)`),
155+
"Spring": regexp.MustCompile(`spring-(core|framework)/([\d.]+)`),
156+
"Flask": regexp.MustCompile(`Flask/([\d.]+)`),
157+
}
158+
159+
if pattern, exists := patterns[framework]; exists {
160+
matches := pattern.FindStringSubmatch(body)
161+
if len(matches) > 1 {
162+
return matches[1]
163+
}
164+
}
165+
return "Unknown"
166+
}
167+
168+
func getVulnerabilities(framework, version string) ([]string, []string) {
169+
// TODO: Implement CVE database lookup
170+
if framework == "Laravel" && version == "8.0.0" {
171+
return []string{
172+
"CVE-2021-3129",
173+
}, []string{
174+
"Update to Laravel 8.4.2 or later",
175+
"Implement additional input validation",
176+
}
177+
}
178+
return nil, nil
179+
}

sif.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/dropalldatabases/sif/pkg/config"
1717
"github.com/dropalldatabases/sif/pkg/logger"
1818
"github.com/dropalldatabases/sif/pkg/scan"
19+
"github.com/dropalldatabases/sif/pkg/scan/frameworks"
1920
jsscan "github.com/dropalldatabases/sif/pkg/scan/js"
2021
)
2122

@@ -113,6 +114,16 @@ func (app *App) Run() error {
113114
scansRun = append(scansRun, "Basic Scan")
114115
}
115116

117+
if app.settings.Framework {
118+
result, err := frameworks.DetectFramework(url, app.settings.Timeout, app.settings.LogDir)
119+
if err != nil {
120+
log.Errorf("Error while running framework detection: %s", err)
121+
} else if result != nil {
122+
moduleResults = append(moduleResults, ModuleResult{"framework", result})
123+
scansRun = append(scansRun, "Framework Detection")
124+
}
125+
}
126+
116127
if app.settings.Dirlist != "none" {
117128
result, err := scan.Dirlist(app.settings.Dirlist, url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
118129
if err != nil {

0 commit comments

Comments
 (0)