From e58a48ba9b38761abb880750abaa8a50c9048a68 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Sat, 26 Apr 2025 06:18:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=AB=E6=8F=8F?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common/Flag.go | 4 +- Common/Types.go | 21 +- Core/LocalScanner.go | 112 +++++++++ Core/PluginUtils.go | 71 ++++++ Core/Registry.go | 42 +++- Core/Scanner.go | 555 +++++++---------------------------------- Core/ServiceScanner.go | 218 ++++++++++++++++ Core/WebScanner.go | 125 ++++++++++ 8 files changed, 672 insertions(+), 476 deletions(-) create mode 100644 Core/LocalScanner.go create mode 100644 Core/PluginUtils.go create mode 100644 Core/ServiceScanner.go create mode 100644 Core/WebScanner.go diff --git a/Common/Flag.go b/Common/Flag.go index 8c9e937..bd5df7e 100644 --- a/Common/Flag.go +++ b/Common/Flag.go @@ -73,7 +73,7 @@ func Flag(Info *HostInfo) { // ═════════════════════════════════════════════════ // 扫描控制参数 // ═════════════════════════════════════════════════ - flag.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode")) + flag.StringVar(&ScanMode, "m", "all", GetText("flag_scan_mode")) flag.IntVar(&ThreadNum, "t", 10, GetText("flag_thread_num")) flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout")) flag.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num")) @@ -183,7 +183,7 @@ func FlagFromRemote(info *HostInfo, argString string) error { fs.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file")) fs.StringVar(&PortsFile, "pf", "", GetText("flag_ports_file")) - fs.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode")) + fs.StringVar(&ScanMode, "m", "all", GetText("flag_scan_mode")) fs.IntVar(&ThreadNum, "t", 10, GetText("flag_thread_num")) fs.Int64Var(&Timeout, "time", 3, GetText("flag_timeout")) fs.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num")) diff --git a/Common/Types.go b/Common/Types.go index 3c36aea..c3ec478 100644 --- a/Common/Types.go +++ b/Common/Types.go @@ -8,13 +8,32 @@ type HostInfo struct { Infostr []string } +// 在 Common/const.go 中添加 +// 插件类型常量 +const ( + PluginTypeService = "service" // 服务类型插件 + PluginTypeWeb = "web" // Web类型插件 + PluginTypeLocal = "local" // 本地类型插件 +) + // ScanPlugin 定义扫描插件的结构 type ScanPlugin struct { Name string // 插件名称 - Ports []int // 关联的端口列表,空切片表示特殊扫描类型 + Ports []int // 适用端口 + Types []string // 插件类型标签,一个插件可以有多个类型 ScanFunc func(*HostInfo) error // 扫描函数 } +// 添加一个用于检查插件类型的辅助方法 +func (p ScanPlugin) HasType(typeName string) bool { + for _, t := range p.Types { + if t == typeName { + return true + } + } + return false +} + // HasPort 检查插件是否支持指定端口 func (p *ScanPlugin) HasPort(port int) bool { // 如果没有指定端口列表,表示支持所有端口 diff --git a/Core/LocalScanner.go b/Core/LocalScanner.go new file mode 100644 index 0000000..81cd264 --- /dev/null +++ b/Core/LocalScanner.go @@ -0,0 +1,112 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "strings" + "sync" +) + +// LocalScanStrategy 本地扫描策略 +type LocalScanStrategy struct{} + +// NewLocalScanStrategy 创建新的本地扫描策略 +func NewLocalScanStrategy() *LocalScanStrategy { + return &LocalScanStrategy{} +} + +// Name 返回策略名称 +func (s *LocalScanStrategy) Name() string { + return "本地扫描" +} + +// Description 返回策略描述 +func (s *LocalScanStrategy) Description() string { + return "收集本地系统信息" +} + +// Execute 执行本地扫描策略 +func (s *LocalScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + Common.LogInfo("执行本地信息收集") + + // 验证插件配置 + if err := validateScanPlugins(); err != nil { + Common.LogError(err.Error()) + return + } + + // 输出插件信息 + s.LogPluginInfo() + + // 准备目标(本地扫描通常只有一个目标,即本机) + targets := s.PrepareTargets(info) + + // 执行扫描任务 + ExecuteScanTasks(targets, s, ch, wg) +} + +// PrepareTargets 准备本地扫描目标 +func (s *LocalScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo { + // 本地扫描只使用传入的目标信息,不做额外处理 + return []Common.HostInfo{info} +} + +// GetPlugins 获取本地扫描插件列表 +func (s *LocalScanStrategy) GetPlugins() ([]string, bool) { + // 如果指定了特定插件且不是"all" + if Common.ScanMode != "" && Common.ScanMode != "all" { + requestedPlugins := parsePluginList(Common.ScanMode) + if len(requestedPlugins) == 0 { + requestedPlugins = []string{Common.ScanMode} + } + + // 验证插件是否存在,不做Local类型过滤 + var validPlugins []string + for _, name := range requestedPlugins { + if _, exists := Common.PluginManager[name]; exists { + validPlugins = append(validPlugins, name) + } + } + + return validPlugins, true + } + + // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 + return GetAllPlugins(), false +} + +// LogPluginInfo 输出本地扫描插件信息 +func (s *LocalScanStrategy) LogPluginInfo() { + allPlugins, isCustomMode := s.GetPlugins() + + // 如果是自定义模式,直接显示用户指定的插件 + if isCustomMode { + Common.LogInfo(fmt.Sprintf("本地模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) + return + } + + // 在自动模式下,只显示Local类型的插件 + var applicablePlugins []string + for _, pluginName := range allPlugins { + plugin, exists := Common.PluginManager[pluginName] + if exists && plugin.HasType(Common.PluginTypeLocal) { + applicablePlugins = append(applicablePlugins, pluginName) + } + } + + if len(applicablePlugins) > 0 { + Common.LogInfo(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", "))) + } else { + Common.LogInfo("本地模式: 未找到可用的本地插件") + } +} + +// IsPluginApplicable 判断插件是否适用于本地扫描 +func (s *LocalScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { + // 自定义模式下运行所有明确指定的插件 + if isCustomMode { + return true + } + // 非自定义模式下,只运行Local类型插件 + return plugin.HasType(Common.PluginTypeLocal) +} diff --git a/Core/PluginUtils.go b/Core/PluginUtils.go new file mode 100644 index 0000000..89cddd5 --- /dev/null +++ b/Core/PluginUtils.go @@ -0,0 +1,71 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "sort" + "strings" +) + +// 插件列表解析和验证 +func parsePluginList(pluginStr string) []string { + if pluginStr == "" { + return nil + } + + // 按逗号分割并去除每个插件名称两端的空白 + plugins := strings.Split(pluginStr, ",") + for i, p := range plugins { + plugins[i] = strings.TrimSpace(p) + } + + // 过滤空字符串 + var result []string + for _, p := range plugins { + if p != "" { + result = append(result, p) + } + } + + return result +} + +// 验证扫描插件的有效性 +func validateScanPlugins() error { + // 如果未指定扫描模式或使用All模式,则无需验证 + if Common.ScanMode == "" || Common.ScanMode == "all" { + return nil + } + + // 解析插件列表 + plugins := parsePluginList(Common.ScanMode) + if len(plugins) == 0 { + plugins = []string{Common.ScanMode} + } + + // 验证每个插件是否有效 + var invalidPlugins []string + for _, plugin := range plugins { + if _, exists := Common.PluginManager[plugin]; !exists { + invalidPlugins = append(invalidPlugins, plugin) + } + } + + if len(invalidPlugins) > 0 { + return fmt.Errorf("无效的插件: %s", strings.Join(invalidPlugins, ", ")) + } + + return nil +} + +// 根据类型获取插件列表 +func GetPluginsByType(typeName string) []string { + var result []string + for name, plugin := range Common.PluginManager { + if plugin.HasType(typeName) { + result = append(result, name) + } + } + sort.Strings(result) + return result +} diff --git a/Core/Registry.go b/Core/Registry.go index ec61084..9557aa6 100644 --- a/Core/Registry.go +++ b/Core/Registry.go @@ -15,18 +15,21 @@ func init() { Name: "FTP", Ports: []int{21}, ScanFunc: Plugins.FtpScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("ssh", Common.ScanPlugin{ Name: "SSH", Ports: []int{22, 2222}, ScanFunc: Plugins.SshScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("telnet", Common.ScanPlugin{ Name: "Telnet", Ports: []int{23}, ScanFunc: Plugins.TelnetScan, + Types: []string{Common.PluginTypeService}, }) // Windows网络服务 @@ -34,18 +37,21 @@ func init() { Name: "FindNet", Ports: []int{135}, ScanFunc: Plugins.Findnet, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("netbios", Common.ScanPlugin{ Name: "NetBIOS", Ports: []int{139}, ScanFunc: Plugins.NetBIOS, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("smb", Common.ScanPlugin{ Name: "SMB", Ports: []int{445}, ScanFunc: Plugins.SmbScan, + Types: []string{Common.PluginTypeService}, }) // 数据库服务 @@ -53,18 +59,21 @@ func init() { Name: "MSSQL", Ports: []int{1433, 1434}, ScanFunc: Plugins.MssqlScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("oracle", Common.ScanPlugin{ Name: "Oracle", Ports: []int{1521, 1522, 1526}, ScanFunc: Plugins.OracleScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("mysql", Common.ScanPlugin{ Name: "MySQL", Ports: []int{3306, 3307, 13306, 33306}, ScanFunc: Plugins.MysqlScan, + Types: []string{Common.PluginTypeService}, }) // 中间件和消息队列服务 @@ -72,24 +81,28 @@ func init() { Name: "Elasticsearch", Ports: []int{9200, 9300}, ScanFunc: Plugins.ElasticScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("rabbitmq", Common.ScanPlugin{ Name: "RabbitMQ", Ports: []int{5672, 5671, 15672, 15671}, ScanFunc: Plugins.RabbitMQScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("kafka", Common.ScanPlugin{ Name: "Kafka", Ports: []int{9092, 9093}, ScanFunc: Plugins.KafkaScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("activemq", Common.ScanPlugin{ Name: "ActiveMQ", Ports: []int{61613}, ScanFunc: Plugins.ActiveMQScan, + Types: []string{Common.PluginTypeService}, }) // 目录和认证服务 @@ -97,6 +110,7 @@ func init() { Name: "LDAP", Ports: []int{389, 636}, ScanFunc: Plugins.LDAPScan, + Types: []string{Common.PluginTypeService}, }) // 邮件服务 @@ -104,18 +118,21 @@ func init() { Name: "SMTP", Ports: []int{25, 465, 587}, ScanFunc: Plugins.SmtpScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("imap", Common.ScanPlugin{ Name: "IMAP", Ports: []int{143, 993}, ScanFunc: Plugins.IMAPScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("pop3", Common.ScanPlugin{ Name: "POP3", Ports: []int{110, 995}, ScanFunc: Plugins.POP3Scan, + Types: []string{Common.PluginTypeService}, }) // 网络管理和监控服务 @@ -123,12 +140,14 @@ func init() { Name: "SNMP", Ports: []int{161, 162}, ScanFunc: Plugins.SNMPScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("modbus", Common.ScanPlugin{ Name: "Modbus", Ports: []int{502, 5020}, ScanFunc: Plugins.ModbusScan, + Types: []string{Common.PluginTypeService}, }) // 数据同步和备份服务 @@ -136,6 +155,7 @@ func init() { Name: "Rsync", Ports: []int{873}, ScanFunc: Plugins.RsyncScan, + Types: []string{Common.PluginTypeService}, }) // NoSQL数据库 @@ -143,12 +163,14 @@ func init() { Name: "Cassandra", Ports: []int{9042}, ScanFunc: Plugins.CassandraScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("neo4j", Common.ScanPlugin{ Name: "Neo4j", Ports: []int{7687}, ScanFunc: Plugins.Neo4jScan, + Types: []string{Common.PluginTypeService}, }) // 远程桌面和显示服务 @@ -156,18 +178,21 @@ func init() { Name: "RDP", Ports: []int{3389, 13389, 33389}, ScanFunc: Plugins.RdpScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("postgres", Common.ScanPlugin{ Name: "PostgreSQL", Ports: []int{5432, 5433}, ScanFunc: Plugins.PostgresScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("vnc", Common.ScanPlugin{ Name: "VNC", Ports: []int{5900, 5901, 5902}, ScanFunc: Plugins.VncScan, + Types: []string{Common.PluginTypeService}, }) // 缓存和键值存储服务 @@ -175,18 +200,21 @@ func init() { Name: "Redis", Ports: []int{6379, 6380, 16379}, ScanFunc: Plugins.RedisScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("memcached", Common.ScanPlugin{ Name: "Memcached", Ports: []int{11211}, ScanFunc: Plugins.MemcachedScan, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("mongodb", Common.ScanPlugin{ Name: "MongoDB", Ports: []int{27017, 27018}, ScanFunc: Plugins.MongodbScan, + Types: []string{Common.PluginTypeService}, }) // 2. 特殊漏洞扫描插件 @@ -194,12 +222,14 @@ func init() { Name: "MS17010", Ports: []int{445}, ScanFunc: Plugins.MS17010, + Types: []string{Common.PluginTypeService}, }) Common.RegisterPlugin("smbghost", Common.ScanPlugin{ Name: "SMBGhost", Ports: []int{445}, ScanFunc: Plugins.SmbGhost, + Types: []string{Common.PluginTypeService}, }) // 3. Web应用扫描插件 @@ -207,12 +237,14 @@ func init() { Name: "WebTitle", Ports: Common.ParsePortsFromString(Common.WebPorts), ScanFunc: Plugins.WebTitle, + Types: []string{Common.PluginTypeWeb}, }) Common.RegisterPlugin("webpoc", Common.ScanPlugin{ Name: "WebPoc", Ports: Common.ParsePortsFromString(Common.WebPorts), ScanFunc: Plugins.WebPoc, + Types: []string{Common.PluginTypeWeb}, }) // 4. Windows系统专用插件 @@ -220,6 +252,7 @@ func init() { Name: "SMBScan2", Ports: []int{445}, ScanFunc: Plugins.SmbScan2, + Types: []string{Common.PluginTypeService}, }) // 5. 本地信息收集插件 @@ -227,33 +260,30 @@ func init() { Name: "LocalInfo", Ports: []int{}, ScanFunc: Plugins.LocalInfoScan, + Types: []string{Common.PluginTypeLocal}, }) Common.RegisterPlugin("dcinfo", Common.ScanPlugin{ Name: "DCInfo", Ports: []int{}, ScanFunc: Plugins.DCInfoScan, + Types: []string{Common.PluginTypeLocal}, }) Common.RegisterPlugin("minidump", Common.ScanPlugin{ Name: "MiniDump", Ports: []int{}, ScanFunc: Plugins.MiniDump, + Types: []string{Common.PluginTypeLocal}, }) } // GetAllPlugins 返回所有已注册插件的名称列表 -// 当用户未指定特定插件或使用"All"模式时使用 func GetAllPlugins() []string { pluginNames := make([]string, 0, len(Common.PluginManager)) - - // 遍历插件管理器,获取所有插件名称 for name := range Common.PluginManager { pluginNames = append(pluginNames, name) } - - // 对插件名称进行排序,使输出更加一致 sort.Strings(pluginNames) - return pluginNames } diff --git a/Core/Scanner.go b/Core/Scanner.go index e166359..59e7f49 100644 --- a/Core/Scanner.go +++ b/Core/Scanner.go @@ -5,7 +5,6 @@ import ( "github.com/schollz/progressbar/v3" "github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/WebScan/lib" - "sort" "strconv" "strings" "sync" @@ -13,31 +12,59 @@ import ( "time" ) -// 全局状态 -var ( - LocalScan bool // 本地扫描模式标识 - WebScan bool // Web扫描模式标识 -) - // ScanTask 表示单个扫描任务 type ScanTask struct { pluginName string // 插件名称 target Common.HostInfo // 目标信息 } -// 添加一个本地插件集合,用于识别哪些插件是本地信息收集插件 -var localPlugins = map[string]bool{ - "localinfo": true, - "dcinfo": true, - "minidump": true, +// ScanStrategy 定义扫描策略接口 +type ScanStrategy interface { + // 名称和描述 + Name() string + Description() string + + // 执行扫描的主要方法 + Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) + + // 插件管理方法 + GetPlugins() ([]string, bool) + LogPluginInfo() + + // 任务准备方法 + PrepareTargets(info Common.HostInfo) []Common.HostInfo + IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool } -// ----------------------------------------------------------------------------- -// 主扫描流程 -// ----------------------------------------------------------------------------- +// Scanner 扫描器结构体 +type Scanner struct { + strategy ScanStrategy +} -// Scan 执行整体扫描流程的入口函数 -func Scan(info Common.HostInfo) { +// NewScanner 创建新的扫描器并选择合适的策略 +func NewScanner(info Common.HostInfo) *Scanner { + scanner := &Scanner{} + scanner.selectStrategy(info) + return scanner +} + +// selectStrategy 根据扫描配置选择适当的扫描策略 +func (s *Scanner) selectStrategy(info Common.HostInfo) { + switch { + case Common.LocalMode: + s.strategy = NewLocalScanStrategy() + Common.LogInfo("已选择本地扫描模式") + case len(Common.URLs) > 0: + s.strategy = NewWebScanStrategy() + Common.LogInfo("已选择Web扫描模式") + default: + s.strategy = NewServiceScanStrategy() + Common.LogInfo("已选择服务扫描模式") + } +} + +// Scan 执行整体扫描流程 +func (s *Scanner) Scan(info Common.HostInfo) { Common.LogInfo("开始信息扫描") lib.Inithttp() @@ -45,30 +72,16 @@ func Scan(info Common.HostInfo) { ch := make(chan struct{}, Common.ThreadNum) wg := sync.WaitGroup{} - // 选择并执行扫描模式 - selectScanMode(info, &ch, &wg) + // 执行策略 + s.strategy.Execute(info, &ch, &wg) // 等待所有扫描完成 wg.Wait() - finishScan() + s.finishScan() } -// 根据配置选择扫描模式 -func selectScanMode(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - switch { - case Common.LocalMode: - LocalScan = true - executeLocalScan(info, ch, wg) - case len(Common.URLs) > 0: - WebScan = true - executeWebScan(info, ch, wg) - default: - executeHostScan(info, ch, wg) - } -} - -// 完成扫描并输出结果 -func finishScan() { +// finishScan 完成扫描并输出结果 +func (s *Scanner) finishScan() { if Common.ProgressBar != nil { Common.ProgressBar.Finish() fmt.Println() @@ -76,391 +89,13 @@ func finishScan() { Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num)) } -// ----------------------------------------------------------------------------- -// 三种扫描模式实现 -// ----------------------------------------------------------------------------- - -// 执行本地信息收集 -func executeLocalScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - Common.LogInfo("执行本地信息收集") - - // 验证插件配置 - if err := validateScanPlugins(); err != nil { - Common.LogError(err.Error()) - return - } - - // 输出插件信息 - logPluginInfo() - - // 执行扫描任务 - executeScanTasks([]Common.HostInfo{info}, ch, wg) -} - -// 执行Web扫描 -func executeWebScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - Common.LogInfo("开始Web扫描") - - // 验证插件配置 - if err := validateScanPlugins(); err != nil { - Common.LogError(err.Error()) - return - } - - // 准备URL目标 - targetInfos := prepareURLTargets(info) - - // 输出插件信息 - logPluginInfo() - - // 执行扫描任务 - executeScanTasks(targetInfos, ch, wg) -} - -// 执行主机扫描 -func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - // 验证扫描目标 - if info.Host == "" { - Common.LogError("未指定扫描目标") - return - } - - // 验证插件配置 - if err := validateScanPlugins(); err != nil { - Common.LogError(err.Error()) - return - } - - // 解析目标主机 - hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts) - if err != nil { - Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) - return - } - - Common.LogInfo("开始主机扫描") - - // 输出插件信息 - logPluginInfo() - - // 执行主机扫描 - performHostScan(hosts, info, ch, wg) -} - -// ----------------------------------------------------------------------------- -// 主机扫描流程详细实现 -// ----------------------------------------------------------------------------- - -// 执行主机扫描的完整流程 -func performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - var targetInfos []Common.HostInfo - - // 主机存活性检测和端口扫描 - if len(hosts) > 0 || len(Common.HostPort) > 0 { - // 主机存活检测 - if shouldPerformLivenessCheck(hosts) { - hosts = CheckLive(hosts, Common.UsePing) - Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts))) - } - - // 端口扫描 - targetInfos = scanPortsAndPrepareTargets(hosts, info) - } - - // 添加URL目标 - targetInfos = appendURLTargets(targetInfos, info) - - // 执行漏洞扫描 - if len(targetInfos) > 0 { - Common.LogInfo("开始漏洞扫描") - executeScanTasks(targetInfos, ch, wg) - } -} - -// 判断是否需要执行存活性检测 -func shouldPerformLivenessCheck(hosts []string) bool { - return Common.DisablePing == false && len(hosts) > 1 -} - -// 扫描端口并准备目标信息 -func scanPortsAndPrepareTargets(hosts []string, info Common.HostInfo) []Common.HostInfo { - // 扫描存活端口 - alivePorts := discoverAlivePorts(hosts) - if len(alivePorts) == 0 { - return nil - } - - // 转换为目标信息 - return convertToTargetInfos(alivePorts, info) -} - -// 发现存活的端口 -func discoverAlivePorts(hosts []string) []string { - var alivePorts []string - - // 根据扫描模式选择端口扫描方式 - if WebScan || len(Common.URLs) > 0 { - alivePorts = NoPortScan(hosts, Common.Ports) - } else if len(hosts) > 0 { - alivePorts = PortScan(hosts, Common.Ports, Common.Timeout) - Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) - } - - // 合并额外指定的端口 - if len(Common.HostPort) > 0 { - alivePorts = append(alivePorts, Common.HostPort...) - alivePorts = Common.RemoveDuplicate(alivePorts) - Common.HostPort = nil - Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) - } - - return alivePorts -} - -// ----------------------------------------------------------------------------- -// 插件管理和解析 -// ----------------------------------------------------------------------------- - -// getAllLocalPlugins 返回所有本地插件的名称列表 -func getAllLocalPlugins() []string { - var localPluginList []string - for plugin := range localPlugins { - localPluginList = append(localPluginList, plugin) - } - sort.Strings(localPluginList) - return localPluginList -} - -// parsePluginList 解析逗号分隔的插件列表 -// pluginStr: 逗号分隔的插件字符串,如 "ssh,ftp,telnet" -// 返回: 插件名称的字符串切片 -func parsePluginList(pluginStr string) []string { - if pluginStr == "" { - return nil - } - - // 按逗号分割并去除每个插件名称两端的空白 - plugins := strings.Split(pluginStr, ",") - for i, p := range plugins { - plugins[i] = strings.TrimSpace(p) - } - - // 过滤空字符串 - var result []string - for _, p := range plugins { - if p != "" { - result = append(result, p) - } - } - - return result -} - -// validateScanPlugins 验证扫描插件的有效性 -// 返回: 错误信息 -func validateScanPlugins() error { - // 如果未指定扫描模式或使用All模式,则无需验证 - if Common.ScanMode == "" || Common.ScanMode == "All" { - return nil - } - - // 解析插件列表 - plugins := parsePluginList(Common.ScanMode) - if len(plugins) == 0 { - plugins = []string{Common.ScanMode} - } - - // 验证每个插件是否有效 - var invalidPlugins []string - for _, plugin := range plugins { - if _, exists := Common.PluginManager[plugin]; !exists { - invalidPlugins = append(invalidPlugins, plugin) - } - } - - if len(invalidPlugins) > 0 { - return fmt.Errorf("无效的插件: %s", strings.Join(invalidPlugins, ", ")) - } - - // 如果是本地模式,验证是否包含非本地插件 - if Common.LocalMode { - var nonLocalPlugins []string - for _, plugin := range plugins { - if !isLocalPlugin(plugin) { - nonLocalPlugins = append(nonLocalPlugins, plugin) - } - } - - if len(nonLocalPlugins) > 0 { - Common.LogInfo(fmt.Sprintf("本地模式下,以下非本地插件将被忽略: %s", strings.Join(nonLocalPlugins, ", "))) - } - } - - return nil -} - -// isLocalPlugin 判断插件是否为本地信息收集插件 -func isLocalPlugin(pluginName string) bool { - return localPlugins[pluginName] -} - -// getPluginsToRun 获取要执行的插件列表 -// 返回: 插件列表和是否为自定义插件模式 -func getPluginsToRun() ([]string, bool) { - // 本地模式处理 - if Common.LocalMode { - // 在本地模式下只执行本地插件 - - // 如果指定了特定插件(单个或多个) - if Common.ScanMode != "" && Common.ScanMode != "All" { - requestedPlugins := parsePluginList(Common.ScanMode) - if len(requestedPlugins) == 0 { - requestedPlugins = []string{Common.ScanMode} - } - - // 过滤出本地插件 - var localPluginsToRun []string - for _, plugin := range requestedPlugins { - if isLocalPlugin(plugin) { - localPluginsToRun = append(localPluginsToRun, plugin) - } - } - - return localPluginsToRun, true - } - - // 如果是All模式或未指定,则返回所有本地插件 - return getAllLocalPlugins(), true - } - - // 非本地模式处理(保持原有行为) - // 如果指定了插件列表(逗号分隔) - if Common.ScanMode != "" && Common.ScanMode != "All" { - plugins := parsePluginList(Common.ScanMode) - if len(plugins) > 0 { - return plugins, true - } - return []string{Common.ScanMode}, true - } - - // 默认情况:使用所有非本地插件 - allPlugins := GetAllPlugins() - filteredPlugins := make([]string, 0, len(allPlugins)) - - for _, plugin := range allPlugins { - if !isLocalPlugin(plugin) { - filteredPlugins = append(filteredPlugins, plugin) - } - } - - return filteredPlugins, false -} - -// logPluginInfo 输出插件信息 -func logPluginInfo() { - if Common.LocalMode { - if Common.ScanMode == "" || Common.ScanMode == "All" { - Common.LogInfo("本地模式: 使用所有本地信息收集插件") - } else { - plugins := parsePluginList(Common.ScanMode) - if len(plugins) == 0 { - plugins = []string{Common.ScanMode} - } - - // 过滤出本地插件 - var localPluginsToRun []string - for _, plugin := range plugins { - if isLocalPlugin(plugin) { - localPluginsToRun = append(localPluginsToRun, plugin) - } - } - - if len(localPluginsToRun) > 1 { - Common.LogInfo(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(localPluginsToRun, ", "))) - } else if len(localPluginsToRun) == 1 { - Common.LogInfo(fmt.Sprintf("本地模式: 使用本地插件: %s", localPluginsToRun[0])) - } else { - Common.LogInfo("本地模式: 未指定有效的本地插件,将不执行任何扫描") - } - } - return - } - - // 非本地模式的原有逻辑 - if Common.ScanMode == "" || Common.ScanMode == "All" { - Common.LogInfo("使用所有可用插件(已排除本地敏感插件)") - } else { - plugins := parsePluginList(Common.ScanMode) - if len(plugins) > 1 { - Common.LogInfo(fmt.Sprintf("使用插件: %s", strings.Join(plugins, ", "))) - } else { - Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode)) - } - } -} - -// ----------------------------------------------------------------------------- -// 目标准备 -// ----------------------------------------------------------------------------- - -// 准备URL目标列表 -func prepareURLTargets(baseInfo Common.HostInfo) []Common.HostInfo { - var targetInfos []Common.HostInfo - - for _, url := range Common.URLs { - urlInfo := baseInfo - // 确保URL包含协议头 - if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { - url = "http://" + url - } - urlInfo.Url = url - targetInfos = append(targetInfos, urlInfo) - } - - return targetInfos -} - -// 将端口列表转换为目标信息 -func convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo { - var infos []Common.HostInfo - - for _, targetIP := range ports { - hostParts := strings.Split(targetIP, ":") - if len(hostParts) != 2 { - Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP)) - continue - } - - info := baseInfo - info.Host = hostParts[0] - info.Ports = hostParts[1] - infos = append(infos, info) - } - - return infos -} - -// 添加URL扫描目标 -func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) []Common.HostInfo { - for _, url := range Common.URLs { - urlInfo := baseInfo - urlInfo.Url = url - targetInfos = append(targetInfos, urlInfo) - } - return targetInfos -} - -// ----------------------------------------------------------------------------- -// 任务执行 -// ----------------------------------------------------------------------------- - -// 执行扫描任务集合 -func executeScanTasks(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { +// 任务执行通用框架 +func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) { // 获取要执行的插件 - pluginsToRun, isCustomMode := getPluginsToRun() + pluginsToRun, isCustomMode := strategy.GetPlugins() // 准备扫描任务 - tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode) + tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy) // 输出扫描计划 if Common.ShowScanPlan && len(tasks) > 0 { @@ -478,6 +113,35 @@ func executeScanTasks(targets []Common.HostInfo, ch *chan struct{}, wg *sync.Wai } } +// 准备扫描任务列表 +func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) []ScanTask { + var tasks []ScanTask + + for _, target := range targets { + targetPort := 0 + if target.Ports != "" { + targetPort, _ = strconv.Atoi(target.Ports) + } + + for _, pluginName := range pluginsToRun { + plugin, exists := Common.PluginManager[pluginName] + if !exists { + continue + } + + // 检查插件是否适用于当前目标 (通过策略判断) + if strategy.IsPluginApplicable(plugin, targetPort, isCustomMode) { + tasks = append(tasks, ScanTask{ + pluginName: pluginName, + target: target, + }) + } + } + } + + return tasks +} + // logScanPlan 输出扫描计划信息 func logScanPlan(tasks []ScanTask) { // 统计每个插件的目标数量 @@ -497,55 +161,6 @@ func logScanPlan(tasks []ScanTask) { Common.LogInfo(planInfo.String()) } -// 准备扫描任务列表 -func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool) []ScanTask { - var tasks []ScanTask - - for _, target := range targets { - targetPort, _ := strconv.Atoi(target.Ports) - - for _, pluginName := range pluginsToRun { - plugin, exists := Common.PluginManager[pluginName] - if !exists { - continue - } - - // 检查插件是否适用于当前目标 - if isPluginApplicable(plugin, targetPort, isCustomMode, pluginName) { - tasks = append(tasks, ScanTask{ - pluginName: pluginName, - target: target, - }) - } - } - } - - return tasks -} - -// isPluginApplicable 判断插件是否适用于目标 -func isPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool, pluginName string) bool { - // 本地模式下,只执行本地插件 - if LocalScan { - return isLocalPlugin(pluginName) - } - - // 非本地模式下,本地插件特殊处理 - if isLocalPlugin(pluginName) { - // 只有在自定义模式下明确指定时才执行本地插件 - return isCustomMode - } - - // 特殊扫描模式下的处理 - if WebScan || isCustomMode { - return true - } - - // 端口匹配检查 - // 无端口限制的插件或端口匹配的插件 - return len(plugin.Ports) == 0 || plugin.HasPort(targetPort) -} - // 初始化进度条 func initProgressBar(totalTasks int) { Common.ProgressBar = progressbar.NewOptions(totalTasks, @@ -623,3 +238,9 @@ func updateProgress() { Common.ProgressBar.Add(1) } } + +// 入口函数,向后兼容旧的调用方式 +func Scan(info Common.HostInfo) { + scanner := NewScanner(info) + scanner.Scan(info) +} diff --git a/Core/ServiceScanner.go b/Core/ServiceScanner.go new file mode 100644 index 0000000..5cc9188 --- /dev/null +++ b/Core/ServiceScanner.go @@ -0,0 +1,218 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "strings" + "sync" +) + +// ServiceScanStrategy 服务扫描策略 +type ServiceScanStrategy struct{} + +// NewServiceScanStrategy 创建新的服务扫描策略 +func NewServiceScanStrategy() *ServiceScanStrategy { + return &ServiceScanStrategy{} +} + +// Name 返回策略名称 +func (s *ServiceScanStrategy) Name() string { + return "服务扫描" +} + +// Description 返回策略描述 +func (s *ServiceScanStrategy) Description() string { + return "扫描主机服务和漏洞" +} + +// Execute 执行服务扫描策略 +func (s *ServiceScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + // 验证扫描目标 + if info.Host == "" { + Common.LogError("未指定扫描目标") + return + } + + // 验证插件配置 + if err := validateScanPlugins(); err != nil { + Common.LogError(err.Error()) + return + } + + // 解析目标主机 + hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts) + if err != nil { + Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) + return + } + + Common.LogInfo("开始主机扫描") + + // 输出插件信息 + s.LogPluginInfo() + + // 执行主机扫描流程 + s.performHostScan(hosts, info, ch, wg) +} + +// performHostScan 执行主机扫描的完整流程 +func (s *ServiceScanStrategy) performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + var targetInfos []Common.HostInfo + + // 主机存活性检测和端口扫描 + if len(hosts) > 0 || len(Common.HostPort) > 0 { + // 主机存活检测 + if s.shouldPerformLivenessCheck(hosts) { + hosts = CheckLive(hosts, Common.UsePing) + Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts))) + } + + // 端口扫描 + alivePorts := s.discoverAlivePorts(hosts) + if len(alivePorts) > 0 { + targetInfos = s.convertToTargetInfos(alivePorts, info) + } + } + + // 执行漏洞扫描 + if len(targetInfos) > 0 { + Common.LogInfo("开始漏洞扫描") + ExecuteScanTasks(targetInfos, s, ch, wg) + } +} + +// shouldPerformLivenessCheck 判断是否需要执行存活性检测 +func (s *ServiceScanStrategy) shouldPerformLivenessCheck(hosts []string) bool { + return Common.DisablePing == false && len(hosts) > 1 +} + +// discoverAlivePorts 发现存活的端口 +func (s *ServiceScanStrategy) discoverAlivePorts(hosts []string) []string { + var alivePorts []string + + // 根据扫描模式选择端口扫描方式 + if len(hosts) > 0 { + alivePorts = PortScan(hosts, Common.Ports, Common.Timeout) + Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) + } + + // 合并额外指定的端口 + if len(Common.HostPort) > 0 { + alivePorts = append(alivePorts, Common.HostPort...) + alivePorts = Common.RemoveDuplicate(alivePorts) + Common.HostPort = nil + Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) + } + + return alivePorts +} + +// PrepareTargets 准备目标信息 +func (s *ServiceScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo { + // 解析目标主机 + hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts) + if err != nil { + Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) + return nil + } + + var targetInfos []Common.HostInfo + + // 主机存活性检测和端口扫描 + if len(hosts) > 0 || len(Common.HostPort) > 0 { + // 主机存活检测 + if s.shouldPerformLivenessCheck(hosts) { + hosts = CheckLive(hosts, Common.UsePing) + } + + // 端口扫描 + alivePorts := s.discoverAlivePorts(hosts) + if len(alivePorts) > 0 { + targetInfos = s.convertToTargetInfos(alivePorts, info) + } + } + + return targetInfos +} + +// convertToTargetInfos 将端口列表转换为目标信息 +func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo { + var infos []Common.HostInfo + + for _, targetIP := range ports { + hostParts := strings.Split(targetIP, ":") + if len(hostParts) != 2 { + Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP)) + continue + } + + info := baseInfo + info.Host = hostParts[0] + info.Ports = hostParts[1] + infos = append(infos, info) + } + + return infos +} + +// GetPlugins 获取服务扫描插件列表 +func (s *ServiceScanStrategy) GetPlugins() ([]string, bool) { + // 如果指定了插件列表且不是"all" + if Common.ScanMode != "" && Common.ScanMode != "all" { + plugins := parsePluginList(Common.ScanMode) + if len(plugins) > 0 { + return plugins, true + } + return []string{Common.ScanMode}, true + } + + // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 + return GetAllPlugins(), false +} + +// LogPluginInfo 输出服务扫描插件信息 +func (s *ServiceScanStrategy) LogPluginInfo() { + allPlugins, isCustomMode := s.GetPlugins() + + // 如果是自定义模式,直接显示用户指定的插件 + if isCustomMode { + Common.LogInfo(fmt.Sprintf("使用指定插件: %s", strings.Join(allPlugins, ", "))) + return + } + + // 在自动模式下,过滤掉本地插件,只显示服务类型插件 + var applicablePlugins []string + for _, pluginName := range allPlugins { + plugin, exists := Common.PluginManager[pluginName] + if exists && !plugin.HasType(Common.PluginTypeLocal) { + applicablePlugins = append(applicablePlugins, pluginName) + } + } + + if len(applicablePlugins) > 0 { + Common.LogInfo(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", "))) + } else { + Common.LogInfo("未找到可用的服务插件") + } +} + +// IsPluginApplicable 判断插件是否适用于服务扫描 +func (s *ServiceScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { + // 自定义模式下运行所有明确指定的插件 + if isCustomMode { + return true + } + + // 非自定义模式下,排除本地插件 + if plugin.HasType(Common.PluginTypeLocal) { + return false + } + + // 检查端口是否匹配 + if len(plugin.Ports) > 0 && targetPort > 0 { + return plugin.HasPort(targetPort) + } + + // 无端口限制的插件或适用于服务扫描的插件 + return len(plugin.Ports) == 0 || plugin.HasType(Common.PluginTypeService) +} diff --git a/Core/WebScanner.go b/Core/WebScanner.go new file mode 100644 index 0000000..d825765 --- /dev/null +++ b/Core/WebScanner.go @@ -0,0 +1,125 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "strings" + "sync" +) + +// WebScanStrategy Web扫描策略 +type WebScanStrategy struct{} + +// NewWebScanStrategy 创建新的Web扫描策略 +func NewWebScanStrategy() *WebScanStrategy { + return &WebScanStrategy{} +} + +// Name 返回策略名称 +func (s *WebScanStrategy) Name() string { + return "Web扫描" +} + +// Description 返回策略描述 +func (s *WebScanStrategy) Description() string { + return "扫描Web应用漏洞和信息" +} + +// Execute 执行Web扫描策略 +func (s *WebScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + Common.LogInfo("开始Web扫描") + + // 验证插件配置 + if err := validateScanPlugins(); err != nil { + Common.LogError(err.Error()) + return + } + + // 准备URL目标 + targets := s.PrepareTargets(info) + + // 输出插件信息 + s.LogPluginInfo() + + // 执行扫描任务 + ExecuteScanTasks(targets, s, ch, wg) +} + +// PrepareTargets 准备URL目标列表 +func (s *WebScanStrategy) PrepareTargets(baseInfo Common.HostInfo) []Common.HostInfo { + var targetInfos []Common.HostInfo + + for _, url := range Common.URLs { + urlInfo := baseInfo + // 确保URL包含协议头 + if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { + url = "http://" + url + } + urlInfo.Url = url + targetInfos = append(targetInfos, urlInfo) + } + + return targetInfos +} + +// GetPlugins 获取Web扫描插件列表 +func (s *WebScanStrategy) GetPlugins() ([]string, bool) { + // 如果指定了自定义插件并且不是"all" + if Common.ScanMode != "" && Common.ScanMode != "all" { + requestedPlugins := parsePluginList(Common.ScanMode) + if len(requestedPlugins) == 0 { + requestedPlugins = []string{Common.ScanMode} + } + + // 验证插件是否存在,不做Web类型过滤 + var validPlugins []string + for _, name := range requestedPlugins { + if _, exists := Common.PluginManager[name]; exists { + validPlugins = append(validPlugins, name) + } + } + + if len(validPlugins) > 0 { + return validPlugins, true + } + } + + // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 + return GetAllPlugins(), false +} + +// LogPluginInfo 输出Web扫描插件信息 +func (s *WebScanStrategy) LogPluginInfo() { + allPlugins, isCustomMode := s.GetPlugins() + + // 如果是自定义模式,直接显示用户指定的插件 + if isCustomMode { + Common.LogInfo(fmt.Sprintf("Web扫描模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) + return + } + + // 在自动模式下,只显示Web类型的插件 + var applicablePlugins []string + for _, pluginName := range allPlugins { + plugin, exists := Common.PluginManager[pluginName] + if exists && plugin.HasType(Common.PluginTypeWeb) { + applicablePlugins = append(applicablePlugins, pluginName) + } + } + + if len(applicablePlugins) > 0 { + Common.LogInfo(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", "))) + } else { + Common.LogInfo("Web扫描模式: 未找到可用的Web插件") + } +} + +// IsPluginApplicable 判断插件是否适用于Web扫描 +func (s *WebScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { + // 自定义模式下运行所有明确指定的插件 + if isCustomMode { + return true + } + // 非自定义模式下,只运行Web类型插件 + return plugin.HasType(Common.PluginTypeWeb) +}