mirror of
https://github.com/lunchcat/sif.git
synced 2026-01-14 05:46:33 -08:00
- add 20+ new framework signatures (vue, angular, react, svelte, sveltekit, remix, gatsby, joomla, magento, shopify, ghost, ember, backbone, meteor, strapi, adonisjs, cakephp, codeigniter, asp.net core, spring boot) - add version confidence scoring with multiple detection sources - add concurrent framework scanning for better performance - expand cve database with 15+ known vulnerabilities (spring4shell, etc.) - add risk level assessment based on cve severity - add comprehensive security recommendations - add new tests for all features
766 lines
24 KiB
Go
766 lines
24 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2025 vmfunc (Celeste Hickenlooper), xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package frameworks
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"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"`
|
|
VersionConfidence float32 `json:"version_confidence"`
|
|
CVEs []string `json:"cves,omitempty"`
|
|
Suggestions []string `json:"suggestions,omitempty"`
|
|
RiskLevel string `json:"risk_level,omitempty"`
|
|
}
|
|
|
|
type FrameworkSignature struct {
|
|
Pattern string
|
|
Weight float32
|
|
HeaderOnly bool
|
|
}
|
|
|
|
var frameworkSignatures = map[string][]FrameworkSignature{
|
|
"Laravel": {
|
|
{Pattern: `laravel_session`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `XSRF-TOKEN`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `<meta name="csrf-token"`, Weight: 0.3},
|
|
},
|
|
"Django": {
|
|
{Pattern: `csrfmiddlewaretoken`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `csrftoken`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `django.contrib`, Weight: 0.3},
|
|
{Pattern: `django.core`, Weight: 0.3},
|
|
{Pattern: `__admin_media_prefix__`, Weight: 0.3},
|
|
},
|
|
"Ruby on Rails": {
|
|
{Pattern: `csrf-param`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `csrf-token`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `_rails_session`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `ruby-on-rails`, Weight: 0.3},
|
|
{Pattern: `rails-env`, Weight: 0.3},
|
|
{Pattern: `data-turbo`, Weight: 0.2},
|
|
},
|
|
"Express.js": {
|
|
{Pattern: `Express`, Weight: 0.5, HeaderOnly: true},
|
|
{Pattern: `connect.sid`, Weight: 0.3, HeaderOnly: true},
|
|
},
|
|
"ASP.NET": {
|
|
{Pattern: `X-AspNet-Version`, Weight: 0.5, HeaderOnly: true},
|
|
{Pattern: `X-AspNetMvc-Version`, Weight: 0.5, HeaderOnly: true},
|
|
{Pattern: `ASP.NET`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `__VIEWSTATE`, Weight: 0.4},
|
|
{Pattern: `__EVENTVALIDATION`, Weight: 0.3},
|
|
{Pattern: `__VIEWSTATEGENERATOR`, Weight: 0.3},
|
|
{Pattern: `.aspx`, Weight: 0.2},
|
|
{Pattern: `.ashx`, Weight: 0.2},
|
|
{Pattern: `.asmx`, Weight: 0.2},
|
|
{Pattern: `asp.net_sessionid`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `X-Powered-By: ASP.NET`, Weight: 0.4, HeaderOnly: true},
|
|
},
|
|
"ASP.NET Core": {
|
|
{Pattern: `.AspNetCore.`, Weight: 0.5, HeaderOnly: true},
|
|
{Pattern: `blazor`, Weight: 0.4},
|
|
{Pattern: `_blazor`, Weight: 0.4},
|
|
{Pattern: `dotnet`, Weight: 0.2, HeaderOnly: true},
|
|
},
|
|
"Spring": {
|
|
{Pattern: `org.springframework`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `spring-security`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `JSESSIONID`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `X-Application-Context`, Weight: 0.3, HeaderOnly: true},
|
|
},
|
|
"Spring Boot": {
|
|
{Pattern: `spring-boot`, Weight: 0.5},
|
|
{Pattern: `actuator`, Weight: 0.3},
|
|
{Pattern: `whitelabel`, Weight: 0.2},
|
|
},
|
|
"Flask": {
|
|
{Pattern: `Werkzeug`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `flask`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `jinja2`, Weight: 0.3},
|
|
},
|
|
"Next.js": {
|
|
{Pattern: `__NEXT_DATA__`, Weight: 0.5},
|
|
{Pattern: `_next/static`, Weight: 0.4},
|
|
{Pattern: `__next`, Weight: 0.3},
|
|
{Pattern: `x-nextjs`, Weight: 0.3, HeaderOnly: true},
|
|
},
|
|
"Nuxt.js": {
|
|
{Pattern: `__NUXT__`, Weight: 0.5},
|
|
{Pattern: `_nuxt/`, Weight: 0.4},
|
|
{Pattern: `nuxt`, Weight: 0.2},
|
|
},
|
|
"Vue.js": {
|
|
{Pattern: `data-v-`, Weight: 0.5},
|
|
{Pattern: `Vue.js`, Weight: 0.4},
|
|
{Pattern: `vue.runtime`, Weight: 0.4},
|
|
{Pattern: `vue.min.js`, Weight: 0.4},
|
|
{Pattern: `__vue__`, Weight: 0.3},
|
|
{Pattern: `v-cloak`, Weight: 0.3},
|
|
},
|
|
"Angular": {
|
|
{Pattern: `ng-version`, Weight: 0.5},
|
|
{Pattern: `ng-app`, Weight: 0.4},
|
|
{Pattern: `ng-controller`, Weight: 0.4},
|
|
{Pattern: `angular.js`, Weight: 0.4},
|
|
{Pattern: `angular.min.js`, Weight: 0.4},
|
|
{Pattern: `ng-binding`, Weight: 0.3},
|
|
{Pattern: `_nghost`, Weight: 0.3},
|
|
{Pattern: `_ngcontent`, Weight: 0.3},
|
|
},
|
|
"React": {
|
|
{Pattern: `data-reactroot`, Weight: 0.5},
|
|
{Pattern: `react-dom`, Weight: 0.4},
|
|
{Pattern: `__REACT_DEVTOOLS`, Weight: 0.4},
|
|
{Pattern: `react.production`, Weight: 0.4},
|
|
{Pattern: `_reactRootContainer`, Weight: 0.3},
|
|
},
|
|
"Svelte": {
|
|
{Pattern: `svelte`, Weight: 0.4},
|
|
{Pattern: `__svelte`, Weight: 0.5},
|
|
{Pattern: `svelte-`, Weight: 0.3},
|
|
},
|
|
"SvelteKit": {
|
|
{Pattern: `__sveltekit`, Weight: 0.5},
|
|
{Pattern: `_app/immutable`, Weight: 0.4},
|
|
{Pattern: `sveltekit`, Weight: 0.3},
|
|
},
|
|
"Remix": {
|
|
{Pattern: `__remixContext`, Weight: 0.5},
|
|
{Pattern: `remix`, Weight: 0.3},
|
|
{Pattern: `_remix`, Weight: 0.4},
|
|
},
|
|
"Gatsby": {
|
|
{Pattern: `___gatsby`, Weight: 0.5},
|
|
{Pattern: `gatsby-`, Weight: 0.4},
|
|
{Pattern: `page-data.json`, Weight: 0.3},
|
|
},
|
|
"WordPress": {
|
|
{Pattern: `wp-content`, Weight: 0.4},
|
|
{Pattern: `wp-includes`, Weight: 0.4},
|
|
{Pattern: `wp-json`, Weight: 0.3},
|
|
{Pattern: `wordpress`, Weight: 0.3},
|
|
{Pattern: `wp-emoji`, Weight: 0.2},
|
|
},
|
|
"Drupal": {
|
|
{Pattern: `Drupal`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `drupal.js`, Weight: 0.4},
|
|
{Pattern: `/sites/default/files`, Weight: 0.3},
|
|
{Pattern: `Drupal.settings`, Weight: 0.3},
|
|
},
|
|
"Joomla": {
|
|
{Pattern: `Joomla`, Weight: 0.4},
|
|
{Pattern: `/media/jui/`, Weight: 0.4},
|
|
{Pattern: `/components/com_`, Weight: 0.3},
|
|
{Pattern: `joomla.javascript`, Weight: 0.3},
|
|
},
|
|
"Magento": {
|
|
{Pattern: `Magento`, Weight: 0.4},
|
|
{Pattern: `/static/frontend/`, Weight: 0.4},
|
|
{Pattern: `mage/`, Weight: 0.3},
|
|
{Pattern: `Mage.Cookies`, Weight: 0.3},
|
|
},
|
|
"Shopify": {
|
|
{Pattern: `Shopify`, Weight: 0.5},
|
|
{Pattern: `cdn.shopify.com`, Weight: 0.4},
|
|
{Pattern: `shopify-section`, Weight: 0.4},
|
|
{Pattern: `myshopify.com`, Weight: 0.3},
|
|
},
|
|
"Ghost": {
|
|
{Pattern: `ghost-`, Weight: 0.4},
|
|
{Pattern: `Ghost`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `/ghost/api/`, Weight: 0.4},
|
|
},
|
|
"Symfony": {
|
|
{Pattern: `symfony`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `sf_`, Weight: 0.3, HeaderOnly: true},
|
|
{Pattern: `_sf2_`, Weight: 0.3, HeaderOnly: true},
|
|
},
|
|
"FastAPI": {
|
|
{Pattern: `fastapi`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `starlette`, Weight: 0.3, HeaderOnly: true},
|
|
},
|
|
"Gin": {
|
|
{Pattern: `gin-gonic`, Weight: 0.4},
|
|
{Pattern: `gin`, Weight: 0.2, HeaderOnly: true},
|
|
},
|
|
"Phoenix": {
|
|
{Pattern: `_csrf_token`, Weight: 0.4, HeaderOnly: true},
|
|
{Pattern: `phx-`, Weight: 0.3},
|
|
{Pattern: `phoenix`, Weight: 0.2},
|
|
},
|
|
"Ember.js": {
|
|
{Pattern: `ember`, Weight: 0.4},
|
|
{Pattern: `ember-cli`, Weight: 0.4},
|
|
{Pattern: `data-ember`, Weight: 0.3},
|
|
},
|
|
"Backbone.js": {
|
|
{Pattern: `backbone`, Weight: 0.4},
|
|
{Pattern: `Backbone.`, Weight: 0.4},
|
|
},
|
|
"Meteor": {
|
|
{Pattern: `__meteor_runtime_config__`, Weight: 0.5},
|
|
{Pattern: `meteor`, Weight: 0.3},
|
|
},
|
|
"Strapi": {
|
|
{Pattern: `strapi`, Weight: 0.4},
|
|
{Pattern: `/api/`, Weight: 0.2},
|
|
},
|
|
"AdonisJS": {
|
|
{Pattern: `adonis`, Weight: 0.4},
|
|
{Pattern: `_csrf`, Weight: 0.2, HeaderOnly: true},
|
|
},
|
|
"CakePHP": {
|
|
{Pattern: `cakephp`, Weight: 0.4},
|
|
{Pattern: `cake`, Weight: 0.2},
|
|
},
|
|
"CodeIgniter": {
|
|
{Pattern: `codeigniter`, Weight: 0.4},
|
|
{Pattern: `ci_session`, Weight: 0.4, HeaderOnly: true},
|
|
},
|
|
}
|
|
|
|
// frameworkMatch holds the result of checking a single framework
|
|
type frameworkMatch struct {
|
|
framework string
|
|
confidence float32
|
|
}
|
|
|
|
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)
|
|
|
|
// concurrent framework detection
|
|
results := make(chan frameworkMatch, len(frameworkSignatures))
|
|
var wg sync.WaitGroup
|
|
|
|
for framework, signatures := range frameworkSignatures {
|
|
wg.Add(1)
|
|
go func(fw string, sigs []FrameworkSignature) {
|
|
defer wg.Done()
|
|
|
|
var weightedScore float32
|
|
var totalWeight float32
|
|
|
|
for _, sig := range sigs {
|
|
totalWeight += sig.Weight
|
|
|
|
if sig.HeaderOnly {
|
|
if containsHeader(resp.Header, sig.Pattern) {
|
|
weightedScore += sig.Weight
|
|
}
|
|
} else if strings.Contains(bodyStr, sig.Pattern) {
|
|
weightedScore += sig.Weight
|
|
}
|
|
}
|
|
|
|
confidence := float32(1.0 / (1.0 + math.Exp(-float64(weightedScore/totalWeight)*6.0)))
|
|
results <- frameworkMatch{framework: fw, confidence: confidence}
|
|
}(framework, signatures)
|
|
}
|
|
|
|
// close results channel when all goroutines complete
|
|
go func() {
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
// find the best match
|
|
var bestMatch string
|
|
var highestConfidence float32
|
|
|
|
for match := range results {
|
|
if match.confidence > highestConfidence {
|
|
highestConfidence = match.confidence
|
|
bestMatch = match.framework
|
|
}
|
|
}
|
|
|
|
if highestConfidence > 0.5 { // threshold for detection
|
|
versionMatch := extractVersionWithConfidence(bodyStr, bestMatch)
|
|
cves, suggestions := getVulnerabilities(bestMatch, versionMatch.Version)
|
|
|
|
result := &FrameworkResult{
|
|
Name: bestMatch,
|
|
Version: versionMatch.Version,
|
|
Confidence: highestConfidence,
|
|
VersionConfidence: versionMatch.Confidence,
|
|
CVEs: cves,
|
|
Suggestions: suggestions,
|
|
RiskLevel: getRiskLevel(cves),
|
|
}
|
|
|
|
if logdir != "" {
|
|
logEntry := fmt.Sprintf("Detected framework: %s (version: %s, confidence: %.2f, version_confidence: %.2f)\n",
|
|
bestMatch, versionMatch.Version, highestConfidence, versionMatch.Confidence)
|
|
if len(cves) > 0 {
|
|
logEntry += fmt.Sprintf(" Risk Level: %s\n", result.RiskLevel)
|
|
logEntry += fmt.Sprintf(" CVEs: %v\n", cves)
|
|
logEntry += fmt.Sprintf(" Recommendations: %v\n", suggestions)
|
|
}
|
|
logger.Write(url, logdir, logEntry)
|
|
}
|
|
|
|
frameworklog.Infof("Detected %s framework (version: %s, confidence: %.2f)",
|
|
styles.Highlight.Render(bestMatch), versionMatch.Version, highestConfidence)
|
|
|
|
if versionMatch.Confidence > 0 {
|
|
frameworklog.Debugf("Version detected from: %s (confidence: %.2f)",
|
|
versionMatch.Source, versionMatch.Confidence)
|
|
}
|
|
|
|
if len(cves) > 0 {
|
|
frameworklog.Warnf("Risk level: %s", styles.SeverityHigh.Render(result.RiskLevel))
|
|
for _, cve := range cves {
|
|
frameworklog.Warnf("Found potential vulnerability: %s", styles.Highlight.Render(cve))
|
|
}
|
|
for _, suggestion := range suggestions {
|
|
frameworklog.Infof("Recommendation: %s", suggestion)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
frameworklog.Info("No framework detected with sufficient confidence")
|
|
return nil, nil
|
|
}
|
|
|
|
func containsHeader(headers http.Header, signature string) bool {
|
|
sigLower := strings.ToLower(signature)
|
|
|
|
// check header names
|
|
for name := range headers {
|
|
if strings.Contains(strings.ToLower(name), sigLower) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// check header values
|
|
for _, values := range headers {
|
|
for _, value := range values {
|
|
if strings.Contains(strings.ToLower(value), sigLower) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func detectVersion(body string, framework string) string {
|
|
return extractVersion(body, framework)
|
|
}
|
|
|
|
// CVEEntry represents a known vulnerability for a framework version
|
|
type CVEEntry struct {
|
|
CVE string
|
|
AffectedVersions []string // versions affected (use semver ranges in future)
|
|
FixedVersion string
|
|
Severity string // critical, high, medium, low
|
|
Description string
|
|
Recommendations []string
|
|
}
|
|
|
|
// Known CVEs database - can be extended or loaded from external source
|
|
var knownCVEs = map[string][]CVEEntry{
|
|
"Laravel": {
|
|
{
|
|
CVE: "CVE-2021-3129",
|
|
AffectedVersions: []string{"8.0.0", "8.0.1", "8.0.2", "8.1.0", "8.2.0", "8.3.0", "8.4.0", "8.4.1"},
|
|
FixedVersion: "8.4.2",
|
|
Severity: "critical",
|
|
Description: "Ignition debug mode RCE vulnerability",
|
|
Recommendations: []string{"Update to Laravel 8.4.2 or later", "Disable debug mode in production"},
|
|
},
|
|
{
|
|
CVE: "CVE-2021-21263",
|
|
AffectedVersions: []string{"8.0.0", "8.1.0", "8.2.0", "8.3.0", "8.4.0"},
|
|
FixedVersion: "8.5.0",
|
|
Severity: "high",
|
|
Description: "SQL injection via request validation",
|
|
Recommendations: []string{"Update to Laravel 8.5.0 or later", "Use parameterized queries"},
|
|
},
|
|
},
|
|
"Django": {
|
|
{
|
|
CVE: "CVE-2023-36053",
|
|
AffectedVersions: []string{"3.2.0", "3.2.1", "3.2.2", "4.0.0", "4.1.0"},
|
|
FixedVersion: "4.2.3",
|
|
Severity: "high",
|
|
Description: "Potential ReDoS in EmailValidator and URLValidator",
|
|
Recommendations: []string{"Update to Django 4.2.3 or later"},
|
|
},
|
|
{
|
|
CVE: "CVE-2023-31047",
|
|
AffectedVersions: []string{"3.2.0", "4.0.0", "4.1.0"},
|
|
FixedVersion: "4.1.9",
|
|
Severity: "medium",
|
|
Description: "File upload validation bypass",
|
|
Recommendations: []string{"Update to Django 4.1.9 or later", "Implement additional file validation"},
|
|
},
|
|
},
|
|
"WordPress": {
|
|
{
|
|
CVE: "CVE-2023-2745",
|
|
AffectedVersions: []string{"5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "6.0", "6.1"},
|
|
FixedVersion: "6.2",
|
|
Severity: "medium",
|
|
Description: "Directory traversal vulnerability",
|
|
Recommendations: []string{"Update to WordPress 6.2 or later"},
|
|
},
|
|
},
|
|
"Drupal": {
|
|
{
|
|
CVE: "CVE-2023-44487",
|
|
AffectedVersions: []string{"9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "10.0"},
|
|
FixedVersion: "10.1.4",
|
|
Severity: "high",
|
|
Description: "HTTP/2 rapid reset attack (DoS)",
|
|
Recommendations: []string{"Update to Drupal 10.1.4 or later", "Configure HTTP/2 rate limiting"},
|
|
},
|
|
},
|
|
"Next.js": {
|
|
{
|
|
CVE: "CVE-2023-46298",
|
|
AffectedVersions: []string{"13.0.0", "13.1.0", "13.2.0", "13.3.0", "13.4.0"},
|
|
FixedVersion: "13.5.0",
|
|
Severity: "medium",
|
|
Description: "Server-side request forgery vulnerability",
|
|
Recommendations: []string{"Update to Next.js 13.5.0 or later"},
|
|
},
|
|
},
|
|
"Angular": {
|
|
{
|
|
CVE: "CVE-2023-26117",
|
|
AffectedVersions: []string{"14.0.0", "14.1.0", "14.2.0", "15.0.0"},
|
|
FixedVersion: "15.2.0",
|
|
Severity: "medium",
|
|
Description: "Regular expression denial of service",
|
|
Recommendations: []string{"Update to Angular 15.2.0 or later"},
|
|
},
|
|
},
|
|
"Vue.js": {
|
|
{
|
|
CVE: "CVE-2024-5987",
|
|
AffectedVersions: []string{"2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0"},
|
|
FixedVersion: "2.7.16",
|
|
Severity: "medium",
|
|
Description: "XSS vulnerability in certain configurations",
|
|
Recommendations: []string{"Update to Vue.js 2.7.16 or 3.x"},
|
|
},
|
|
},
|
|
"Express.js": {
|
|
{
|
|
CVE: "CVE-2024-29041",
|
|
AffectedVersions: []string{"4.0.0", "4.1.0", "4.2.0", "4.3.0", "4.4.0"},
|
|
FixedVersion: "4.19.2",
|
|
Severity: "medium",
|
|
Description: "Open redirect vulnerability",
|
|
Recommendations: []string{"Update to Express.js 4.19.2 or later"},
|
|
},
|
|
},
|
|
"Ruby on Rails": {
|
|
{
|
|
CVE: "CVE-2023-22795",
|
|
AffectedVersions: []string{"6.0.0", "6.1.0", "7.0.0"},
|
|
FixedVersion: "7.0.4.1",
|
|
Severity: "high",
|
|
Description: "ReDoS vulnerability in Action Dispatch",
|
|
Recommendations: []string{"Update to Rails 7.0.4.1 or later"},
|
|
},
|
|
},
|
|
"Spring": {
|
|
{
|
|
CVE: "CVE-2022-22965",
|
|
AffectedVersions: []string{"5.0.0", "5.1.0", "5.2.0", "5.3.0"},
|
|
FixedVersion: "5.3.18",
|
|
Severity: "critical",
|
|
Description: "Spring4Shell RCE vulnerability",
|
|
Recommendations: []string{"Update to Spring 5.3.18 or later", "Disable class binding on user input"},
|
|
},
|
|
},
|
|
"Spring Boot": {
|
|
{
|
|
CVE: "CVE-2022-22963",
|
|
AffectedVersions: []string{"2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0"},
|
|
FixedVersion: "2.6.6",
|
|
Severity: "critical",
|
|
Description: "RCE via Spring Cloud Function",
|
|
Recommendations: []string{"Update to Spring Boot 2.6.6 or later"},
|
|
},
|
|
},
|
|
"ASP.NET": {
|
|
{
|
|
CVE: "CVE-2023-36899",
|
|
AffectedVersions: []string{"4.0", "4.5", "4.6", "4.7", "4.8"},
|
|
FixedVersion: "latest security patches",
|
|
Severity: "high",
|
|
Description: "Elevation of privilege vulnerability",
|
|
Recommendations: []string{"Apply latest security patches", "Ensure proper request validation"},
|
|
},
|
|
},
|
|
"Joomla": {
|
|
{
|
|
CVE: "CVE-2023-23752",
|
|
AffectedVersions: []string{"4.0.0", "4.1.0", "4.2.0"},
|
|
FixedVersion: "4.2.8",
|
|
Severity: "critical",
|
|
Description: "Improper access check allowing unauthorized access to webservice endpoints",
|
|
Recommendations: []string{"Update to Joomla 4.2.8 or later"},
|
|
},
|
|
},
|
|
"Magento": {
|
|
{
|
|
CVE: "CVE-2022-24086",
|
|
AffectedVersions: []string{"2.3.0", "2.3.1", "2.3.2", "2.4.0", "2.4.1", "2.4.2"},
|
|
FixedVersion: "2.4.3-p1",
|
|
Severity: "critical",
|
|
Description: "Improper input validation leading to arbitrary code execution",
|
|
Recommendations: []string{"Update to Magento 2.4.3-p1 or later"},
|
|
},
|
|
},
|
|
}
|
|
|
|
func getVulnerabilities(framework, version string) ([]string, []string) {
|
|
entries, exists := knownCVEs[framework]
|
|
if !exists {
|
|
return nil, nil
|
|
}
|
|
|
|
var cves []string
|
|
var recommendations []string
|
|
seenRecs := make(map[string]bool)
|
|
|
|
for _, entry := range entries {
|
|
for _, affectedVer := range entry.AffectedVersions {
|
|
if version == affectedVer || strings.HasPrefix(version, affectedVer) {
|
|
cves = append(cves, fmt.Sprintf("%s (%s)", entry.CVE, entry.Severity))
|
|
for _, rec := range entry.Recommendations {
|
|
if !seenRecs[rec] {
|
|
recommendations = append(recommendations, rec)
|
|
seenRecs[rec] = true
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return cves, recommendations
|
|
}
|
|
|
|
// getRiskLevel determines overall risk based on detected CVEs
|
|
func getRiskLevel(cves []string) string {
|
|
if len(cves) == 0 {
|
|
return "low"
|
|
}
|
|
for _, cve := range cves {
|
|
if strings.Contains(cve, "critical") {
|
|
return "critical"
|
|
}
|
|
}
|
|
for _, cve := range cves {
|
|
if strings.Contains(cve, "high") {
|
|
return "high"
|
|
}
|
|
}
|
|
return "medium"
|
|
}
|
|
|
|
// VersionMatch represents a version detection result with confidence
|
|
type VersionMatch struct {
|
|
Version string
|
|
Confidence float32
|
|
Source string // where the version was found
|
|
}
|
|
|
|
func extractVersion(body string, framework string) string {
|
|
match := extractVersionWithConfidence(body, framework)
|
|
return match.Version
|
|
}
|
|
|
|
func extractVersionWithConfidence(body string, framework string) VersionMatch {
|
|
versionPatterns := map[string][]struct {
|
|
pattern string
|
|
confidence float32
|
|
source string
|
|
}{
|
|
"Laravel": {
|
|
{`Laravel\s+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`laravel/framework.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "composer.json"},
|
|
},
|
|
"Django": {
|
|
{`Django[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`django.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "package reference"},
|
|
},
|
|
"Ruby on Rails": {
|
|
{`Rails[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`rails.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "gem reference"},
|
|
},
|
|
"Express.js": {
|
|
{`Express[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"express":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
},
|
|
"ASP.NET": {
|
|
{`X-AspNet-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.95, "header"},
|
|
{`ASP\.NET[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`X-AspNetMvc-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.9, "MVC header"},
|
|
},
|
|
"ASP.NET Core": {
|
|
{`\.NET\s*(\d+\.\d+(?:\.\d+)?)`, 0.8, "dotnet version"},
|
|
},
|
|
"Spring": {
|
|
{`Spring[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`spring-core.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "maven"},
|
|
},
|
|
"Spring Boot": {
|
|
{`spring-boot.*?(\d+\.\d+(?:\.\d+)?)`, 0.9, "maven"},
|
|
},
|
|
"Flask": {
|
|
{`Flask[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`Werkzeug[/\s]+(\d+\.\d+(?:\.\d+)?)`, 0.7, "werkzeug version"},
|
|
},
|
|
"Next.js": {
|
|
{`Next\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"next":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
{`_next/static/.*?(\d+\.\d+(?:\.\d+)?)`, 0.6, "static path"},
|
|
},
|
|
"Nuxt.js": {
|
|
{`Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"nuxt":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
},
|
|
"Vue.js": {
|
|
{`Vue\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"vue":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
{`vue@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"},
|
|
},
|
|
"Angular": {
|
|
{`ng-version="(\d+\.\d+(?:\.\d+)?)"`, 0.95, "ng-version attribute"},
|
|
{`Angular[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"@angular/core":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
},
|
|
"React": {
|
|
{`React[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"react":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
{`react@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"},
|
|
},
|
|
"Svelte": {
|
|
{`Svelte[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`"svelte":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
},
|
|
"SvelteKit": {
|
|
{`"@sveltejs/kit":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
|
},
|
|
"WordPress": {
|
|
{`WordPress[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`wp-includes/version\.php.*?(\d+\.\d+(?:\.\d+)?)`, 0.85, "version.php"},
|
|
{`<meta name="generator" content="WordPress (\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
|
|
},
|
|
"Drupal": {
|
|
{`Drupal[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`<meta name="Generator" content="Drupal (\d+)`, 0.9, "generator meta"},
|
|
},
|
|
"Joomla": {
|
|
{`Joomla[!/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
{`<meta name="generator" content="Joomla! - Open Source Content Management - Version (\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
|
|
},
|
|
"Magento": {
|
|
{`Magento[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Shopify": {
|
|
{`Shopify\.theme.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "theme version"},
|
|
},
|
|
"Symfony": {
|
|
{`Symfony[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"FastAPI": {
|
|
{`FastAPI[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Gin": {
|
|
{`Gin[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Phoenix": {
|
|
{`Phoenix[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Ember.js": {
|
|
{`Ember[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Backbone.js": {
|
|
{`Backbone[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Meteor": {
|
|
{`Meteor[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
"Ghost": {
|
|
{`Ghost[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
|
},
|
|
}
|
|
|
|
patterns, exists := versionPatterns[framework]
|
|
if !exists {
|
|
return VersionMatch{Version: "unknown", Confidence: 0, Source: ""}
|
|
}
|
|
|
|
var bestMatch VersionMatch
|
|
for _, p := range patterns {
|
|
re := regexp.MustCompile(p.pattern)
|
|
matches := re.FindStringSubmatch(body)
|
|
if len(matches) > 1 && p.confidence > bestMatch.Confidence {
|
|
bestMatch = VersionMatch{
|
|
Version: matches[1],
|
|
Confidence: p.confidence,
|
|
Source: p.source,
|
|
}
|
|
}
|
|
}
|
|
|
|
if bestMatch.Version == "" {
|
|
return VersionMatch{Version: "unknown", Confidence: 0, Source: ""}
|
|
}
|
|
return bestMatch
|
|
}
|