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

- 更新插件初始化器集成三个插件系统(Service、Web、Local) - 清理WebPOC插件:移除重复端口检测和模拟漏洞数据 - 简化WebTitle插件:去除过度设计的WebInfo结构和技术检测 - 移除Web插件系统中的冗余辅助函数 - 统一插件接口实现,提升代码一致性
135 lines
2.7 KiB
Go
135 lines
2.7 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 := "http"
|
|
if info.Ports == "443" || info.Ports == "8443" {
|
|
protocol = "https"
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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()
|
|
})
|
|
}
|