fscan/core/WebDetection.go
ZacharyZcR a3177b28a6 fix: 修复插件系统逻辑Bug和架构问题
主要修复:
1. 修复时间显示Bug - StartTime初始化问题
2. 修复Web智能探测错误检测预定义端口而非用户指定端口
3. 修复本地插件被错误调用到端口扫描中的问题
4. 修复host:port格式双重处理导致的多余端口扫描
5. 统一插件过滤逻辑,消除接口不一致性
6. 优化Web检测缓存机制,减少重复HTTP请求

技术改进:
- 重构插件适用性检查逻辑,确保策略过滤器正确工作
- 区分Web检测的自动发现模式和用户指定端口模式
- 在解析阶段正确处理host:port格式,避免与默认端口冲突
- 完善缓存机制,提升性能

测试验证:
- ./fscan -h 127.0.0.1:3306 现在只检测3306端口
- 本地插件不再参与端口扫描
- Web检测只对指定端口进行协议检测
- 时间显示正确
2025-09-01 23:50:32 +00:00

399 lines
10 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 core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
)
// WebPortDetector Web端口检测器
type WebPortDetector struct {
// 常见Web端口列表
commonWebPorts map[int]bool
// HTTP检测超时时间
httpTimeout time.Duration
// HTTP客户端
httpClient *http.Client
// HTTPS客户端
httpsClient *http.Client
// 检测结果缓存(避免重复检测)
detectionCache map[string]bool
// 缓存互斥锁
cacheMutex sync.RWMutex
}
// NewWebPortDetector 创建Web端口检测器
func NewWebPortDetector() *WebPortDetector {
timeout := 3 * time.Second
// 定义常见Web端口扩展列表
commonPorts := map[int]bool{
// 标准Web端口
80: true, // HTTP
443: true, // HTTPS
8080: true, // HTTP alternate
8443: true, // HTTPS alternate
// 开发服务器端口
3000: true, // Node.js dev server
4000: true, // Ruby dev server
5000: true, // Python dev server
8000: true, // Development server
8888: true, // Common dev port
9000: true, // Common dev port
9090: true, // Common dev port
// Web服务器alternate端口
8081: true, 8082: true, 8083: true, 8084: true, 8085: true,
8086: true, 8087: true, 8088: true, 8089: true,
// 企业级Web端口
9080: true, // WebSphere
9443: true, // WebSphere SSL
7001: true, // WebLogic
7002: true, // WebLogic SSL
// 应用服务器端口
8180: true, // Tomcat alternate
8181: true, // Tomcat alternate
8282: true, // Common alternate
8383: true, // Common alternate
8484: true, // Common alternate
8585: true, // Common alternate
// 代理和负载均衡端口
3128: true, // Squid proxy
8008: true, // HTTP proxy
8118: true, // Privoxy
// 监控和管理界面
9200: true, // Elasticsearch
5601: true, // Kibana
3001: true, // Grafana alternate
9091: true, // Prometheus
}
// 创建HTTP客户端
httpClient := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: timeout,
}).DialContext,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableKeepAlives: true,
MaxConnsPerHost: 5,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不跟随重定向,减少检测时间
},
}
// 创建HTTPS客户端跳过证书验证
httpsClient := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: timeout,
}).DialContext,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableKeepAlives: true,
MaxConnsPerHost: 5,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return &WebPortDetector{
commonWebPorts: commonPorts,
httpTimeout: timeout,
httpClient: httpClient,
httpsClient: httpsClient,
detectionCache: make(map[string]bool),
}
}
// IsWebService 智能检测端口是否运行Web服务
func (w *WebPortDetector) IsWebService(host string, port int) bool {
// 1. 快速路径常见Web端口直接返回true
if w.IsCommonWebPort(port) {
common.LogDebug(fmt.Sprintf("端口 %d 是常见Web端口启用Web插件", port))
return true
}
// 2. 缓存检查:避免重复检测同一端口
cacheKey := fmt.Sprintf("%s:%d", host, port)
w.cacheMutex.RLock()
if cached, exists := w.detectionCache[cacheKey]; exists {
w.cacheMutex.RUnlock()
common.LogDebug(fmt.Sprintf("端口 %d 使用缓存结果: %v", port, cached))
return cached
}
w.cacheMutex.RUnlock()
// 3. 智能路径对非常见端口进行HTTP协议探测
common.LogDebug(fmt.Sprintf("对端口 %d 进行智能Web检测", port))
result := w.detectHTTPService(host, port)
// 4. 缓存结果
w.cacheMutex.Lock()
w.detectionCache[cacheKey] = result
w.cacheMutex.Unlock()
common.LogDebug(fmt.Sprintf("端口 %d 智能检测结果: %v", port, result))
return result
}
// DetectHTTPServiceOnly 仅通过HTTP协议检测端口不使用预定义端口列表
func (w *WebPortDetector) DetectHTTPServiceOnly(host string, port int) bool {
// 缓存检查:避免重复检测同一端口
cacheKey := fmt.Sprintf("%s:%d", host, port)
w.cacheMutex.RLock()
if cached, exists := w.detectionCache[cacheKey]; exists {
w.cacheMutex.RUnlock()
return cached
}
w.cacheMutex.RUnlock()
// 跳过预定义端口检查,直接进行协议探测
result := w.detectHTTPService(host, port)
// 缓存结果
w.cacheMutex.Lock()
w.detectionCache[cacheKey] = result
w.cacheMutex.Unlock()
return result
}
// 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和HTTPS
httpChan := make(chan bool, 1)
httpsChan := make(chan bool, 1)
// 检测HTTP
go func() {
httpChan <- w.tryHTTPConnection(ctx, host, port, "http")
}()
// 检测HTTPS对于高端口常见
go func() {
httpsChan <- w.tryHTTPConnection(ctx, host, port, "https")
}()
// 等待任一协议检测成功
select {
case result := <-httpChan:
if result {
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTP服务", port))
return true
}
case <-ctx.Done():
return false
}
select {
case result := <-httpsChan:
if result {
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTPS服务", port))
return true
}
case <-ctx.Done():
return false
}
return false
}
// tryHTTPConnection 尝试HTTP连接
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
// 构造URL
var url string
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
url = fmt.Sprintf("%s://%s", protocol, host)
} else {
url = fmt.Sprintf("%s://%s:%d", protocol, host, port)
}
// 选择客户端
var client *http.Client
if protocol == "https" {
client = w.httpsClient
} else {
client = w.httpClient
}
// 发送HEAD请求最小化网络开销
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false
}
req.Header.Set("User-Agent", "fscan-web-detector/2.2")
req.Header.Set("Accept", "*/*")
resp, err := client.Do(req)
if err != nil {
// 检查错误类型某些错误也表明是HTTP服务
return w.analyzeHTTPError(err)
}
defer resp.Body.Close()
// 分析HTTP响应
return w.analyzeHTTPResponse(resp, url)
}
// analyzeHTTPError 分析HTTP错误判断是否表明存在HTTP服务
func (w *WebPortDetector) analyzeHTTPError(err error) bool {
errStr := strings.ToLower(err.Error())
// 先检查明确的非Web服务错误
nonWebErrors := []string{
"connection refused",
"no such host",
"network unreachable",
"timeout",
"deadline exceeded",
"connection reset",
"eof",
}
for _, nonWebErr := range nonWebErrors {
if strings.Contains(errStr, nonWebErr) {
return false
}
}
// 这些错误表明连接到了HTTP服务器只是协议或其他问题
webIndicators := []string{
"malformed http response",
"unexpected http response",
"ssl handshake",
"certificate",
"tls",
"bad request",
"method not allowed",
"server gave http response",
}
for _, indicator := range webIndicators {
if strings.Contains(errStr, indicator) {
return true
}
}
return false
}
// analyzeHTTPResponse 分析HTTP响应判断是否为Web服务
func (w *WebPortDetector) analyzeHTTPResponse(resp *http.Response, url string) bool {
// 任何HTTP状态码都表明这是Web服务
if resp.StatusCode > 0 {
// 分析响应头获取更多信息
serverHeader := resp.Header.Get("Server")
contentType := resp.Header.Get("Content-Type")
// 记录检测到的Web服务器信息
if serverHeader != "" {
common.LogDebug(fmt.Sprintf("检测到Web服务器: %s (Server: %s)", url, serverHeader))
}
// 特殊处理某些非Web服务也会返回HTTP响应
if w.isNonWebHTTPService(serverHeader, contentType) {
common.LogDebug(fmt.Sprintf("端口返回HTTP响应但可能不是Web服务: %s", url))
return false
}
return true
}
return false
}
// isNonWebHTTPService 判断是否为非Web的HTTP服务
func (w *WebPortDetector) isNonWebHTTPService(serverHeader, contentType string) bool {
// 某些服务使用HTTP协议但不是传统Web服务
nonWebServices := []string{
"docker",
"kubernetes",
"consul",
"etcd",
"vault",
"nomad",
}
serverLower := strings.ToLower(serverHeader)
for _, service := range nonWebServices {
if strings.Contains(serverLower, service) {
return true
}
}
return false
}
// GetWebPortRange 获取可能的Web端口范围用于高级扫描
func (w *WebPortDetector) GetWebPortRange() []int {
// 返回扩展的Web端口列表用于目标发现
var ports []int
for port := range w.commonWebPorts {
ports = append(ports, port)
}
// 添加一些常见的自定义端口范围
customRanges := []int{
8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099,
9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009,
10000, 10001, 10002, 10003, 10004, 10005,
}
ports = append(ports, customRanges...)
return ports
}
// IsWebPortByPattern 基于端口模式判断是否可能是Web端口
func (w *WebPortDetector) IsWebPortByPattern(port int) bool {
portStr := strconv.Itoa(port)
// Web端口的常见模式
webPatterns := []*regexp.Regexp{
regexp.MustCompile(`^80\d{2}$`), // 80xx
regexp.MustCompile(`^90\d{2}$`), // 90xx
regexp.MustCompile(`^300\d$`), // 300x
regexp.MustCompile(`^[3-9]000$`), // x000
regexp.MustCompile(`^1[0-9]000$`), // 1x000
}
for _, pattern := range webPatterns {
if pattern.MatchString(portStr) {
return true
}
}
return false
}