From 8a79f3cf0fdf2938b4a19f9e7fdec50df5dbc3a6 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Tue, 26 Aug 2025 16:30:46 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=AE=80=E5=8C=96Web=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=B3=BB=E7=BB=9F=E5=B9=B6=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新插件初始化器集成三个插件系统(Service、Web、Local) - 清理WebPOC插件:移除重复端口检测和模拟漏洞数据 - 简化WebTitle插件:去除过度设计的WebInfo结构和技术检测 - 移除Web插件系统中的冗余辅助函数 - 统一插件接口实现,提升代码一致性 --- app/initializer.go | 32 ++++-- plugins/web/init.go | 48 +------- plugins/web/webpoc.go | 75 ++----------- plugins/web/webtitle.go | 237 +++++----------------------------------- 4 files changed, 61 insertions(+), 331 deletions(-) diff --git a/app/initializer.go b/app/initializer.go index 19cf885..3c91aea 100644 --- a/app/initializer.go +++ b/app/initializer.go @@ -1,10 +1,13 @@ package app import ( + "fmt" "sort" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins/services" + "github.com/shadow1ng/fscan/plugins/web" + "github.com/shadow1ng/fscan/plugins/local" ) // Initializer 初始化器接口 @@ -21,23 +24,30 @@ func (p *PluginInitializer) Name() string { } func (p *PluginInitializer) Initialize() error { - var localPlugins []string + // 直接调用三个包的函数 + servicePlugins := services.GetAllPlugins() + webPlugins := web.GetAllWebPlugins() + localPlugins := local.GetAllLocalPlugins() - // 获取所有注册的插件 - allPlugins := services.GetAllPlugins() - - for _, pluginName := range allPlugins { - // 新插件系统中local插件在plugins/local目录中,暂时跳过分类 - // 后续可以通过插件名或其他方式区分 - localPlugins = append(localPlugins, pluginName) - } - - // 排序以保持一致性 + // 排序 + sort.Strings(servicePlugins) + sort.Strings(webPlugins) sort.Strings(localPlugins) + // 合并所有插件 + var allPlugins []string + allPlugins = append(allPlugins, servicePlugins...) + allPlugins = append(allPlugins, webPlugins...) + allPlugins = append(allPlugins, localPlugins...) + sort.Strings(allPlugins) + // 设置全局变量 common.LocalPluginsList = localPlugins + // 记录插件统计 + common.LogInfo(fmt.Sprintf("插件系统初始化完成: Service(%d) Web(%d) Local(%d) Total(%d)", + len(servicePlugins), len(webPlugins), len(localPlugins), len(allPlugins))) + return nil } diff --git a/plugins/web/init.go b/plugins/web/init.go index a6732b6..ce7b7b8 100644 --- a/plugins/web/init.go +++ b/plugins/web/init.go @@ -2,7 +2,6 @@ package web import ( "context" - "fmt" "sync" "github.com/shadow1ng/fscan/common" @@ -18,11 +17,11 @@ type WebPlugin interface { // WebScanResult Web扫描结果 type WebScanResult struct { Success bool - Title string // 网页标题 - Status int // HTTP状态码 - Server string // 服务器信息 - Length int // 响应长度 - VulInfo string // 漏洞信息(如果有) + Title string // 网页标题 + Status int // HTTP状态码 + Server string // 服务器信息 + Length int // 响应长度 + VulInfo string // 漏洞信息(如果有) Error error } @@ -39,49 +38,14 @@ func RegisterWebPlugin(name string, creator func() WebPlugin) { webPluginRegistry[name] = creator } -// GetWebPlugin 获取指定Web插件 -func GetWebPlugin(name string) WebPlugin { - webPluginMutex.RLock() - defer webPluginMutex.RUnlock() - - if creator, exists := webPluginRegistry[name]; exists { - return creator() - } - return nil -} - // GetAllWebPlugins 获取所有已注册Web插件的名称 func GetAllWebPlugins() []string { webPluginMutex.RLock() defer webPluginMutex.RUnlock() - + var plugins []string for name := range webPluginRegistry { plugins = append(plugins, name) } return plugins } - -// IsWebPort 判断是否为Web端口 -func IsWebPort(port int) bool { - webPorts := []int{80, 443, 8080, 8443, 8000, 8888, 9000, 9090, 3000, 5000} - for _, p := range webPorts { - if p == port { - return true - } - } - return false -} - -// BuildWebURL 构建Web URL -func BuildWebURL(host string, port int) string { - scheme := "http" - if port == 443 || port == 8443 { - scheme = "https" - } - - if port == 80 || port == 443 { - return fmt.Sprintf("%s://%s", scheme, host) - } - return fmt.Sprintf("%s://%s:%d", scheme, host, port) -} \ No newline at end of file diff --git a/plugins/web/webpoc.go b/plugins/web/webpoc.go index c054cfd..bcd28b6 100644 --- a/plugins/web/webpoc.go +++ b/plugins/web/webpoc.go @@ -8,17 +8,15 @@ import ( "github.com/shadow1ng/fscan/webscan" ) -// WebPocPlugin Web漏洞扫描插件 - 执行POC检测 +// WebPocPlugin Web漏洞扫描插件 type WebPocPlugin struct { - name string - ports []int + name string } // NewWebPocPlugin 创建Web POC插件 func NewWebPocPlugin() *WebPocPlugin { return &WebPocPlugin{ - name: "webpoc", - ports: []int{80, 443, 8080, 8443, 8000, 8888}, // 常见Web端口 + name: "webpoc", } } @@ -27,16 +25,13 @@ func (p *WebPocPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 +// GetPorts 实现Plugin接口 - Web插件不需要预定义端口,依赖全局检测器 func (p *WebPocPlugin) GetPorts() []int { - return p.ports + return []int{} } // Scan 执行Web POC扫描 func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - // 检查是否禁用POC扫描 if common.DisablePocScan { return &WebScanResult{ Success: false, @@ -44,69 +39,15 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScan } } - // 检查是否为Web端口 - if !p.isWebPort(info.Ports) { - return &WebScanResult{ - Success: false, - Error: fmt.Errorf("端口 %s 不是常见Web端口", info.Ports), - } - } - + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) common.LogSuccess(fmt.Sprintf("WebPOC %s 开始扫描", target)) - // 执行Web POC扫描 - results := p.runWebScan(ctx, info) - if len(results) > 0 { - common.LogSuccess(fmt.Sprintf("WebPOC %s 发现 %d 个漏洞", target, len(results))) - return &WebScanResult{ - Success: true, - VulInfo: fmt.Sprintf("发现 %d 个Web漏洞", len(results)), - } - } - - return &WebScanResult{ - Success: false, - Error: fmt.Errorf("未发现Web漏洞"), - } -} - - -// isWebPort 检查是否为Web端口 -func (p *WebPocPlugin) isWebPort(port string) bool { - webPorts := map[string]bool{ - "80": true, "443": true, "8080": true, "8443": true, - "8000": true, "8888": true, "9000": true, "9090": true, - "3000": true, "5000": true, "8001": true, "8008": true, - "8081": true, "8082": true, "8083": true, "8090": true, - "9001": true, "9080": true, "9999": true, "10000": true, - } - return webPorts[port] -} - -// runWebScan 执行Web扫描并返回结果 -func (p *WebPocPlugin) runWebScan(ctx context.Context, info *common.HostInfo) []string { - // 执行Web扫描 + // 直接执行Web扫描 WebScan.WebScan(info) - // 简化实现:返回模拟的扫描结果 - // 实际中会通过其他方式捕获WebScan的输出 - var results []string - results = append(results, "WebPOC扫描完成") - results = append(results, "检测到潜在漏洞:SQL注入") - results = append(results, "检测到潜在漏洞:XSS") - - return results -} - -// identifyService 服务识别 - Web服务检测 -func (p *WebPocPlugin) identifyService(ctx context.Context, info *common.HostInfo) *WebScanResult { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - banner := "Web应用程序" - common.LogSuccess(fmt.Sprintf("WebPOC %s %s", target, banner)) - return &WebScanResult{ Success: true, - VulInfo: banner, + VulInfo: "WebPOC扫描完成", } } diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 1f49803..b821d2f 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -14,17 +14,15 @@ import ( "github.com/shadow1ng/fscan/common" ) -// WebTitlePlugin Web标题获取插件 - 收集Web应用信息和指纹识别 +// WebTitlePlugin Web标题获取插件 type WebTitlePlugin struct { - name string - ports []int + name string } // NewWebTitlePlugin 创建WebTitle插件 func NewWebTitlePlugin() *WebTitlePlugin { return &WebTitlePlugin{ - name: "webtitle", - ports: []int{80, 443, 8080, 8443, 8000, 8888, 9000, 9090}, // 常见Web端口 + name: "webtitle", } } @@ -33,32 +31,16 @@ func (p *WebTitlePlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 +// GetPorts 实现Plugin接口 - Web插件不需要预定义端口 func (p *WebTitlePlugin) GetPorts() []int { - return p.ports + return []int{} } -// Scan 执行WebTitle扫描 - Web服务识别和指纹获取 +// Scan 执行WebTitle扫描 func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - // 检查是否为Web端口 - webPorts := map[string]bool{ - "80": true, "443": true, "8080": true, "8443": true, - "8000": true, "8888": true, "9000": true, "9090": true, - "8001": true, "8081": true, "8008": true, "8086": true, - "9001": true, "8089": true, "8090": true, "8082": true, - } - - if !webPorts[info.Ports] { - return &WebScanResult{ - Success: false, - Error: fmt.Errorf("非Web端口"), - } - } - - // 获取Web信息 - webInfo, err := p.getWebInfo(ctx, info) + + title, status, server, err := p.getWebTitle(ctx, info) if err != nil { return &WebScanResult{ Success: false, @@ -66,168 +48,77 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebSc } } - if !webInfo.Valid { - return &WebScanResult{ - Success: false, - Error: fmt.Errorf("未发现有效的Web服务"), - } - } - - // 记录Web信息发现 msg := fmt.Sprintf("WebTitle %s", target) - if webInfo.Title != "" { - msg += fmt.Sprintf(" [%s]", webInfo.Title) + if title != "" { + msg += fmt.Sprintf(" [%s]", title) } - if webInfo.StatusCode != 0 { - msg += fmt.Sprintf(" %d", webInfo.StatusCode) + if status != 0 { + msg += fmt.Sprintf(" %d", status) } - if webInfo.Server != "" { - msg += fmt.Sprintf(" %s", webInfo.Server) + if server != "" { + msg += fmt.Sprintf(" %s", server) } common.LogSuccess(msg) return &WebScanResult{ Success: true, - Title: webInfo.Title, - Status: webInfo.StatusCode, - Server: webInfo.Server, - Length: int(webInfo.ContentLength), + Title: title, + Status: status, + Server: server, } } -// WebInfo Web信息结构 -type WebInfo struct { - Valid bool - URL string - StatusCode int - Title string - Server string - ContentType string - ContentLength int64 - Technologies []string - SecurityHeaders map[string]string -} - -// Summary 返回Web信息摘要 -func (wi *WebInfo) Summary() string { - if !wi.Valid { - return "Web服务检测失败" - } - - var parts []string - - if wi.StatusCode != 0 { - parts = append(parts, fmt.Sprintf("HTTP %d", wi.StatusCode)) - } - - if wi.Title != "" { - title := wi.Title - if len(title) > 30 { - title = title[:30] + "..." - } - parts = append(parts, fmt.Sprintf("[%s]", title)) - } - - if wi.Server != "" { - parts = append(parts, wi.Server) - } - - return strings.Join(parts, " ") -} - -// getWebInfo 获取Web服务信息 -func (p *WebTitlePlugin) getWebInfo(ctx context.Context, info *common.HostInfo) (*WebInfo, error) { - webInfo := &WebInfo{ - Valid: false, - SecurityHeaders: make(map[string]string), - Technologies: []string{}, - } - - // 确定协议 +func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo) (string, int, string, error) { protocol := "http" if info.Ports == "443" || info.Ports == "8443" { protocol = "https" } - webInfo.URL = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports) + url := fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports) - // 创建HTTP客户端 client := &http.Client{ Timeout: time.Duration(common.WebTimeout) * time.Second, Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DisableKeepAlives: true, }, } - // 发送HTTP请求 - req, err := http.NewRequestWithContext(ctx, "GET", webInfo.URL, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { - return webInfo, fmt.Errorf("创建HTTP请求失败: %v", err) + return "", 0, "", err } - // 设置User-Agent 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 webInfo, fmt.Errorf("HTTP请求失败: %v", err) + return "", 0, "", err } defer resp.Body.Close() - webInfo.Valid = true - webInfo.StatusCode = resp.StatusCode - - // 获取基本响应头信息 - if server := resp.Header.Get("Server"); server != "" { - webInfo.Server = server - } - - if contentType := resp.Header.Get("Content-Type"); contentType != "" { - webInfo.ContentType = contentType - } - - webInfo.ContentLength = resp.ContentLength - - // 检测安全头 - p.detectSecurityHeaders(resp, webInfo) - - // 读取响应内容 body, err := io.ReadAll(resp.Body) if err != nil { - return webInfo, fmt.Errorf("读取响应内容失败: %v", err) + return "", resp.StatusCode, resp.Header.Get("Server"), err } - // 提取页面标题 - webInfo.Title = p.extractTitle(string(body)) - - // 检测Web技术 - p.detectTechnologies(resp, string(body), webInfo) - - return webInfo, nil + title := p.extractTitle(string(body)) + return title, resp.StatusCode, resp.Header.Get("Server"), nil } -// extractTitle 提取页面标题 func (p *WebTitlePlugin) extractTitle(html string) string { - // 正则表达式匹配title标签 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] + "..." } - // 确保是有效的UTF-8 if utf8.ValidString(title) { return title } @@ -236,82 +127,6 @@ func (p *WebTitlePlugin) extractTitle(html string) string { return "" } -// detectSecurityHeaders 检测安全头 -func (p *WebTitlePlugin) detectSecurityHeaders(resp *http.Response, webInfo *WebInfo) { - securityHeaders := []string{ - "X-Frame-Options", - "X-XSS-Protection", - "X-Content-Type-Options", - "Strict-Transport-Security", - "Content-Security-Policy", - "X-Powered-By", - } - - for _, header := range securityHeaders { - if value := resp.Header.Get(header); value != "" { - webInfo.SecurityHeaders[header] = value - } - } -} - -// detectTechnologies 检测Web技术 -func (p *WebTitlePlugin) detectTechnologies(resp *http.Response, body string, webInfo *WebInfo) { - // 基于响应头检测 - if xPoweredBy := resp.Header.Get("X-Powered-By"); xPoweredBy != "" { - webInfo.Technologies = append(webInfo.Technologies, fmt.Sprintf("X-Powered-By: %s", xPoweredBy)) - } - - // 基于Server头检测 - if server := resp.Header.Get("Server"); server != "" { - if strings.Contains(strings.ToLower(server), "apache") { - webInfo.Technologies = append(webInfo.Technologies, "Apache HTTP Server") - } else if strings.Contains(strings.ToLower(server), "nginx") { - webInfo.Technologies = append(webInfo.Technologies, "Nginx") - } else if strings.Contains(strings.ToLower(server), "iis") { - webInfo.Technologies = append(webInfo.Technologies, "Microsoft IIS") - } - } - - // 基于HTML内容检测 - bodyLower := strings.ToLower(body) - - // 检测常见框架 - if strings.Contains(bodyLower, "wordpress") { - webInfo.Technologies = append(webInfo.Technologies, "WordPress") - } - - if strings.Contains(bodyLower, "joomla") { - webInfo.Technologies = append(webInfo.Technologies, "Joomla") - } - - if strings.Contains(bodyLower, "drupal") { - webInfo.Technologies = append(webInfo.Technologies, "Drupal") - } - - // 检测JavaScript框架 - if strings.Contains(bodyLower, "jquery") { - webInfo.Technologies = append(webInfo.Technologies, "jQuery") - } - - if strings.Contains(bodyLower, "angular") { - webInfo.Technologies = append(webInfo.Technologies, "AngularJS") - } - - if strings.Contains(bodyLower, "react") { - webInfo.Technologies = append(webInfo.Technologies, "React") - } - - // 检测Web服务器相关 - if strings.Contains(bodyLower, "apache tomcat") || strings.Contains(bodyLower, "tomcat") { - webInfo.Technologies = append(webInfo.Technologies, "Apache Tomcat") - } - - if strings.Contains(bodyLower, "jetty") { - webInfo.Technologies = append(webInfo.Technologies, "Eclipse Jetty") - } -} - -// init 自动注册Web插件 func init() { RegisterWebPlugin("webtitle", func() WebPlugin { return NewWebTitlePlugin()