package web import ( "context" "crypto/tls" "fmt" "io" "net/http" "regexp" "strings" "time" "unicode/utf8" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins" ) // WebTitlePlugin Web标题获取插件 type WebTitlePlugin struct { plugins.BasePlugin } // NewWebTitlePlugin 创建WebTitle插件 func NewWebTitlePlugin() *WebTitlePlugin { return &WebTitlePlugin{ BasePlugin: plugins.NewBasePlugin("webtitle"), } } // Scan 执行WebTitle扫描 func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) title, status, server, err := p.getWebTitle(ctx, info) if err != nil { return &WebScanResult{ Success: false, Error: err, } } msg := fmt.Sprintf("WebTitle %s", target) if title != "" { msg += fmt.Sprintf(" [%s]", title) } if status != 0 { msg += fmt.Sprintf(" %d", status) } if server != "" { msg += fmt.Sprintf(" %s", server) } common.LogSuccess(msg) return &WebScanResult{ Success: true, Title: title, Status: status, Server: server, } } func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo) (string, int, string, error) { // 智能协议检测 protocol := p.detectProtocol(info) url := fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports) client := &http.Client{ Timeout: time.Duration(common.WebTimeout) * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DisableKeepAlives: true, }, } req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return "", 0, "", err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") resp, err := client.Do(req) if err != nil { return "", 0, "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", resp.StatusCode, resp.Header.Get("Server"), err } title := p.extractTitle(string(body)) return title, resp.StatusCode, resp.Header.Get("Server"), nil } // detectProtocol 智能检测HTTP/HTTPS协议 func (p *WebTitlePlugin) detectProtocol(info *common.HostInfo) string { port := info.Ports // 已知的HTTPS端口 httpsPorts := []string{"443", "8443", "9443"} for _, httpsPort := range httpsPorts { if port == httpsPort { return "https" } } // 常见HTTP端口优先使用HTTP httpPorts := []string{"80", "8080", "8000", "8888", "8090", "3000", "5000", "9000"} for _, httpPort := range httpPorts { if port == httpPort { return "http" } } // 对于其他端口,优先尝试HTTP return "http" } func (p *WebTitlePlugin) extractTitle(html string) string { titleRe := regexp.MustCompile(`(?i)]*>([^<]+)`) matches := titleRe.FindStringSubmatch(html) if len(matches) > 1 { title := strings.TrimSpace(matches[1]) title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ") if len(title) > 100 { title = title[:100] + "..." } if utf8.ValidString(title) { return title } } return "" } func init() { RegisterWebPlugin("webtitle", func() WebPlugin { return NewWebTitlePlugin() }) }