diff --git a/Core/BaseScanStrategy.go b/Core/BaseScanStrategy.go index 1e1ab76..3ff334f 100644 --- a/Core/BaseScanStrategy.go +++ b/Core/BaseScanStrategy.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" "strings" ) @@ -131,6 +132,11 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor return false } + // 智能Web插件检测:如果是Web插件且检测到Web服务,则包含Web插件 + if b.shouldIncludeWebPlugin(metadata, targetPort) { + return true + } + // 检查类型匹配 if !b.isPluginTypeMatchedByName(pluginName) { return false @@ -149,6 +155,35 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor return true } +// shouldIncludeWebPlugin 判断是否应该包含Web插件(智能检测) +func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool { + // 只对服务扫描策略启用Web插件智能检测 + if b.filterType != FilterService { + return false + } + + // 只对Web类别的插件进行检测 + if metadata.Category != "web" { + return false + } + + // 如果没有指定端口,跳过检测 + if targetPort <= 0 { + return false + } + + // 获取Web检测器实例(延迟初始化) + if globalWebDetector == nil { + globalWebDetector = NewWebPortDetector() + } + + // 检测是否为Web服务(这里暂时只检查常见端口,避免每次都进行HTTP探测) + return globalWebDetector.IsCommonWebPort(targetPort) +} + +// globalWebDetector Web检测器全局实例 +var globalWebDetector *WebPortDetector + // IsPluginApplicable 判断插件是否适用(通用实现) func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { // 自定义模式下运行所有明确指定的插件 diff --git a/Core/WebDetection.go b/Core/WebDetection.go new file mode 100644 index 0000000..75ab4f2 --- /dev/null +++ b/Core/WebDetection.go @@ -0,0 +1,145 @@ +package core + +import ( + "context" + "net" + "net/http" + "strings" + "time" +) + +// WebPortDetector Web端口检测器 +type WebPortDetector struct { + // 常见Web端口列表 + commonWebPorts map[int]bool + // HTTP检测超时时间 + httpTimeout time.Duration +} + +// NewWebPortDetector 创建Web端口检测器 +func NewWebPortDetector() *WebPortDetector { + // 定义常见Web端口 + commonPorts := map[int]bool{ + 80: true, // HTTP + 443: true, // HTTPS + 8080: true, // HTTP alternate + 8443: true, // HTTPS alternate + 8000: true, // Development server + 8888: true, // Common dev port + 9000: true, // Common dev port + 9090: true, // Common dev port + 3000: true, // Node.js dev server + 4000: true, // Ruby dev server + 5000: true, // Python dev server + 8081: true, // HTTP alternate + 8082: true, // HTTP alternate + 8083: true, // HTTP alternate + 8084: true, // HTTP alternate + 8085: true, // HTTP alternate + 8086: true, // HTTP alternate + 8087: true, // HTTP alternate + 8088: true, // HTTP alternate + 8089: true, // HTTP alternate + } + + return &WebPortDetector{ + commonWebPorts: commonPorts, + httpTimeout: 2 * time.Second, // 2秒超时,快速检测 + } +} + +// IsWebService 检测指定主机端口是否为Web服务 +func (w *WebPortDetector) IsWebService(host string, port int) bool { + // 1. 首先检查是否为常见Web端口 + if w.commonWebPorts[port] { + return true + } + + // 2. 对于非常见端口,进行HTTP协议检测 + return w.detectHTTPService(host, port) +} + +// IsCommonWebPort 检查是否为常见Web端口 +func (w *WebPortDetector) IsCommonWebPort(port int) bool { + return w.commonWebPorts[port] +} + +// detectHTTPService 检测HTTP服务(轻量级探测) +func (w *WebPortDetector) detectHTTPService(host string, port int) bool { + // 创建检测上下文,避免长时间阻塞 + ctx, cancel := context.WithTimeout(context.Background(), w.httpTimeout) + defer cancel() + + // 尝试HTTP连接 + if w.tryHTTPConnection(ctx, host, port, "http") { + return true + } + + // 尝试HTTPS连接(对于高端口常见) + if w.tryHTTPConnection(ctx, host, port, "https") { + return true + } + + return false +} + +// tryHTTPConnection 尝试HTTP连接 +func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool { + url := "" + if port == 80 && protocol == "http" { + url = "http://" + host + } else if port == 443 && protocol == "https" { + url = "https://" + host + } else { + url = protocol + "://" + host + ":" + string(rune(port)) + } + + // 创建HTTP客户端,快速检测 + client := &http.Client{ + Timeout: w.httpTimeout, + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: w.httpTimeout, + }).DialContext, + TLSHandshakeTimeout: w.httpTimeout, + ResponseHeaderTimeout: w.httpTimeout, + DisableKeepAlives: true, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse // 不跟随重定向,加快检测速度 + }, + } + + // 发送HEAD请求,最小化网络开销 + req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil) + if err != nil { + return false + } + + req.Header.Set("User-Agent", "fscan-web-detector/2.0") + + resp, err := client.Do(req) + if err != nil { + // 检查错误类型,某些错误也表明是HTTP服务 + errStr := strings.ToLower(err.Error()) + if strings.Contains(errStr, "http") || + strings.Contains(errStr, "malformed http") || + strings.Contains(errStr, "server closed") { + return true + } + return false + } + defer resp.Body.Close() + + // 任何HTTP响应都表明这是Web服务 + return resp.StatusCode > 0 +} + +// GetCommonWebPorts 获取常见Web端口列表 +func (w *WebPortDetector) GetCommonWebPorts() []int { + ports := make([]int, 0, len(w.commonWebPorts)) + for port := range w.commonWebPorts { + ports = append(ports, port) + } + return ports +} \ No newline at end of file diff --git a/Plugins/legacy/webpoc/plugin.go b/Plugins/legacy/webpoc/plugin.go new file mode 100644 index 0000000..da70944 --- /dev/null +++ b/Plugins/legacy/webpoc/plugin.go @@ -0,0 +1,54 @@ +package webpoc + +import ( + "github.com/shadow1ng/fscan/plugins/adapters" + "github.com/shadow1ng/fscan/plugins/base" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" +) + +// NewWebPocPlugin 创建WebPoc漏洞扫描插件 +func NewWebPocPlugin() base.Plugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "webpoc", + Version: "1.0.0", + Author: "fscan-team", + Description: "Web应用漏洞POC扫描检测", + Category: "web", + Ports: []int{}, // Web插件不限制端口,支持任意端口的URL + Protocols: []string{"http", "https"}, + Tags: []string{"web", "poc", "vulnerability", "exploit"}, + } + + // 适配器选项 + options := &adapters.LegacyPluginOptions{ + CheckBruteFlag: false, // WebPoc不依赖暴力破解标志 + IsVulnPlugin: true, // 这是漏洞检测插件 + IsInfoPlugin: false, + CustomPorts: []int{}, // Web插件不限制端口,支持任意端口的URL + } + + // 创建适配器,使用老版本的WebPoc函数 + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.WebPoc, options) +} + +// init 自动注册WebPoc插件 +func init() { + // 创建插件工厂 + metadata := &base.PluginMetadata{ + Name: "webpoc", + Version: "1.0.0", + Author: "fscan-team", + Description: "Web应用漏洞POC扫描检测", + Category: "web", + Ports: []int{}, // Web插件不限制端口,支持任意端口的URL + Protocols: []string{"http", "https"}, + Tags: []string{"web", "poc", "vulnerability", "exploit"}, + } + + factory := base.NewSimplePluginFactory(metadata, func() base.Plugin { + return NewWebPocPlugin() + }) + + base.GlobalPluginRegistry.Register("webpoc", factory) +} \ No newline at end of file diff --git a/Plugins/legacy/webtitle/plugin.go b/Plugins/legacy/webtitle/plugin.go new file mode 100644 index 0000000..d4ffe68 --- /dev/null +++ b/Plugins/legacy/webtitle/plugin.go @@ -0,0 +1,54 @@ +package webtitle + +import ( + "github.com/shadow1ng/fscan/plugins/adapters" + "github.com/shadow1ng/fscan/plugins/base" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" +) + +// NewWebTitlePlugin 创建WebTitle网站标题获取插件 +func NewWebTitlePlugin() base.Plugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "webtitle", + Version: "1.0.0", + Author: "fscan-team", + Description: "Web网站标题和指纹识别扫描", + Category: "web", + Ports: []int{}, // Web插件不限制端口,支持任意端口的URL + Protocols: []string{"http", "https"}, + Tags: []string{"web", "title", "fingerprint", "information-gathering"}, + } + + // 适配器选项 + options := &adapters.LegacyPluginOptions{ + CheckBruteFlag: false, // WebTitle不依赖暴力破解标志 + IsVulnPlugin: false, // 这不是漏洞检测插件 + IsInfoPlugin: true, // 这是信息收集插件 + CustomPorts: []int{}, // Web插件不限制端口,支持任意端口的URL + } + + // 创建适配器,使用老版本的WebTitle函数 + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.WebTitle, options) +} + +// init 自动注册WebTitle插件 +func init() { + // 创建插件工厂 + metadata := &base.PluginMetadata{ + Name: "webtitle", + Version: "1.0.0", + Author: "fscan-team", + Description: "Web网站标题和指纹识别扫描", + Category: "web", + Ports: []int{}, // Web插件不限制端口,支持任意端口的URL + Protocols: []string{"http", "https"}, + Tags: []string{"web", "title", "fingerprint", "information-gathering"}, + } + + factory := base.NewSimplePluginFactory(metadata, func() base.Plugin { + return NewWebTitlePlugin() + }) + + base.GlobalPluginRegistry.Register("webtitle", factory) +} \ No newline at end of file