-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: add passive CPE and WordPress detection #2366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,225 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package runner | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| awesomesearchqueries "github.com/projectdiscovery/awesome-search-queries" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| type CPEInfo struct { | ||||||||||||||||||||||||||||||||||||||||||||
| Product string `json:"product,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||
| Vendor string `json:"vendor,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||
| CPE string `json:"cpe,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| type CPEDetector struct { | ||||||||||||||||||||||||||||||||||||||||||||
| titlePatterns map[string][]CPEInfo | ||||||||||||||||||||||||||||||||||||||||||||
| bodyPatterns map[string][]CPEInfo | ||||||||||||||||||||||||||||||||||||||||||||
| faviconPatterns map[string][]CPEInfo | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| type rawQuery struct { | ||||||||||||||||||||||||||||||||||||||||||||
| Name string `json:"name"` | ||||||||||||||||||||||||||||||||||||||||||||
| Vendor json.RawMessage `json:"vendor"` | ||||||||||||||||||||||||||||||||||||||||||||
| Type string `json:"type"` | ||||||||||||||||||||||||||||||||||||||||||||
| Engines []rawEngine `json:"engines"` | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| type rawEngine struct { | ||||||||||||||||||||||||||||||||||||||||||||
| Platform string `json:"platform"` | ||||||||||||||||||||||||||||||||||||||||||||
| Queries []string `json:"queries"` | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func NewCPEDetector() (*CPEDetector, error) { | ||||||||||||||||||||||||||||||||||||||||||||
| data, err := awesomesearchqueries.GetQueries() | ||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to load queries: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| var queries []rawQuery | ||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(data, &queries); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to parse queries: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| detector := &CPEDetector{ | ||||||||||||||||||||||||||||||||||||||||||||
| titlePatterns: make(map[string][]CPEInfo), | ||||||||||||||||||||||||||||||||||||||||||||
| bodyPatterns: make(map[string][]CPEInfo), | ||||||||||||||||||||||||||||||||||||||||||||
| faviconPatterns: make(map[string][]CPEInfo), | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for _, q := range queries { | ||||||||||||||||||||||||||||||||||||||||||||
| vendor := parseVendor(q.Vendor) | ||||||||||||||||||||||||||||||||||||||||||||
| info := CPEInfo{ | ||||||||||||||||||||||||||||||||||||||||||||
| Product: q.Name, | ||||||||||||||||||||||||||||||||||||||||||||
| Vendor: vendor, | ||||||||||||||||||||||||||||||||||||||||||||
| CPE: generateCPE(vendor, q.Name), | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for _, engine := range q.Engines { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, query := range engine.Queries { | ||||||||||||||||||||||||||||||||||||||||||||
| detector.extractPattern(query, info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return detector, nil | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func parseVendor(raw json.RawMessage) string { | ||||||||||||||||||||||||||||||||||||||||||||
| var vendorStr string | ||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(raw, &vendorStr); err == nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return vendorStr | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| var vendorSlice []string | ||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(raw, &vendorSlice); err == nil && len(vendorSlice) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||
| return vendorSlice[0] | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func generateCPE(vendor, product string) string { | ||||||||||||||||||||||||||||||||||||||||||||
| if vendor == "" || product == "" { | ||||||||||||||||||||||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Sprintf("cpe:2.3:a:%s:%s:*:*:*:*:*:*:*:*", | ||||||||||||||||||||||||||||||||||||||||||||
| strings.ToLower(strings.ReplaceAll(vendor, " ", "_")), | ||||||||||||||||||||||||||||||||||||||||||||
| strings.ToLower(strings.ReplaceAll(product, " ", "_"))) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func (d *CPEDetector) extractPattern(query string, info CPEInfo) { | ||||||||||||||||||||||||||||||||||||||||||||
| query = strings.TrimSpace(query) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| titlePrefixes := []string{ | ||||||||||||||||||||||||||||||||||||||||||||
| "http.title:", | ||||||||||||||||||||||||||||||||||||||||||||
| "title=", | ||||||||||||||||||||||||||||||||||||||||||||
| "title==", | ||||||||||||||||||||||||||||||||||||||||||||
| "intitle:", | ||||||||||||||||||||||||||||||||||||||||||||
| "title:", | ||||||||||||||||||||||||||||||||||||||||||||
| "title='", | ||||||||||||||||||||||||||||||||||||||||||||
| `title="`, | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for _, prefix := range titlePrefixes { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(strings.ToLower(query), strings.ToLower(prefix)) { | ||||||||||||||||||||||||||||||||||||||||||||
| pattern := extractQuotedValue(strings.TrimPrefix(query, prefix)) | ||||||||||||||||||||||||||||||||||||||||||||
| pattern = strings.TrimPrefix(pattern, prefix[:len(prefix)-1]) | ||||||||||||||||||||||||||||||||||||||||||||
| if pattern != "" { | ||||||||||||||||||||||||||||||||||||||||||||
| pattern = strings.ToLower(pattern) | ||||||||||||||||||||||||||||||||||||||||||||
| d.titlePatterns[pattern] = appendUnique(d.titlePatterns[pattern], info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+106
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suspicious redundant prefix stripping. Line 109 attempts to strip 🔎 Suggested fixIf the intent was to handle cases where the prefix includes a trailing quote character (like if strings.HasPrefix(strings.ToLower(query), strings.ToLower(prefix)) {
pattern := extractQuotedValue(strings.TrimPrefix(query, prefix))
- pattern = strings.TrimPrefix(pattern, prefix[:len(prefix)-1])
if pattern != "" {
pattern = strings.ToLower(pattern)
d.titlePatterns[pattern] = appendUnique(d.titlePatterns[pattern], info)
}
return
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| bodyPrefixes := []string{ | ||||||||||||||||||||||||||||||||||||||||||||
| "http.html:", | ||||||||||||||||||||||||||||||||||||||||||||
| "body=", | ||||||||||||||||||||||||||||||||||||||||||||
| "body==", | ||||||||||||||||||||||||||||||||||||||||||||
| "intext:", | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for _, prefix := range bodyPrefixes { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(strings.ToLower(query), strings.ToLower(prefix)) { | ||||||||||||||||||||||||||||||||||||||||||||
| pattern := extractQuotedValue(strings.TrimPrefix(query, prefix)) | ||||||||||||||||||||||||||||||||||||||||||||
| if pattern != "" { | ||||||||||||||||||||||||||||||||||||||||||||
| pattern = strings.ToLower(pattern) | ||||||||||||||||||||||||||||||||||||||||||||
| d.bodyPatterns[pattern] = appendUnique(d.bodyPatterns[pattern], info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| faviconPrefixes := []string{ | ||||||||||||||||||||||||||||||||||||||||||||
| "http.favicon.hash:", | ||||||||||||||||||||||||||||||||||||||||||||
| "icon_hash=", | ||||||||||||||||||||||||||||||||||||||||||||
| "icon_hash==", | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for _, prefix := range faviconPrefixes { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(strings.ToLower(query), strings.ToLower(prefix)) { | ||||||||||||||||||||||||||||||||||||||||||||
| pattern := extractQuotedValue(strings.TrimPrefix(query, prefix)) | ||||||||||||||||||||||||||||||||||||||||||||
| if pattern != "" { | ||||||||||||||||||||||||||||||||||||||||||||
| d.faviconPatterns[pattern] = appendUnique(d.faviconPatterns[pattern], info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func extractQuotedValue(s string) string { | ||||||||||||||||||||||||||||||||||||||||||||
| s = strings.TrimSpace(s) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if len(s) >= 2 { | ||||||||||||||||||||||||||||||||||||||||||||
| if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') { | ||||||||||||||||||||||||||||||||||||||||||||
| s = s[1 : len(s)-1] | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if idx := strings.Index(s, "\" ||"); idx > 0 { | ||||||||||||||||||||||||||||||||||||||||||||
| s = s[:idx] | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if idx := strings.Index(s, "' ||"); idx > 0 { | ||||||||||||||||||||||||||||||||||||||||||||
| s = s[:idx] | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return strings.TrimSpace(s) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func appendUnique(slice []CPEInfo, info CPEInfo) []CPEInfo { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, existing := range slice { | ||||||||||||||||||||||||||||||||||||||||||||
| if existing.Product == info.Product && existing.Vendor == info.Vendor { | ||||||||||||||||||||||||||||||||||||||||||||
| return slice | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return append(slice, info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func (d *CPEDetector) Detect(title, body, faviconHash string) []CPEInfo { | ||||||||||||||||||||||||||||||||||||||||||||
| seen := make(map[string]bool) | ||||||||||||||||||||||||||||||||||||||||||||
| var results []CPEInfo | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| titleLower := strings.ToLower(title) | ||||||||||||||||||||||||||||||||||||||||||||
| bodyLower := strings.ToLower(body) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for pattern, infos := range d.titlePatterns { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.Contains(titleLower, pattern) { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, info := range infos { | ||||||||||||||||||||||||||||||||||||||||||||
| key := info.Product + "|" + info.Vendor | ||||||||||||||||||||||||||||||||||||||||||||
| if !seen[key] { | ||||||||||||||||||||||||||||||||||||||||||||
| seen[key] = true | ||||||||||||||||||||||||||||||||||||||||||||
| results = append(results, info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| for pattern, infos := range d.bodyPatterns { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.Contains(bodyLower, pattern) { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, info := range infos { | ||||||||||||||||||||||||||||||||||||||||||||
| key := info.Product + "|" + info.Vendor | ||||||||||||||||||||||||||||||||||||||||||||
| if !seen[key] { | ||||||||||||||||||||||||||||||||||||||||||||
| seen[key] = true | ||||||||||||||||||||||||||||||||||||||||||||
| results = append(results, info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if faviconHash != "" { | ||||||||||||||||||||||||||||||||||||||||||||
| if infos, ok := d.faviconPatterns[faviconHash]; ok { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, info := range infos { | ||||||||||||||||||||||||||||||||||||||||||||
| key := info.Product + "|" + info.Vendor | ||||||||||||||||||||||||||||||||||||||||||||
| if !seen[key] { | ||||||||||||||||||||||||||||||||||||||||||||
| seen[key] = true | ||||||||||||||||||||||||||||||||||||||||||||
| results = append(results, info) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return results | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: projectdiscovery/httpx
Length of output: 103
🏁 Script executed:
Repository: projectdiscovery/httpx
Length of output: 303
🏁 Script executed:
Repository: projectdiscovery/httpx
Length of output: 397
Dependency should be marked as direct, not indirect.
The
awesome-search-queriesdependency is directly imported inrunner/cpe.goandrunner/wordpress.go, so it should not be marked as// indirectin go.mod. Rungo mod tidyto correct this automatically.🤖 Prompt for AI Agents