Skip to content

Commit 0e16eb3

Browse files
committed
feat: improve framework detection with more signatures and tests
- use math.Exp instead of custom exp implementation - add more framework signatures: next.js, nuxt.js, wordpress, drupal, symfony, fastapi, gin, phoenix - fix header detection to check both header names and values - simplify version detection (remove unnecessary padding) - add comprehensive test suite for framework detection - fix formatting in dork.go
1 parent ebc9b99 commit 0e16eb3

File tree

3 files changed

+365
-43
lines changed

3 files changed

+365
-43
lines changed

pkg/scan/dork.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
109109

110110
for i, dork := range dorks {
111111

112-
113112
if i%threads != thread {
114113
continue
115114
}

pkg/scan/frameworks/detect.go

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package frameworks
33
import (
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

70118
func 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

151199
func 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

162220
func 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

193224
func getVulnerabilities(framework, version string) ([]string, []string) {
@@ -205,13 +236,21 @@ func getVulnerabilities(framework, version string) ([]string, []string) {
205236

206237
func 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

Comments
 (0)