@@ -22,42 +22,48 @@ type FrameworkResult struct {
2222 Suggestions []string `json:"suggestions,omitempty"`
2323}
2424
25- var frameworkSignatures = map [string ][]string {
25+ type FrameworkSignature struct {
26+ Pattern string
27+ Weight float32
28+ HeaderOnly bool
29+ }
30+
31+ var frameworkSignatures = map [string ][]FrameworkSignature {
2632 "Laravel" : {
27- `laravel_session` ,
28- `XSRF-TOKEN` ,
29- `<meta name="csrf-token"` ,
33+ { Pattern : `laravel_session` , Weight : 0.4 , HeaderOnly : true } ,
34+ { Pattern : `XSRF-TOKEN` , Weight : 0.3 , HeaderOnly : true } ,
35+ { Pattern : `<meta name="csrf-token"` , Weight : 0.3 } ,
3036 },
3137 "Django" : {
32- `csrfmiddlewaretoken` ,
33- `django.contrib` ,
34- `django.core` ,
35- `__admin_media_prefix__` ,
38+ { Pattern : `csrfmiddlewaretoken` , Weight : 0.4 , HeaderOnly : true } ,
39+ { Pattern : `django.contrib` , Weight : 0.3 } ,
40+ { Pattern : `django.core` , Weight : 0.3 } ,
41+ { Pattern : `__admin_media_prefix__` , Weight : 0.3 } ,
3642 },
3743 "Ruby on Rails" : {
38- `csrf-param` ,
39- `csrf-token` ,
40- `ruby-on-rails` ,
41- `rails-env` ,
44+ { Pattern : `csrf-param` , Weight : 0.4 , HeaderOnly : true } ,
45+ { Pattern : `csrf-token` , Weight : 0.3 , HeaderOnly : true } ,
46+ { Pattern : `ruby-on-rails` , Weight : 0.3 } ,
47+ { Pattern : `rails-env` , Weight : 0.3 } ,
4248 },
4349 "Express.js" : {
44- `express` ,
45- `connect.sid` ,
50+ { Pattern : `express` , Weight : 0.4 , HeaderOnly : true } ,
51+ { Pattern : `connect.sid` , Weight : 0.3 , HeaderOnly : true } ,
4652 },
4753 "ASP.NET" : {
48- `ASP.NET` ,
49- `__VIEWSTATE` ,
50- `__EVENTVALIDATION` ,
54+ { Pattern : `ASP.NET` , Weight : 0.4 , HeaderOnly : true } ,
55+ { Pattern : `__VIEWSTATE` , Weight : 0.3 } ,
56+ { Pattern : `__EVENTVALIDATION` , Weight : 0.3 } ,
5157 },
5258 "Spring" : {
53- `org.springframework` ,
54- `spring-security` ,
55- `jsessionid` ,
59+ { Pattern : `org.springframework` , Weight : 0.4 , HeaderOnly : true } ,
60+ { Pattern : `spring-security` , Weight : 0.3 , HeaderOnly : true } ,
61+ { Pattern : `jsessionid` , Weight : 0.3 , HeaderOnly : true } ,
5662 },
5763 "Flask" : {
58- `flask` ,
59- `werkzeug` ,
60- `jinja2` ,
64+ { Pattern : `flask` , Weight : 0.4 , HeaderOnly : true } ,
65+ { Pattern : `werkzeug` , Weight : 0.3 , HeaderOnly : true } ,
66+ { Pattern : `jinja2` , Weight : 0.3 } ,
6167 },
6268}
6369
@@ -88,14 +94,23 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
8894 var highestConfidence float32
8995
9096 for framework , signatures := range frameworkSignatures {
91- var matches int
97+ var weightedScore float32
98+ var totalWeight float32
99+
92100 for _ , sig := range signatures {
93- if strings .Contains (bodyStr , sig ) || containsHeader (resp .Header , sig ) {
94- matches ++
101+ totalWeight += sig .Weight
102+
103+ if sig .HeaderOnly {
104+ if containsHeader (resp .Header , sig .Pattern ) {
105+ weightedScore += sig .Weight
106+ }
107+ } else if strings .Contains (bodyStr , sig .Pattern ) {
108+ weightedScore += sig .Weight
95109 }
96110 }
97111
98- confidence := float32 (matches ) / float32 (len (signatures ))
112+ confidence := float32 (1.0 / (1.0 + exp (- float64 (weightedScore / totalWeight )* 6.0 )))
113+
99114 if confidence > highestConfidence {
100115 highestConfidence = confidence
101116 bestMatch = framework
@@ -118,7 +133,6 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
118133 frameworklog .Infof ("Detected %s framework (version: %s) with %.2f confidence" ,
119134 styles .Highlight .Render (bestMatch ), version , highestConfidence )
120135
121- // Add CVEs and suggestions based on version
122136 if cves , suggestions := getVulnerabilities (bestMatch , version ); len (cves ) > 0 {
123137 result .CVEs = cves
124138 result .Suggestions = suggestions
@@ -146,23 +160,34 @@ func containsHeader(headers http.Header, signature string) bool {
146160}
147161
148162func 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.]+)` ),
163+ version := extractVersion (body , framework )
164+ if version == "Unknown" {
165+ return version
157166 }
158167
159- if pattern , exists := patterns [framework ]; exists {
160- matches := pattern .FindStringSubmatch (body )
161- if len (matches ) > 1 {
162- return matches [1 ]
163- }
168+ parts := strings .Split (version , "." )
169+ var normalized string
170+ if len (parts ) >= 3 {
171+ normalized = fmt .Sprintf ("%05s.%05s.%05s" , parts [0 ], parts [1 ], parts [2 ])
164172 }
165- return "Unknown"
173+ return normalized
174+ }
175+
176+ func exp (x float64 ) float64 {
177+ if x > 88.0 {
178+ return 1e38
179+ }
180+ if x < - 88.0 {
181+ return 0
182+ }
183+
184+ sum := 1.0
185+ term := 1.0
186+ for i := 1 ; i <= 20 ; i ++ {
187+ term *= x / float64 (i )
188+ sum += term
189+ }
190+ return sum
166191}
167192
168193func getVulnerabilities (framework , version string ) ([]string , []string ) {
@@ -177,3 +202,24 @@ func getVulnerabilities(framework, version string) ([]string, []string) {
177202 }
178203 return nil , nil
179204}
205+
206+ func extractVersion (body string , framework string ) string {
207+ versionPatterns := map [string ]string {
208+ "Laravel" : `Laravel\s+[Vv]?(\d+\.\d+\.\d+)` ,
209+ "Django" : `Django\s+[Vv]?(\d+\.\d+\.\d+)` ,
210+ "Ruby on Rails" : `Rails\s+[Vv]?(\d+\.\d+\.\d+)` ,
211+ "Express.js" : `Express\s+[Vv]?(\d+\.\d+\.\d+)` ,
212+ "ASP.NET" : `ASP\.NET\s+[Vv]?(\d+\.\d+\.\d+)` ,
213+ "Spring" : `Spring\s+[Vv]?(\d+\.\d+\.\d+)` ,
214+ "Flask" : `Flask\s+[Vv]?(\d+\.\d+\.\d+)` ,
215+ }
216+
217+ if pattern , exists := versionPatterns [framework ]; exists {
218+ re := regexp .MustCompile (pattern )
219+ matches := re .FindStringSubmatch (body )
220+ if len (matches ) > 1 {
221+ return matches [1 ]
222+ }
223+ }
224+ return "Unknown"
225+ }
0 commit comments