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

主要改进: 1. 修复Services插件端口数据重复问题 - 删除插件结构体中的ports字段和GetPorts()方法 - 系统统一使用注册时的端口信息 2. 引入BasePlugin基础结构体 - 消除51个插件中重复的name字段和Name()方法 - 统一插件基础功能,简化代码维护 3. 统一插件接口设计 - 保持向后兼容,功能完全不变 - 代码更简洁,符合工程最佳实践 影响范围: - services插件:29个文件简化 - web插件:2个文件简化 - local插件:21个文件简化 - 总计删除约150行重复代码
148 lines
3.1 KiB
Go
148 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"
|
||
"github.com/shadow1ng/fscan/plugins"
|
||
)
|
||
|
||
// WebTitlePlugin Web标题获取插件
|
||
type WebTitlePlugin struct {
|
||
plugins.BasePlugin
|
||
}
|
||
|
||
// NewWebTitlePlugin 创建WebTitle插件
|
||
func NewWebTitlePlugin() *WebTitlePlugin {
|
||
return &WebTitlePlugin{
|
||
BasePlugin: plugins.NewBasePlugin("webtitle"),
|
||
}
|
||
}
|
||
|
||
|
||
// 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()
|
||
})
|
||
}
|