fscan/plugins/web/webtitle.go
ZacharyZcR 95497da8ca refactor: 优化插件系统设计,消除代码重复
主要改进:
1. 修复Services插件端口数据重复问题
   - 删除插件结构体中的ports字段和GetPorts()方法
   - 系统统一使用注册时的端口信息

2. 引入BasePlugin基础结构体
   - 消除51个插件中重复的name字段和Name()方法
   - 统一插件基础功能,简化代码维护

3. 统一插件接口设计
   - 保持向后兼容,功能完全不变
   - 代码更简洁,符合工程最佳实践

影响范围:
- services插件:29个文件简化
- web插件:2个文件简化
- local插件:21个文件简化
- 总计删除约150行重复代码
2025-09-02 05:36:12 +08:00

148 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)<title[^>]*>([^<]+)</title>`)
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()
})
}