@@ -3,6 +3,7 @@ package frameworks
33import (
44 "fmt"
55 "io"
6+ "math"
67 "net/http"
78 "os"
89 "regexp"
@@ -36,35 +37,82 @@ var frameworkSignatures = map[string][]FrameworkSignature{
3637 },
3738 "Django" : {
3839 {Pattern : `csrfmiddlewaretoken` , Weight : 0.4 , HeaderOnly : true },
40+ {Pattern : `csrftoken` , Weight : 0.3 , HeaderOnly : true },
3941 {Pattern : `django.contrib` , Weight : 0.3 },
4042 {Pattern : `django.core` , Weight : 0.3 },
4143 {Pattern : `__admin_media_prefix__` , Weight : 0.3 },
4244 },
4345 "Ruby on Rails" : {
4446 {Pattern : `csrf-param` , Weight : 0.4 , HeaderOnly : true },
4547 {Pattern : `csrf-token` , Weight : 0.3 , HeaderOnly : true },
48+ {Pattern : `_rails_session` , Weight : 0.3 , HeaderOnly : true },
4649 {Pattern : `ruby-on-rails` , Weight : 0.3 },
4750 {Pattern : `rails-env` , Weight : 0.3 },
51+ {Pattern : `data-turbo` , Weight : 0.2 },
4852 },
4953 "Express.js" : {
50- {Pattern : `express ` , Weight : 0.4 , HeaderOnly : true },
54+ {Pattern : `Express ` , Weight : 0.5 , HeaderOnly : true },
5155 {Pattern : `connect.sid` , Weight : 0.3 , HeaderOnly : true },
5256 },
5357 "ASP.NET" : {
58+ {Pattern : `X-AspNet-Version` , Weight : 0.5 , HeaderOnly : true },
5459 {Pattern : `ASP.NET` , Weight : 0.4 , HeaderOnly : true },
5560 {Pattern : `__VIEWSTATE` , Weight : 0.3 },
5661 {Pattern : `__EVENTVALIDATION` , Weight : 0.3 },
62+ {Pattern : `.aspx` , Weight : 0.2 },
5763 },
5864 "Spring" : {
5965 {Pattern : `org.springframework` , Weight : 0.4 , HeaderOnly : true },
6066 {Pattern : `spring-security` , Weight : 0.3 , HeaderOnly : true },
61- {Pattern : `jsessionid` , Weight : 0.3 , HeaderOnly : true },
67+ {Pattern : `JSESSIONID` , Weight : 0.3 , HeaderOnly : true },
68+ {Pattern : `X-Application-Context` , Weight : 0.3 , HeaderOnly : true },
6269 },
6370 "Flask" : {
64- {Pattern : `flask ` , Weight : 0.4 , HeaderOnly : true },
65- {Pattern : `werkzeug ` , Weight : 0.3 , HeaderOnly : true },
71+ {Pattern : `Werkzeug ` , Weight : 0.4 , HeaderOnly : true },
72+ {Pattern : `flask ` , Weight : 0.3 , HeaderOnly : true },
6673 {Pattern : `jinja2` , Weight : 0.3 },
6774 },
75+ "Next.js" : {
76+ {Pattern : `__NEXT_DATA__` , Weight : 0.5 },
77+ {Pattern : `_next/static` , Weight : 0.4 },
78+ {Pattern : `__next` , Weight : 0.3 },
79+ {Pattern : `x-nextjs` , Weight : 0.3 , HeaderOnly : true },
80+ },
81+ "Nuxt.js" : {
82+ {Pattern : `__NUXT__` , Weight : 0.5 },
83+ {Pattern : `_nuxt/` , Weight : 0.4 },
84+ {Pattern : `nuxt` , Weight : 0.2 },
85+ },
86+ "WordPress" : {
87+ {Pattern : `wp-content` , Weight : 0.4 },
88+ {Pattern : `wp-includes` , Weight : 0.4 },
89+ {Pattern : `wp-json` , Weight : 0.3 },
90+ {Pattern : `wordpress` , Weight : 0.3 },
91+ },
92+ "Drupal" : {
93+ {Pattern : `Drupal` , Weight : 0.4 , HeaderOnly : true },
94+ {Pattern : `drupal.js` , Weight : 0.4 },
95+ {Pattern : `/sites/default/files` , Weight : 0.3 },
96+ {Pattern : `Drupal.settings` , Weight : 0.3 },
97+ },
98+ "Symfony" : {
99+ {Pattern : `symfony` , Weight : 0.4 , HeaderOnly : true },
100+ {Pattern : `sf_` , Weight : 0.3 , HeaderOnly : true },
101+ {Pattern : `_sf2_` , Weight : 0.3 , HeaderOnly : true },
102+ },
103+ "FastAPI" : {
104+ {Pattern : `fastapi` , Weight : 0.4 , HeaderOnly : true },
105+ {Pattern : `starlette` , Weight : 0.3 , HeaderOnly : true },
106+ },
107+ "Gin" : {
108+ {Pattern : `gin-gonic` , Weight : 0.4 },
109+ {Pattern : `gin` , Weight : 0.2 , HeaderOnly : true },
110+ },
111+ "Phoenix" : {
112+ {Pattern : `_csrf_token` , Weight : 0.4 , HeaderOnly : true },
113+ {Pattern : `phx-` , Weight : 0.3 },
114+ {Pattern : `phoenix` , Weight : 0.2 },
115+ },
68116}
69117
70118func DetectFramework (url string , timeout time.Duration , logdir string ) (* FrameworkResult , error ) {
@@ -109,7 +157,7 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
109157 }
110158 }
111159
112- confidence := float32 (1.0 / (1.0 + exp (- float64 (weightedScore / totalWeight )* 6.0 )))
160+ confidence := float32 (1.0 / (1.0 + math . Exp (- float64 (weightedScore / totalWeight )* 6.0 )))
113161
114162 if confidence > highestConfidence {
115163 highestConfidence = confidence
@@ -149,9 +197,19 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
149197}
150198
151199func containsHeader (headers http.Header , signature string ) bool {
200+ sigLower := strings .ToLower (signature )
201+
202+ // check header names
203+ for name := range headers {
204+ if strings .Contains (strings .ToLower (name ), sigLower ) {
205+ return true
206+ }
207+ }
208+
209+ // check header values
152210 for _ , values := range headers {
153211 for _ , value := range values {
154- if strings .Contains (strings .ToLower (value ), strings . ToLower ( signature ) ) {
212+ if strings .Contains (strings .ToLower (value ), sigLower ) {
155213 return true
156214 }
157215 }
@@ -160,34 +218,7 @@ func containsHeader(headers http.Header, signature string) bool {
160218}
161219
162220func detectVersion (body string , framework string ) string {
163- version := extractVersion (body , framework )
164- if version == "Unknown" {
165- return version
166- }
167-
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 ])
172- }
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
221+ return extractVersion (body , framework )
191222}
192223
193224func getVulnerabilities (framework , version string ) ([]string , []string ) {
@@ -205,13 +236,21 @@ func getVulnerabilities(framework, version string) ([]string, []string) {
205236
206237func extractVersion (body string , framework string ) string {
207238 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+)` ,
239+ "Laravel" : `Laravel\s+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
240+ "Django" : `Django[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
241+ "Ruby on Rails" : `Rails[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
242+ "Express.js" : `Express[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
243+ "ASP.NET" : `ASP\.NET[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
244+ "Spring" : `Spring[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
245+ "Flask" : `Flask[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
246+ "Next.js" : `Next\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
247+ "Nuxt.js" : `Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
248+ "WordPress" : `WordPress[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
249+ "Drupal" : `Drupal[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
250+ "Symfony" : `Symfony[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
251+ "FastAPI" : `FastAPI[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
252+ "Gin" : `Gin[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
253+ "Phoenix" : `Phoenix[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)` ,
215254 }
216255
217256 if pattern , exists := versionPatterns [framework ]; exists {
@@ -221,5 +260,5 @@ func extractVersion(body string, framework string) string {
221260 return matches [1 ]
222261 }
223262 }
224- return "Unknown "
263+ return "unknown "
225264}
0 commit comments