mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 11:20:41 +08:00
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package common
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
/*
|
|
globals.go - 全局配置变量
|
|
|
|
运行时数据和必要的全局状态。
|
|
命令行参数现通过 GetFlagVars() 访问,配置通过 GetGlobalConfig() 访问。
|
|
*/
|
|
|
|
// =============================================================================
|
|
// 核心数据结构
|
|
// =============================================================================
|
|
|
|
// HostInfo 主机信息结构 - 最核心的数据结构
|
|
type HostInfo struct {
|
|
Host string // 主机地址
|
|
Port int // 端口号(单个端口)
|
|
URL string // URL地址
|
|
Info []string // 附加信息
|
|
}
|
|
|
|
// Target 返回 host:port 格式字符串
|
|
func (h *HostInfo) Target() string {
|
|
return fmt.Sprintf("%s:%d", h.Host, h.Port)
|
|
}
|
|
|
|
// =============================================================================
|
|
// 默认配置常量
|
|
// =============================================================================
|
|
|
|
const (
|
|
// DefaultThreadNum 默认线程数
|
|
DefaultThreadNum = 600
|
|
// DefaultTimeout 默认超时时间(秒)
|
|
DefaultTimeout = 3
|
|
// DefaultScanMode 默认扫描模式
|
|
DefaultScanMode = "all"
|
|
// DefaultLanguage 默认语言
|
|
DefaultLanguage = "zh"
|
|
// DefaultLogLevel 默认日志级别
|
|
DefaultLogLevel = "base"
|
|
)
|
|
|
|
// 日志级别常量
|
|
const (
|
|
LogLevelAll = "all"
|
|
LogLevelError = "error"
|
|
LogLevelBase = "base"
|
|
LogLevelInfo = "info"
|
|
LogLevelSuccess = "success"
|
|
LogLevelDebug = "debug"
|
|
LogLevelInfoSuccess = "info,success"
|
|
LogLevelBaseInfoSuccess = "base,info,success"
|
|
)
|
|
|
|
const version = "2.1.0"
|
|
|
|
// 运行时数据已迁移到Config对象中,使用GetGlobalConfig()访问
|
|
|
|
// Shell状态已迁移到State对象中,使用GetGlobalState()访问
|
|
|
|
// POC配置、输出控制、发包控制、初始化已迁移到Config/State对象中
|
|
|
|
// =============================================================================
|
|
// 发包限制错误类型
|
|
// =============================================================================
|
|
|
|
// 哨兵错误 - 用于 errors.Is 判断
|
|
var (
|
|
ErrMaxPacketReached = errors.New("max packet count reached")
|
|
ErrPacketRateLimited = errors.New("packet rate limited")
|
|
)
|
|
|
|
// PacketLimitError 发包限制错误(包含详情)
|
|
type PacketLimitError struct {
|
|
Sentinel error // ErrMaxPacketReached 或 ErrPacketRateLimited
|
|
Limit int64
|
|
Current int64
|
|
}
|
|
|
|
func (e *PacketLimitError) Error() string {
|
|
if e.Sentinel == ErrMaxPacketReached {
|
|
return fmt.Sprintf("已达到最大发包数量限制: %d", e.Limit)
|
|
}
|
|
return fmt.Sprintf("发包速率受限: %d包/分钟", e.Limit)
|
|
}
|
|
|
|
func (e *PacketLimitError) Unwrap() error {
|
|
return e.Sentinel
|
|
}
|
|
|
|
// =============================================================================
|
|
// 发包频率控制功能
|
|
// =============================================================================
|
|
|
|
// CanSendPacketWith 检查是否可以发包 - 同时检查频率限制和总数限制
|
|
// 返回值: (可以发包, 错误)
|
|
func CanSendPacketWith(config *Config, state *State) (bool, error) {
|
|
// 检查总数限制
|
|
maxPacketCount := config.Network.MaxPacketCount
|
|
if maxPacketCount > 0 {
|
|
currentTotal := state.GetPacketCount()
|
|
if currentTotal >= maxPacketCount {
|
|
return false, &PacketLimitError{
|
|
Sentinel: ErrMaxPacketReached,
|
|
Limit: maxPacketCount,
|
|
Current: currentTotal,
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查频率限制
|
|
return state.CheckAndIncrementPacketRate(config.Network.PacketRateLimit)
|
|
}
|
|
|
|
// CanSendPacket 便捷API - 使用全局配置和状态
|
|
// 内部调用 CanSendPacketWith,保持向后兼容(返回string)
|
|
func CanSendPacket() (bool, string) {
|
|
ok, err := CanSendPacketWith(GetGlobalConfig(), GetGlobalState())
|
|
if err != nil {
|
|
return ok, err.Error()
|
|
}
|
|
return ok, ""
|
|
}
|
|
|
|
// =============================================================================
|
|
// 全局 Config 和 State 实例(新架构)
|
|
// =============================================================================
|
|
|
|
var (
|
|
// globalConfig 全局配置实例(小写,不直接暴露)
|
|
globalConfig *Config
|
|
|
|
// globalState 全局状态实例(小写,不直接暴露)
|
|
globalState *State
|
|
|
|
// globalMu 保护全局变量的读写锁
|
|
globalMu sync.RWMutex
|
|
)
|
|
|
|
// GetGlobalConfig 获取全局配置实例(线程安全)
|
|
// 使用读写锁保护,避免竞态条件
|
|
func GetGlobalConfig() *Config {
|
|
globalMu.RLock()
|
|
cfg := globalConfig
|
|
globalMu.RUnlock()
|
|
|
|
if cfg != nil {
|
|
return cfg
|
|
}
|
|
|
|
// 需要初始化,获取写锁
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
|
|
// 双重检查,避免重复初始化
|
|
if globalConfig == nil {
|
|
globalConfig = NewConfig()
|
|
}
|
|
return globalConfig
|
|
}
|
|
|
|
// SetGlobalConfig 设置全局配置实例(线程安全)
|
|
func SetGlobalConfig(cfg *Config) {
|
|
globalMu.Lock()
|
|
globalConfig = cfg
|
|
globalMu.Unlock()
|
|
}
|
|
|
|
// GetGlobalState 获取全局状态实例(线程安全)
|
|
// 使用读写锁保护,避免竞态条件
|
|
func GetGlobalState() *State {
|
|
globalMu.RLock()
|
|
st := globalState
|
|
globalMu.RUnlock()
|
|
|
|
if st != nil {
|
|
return st
|
|
}
|
|
|
|
// 需要初始化,获取写锁
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
|
|
// 双重检查,避免重复初始化
|
|
if globalState == nil {
|
|
globalState = NewState()
|
|
}
|
|
return globalState
|
|
}
|
|
|
|
// SetGlobalState 设置全局状态实例(线程安全)
|
|
func SetGlobalState(state *State) {
|
|
globalMu.Lock()
|
|
globalState = state
|
|
globalMu.Unlock()
|
|
}
|
|
|
|
// =============================================================================
|
|
// 字符串工具函数
|
|
// =============================================================================
|
|
|
|
// ContainsAny 检查字符串是否包含任意一个子串
|
|
func ContainsAny(s string, substrs ...string) bool {
|
|
for _, substr := range substrs {
|
|
if strings.Contains(s, substr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|