feat: 实现Web插件智能检测,增强服务扫描模式

当服务扫描模式检测到常见Web端口时,自动包含WebPoc和WebTitle插件,
提升Web服务扫描覆盖率的同时保持完全向后兼容性。

主要改进:
- 新增WebPortDetector智能检测Web服务
- 服务扫描模式自动包含Web插件当检测到Web端口时
- 创建WebPoc和WebTitle插件适配器
- 保持所有现有功能和参数行为不变
This commit is contained in:
ZacharyZcR 2025-08-09 18:03:48 +08:00
parent 798d4e211a
commit 143801ff58
4 changed files with 288 additions and 0 deletions

View File

@ -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 {
// 自定义模式下运行所有明确指定的插件

145
Core/WebDetection.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}