mirror of
https://github.com/lunchcat/sif.git
synced 2026-03-12 21:23:04 -07:00
fix: improve version detection and add documentation
- fix version detection to validate reasonable version numbers (major < 100) - remove overly permissive patterns that caused false positives - add comprehensive framework contribution documentation to CONTRIBUTING.md - document signature patterns, version detection, and CVE data format - add configuration documentation for flags and env vars - outline future enhancements for community contributions
This commit is contained in:
@@ -53,6 +53,98 @@ When making a pull request, please adhere to the following conventions:
|
|||||||
|
|
||||||
If you have any questions, feel free to ask around on the IRC channel.
|
If you have any questions, feel free to ask around on the IRC channel.
|
||||||
|
|
||||||
|
## Contributing Framework Detection Patterns
|
||||||
|
|
||||||
|
The framework detection module (`pkg/scan/frameworks/detect.go`) identifies web frameworks by analyzing HTTP headers and response bodies. To add support for a new framework:
|
||||||
|
|
||||||
|
### Adding a New Framework Signature
|
||||||
|
|
||||||
|
1. Add your framework to the `frameworkSignatures` map:
|
||||||
|
|
||||||
|
```go
|
||||||
|
"MyFramework": {
|
||||||
|
{Pattern: `unique-identifier`, Weight: 0.5},
|
||||||
|
{Pattern: `header-signature`, Weight: 0.4, HeaderOnly: true},
|
||||||
|
{Pattern: `body-signature`, Weight: 0.3},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern Guidelines:**
|
||||||
|
- `Weight`: How much this signature contributes to detection (0.0-1.0)
|
||||||
|
- `HeaderOnly`: Set to `true` for HTTP header patterns
|
||||||
|
- Use unique identifiers that won't false-positive on other frameworks
|
||||||
|
- Include multiple patterns for higher confidence
|
||||||
|
|
||||||
|
### Adding Version Detection
|
||||||
|
|
||||||
|
Add version patterns to `extractVersionWithConfidence()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
"MyFramework": {
|
||||||
|
{`MyFramework[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
||||||
|
{`"myframework":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding CVE Data
|
||||||
|
|
||||||
|
Add known vulnerabilities to the `knownCVEs` map:
|
||||||
|
|
||||||
|
```go
|
||||||
|
"MyFramework": {
|
||||||
|
{
|
||||||
|
CVE: "CVE-YYYY-XXXXX",
|
||||||
|
AffectedVersions: []string{"1.0.0", "1.0.1", "1.1.0"},
|
||||||
|
FixedVersion: "1.2.0",
|
||||||
|
Severity: "high", // critical, high, medium, low
|
||||||
|
Description: "Brief description of the vulnerability",
|
||||||
|
Recommendations: []string{"Update to 1.2.0 or later"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Your Changes
|
||||||
|
|
||||||
|
Always add tests for new frameworks in `detect_test.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestDetectFramework_MyFramework(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`<html><body>unique-identifier</body></html>`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
result, err := DetectFramework(server.URL, 5*time.Second, "")
|
||||||
|
// assertions...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Future Enhancements (Help Wanted)
|
||||||
|
|
||||||
|
- **Custom Signature Support**: Allow users to define signatures via config file
|
||||||
|
- **CVE API Integration**: Real-time CVE data from NVD or other sources
|
||||||
|
- **Automated Signature Updates**: Fetch new signatures from a central repository
|
||||||
|
- **Framework Fingerprint Database**: Community-maintained signature database
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Framework Detection Flags
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `-framework` | Enable framework detection |
|
||||||
|
| `-timeout` | HTTP request timeout (affects all modules) |
|
||||||
|
| `-threads` | Number of concurrent workers |
|
||||||
|
| `-log` | Directory to save scan results |
|
||||||
|
| `-debug` | Enable debug logging for verbose output |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `SHODAN_API_KEY` | API key for Shodan host intelligence |
|
||||||
|
|
||||||
## Packaging
|
## Packaging
|
||||||
|
|
||||||
We'd love it if you helped us bring sif to your distribution.
|
We'd love it if you helped us bring sif to your distribution.
|
||||||
|
|||||||
@@ -618,6 +618,24 @@ type VersionMatch struct {
|
|||||||
Source string // where the version was found
|
Source string // where the version was found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidVersion checks if a version string looks reasonable
|
||||||
|
func isValidVersion(version string) bool {
|
||||||
|
if version == "" || version == "unknown" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// parse major version and check if reasonable (< 100)
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var major int
|
||||||
|
_, err := fmt.Sscanf(parts[0], "%d", &major)
|
||||||
|
if err != nil || major >= 100 || major < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func extractVersion(body string, framework string) string {
|
func extractVersion(body string, framework string) string {
|
||||||
match := extractVersionWithConfidence(body, framework)
|
match := extractVersionWithConfidence(body, framework)
|
||||||
return match.Version
|
return match.Version
|
||||||
@@ -665,9 +683,8 @@ func extractVersionWithConfidence(body string, framework string) VersionMatch {
|
|||||||
{`Werkzeug[/\s]+(\d+\.\d+(?:\.\d+)?)`, 0.7, "werkzeug version"},
|
{`Werkzeug[/\s]+(\d+\.\d+(?:\.\d+)?)`, 0.7, "werkzeug version"},
|
||||||
},
|
},
|
||||||
"Next.js": {
|
"Next.js": {
|
||||||
{`Next\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
{`Next\.js[/\s]+[Vv]?(\d{1,2}\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
||||||
{`"next":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
{`"next":\s*"[~^]?(\d{1,2}\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
||||||
{`_next/static/.*?(\d+\.\d+(?:\.\d+)?)`, 0.6, "static path"},
|
|
||||||
},
|
},
|
||||||
"Nuxt.js": {
|
"Nuxt.js": {
|
||||||
{`Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
{`Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
||||||
@@ -696,9 +713,8 @@ func extractVersionWithConfidence(body string, framework string) VersionMatch {
|
|||||||
{`"@sveltejs/kit":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
{`"@sveltejs/kit":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
|
||||||
},
|
},
|
||||||
"WordPress": {
|
"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"},
|
{`<meta name="generator" content="WordPress (\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
|
||||||
|
{`WordPress (\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
||||||
},
|
},
|
||||||
"Drupal": {
|
"Drupal": {
|
||||||
{`Drupal[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
{`Drupal[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
|
||||||
@@ -750,10 +766,14 @@ func extractVersionWithConfidence(body string, framework string) VersionMatch {
|
|||||||
re := regexp.MustCompile(p.pattern)
|
re := regexp.MustCompile(p.pattern)
|
||||||
matches := re.FindStringSubmatch(body)
|
matches := re.FindStringSubmatch(body)
|
||||||
if len(matches) > 1 && p.confidence > bestMatch.Confidence {
|
if len(matches) > 1 && p.confidence > bestMatch.Confidence {
|
||||||
bestMatch = VersionMatch{
|
candidate := matches[1]
|
||||||
Version: matches[1],
|
// validate version looks reasonable
|
||||||
Confidence: p.confidence,
|
if isValidVersion(candidate) {
|
||||||
Source: p.source,
|
bestMatch = VersionMatch{
|
||||||
|
Version: candidate,
|
||||||
|
Confidence: p.confidence,
|
||||||
|
Source: p.source,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user