mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
refactor: 简化Web插件系统并移除冗余代码
- 更新插件初始化器集成三个插件系统(Service、Web、Local) - 清理WebPOC插件:移除重复端口检测和模拟漏洞数据 - 简化WebTitle插件:去除过度设计的WebInfo结构和技术检测 - 移除Web插件系统中的冗余辅助函数 - 统一插件接口实现,提升代码一致性
This commit is contained in:
parent
91aa99a11a
commit
8a79f3cf0f
@ -1,10 +1,13 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/plugins/services"
|
"github.com/shadow1ng/fscan/plugins/services"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/web"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/local"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initializer 初始化器接口
|
// Initializer 初始化器接口
|
||||||
@ -21,23 +24,30 @@ func (p *PluginInitializer) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PluginInitializer) Initialize() error {
|
func (p *PluginInitializer) Initialize() error {
|
||||||
var localPlugins []string
|
// 直接调用三个包的函数
|
||||||
|
servicePlugins := services.GetAllPlugins()
|
||||||
|
webPlugins := web.GetAllWebPlugins()
|
||||||
|
localPlugins := local.GetAllLocalPlugins()
|
||||||
|
|
||||||
// 获取所有注册的插件
|
// 排序
|
||||||
allPlugins := services.GetAllPlugins()
|
sort.Strings(servicePlugins)
|
||||||
|
sort.Strings(webPlugins)
|
||||||
for _, pluginName := range allPlugins {
|
|
||||||
// 新插件系统中local插件在plugins/local目录中,暂时跳过分类
|
|
||||||
// 后续可以通过插件名或其他方式区分
|
|
||||||
localPlugins = append(localPlugins, pluginName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 排序以保持一致性
|
|
||||||
sort.Strings(localPlugins)
|
sort.Strings(localPlugins)
|
||||||
|
|
||||||
|
// 合并所有插件
|
||||||
|
var allPlugins []string
|
||||||
|
allPlugins = append(allPlugins, servicePlugins...)
|
||||||
|
allPlugins = append(allPlugins, webPlugins...)
|
||||||
|
allPlugins = append(allPlugins, localPlugins...)
|
||||||
|
sort.Strings(allPlugins)
|
||||||
|
|
||||||
// 设置全局变量
|
// 设置全局变量
|
||||||
common.LocalPluginsList = localPlugins
|
common.LocalPluginsList = localPlugins
|
||||||
|
|
||||||
|
// 记录插件统计
|
||||||
|
common.LogInfo(fmt.Sprintf("插件系统初始化完成: Service(%d) Web(%d) Local(%d) Total(%d)",
|
||||||
|
len(servicePlugins), len(webPlugins), len(localPlugins), len(allPlugins)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
@ -18,11 +17,11 @@ type WebPlugin interface {
|
|||||||
// WebScanResult Web扫描结果
|
// WebScanResult Web扫描结果
|
||||||
type WebScanResult struct {
|
type WebScanResult struct {
|
||||||
Success bool
|
Success bool
|
||||||
Title string // 网页标题
|
Title string // 网页标题
|
||||||
Status int // HTTP状态码
|
Status int // HTTP状态码
|
||||||
Server string // 服务器信息
|
Server string // 服务器信息
|
||||||
Length int // 响应长度
|
Length int // 响应长度
|
||||||
VulInfo string // 漏洞信息(如果有)
|
VulInfo string // 漏洞信息(如果有)
|
||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,49 +38,14 @@ func RegisterWebPlugin(name string, creator func() WebPlugin) {
|
|||||||
webPluginRegistry[name] = creator
|
webPluginRegistry[name] = creator
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWebPlugin 获取指定Web插件
|
|
||||||
func GetWebPlugin(name string) WebPlugin {
|
|
||||||
webPluginMutex.RLock()
|
|
||||||
defer webPluginMutex.RUnlock()
|
|
||||||
|
|
||||||
if creator, exists := webPluginRegistry[name]; exists {
|
|
||||||
return creator()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllWebPlugins 获取所有已注册Web插件的名称
|
// GetAllWebPlugins 获取所有已注册Web插件的名称
|
||||||
func GetAllWebPlugins() []string {
|
func GetAllWebPlugins() []string {
|
||||||
webPluginMutex.RLock()
|
webPluginMutex.RLock()
|
||||||
defer webPluginMutex.RUnlock()
|
defer webPluginMutex.RUnlock()
|
||||||
|
|
||||||
var plugins []string
|
var plugins []string
|
||||||
for name := range webPluginRegistry {
|
for name := range webPluginRegistry {
|
||||||
plugins = append(plugins, name)
|
plugins = append(plugins, name)
|
||||||
}
|
}
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWebPort 判断是否为Web端口
|
|
||||||
func IsWebPort(port int) bool {
|
|
||||||
webPorts := []int{80, 443, 8080, 8443, 8000, 8888, 9000, 9090, 3000, 5000}
|
|
||||||
for _, p := range webPorts {
|
|
||||||
if p == port {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildWebURL 构建Web URL
|
|
||||||
func BuildWebURL(host string, port int) string {
|
|
||||||
scheme := "http"
|
|
||||||
if port == 443 || port == 8443 {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
if port == 80 || port == 443 {
|
|
||||||
return fmt.Sprintf("%s://%s", scheme, host)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s://%s:%d", scheme, host, port)
|
|
||||||
}
|
|
@ -8,17 +8,15 @@ import (
|
|||||||
"github.com/shadow1ng/fscan/webscan"
|
"github.com/shadow1ng/fscan/webscan"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebPocPlugin Web漏洞扫描插件 - 执行POC检测
|
// WebPocPlugin Web漏洞扫描插件
|
||||||
type WebPocPlugin struct {
|
type WebPocPlugin struct {
|
||||||
name string
|
name string
|
||||||
ports []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebPocPlugin 创建Web POC插件
|
// NewWebPocPlugin 创建Web POC插件
|
||||||
func NewWebPocPlugin() *WebPocPlugin {
|
func NewWebPocPlugin() *WebPocPlugin {
|
||||||
return &WebPocPlugin{
|
return &WebPocPlugin{
|
||||||
name: "webpoc",
|
name: "webpoc",
|
||||||
ports: []int{80, 443, 8080, 8443, 8000, 8888}, // 常见Web端口
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,16 +25,13 @@ func (p *WebPocPlugin) GetName() string {
|
|||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPorts 实现Plugin接口
|
// GetPorts 实现Plugin接口 - Web插件不需要预定义端口,依赖全局检测器
|
||||||
func (p *WebPocPlugin) GetPorts() []int {
|
func (p *WebPocPlugin) GetPorts() []int {
|
||||||
return p.ports
|
return []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan 执行Web POC扫描
|
// Scan 执行Web POC扫描
|
||||||
func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult {
|
func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
|
|
||||||
// 检查是否禁用POC扫描
|
|
||||||
if common.DisablePocScan {
|
if common.DisablePocScan {
|
||||||
return &WebScanResult{
|
return &WebScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -44,69 +39,15 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为Web端口
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
if !p.isWebPort(info.Ports) {
|
|
||||||
return &WebScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("端口 %s 不是常见Web端口", info.Ports),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogSuccess(fmt.Sprintf("WebPOC %s 开始扫描", target))
|
common.LogSuccess(fmt.Sprintf("WebPOC %s 开始扫描", target))
|
||||||
|
|
||||||
// 执行Web POC扫描
|
// 直接执行Web扫描
|
||||||
results := p.runWebScan(ctx, info)
|
|
||||||
if len(results) > 0 {
|
|
||||||
common.LogSuccess(fmt.Sprintf("WebPOC %s 发现 %d 个漏洞", target, len(results)))
|
|
||||||
return &WebScanResult{
|
|
||||||
Success: true,
|
|
||||||
VulInfo: fmt.Sprintf("发现 %d 个Web漏洞", len(results)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &WebScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("未发现Web漏洞"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// isWebPort 检查是否为Web端口
|
|
||||||
func (p *WebPocPlugin) isWebPort(port string) bool {
|
|
||||||
webPorts := map[string]bool{
|
|
||||||
"80": true, "443": true, "8080": true, "8443": true,
|
|
||||||
"8000": true, "8888": true, "9000": true, "9090": true,
|
|
||||||
"3000": true, "5000": true, "8001": true, "8008": true,
|
|
||||||
"8081": true, "8082": true, "8083": true, "8090": true,
|
|
||||||
"9001": true, "9080": true, "9999": true, "10000": true,
|
|
||||||
}
|
|
||||||
return webPorts[port]
|
|
||||||
}
|
|
||||||
|
|
||||||
// runWebScan 执行Web扫描并返回结果
|
|
||||||
func (p *WebPocPlugin) runWebScan(ctx context.Context, info *common.HostInfo) []string {
|
|
||||||
// 执行Web扫描
|
|
||||||
WebScan.WebScan(info)
|
WebScan.WebScan(info)
|
||||||
|
|
||||||
// 简化实现:返回模拟的扫描结果
|
|
||||||
// 实际中会通过其他方式捕获WebScan的输出
|
|
||||||
var results []string
|
|
||||||
results = append(results, "WebPOC扫描完成")
|
|
||||||
results = append(results, "检测到潜在漏洞:SQL注入")
|
|
||||||
results = append(results, "检测到潜在漏洞:XSS")
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// identifyService 服务识别 - Web服务检测
|
|
||||||
func (p *WebPocPlugin) identifyService(ctx context.Context, info *common.HostInfo) *WebScanResult {
|
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
banner := "Web应用程序"
|
|
||||||
common.LogSuccess(fmt.Sprintf("WebPOC %s %s", target, banner))
|
|
||||||
|
|
||||||
return &WebScanResult{
|
return &WebScanResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
VulInfo: banner,
|
VulInfo: "WebPOC扫描完成",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,17 +14,15 @@ import (
|
|||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebTitlePlugin Web标题获取插件 - 收集Web应用信息和指纹识别
|
// WebTitlePlugin Web标题获取插件
|
||||||
type WebTitlePlugin struct {
|
type WebTitlePlugin struct {
|
||||||
name string
|
name string
|
||||||
ports []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebTitlePlugin 创建WebTitle插件
|
// NewWebTitlePlugin 创建WebTitle插件
|
||||||
func NewWebTitlePlugin() *WebTitlePlugin {
|
func NewWebTitlePlugin() *WebTitlePlugin {
|
||||||
return &WebTitlePlugin{
|
return &WebTitlePlugin{
|
||||||
name: "webtitle",
|
name: "webtitle",
|
||||||
ports: []int{80, 443, 8080, 8443, 8000, 8888, 9000, 9090}, // 常见Web端口
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,32 +31,16 @@ func (p *WebTitlePlugin) GetName() string {
|
|||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPorts 实现Plugin接口
|
// GetPorts 实现Plugin接口 - Web插件不需要预定义端口
|
||||||
func (p *WebTitlePlugin) GetPorts() []int {
|
func (p *WebTitlePlugin) GetPorts() []int {
|
||||||
return p.ports
|
return []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan 执行WebTitle扫描 - Web服务识别和指纹获取
|
// Scan 执行WebTitle扫描
|
||||||
func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult {
|
func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
// 检查是否为Web端口
|
title, status, server, err := p.getWebTitle(ctx, info)
|
||||||
webPorts := map[string]bool{
|
|
||||||
"80": true, "443": true, "8080": true, "8443": true,
|
|
||||||
"8000": true, "8888": true, "9000": true, "9090": true,
|
|
||||||
"8001": true, "8081": true, "8008": true, "8086": true,
|
|
||||||
"9001": true, "8089": true, "8090": true, "8082": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !webPorts[info.Ports] {
|
|
||||||
return &WebScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("非Web端口"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Web信息
|
|
||||||
webInfo, err := p.getWebInfo(ctx, info)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &WebScanResult{
|
return &WebScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -66,168 +48,77 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !webInfo.Valid {
|
|
||||||
return &WebScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("未发现有效的Web服务"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录Web信息发现
|
|
||||||
msg := fmt.Sprintf("WebTitle %s", target)
|
msg := fmt.Sprintf("WebTitle %s", target)
|
||||||
if webInfo.Title != "" {
|
if title != "" {
|
||||||
msg += fmt.Sprintf(" [%s]", webInfo.Title)
|
msg += fmt.Sprintf(" [%s]", title)
|
||||||
}
|
}
|
||||||
if webInfo.StatusCode != 0 {
|
if status != 0 {
|
||||||
msg += fmt.Sprintf(" %d", webInfo.StatusCode)
|
msg += fmt.Sprintf(" %d", status)
|
||||||
}
|
}
|
||||||
if webInfo.Server != "" {
|
if server != "" {
|
||||||
msg += fmt.Sprintf(" %s", webInfo.Server)
|
msg += fmt.Sprintf(" %s", server)
|
||||||
}
|
}
|
||||||
common.LogSuccess(msg)
|
common.LogSuccess(msg)
|
||||||
|
|
||||||
return &WebScanResult{
|
return &WebScanResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Title: webInfo.Title,
|
Title: title,
|
||||||
Status: webInfo.StatusCode,
|
Status: status,
|
||||||
Server: webInfo.Server,
|
Server: server,
|
||||||
Length: int(webInfo.ContentLength),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// WebInfo Web信息结构
|
func (p *WebTitlePlugin) getWebTitle(ctx context.Context, info *common.HostInfo) (string, int, string, error) {
|
||||||
type WebInfo struct {
|
|
||||||
Valid bool
|
|
||||||
URL string
|
|
||||||
StatusCode int
|
|
||||||
Title string
|
|
||||||
Server string
|
|
||||||
ContentType string
|
|
||||||
ContentLength int64
|
|
||||||
Technologies []string
|
|
||||||
SecurityHeaders map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary 返回Web信息摘要
|
|
||||||
func (wi *WebInfo) Summary() string {
|
|
||||||
if !wi.Valid {
|
|
||||||
return "Web服务检测失败"
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts []string
|
|
||||||
|
|
||||||
if wi.StatusCode != 0 {
|
|
||||||
parts = append(parts, fmt.Sprintf("HTTP %d", wi.StatusCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
if wi.Title != "" {
|
|
||||||
title := wi.Title
|
|
||||||
if len(title) > 30 {
|
|
||||||
title = title[:30] + "..."
|
|
||||||
}
|
|
||||||
parts = append(parts, fmt.Sprintf("[%s]", title))
|
|
||||||
}
|
|
||||||
|
|
||||||
if wi.Server != "" {
|
|
||||||
parts = append(parts, wi.Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWebInfo 获取Web服务信息
|
|
||||||
func (p *WebTitlePlugin) getWebInfo(ctx context.Context, info *common.HostInfo) (*WebInfo, error) {
|
|
||||||
webInfo := &WebInfo{
|
|
||||||
Valid: false,
|
|
||||||
SecurityHeaders: make(map[string]string),
|
|
||||||
Technologies: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确定协议
|
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if info.Ports == "443" || info.Ports == "8443" {
|
if info.Ports == "443" || info.Ports == "8443" {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
webInfo.URL = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
|
url := fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
|
||||||
|
|
||||||
// 创建HTTP客户端
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: time.Duration(common.WebTimeout) * time.Second,
|
Timeout: time.Duration(common.WebTimeout) * time.Second,
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送HTTP请求
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", webInfo.URL, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return webInfo, fmt.Errorf("创建HTTP请求失败: %v", err)
|
return "", 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置User-Agent
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return webInfo, fmt.Errorf("HTTP请求失败: %v", err)
|
return "", 0, "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
webInfo.Valid = true
|
|
||||||
webInfo.StatusCode = resp.StatusCode
|
|
||||||
|
|
||||||
// 获取基本响应头信息
|
|
||||||
if server := resp.Header.Get("Server"); server != "" {
|
|
||||||
webInfo.Server = server
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentType := resp.Header.Get("Content-Type"); contentType != "" {
|
|
||||||
webInfo.ContentType = contentType
|
|
||||||
}
|
|
||||||
|
|
||||||
webInfo.ContentLength = resp.ContentLength
|
|
||||||
|
|
||||||
// 检测安全头
|
|
||||||
p.detectSecurityHeaders(resp, webInfo)
|
|
||||||
|
|
||||||
// 读取响应内容
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return webInfo, fmt.Errorf("读取响应内容失败: %v", err)
|
return "", resp.StatusCode, resp.Header.Get("Server"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取页面标题
|
title := p.extractTitle(string(body))
|
||||||
webInfo.Title = p.extractTitle(string(body))
|
return title, resp.StatusCode, resp.Header.Get("Server"), nil
|
||||||
|
|
||||||
// 检测Web技术
|
|
||||||
p.detectTechnologies(resp, string(body), webInfo)
|
|
||||||
|
|
||||||
return webInfo, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractTitle 提取页面标题
|
|
||||||
func (p *WebTitlePlugin) extractTitle(html string) string {
|
func (p *WebTitlePlugin) extractTitle(html string) string {
|
||||||
// 正则表达式匹配title标签
|
|
||||||
titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
|
titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
|
||||||
matches := titleRe.FindStringSubmatch(html)
|
matches := titleRe.FindStringSubmatch(html)
|
||||||
|
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
title := strings.TrimSpace(matches[1])
|
title := strings.TrimSpace(matches[1])
|
||||||
|
|
||||||
// 移除多余的空白字符
|
|
||||||
title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ")
|
title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ")
|
||||||
|
|
||||||
// 长度限制
|
|
||||||
if len(title) > 100 {
|
if len(title) > 100 {
|
||||||
title = title[:100] + "..."
|
title = title[:100] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保是有效的UTF-8
|
|
||||||
if utf8.ValidString(title) {
|
if utf8.ValidString(title) {
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
@ -236,82 +127,6 @@ func (p *WebTitlePlugin) extractTitle(html string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectSecurityHeaders 检测安全头
|
|
||||||
func (p *WebTitlePlugin) detectSecurityHeaders(resp *http.Response, webInfo *WebInfo) {
|
|
||||||
securityHeaders := []string{
|
|
||||||
"X-Frame-Options",
|
|
||||||
"X-XSS-Protection",
|
|
||||||
"X-Content-Type-Options",
|
|
||||||
"Strict-Transport-Security",
|
|
||||||
"Content-Security-Policy",
|
|
||||||
"X-Powered-By",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, header := range securityHeaders {
|
|
||||||
if value := resp.Header.Get(header); value != "" {
|
|
||||||
webInfo.SecurityHeaders[header] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectTechnologies 检测Web技术
|
|
||||||
func (p *WebTitlePlugin) detectTechnologies(resp *http.Response, body string, webInfo *WebInfo) {
|
|
||||||
// 基于响应头检测
|
|
||||||
if xPoweredBy := resp.Header.Get("X-Powered-By"); xPoweredBy != "" {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, fmt.Sprintf("X-Powered-By: %s", xPoweredBy))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基于Server头检测
|
|
||||||
if server := resp.Header.Get("Server"); server != "" {
|
|
||||||
if strings.Contains(strings.ToLower(server), "apache") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Apache HTTP Server")
|
|
||||||
} else if strings.Contains(strings.ToLower(server), "nginx") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Nginx")
|
|
||||||
} else if strings.Contains(strings.ToLower(server), "iis") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Microsoft IIS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基于HTML内容检测
|
|
||||||
bodyLower := strings.ToLower(body)
|
|
||||||
|
|
||||||
// 检测常见框架
|
|
||||||
if strings.Contains(bodyLower, "wordpress") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "WordPress")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(bodyLower, "joomla") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Joomla")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(bodyLower, "drupal") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Drupal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测JavaScript框架
|
|
||||||
if strings.Contains(bodyLower, "jquery") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "jQuery")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(bodyLower, "angular") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "AngularJS")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(bodyLower, "react") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "React")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测Web服务器相关
|
|
||||||
if strings.Contains(bodyLower, "apache tomcat") || strings.Contains(bodyLower, "tomcat") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Apache Tomcat")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(bodyLower, "jetty") {
|
|
||||||
webInfo.Technologies = append(webInfo.Technologies, "Eclipse Jetty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// init 自动注册Web插件
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterWebPlugin("webtitle", func() WebPlugin {
|
RegisterWebPlugin("webtitle", func() WebPlugin {
|
||||||
return NewWebTitlePlugin()
|
return NewWebTitlePlugin()
|
||||||
|
Loading…
Reference in New Issue
Block a user