package wappalyzer import ( "context" "fmt" "io" "net/http" "sort" "strings" wappalyzergo "github.com/projectdiscovery/wappalyzergo" "github.com/anotherhadi/iknowyou/internal/tools" ) const ( name = "wappalyzer" description = "Web technology fingerprinting via wappalyzergo — detects CMS, frameworks, web servers, analytics, CDN, and 1500+ other technologies running on a domain." link = "https://github.com/projectdiscovery/wappalyzergo" icon = "wappalyzer" ) type Runner struct { wappalyze *wappalyzergo.Wappalyze } func New() tools.ToolRunner { w, _ := wappalyzergo.New() return &Runner{wappalyze: w} } func (r *Runner) Name() string { return name } func (r *Runner) Description() string { return description } func (r *Runner) Link() string { return link } func (r *Runner) Icon() string { return icon } func (r *Runner) InputTypes() []tools.InputType { return []tools.InputType{tools.InputTypeDomain} } func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error { defer close(out) // Try HTTPS first, fall back to HTTP var ( resp *http.Response body []byte err error ) for _, scheme := range []string{"https", "http"} { targetURL := fmt.Sprintf("%s://%s", scheme, target) req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil) if reqErr != nil { continue } req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)") resp, err = http.DefaultClient.Do(req) if err == nil { defer resp.Body.Close() body, err = io.ReadAll(resp.Body) if err == nil { break } } if ctx.Err() != nil { out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"} out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0} out <- tools.Event{Tool: name, Type: tools.EventTypeDone} return nil } } if err != nil || resp == nil { msg := "failed to connect to target" if err != nil { msg = err.Error() } out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg} out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0} out <- tools.Event{Tool: name, Type: tools.EventTypeDone} return nil } fingerprints := r.wappalyze.FingerprintWithInfo(resp.Header, body) if len(fingerprints) == 0 { out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0} out <- tools.Event{Tool: name, Type: tools.EventTypeDone} return nil } // Group by category byCategory := make(map[string][]string) for tech, info := range fingerprints { cats := info.Categories if len(cats) == 0 { cats = []string{"Other"} } for _, cat := range cats { byCategory[cat] = append(byCategory[cat], tech) } } cats := make([]string, 0, len(byCategory)) for c := range byCategory { cats = append(cats, c) } sort.Strings(cats) var sb strings.Builder sb.WriteString(fmt.Sprintf("Detected %d technologies\n\n", len(fingerprints))) for _, cat := range cats { techs := byCategory[cat] sort.Strings(techs) sb.WriteString(fmt.Sprintf("%s:\n", cat)) for _, t := range techs { sb.WriteString(fmt.Sprintf(" - %s\n", t)) } sb.WriteString("\n") } out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())} out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: len(fingerprints)} out <- tools.Event{Tool: name, Type: tools.EventTypeDone} return nil }