mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 实现本地插件严格单个指定控制,拒绝多插件分隔符 - 修复本地插件自动调用问题,避免不必要的插件实例创建 - 添加-local与-h/-u参数的互斥性检查 - 优化插件存在性检查,使用pluginExists()替代plugins.Get() - 完善统一插件系统的端口信息管理 - 增强Web插件的协议智能检测功能 主要变更: * 本地插件现在只能通过-local参数明确指定单个插件运行 * 插件适用性检查不再创建不必要的插件实例,提升性能 * 本地扫描与网络扫描参数完全隔离,避免配置冲突
156 lines
3.3 KiB
Go
156 lines
3.3 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) GetName() string {
|
||
return p.name
|
||
}
|
||
|
||
// GetPorts 实现Plugin接口 - Web插件不需要预定义端口
|
||
func (p *WebTitlePlugin) GetPorts() []int {
|
||
return []int{}
|
||
}
|
||
|
||
// 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()
|
||
})
|
||
}
|