mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

经Linus式架构审计,发现并修复插件系统中的具体问题: ## 核心修复 ### 1. 消除local插件GetPorts()方法冗余 - 删除21个local插件中无意义的GetPorts()方法 - 简化local.Plugin接口:移除端口概念 - 理由:本地插件不涉及网络,端口概念完全多余 ### 2. 消除web插件GetPorts()方法冗余 - 删除2个web插件中无用的GetPorts()方法 - 简化web.WebPlugin接口:专注智能HTTP检测 - 理由:Web插件使用动态HTTP检测,预定义端口无价值 ### 3. 统一插件命名规范 - 统一所有插件接口使用Name()方法(符合Go惯例) - 消除GetName()与Name()不一致问题 - 简化适配器:不再需要方法名转换 ## 技术改进 接口精简: - local插件:GetName() + GetPorts() → Name() - web插件:GetName() + GetPorts() → Name() - services插件:GetName() → Name()(保留GetPorts(),业务必需) 代码减少: - 删除23个无用GetPorts()方法 - 重命名52个Name()方法 - 简化3个插件接口定义 ## 影响范围 修改文件:55个插件文件 代码变更:-155行 +61行(净减少94行) 功能影响:零破坏性,保持所有业务逻辑不变 这是基于业务需求分析的精准重构,消除真正多余的部分, 保持系统架构合理性和向后兼容性。
152 lines
3.1 KiB
Go
152 lines
3.1 KiB
Go
package web
|
||
|
||
import (
|
||
"context"
|
||
"crypto/tls"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
)
|
||
|
||
// WebTitlePlugin Web标题获取插件
|
||
type WebTitlePlugin struct {
|
||
name string
|
||
}
|
||
|
||
// NewWebTitlePlugin 创建WebTitle插件
|
||
func NewWebTitlePlugin() *WebTitlePlugin {
|
||
return &WebTitlePlugin{
|
||
name: "webtitle",
|
||
}
|
||
}
|
||
|
||
// GetName 实现Plugin接口
|
||
func (p *WebTitlePlugin) Name() string {
|
||
return p.name
|
||
}
|
||
|
||
|
||
// 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()
|
||
})
|
||
}
|