Compare commits

...

80 Commits

Author SHA1 Message Date
ZacharyZcR
05eaa0f70e cleanup: 清理项目根目录和无用文档
- 删除根目录下所有技术文档文件 (MySQL连接优化报告.md, PARAMETERS.md等)
- 清理冗余的adapters目录 (已迁移到plugins/adapters)
- 删除mysql_tests测试目录和相关测试文件
- 清理plugins目录下的文档和测试文件
- 移除test目录和相关测试环境文件
- 删除临时结果文件 result.txt

项目结构更加简洁,只保留核心功能代码
2025-08-09 18:40:37 +08:00
ZacharyZcR
e36944a630 version: 更新版本号为v2.1.0
- 更新version变量从2.0.2到2.1.0
- 反映分支重命名从v2.0.2到v2.1.0
- 对应大量新功能和架构重构的版本升级
2025-08-09 18:37:20 +08:00
ZacharyZcR
143801ff58 feat: 实现Web插件智能检测,增强服务扫描模式
当服务扫描模式检测到常见Web端口时,自动包含WebPoc和WebTitle插件,
提升Web服务扫描覆盖率的同时保持完全向后兼容性。

主要改进:
- 新增WebPortDetector智能检测Web服务
- 服务扫描模式自动包含Web插件当检测到Web端口时
- 创建WebPoc和WebTitle插件适配器
- 保持所有现有功能和参数行为不变
2025-08-09 18:04:13 +08:00
ZacharyZcR
798d4e211a feat: 实现Web插件架构,完善Web扫描模式
- 创建WebTitle和WebPoc的legacy适配器插件
- 将Web插件整合到新插件系统架构中
- 移动WebTitle.go和WebPoc.go到plugins/legacy目录
- 在Registry.go中注册webtitle和webpoc插件
- 配置Web插件支持任意端口URL扫描(无端口限制)
- 修复WebScanStrategy能正确识别和执行web分类插件

功能特性:
- Web模式现支持标题获取、指纹识别、POC漏洞扫描
- 统一的插件架构,与service/local插件保持一致
- 支持任意端口的URL扫描,不受端口列表限制
- 保持所有原有Web扫描功能完整性

测试验证:Web扫描模式现已完全集成到新插件系统中
2025-08-09 17:42:35 +08:00
ZacharyZcR
b2b2c4a215 refactor: 统一插件分类系统,修复FindNet执行问题
- 修复FindNet插件分类从information改为service,解决不在服务扫描阶段执行的问题
- 统一所有插件分类为标准的三种类型:service、web、local
- 将vulnerability类型插件(smbghost、ms17010)重分类为service
- 将information类型插件(netbios、findnet)重分类为service
- 确保所有网络服务端口扫描插件都在服务扫描阶段正确执行
- 保持插件功能不变,仅调整分类以符合新架构要求

测试验证:FindNet现已能正确在135端口扫描时显示网络接口信息
2025-08-09 17:31:40 +08:00
ZacharyZcR
99b0481616 refactor: 完成插件架构重构,移除冗余文件并统一legacy插件引用
- 移动adapters目录到plugins下统一管理
- 删除已迁移的老版本插件文件,避免代码重复
- 更新所有legacy适配器引用,统一使用legacy目录下的源文件
- 新增FindNet插件的legacy适配器,支持135端口RPC扫描
- 修复包导入路径,确保适配器正常工作
- 清理插件目录结构,分离新架构插件和legacy插件
2025-08-09 17:17:45 +08:00
ZacharyZcR
f06bd3c1ac refactor: 最终整理插件目录结构,分离已迁移的老版本插件
目录结构:
- plugins/legacy/ - 存放已迁移到新架构的老版本插件
- adapters/legacy_plugins/ - 对应的适配器实现
- plugins/services/ - 新架构服务插件

已迁移的老版本插件:
- MS17010.go, MS17010-Exp.go - MS17010漏洞检测
- SmbGhost.go - SMBGhost漏洞检测
- SMB.go, SMB2.go - SMB服务检测
- RDP.go - RDP服务检测
- NetBIOS.go - NetBIOS信息收集
- Elasticsearch.go - Elasticsearch服务检测
- Base.go - 工具函数模块

架构特点:
- 老版本插件代码完全不变
- 通过适配器实现与新架构的桥接
- 清晰的职责分离和目录组织
- 为后续Web插件和本地插件整理预留空间

测试验证:✓ 所有功能正常
2025-08-09 16:29:21 +08:00
ZacharyZcR
32223db6e3 refactor: 重新组织插件架构,分离老版本插件和适配器
目录结构优化:
- 保留 plugins/ 目录下的老版本插件代码不变
- 新增 adapters/ 目录专门存放适配器相关代码
- adapters/legacy_plugin.go - 老版本插件适配器框架
- adapters/legacy_plugins/ - 老版本插件适配器实现

架构分离:
- plugins/ - 原始老版本插件代码,保持不变
- plugins/services/ - 新架构服务插件
- adapters/ - 各种适配器,实现架构间桥接

设计原则:
- 零侵入性:老版本插件完全不需要修改
- 分离关注点:不同类型的代码放在合适的目录
- 统一管理:通过注册表统一管理所有插件

测试验证:✓ 重组后所有插件功能正常
2025-08-09 16:24:26 +08:00
ZacharyZcR
d57e5d519d feat: 实现老版本插件适配器架构,无缝接入新插件系统
核心改进:
- 创建LegacyPlugin适配器基础框架,支持老版本插件接入新架构
- 实现适配器模式,保持老版本插件代码完全不变
- 支持不同类型插件的能力配置(漏洞检测、信息收集、服务检测)

已适配插件:
- MS17010: SMB远程代码执行漏洞检测 (EternalBlue)
- SmbGhost: CVE-2020-0796 远程代码执行漏洞检测
- SMB/SMB2: SMB服务弱密码检测和共享枚举
- RDP: 远程桌面服务弱密码检测
- NetBIOS: 信息收集和主机名解析
- Elasticsearch: 弱密码检测和未授权访问检测

技术特点:
- 零代码修改:老版本插件完全不需要修改
- 完整接口实现:适配Plugin、Scanner、Exploiter接口
- 灵活配置:支持暴力破解标志检查、插件类型配置
- 统一管理:通过新的插件注册表统一管理所有插件

测试验证:✓ 所有适配器插件正常工作,与新架构完美集成
2025-08-09 16:15:55 +08:00
ZacharyZcR
6fda09f183 feat: 实现VNC远程桌面协议专业扫描插件
- 新增VNC服务连接器,支持mitchellh/go-vnc库
- 实现基于密码的VNC认证机制
- 支持VNC常用端口(5900-5903)扫描
- 集成弱密码检测功能
- 添加VNC服务识别能力
- 配置Docker测试环境
- 更新插件注册表和国际化消息

测试验证: ✓ VNC弱密码检测正常工作
2025-08-09 16:02:05 +08:00
ZacharyZcR
05727a0db7 feat: 实现Telnet远程终端协议专业扫描插件
- 新增Telnet服务插件,支持完整的Telnet协议实现和弱口令检测
- 实现Telnet连接器、利用器和主插件的完整新架构
- 支持智能服务器类型识别(无需认证、仅密码、用户名+密码)
- 集成IAC命令处理、选项协商等完整Telnet协议功能
- 支持多种认证模式和用户名/密码组合暴力破解
- 完善国际化消息支持和Docker测试环境配置
- 测试验证:成功检测root:123456等弱密码组合
2025-08-09 15:42:46 +08:00
ZacharyZcR
8da185257b feat: 实现SNMP网络管理协议专业扫描插件
- 新增SNMP服务插件,支持UDP协议和community字符串认证
- 实现SNMP连接器、利用器和主插件的完整架构
- 添加UDP端口161的特殊处理机制,解决UDP端口扫描问题
- 支持默认community字符串爆破(public, private, cisco, community)
- 集成SNMP系统信息获取和服务识别功能
- 完善国际化消息支持和Docker测试环境配置
2025-08-09 15:34:05 +08:00
ZacharyZcR
fbc75bb709 feat: 添加Rsync和SMTP插件实现文件
包含新架构下的连接器、利用器和插件主体实现:
- rsync服务插件:支持RSYNCD协议和模块扫描
- smtp服务插件:支持SMTP协议和PLAIN认证
- PostgreSQL插件文件(之前遗漏)
- Docker测试环境配置文件
2025-08-09 15:05:42 +08:00
ZacharyZcR
188f949f09 feat: 完成Rsync和SMTP服务插件迁移到新架构
- 完成Rsync文件同步服务插件迁移
  * 实现RSYNCD协议支持和模块列表获取
  * 支持匿名访问和认证扫描
  * 添加Docker测试环境配置

- 完成SMTP邮件服务插件迁移
  * 实现SMTP协议和PLAIN认证支持
  * 支持匿名访问检测和弱密码扫描
  * 添加Docker测试环境配置

- 更新国际化消息和插件注册机制
- 两个插件均通过完整功能测试验证
2025-08-09 15:05:19 +08:00
ZacharyZcR
c2bb4bfd35 feat: Rsync和SMTP服务插件迁移到新架构完成
- 完成Rsync文件同步服务插件迁移
  * 实现RSYNCD协议支持和模块列表获取
  * 支持匿名访问和认证扫描
  * 添加Docker测试环境配置

- 完成SMTP邮件服务插件迁移
  * 实现SMTP协议和PLAIN认证支持
  * 支持匿名访问检测和弱密码扫描
  * 添加Docker测试环境配置

- 更新国际化消息和插件注册机制
- 两个插件均通过完整功能测试验证
2025-08-09 13:46:46 +08:00
ZacharyZcR
3e71e7e4c9 feat: RabbitMQ消息队列服务插件迁移到新架构完成
- 实现AMQP协议连接器,支持RabbitMQ消息队列服务扫描
- 支持弱密码检测和服务识别功能
- 优先检测默认凭据admin/123456和guest/guest
- 服务识别使用TCP连接检测,避免AMQP协议复杂度
- 简化实现,不提供利用功能,专注于安全扫描
- 集成国际化消息系统
- 测试通过:服务识别和弱密码检测功能
2025-08-09 13:30:24 +08:00
ZacharyZcR
532daf16ed feat: POP3邮件服务插件迁移到新架构完成
- 实现POP3协议连接器,支持普通和TLS连接(端口110/995)
- 支持弱密码检测和邮件服务识别功能
- 简化实现,不提供利用功能,专注于安全扫描
- 集成国际化消息系统
- 测试通过:服务识别和弱密码检测功能
2025-08-09 13:16:47 +08:00
ZacharyZcR
3de7b21fe0 feat: Oracle数据库插件迁移到新架构完成
- 实现Oracle TNS协议连接器,支持多种服务名
- 支持高危用户(SYS/SYSTEM)检测和SYSDBA权限
- 实现服务识别和弱密码检测功能
- 集成国际化消息系统
- 测试通过:服务识别和高危用户认证功能
2025-08-09 13:02:11 +08:00
ZacharyZcR
dc8579f554 feat: 实现Neo4j图数据库专业扫描插件
- 新增Neo4j Bolt协议识别和弱密码检测
- 支持未授权访问和默认凭据检测
- 实现ServiceConnector三层架构模式
- 添加Neo4j专用国际化消息
- 支持7474/7687端口扫描
- 自动注册到插件系统
2025-08-09 11:57:35 +08:00
ZacharyZcR
a71092b514 feat: 实现Microsoft SQL Server数据库专业扫描插件
- 新增MSSQL协议识别和弱密码检测
- 支持sa等管理员账户暴力破解
- 实现ServiceConnector三层架构模式
- 添加MSSQL专用国际化消息
- 支持SOCKS代理连接
- 自动注册到插件系统
2025-08-09 11:46:07 +08:00
ZacharyZcR
7e4d5a0bcd feat: 实现MongoDB NoSQL数据库专业扫描插件
- 新增MongoDB协议识别和未授权访问检测
- 支持Wire Protocol(OP_MSG和OP_QUERY)
- 实现connector/exploiter/plugin三层架构
- 添加MongoDB专用国际化消息
- 自动注册到插件系统
2025-08-08 14:02:13 +08:00
ZacharyZcR
fbe141cc80 feat: 实现Memcached和Modbus专业扫描插件 2025-08-08 13:37:58 +08:00
ZacharyZcR
7b8d2b4add feat: 实现LDAP轻量级目录访问协议专业扫描插件 2025-08-08 12:54:03 +08:00
ZacharyZcR
4e661735f8 feat: 实现FTP文件传输协议专业扫描插件 2025-08-08 12:12:29 +08:00
ZacharyZcR
6e936f604a feat: 实现Apache Kafka消息队列专业扫描插件
- 新增Kafka插件支持9092等端口扫描
- 实现SASL PLAIN认证机制支持
- 支持弱密码暴力破解和服务识别功能
- 延迟连接设计避免SASL握手错误
- 支持Consumer和Client双重连接验证
- 完善Kafka相关国际化消息支持
- 兼容新插件架构设计模式
2025-08-08 11:34:37 +08:00
ZacharyZcR
0808461026 feat: 实现IMAP邮件服务专业扫描插件
- 新增IMAP/IMAPS插件支持143和993端口扫描
- 实现TLS/SSL加密连接支持
- 添加IMAP协议专用超时机制(默认超时+5秒)
- 支持弱密码暴力破解和服务识别功能
- 完善IMAP相关国际化消息支持
- 兼容新插件架构设计模式
2025-08-08 11:27:55 +08:00
ZacharyZcR
a70df9bc3c refactor: 清理Cassandra插件exploiter.go为最小实现
- 移除所有利用功能代码,简化为最小版本
- 移除gocql依赖和复杂的数据提取逻辑
- 保持与其他插件一致的最小化实现模式
2025-08-08 10:26:00 +08:00
ZacharyZcR
ab8834a602 refactor: 简化非Redis插件的exploiter实现为最小版本
- 清理SSH、MySQL、FTP、ActiveMQ插件的exploiter.go为最小实现
- 移除所有未使用的利用功能代码和依赖
- 仅保留基础结构以维持接口兼容性
- 只有Redis插件保留完整的参数驱动利用功能
2025-08-08 10:14:30 +08:00
ZacharyZcR
90576b122c fix: 完全移除SSH插件的自动利用调用
- 删除SSH插件中的autoExploit方法调用
- 移除autoExploit方法定义
- SSH插件现在只进行弱密码扫描,不再尝试任何利用功能
- -sshkey参数仅用于私钥文件认证,不涉及利用功能

修复后SSH插件不再显示"利用失败"消息
2025-08-08 09:45:34 +08:00
ZacharyZcR
60e59f5a78 refactor: 精简利用功能,只保留真正有攻击价值的利用方法
- 完全移除FTP、MySQL、SSH、ActiveMQ的利用功能,只保留弱密码扫描
- 重构Redis插件利用方法,严格按参数控制启用:
  * arbitrary_file_write: 需要-rwp和(-rwc或-rwf)参数
  * ssh_key_write: 需要-rf参数
  * crontab_injection: 需要-rs参数
- 修复Redis未授权访问时的利用条件检查问题
- 去除所有信息收集类利用,只保留GetShell和文件写入等实际攻击能力

现在利用功能完全参数驱动,只有提供对应参数时才启动相应利用方法
2025-08-08 09:40:56 +08:00
ZacharyZcR
4b482b603d fix: 修复FTP插件注册和利用功能问题
- 在Core/Registry.go中添加FTP插件导入,确保插件正确注册
- 完善FTP插件的i18n消息支持,添加完整的中英文消息
- 修复FTP利用器错误处理逻辑,改进错误报告机制
- 添加FTP测试环境docker-compose配置文件

修复后FTP插件支持:
- 服务识别和版本检测
- 弱密码扫描和匿名登录检测
- 目录枚举、文件上传下载测试等利用功能
2025-08-08 08:58:36 +08:00
ZacharyZcR
83afd0f994 feat: 实现FTP文件传输协议专业扫描插件
• 新增FTP插件支持标准21端口和常见替代端口
• 实现匿名访问检测和弱密码爆破功能
• 支持自动利用:目录枚举、文件下载测试、文件上传测试
• 集成-nobr模式服务识别和-ne自动利用功能
• 完整的Context超时机制和连接数管理
• 添加中英文国际化消息支持
• 基于新插件架构实现模块化设计

功能特性:
- 支持jlaffaye/ftp驱动的FTP协议通信
- 智能识别vsFTPd、ProFTPD等多种FTP服务器
- 三种利用方法:目录结构探测、文件操作测试
- 完整的错误处理和连接限制处理机制
2025-08-08 04:46:35 +08:00
ZacharyZcR
516225a11f feat: 实现Apache Cassandra数据库服务专业扫描插件
• 新增Cassandra插件支持Native Protocol (9042端口)
• 实现弱密码检测和未授权访问检测
• 支持自动利用:信息收集、Keyspace枚举、数据提取
• 集成-nobr模式服务识别和-ne自动利用功能
• 完整的Context超时机制和代理支持
• 添加中英文国际化消息支持
• 基于新插件架构实现模块化设计

功能特性:
- 支持gocql驱动的CQL查询和认证
- 智能识别Cassandra Native Protocol v4
- 三种利用方法:系统信息、数据库结构、敏感数据
- 完整的错误处理和超时控制机制
2025-08-08 04:34:32 +08:00
ZacharyZcR
51735c4e25 feat: 实现-nobr/-ne参数和服务识别功能
新增功能:
- 添加-ne参数禁用利用攻击,实现弱密码检测和利用攻击的分离控制
- 实现-nobr模式下所有插件的服务识别功能,单数据包获取最大信息
- 修复端口插件匹配逻辑,只调用适配端口的插件提升扫描效率

插件改造:
- MySQL: 通过握手包识别获取版本信息
- Redis: INFO命令识别版本,优先检测未授权访问
- SSH: Banner识别获取协议和服务器版本
- ActiveMQ: STOMP协议识别获取版本和认证状态

技术改进:
- 新增端口匹配算法确保精准插件调用
- 完善i18n国际化支持所有新功能
- 统一服务识别接口设计便于扩展
2025-08-08 03:32:00 +08:00
ZacharyZcR
ecc79aa9b8 feat: 实现ActiveMQ消息队列服务专业扫描插件
新增功能:
- 基于STOMP协议的ActiveMQ弱密码检测
- 完整的多语言i18n支持(中英文)
- 自动信息收集和权限识别
- 队列枚举和管理权限检测
- 优化的Docker测试环境配置

技术特性:
- 支持端口61613(STOMP)和61614(STOMP+SSL)
- 智能用户权限分析
- 异步利用执行机制
- 统一的插件架构设计
- 完善的错误处理和日志记录

测试环境:
- 简化的ActiveMQ Docker配置
- 预配置多种测试凭据
- 专注STOMP协议,提升性能
2025-08-08 02:53:14 +08:00
ZacharyZcR
d91ed05d0e restore: 从main分支恢复TestDocker测试环境目录
- 恢复完整的Docker测试环境配置
- 支持MySQL, Redis, SSH, MongoDB等多种服务测试
- 包含57个测试环境配置文件
- 方便进行插件功能验证和性能测试
2025-08-07 23:43:34 +08:00
ZacharyZcR
8a2c9737f3 enhance: 增强进度条显示和扫描配置功能
- 在扫描开始时显示详细配置信息(-t, -time, -mt, -gt参数)
- 端口扫描进度条显示线程数配置
- 进度条实时显示插件和连接并发状态
- 添加ConcurrencyMonitor并发监控系统
- 提升mt参数默认值从10到50以提高扫描性能
- 移除端口范围限制,支持全端口扫描(1-65535)
- 完善中英文国际化支持
2025-08-07 14:48:32 +08:00
ZacharyZcR
de286026e8 refactor: 统一插件超时机制实现Context-First架构
- 重构SSH/MySQL/Redis插件超时控制,移除第三方库超时依赖
- 统一使用Go Context超时机制,提升超时控制可靠性和精确度
- 扩展MySQL/Redis/SSH插件默认端口支持,提升扫描覆盖率
- 修复插件系统中ConcurrentScanConfig超时配置缺失问题
- 优化插件检测逻辑,正确识别新架构插件并显示准确状态信息
- 解决插件在错误端口上长时间等待问题,显著提升扫描效率
2025-08-07 14:01:50 +08:00
ZacharyZcR
b346e6bdc1 feat: 完善插件系统i18n国际化支持
- 修复重复输出问题:适配器层改为debug输出,避免与插件层重复
- 修复格式化错误:修正SaveExploitResult中的端口格式化问题
- 新增利用方法名称i18n:添加GetExploitMethodName函数支持方法名本地化
- 扩展i18n消息模板:新增利用方法执行、MySQL/Redis专用消息模板
- 完善exploiter国际化:所有利用方法和结果消息支持中英文切换
- 优化用户体验:利用方法显示从"information_gathering"变为"信息收集"
2025-08-07 12:30:17 +08:00
ZacharyZcR
43f210ffc6 feat: 实现新一代插件注册系统完全替代传统手动注册模式
- 重构插件注册架构采用现代工厂模式和自动发现机制
- 新增完整的插件元数据管理系统支持版本能力标签等信息
- 实现智能插件适配器提供向后兼容的桥接功能
- 建立MySQL Redis SSH三个标准插件作为新架构参考实现
- 优化插件扫描逻辑支持按端口按类型的智能查询和过滤
- 添加国际化支持和完善的文档体系
- 代码量减少67%维护成本大幅降低扩展性显著提升

新架构特点:
- 零配置插件注册import即用
- 工厂模式延迟初始化和依赖注入
- 丰富元数据系统和能力声明
- 完全解耦的模块化设计
- 面向未来的可扩展架构

测试验证: MySQL和Redis插件功能完整包括弱密码检测未授权访问检测和自动利用攻击
2025-08-07 11:28:34 +08:00
ZacharyZcR
b38684bc9e fix: 修复AliveHosts全局变量内存泄漏问题
- 重构CheckLive函数使用局部变量代替全局变量
- 预分配容量避免频繁扩容提升性能
- 移除全局AliveHosts和ExistHosts变量声明
- 更新AliveScanner以使用局部存活主机列表
- 修复多次调用时内存累积问题
2025-08-07 10:08:46 +08:00
ZacharyZcR
78b8ff4f81 fix: 修复扫描模式验证和帮助文本过时问题
修复内容:
- 移除ValidationParser中无效的扫描模式(main, web, db, service, top1000, custom)
- 简化扫描模式验证逻辑,只保留真正支持的预定义模式(all, icmp)
- 允许任何插件名称作为扫描模式,实际验证在运行时进行
- 更新帮助文本,移除过时的portscan/tcpscan/udpscan引用

技术改进:
- 避免维护两套插件列表,减少维护成本
- 使验证逻辑与实际功能保持一致
- 提供更准确的用户指导信息

支持的扫描模式:
- all: 运行所有插件
- icmp: 存活探测模式
- 插件名称: mysql, redis, ssh等任何注册插件
- 多插件: mysql,redis,ssh等逗号分隔格式
2025-08-07 09:54:56 +08:00
ZacharyZcR
022461f407 feat: 添加-m icmp参数支持存活探测模式
新增功能:
- 支持通过-m icmp参数启用存活探测功能
- 与现有-ao参数功能等价,提供更直观的使用方式
- 添加参数冲突检查,当同时使用-ao和-m icmp时给出友好提示
- 完善参数验证,将icmp添加到有效扫描模式列表

技术实现:
- 扩展ValidationParser支持icmp模式验证
- 修改Scanner策略选择逻辑支持icmp模式
- 新增参数冲突检查函数checkParameterConflicts
- 更新帮助信息和国际化消息支持

向后兼容:
- 保持-ao参数原有功能不变
- 两种参数方式都能正确触发存活探测
- 支持中英文双语界面和提示信息
2025-08-07 09:45:01 +08:00
ZacharyZcR
78f81610cd feat: 实现存活探测模式(-ao)支持快速主机存活状态检测
新增功能:
- 添加-ao参数启用存活探测模式,专注于ICMP ping检测
- 实现AliveScanner专用扫描器,提供详细统计信息
- 集成到Scanner架构,支持与其他扫描模式无缝切换
- 完善i18n国际化支持,覆盖中英文界面

技术实现:
- 新增core/AliveScanner.go实现专用存活检测逻辑
- 扩展Scanner.go选择策略支持存活探测模式
- 优化目标解析和错误处理机制
- 提供成功率、耗时等详细扫描统计

使用场景:
- 快速批量主机存活性验证
- 网络拓扑发现前期探测
- 大规模网络资产盘点预检
2025-08-07 09:28:11 +08:00
ZacharyZcR
6c93129cb1 refactor: 重构扫描器架构优化代码复用和性能
主要改进:
- 创建BaseScanStrategy基础类提取通用功能,减少60%代码重复
- 新增PortDiscoveryService分离端口发现逻辑,提升职责清晰度
- 优化插件匹配算法,从O(n×m×p)降至O(n×p)复杂度
- 修复插件适用性判断逻辑错误,确保精确端口匹配
- 完善国际化支持,新增21个i18n消息定义
- 代码行数显著减少:LocalScanner(-50%)、ServiceScanner(-42%)、WebScanner(-44%)

技术优化:
- 组合模式替代继承,提升扩展性
- 策略模式实现插件过滤器,支持Local/Service/Web类型
- 服务分离提升可测试性和维护性
- 性能优化减少嵌套循环和重复计算

确保漏洞扫描插件列表与实际执行插件保持精确一致。
2025-08-07 09:16:03 +08:00
ZacharyZcR
e8a7c594e9 refactor: 重构i18n模块为模块化架构提升维护性
将单个messages.go文件(935行)拆分为按功能分类的多个文件:
- core.go: 核心系统消息
- parse.go: 解析相关消息
- config.go: 配置相关消息
- scan.go: 扫描相关消息
- network.go: 网络相关消息
- output.go: 输出相关消息
- error.go: 通用错误消息
- flag.go: 命令行参数消息
- constants.go: 语言常量定义
- init.go: 统一初始化机制

提升了代码维护性、可读性和团队协作效率,为后续国际化扩展奠定良好基础。
2025-08-07 08:53:51 +08:00
ZacharyZcR
a250fd7e22 refactor: 重构Scanner.go架构并完善国际化支持
架构优化:
- 简化ScanStrategy接口,从8个方法减少到3个
- 移除不必要的Scanner结构体包装,改为直接函数调用
- 合并scheduleScanTask和executeSingleScan为单一函数
- 移除prepareScanTasks中间层,采用流式处理避免预构建任务列表
- 移除ScanTask结构体,减少内存分配

性能提升:
- 流式任务执行,降低内存占用
- 统一资源管理,简化并发控制
- 减少函数调用链层次,提高执行效率

国际化完善:
- 新增5个扫描流程相关的双语消息定义
- 所有硬编码中文字符串替换为i18n调用
- 完整支持中英文扫描模式选择和错误提示

代码质量:
- 从187行减少到166行,函数数量从9个减少到7个
- 职责更清晰,耦合度更低,可维护性提升
- 保持向后兼容,核心功能完整
2025-08-07 08:27:12 +08:00
ZacharyZcR
647860f170 refactor: 清理无法访问的死代码函数
移除项目中无法访问的死代码:
- 移除 Scanner.go 中的 logScanPlan 函数
- 移除 match_engine.go 中的 containsPort 和 IsPortMatch 函数
- 清理相关的未使用导入:strings、strconv

优化效果:
- 减少约60行无用代码
- 消除编译器 unreachable func 警告
- 提升代码可维护性和可读性
- 优化二进制文件大小

这些函数在参数精简过程中失去了调用者,成为死代码。
移除后核心功能保持完整,通过完整测试验证。
2025-08-07 08:05:39 +08:00
ZacharyZcR
f943f04de7 refactor: 精简命令行参数提升用户体验
移除冗余和低频使用的参数:
- 移除 -ping 参数,统一使用 -np 控制存活检测
- 移除 -top 参数,改为智能计算显示数量
- 移除 -np-bar, -slow, -sp 调试参数
- 重构 -pg 为 -nopg,简化进度条控制逻辑

主要变更:
- 将进度条控制从默认开启改为默认显示,使用 -nopg 禁用
- 实现智能TOP计算,根据扫描规模自动调整显示数量
- 统一参数命名风格,提高易用性
- 完善参数文档,新增 PARAMETERS.md

影响:
- 简化了用户界面,减少参数学习成本
- 保持核心功能不变,提升使用体验
- 移除功能重复和混淆的参数选项
2025-08-07 07:18:32 +08:00
ZacharyZcR
8c3039506f refactor: 重命名common/core为common/base并移除LegacyParser中间层
- 重命名common/core包为common/base,解决与根目录core包名冲突问题
- 移除common/parsers/LegacyParser.go向后兼容层,简化代码架构
- 将SimpleParseIP/SimpleParsePort等函数重命名为ParseIP/ParsePort
- 更新所有相关引用和导入路径
- 删除41行冗余代码,提升代码可维护性
2025-08-07 06:31:05 +08:00
ZacharyZcR
0a60d76f71 refactor: 重构PortFinger.go为模块化架构以提升代码可维护性
将原有的878行单一文件重构为多个专门化模块:
- 类型定义模块:集中管理所有数据结构
- 扫描器核心:初始化和全局状态管理
- 编码工具:处理各种编码格式转换
- 探测器解析:解析nmap-service-probes格式
- 匹配引擎:模式匹配和服务识别
- 版本解析:服务版本信息提取

通过向后兼容层保持原有API接口不变,确保现有代码无需修改即可使用新架构
2025-08-07 02:26:12 +08:00
ZacharyZcR
69a70fc577 feat: 在漏洞扫描阶段显示启用的插件列表
增强用户体验,让用户清楚了解漏洞扫描时启用的具体插件:

功能特点:
• 在"开始漏洞扫描"后显示实际启用的插件列表
• 智能过滤,只显示针对发现端口的适用插件
• 完整国际化支持,中英文界面均正常显示
• 格式与现有"使用服务插件"保持一致

实现细节:
* Core/ServiceScanner.go: 新增LogVulnerabilityPluginInfo函数
  - 使用与实际扫描相同的插件过滤逻辑
  - 确保显示的插件列表与实际执行的插件一致
  - 支持插件去重和格式化显示

* Common/i18n/messages.go: 添加漏洞扫描相关国际化文本
  - scan_vulnerability_start: "开始漏洞扫描" / "Starting vulnerability scan"
  - scan_vulnerability_plugins: "使用漏洞扫描插件: %s" / "Using vulnerability scan plugins: %s"
  - scan_no_vulnerability_plugins: "未找到可用的漏洞扫描插件" / "No available vulnerability scan plugins found"

显示效果:
- 针对SMB端口(445,135): 显示"ms17010, smb, smb2, smbghost, findnet"
- 针对Web端口(80,443): 显示"webpoc, webtitle"
- 根据实际发现的开放端口智能显示相关插件

提升用户对扫描过程的可见性和控制感,便于调试和性能优化。
2025-08-07 01:38:51 +08:00
ZacharyZcR
291da0c879 refactor: 清理内存优化工具包中的死代码函数
根据静态分析结果清理未使用的函数:

删除文件:
- Common/utils/mappool.go (整个Map对象池模块未被使用)

精简文件:
- Common/utils/slicepool.go: 移除未使用的池管理函数,保留核心去重功能
- Common/utils/stringbuilder.go: 移除未使用的格式化和统计函数
- Common/utils/benchmark_test.go: 更新测试以适应清理后的API

保留的核心功能:
✓ JoinStrings/JoinInts - 字符串连接优化 (18x性能提升)
✓ DeduplicateStrings - 字符串去重功能
✓ StringBuilderPool - 字符串构建器池化机制

清理效果:
- 减少约150行无用代码
- 简化API设计,提高可维护性
- 保持所有实际使用的优化功能完整性
- 编译测试和功能测试全部通过

经验证,核心的内存优化功能(字符串连接18x性能提升,99.8%内存减少)完全保留。
2025-08-07 01:17:46 +08:00
ZacharyZcR
095437ad1a feat: 实施内存分配优化提升扫描性能
主要优化:
• 创建字符串构建器池,字符串连接性能提升18倍,内存减少99.8%
• 实施切片和Map对象池复用机制,减少频繁内存分配
• 优化SSH凭证生成,预分配切片容量减少58.6%内存使用
• 改进端口扫描和ICMP模块的Map容量预估机制
• 保持100%向后API兼容性

性能改进:
- 字符串操作: 8154ns→447ns (18x提升)
- 内存分配减少: 99.8% (8.3GB→16MB)
- SSH凭证生成: 内存减少58.6%
- 对象池复用率: 100%

新增文件:
+ common/utils/stringbuilder.go - 字符串构建器池
+ common/utils/slicepool.go - 切片对象池
+ common/utils/mappool.go - Map对象池
+ common/utils/benchmark_test.go - 性能基准测试
+ Common/utils/ - 大写版本兼容目录

修改文件:
* Common/Parse.go - 使用优化的字符串连接和去重函数
* Plugins/SSH.go - 凭证生成预分配优化
* Core/ICMP.go - 网段统计Map容量预估
* Core/PortScan.go - 端口排除Map预分配

通过专业基准测试验证,显著改善大规模扫描场景的内存效率和性能表现。
2025-08-07 01:09:54 +08:00
ZacharyZcR
68a0c99c4c fix: 修复globals.go并发安全问题并清理死代码
修复内容:
- 消除双向同步机制中的竞态条件风险
- 移除复杂的syncWithCore/syncToCore函数
- 简化全局状态管理架构

优化改进:
- 使用单一数据源(core包)避免数据不一致
- 保持向后兼容性,不破坏现有API
- 清理11个unreachable函数,减少30%代码量
- 从270行精简至188行,提升可维护性

技术细节:
- 移除globalState复杂结构体
- 简化配置同步逻辑为SyncFromCore/SyncToCore
- 保留必要的全局变量以维持兼容性
- 所有核心扫描功能验证正常

安全提升:
- 消除数据竞争和竞态条件
- 确保配置同步的原子性
- 提高多线程环境下的稳定性

涉及文件: 2个文件,+85行,-64行
2025-08-07 00:29:44 +08:00
ZacharyZcR
20cb3356de refactor: 重构common包错误消息系统并优化常量管理
主要改进:
- 将output和parsers包中的硬编码错误消息迁移到i18n国际化系统
- 修复constants.go中未引用常量的使用问题
- 消除所有硬编码字符串、魔术数字和协议标识符
- 清理死代码常量MinIPv4OctetValue

具体变更:
* output包: 删除19个硬编码错误消息,新增13个i18n消息键
* parsers包: 删除24个硬编码错误模板,新增22个i18n消息键
* 修复8个未引用的Default常量在对应默认选项函数中的使用
* 替换11处网络协议相关的硬编码值为常量引用
* 更新6个错误类型常量和验证逻辑的硬编码使用
* 修复2处IPv4八位组计数的硬编码为常量引用

提升效果:
- 支持中英文错误消息国际化切换
- 统一常量管理,提高代码可维护性
- 消除代码重复,符合DRY原则
- 清理死代码,优化代码质量

涉及文件: 11个文件,新增210行,删除103行
2025-08-06 22:33:26 +08:00
ZacharyZcR
84b0bb1e28 refactor: 完成common包常量提取和代码重构优化
- 新增constants.go文件统一管理各包常量定义
- 提取logging、output、parsers、proxy包中的硬编码值
- 将30+个魔法数字替换为语义化常量
- 统一错误代码和消息格式
- 清理死代码和未使用变量
- 优化代码可维护性和可读性
- 保持完全向后兼容性

涉及包:
- common/logging: 日志级别和格式常量
- common/output: 输出配置和格式常量
- common/parsers: 解析器配置和验证常量
- common/proxy: 代理协议和错误常量
2025-08-06 21:29:30 +08:00
ZacharyZcR
2f9c213e80 refactor: 系统性清理common包中的死代码和未使用变量
主要清理内容:
- logging包:移除DetailedFormatter、JSONFormatter及相关方法
- output包:清理Manager和Statistics中的未使用方法
- parsers包:移除各Parser中的Validate()和GetStatistics()方法
- proxy包:清理Factory和Global中的所有未使用函数
- ProgressManager:移除未使用的进度条方法和字段
- globals:清理未使用的status变量

技术改进:
- 使用deadcode和golangci-lint工具系统性检测
- 保持代码结构清晰,添加清理注释
- 修复相关依赖引用问题
- 确保编译通过和功能正常

代码减少:移除约40个死代码函数和多个未使用字段
2025-08-06 08:14:00 +08:00
ZacharyZcR
aff27e0b2c refactor: 清理common/i18n包中的死代码函数
- 删除Manager结构体中8个未使用的方法
- 清理全局访问函数中11个死代码函数
- 保留核心功能:SetLanguage, AddMessages, GetText
- 简化国际化管理架构,提升代码维护性
- 总计删除约300行死代码,包大小减少60%
2025-08-06 07:37:11 +08:00
ZacharyZcR
cb89558ce6 refactor: 清理common/core包中的死代码并精简常量定义
- 删除未使用的扫描模式、服务类型、输出格式等常量定义
- 移除Manager.go中的死代码访问器函数
- 清理Plugin.go中的未使用插件管理方法
- 简化配置管理架构,保留核心功能
- 总计删除约200行死代码,提升代码维护性
2025-08-06 07:23:17 +08:00
ZacharyZcR
aee8720a72 refactor: 清理common/config包中的死代码
- 删除Manager.go: 完全未使用的配置管理器模式(239行)
- 删除ScanOptions.go: 完全未使用的扫描选项管理器(408行)
- 简化PortMapping.go: 保留核心功能,删除未使用方法(减少151行)
- 简化ServiceDict.go: 保留核心功能,删除未使用方法(减少152行)
- 清理Types.go: 删除未使用的NewConfig函数(24行)
- 清理constants.go: 删除未使用的版本和验证常量(54行)

总计减少1032行死代码,包大小减少85%,保持功能完整性
2025-08-06 07:11:21 +08:00
ZacharyZcR
619aa632d0 refactor: 将硬编码常量提取到constants.go统一管理 2025-08-06 06:41:16 +08:00
ZacharyZcR
c8038bdc62 fix: 修复进度条显示错位问题,实现真正的固定底部进度条
- 简化进度条定位逻辑,移除复杂的光标定位操作
- 优化LogWithProgress协调机制,确保日志与进度条正确交互
- 修复ANSI转义序列被直接输出的问题
- 进度条现在能够在底部原地更新,不再与日志输出争抢显示空间
2025-08-06 05:00:21 +08:00
ZacharyZcR
9b6c389ea8 fix: 移除输出中的emoji和Unicode字符,提升终端兼容性
主要更改:
- 替换进度条中的Unicode块字符(█░│)为ASCII字符(#.|)
- 移除完成提示中的✓符号,改为[完成]标识
- 替换网络接口显示中的树形字符(└─)为简单短横线(-)
- 清理国际化信息中的emoji字符(🔍)

优化效果:
- 提升跨平台终端兼容性
- 避免在不支持Unicode的环境中显示乱码
- 输出更适合日志记录和脚本处理
- 符合传统命令行工具的专业输出风格
2025-08-06 01:39:58 +08:00
ZacharyZcR
05ba01f170 refactor: 统一包命名规范并清理冗余文件
主要更改:
- 统一包目录命名为小写(Core→core, Plugins→plugins, WebScan→webscan)
- 更新所有import路径以符合Go语言命名规范
- 重构parsers模块,简化复杂的工厂模式(从2000+行优化至400行)
- 移除i18n兼容层,统一使用模块化i18n包
- 简化Core/Manager.go架构(从591行优化至133行)
- 清理冗余文件:备份文件、构建产物、测试配置、重复图片
- 移除TestDocker测试环境配置目录
- 解决变量命名冲突问题

性能优化:
- 减少代码复杂度60-70%
- 提升构建和运行性能
- 保持完整功能兼容性

代码质量:
- 符合Go语言最佳实践
- 统一命名规范
- 优化项目结构
2025-08-06 01:30:18 +08:00
ZacharyZcR
4101ccc91a feat: 重构进度条系统并优化日志级别控制
- 实现固定底部进度条显示,与正常输出分离
- 创建ProgressManager统一管理进度条状态和渲染
- 优化日志级别过滤,默认只显示BASE、INFO、SUCCESS级别
- 修复进度条与并发日志输出的冲突问题
- 重构日志系统以支持动态级别配置和进度条协调
- 改进用户体验,提供清晰、专业的扫描进度反馈

主要改进:
* 新增ProgressManager.go实现固定底部进度条
* 修复日志初始化时机,确保级别配置正确生效
* 实现日志输出与进度条的互斥显示机制
* 优化默认日志级别,过滤干扰性调试和错误信息
* 保持向后兼容性,支持用户自定义日志级别
2025-08-06 00:06:49 +08:00
ZacharyZcR
a00e7a735a fix: 完善帮助信息国际化和参数验证机制
- 补充40+个缺失的flag参数翻译,实现完整中英文帮助信息
- 优化语言预处理机制,确保帮助信息生成前语言设置生效
- 新增智能帮助显示逻辑,无目标时自动显示帮助而非执行扫描
- 添加-help参数支持显式帮助请求
- 改善用户体验,避免误导性的默认扫描行为
2025-08-05 23:03:12 +08:00
ZacharyZcR
09d578a476 feat: 完善i18n国际化系统,实现完整的中英文切换支持
- 新增核心扫描流程国际化消息:扫描模式、进度状态、端口统计等
- 修复硬编码中文消息,统一使用GetText()获取国际化文本
- 优化import循环依赖,config和parsers包直接导入i18n包
- 完善消息覆盖:配置警告、扫描状态、任务进度全面国际化
- 实现实时语言切换,-lang en/zh参数立即生效

功能验证:
- 中英文输出100%准确,格式化参数正常工作
- 核心扫描流程消息完全国际化覆盖
- 线程安全并发访问,性能无明显影响
- 向后兼容性完整,现有代码无需修改

使fscan具备专业级国际化能力,支持全球用户使用
2025-08-05 21:25:02 +08:00
ZacharyZcR
a850e141fc refactor: 封装i18n为独立包,优化国际化架构
- 新建Common/i18n/子包,提供专业的国际化管理
- i18n/manager.go: 线程安全的国际化管理器,支持动态语言切换
- i18n/messages.go: 完整的消息库,200+条国际化文本
- 重构Common/i18n.go为向后兼容层,引用新i18n包
- 支持多语言回退机制和消息格式化功能
- 提供统一的国际化接口,便于维护和扩展

架构优势:
- 模块化设计: 独立的i18n包,职责单一
- 线程安全: 支持并发访问的国际化管理器
- 灵活配置: 支持动态语言切换和消息管理
- 向后兼容: 100%兼容现有GetText()调用
- 易于扩展: 支持新语言和消息的动态添加

使项目国际化架构更加整洁和专业化
2025-08-05 21:13:23 +08:00
ZacharyZcR
8594a8ba6c refactor: 创建Core子包整合Common包核心模块
- 新建Common/Core/子包,整合分散的核心功能
- Core/Constants.go: 整合端口常量和系统常量
- Core/Plugin.go: 整合插件系统,提供统一插件管理
- Core/Manager.go: 整合配置管理、参数解析和全局变量
- 更新原有文件保持向后兼容,引用Core包新结构
- 优化模块边界,减少文件数量,提升代码组织性
- 验证功能完整性,确保重构后项目正常运行

重构效果:
- 文件数量: 7个核心文件 → 3个Core文件 + 兼容层
- 模块化程度: 高度模块化的插件和配置管理
- 向后兼容: 100%兼容现有API调用
2025-08-05 21:06:21 +08:00
ZacharyZcR
f5843e486b 完善Common包i18n国际化支持,优化错误格式化
- 完成Bridge.go中所有硬编码中文的i18n改造
- 修复Parse.go中的国际化消息调用格式
- 扩展i18n.go消息库至200+条完整消息覆盖
- 统一各子系统的多语言错误处理机制
2025-08-05 20:58:01 +08:00
ZacharyZcR
b436c0d546 refactor: 大规模精简代码结构,移除冗余函数和优化模块
核心优化:
- 移除大量未使用的函数和方法,显著减少代码复杂度
- 精简多个子模块的接口定义和类型声明
- 优化Bridge.go桥接层,统一API调用接口
- 简化Parse.go主解析逻辑,提高代码可读性

模块精简:
- config模块:简化配置管理接口,移除冗余配置项
- logging模块:精简日志格式化和处理逻辑
- output模块:优化输出管理和统计功能
- parsers模块:简化类型定义和解析接口

性能提升:
- 减少不必要的函数调用开销
- 优化内存分配和垃圾回收压力
- 简化模块间依赖关系
- 提升编译速度和运行效率

安全验证:
- 编译测试完全通过,无编译错误或警告
- 功能完整性验证通过,所有核心功能正常
- 静态分析确认无隐藏依赖或反射调用风险
- 运行时测试验证系统稳定性和正确性
2025-08-05 20:00:50 +08:00
ZacharyZcR
ba6b1678d6 refactor: 激进重构Common包架构,大幅简化代码结构
架构优化:
- 删除Config.go、Log.go、Output.go、Proxy.go四个包装层文件
- 新增Bridge.go统一管理所有桥接功能,减少文件散乱
- 移除冗余测试文件和备份文件,清理项目结构

代码精简:
- i18n.go从1100+行精简至63行,移除95%冗余多语言支持
- Variables.go从223行优化至71行,保留核心15个变量
- 将大部分全局变量分散到实际使用模块中

模块整合:
- 四个包装层合并为单一Bridge.go文件
- 统一桥接接口,提供一致的API调用体验
- 优化config.Manager配置管理功能

性能提升:
- 减少文件数量和代码冗余,提升编译速度
- 简化模块依赖关系,降低内存占用
- 保持100%向后兼容性,无破坏性变更

测试验证:
- 所有核心功能测试通过
- API接口完全兼容,现有代码无需修改
- 编译无错误,运行稳定可靠
2025-08-05 19:45:39 +08:00
ZacharyZcR
39fc57f5a5 refactor: 深度重构Common包,移除冗余代码和优化架构
主要变更:
- 移除ParseIP.go和ParsePort.go包装层,统一使用parsers模块
- 精简i18n.go国际化系统,移除日俄语言支持,减少79%代码量
- 简化Variables.go配置同步机制,移除未使用的SyncToConfig函数
- 优化LegacyParser.go兼容层,移除扩展功能函数
- 修复结构体字面量和测试用例,提升代码质量

性能优化:
- 减少总代码量约2000行,提升维护性
- 保持100%API兼容性,现有调用无需修改
- 优化系统启动速度和内存使用
- 统一解析逻辑,消除功能重复

测试验证:
- 全项目编译通过,无错误或警告
- 所有核心功能正常工作
- 单元测试和回归测试通过
- IP/端口解析功能完整保留
2025-08-05 19:19:40 +08:00
ZacharyZcR
879293e680 refactor: 重构代理系统为模块化架构
将Proxy.go的代理连接逻辑重构为完整的模块化系统,
提供更好的性能、可维护性和功能扩展。

主要变更:
- Proxy.go: 从192行单体实现重构为简洁包装层
- 新增proxy模块包含5个核心文件:Types.go、Manager.go等
- 支持多种代理类型:HTTP、HTTPS、SOCKS5、直连
- 实现连接池、缓存机制和智能资源管理
- 添加详细的连接统计和性能监控
- 提供线程安全的配置管理和动态更新
- 完全保持API向后兼容性,无需修改调用代码

新增功能:
- HTTP/HTTPS代理支持(原来仅支持SOCKS5)
- 连接跟踪和统计分析
- 错误分类和详细上下文信息
- 配置验证和URL解析
- 全局代理管理实例

测试验证:
- 编译无错误 ✓
- 基础连接功能正常 ✓
- 向后兼容性验证通过 ✓
2025-08-05 03:36:53 +08:00
ZacharyZcR
f09bfdc346 refactor: 重构ParseIP和ParsePort为模块化架构
将ParseIP.go和ParsePort.go的复杂解析逻辑迁移到parsers模块,
提供更好的错误处理、线程安全和功能扩展。

主要变更:
- ParseIP.go: 从550行复杂实现重构为199行简洁包装层
- ParsePort.go: 从94行实现重构为33行简洁包装层
- 所有解析逻辑统一到parsers/TargetParser.go中
- 新增parsers/LegacyParser.go提供向后兼容接口
- 支持所有原有功能:IP范围、CIDR、网段简写、端口组等
- 完全保持API兼容性,无需修改调用代码

测试验证:
- IP范围解析: 192.168.1.1-3 ✓
- 端口组展开: web → 210个端口 ✓
- CIDR和网段简写功能正常 ✓
2025-08-05 03:01:41 +08:00
ZacharyZcR
5b1dde0a59 refactor: 重构配置系统为模块化架构
- 将Config相关文件重构为独立的config模块
- 创建config/Types.go定义核心配置数据结构
- 新增config/ServiceDict.go管理服务认证字典(线程安全)
- 新增config/PortMapping.go管理端口探测器映射(线程安全)
- 新增config/ScanOptions.go提供扫描选项管理(线程安全)
- 新增config/Manager.go统一配置管理器
- 新增Variables.go作为向后兼容桥接层
- 重构Config.go为兼容入口点,委托给新模块
- 删除原有的单体配置文件
- 修复用户字典和密码字典初始化问题
- 保持完全向后兼容性,现有API无需修改
- 提升代码组织性和可维护性
2025-08-05 02:42:17 +08:00
ZacharyZcR
e095f376f9 refactor: 重构日志和输出系统,优化日志级别和时间显示
主要更改:
- 重构Log.go和Output.go为模块化架构
- 创建独立的logging和output模块
- 新增LevelBaseInfoSuccess默认日志级别(显示BASE、INFO、SUCCESS)
- 添加运行时间显示到每条日志前面
- 保持完全向后兼容的API接口
- 支持多种输出格式(TXT、JSON、CSV)
- 优化日志格式化和颜色显示

技术改进:
- 模块化设计便于扩展和维护
- 智能时间格式化(毫秒→秒→分钟→小时)
- 支持缓冲和批量输出
- 线程安全的并发处理
2025-08-05 02:14:25 +08:00
ZacharyZcR
c04bfcfd07 refactor: 重构Parse.go解析模块,优化参数验证和信息显示
主要改进:
- 模块化设计:将549行Parse.go拆分为6个专用解析器
- 性能优化:添加文件缓存、并发处理和去重机制
- 增强验证:实现配置冲突检测和参数验证
- 改善体验:清理冗余警告,添加解析结果摘要显示
- 向后兼容:保持所有原有API接口不变

新增模块:
- FileReader: 高性能文件读取和缓存
- CredentialParser: 用户名密码解析
- TargetParser: 主机目标解析
- NetworkParser: 网络配置解析
- ValidationParser: 参数验证和冲突检测
- Types: 统一的数据结构定义

修复问题:
- 消除重复的"Web超时时间大于普通超时时间"警告
- 添加目标主机、端口、代理等配置信息显示
- 删除说教性安全警告,保留技术性提示
2025-08-05 02:14:10 +08:00
ZacharyZcR
7077590bae 重构: 将Config.go拆分为模块化配置文件
优化目标:
- 解决单文件过大问题(970行 -> 4个模块文件)
- 提升代码可维护性和可读性
- 保持包的一致性和向后兼容性

拆分方案:
- Config.go (48行): 主配置文件,包含版本信息和基础配置
- ConfigServiceDict.go (65行): 服务认证字典和默认密码管理
- ConfigPortMapping.go (850行): 端口与探测器映射关系
- ConfigScanOptions.go (260行): 扫描相关的各种配置选项

技术优势:
- 模块化组织,职责分明
- 同包结构避免导入复杂性
- 完全向后兼容,现有代码无需修改
- 便于后续功能扩展和维护

为v2.0.2版本架构优化奠定基础
2025-08-05 01:06:07 +08:00
247 changed files with 43610 additions and 14351 deletions

View File

@ -0,0 +1,191 @@
package common
import (
"fmt"
"sync"
"sync/atomic"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ConcurrencyMonitor.go - 并发监控器
监控两个层级的并发
1. 主扫描器线程数 (-t 参数控制)
2. 插件内连接线程数 (-mt 参数控制)
*/
// ConcurrencyMonitor 并发监控器
type ConcurrencyMonitor struct {
// 主扫描器层级
activePluginTasks int64 // 当前活跃的插件任务数
totalPluginTasks int64 // 总插件任务数
// 插件内连接层级 (每个插件的连接线程数)
pluginConnections sync.Map // map[string]*PluginConnectionInfo
mu sync.RWMutex
}
// PluginConnectionInfo 单个插件的连接信息
type PluginConnectionInfo struct {
PluginName string // 插件名称
Target string // 目标地址
ActiveConnections int64 // 当前活跃连接数
TotalConnections int64 // 总连接数
}
var (
globalConcurrencyMonitor *ConcurrencyMonitor
concurrencyMutex sync.Once
)
// GetConcurrencyMonitor 获取全局并发监控器
func GetConcurrencyMonitor() *ConcurrencyMonitor {
concurrencyMutex.Do(func() {
globalConcurrencyMonitor = &ConcurrencyMonitor{
activePluginTasks: 0,
totalPluginTasks: 0,
}
})
return globalConcurrencyMonitor
}
// =============================================================================
// 主扫描器层级监控
// =============================================================================
// StartPluginTask 开始插件任务
func (m *ConcurrencyMonitor) StartPluginTask() {
atomic.AddInt64(&m.activePluginTasks, 1)
atomic.AddInt64(&m.totalPluginTasks, 1)
}
// FinishPluginTask 完成插件任务
func (m *ConcurrencyMonitor) FinishPluginTask() {
atomic.AddInt64(&m.activePluginTasks, -1)
}
// GetPluginTaskStats 获取插件任务统计
func (m *ConcurrencyMonitor) GetPluginTaskStats() (active int64, total int64) {
return atomic.LoadInt64(&m.activePluginTasks), atomic.LoadInt64(&m.totalPluginTasks)
}
// =============================================================================
// 插件内连接层级监控
// =============================================================================
// StartConnection 开始连接
func (m *ConcurrencyMonitor) StartConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
value, _ := m.pluginConnections.LoadOrStore(key, &PluginConnectionInfo{
PluginName: pluginName,
Target: target,
})
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, 1)
atomic.AddInt64(&info.TotalConnections, 1)
}
// FinishConnection 完成连接
func (m *ConcurrencyMonitor) FinishConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
if value, ok := m.pluginConnections.Load(key); ok {
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, -1)
}
}
// GetConnectionStats 获取所有插件连接统计
func (m *ConcurrencyMonitor) GetConnectionStats() map[string]*PluginConnectionInfo {
stats := make(map[string]*PluginConnectionInfo)
m.pluginConnections.Range(func(key, value interface{}) bool {
keyStr := key.(string)
info := value.(*PluginConnectionInfo)
// 只返回当前活跃的连接
if atomic.LoadInt64(&info.ActiveConnections) > 0 {
stats[keyStr] = &PluginConnectionInfo{
PluginName: info.PluginName,
Target: info.Target,
ActiveConnections: atomic.LoadInt64(&info.ActiveConnections),
TotalConnections: atomic.LoadInt64(&info.TotalConnections),
}
}
return true
})
return stats
}
// GetTotalActiveConnections 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
var total int64
m.pluginConnections.Range(func(key, value interface{}) bool {
info := value.(*PluginConnectionInfo)
total += atomic.LoadInt64(&info.ActiveConnections)
return true
})
return total
}
// Reset 重置监控器
func (m *ConcurrencyMonitor) Reset() {
atomic.StoreInt64(&m.activePluginTasks, 0)
atomic.StoreInt64(&m.totalPluginTasks, 0)
m.pluginConnections.Range(func(key, value interface{}) bool {
m.pluginConnections.Delete(key)
return true
})
}
// GetConcurrencyStatus 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
totalConnections := m.GetTotalActiveConnections()
if activePlugins == 0 && totalConnections == 0 {
return ""
}
if totalConnections == 0 {
return fmt.Sprintf("%s:%d", i18n.GetText("concurrency_plugin"), activePlugins)
}
return fmt.Sprintf("%s:%d %s:%d",
i18n.GetText("concurrency_plugin"), activePlugins,
i18n.GetText("concurrency_connection"), totalConnections)
}
// GetDetailedStatus 获取详细的并发状态
func (m *ConcurrencyMonitor) GetDetailedStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
connectionStats := m.GetConnectionStats()
if activePlugins == 0 && len(connectionStats) == 0 {
return i18n.GetText("concurrency_no_active_tasks")
}
status := fmt.Sprintf("%s: %d", i18n.GetText("concurrency_plugin_tasks"), activePlugins)
if len(connectionStats) > 0 {
status += " | " + i18n.GetText("concurrency_connection_details") + ": "
first := true
for _, info := range connectionStats {
if !first {
status += ", "
}
status += fmt.Sprintf("%s@%s:%d", info.PluginName, info.Target, info.ActiveConnections)
first = false
}
}
return status
}

View File

@ -1,969 +0,0 @@
package Common
import (
"github.com/schollz/progressbar/v3"
"sync"
)
var version = "2.0.1"
var Userdict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
var DefaultMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
var PortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
256: {"LDAPSearchReq", "LDAPBindReq"},
257: {"LDAPSearchReq", "LDAPBindReq"},
261: {"SSLSessionReq"},
264: {"GenericLines"},
271: {"SSLSessionReq"},
280: {"GetRequest"},
322: {"RTSPRequest", "SSLSessionReq"},
324: {"SSLSessionReq"},
389: {"LDAPSearchReq", "LDAPBindReq"},
390: {"LDAPSearchReq", "LDAPBindReq"},
406: {"SIPOptions"},
427: {"NotesRPC"},
443: {"TLSSessionReq", "GetRequest", "HTTPOptions", "SSLSessionReq", "SSLv23SessionReq", "X11Probe", "FourOhFourRequest", "tor-versions", "OpenVPN"},
444: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
445: {"SMBProgNeg"},
448: {"SSLSessionReq"},
449: {"GenericLines"},
465: {"Hello", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
497: {"GetRequest", "X11Probe"},
500: {"OpenVPN"},
505: {"GenericLines", "GetRequest"},
510: {"GenericLines"},
512: {"DNSVersionBindReqTCP"},
513: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
514: {"GetRequest", "RPCCheck", "DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
515: {"GetRequest", "Help", "LPDString", "TerminalServer"},
523: {"ibm-db2-das", "ibm-db2"},
524: {"NCP"},
540: {"GenericLines", "GetRequest"},
543: {"DNSVersionBindReqTCP"},
544: {"RPCCheck", "DNSVersionBindReqTCP"},
548: {"SSLSessionReq", "SSLv23SessionReq", "afp"},
554: {"GetRequest", "RTSPRequest"},
563: {"SSLSessionReq"},
585: {"SSLSessionReq"},
587: {"GenericLines", "Hello", "Help"},
591: {"GetRequest"},
616: {"GenericLines"},
620: {"GetRequest"},
623: {"tn3270"},
628: {"GenericLines", "DNSVersionBindReqTCP"},
631: {"GetRequest", "HTTPOptions"},
636: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "LDAPSearchReq", "LDAPBindReq"},
637: {"LDAPSearchReq", "LDAPBindReq"},
641: {"HTTPOptions"},
660: {"SMBProgNeg"},
666: {"GenericLines", "beast2"},
684: {"SSLSessionReq"},
706: {"JavaRMI", "mydoom", "WWWOFFLEctrlstat"},
710: {"RPCCheck"},
711: {"RPCCheck"},
731: {"GenericLines"},
771: {"GenericLines"},
782: {"GenericLines"},
783: {"GetRequest"},
853: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP", "SSLSessionReq"},
888: {"GetRequest"},
898: {"GetRequest"},
900: {"GetRequest"},
901: {"GetRequest"},
989: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
990: {"GenericLines", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
992: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "tn3270"},
993: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
994: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
995: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
999: {"JavaRMI"},
1000: {"GenericLines"},
1010: {"GenericLines"},
1025: {"SMBProgNeg"},
1026: {"GetRequest"},
1027: {"SMBProgNeg"},
1028: {"TerminalServer"},
1029: {"DNSVersionBindReqTCP"},
1030: {"JavaRMI"},
1031: {"SMBProgNeg"},
1035: {"JavaRMI", "oracle-tns"},
1040: {"GenericLines"},
1041: {"GenericLines"},
1042: {"GenericLines", "GetRequest"},
1043: {"GenericLines"},
1068: {"TerminalServer"},
1080: {"GenericLines", "GetRequest", "Socks5", "Socks4"},
1090: {"JavaRMI", "Socks5", "Socks4"},
1095: {"Socks5", "Socks4"},
1098: {"JavaRMI"},
1099: {"JavaRMI"},
1100: {"JavaRMI", "Socks5", "Socks4"},
1101: {"JavaRMI"},
1102: {"JavaRMI"},
1103: {"JavaRMI"},
1105: {"Socks5", "Socks4"},
1109: {"Socks5", "Socks4"},
1111: {"Help"},
1112: {"SMBProgNeg"},
1129: {"JavaRMI"},
1194: {"OpenVPN"},
1199: {"JavaRMI"},
1200: {"NCP"},
1212: {"GenericLines"},
1214: {"GetRequest"},
1217: {"NCP"},
1220: {"GenericLines", "GetRequest"},
1234: {"GetRequest", "JavaRMI"},
1241: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "NessusTPv12", "NessusTPv12", "NessusTPv11", "NessusTPv11", "NessusTPv10", "NessusTPv10"},
1248: {"GenericLines"},
1302: {"GenericLines"},
1311: {"GetRequest", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
1314: {"GetRequest"},
1344: {"GetRequest"},
1352: {"NotesRPC"},
1400: {"GenericLines"},
1414: {"ibm-mqseries"},
1415: {"ibm-mqseries"},
1416: {"ibm-mqseries"},
1417: {"ibm-mqseries"},
1418: {"ibm-mqseries"},
1419: {"ibm-mqseries"},
1420: {"ibm-mqseries"},
1432: {"GenericLines"},
1433: {"ms-sql-s", "RPCCheck"},
1440: {"JavaRMI"},
1443: {"GetRequest", "SSLSessionReq"},
1467: {"GenericLines"},
1500: {"Verifier"},
1501: {"GenericLines", "VerifierAdvanced"},
1503: {"GetRequest", "TerminalServer"},
1505: {"GenericLines"},
1521: {"oracle-tns"},
1522: {"oracle-tns"},
1525: {"oracle-tns"},
1526: {"oracle-tns", "informix", "drda"},
1527: {"drda"},
1549: {"WMSRequest"},
1550: {"X11Probe"},
1574: {"oracle-tns"},
1583: {"pervasive-relational", "pervasive-btrieve"},
1599: {"LibreOfficeImpressSCPair"},
1610: {"GetRequest"},
1611: {"GetRequest"},
1666: {"GenericLines"},
1687: {"GenericLines"},
1688: {"GenericLines"},
1702: {"LDAPSearchReq", "LDAPBindReq"},
1720: {"TerminalServer"},
1748: {"oracle-tns"},
1754: {"oracle-tns"},
1755: {"WMSRequest"},
1761: {"LANDesk-RC"},
1762: {"LANDesk-RC"},
1763: {"LANDesk-RC"},
1830: {"GetRequest"},
1883: {"mqtt"},
1900: {"GetRequest"},
1911: {"niagara-fox"},
1935: {"TerminalServer"},
1962: {"pcworx"},
1972: {"NotesRPC"},
1981: {"JavaRMI"},
2000: {"SSLSessionReq", "SSLv23SessionReq", "NCP"},
2001: {"GetRequest"},
2002: {"GetRequest", "X11Probe"},
2010: {"GenericLines"},
2023: {"tn3270"},
2024: {"GenericLines"},
2030: {"GetRequest"},
2040: {"TerminalServer"},
2049: {"RPCCheck"},
2050: {"dominoconsole"},
2064: {"GetRequest"},
2068: {"DNSVersionBindReqTCP"},
2100: {"FourOhFourRequest"},
2105: {"DNSVersionBindReqTCP"},
2160: {"GetRequest"},
2181: {"Memcache"},
2199: {"JavaRMI"},
2221: {"SSLSessionReq"},
2252: {"TLSSessionReq", "SSLSessionReq", "NJE"},
2301: {"HTTPOptions"},
2306: {"GetRequest"},
2323: {"tn3270"},
2375: {"docker"},
2376: {"SSLSessionReq", "docker"},
2379: {"docker"},
2380: {"docker"},
2396: {"GetRequest"},
2401: {"Help"},
2443: {"SSLSessionReq"},
2481: {"giop"},
2482: {"giop"},
2525: {"GetRequest"},
2600: {"GenericLines"},
2627: {"Help"},
2701: {"LANDesk-RC"},
2715: {"GetRequest"},
2809: {"JavaRMI"},
2869: {"GetRequest"},
2947: {"LPDString"},
2967: {"DNSVersionBindReqTCP"},
3000: {"GenericLines", "GetRequest", "Help", "NCP"},
3001: {"NCP"},
3002: {"GetRequest", "NCP"},
3003: {"NCP"},
3004: {"NCP"},
3005: {"GenericLines", "NCP"},
3006: {"SMBProgNeg", "NCP"},
3025: {"Hello"},
3031: {"NCP"},
3050: {"firebird"},
3052: {"GetRequest", "RTSPRequest"},
3127: {"mydoom"},
3128: {"GenericLines", "GetRequest", "HTTPOptions", "mydoom", "Socks5", "Socks4"},
3129: {"mydoom"},
3130: {"mydoom"},
3131: {"mydoom"},
3132: {"mydoom"},
3133: {"mydoom"},
3134: {"mydoom"},
3135: {"mydoom"},
3136: {"mydoom"},
3137: {"mydoom"},
3138: {"mydoom"},
3139: {"mydoom"},
3140: {"mydoom"},
3141: {"mydoom"},
3142: {"mydoom"},
3143: {"mydoom"},
3144: {"mydoom"},
3145: {"mydoom"},
3146: {"mydoom"},
3147: {"mydoom"},
3148: {"mydoom"},
3149: {"mydoom"},
3150: {"mydoom"},
3151: {"mydoom"},
3152: {"mydoom"},
3153: {"mydoom"},
3154: {"mydoom"},
3155: {"mydoom"},
3156: {"mydoom"},
3157: {"mydoom"},
3158: {"mydoom"},
3159: {"mydoom"},
3160: {"mydoom"},
3161: {"mydoom"},
3162: {"mydoom"},
3163: {"mydoom"},
3164: {"mydoom"},
3165: {"mydoom"},
3166: {"mydoom"},
3167: {"mydoom"},
3168: {"mydoom"},
3169: {"mydoom"},
3170: {"mydoom"},
3171: {"mydoom"},
3172: {"mydoom"},
3173: {"mydoom"},
3174: {"mydoom"},
3175: {"mydoom"},
3176: {"mydoom"},
3177: {"mydoom"},
3178: {"mydoom"},
3179: {"mydoom"},
3180: {"mydoom"},
3181: {"mydoom"},
3182: {"mydoom"},
3183: {"mydoom"},
3184: {"mydoom"},
3185: {"mydoom"},
3186: {"mydoom"},
3187: {"mydoom"},
3188: {"mydoom"},
3189: {"mydoom"},
3190: {"mydoom"},
3191: {"mydoom"},
3192: {"mydoom"},
3193: {"mydoom"},
3194: {"mydoom"},
3195: {"mydoom"},
3196: {"mydoom"},
3197: {"mydoom"},
3198: {"mydoom"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq"},
3273: {"JavaRMI"},
3280: {"GetRequest"},
3310: {"GenericLines", "VersionRequest"},
3333: {"GenericLines", "LPDString", "JavaRMI", "kumo-server"},
3351: {"pervasive-relational", "pervasive-btrieve"},
3372: {"GetRequest", "RTSPRequest"},
3388: {"TLSSessionReq", "TerminalServerCookie", "TerminalServer"},
3389: {"TerminalServerCookie", "TerminalServer", "TLSSessionReq"},
3443: {"GetRequest", "SSLSessionReq"},
3493: {"Help"},
3531: {"GetRequest"},
3632: {"DistCCD"},
3689: {"GetRequest"},
3790: {"metasploit-msgrpc"},
3872: {"GetRequest"},
3892: {"LDAPSearchReq", "LDAPBindReq"},
3900: {"SMBProgNeg", "JavaRMI"},
3940: {"GenericLines"},
4000: {"GetRequest", "NoMachine"},
4035: {"LDAPBindReq", "LDAPBindReq"},
4045: {"RPCCheck"},
4155: {"GenericLines"},
4369: {"epmd"},
4433: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "FourOhFourRequest"},
4444: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4533: {"rotctl"},
4567: {"GetRequest"},
4660: {"GetRequest"},
4711: {"GetRequest", "piholeVersion"},
4899: {"Radmin"},
4911: {"SSLSessionReq", "niagara-fox"},
4999: {"RPCCheck"},
5000: {"GenericLines", "GetRequest", "RTSPRequest", "DNSVersionBindReqTCP", "SMBProgNeg", "ZendJavaBridge"},
5001: {"WMSRequest", "ZendJavaBridge"},
5002: {"ZendJavaBridge"},
5009: {"SMBProgNeg"},
5060: {"GetRequest", "SIPOptions"},
5061: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SIPOptions"},
5201: {"iperf3"},
5222: {"GetRequest"},
5232: {"HTTPOptions"},
5269: {"GetRequest"},
5280: {"GetRequest"},
5302: {"X11Probe"},
5323: {"DNSVersionBindReqTCP"},
5400: {"GenericLines"},
5427: {"GetRequest"},
5432: {"GenericLines", "GetRequest", "SMBProgNeg"},
5443: {"SSLSessionReq"},
5520: {"DNSVersionBindReqTCP", "JavaRMI"},
5521: {"JavaRMI"},
5530: {"DNSVersionBindReqTCP"},
5550: {"SSLSessionReq", "SSLv23SessionReq"},
5555: {"GenericLines", "DNSVersionBindReqTCP", "SMBProgNeg", "adbConnect"},
5556: {"DNSVersionBindReqTCP"},
5570: {"GenericLines"},
5580: {"JavaRMI"},
5600: {"SMBProgNeg"},
5701: {"hazelcast-http"},
5702: {"hazelcast-http"},
5703: {"hazelcast-http"},
5704: {"hazelcast-http"},
5705: {"hazelcast-http"},
5706: {"hazelcast-http"},
5707: {"hazelcast-http"},
5708: {"hazelcast-http"},
5709: {"LANDesk-RC", "hazelcast-http"},
5800: {"GetRequest"},
5801: {"GetRequest"},
5802: {"GetRequest"},
5803: {"GetRequest"},
5868: {"SSLSessionReq"},
5900: {"GetRequest"},
5985: {"GetRequest"},
5986: {"GetRequest", "SSLSessionReq"},
5999: {"JavaRMI"},
6000: {"HTTPOptions", "X11Probe"},
6001: {"X11Probe"},
6002: {"X11Probe"},
6003: {"X11Probe"},
6004: {"X11Probe"},
6005: {"X11Probe"},
6006: {"X11Probe"},
6007: {"X11Probe"},
6008: {"X11Probe"},
6009: {"X11Probe"},
6010: {"X11Probe"},
6011: {"X11Probe"},
6012: {"X11Probe"},
6013: {"X11Probe"},
6014: {"X11Probe"},
6015: {"X11Probe"},
6016: {"X11Probe"},
6017: {"X11Probe"},
6018: {"X11Probe"},
6019: {"X11Probe"},
6020: {"X11Probe"},
6050: {"DNSStatusRequestTCP"},
6060: {"JavaRMI"},
6103: {"GetRequest"},
6112: {"GenericLines"},
6163: {"HELP4STOMP"},
6251: {"SSLSessionReq"},
6346: {"GetRequest"},
6379: {"redis-server"},
6432: {"GenericLines"},
6443: {"SSLSessionReq"},
6543: {"DNSVersionBindReqTCP"},
6544: {"GetRequest"},
6560: {"Help"},
6588: {"Socks5", "Socks4"},
6600: {"GetRequest"},
6660: {"Socks5", "Socks4"},
6661: {"Socks5", "Socks4"},
6662: {"Socks5", "Socks4"},
6663: {"Socks5", "Socks4"},
6664: {"Socks5", "Socks4"},
6665: {"Socks5", "Socks4"},
6666: {"Help", "Socks5", "Socks4", "beast2", "vp3"},
6667: {"GenericLines", "Help", "Socks5", "Socks4"},
6668: {"GenericLines", "Help", "Socks5", "Socks4"},
6669: {"GenericLines", "Help", "Socks5", "Socks4"},
6670: {"GenericLines", "Help"},
6679: {"TLSSessionReq", "SSLSessionReq"},
6697: {"TLSSessionReq", "SSLSessionReq"},
6699: {"GetRequest"},
6715: {"JMON", "JMON"},
6789: {"JavaRMI"},
6802: {"NCP"},
6969: {"GetRequest"},
6996: {"JavaRMI"},
7000: {"RPCCheck", "DNSVersionBindReqTCP", "SSLSessionReq", "X11Probe"},
7002: {"GetRequest"},
7007: {"GetRequest"},
7008: {"DNSVersionBindReqTCP"},
7070: {"GetRequest", "RTSPRequest"},
7100: {"GetRequest", "X11Probe"},
7101: {"X11Probe"},
7144: {"GenericLines"},
7145: {"GenericLines"},
7171: {"NotesRPC"},
7200: {"GenericLines"},
7210: {"SSLSessionReq", "SSLv23SessionReq"},
7272: {"SSLSessionReq", "SSLv23SessionReq"},
7402: {"GetRequest"},
7443: {"GetRequest", "SSLSessionReq"},
7461: {"SMBProgNeg"},
7700: {"JavaRMI"},
7776: {"GetRequest"},
7777: {"X11Probe", "Socks5", "Arucer"},
7780: {"GenericLines"},
7800: {"JavaRMI"},
7801: {"JavaRMI"},
7878: {"JavaRMI"},
7887: {"xmlsysd"},
7890: {"JavaRMI"},
8000: {"GenericLines", "GetRequest", "X11Probe", "FourOhFourRequest", "Socks5", "Socks4"},
8001: {"GetRequest", "FourOhFourRequest"},
8002: {"GetRequest", "FourOhFourRequest"},
8003: {"GetRequest", "FourOhFourRequest"},
8004: {"GetRequest", "FourOhFourRequest"},
8005: {"GetRequest", "FourOhFourRequest"},
8006: {"GetRequest", "FourOhFourRequest"},
8007: {"GetRequest", "FourOhFourRequest"},
8008: {"GetRequest", "FourOhFourRequest", "Socks5", "Socks4", "ajp"},
8009: {"GetRequest", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest", "ajp"},
8010: {"GetRequest", "FourOhFourRequest", "Socks5"},
8050: {"JavaRMI"},
8051: {"JavaRMI"},
8080: {"GetRequest", "HTTPOptions", "RTSPRequest", "FourOhFourRequest", "Socks5", "Socks4"},
8081: {"GetRequest", "FourOhFourRequest", "SIPOptions", "WWWOFFLEctrlstat"},
8082: {"GetRequest", "FourOhFourRequest"},
8083: {"GetRequest", "FourOhFourRequest"},
8084: {"GetRequest", "FourOhFourRequest"},
8085: {"GetRequest", "FourOhFourRequest", "JavaRMI"},
8087: {"riak-pbc"},
8088: {"GetRequest", "Socks5", "Socks4"},
8091: {"JavaRMI"},
8118: {"GetRequest"},
8138: {"GenericLines"},
8181: {"GetRequest", "SSLSessionReq"},
8194: {"SSLSessionReq", "SSLv23SessionReq"},
8205: {"JavaRMI"},
8303: {"JavaRMI"},
8307: {"RPCCheck"},
8333: {"RPCCheck"},
8443: {"GetRequest", "HTTPOptions", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest"},
8530: {"GetRequest"},
8531: {"GetRequest", "SSLSessionReq"},
8642: {"JavaRMI"},
8686: {"JavaRMI"},
8701: {"JavaRMI"},
8728: {"NotesRPC"},
8770: {"apple-iphoto"},
8880: {"GetRequest", "FourOhFourRequest"},
8881: {"GetRequest", "FourOhFourRequest"},
8882: {"GetRequest", "FourOhFourRequest"},
8883: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "FourOhFourRequest", "mqtt"},
8884: {"GetRequest", "FourOhFourRequest"},
8885: {"GetRequest", "FourOhFourRequest"},
8886: {"GetRequest", "FourOhFourRequest"},
8887: {"GetRequest", "FourOhFourRequest"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI", "LSCP"},
8889: {"JavaRMI"},
8890: {"JavaRMI"},
8901: {"JavaRMI"},
8902: {"JavaRMI"},
8903: {"JavaRMI"},
8999: {"JavaRMI"},
9000: {"GenericLines", "GetRequest"},
9001: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "JavaRMI", "Radmin", "mongodb", "tarantool", "tor-versions"},
9002: {"GenericLines", "tor-versions"},
9003: {"GenericLines", "JavaRMI"},
9004: {"JavaRMI"},
9005: {"JavaRMI"},
9030: {"GetRequest"},
9050: {"GetRequest", "JavaRMI"},
9080: {"GetRequest"},
9088: {"informix", "drda"},
9089: {"informix", "drda"},
9090: {"GetRequest", "JavaRMI", "WMSRequest", "ibm-db2-das", "SqueezeCenter_CLI", "informix", "drda"},
9091: {"informix", "drda"},
9092: {"informix", "drda"},
9093: {"informix", "drda"},
9094: {"informix", "drda"},
9095: {"informix", "drda"},
9096: {"informix", "drda"},
9097: {"informix", "drda"},
9098: {"informix", "drda"},
9099: {"JavaRMI", "informix", "drda"},
9100: {"hp-pjl", "informix", "drda"},
9101: {"hp-pjl"},
9102: {"SMBProgNeg", "hp-pjl"},
9103: {"SMBProgNeg", "hp-pjl"},
9104: {"hp-pjl"},
9105: {"hp-pjl"},
9106: {"hp-pjl"},
9107: {"hp-pjl"},
9300: {"JavaRMI"},
9390: {"metasploit-xmlrpc"},
9443: {"GetRequest", "SSLSessionReq"},
9481: {"Socks5"},
9500: {"JavaRMI"},
9711: {"JavaRMI"},
9761: {"insteonPLM"},
9801: {"GenericLines"},
9809: {"JavaRMI"},
9810: {"JavaRMI"},
9811: {"JavaRMI"},
9812: {"JavaRMI"},
9813: {"JavaRMI"},
9814: {"JavaRMI"},
9815: {"JavaRMI"},
9875: {"JavaRMI"},
9910: {"JavaRMI"},
9930: {"ibm-db2-das"},
9931: {"ibm-db2-das"},
9932: {"ibm-db2-das"},
9933: {"ibm-db2-das"},
9934: {"ibm-db2-das"},
9991: {"JavaRMI"},
9998: {"teamspeak-tcpquery-ver"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10000: {"GetRequest", "HTTPOptions", "RTSPRequest"},
10001: {"GetRequest", "JavaRMI", "ZendJavaBridge"},
10002: {"ZendJavaBridge", "SharpTV"},
10003: {"ZendJavaBridge"},
10005: {"GetRequest"},
10031: {"HTTPOptions"},
10098: {"JavaRMI"},
10099: {"JavaRMI"},
10162: {"JavaRMI"},
10333: {"teamtalk-login"},
10443: {"GetRequest", "SSLSessionReq"},
10990: {"JavaRMI"},
11001: {"JavaRMI"},
11099: {"JavaRMI"},
11210: {"couchbase-data"},
11211: {"Memcache"},
11333: {"JavaRMI"},
11371: {"GenericLines", "GetRequest"},
11711: {"LDAPSearchReq"},
11712: {"LDAPSearchReq"},
11965: {"GenericLines"},
12000: {"JavaRMI"},
12345: {"Help", "OfficeScan"},
13013: {"GetRequest", "JavaRMI"},
13666: {"GetRequest"},
13720: {"GenericLines"},
13722: {"GetRequest"},
13783: {"DNSVersionBindReqTCP"},
14000: {"JavaRMI"},
14238: {"oracle-tns"},
14443: {"GetRequest", "SSLSessionReq"},
14534: {"GetRequest"},
14690: {"Help"},
15000: {"GenericLines", "GetRequest", "JavaRMI"},
15001: {"GenericLines", "JavaRMI"},
15002: {"GenericLines", "SSLSessionReq"},
15200: {"JavaRMI"},
16000: {"JavaRMI"},
17007: {"RPCCheck"},
17200: {"JavaRMI"},
17988: {"GetRequest"},
18086: {"GenericLines"},
18182: {"SMBProgNeg"},
18264: {"GetRequest"},
18980: {"JavaRMI"},
19150: {"GenericLines", "gkrellm"},
19350: {"LPDString"},
19700: {"kumo-server"},
19800: {"kumo-server"},
20000: {"JavaRMI", "oracle-tns"},
20547: {"proconos"},
22001: {"NotesRPC"},
22490: {"Help"},
23791: {"JavaRMI"},
25565: {"minecraft-ping"},
26214: {"GenericLines"},
26256: {"JavaRMI"},
26470: {"GenericLines"},
27000: {"SMBProgNeg"},
27001: {"SMBProgNeg"},
27002: {"SMBProgNeg"},
27003: {"SMBProgNeg"},
27004: {"SMBProgNeg"},
27005: {"SMBProgNeg"},
27006: {"SMBProgNeg"},
27007: {"SMBProgNeg"},
27008: {"SMBProgNeg"},
27009: {"SMBProgNeg"},
27010: {"SMBProgNeg"},
27017: {"mongodb"},
27036: {"TLS-PSK"},
30444: {"GenericLines"},
31099: {"JavaRMI"},
31337: {"GetRequest", "SIPOptions"},
31416: {"GenericLines"},
32211: {"LPDString"},
32750: {"RPCCheck"},
32751: {"RPCCheck"},
32752: {"RPCCheck"},
32753: {"RPCCheck"},
32754: {"RPCCheck"},
32755: {"RPCCheck"},
32756: {"RPCCheck"},
32757: {"RPCCheck"},
32758: {"RPCCheck"},
32759: {"RPCCheck"},
32760: {"RPCCheck"},
32761: {"RPCCheck"},
32762: {"RPCCheck"},
32763: {"RPCCheck"},
32764: {"RPCCheck"},
32765: {"RPCCheck"},
32766: {"RPCCheck"},
32767: {"RPCCheck"},
32768: {"RPCCheck"},
32769: {"RPCCheck"},
32770: {"RPCCheck"},
32771: {"RPCCheck"},
32772: {"RPCCheck"},
32773: {"RPCCheck"},
32774: {"RPCCheck"},
32775: {"RPCCheck"},
32776: {"RPCCheck"},
32777: {"RPCCheck"},
32778: {"RPCCheck"},
32779: {"RPCCheck"},
32780: {"RPCCheck"},
32781: {"RPCCheck"},
32782: {"RPCCheck"},
32783: {"RPCCheck"},
32784: {"RPCCheck"},
32785: {"RPCCheck"},
32786: {"RPCCheck"},
32787: {"RPCCheck"},
32788: {"RPCCheck"},
32789: {"RPCCheck"},
32790: {"RPCCheck"},
32791: {"RPCCheck"},
32792: {"RPCCheck"},
32793: {"RPCCheck"},
32794: {"RPCCheck"},
32795: {"RPCCheck"},
32796: {"RPCCheck"},
32797: {"RPCCheck"},
32798: {"RPCCheck"},
32799: {"RPCCheck"},
32800: {"RPCCheck"},
32801: {"RPCCheck"},
32802: {"RPCCheck"},
32803: {"RPCCheck"},
32804: {"RPCCheck"},
32805: {"RPCCheck"},
32806: {"RPCCheck"},
32807: {"RPCCheck"},
32808: {"RPCCheck"},
32809: {"RPCCheck"},
32810: {"RPCCheck"},
32913: {"JavaRMI"},
33000: {"JavaRMI"},
33015: {"tarantool"},
34012: {"GenericLines"},
37435: {"HTTPOptions"},
37718: {"JavaRMI"},
38978: {"RPCCheck"},
40193: {"GetRequest"},
41523: {"DNSStatusRequestTCP"},
44443: {"GetRequest", "SSLSessionReq"},
45230: {"JavaRMI"},
47001: {"JavaRMI"},
47002: {"JavaRMI"},
49152: {"FourOhFourRequest"},
49153: {"mongodb"},
49400: {"HTTPOptions"},
50000: {"GetRequest", "ibm-db2-das", "ibm-db2", "drda"},
50001: {"ibm-db2"},
50002: {"ibm-db2"},
50003: {"ibm-db2"},
50004: {"ibm-db2"},
50005: {"ibm-db2"},
50006: {"ibm-db2"},
50007: {"ibm-db2"},
50008: {"ibm-db2"},
50009: {"ibm-db2"},
50010: {"ibm-db2"},
50011: {"ibm-db2"},
50012: {"ibm-db2"},
50013: {"ibm-db2"},
50014: {"ibm-db2"},
50015: {"ibm-db2"},
50016: {"ibm-db2"},
50017: {"ibm-db2"},
50018: {"ibm-db2"},
50019: {"ibm-db2"},
50020: {"ibm-db2"},
50021: {"ibm-db2"},
50022: {"ibm-db2"},
50023: {"ibm-db2"},
50024: {"ibm-db2"},
50025: {"ibm-db2"},
50050: {"JavaRMI"},
50500: {"JavaRMI"},
50501: {"JavaRMI"},
50502: {"JavaRMI"},
50503: {"JavaRMI"},
50504: {"JavaRMI"},
50505: {"metasploit-msgrpc"},
51234: {"teamspeak-tcpquery-ver"},
55552: {"metasploit-msgrpc"},
55553: {"metasploit-xmlrpc", "metasploit-xmlrpc"},
55555: {"GetRequest"},
56667: {"GenericLines"},
59100: {"kumo-server"},
60000: {"ibm-db2", "drda"},
60001: {"ibm-db2"},
60002: {"ibm-db2"},
60003: {"ibm-db2"},
60004: {"ibm-db2"},
60005: {"ibm-db2"},
60006: {"ibm-db2"},
60007: {"ibm-db2"},
60008: {"ibm-db2"},
60009: {"ibm-db2"},
60010: {"ibm-db2"},
60011: {"ibm-db2"},
60012: {"ibm-db2"},
60013: {"ibm-db2"},
60014: {"ibm-db2"},
60015: {"ibm-db2"},
60016: {"ibm-db2"},
60017: {"ibm-db2"},
60018: {"ibm-db2"},
60019: {"ibm-db2"},
60020: {"ibm-db2"},
60021: {"ibm-db2"},
60022: {"ibm-db2"},
60023: {"ibm-db2"},
60024: {"ibm-db2"},
60025: {"ibm-db2"},
60443: {"GetRequest", "SSLSessionReq"},
61613: {"HELP4STOMP"},
}
var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "Password", "P@ssword123", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456", "1q2w3e", "Charge123", "Aa123456789", "elastic123"}
var (
Outputfile string // 输出文件路径
OutputFormat string // 输出格式
)
// 添加一个全局的进度条变量
var ProgressBar *progressbar.ProgressBar
// 添加一个全局互斥锁来控制输出
var OutputMutex sync.Mutex
type PocInfo struct {
Target string
PocName string
}
var (
// =========================================================
// 扫描目标配置
// =========================================================
Ports string // 要扫描的端口列表,如"80,443,8080"
ExcludePorts string // 要排除的端口列表
ExcludeHosts string // 要排除的主机列表
AddPorts string // 额外添加的端口列表
HostPort []string // 主机:端口格式的目标列表
// =========================================================
// 认证与凭据配置
// =========================================================
Username string // 用于认证的用户名
Password string // 用于认证的密码
AddUsers string // 额外添加的用户名列表
AddPasswords string // 额外添加的密码列表
// 特定服务认证
Domain string // Active Directory/SMB域名
HashValue string // 用于哈希认证的单个哈希值
HashValues []string // 哈希值列表
HashBytes [][]byte // 二进制格式的哈希值列表
HashFile string // 包含哈希值的文件路径
SshKeyPath string // SSH私钥文件路径
// =========================================================
// 扫描控制配置
// =========================================================
ScanMode string // 扫描模式或指定的插件列表
ThreadNum int // 并发扫描线程数
ModuleThreadNum int // 模块内部线程数
Timeout int64 // 单个扫描操作超时时间(秒)
GlobalTimeout int64 // 整体扫描超时时间(秒)
LiveTop int // 显示的存活主机排名数量
DisablePing bool // 是否禁用主机存活性检测
UsePing bool // 是否使用ICMP Ping检测主机存活
EnableFingerprint bool // 是否跳过服务指纹识别
LocalMode bool // 是否启用本地信息收集模式
// =========================================================
// 输入文件配置
// =========================================================
HostsFile string // 包含目标主机的文件路径
UsersFile string // 包含用户名列表的文件路径
PasswordsFile string // 包含密码列表的文件路径
PortsFile string // 包含端口列表的文件路径
// =========================================================
// Web扫描配置
// =========================================================
TargetURL string // 单个目标URL
URLsFile string // 包含URL列表的文件路径
URLs []string // 解析后的URL目标列表
WebTimeout int64 // Web请求超时时间(秒)默认5秒
HttpProxy string // HTTP代理地址
Socks5Proxy string // SOCKS5代理地址
// =========================================================
// POC与漏洞利用配置
// =========================================================
// POC配置
PocPath string // POC脚本路径
Pocinfo PocInfo // POC详细信息结构
DisablePocScan bool //nopoc
// Redis利用
RedisFile string // Redis利用目标文件
RedisShell string // Redis反弹Shell命令
DisableRedis bool // 是否禁用Redis利用测试
RedisWritePath string // Redis文件写入路径
RedisWriteContent string // Redis文件写入内容
RedisWriteFile string // Redis写入的源文件
// 其他漏洞利用
Shellcode string // 用于MS17010等漏洞利用的Shellcode
// =========================================================
// 暴力破解控制
// =========================================================
DisableBrute bool // 是否禁用暴力破解模块
MaxRetries int // 连接失败最大重试次数
// =========================================================
// 输出与显示配置
// =========================================================
DisableSave bool // 是否禁止保存扫描结果
Silent bool // 是否启用静默模式
NoColor bool // 是否禁用彩色输出
LogLevel string // 日志输出级别
ShowProgress bool // 是否显示进度条
ShowScanPlan bool // 是否显示扫描计划详情
SlowLogOutput bool // 是否启用慢速日志输出
Language string // 界面语言设置
)
var (
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
DnsLog bool
PocNum int
PocFull bool
Cookie string
)

View File

@ -1,4 +1,4 @@
package Common package common
import ( import (
"flag" "flag"
@ -7,8 +7,72 @@ import (
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/common/i18n"
) )
// Flag专用变量 (只在Flag.go中使用的变量直接定义在这里)
var (
ExcludeHosts string
Ports string
ExcludePorts string
AddPorts string
HostsFile string
PortsFile string
ModuleThreadNum int
GlobalTimeout int64
EnableFingerprint bool
AddUsers string
AddPasswords string
UsersFile string
PasswordsFile string
HashFile string
HashValue string
Domain string
SshKeyPath string
TargetURL string
URLsFile string
Cookie string
WebTimeout int64
UserAgent string
Accept string
PocPath string
PocFull bool
DnsLog bool
PocNum int
DisablePocScan bool
RedisFile string
RedisShell string
DisableRedis bool
RedisWritePath string
RedisWriteContent string
RedisWriteFile string
DisableBrute bool
DisableExploit bool
MaxRetries int
DisableSave bool
Silent bool
DisableProgress bool
Shellcode string
// Parse.go 使用的变量
HostPort []string
URLs []string
HashValues []string
HashBytes [][]byte
)
// Pocinfo POC信息变量
var Pocinfo config.PocInfo
func Banner() { func Banner() {
// 定义暗绿色系 // 定义暗绿色系
colors := []color.Attribute{ colors := []color.Attribute{
@ -60,104 +124,120 @@ func Banner() {
func Flag(Info *HostInfo) { func Flag(Info *HostInfo) {
Banner() Banner()
// 预处理语言设置 - 在定义flag之前检查lang参数
preProcessLanguage()
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 目标配置参数 // 目标配置参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Info.Host, "h", "", GetText("flag_host")) flag.StringVar(&Info.Host, "h", "", i18n.GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts")) flag.StringVar(&ExcludeHosts, "eh", "", i18n.GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports")) flag.StringVar(&Ports, "p", MainPorts, i18n.GetText("flag_ports"))
flag.StringVar(&ExcludePorts, "ep", "", GetText("flag_exclude_ports")) flag.StringVar(&ExcludePorts, "ep", "", i18n.GetText("flag_exclude_ports"))
flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file")) flag.StringVar(&HostsFile, "hf", "", i18n.GetText("flag_hosts_file"))
flag.StringVar(&PortsFile, "pf", "", GetText("flag_ports_file")) flag.StringVar(&PortsFile, "pf", "", i18n.GetText("flag_ports_file"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 扫描控制参数 // 扫描控制参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&ScanMode, "m", "all", GetText("flag_scan_mode")) flag.StringVar(&ScanMode, "m", "all", i18n.GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 600, GetText("flag_thread_num")) flag.IntVar(&ThreadNum, "t", 600, i18n.GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout")) flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
flag.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num")) flag.IntVar(&ModuleThreadNum, "mt", 50, i18n.GetText("flag_module_thread_num"))
flag.Int64Var(&GlobalTimeout, "gt", 180, GetText("flag_global_timeout")) flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top")) // LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping")) flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping")) flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, GetText("flag_enable_fingerprint")) flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode")) flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 认证与凭据参数 // 认证与凭据参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Username, "user", "", GetText("flag_username")) flag.StringVar(&Username, "user", "", i18n.GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", GetText("flag_password")) flag.StringVar(&Password, "pwd", "", i18n.GetText("flag_password"))
flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users")) flag.StringVar(&AddUsers, "usera", "", i18n.GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords")) flag.StringVar(&AddPasswords, "pwda", "", i18n.GetText("flag_add_passwords"))
flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file")) flag.StringVar(&UsersFile, "userf", "", i18n.GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file")) flag.StringVar(&PasswordsFile, "pwdf", "", i18n.GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file")) flag.StringVar(&HashFile, "hashf", "", i18n.GetText("flag_hash_file"))
flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value")) flag.StringVar(&HashValue, "hash", "", i18n.GetText("flag_hash_value"))
flag.StringVar(&Domain, "domain", "", GetText("flag_domain")) // SMB扫描用 flag.StringVar(&Domain, "domain", "", i18n.GetText("flag_domain")) // SMB扫描用
flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key")) // SSH扫描用 flag.StringVar(&SshKeyPath, "sshkey", "", i18n.GetText("flag_ssh_key")) // SSH扫描用
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// Web扫描参数 // Web扫描参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url")) flag.StringVar(&TargetURL, "u", "", i18n.GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file")) flag.StringVar(&URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie")) flag.StringVar(&Cookie, "cookie", "", i18n.GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, GetText("flag_web_timeout")) flag.Int64Var(&WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy")) flag.StringVar(&HttpProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy")) flag.StringVar(&Socks5Proxy, "socks5", "", i18n.GetText("flag_socks5_proxy"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// POC测试参数 // POC测试参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path")) flag.StringVar(&PocPath, "pocpath", "", i18n.GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name")) flag.StringVar(&Pocinfo.PocName, "pocname", "", i18n.GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full")) flag.BoolVar(&PocFull, "full", false, i18n.GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log")) flag.BoolVar(&DnsLog, "dns", false, i18n.GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num")) flag.IntVar(&PocNum, "num", 20, i18n.GetText("flag_poc_num"))
flag.BoolVar(&DisablePocScan, "nopoc", false, GetText("flag_no_poc")) flag.BoolVar(&DisablePocScan, "nopoc", false, i18n.GetText("flag_no_poc"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// Redis利用参数 // Redis利用参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file")) flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell")) flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis")) flag.BoolVar(&DisableRedis, "noredis", false, i18n.GetText("flag_disable_redis"))
flag.StringVar(&RedisWritePath, "rwp", "", GetText("flag_redis_write_path")) flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", GetText("flag_redis_write_content")) flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", GetText("flag_redis_write_file")) flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 暴力破解控制参数 // 暴力破解控制参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute")) flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries")) flag.BoolVar(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 输出与显示控制参数 // 输出与显示控制参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file")) flag.StringVar(&Outputfile, "o", "result.txt", i18n.GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format")) flag.StringVar(&OutputFormat, "f", "txt", i18n.GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save")) flag.BoolVar(&DisableSave, "no", false, i18n.GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode")) flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color")) flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level")) flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress")) flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 其他参数 // 其他参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode")) flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&Language, "lang", "zh", GetText("flag_language")) flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数
var showHelp bool
flag.BoolVar(&showHelp, "help", false, i18n.GetText("flag_help"))
// 解析命令行参数 // 解析命令行参数
parseCommandLineArgs() parseCommandLineArgs()
// 设置语言 // 设置语言
SetLanguage() i18n.SetLanguage(Language)
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
os.Exit(0)
}
} }
// parseCommandLineArgs 处理来自环境变量和命令行的参数 // parseCommandLineArgs 处理来自环境变量和命令行的参数
@ -177,6 +257,9 @@ func parseCommandLineArgs() {
// 解析命令行参数 // 解析命令行参数
flag.Parse() flag.Parse()
// 检查参数冲突
checkParameterConflicts()
} }
// parseEnvironmentArgs 安全地解析环境变量中的参数 // parseEnvironmentArgs 安全地解析环境变量中的参数
@ -218,3 +301,53 @@ func parseEnvironmentArgs(argsString string) ([]string, error) {
return args, nil return args, nil
} }
// preProcessLanguage 预处理语言参数在定义flag之前设置语言
func preProcessLanguage() {
// 遍历命令行参数查找-lang参数
for i, arg := range os.Args {
if arg == "-lang" && i+1 < len(os.Args) {
lang := os.Args[i+1]
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
} else if strings.HasPrefix(arg, "-lang=") {
lang := strings.TrimPrefix(arg, "-lang=")
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
}
}
// 检查环境变量
envLang := os.Getenv("FS_LANG")
if envLang == "en" || envLang == "zh" {
Language = envLang
i18n.SetLanguage(envLang)
}
}
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != "" || LocalMode
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
return true
}
return false
}
// checkParameterConflicts 检查参数冲突和兼容性
func checkParameterConflicts() {
// 检查 -ao 和 -m icmp 同时指定的情况(向后兼容提示)
if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
}
}

View File

@ -1,261 +0,0 @@
package Common
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/fatih/color"
)
// 全局变量定义
var (
// 扫描状态管理器,记录最近一次成功和错误的时间
status = &ScanStatus{lastSuccess: time.Now(), lastError: time.Now()}
// Num 表示待处理的总任务数量
Num int64
// End 表示已经完成的任务数量
End int64
)
// ScanStatus 用于记录和管理扫描状态的结构体
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁,用于保护并发访问
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level string // 日志级别: ERROR/INFO/SUCCESS/DEBUG
Time time.Time // 日志时间
Content string // 日志内容
}
// 定义系统支持的日志级别常量
const (
LogLevelAll = "ALL" // 显示所有级别日志
LogLevelError = "ERROR" // 仅显示错误日志
LogLevelBase = "BASE" // 仅显示信息日志
LogLevelInfo = "INFO" // 仅显示信息日志
LogLevelSuccess = "SUCCESS" // 仅显示成功日志
LogLevelDebug = "DEBUG" // 仅显示调试日志
)
// 日志级别对应的显示颜色映射
var logColors = map[string]color.Attribute{
LogLevelError: color.FgBlue, // 错误日志显示蓝色
LogLevelBase: color.FgYellow, // 信息日志显示黄色
LogLevelInfo: color.FgGreen, // 信息日志显示绿色
LogLevelSuccess: color.FgRed, // 成功日志显示红色
LogLevelDebug: color.FgWhite, // 调试日志显示白色
}
// InitLogger 初始化日志系统
func InitLogger() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
var StartTime = time.Now()
// formatLogMessage 格式化日志消息为标准格式
// 返回格式:[时间] [级别] 内容
func formatLogMessage(entry *LogEntry) string {
elapsed := time.Since(StartTime)
var timeStr string
// 根据时间长短选择合适的单位
switch {
case elapsed < time.Second:
// 毫秒显示,不需要小数
timeStr = fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < time.Minute:
// 秒显示,保留一位小数
timeStr = fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < time.Hour:
// 分钟和秒显示
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
timeStr = fmt.Sprintf("%dm%ds", minutes, seconds)
default:
// 小时、分钟和秒显示
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
timeStr = fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
str := " "
switch entry.Level {
case LogLevelSuccess:
str = "[+]"
case LogLevelInfo:
str = "[*]"
case LogLevelError:
str = "[-]"
}
return fmt.Sprintf("[%s] %s %s", timeStr, str, entry.Content)
}
// printLog 根据日志级别打印日志
func printLog(entry *LogEntry) {
if LogLevel != "debug" && (entry.Level == LogLevelDebug || entry.Level == LogLevelError) {
return
}
OutputMutex.Lock()
defer OutputMutex.Unlock()
// 处理进度条
clearAndWaitProgress()
// 打印日志消息
logMsg := formatLogMessage(entry)
if !NoColor {
// 使用彩色输出
if colorAttr, ok := logColors[entry.Level]; ok {
color.New(colorAttr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
// 普通输出
fmt.Println(logMsg)
}
// 根据慢速输出设置决定是否添加延迟
if SlowLogOutput {
time.Sleep(50 * time.Millisecond)
}
// 重新显示进度条
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// clearAndWaitProgress 清除进度条并等待
func clearAndWaitProgress() {
if ProgressBar != nil {
ProgressBar.Clear()
time.Sleep(10 * time.Millisecond)
}
}
// handleLog 统一处理日志的输出
func handleLog(entry *LogEntry) {
if ProgressBar != nil {
ProgressBar.Clear()
}
printLog(entry)
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// LogDebug 记录调试日志
func LogDebug(msg string) {
handleLog(&LogEntry{
Level: LogLevelDebug,
Time: time.Now(),
Content: msg,
})
}
// LogBase 记录进度信息
func LogBase(msg string) {
handleLog(&LogEntry{
Level: LogLevelBase,
Time: time.Now(),
Content: msg,
})
}
// LogInfo 记录信息日志
// [*]
func LogInfo(msg string) {
handleLog(&LogEntry{
Level: LogLevelInfo,
Time: time.Now(),
Content: msg,
})
}
// LogSuccess 记录成功日志,并更新最后成功时间
// [+]
func LogSuccess(result string) {
entry := &LogEntry{
Level: LogLevelSuccess,
Time: time.Now(),
Content: result,
}
handleLog(entry)
// 更新最后成功时间
status.mu.Lock()
status.lastSuccess = time.Now()
status.mu.Unlock()
}
// LogError 记录错误日志,自动包含文件名和行号信息
func LogError(errMsg string) {
// 获取调用者的文件名和行号
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "unknown"
line = 0
}
file = filepath.Base(file)
errorMsg := fmt.Sprintf("%s:%d - %s", file, line, errMsg)
entry := &LogEntry{
Level: LogLevelError,
Time: time.Now(),
Content: errorMsg,
}
handleLog(entry)
}
// CheckErrs 检查是否为需要重试的错误
func CheckErrs(err error) error {
if err == nil {
return nil
}
// 已知需要重试的错误列表
errs := []string{
"closed by the remote host", "too many connections",
"EOF", "A connection attempt failed",
"established connection failed", "connection attempt failed",
"Unable to read", "is not allowed to connect to this",
"no pg_hba.conf entry",
"No connection could be made",
"invalid packet size",
"bad connection",
}
// 检查错误是否匹配
errLower := strings.ToLower(err.Error())
for _, key := range errs {
if strings.Contains(errLower, strings.ToLower(key)) {
time.Sleep(1 * time.Second)
return err
}
}
return nil
}

View File

@ -1,312 +0,0 @@
package Common
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// 全局输出管理器
var ResultOutput *OutputManager
// OutputManager 输出管理器结构体
type OutputManager struct {
mu sync.Mutex
outputPath string
outputFormat string
file *os.File
csvWriter *csv.Writer
jsonEncoder *json.Encoder
isInitialized bool
}
// ResultType 定义结果类型
type ResultType string
const (
HOST ResultType = "HOST" // 主机存活
PORT ResultType = "PORT" // 端口开放
SERVICE ResultType = "SERVICE" // 服务识别
VULN ResultType = "VULN" // 漏洞发现
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// InitOutput 初始化输出系统
func InitOutput() error {
LogDebug(GetText("output_init_start"))
// 验证输出格式
switch OutputFormat {
case "txt", "json", "csv":
// 有效的格式
default:
return fmt.Errorf(GetText("output_format_invalid"), OutputFormat)
}
// 验证输出路径
if Outputfile == "" {
return fmt.Errorf(GetText("output_path_empty"))
}
dir := filepath.Dir(Outputfile)
if err := os.MkdirAll(dir, 0755); err != nil {
LogDebug(GetText("output_create_dir_failed", err))
return fmt.Errorf(GetText("output_create_dir_failed", err))
}
manager := &OutputManager{
outputPath: Outputfile,
outputFormat: OutputFormat,
}
if err := manager.initialize(); err != nil {
LogDebug(GetText("output_init_failed", err))
return fmt.Errorf(GetText("output_init_failed", err))
}
ResultOutput = manager
LogDebug(GetText("output_init_success"))
return nil
}
func (om *OutputManager) initialize() error {
om.mu.Lock()
defer om.mu.Unlock()
if om.isInitialized {
LogDebug(GetText("output_already_init"))
return nil
}
LogDebug(GetText("output_opening_file", om.outputPath))
file, err := os.OpenFile(om.outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
LogDebug(GetText("output_open_file_failed", err))
return fmt.Errorf(GetText("output_open_file_failed", err))
}
om.file = file
switch om.outputFormat {
case "csv":
LogDebug(GetText("output_init_csv"))
om.csvWriter = csv.NewWriter(file)
headers := []string{"Time", "Type", "Target", "Status", "Details"}
if err := om.csvWriter.Write(headers); err != nil {
LogDebug(GetText("output_write_csv_header_failed", err))
file.Close()
return fmt.Errorf(GetText("output_write_csv_header_failed", err))
}
om.csvWriter.Flush()
case "json":
LogDebug(GetText("output_init_json"))
om.jsonEncoder = json.NewEncoder(file)
om.jsonEncoder.SetIndent("", " ")
case "txt":
LogDebug(GetText("output_init_txt"))
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
}
om.isInitialized = true
LogDebug(GetText("output_init_complete"))
return nil
}
// SaveResult 保存扫描结果
func SaveResult(result *ScanResult) error {
if ResultOutput == nil {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
LogDebug(GetText("output_saving_result", result.Type, result.Target))
return ResultOutput.saveResult(result)
}
func GetResults() ([]*ScanResult, error) {
if ResultOutput == nil {
return nil, fmt.Errorf(GetText("output_not_init"))
}
if ResultOutput.outputFormat == "csv" {
return ResultOutput.getResult()
}
// 其他格式尚未实现读取支持
return nil, fmt.Errorf(GetText("output_format_read_not_supported"))
}
func (om *OutputManager) saveResult(result *ScanResult) error {
om.mu.Lock()
defer om.mu.Unlock()
if !om.isInitialized {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
var err error
switch om.outputFormat {
case "txt":
err = om.writeTxt(result)
case "json":
err = om.writeJson(result)
case "csv":
err = om.writeCsv(result)
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
return fmt.Errorf(GetText("output_format_invalid", om.outputFormat))
}
if err != nil {
LogDebug(GetText("output_save_failed", err))
} else {
LogDebug(GetText("output_save_success", result.Type, result.Target))
}
return err
}
func (om *OutputManager) getResult() ([]*ScanResult, error) {
om.mu.Lock()
defer om.mu.Unlock()
if !om.isInitialized {
LogDebug(GetText("output_not_init"))
return nil, fmt.Errorf(GetText("output_not_init"))
}
file, err := os.Open(om.outputPath)
if err != nil {
LogDebug(GetText("output_open_file_failed", err))
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
LogDebug(GetText("output_read_csv_failed", err))
return nil, err
}
var results []*ScanResult
for i, row := range records {
// 跳过 CSV 头部
if i == 0 {
continue
}
if len(row) < 5 {
continue // 数据不完整
}
t, err := time.Parse("2006-01-02 15:04:05", row[0])
if err != nil {
continue
}
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[4]), &details); err != nil {
details = make(map[string]interface{})
}
result := &ScanResult{
Time: t,
Type: ResultType(row[1]),
Target: row[2],
Status: row[3],
Details: details,
}
results = append(results, result)
}
LogDebug(GetText("output_read_csv_success", len(results)))
return results, nil
}
func (om *OutputManager) writeTxt(result *ScanResult) error {
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
}
details = strings.Join(pairs, ", ")
}
txt := GetText("output_txt_format",
result.Time.Format("2006-01-02 15:04:05"),
result.Type,
result.Target,
result.Status,
details,
) + "\n"
_, err := om.file.WriteString(txt)
return err
}
func (om *OutputManager) writeJson(result *ScanResult) error {
return om.jsonEncoder.Encode(result)
}
func (om *OutputManager) writeCsv(result *ScanResult) error {
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte("{}")
}
record := []string{
result.Time.Format("2006-01-02 15:04:05"),
string(result.Type),
result.Target,
result.Status,
string(details),
}
if err := om.csvWriter.Write(record); err != nil {
return err
}
om.csvWriter.Flush()
return om.csvWriter.Error()
}
// CloseOutput 关闭输出系统
func CloseOutput() error {
if ResultOutput == nil {
LogDebug(GetText("output_no_need_close"))
return nil
}
LogDebug(GetText("output_closing"))
ResultOutput.mu.Lock()
defer ResultOutput.mu.Unlock()
if !ResultOutput.isInitialized {
LogDebug(GetText("output_no_need_close"))
return nil
}
if ResultOutput.csvWriter != nil {
LogDebug(GetText("output_flush_csv"))
ResultOutput.csvWriter.Flush()
}
if err := ResultOutput.file.Close(); err != nil {
LogDebug(GetText("output_close_failed", err))
return fmt.Errorf(GetText("output_close_failed", err))
}
ResultOutput.isInitialized = false
LogDebug(GetText("output_closed"))
return nil
}

View File

@ -1,541 +1,333 @@
package Common package common
import ( import (
"bufio"
"encoding/hex"
"flag"
"fmt" "fmt"
"net/url" "sync"
"os" "time"
"strings"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/parsers"
"github.com/shadow1ng/fscan/common/utils"
) )
// Parse 配置解析的总入口函数 // ParsedConfiguration 解析后的完整配置(兼容旧代码)
// 协调调用各解析子函数,完成完整的配置处理流程 type ParsedConfiguration struct {
*parsers.ParsedConfig
}
// Parser 主解析器
type Parser struct {
mu sync.RWMutex
fileReader *parsers.FileReader
credentialParser *parsers.CredentialParser
targetParser *parsers.TargetParser
networkParser *parsers.NetworkParser
validationParser *parsers.ValidationParser
options *parsers.ParserOptions
initialized bool
}
// NewParser 创建新的解析器实例
func NewParser(options *parsers.ParserOptions) *Parser {
if options == nil {
options = parsers.DefaultParserOptions()
}
// 创建文件读取器
fileReader := parsers.NewFileReader(nil)
// 创建各个子解析器
credentialParser := parsers.NewCredentialParser(fileReader, nil)
targetParser := parsers.NewTargetParser(fileReader, nil)
networkParser := parsers.NewNetworkParser(nil)
validationParser := parsers.NewValidationParser(nil)
return &Parser{
fileReader: fileReader,
credentialParser: credentialParser,
targetParser: targetParser,
networkParser: networkParser,
validationParser: validationParser,
options: options,
initialized: true,
}
}
// 全局解析器实例
var globalParser *Parser
var initOnce sync.Once
// getGlobalParser 获取全局解析器实例
func getGlobalParser() *Parser {
initOnce.Do(func() {
globalParser = NewParser(nil)
})
return globalParser
}
// Parse 主解析函数 - 保持与原版本兼容的接口
func Parse(Info *HostInfo) error { func Parse(Info *HostInfo) error {
// 按照依赖顺序解析各类配置 // 首先应用LogLevel配置到日志系统
if err := ParseUser(); err != nil { applyLogLevel()
return fmt.Errorf("用户名解析错误: %v", err)
parser := getGlobalParser()
// 构建输入参数
input := &AllInputs{
Credential: &parsers.CredentialInput{
Username: Username,
Password: Password,
AddUsers: AddUsers,
AddPasswords: AddPasswords,
HashValue: HashValue,
SshKeyPath: SshKeyPath,
Domain: Domain,
UsersFile: UsersFile,
PasswordsFile: PasswordsFile,
HashFile: HashFile,
},
Target: &parsers.TargetInput{
Host: Info.Host,
HostsFile: HostsFile,
ExcludeHosts: ExcludeHosts,
Ports: Ports,
PortsFile: PortsFile,
AddPorts: AddPorts,
ExcludePorts: ExcludePorts,
TargetURL: TargetURL,
URLsFile: URLsFile,
HostPort: HostPort,
LocalMode: LocalMode,
},
Network: &parsers.NetworkInput{
HttpProxy: HttpProxy,
Socks5Proxy: Socks5Proxy,
Timeout: Timeout,
WebTimeout: WebTimeout,
DisablePing: DisablePing,
DnsLog: DnsLog,
UserAgent: UserAgent,
Cookie: Cookie,
},
} }
if err := ParsePass(Info); err != nil { // 执行解析
return fmt.Errorf("密码与目标解析错误: %v", err) result, err := parser.ParseAll(input)
}
if err := ParseInput(Info); err != nil {
return fmt.Errorf("输入参数解析错误: %v", err)
}
return nil
}
// ParseUser 解析用户名配置
// 处理直接指定的用户名和从文件加载的用户名,更新全局用户字典
func ParseUser() error {
// 如果未指定用户名和用户名文件,无需处理
if Username == "" && UsersFile == "" {
return nil
}
// 收集所有用户名
var usernames []string
// 处理命令行参数指定的用户名列表
if Username != "" {
usernames = strings.Split(Username, ",")
LogBase(GetText("no_username_specified", len(usernames)))
}
// 从文件加载用户名列表
if UsersFile != "" {
fileUsers, err := ReadFileLines(UsersFile)
if err != nil { if err != nil {
return fmt.Errorf("读取用户名文件失败: %v", err) return fmt.Errorf(i18n.GetText("parse_error_config_failed", err))
} }
// 添加非空用户名 // 更新全局变量以保持兼容性
for _, user := range fileUsers { if err := updateGlobalVariables(result.Config, Info); err != nil {
if user != "" { return fmt.Errorf(i18n.GetText("parse_error_update_vars_failed", err))
usernames = append(usernames, user)
}
}
LogBase(GetText("load_usernames_from_file", len(fileUsers)))
} }
// 去重处理 // 报告警告
usernames = RemoveDuplicate(usernames) for _, warning := range result.Warnings {
LogBase(GetText("total_usernames", len(usernames))) LogBase(warning)
// 更新所有字典的用户名列表
for name := range Userdict {
Userdict[name] = usernames
} }
// 显示解析结果摘要
showParseSummary(result.Config)
// 同步配置到core包
SyncToCore()
return nil return nil
} }
// ParsePass 解析密码、URL、主机和端口等目标配置 // AllInputs 所有输入参数的集合
// 处理多种输入源的配置,并更新全局目标信息 type AllInputs struct {
func ParsePass(Info *HostInfo) error { Credential *parsers.CredentialInput `json:"credential"`
// 处理密码配置 Target *parsers.TargetInput `json:"target"`
parsePasswords() Network *parsers.NetworkInput `json:"network"`
// 处理哈希值配置
parseHashes()
// 处理URL配置
parseURLs()
// 处理主机配置
if err := parseHosts(Info); err != nil {
return err
} }
// 处理端口配置 // ParseAll 解析所有配置
if err := parsePorts(); err != nil { func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
return err if input == nil {
return nil, fmt.Errorf(i18n.GetText("parse_error_empty_input"))
} }
return nil p.mu.Lock()
defer p.mu.Unlock()
if !p.initialized {
return nil, fmt.Errorf(i18n.GetText("parse_error_parser_not_init"))
} }
// parsePasswords 解析密码配置 startTime := time.Now()
// 处理直接指定的密码和从文件加载的密码 result := &parsers.ParseResult{
func parsePasswords() { Config: &parsers.ParsedConfig{},
var pwdList []string Success: true,
// 处理命令行参数指定的密码列表
if Password != "" {
passes := strings.Split(Password, ",")
for _, pass := range passes {
// 保留空密码,因为空口令是重要的安全测试场景
pwdList = append(pwdList, pass)
}
Passwords = pwdList
LogBase(GetText("load_passwords", len(pwdList)))
} }
// 从文件加载密码列表 var allErrors []error
if PasswordsFile != "" { var allWarnings []string
passes, err := ReadFileLines(PasswordsFile)
// 解析凭据配置
if input.Credential != nil {
credResult, err := p.credentialParser.Parse(input.Credential, p.options)
if err != nil { if err != nil {
LogError(fmt.Sprintf("读取密码文件失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_credential_failed", err)))
return
}
for _, pass := range passes {
// 保留空密码,用户可能在文件中故意添加空行来测试空口令
pwdList = append(pwdList, pass)
}
Passwords = pwdList
LogBase(GetText("load_passwords_from_file", len(passes)))
}
}
// parseHashes 解析哈希值配置
// 验证并处理哈希文件中的哈希值
func parseHashes() {
// 处理哈希文件
if HashFile == "" {
return
}
hashes, err := ReadFileLines(HashFile)
if err != nil {
LogError(fmt.Sprintf("读取哈希文件失败: %v", err))
return
}
validCount := 0
for _, line := range hashes {
if line == "" {
continue
}
// 验证哈希长度(MD5哈希为32位)
if len(line) == 32 {
HashValues = append(HashValues, line)
validCount++
} else { } else {
LogError(GetText("invalid_hash", line)) result.Config.Credentials = credResult.Config.Credentials
} allErrors = append(allErrors, credResult.Errors...)
} allWarnings = append(allWarnings, credResult.Warnings...)
LogBase(GetText("load_valid_hashes", validCount))
}
// parseURLs 解析URL目标配置
// 处理命令行和文件指定的URL列表去重后更新全局URL列表
func parseURLs() {
urlMap := make(map[string]struct{})
// 处理命令行参数指定的URL列表
if TargetURL != "" {
urls := strings.Split(TargetURL, ",")
for _, url := range urls {
if url != "" {
urlMap[url] = struct{}{}
}
} }
} }
// 从文件加载URL列表 // 解析目标配置
if URLsFile != "" { if input.Target != nil {
urls, err := ReadFileLines(URLsFile) targetResult, err := p.targetParser.Parse(input.Target, p.options)
if err != nil { if err != nil {
LogError(fmt.Sprintf("读取URL文件失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_target_failed", err)))
return
}
for _, url := range urls {
if url != "" {
urlMap[url] = struct{}{}
}
}
}
// 更新全局URL列表(已去重)
URLs = make([]string, 0, len(urlMap))
for u := range urlMap {
URLs = append(URLs, u)
}
if len(URLs) > 0 {
LogBase(GetText("load_urls", len(URLs)))
}
}
// parseHosts 解析主机配置
// 从文件加载主机列表并更新目标信息
func parseHosts(Info *HostInfo) error {
// 如果未指定主机文件,无需处理
if HostsFile == "" {
return nil
}
hosts, err := ReadFileLines(HostsFile)
if err != nil {
return fmt.Errorf("读取主机文件失败: %v", err)
}
// 去重处理
hostMap := make(map[string]struct{})
for _, host := range hosts {
if host != "" {
hostMap[host] = struct{}{}
}
}
// 构建主机列表并更新Info.Host
if len(hostMap) > 0 {
var hostList []string
for host := range hostMap {
hostList = append(hostList, host)
}
hostStr := strings.Join(hostList, ",")
if Info.Host == "" {
Info.Host = hostStr
} else { } else {
Info.Host += "," + hostStr result.Config.Targets = targetResult.Config.Targets
allErrors = append(allErrors, targetResult.Errors...)
allWarnings = append(allWarnings, targetResult.Warnings...)
}
} }
LogBase(GetText("load_hosts_from_file", len(hosts))) // 解析网络配置
} if input.Network != nil {
networkResult, err := p.networkParser.Parse(input.Network, p.options)
return nil
}
// parsePorts 解析端口配置
// 从文件加载端口列表并更新全局端口配置
func parsePorts() error {
// 如果未指定端口文件,无需处理
if PortsFile == "" {
return nil
}
ports, err := ReadFileLines(PortsFile)
if err != nil { if err != nil {
return fmt.Errorf("读取端口文件失败: %v", err) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_network_failed", err)))
}
// 构建端口列表字符串
var portBuilder strings.Builder
for _, port := range ports {
if port != "" {
portBuilder.WriteString(port)
portBuilder.WriteString(",")
}
}
// 更新全局端口配置
Ports = portBuilder.String()
LogBase(GetText("load_ports_from_file"))
return nil
}
// parseExcludePorts 解析排除端口配置
// 更新全局排除端口配置
func parseExcludePorts() {
if ExcludePorts != "" {
LogBase(GetText("exclude_ports", ExcludePorts))
// 确保排除端口被正确设置到全局配置中
// 这将由PortScan函数在处理端口时使用
}
}
// ReadFileLines 读取文件内容并返回非空行的切片
// 通用的文件读取函数,处理文件打开、读取和错误报告
func ReadFileLines(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
LogError(GetText("open_file_failed", filename, err))
return nil, err
}
defer file.Close()
var content []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// 逐行读取文件内容,忽略空行
lineCount := 0
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
content = append(content, text)
lineCount++
}
}
// 检查扫描过程中是否有错误
if err := scanner.Err(); err != nil {
LogError(GetText("read_file_failed", filename, err))
return nil, err
}
LogBase(GetText("read_file_success", filename, lineCount))
return content, nil
}
// ParseInput 解析和验证输入参数配置
// 处理多种配置的冲突检查、格式验证和参数处理
func ParseInput(Info *HostInfo) error {
// 检查扫描模式冲突
if err := validateScanMode(Info); err != nil {
return err
}
// 处理端口配置组合
processPortsConfig()
// 处理排除端口配置
parseExcludePorts()
// 处理额外用户名和密码
processExtraCredentials()
// 处理代理配置
if err := processProxySettings(); err != nil {
return err
}
// 处理哈希值
if err := processHashValues(); err != nil {
return err
}
return nil
}
// validateScanMode 验证扫描模式
// 检查互斥的扫描模式配置,避免参数冲突
func validateScanMode(Info *HostInfo) error {
// 检查互斥的扫描模式(主机扫描、URL扫描、本地模式)
modes := 0
if Info.Host != "" || HostsFile != "" {
modes++
}
if len(URLs) > 0 || TargetURL != "" || URLsFile != "" {
modes++
}
if LocalMode {
modes++
}
// 处理扫描模式验证结果
if modes == 0 {
// 无参数时显示帮助
flag.Usage()
return fmt.Errorf(GetText("specify_scan_params"))
} else if modes > 1 {
return fmt.Errorf(GetText("params_conflict"))
}
return nil
}
// processPortsConfig 处理端口配置
// 合并默认端口和附加端口配置
func processPortsConfig() {
// 如果使用主要端口添加Web端口
if Ports == MainPorts {
Ports += "," + WebPorts
}
// 处理附加端口
if AddPorts != "" {
if strings.HasSuffix(Ports, ",") {
Ports += AddPorts
} else { } else {
Ports += "," + AddPorts result.Config.Network = networkResult.Config.Network
} allErrors = append(allErrors, networkResult.Errors...)
LogBase(GetText("extra_ports", AddPorts)) allWarnings = append(allWarnings, networkResult.Warnings...)
}
// 确保排除端口配置被记录
if ExcludePorts != "" {
LogBase(GetText("exclude_ports_applied", ExcludePorts))
} }
} }
// processExtraCredentials 处理额外的用户名和密码 // 执行验证
// 添加命令行指定的额外用户名和密码到现有配置 validationInput := &parsers.ValidationInput{
func processExtraCredentials() { ScanMode: ScanMode,
// 处理额外用户名 LocalMode: LocalMode,
if AddUsers != "" { HasHosts: input.Target != nil && (input.Target.Host != "" || input.Target.HostsFile != ""),
users := strings.Split(AddUsers, ",") HasURLs: input.Target != nil && (input.Target.TargetURL != "" || input.Target.URLsFile != ""),
for dict := range Userdict { HasPorts: input.Target != nil && (input.Target.Ports != "" || input.Target.PortsFile != ""),
Userdict[dict] = append(Userdict[dict], users...) HasProxy: input.Network != nil && (input.Network.HttpProxy != "" || input.Network.Socks5Proxy != ""),
Userdict[dict] = RemoveDuplicate(Userdict[dict]) DisablePing: input.Network != nil && input.Network.DisablePing,
} HasCredentials: input.Credential != nil && (input.Credential.Username != "" || input.Credential.UsersFile != ""),
LogBase(GetText("extra_usernames", AddUsers))
} }
// 处理额外密码 validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options)
if AddPasswords != "" { if err != nil {
passes := strings.Split(AddPasswords, ",") allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_validation_failed", err)))
Passwords = append(Passwords, passes...)
Passwords = RemoveDuplicate(Passwords)
LogBase(GetText("extra_passwords", AddPasswords))
}
}
// processProxySettings 处理代理设置
// 解析并验证Socks5和HTTP代理配置
func processProxySettings() error {
// 处理Socks5代理
if Socks5Proxy != "" {
if err := setupSocks5Proxy(); err != nil {
return err
}
}
// 处理HTTP代理
if HttpProxy != "" {
if err := setupHttpProxy(); err != nil {
return err
}
}
return nil
}
// setupSocks5Proxy 设置Socks5代理
// 格式化和验证Socks5代理URL
func setupSocks5Proxy() error {
// 规范化Socks5代理URL格式
if !strings.HasPrefix(Socks5Proxy, "socks5://") {
if !strings.Contains(Socks5Proxy, ":") {
// 仅指定端口时使用本地地址
Socks5Proxy = "socks5://127.0.0.1:" + Socks5Proxy
} else { } else {
// 指定IP:PORT时添加协议前缀 result.Config.Validation = validationResult.Config.Validation
Socks5Proxy = "socks5://" + Socks5Proxy allErrors = append(allErrors, validationResult.Errors...)
allWarnings = append(allWarnings, validationResult.Warnings...)
}
// 汇总结果
result.Errors = allErrors
result.Warnings = allWarnings
result.ParseTime = time.Since(startTime)
result.Success = len(allErrors) == 0
return result, nil
}
// updateGlobalVariables 更新全局变量以保持向后兼容性
func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
if config == nil {
return nil
}
// 更新凭据相关全局变量
if config.Credentials != nil {
if len(config.Credentials.Usernames) > 0 {
// 更新全局用户字典
for serviceName := range Userdict {
Userdict[serviceName] = config.Credentials.Usernames
} }
} }
// 验证代理URL格式 if len(config.Credentials.Passwords) > 0 {
_, err := url.Parse(Socks5Proxy) Passwords = config.Credentials.Passwords
if err != nil {
return fmt.Errorf(GetText("socks5_proxy_error", err))
} }
// 使用Socks5代理时禁用Ping(无法通过代理进行ICMP) if len(config.Credentials.HashValues) > 0 {
DisablePing = true HashValues = config.Credentials.HashValues
LogBase(GetText("socks5_proxy", Socks5Proxy)) }
if len(config.Credentials.HashBytes) > 0 {
HashBytes = config.Credentials.HashBytes
}
}
// 更新目标相关全局变量
if config.Targets != nil {
if len(config.Targets.Hosts) > 0 {
if info.Host == "" {
info.Host = joinStrings(config.Targets.Hosts, ",")
} else {
info.Host += "," + joinStrings(config.Targets.Hosts, ",")
}
}
if len(config.Targets.URLs) > 0 {
URLs = config.Targets.URLs
}
if len(config.Targets.Ports) > 0 {
Ports = joinInts(config.Targets.Ports, ",")
}
if len(config.Targets.ExcludePorts) > 0 {
ExcludePorts = joinInts(config.Targets.ExcludePorts, ",")
}
if len(config.Targets.HostPorts) > 0 {
HostPort = config.Targets.HostPorts
}
}
// 更新网络相关全局变量
if config.Network != nil {
if config.Network.HttpProxy != "" {
HttpProxy = config.Network.HttpProxy
}
if config.Network.Socks5Proxy != "" {
Socks5Proxy = config.Network.Socks5Proxy
}
if config.Network.Timeout > 0 {
Timeout = int64(config.Network.Timeout.Seconds())
}
if config.Network.WebTimeout > 0 {
WebTimeout = int64(config.Network.WebTimeout.Seconds())
}
if config.Network.UserAgent != "" {
UserAgent = config.Network.UserAgent
}
if config.Network.Cookie != "" {
Cookie = config.Network.Cookie
}
DisablePing = config.Network.DisablePing
DnsLog = config.Network.EnableDNSLog
}
return nil return nil
} }
// setupHttpProxy 设置HTTP代理 // RemoveDuplicate 去重函数 - 恢复原始高效实现
// 处理多种HTTP代理简写形式并验证URL格式
func setupHttpProxy() error {
// 处理HTTP代理简写形式
switch HttpProxy {
case "1":
// 快捷方式1: 本地8080端口(常用代理工具默认端口)
HttpProxy = "http://127.0.0.1:8080"
case "2":
// 快捷方式2: 本地1080端口(常见SOCKS端口)
HttpProxy = "socks5://127.0.0.1:1080"
default:
// 仅指定端口时使用本地HTTP代理
if !strings.Contains(HttpProxy, "://") {
HttpProxy = "http://127.0.0.1:" + HttpProxy
}
}
// 验证代理协议
if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") {
return fmt.Errorf(GetText("unsupported_proxy"))
}
// 验证代理URL格式
_, err := url.Parse(HttpProxy)
if err != nil {
return fmt.Errorf(GetText("proxy_format_error", err))
}
LogBase(GetText("http_proxy", HttpProxy))
return nil
}
// processHashValues 处理哈希值
// 验证单个哈希值并处理哈希列表
func processHashValues() error {
// 处理单个哈希值
if HashValue != "" {
// MD5哈希必须是32位十六进制字符
if len(HashValue) != 32 {
return fmt.Errorf(GetText("hash_length_error"))
}
HashValues = append(HashValues, HashValue)
}
// 处理哈希值列表
HashValues = RemoveDuplicate(HashValues)
for _, hash := range HashValues {
// 将十六进制字符串转换为字节数组
hashByte, err := hex.DecodeString(hash)
if err != nil {
LogError(GetText("hash_decode_failed", hash))
continue
}
HashBytes = append(HashBytes, hashByte)
}
// 清空原始哈希值列表,仅保留字节形式
HashValues = []string{}
return nil
}
// RemoveDuplicate 对字符串切片进行去重
func RemoveDuplicate(old []string) []string { func RemoveDuplicate(old []string) []string {
temp := make(map[string]struct{}) if len(old) <= 1 {
var result []string return old
}
temp := make(map[string]struct{}, len(old))
result := make([]string, 0, len(old))
for _, item := range old { for _, item := range old {
if _, exists := temp[item]; !exists { if _, exists := temp[item]; !exists {
@ -546,3 +338,162 @@ func RemoveDuplicate(old []string) []string {
return result return result
} }
// 辅助函数
// joinStrings 连接字符串切片 - 优化版本使用字符串构建器池
func joinStrings(slice []string, sep string) string {
return utils.JoinStrings(slice, sep)
}
// joinInts 连接整数切片 - 优化版本使用字符串构建器池
func joinInts(slice []int, sep string) string {
return utils.JoinInts(slice, sep)
}
// showParseSummary 显示解析结果摘要
func showParseSummary(config *parsers.ParsedConfig) {
if config == nil {
return
}
// 显示目标信息
if config.Targets != nil {
if len(config.Targets.Hosts) > 0 {
if len(config.Targets.Hosts) <= 5 {
LogBase(i18n.GetText("target_hosts_found", joinStrings(config.Targets.Hosts, ", ")))
} else {
LogBase(i18n.GetText("target_hosts_count", joinStrings(config.Targets.Hosts[:5], ", "), len(config.Targets.Hosts)))
}
}
if len(config.Targets.URLs) > 0 {
if len(config.Targets.URLs) <= 3 {
LogBase(i18n.GetText("target_urls_found", joinStrings(config.Targets.URLs, ", ")))
} else {
LogBase(i18n.GetText("target_urls_count", joinStrings(config.Targets.URLs[:3], ", "), len(config.Targets.URLs)))
}
}
if len(config.Targets.Ports) > 0 {
if len(config.Targets.Ports) <= 20 {
LogBase(i18n.GetText("target_ports_found", joinInts(config.Targets.Ports, ", ")))
} else {
LogBase(i18n.GetText("target_ports_count", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports)))
}
}
if len(config.Targets.ExcludePorts) > 0 {
LogBase(i18n.GetText("target_exclude_ports", joinInts(config.Targets.ExcludePorts, ", ")))
}
if config.Targets.LocalMode {
LogBase(i18n.GetText("target_local_mode"))
}
}
// 显示扫描配置
LogBase(i18n.GetText("scan_config_thread_num", ThreadNum))
LogBase(i18n.GetText("scan_config_timeout", Timeout))
LogBase(i18n.GetText("scan_config_module_thread_num", ModuleThreadNum))
LogBase(i18n.GetText("scan_config_global_timeout", GlobalTimeout))
// 显示网络配置
if config.Network != nil {
if config.Network.HttpProxy != "" {
LogBase(i18n.GetText("network_http_proxy", config.Network.HttpProxy))
}
if config.Network.Socks5Proxy != "" {
LogBase(i18n.GetText("network_socks5_proxy", config.Network.Socks5Proxy))
}
if config.Network.WebTimeout > 0 {
LogBase(i18n.GetText("network_web_timeout", config.Network.WebTimeout))
}
}
// 显示凭据信息
if config.Credentials != nil {
if len(config.Credentials.Usernames) > 0 {
LogBase(i18n.GetText("credential_username_count", len(config.Credentials.Usernames)))
}
if len(config.Credentials.Passwords) > 0 {
LogBase(i18n.GetText("credential_password_count", len(config.Credentials.Passwords)))
}
if len(config.Credentials.HashValues) > 0 {
LogBase(i18n.GetText("credential_hash_count", len(config.Credentials.HashValues)))
}
}
}
// applyLogLevel 应用LogLevel配置到日志系统
func applyLogLevel() {
if LogLevel == "" {
return // 使用默认级别
}
// 根据LogLevel字符串获取对应的日志级别
var level logging.LogLevel
switch LogLevel {
case LogLevelAll:
level = logging.LevelAll
case LogLevelError:
level = logging.LevelError
case LogLevelBase:
level = logging.LevelBase
case LogLevelInfo:
level = logging.LevelInfo
case LogLevelSuccess:
level = logging.LevelSuccess
case LogLevelDebug:
level = logging.LevelDebug
case LogLevelInfoSuccess:
level = logging.LevelInfoSuccess
case LogLevelBaseInfoSuccess:
level = logging.LevelBaseInfoSuccess
default:
// 向后兼容:如果是老的字符串格式
switch LogLevel {
case "ALL":
level = logging.LevelAll
case "ERROR":
level = logging.LevelError
case "BASE":
level = logging.LevelBase
case "INFO":
level = logging.LevelInfo
case "SUCCESS":
level = logging.LevelSuccess
case "DEBUG":
level = logging.LevelDebug
case "debug":
level = logging.LevelAll // 兼容旧的debug行为
default:
return // 无效的级别,保持默认
}
}
// 更新全局日志管理器的级别
if globalLogger != nil {
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
ShowProgress: ShowProgress,
StartTime: StartTime,
LevelColors: logging.GetDefaultLevelColors(),
}
newLogger := logging.NewLogger(config)
if ProgressBar != nil {
newLogger.SetProgressBar(ProgressBar)
}
newLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
newLogger.SetCoordinatedOutput(LogWithProgress)
// 更新全局日志管理器
globalLogger = newLogger
// status变量已移除如需获取状态请直接调用newLogger.GetScanStatus()
}
}

View File

@ -1,549 +0,0 @@
package Common
import (
"bufio"
"errors"
"fmt"
"math/rand"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
// IP解析相关错误
var (
ErrParseIP = errors.New(GetText("parse_ip_error")) // IP解析失败的统一错误
)
// ParseIP 解析各种格式的IP地址
// 参数:
// - host: 主机地址可以是单个IP、IP范围、CIDR或常用网段简写
// - filename: 包含主机地址的文件名
// - nohosts: 需要排除的主机地址列表
//
// 返回:
// - []string: 解析后的IP地址列表
// - error: 解析过程中的错误
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
// 处理主机和端口组合的情况 (格式: IP:PORT)
if filename == "" && strings.Contains(host, ":") {
hostport := strings.Split(host, ":")
if len(hostport) == 2 {
host = hostport[0]
hosts = parseIPList(host)
Ports = hostport[1]
LogBase(GetText("host_port_parsed", Ports))
}
} else {
// 解析主机地址
hosts = parseIPList(host)
// 从文件加载额外主机
if filename != "" {
fileHosts, err := readIPFile(filename)
if err != nil {
LogError(GetText("read_host_file_failed", err))
} else {
hosts = append(hosts, fileHosts...)
LogBase(GetText("extra_hosts_loaded", len(fileHosts)))
}
}
}
// 处理需要排除的主机
hosts = excludeHosts(hosts, nohosts)
// 去重并排序
hosts = removeDuplicateIPs(hosts)
LogBase(GetText("final_valid_hosts", len(hosts)))
// 检查解析结果
if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") {
return nil, ErrParseIP
}
return hosts, nil
}
// parseIPList 解析逗号分隔的IP地址列表
// 参数:
// - ipList: 逗号分隔的IP地址列表字符串
//
// 返回:
// - []string: 解析后的IP地址列表
func parseIPList(ipList string) []string {
var result []string
// 处理逗号分隔的IP列表
if strings.Contains(ipList, ",") {
ips := strings.Split(ipList, ",")
for _, ip := range ips {
if parsed := parseSingleIP(ip); len(parsed) > 0 {
result = append(result, parsed...)
}
}
} else if ipList != "" {
// 解析单个IP地址或范围
result = parseSingleIP(ipList)
}
return result
}
// parseSingleIP 解析单个IP地址或IP范围
// 支持多种格式:
// - 普通IP: 192.168.1.1
// - 简写网段: 192, 172, 10
// - CIDR: 192.168.0.0/24
// - 范围: 192.168.1.1-192.168.1.100 或 192.168.1.1-100
// - 域名: example.com
// 参数:
// - ip: IP地址或范围字符串
//
// 返回:
// - []string: 解析后的IP地址列表
func parseSingleIP(ip string) []string {
// 检测是否包含字母(可能是域名)
isAlpha := regexp.MustCompile(`[a-zA-Z]+`).MatchString(ip)
// 根据不同格式解析IP
switch {
case ip == "192":
// 常用内网段简写
return parseSingleIP("192.168.0.0/16")
case ip == "172":
// 常用内网段简写
return parseSingleIP("172.16.0.0/12")
case ip == "10":
// 常用内网段简写
return parseSingleIP("10.0.0.0/8")
case strings.HasSuffix(ip, "/8"):
// 处理/8网段使用采样方式
return parseSubnet8(ip)
case strings.Contains(ip, "/"):
// 处理CIDR格式
return parseCIDR(ip)
case isAlpha:
// 处理域名,直接返回
return []string{ip}
case strings.Contains(ip, "-"):
// 处理IP范围
return parseIPRange(ip)
default:
// 尝试解析为单个IP地址
if testIP := net.ParseIP(ip); testIP != nil {
return []string{ip}
}
LogError(GetText("invalid_ip_format", ip))
return nil
}
}
// parseCIDR 解析CIDR格式的IP地址段
// 例如: 192.168.1.0/24
// 参数:
// - cidr: CIDR格式的IP地址段
//
// 返回:
// - []string: 展开后的IP地址列表
func parseCIDR(cidr string) []string {
// 解析CIDR格式
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
LogError(GetText("cidr_parse_failed", cidr, err))
return nil
}
// 转换为IP范围
ipRange := calculateIPRange(ipNet)
hosts := parseIPRange(ipRange)
LogBase(GetText("parse_cidr_to_range", cidr, ipRange))
return hosts
}
// calculateIPRange 计算CIDR的起始IP和结束IP
// 例如: 192.168.1.0/24 -> 192.168.1.0-192.168.1.255
// 参数:
// - cidr: 解析后的IPNet对象
//
// 返回:
// - string: 格式为"起始IP-结束IP"的范围字符串
func calculateIPRange(cidr *net.IPNet) string {
// 获取网络起始IP
start := cidr.IP.String()
mask := cidr.Mask
// 计算广播地址(最后一个IP)
bcst := make(net.IP, len(cidr.IP))
copy(bcst, cidr.IP)
// 将网络掩码按位取反然后与IP地址按位或得到广播地址
for i := 0; i < len(mask); i++ {
ipIdx := len(bcst) - i - 1
bcst[ipIdx] = cidr.IP[ipIdx] | ^mask[len(mask)-i-1]
}
end := bcst.String()
result := fmt.Sprintf("%s-%s", start, end)
LogBase(GetText("cidr_range", result))
return result
}
// parseIPRange 解析IP范围格式的地址
// 支持两种格式:
// - 完整格式: 192.168.1.1-192.168.1.100
// - 简写格式: 192.168.1.1-100
// 参数:
// - ipRange: IP范围字符串
//
// 返回:
// - []string: 展开后的IP地址列表
func parseIPRange(ipRange string) []string {
parts := strings.Split(ipRange, "-")
if len(parts) != 2 {
LogError(GetText("ip_range_format_error", ipRange))
return nil
}
startIP := parts[0]
endIP := parts[1]
// 验证起始IP
if net.ParseIP(startIP) == nil {
LogError(GetText("invalid_ip_format", startIP))
return nil
}
// 处理简写格式 (如: 192.168.1.1-100)
if len(endIP) < 4 || !strings.Contains(endIP, ".") {
return parseShortIPRange(startIP, endIP)
} else {
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
return parseFullIPRange(startIP, endIP)
}
}
// parseShortIPRange 解析简写格式的IP范围
// 例如: 192.168.1.1-100 表示从192.168.1.1到192.168.1.100
// 参数:
// - startIP: 起始IP
// - endSuffix: 结束IP的最后一部分
//
// 返回:
// - []string: 展开后的IP地址列表
func parseShortIPRange(startIP, endSuffix string) []string {
var allIP []string
// 将结束段转换为数字
endNum, err := strconv.Atoi(endSuffix)
if err != nil || endNum > 255 {
LogError(GetText("ip_range_format_error", startIP+"-"+endSuffix))
return nil
}
// 分解起始IP
ipParts := strings.Split(startIP, ".")
if len(ipParts) != 4 {
LogError(GetText("ip_format_error", startIP))
return nil
}
// 获取前缀和起始IP的最后一部分
prefixIP := strings.Join(ipParts[0:3], ".")
startNum, err := strconv.Atoi(ipParts[3])
if err != nil || startNum > endNum {
LogError(GetText("invalid_ip_range", startNum, endNum))
return nil
}
// 生成IP范围
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
}
LogBase(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
return allIP
}
// parseFullIPRange 解析完整格式的IP范围
// 例如: 192.168.1.1-192.168.2.100
// 参数:
// - startIP: 起始IP
// - endIP: 结束IP
//
// 返回:
// - []string: 展开后的IP地址列表
func parseFullIPRange(startIP, endIP string) []string {
var allIP []string
// 验证结束IP
if net.ParseIP(endIP) == nil {
LogError(GetText("invalid_ip_format", endIP))
return nil
}
// 分解起始IP和结束IP
startParts := strings.Split(startIP, ".")
endParts := strings.Split(endIP, ".")
if len(startParts) != 4 || len(endParts) != 4 {
LogError(GetText("ip_format_error", startIP+"-"+endIP))
return nil
}
// 转换为整数数组
var start, end [4]int
for i := 0; i < 4; i++ {
var err1, err2 error
start[i], err1 = strconv.Atoi(startParts[i])
end[i], err2 = strconv.Atoi(endParts[i])
if err1 != nil || err2 != nil || start[i] > 255 || end[i] > 255 {
LogError(GetText("ip_format_error", startIP+"-"+endIP))
return nil
}
}
// 计算IP地址的整数表示
startInt := (start[0] << 24) | (start[1] << 16) | (start[2] << 8) | start[3]
endInt := (end[0] << 24) | (end[1] << 16) | (end[2] << 8) | end[3]
// 检查范围的有效性
if startInt > endInt {
LogError(GetText("invalid_ip_range", startIP, endIP))
return nil
}
// 限制IP范围的大小防止生成过多IP导致内存问题
if endInt-startInt > 65535 {
LogError(GetText("ip_range_too_large", startIP, endIP))
// 可以考虑在这里实现采样或截断策略
}
// 生成IP范围
for ipInt := startInt; ipInt <= endInt; ipInt++ {
ip := fmt.Sprintf("%d.%d.%d.%d",
(ipInt>>24)&0xFF,
(ipInt>>16)&0xFF,
(ipInt>>8)&0xFF,
ipInt&0xFF)
allIP = append(allIP, ip)
}
LogBase(GetText("generate_ip_range_full", startIP, endIP, len(allIP)))
return allIP
}
// parseSubnet8 解析/8网段的IP地址生成采样IP列表
// 由于/8网段包含1600多万个IP因此采用采样方式
// 参数:
// - subnet: CIDR格式的/8网段
//
// 返回:
// - []string: 采样的IP地址列表
func parseSubnet8(subnet string) []string {
// 去除CIDR后缀获取基础IP
baseIP := subnet[:len(subnet)-2]
if net.ParseIP(baseIP) == nil {
LogError(GetText("invalid_ip_format", baseIP))
return nil
}
// 获取/8网段的第一段
firstOctet := strings.Split(baseIP, ".")[0]
var sampleIPs []string
LogBase(GetText("parse_subnet", firstOctet))
// 预分配足够的容量以提高性能
// 每个二级网段10个IP共256*256个二级网段
sampleIPs = make([]string, 0, 10)
// 对常用网段进行更全面的扫描
commonSecondOctets := []int{0, 1, 2, 10, 100, 200, 254}
// 对于每个选定的第二段,采样部分第三段
for _, secondOctet := range commonSecondOctets {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += 10 {
// 添加常见的网关和服务器IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet)) // 默认网关
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.254", firstOctet, secondOctet, thirdOctet)) // 通常用于路由器/交换机
// 随机采样不同范围的主机IP
fourthOctet := randomInt(2, 253)
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
}
}
// 对其他二级网段进行稀疏采样
samplingStep := 32 // 每32个二级网段采样1个
for secondOctet := 0; secondOctet < 256; secondOctet += samplingStep {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += samplingStep {
// 对于采样的网段取几个代表性IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet))
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, randomInt(2, 253)))
}
}
LogBase(GetText("sample_ip_generated", len(sampleIPs)))
return sampleIPs
}
// readIPFile 从文件中按行读取IP地址
// 支持两种格式:
// - 每行一个IP或IP范围
// - IP:PORT 格式指定端口
// 参数:
// - filename: 包含IP地址的文件路径
//
// 返回:
// - []string: 解析后的IP地址列表
// - error: 读取和解析过程中的错误
func readIPFile(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
LogError(GetText("open_file_failed", filename, err))
return nil, err
}
defer file.Close()
var ipList []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// 逐行处理
lineCount := 0
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue // 跳过空行和注释行
}
lineCount++
// 处理IP:PORT格式
if strings.Contains(line, ":") {
parts := strings.Split(line, ":")
if len(parts) == 2 {
// 提取端口部分,处理可能的注释
portPart := strings.Split(parts[1], " ")[0]
portPart = strings.Split(portPart, "#")[0]
port, err := strconv.Atoi(portPart)
// 验证端口有效性
if err != nil || port < 1 || port > 65535 {
LogError(GetText("invalid_port", line))
continue
}
// 解析IP部分并与端口组合
hosts := parseIPList(parts[0])
for _, host := range hosts {
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, portPart))
}
LogBase(GetText("parse_ip_port", line))
} else {
LogError(GetText("invalid_ip_port_format", line))
}
} else {
// 处理纯IP格式
hosts := parseIPList(line)
ipList = append(ipList, hosts...)
LogBase(GetText("parse_ip_address", line))
}
}
// 检查扫描过程中的错误
if err := scanner.Err(); err != nil {
LogError(GetText("read_file_error", err))
return ipList, err
}
LogBase(GetText("file_parse_complete", len(ipList)))
return ipList, nil
}
// excludeHosts 从主机列表中排除指定的主机
// 参数:
// - hosts: 原始主机列表
// - nohosts: 需要排除的主机列表(可选)
//
// 返回:
// - []string: 排除后的主机列表
func excludeHosts(hosts []string, nohosts []string) []string {
// 如果没有需要排除的主机,直接返回原列表
if len(nohosts) == 0 || nohosts[0] == "" {
return hosts
}
// 解析排除列表
excludeList := parseIPList(nohosts[0])
if len(excludeList) == 0 {
return hosts
}
// 使用map存储有效主机提高查找效率
hostMap := make(map[string]struct{}, len(hosts))
for _, host := range hosts {
hostMap[host] = struct{}{}
}
// 从map中删除需要排除的主机
for _, host := range excludeList {
delete(hostMap, host)
}
// 重建主机列表
result := make([]string, 0, len(hostMap))
for host := range hostMap {
result = append(result, host)
}
// 排序以保持结果的稳定性
sort.Strings(result)
LogBase(GetText("hosts_excluded", len(excludeList)))
return result
}
// removeDuplicateIPs 去除重复的IP地址
// 参数:
// - ips: 包含可能重复项的IP地址列表
//
// 返回:
// - []string: 去重后的IP地址列表
func removeDuplicateIPs(ips []string) []string {
// 使用map去重
ipMap := make(map[string]struct{}, len(ips))
for _, ip := range ips {
ipMap[ip] = struct{}{}
}
// 创建结果切片并添加唯一的IP
result := make([]string, 0, len(ipMap))
for ip := range ipMap {
result = append(result, ip)
}
// 排序以保持结果的稳定性
sort.Strings(result)
return result
}
// randomInt 生成指定范围内的随机整数
// 参数:
// - min: 最小值(包含)
// - max: 最大值(包含)
//
// 返回:
// - int: 生成的随机数
func randomInt(min, max int) int {
if min >= max || min < 0 || max <= 0 {
return max
}
return rand.Intn(max-min+1) + min
}

View File

@ -1,93 +0,0 @@
package Common
import (
"sort"
"strconv"
"strings"
)
// ParsePort 解析端口配置字符串为端口号列表
func ParsePort(ports string) []int {
// 预定义的端口组
portGroups := map[string]string{
"service": ServicePorts,
"db": DbPorts,
"web": WebPorts,
"all": AllPorts,
"main": MainPorts,
}
// 检查是否匹配预定义组
if definedPorts, exists := portGroups[ports]; exists {
ports = definedPorts
}
if ports == "" {
return nil
}
var scanPorts []int
slices := strings.Split(ports, ",")
// 处理每个端口配置
for _, port := range slices {
port = strings.TrimSpace(port)
if port == "" {
continue
}
// 处理端口范围
upper := port
if strings.Contains(port, "-") {
ranges := strings.Split(port, "-")
if len(ranges) < 2 {
LogError(GetText("port_range_format_error", port))
continue
}
// 确保起始端口小于结束端口
startPort, _ := strconv.Atoi(ranges[0])
endPort, _ := strconv.Atoi(ranges[1])
if startPort < endPort {
port = ranges[0]
upper = ranges[1]
} else {
port = ranges[1]
upper = ranges[0]
}
}
// 生成端口列表
start, _ := strconv.Atoi(port)
end, _ := strconv.Atoi(upper)
for i := start; i <= end; i++ {
if i > 65535 || i < 1 {
LogError(GetText("ignore_invalid_port", i))
continue
}
scanPorts = append(scanPorts, i)
}
}
// 去重并排序
scanPorts = removeDuplicate(scanPorts)
sort.Ints(scanPorts)
LogBase(GetText("valid_port_count", len(scanPorts)))
return scanPorts
}
// removeDuplicate 对整数切片进行去重
func removeDuplicate(old []int) []int {
temp := make(map[int]struct{})
var result []int
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -1,23 +1,15 @@
package Common package common
import ( import "github.com/shadow1ng/fscan/common/base"
"strconv"
"strings" /*
Ports.go - 端口常量向后兼容层
此文件保持向后兼容实际常量定义已迁移到Core/Constants.go
*/
// 向后兼容的端口常量 - 引用Core包中的定义
var (
WebPorts = base.WebPorts // Web服务端口组
MainPorts = base.MainPorts // 主要服务端口组
) )
var ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
var DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
var WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
var AllPorts = "1-65535"
var MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
func ParsePortsFromString(portsStr string) []int {
var ports []int
portStrings := strings.Split(portsStr, ",")
for _, portStr := range portStrings {
if port, err := strconv.Atoi(portStr); err == nil {
ports = append(ports, port)
}
}
return ports
}

329
Common/ProgressManager.go Normal file
View File

@ -0,0 +1,329 @@
package common
import (
"fmt"
"os"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ProgressManager.go - 固定底部进度条管理器
提供固定在终端底部的进度条显示与正常输出内容分离
使用终端控制码实现位置固定和内容保护
*/
// ProgressManager 进度条管理器
type ProgressManager struct {
mu sync.RWMutex
enabled bool
total int64
current int64
description string
startTime time.Time
isActive bool
terminalHeight int
reservedLines int // 为进度条保留的行数
lastContentLine int // 最后一行内容的位置
// 输出缓冲相关
outputMutex sync.Mutex
}
var (
globalProgressManager *ProgressManager
progressMutex sync.Mutex
)
// GetProgressManager 获取全局进度条管理器
func GetProgressManager() *ProgressManager {
progressMutex.Lock()
defer progressMutex.Unlock()
if globalProgressManager == nil {
globalProgressManager = &ProgressManager{
enabled: true,
reservedLines: 2, // 保留2行进度条 + 空行
terminalHeight: getTerminalHeight(),
}
}
return globalProgressManager
}
// InitProgress 初始化进度条
func (pm *ProgressManager) InitProgress(total int64, description string) {
if !ShowProgress || Silent {
pm.enabled = false
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.total = total
pm.current = 0
pm.description = description
pm.startTime = time.Now()
pm.isActive = true
pm.enabled = true
// 为进度条保留空间
pm.setupProgressSpace()
// 初始显示进度条
pm.renderProgress()
}
// UpdateProgress 更新进度
func (pm *ProgressManager) UpdateProgress(increment int64) {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current += increment
if pm.current > pm.total {
pm.current = pm.total
}
pm.renderProgress()
}
// =============================================================================================
// 已删除的死代码未使用SetProgress 设置当前进度
// =============================================================================================
// FinishProgress 完成进度条
func (pm *ProgressManager) FinishProgress() {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current = pm.total
pm.renderProgress()
// 显示完成信息
pm.showCompletionInfo()
// 清理进度条区域,恢复正常输出
pm.clearProgressArea()
pm.isActive = false
}
// setupProgressSpace 设置进度条空间
func (pm *ProgressManager) setupProgressSpace() {
// 简化设计:进度条在原地更新,不需要预留额外空间
// 只是标记进度条开始的位置
pm.lastContentLine = 0
}
// =============================================================================================
// 已删除的死代码未使用moveToContentArea 和 moveToProgressLine 方法
// =============================================================================================
// renderProgress 渲染进度条(使用锁避免输出冲突)
func (pm *ProgressManager) renderProgress() {
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
pm.renderProgressUnsafe()
}
// generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
return fmt.Sprintf("%s: 等待中...", pm.description)
}
percentage := float64(pm.current) / float64(pm.total) * 100
elapsed := time.Since(pm.startTime)
// 获取并发状态
concurrencyStatus := GetConcurrencyMonitor().GetConcurrencyStatus()
// 计算预估剩余时间
var eta string
if pm.current > 0 {
totalTime := elapsed * time.Duration(pm.total) / time.Duration(pm.current)
remaining := totalTime - elapsed
if remaining > 0 {
eta = fmt.Sprintf(" ETA:%s", formatDuration(remaining))
}
}
// 计算速度
speed := float64(pm.current) / elapsed.Seconds()
speedStr := ""
if speed > 0 {
speedStr = fmt.Sprintf(" (%.1f/s)", speed)
}
// 生成进度条
barWidth := 30
filled := int(percentage * float64(barWidth) / 100)
bar := ""
if NoColor {
// 无颜色版本
bar = "[" +
fmt.Sprintf("%s%s",
string(make([]rune, filled)),
string(make([]rune, barWidth-filled))) +
"]"
for i := 0; i < filled; i++ {
bar = bar[:i+1] + "=" + bar[i+2:]
}
for i := filled; i < barWidth; i++ {
bar = bar[:i+1] + "-" + bar[i+2:]
}
} else {
// 彩色版本
bar = "|"
for i := 0; i < barWidth; i++ {
if i < filled {
bar += "#"
} else {
bar += "."
}
}
bar += "|"
}
// 构建基础进度条
baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加并发状态
if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s]", baseProgress, concurrencyStatus)
}
return baseProgress
}
// showCompletionInfo 显示完成信息
func (pm *ProgressManager) showCompletionInfo() {
elapsed := time.Since(pm.startTime)
// 换行并显示完成信息
fmt.Print("\n")
completionMsg := i18n.GetText("progress_scan_completed")
if NoColor {
fmt.Printf("[完成] %s %d/%d (耗时: %s)\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
} else {
fmt.Printf("\033[32m[完成] %s %d/%d\033[0m \033[90m(耗时: %s)\033[0m\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
}
}
// clearProgressArea 清理进度条区域
func (pm *ProgressManager) clearProgressArea() {
// 简单清除当前行
fmt.Print("\033[2K\r")
}
// IsActive 检查进度条是否活跃
func (pm *ProgressManager) IsActive() bool {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.isActive && pm.enabled
}
// getTerminalHeight 获取终端高度
func getTerminalHeight() int {
// 对于固定底部进度条,我们暂时禁用终端高度检测
// 因为在不同终端环境中可能会有问题
// 改为使用相对定位方式
return 0 // 返回0表示使用简化模式
}
// formatDuration 格式化时间间隔
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
} else if d < time.Hour {
return fmt.Sprintf("%.1fm", d.Minutes())
} else {
return fmt.Sprintf("%.1fh", d.Hours())
}
}
// 全局函数,方便其他模块调用
func InitProgressBar(total int64, description string) {
GetProgressManager().InitProgress(total, description)
}
func UpdateProgressBar(increment int64) {
GetProgressManager().UpdateProgress(increment)
}
// =============================================================================================
// 已删除的死代码未使用SetProgressBar 全局函数
// =============================================================================================
func FinishProgressBar() {
GetProgressManager().FinishProgress()
}
func IsProgressActive() bool {
return GetProgressManager().IsActive()
}
// =============================================================================
// 日志输出协调功能
// =============================================================================
// LogWithProgress 在进度条活跃时协调日志输出
func LogWithProgress(message string) {
pm := GetProgressManager()
if !pm.IsActive() {
// 如果进度条不活跃,直接输出
fmt.Println(message)
return
}
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
// 清除当前行(清除进度条)
fmt.Print("\033[2K\r")
// 输出日志消息
fmt.Println(message)
// 重绘进度条
pm.renderProgressUnsafe()
}
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
func (pm *ProgressManager) renderProgressUnsafe() {
if !pm.enabled || !pm.isActive {
return
}
// 移动到行首并清除当前行
fmt.Print("\033[2K\r")
// 生成进度条内容
progressBar := pm.generateProgressBar()
// 输出进度条(带颜色,如果启用)
if NoColor {
fmt.Print(progressBar)
} else {
fmt.Printf("\033[36m%s\033[0m", progressBar) // 青色
}
// 刷新输出
os.Stdout.Sync()
}

View File

@ -1,191 +0,0 @@
package Common
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"strings"
"time"
"golang.org/x/net/proxy"
)
// WrapperTcpWithTimeout 创建一个带超时的TCP连接
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
d := &net.Dialer{Timeout: timeout}
return WrapperTCP(network, address, d)
}
// WrapperTcpWithContext 创建一个带上下文的TCP连接
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
return WrapperTCPWithContext(ctx, network, address, d)
}
// WrapperTCP 根据配置创建TCP连接
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.Dial(network, address)
if err != nil {
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
}
return conn, nil
}
// Socks5代理模式
dialer, err := Socks5Dialer(forward)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
conn, err := dialer.Dial(network, address)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_conn_failed"), err)
}
return conn, nil
}
// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接
func WrapperTCPWithContext(ctx context.Context, network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.DialContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
}
return conn, nil
}
// Socks5代理模式
dialer, err := Socks5Dialer(forward)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
// 创建一个结果通道来处理连接和取消
connChan := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := dialer.Dial(network, address)
select {
case <-ctx.Done():
if conn != nil {
conn.Close()
}
case connChan <- struct {
conn net.Conn
err error
}{conn, err}:
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-connChan:
if result.err != nil {
return nil, fmt.Errorf(GetText("socks5_conn_failed"), result.err)
}
return result.conn, nil
}
}
// Socks5Dialer 创建Socks5代理拨号器
func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
// 解析代理URL
u, err := url.Parse(Socks5Proxy)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_parse_failed"), err)
}
// 验证代理类型
if strings.ToLower(u.Scheme) != "socks5" {
return nil, errors.New(GetText("socks5_only"))
}
address := u.Host
var dialer proxy.Dialer
// 根据认证信息创建代理
if u.User.String() != "" {
// 使用用户名密码认证
auth := proxy.Auth{
User: u.User.Username(),
}
auth.Password, _ = u.User.Password()
dialer, err = proxy.SOCKS5("tcp", address, &auth, forward)
} else {
// 无认证模式
dialer, err = proxy.SOCKS5("tcp", address, nil, forward)
}
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
return dialer, nil
}
// WrapperTlsWithContext 创建一个通过代理的TLS连接
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
dialer := &net.Dialer{}
tcpConn, err := dialer.DialContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf("直连TCP连接失败: %v", err)
}
// 在TCP连接上进行TLS握手
tlsConn := tls.Client(tcpConn, tlsConfig)
// 使用ctx的deadline设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
}
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("TLS握手失败: %v", err)
}
// 清除deadline让上层代码自己管理超时
tlsConn.SetDeadline(time.Time{})
return tlsConn, nil
}
// Socks5代理模式
// 首先通过代理建立到目标的TCP连接
tcpConn, err := WrapperTcpWithContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf("通过代理建立TCP连接失败: %v", err)
}
// 在TCP连接上进行TLS握手
tlsConn := tls.Client(tcpConn, tlsConfig)
// 使用ctx的deadline设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
}
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("TLS握手失败: %v", err)
}
// 清除deadline让上层代码自己管理超时
tlsConn.SetDeadline(time.Time{})
return tlsConn, nil
}

View File

@ -1,59 +0,0 @@
// Config/types.go
package Common
type HostInfo struct {
Host string
Ports string
Url string
Infostr []string
}
// 在 Common/const.go 中添加
// 插件类型常量
const (
PluginTypeService = "service" // 服务类型插件
PluginTypeWeb = "web" // Web类型插件
PluginTypeLocal = "local" // 本地类型插件
)
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
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 {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// PluginManager 管理插件注册
var PluginManager = make(map[string]ScanPlugin)
// RegisterPlugin 注册插件
func RegisterPlugin(name string, plugin ScanPlugin) {
PluginManager[name] = plugin
}

37
Common/base/Constants.go Normal file
View File

@ -0,0 +1,37 @@
package base
/*
Constants.go - 核心常量定义
整合Ports.go等常量文件统一管理所有核心常量
*/
// =============================================================================
// 端口常量 (从Ports.go迁移)
// =============================================================================
// 预定义端口组常量
var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
)
// =============================================================================
// 扫描模式常量
// =============================================================================
const (
ScanModeAll = "all" // 全扫描模式
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultThreadNum = 600 // 默认线程数
DefaultTimeout = 3 // 默认超时时间(秒)
DefaultScanMode = ScanModeAll // 默认扫描模式
DefaultLanguage = "zh" // 默认语言
DefaultLogLevel = "base" // 默认日志级别
)

84
Common/base/Manager.go Normal file
View File

@ -0,0 +1,84 @@
package base
import (
"sync"
"github.com/shadow1ng/fscan/common/config"
)
// =============================================================================
// 全局配置变量
// =============================================================================
var (
// 核心扫描配置
ScanMode string // 扫描模式
ThreadNum int // 线程数
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
// 基础认证配置
Username string // 用户名
Password string // 密码
Userdict map[string][]string // 用户字典
Passwords []string // 密码列表
// 网络配置
HttpProxy string // HTTP代理
Socks5Proxy string // SOCKS5代理
// 显示控制
NoColor bool // 禁用颜色
Language string // 语言
LogLevel string // 日志级别
// 端口映射
PortMap map[int][]string // 端口映射
DefaultMap []string // 默认映射
// 初始化锁
initOnce sync.Once
)
// =============================================================================
// 简化的初始化函数
// =============================================================================
// InitGlobalConfig 初始化全局配置
func InitGlobalConfig() {
initOnce.Do(func() {
// 设置默认值
ScanMode = DefaultScanMode
ThreadNum = DefaultThreadNum
Timeout = DefaultTimeout
LogLevel = DefaultLogLevel
Language = DefaultLanguage
// 初始化映射和切片
Userdict = make(map[string][]string)
PortMap = make(map[int][]string)
DefaultMap = make([]string, 0)
// 从config模块获取字典和映射
if serviceDict := config.GetGlobalServiceDict(); serviceDict != nil {
Userdict = serviceDict.GetAllUserDicts()
Passwords = serviceDict.GetPasswords()
}
if probeMapping := config.GetGlobalProbeMapping(); probeMapping != nil {
PortMap = probeMapping.GetAllPortMappings()
DefaultMap = probeMapping.GetDefaultProbes()
}
})
}
// =============================================================================
// 访问器函数已移除(未使用的死代码)
// 直接使用全局变量进行访问
// =============================================================================
// init 自动初始化
func init() {
InitGlobalConfig()
}

262
Common/base/Plugin.go Normal file
View File

@ -0,0 +1,262 @@
package base
import (
"fmt"
"sync"
)
/*
Plugin.go - 插件系统管理
整合Types.go中的插件系统提供统一的插件注册和管理机制
*/
// =============================================================================
// 核心数据结构 (从Types.go迁移)
// =============================================================================
// HostInfo 主机信息结构
type HostInfo struct {
Host string // 主机地址
Ports string // 端口范围
Url string // URL地址
Infostr []string // 附加信息
}
// =============================================================================
// 插件类型常量
// =============================================================================
const (
PluginTypeService = "service" // 服务类型插件
PluginTypeWeb = "web" // Web类型插件
PluginTypeLocal = "local" // 本地类型插件
PluginTypeBrute = "brute" // 暴力破解插件
PluginTypePoc = "poc" // POC验证插件
PluginTypeScan = "scan" // 扫描探测插件
)
// =============================================================================
// 插件定义和管理
// =============================================================================
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Version string // 插件版本
Description string // 插件描述
Author string // 插件作者
Ports []int // 适用端口
Types []string // 插件类型标签,一个插件可以有多个类型
Priority int // 插件优先级(数字越小优先级越高)
Enabled bool // 是否启用
ScanFunc func(*HostInfo) error // 扫描函数
}
// PluginManager 插件管理器
type PluginManager struct {
mu sync.RWMutex
plugins map[string]*ScanPlugin
types map[string][]*ScanPlugin // 按类型索引的插件
ports map[int][]*ScanPlugin // 按端口索引的插件
}
// 全局插件管理器实例
var globalPluginManager = NewPluginManager()
// NewPluginManager 创建新的插件管理器
func NewPluginManager() *PluginManager {
return &PluginManager{
plugins: make(map[string]*ScanPlugin),
types: make(map[string][]*ScanPlugin),
ports: make(map[int][]*ScanPlugin),
}
}
// =============================================================================
// 插件基础方法
// =============================================================================
// HasType 检查插件是否具有指定类型
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 {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// IsEnabled 检查插件是否启用
func (p *ScanPlugin) IsEnabled() bool {
return p.Enabled
}
// GetInfo 获取插件基本信息
func (p *ScanPlugin) GetInfo() map[string]interface{} {
return map[string]interface{}{
"name": p.Name,
"version": p.Version,
"description": p.Description,
"author": p.Author,
"types": p.Types,
"ports": p.Ports,
"priority": p.Priority,
"enabled": p.Enabled,
}
}
// =============================================================================
// 插件管理器方法
// =============================================================================
// RegisterPlugin 注册插件
func (pm *PluginManager) RegisterPlugin(plugin *ScanPlugin) error {
if plugin == nil {
return fmt.Errorf("plugin cannot be nil")
}
if plugin.Name == "" {
return fmt.Errorf("plugin name cannot be empty")
}
if plugin.ScanFunc == nil {
return fmt.Errorf("plugin scan function cannot be nil")
}
pm.mu.Lock()
defer pm.mu.Unlock()
// 检查插件是否已存在
if _, exists := pm.plugins[plugin.Name]; exists {
return fmt.Errorf("plugin %s already registered", plugin.Name)
}
// 注册插件
pm.plugins[plugin.Name] = plugin
// 按类型索引
for _, pluginType := range plugin.Types {
pm.types[pluginType] = append(pm.types[pluginType], plugin)
}
// 按端口索引
for _, port := range plugin.Ports {
pm.ports[port] = append(pm.ports[port], plugin)
}
return nil
}
// ========================================================================================
// 未使用的插件管理器方法已删除(死代码清理)
// 包括GetPlugin, GetPluginsByType, GetPluginsByPort, GetAllPlugins,
// EnablePlugin, DisablePlugin, UnregisterPlugin, GetPluginCount, GetEnabledPluginCount
// ========================================================================================
// =============================================================================
// 全局插件管理函数 (保持向后兼容)
// =============================================================================
// RegisterPlugin 注册插件到全局管理器
func RegisterPlugin(name string, plugin ScanPlugin) error {
// 转换为新的插件结构
newPlugin := &ScanPlugin{
Name: name,
Ports: plugin.Ports,
Types: plugin.Types,
Enabled: true,
ScanFunc: plugin.ScanFunc,
}
// 注册到新的插件管理器
err := globalPluginManager.RegisterPlugin(newPlugin)
if err != nil {
return err
}
// 同时更新Legacy管理器以保持向后兼容
LegacyPluginManager[name] = plugin
return nil
}
// Clear 清理所有插件(防止内存泄漏)
func (pm *PluginManager) Clear() {
pm.mu.Lock()
defer pm.mu.Unlock()
// 清理插件实例
for name, plugin := range pm.plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
_ = plugin // 避免未使用变量警告
delete(pm.plugins, name)
}
// 清理索引
for typeKey := range pm.types {
delete(pm.types, typeKey)
}
for portKey := range pm.ports {
delete(pm.ports, portKey)
}
// 清理Legacy管理器
for k := range LegacyPluginManager {
delete(LegacyPluginManager, k)
}
}
// ClearPluginsByType 清理指定类型的插件
func (pm *PluginManager) ClearPluginsByType(pluginType string) {
pm.mu.Lock()
defer pm.mu.Unlock()
plugins := pm.types[pluginType]
for _, plugin := range plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
delete(pm.plugins, plugin.Name)
}
delete(pm.types, pluginType)
// 同步清理端口索引
for port, portPlugins := range pm.ports {
filtered := make([]*ScanPlugin, 0)
for _, plugin := range portPlugins {
found := false
for _, typePlugin := range plugins {
if plugin == typePlugin {
found = true
break
}
}
if !found {
filtered = append(filtered, plugin)
}
}
if len(filtered) == 0 {
delete(pm.ports, port)
} else {
pm.ports[port] = filtered
}
}
}
// GetGlobalPluginManager 方法已删除(死代码清理)
// 向后兼容的全局变量 (已废弃建议使用PluginManager)
var LegacyPluginManager = make(map[string]ScanPlugin)

199
Common/common.go Normal file
View File

@ -0,0 +1,199 @@
package common
/*
common.go - 简化的统一入口
移除所有向后兼容层提供清晰的模块化接口
直接导出各子模块的核心功能避免代码债务
*/
import (
"context"
"crypto/tls"
"fmt"
"net"
"sync"
"time"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
)
// =============================================================================
// 核心类型导出 - 直接从core模块导出
// =============================================================================
type HostInfo = base.HostInfo
type ScanPlugin = base.ScanPlugin
// 插件类型常量
const (
PluginTypeService = base.PluginTypeService
PluginTypeWeb = base.PluginTypeWeb
PluginTypeLocal = base.PluginTypeLocal
PluginTypeBrute = base.PluginTypeBrute
PluginTypePoc = base.PluginTypePoc
PluginTypeScan = base.PluginTypeScan
)
// 全局插件管理器
var PluginManager = base.LegacyPluginManager
// =============================================================================
// 核心功能导出 - 直接调用对应模块
// =============================================================================
// 插件系统
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := base.RegisterPlugin(name, plugin); err != nil {
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
// GetGlobalPluginManager 函数已删除(死代码清理)
// =============================================================================
// 日志系统简化接口
// =============================================================================
var globalLogger *logging.Logger
var loggerMutex sync.Mutex
func getGlobalLogger() *logging.Logger {
loggerMutex.Lock()
defer loggerMutex.Unlock()
if globalLogger == nil {
level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
ShowProgress: ShowProgress,
StartTime: StartTime,
}
globalLogger = logging.NewLogger(config)
if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar)
}
globalLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
globalLogger.SetCoordinatedOutput(LogWithProgress)
}
return globalLogger
}
func getLogLevelFromString(levelStr string) logging.LogLevel {
switch levelStr {
case "all", "ALL":
return logging.LevelAll
case "error", "ERROR":
return logging.LevelError
case "base", "BASE":
return logging.LevelBase
case "info", "INFO":
return logging.LevelInfo
case "success", "SUCCESS":
return logging.LevelSuccess
case "debug", "DEBUG":
return logging.LevelDebug
case "info,success":
return logging.LevelInfoSuccess
case "base,info,success", "BASE_INFO_SUCCESS":
return logging.LevelBaseInfoSuccess
default:
return logging.LevelInfoSuccess
}
}
// 日志函数
func InitLogger() {
loggerMutex.Lock()
globalLogger = nil
loggerMutex.Unlock()
getGlobalLogger().Initialize()
}
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
// =============================================================================
// 输出系统简化接口
// =============================================================================
var ResultOutput *output.Manager
func InitOutput() error {
if Outputfile == "" {
return fmt.Errorf("output file not specified")
}
var format output.OutputFormat
switch OutputFormat {
case "txt":
format = output.FormatTXT
case "json":
format = output.FormatJSON
case "csv":
format = output.FormatCSV
default:
return fmt.Errorf("invalid output format: %s", OutputFormat)
}
config := output.DefaultManagerConfig(Outputfile, format)
manager, err := output.NewManager(config)
if err != nil {
return err
}
ResultOutput = manager
return nil
}
func CloseOutput() error {
if ResultOutput == nil {
return nil
}
return ResultOutput.Close()
}
func SaveResult(result *output.ScanResult) error {
if ResultOutput == nil {
return fmt.Errorf("output not initialized")
}
return ResultOutput.SaveResult(result)
}
// =============================================================================
// 网络连接辅助函数
// =============================================================================
// WrapperTcpWithTimeout TCP连接包装器带超时
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout(network, address, timeout)
}
// WrapperTcpWithContext TCP连接包装器带上下文
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, address)
}
// WrapperTlsWithContext TLS连接包装器带上下文
func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) {
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
// =============================================================================
// 错误处理辅助函数
// =============================================================================
// CheckErrs 检查单个错误 - 简化版本
func CheckErrs(err error) error {
return err
}

View File

@ -0,0 +1,81 @@
package config
import (
"sync"
)
// ProbeMapping 探测器映射管理器
type ProbeMapping struct {
mu sync.RWMutex
defaultMap []string
portMap map[int][]string
initialized bool
}
// NewProbeMapping 创建探测器映射管理器
func NewProbeMapping() *ProbeMapping {
return &ProbeMapping{
defaultMap: getDefaultProbeMap(),
portMap: getDefaultPortMap(),
initialized: true,
}
}
// getDefaultProbeMap 获取默认的探测器顺序
func getDefaultProbeMap() []string {
// 返回常量的副本
result := make([]string, len(DefaultProbeMap))
copy(result, DefaultProbeMap)
return result
}
// getDefaultPortMap 获取默认的端口映射
func getDefaultPortMap() map[int][]string {
// 返回常量的深拷贝
result := make(map[int][]string)
for port, probes := range DefaultPortMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// GetDefaultProbes 获取默认探测器列表
func (pm *ProbeMapping) GetDefaultProbes() []string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make([]string, len(pm.defaultMap))
copy(result, pm.defaultMap)
return result
}
// GetAllPortMappings 获取所有端口映射
func (pm *ProbeMapping) GetAllPortMappings() map[int][]string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make(map[int][]string)
for port, probes := range pm.portMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// 全局探测器映射实例
var (
globalProbeMapping *ProbeMapping
probeMappingOnce sync.Once
)
// GetGlobalProbeMapping 获取全局探测器映射实例
func GetGlobalProbeMapping() *ProbeMapping {
probeMappingOnce.Do(func() {
globalProbeMapping = NewProbeMapping()
})
return globalProbeMapping
}

View File

@ -0,0 +1,82 @@
package config
import (
"sync"
)
// ServiceDictionary 服务字典管理器
type ServiceDictionary struct {
mu sync.RWMutex
userDict map[string][]string
passwords []string
initialized bool
}
// NewServiceDictionary 创建服务字典管理器
func NewServiceDictionary() *ServiceDictionary {
return &ServiceDictionary{
userDict: getDefaultUserDict(),
passwords: getDefaultPasswords(),
initialized: true,
}
}
// getDefaultUserDict 获取默认用户字典
func getDefaultUserDict() map[string][]string {
// 返回常量的深拷贝
result := make(map[string][]string)
for service, users := range DefaultUserDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// getDefaultPasswords 获取默认密码字典
func getDefaultPasswords() []string {
// 返回常量的副本
result := make([]string, len(DefaultPasswords))
copy(result, DefaultPasswords)
return result
}
// GetAllUserDicts 获取所有服务的用户字典
func (sd *ServiceDictionary) GetAllUserDicts() map[string][]string {
sd.mu.RLock()
defer sd.mu.RUnlock()
result := make(map[string][]string)
for service, users := range sd.userDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// GetPasswords 获取默认密码字典
func (sd *ServiceDictionary) GetPasswords() []string {
sd.mu.RLock()
defer sd.mu.RUnlock()
// 返回副本,避免外部修改
result := make([]string, len(sd.passwords))
copy(result, sd.passwords)
return result
}
// 全局服务字典实例
var (
globalServiceDict *ServiceDictionary
serviceDictOnce sync.Once
)
// GetGlobalServiceDict 获取全局服务字典实例
func GetGlobalServiceDict() *ServiceDictionary {
serviceDictOnce.Do(func() {
globalServiceDict = NewServiceDictionary()
})
return globalServiceDict
}

151
Common/config/Types.go Normal file
View File

@ -0,0 +1,151 @@
package config
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
)
// Version 版本信息
type Version struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
Full string `json:"full"`
}
// ApplicationConfig 应用程序配置
type ApplicationConfig struct {
Version Version `json:"version"`
ProgressBar *progressbar.ProgressBar `json:"-"`
OutputMutex sync.Mutex `json:"-"`
}
// ScanTargetConfig 扫描目标配置
type ScanTargetConfig struct {
Ports string `json:"ports"` // 要扫描的端口列表
ExcludePorts string `json:"exclude_ports"` // 要排除的端口列表
ExcludeHosts string `json:"exclude_hosts"` // 要排除的主机列表
AddPorts string `json:"add_ports"` // 额外添加的端口列表
HostPort []string `json:"host_port"` // 主机:端口格式的目标列表
}
// CredentialConfig 认证凭据配置
type CredentialConfig struct {
Username string `json:"username"` // 用于认证的用户名
Password string `json:"password"` // 用于认证的密码
AddUsers string `json:"add_users"` // 额外添加的用户名列表
AddPasswords string `json:"add_passwords"` // 额外添加的密码列表
Domain string `json:"domain"` // Active Directory/SMB域名
HashValue string `json:"hash_value"` // 用于哈希认证的单个哈希值
HashValues []string `json:"hash_values"` // 哈希值列表
HashBytes [][]byte `json:"-"` // 二进制格式的哈希值列表
HashFile string `json:"hash_file"` // 包含哈希值的文件路径
SshKeyPath string `json:"ssh_key_path"` // SSH私钥文件路径
}
// ScanControlConfig 扫描控制配置
type ScanControlConfig struct {
ScanMode string `json:"scan_mode"` // 扫描模式或指定的插件列表
ThreadNum int `json:"thread_num"` // 并发扫描线程数
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
// LiveTop 已移除,改为智能控制
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
}
// InputFileConfig 输入文件配置
type InputFileConfig struct {
HostsFile string `json:"hosts_file"` // 包含目标主机的文件路径
UsersFile string `json:"users_file"` // 包含用户名列表的文件路径
PasswordsFile string `json:"passwords_file"` // 包含密码列表的文件路径
PortsFile string `json:"ports_file"` // 包含端口列表的文件路径
}
// WebScanConfig Web扫描配置
type WebScanConfig struct {
TargetURL string `json:"target_url"` // 单个目标URL
URLsFile string `json:"urls_file"` // 包含URL列表的文件路径
URLs []string `json:"urls"` // 解析后的URL目标列表
WebTimeout int64 `json:"web_timeout"` // Web请求超时时间(秒)
HttpProxy string `json:"http_proxy"` // HTTP代理地址
Socks5Proxy string `json:"socks5_proxy"` // SOCKS5代理地址
}
// VulnExploitConfig POC与漏洞利用配置
type VulnExploitConfig struct {
// POC配置
PocPath string `json:"poc_path"` // POC脚本路径
PocInfo PocInfo `json:"poc_info"` // POC详细信息结构
DisablePocScan bool `json:"disable_poc_scan"` // 是否禁用POC扫描
// Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
DisableRedis bool `json:"disable_redis"` // 是否禁用Redis利用测试
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容
RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件
// 其他漏洞利用
Shellcode string `json:"shellcode"` // 用于MS17010等漏洞利用的Shellcode
}
// BruteForceConfig 暴力破解控制配置
type BruteForceConfig struct {
DisableBrute bool `json:"disable_brute"` // 是否禁用暴力破解模块
MaxRetries int `json:"max_retries"` // 连接失败最大重试次数
}
// DisplayConfig 输出与显示配置
type DisplayConfig struct {
DisableSave bool `json:"disable_save"` // 是否禁止保存扫描结果
Silent bool `json:"silent"` // 是否启用静默模式
NoColor bool `json:"no_color"` // 是否禁用彩色输出
LogLevel string `json:"log_level"` // 日志输出级别
DisableProgress bool `json:"disable_progress"` // 是否禁用进度条
Language string `json:"language"` // 界面语言设置
}
// OutputConfig 输出配置
type OutputConfig struct {
Outputfile string `json:"output_file"` // 输出文件路径
OutputFormat string `json:"output_format"` // 输出格式
}
// NetworkConfig 网络配置
type NetworkConfig struct {
UserAgent string `json:"user_agent"` // 用户代理字符串
Accept string `json:"accept"` // Accept头部
DnsLog bool `json:"dns_log"` // 是否启用DNS日志
PocNum int `json:"poc_num"` // POC并发数
PocFull bool `json:"poc_full"` // 是否启用完整POC扫描
Cookie string `json:"cookie"` // Cookie字符串
}
// PocInfo POC详细信息结构
type PocInfo struct {
Target string `json:"target"`
PocName string `json:"poc_name"`
}
// Config 完整的配置结构
type Config struct {
Application *ApplicationConfig `json:"application"`
ScanTarget *ScanTargetConfig `json:"scan_target"`
Credential *CredentialConfig `json:"credential"`
ScanControl *ScanControlConfig `json:"scan_control"`
InputFile *InputFileConfig `json:"input_file"`
WebScan *WebScanConfig `json:"web_scan"`
VulnExploit *VulnExploitConfig `json:"vuln_exploit"`
BruteForce *BruteForceConfig `json:"brute_force"`
Display *DisplayConfig `json:"display"`
Output *OutputConfig `json:"output"`
Network *NetworkConfig `json:"network"`
LastUpdated time.Time `json:"last_updated"`
}

192
Common/config/constants.go Normal file
View File

@ -0,0 +1,192 @@
package config
// 默认探测器列表
var DefaultProbeMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
// 默认端口映射关系
var DefaultPortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
264: {"GenericLines"},
311: {"LDAPSearchReq"},
340: {"GenericLines"},
389: {"LDAPSearchReq", "LDAPBindReq"},
443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
444: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
445: {"SMBProgNeg"},
465: {"Hello", "Help", "GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie"},
502: {"GenericLines"},
503: {"GenericLines"},
513: {"GenericLines"},
514: {"GenericLines"},
515: {"LPDString"},
544: {"GenericLines"},
548: {"afp"},
554: {"GetRequest"},
563: {"GenericLines"},
587: {"Hello", "Help"},
631: {"GetRequest", "HTTPOptions"},
636: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
646: {"LDAPSearchReq", "RPCCheck"},
691: {"GenericLines"},
873: {"GenericLines"},
898: {"GetRequest"},
993: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
995: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
1080: {"GenericLines", "Socks5", "Socks4"},
1099: {"JavaRMI"},
1234: {"SqueezeCenter_CLI"},
1311: {"GenericLines"},
1352: {"oracle-tns"},
1414: {"ibm-mqseries"},
1433: {"ms-sql-s"},
1521: {"oracle-tns"},
1723: {"GenericLines"},
1883: {"mqtt"},
1911: {"oracle-tns"},
2000: {"GenericLines", "oracle-tns"},
2049: {"RPCCheck"},
2121: {"GenericLines", "Help"},
2181: {"GenericLines"},
2222: {"GetRequest", "GenericLines", "HTTPOptions", "Help", "SSH", "TerminalServerCookie"},
2375: {"docker", "GetRequest", "HTTPOptions"},
2376: {"docker", "GetRequest", "HTTPOptions", "SSLSessionReq"},
2484: {"oracle-tns"},
2628: {"dominoconsole"},
3000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
3306: {"GenericLines", "GetRequest", "HTTPOptions"},
3389: {"TerminalServerCookie", "TerminalServer"},
3690: {"GenericLines"},
4000: {"GenericLines"},
4369: {"epmd"},
4444: {"GenericLines"},
4840: {"GenericLines"},
5000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
5050: {"GenericLines"},
5060: {"SIPOptions"},
5222: {"GenericLines"},
5432: {"GenericLines"},
5555: {"GenericLines"},
5560: {"GenericLines", "oracle-tns"},
5631: {"GenericLines", "PCWorkstation"},
5672: {"GenericLines"},
5984: {"GetRequest", "HTTPOptions"},
6000: {"X11Probe"},
6379: {"redis-server"},
6432: {"GenericLines"},
6667: {"GenericLines"},
7000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7001: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7002: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7070: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
7443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
7777: {"GenericLines", "oracle-tns"},
8000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "iperf3"},
8005: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8008: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8009: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "ajp"},
8080: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8081: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8089: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8090: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9042: {"GenericLines"},
9092: {"GenericLines", "kafka"},
9200: {"GetRequest", "HTTPOptions", "elasticsearch"},
9300: {"GenericLines"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "adbConnect"},
10000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10051: {"GenericLines"},
11211: {"Memcache"},
15672: {"GetRequest", "HTTPOptions"},
27017: {"mongodb"},
27018: {"mongodb"},
50070: {"GetRequest", "HTTPOptions"},
61616: {"GenericLines"},
}
// 默认服务用户字典
var DefaultUserDict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
// 默认密码字典
var DefaultPasswords = []string{
"123456", "admin", "admin123", "root", "", "pass123", "pass@123",
"password", "Password", "P@ssword123", "123123", "654321", "111111",
"123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}",
"{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123",
"{user}#123", "{user}@111", "{user}@2019", "{user}@123#4",
"P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test",
"test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666",
"a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888",
"!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111",
"a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123",
"Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ",
"2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456",
"1q2w3e", "Charge123", "Aa123456789", "elastic123",
}

193
Common/globals.go Normal file
View File

@ -0,0 +1,193 @@
package common
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
)
/*
globals.go - 全局变量定义
使用线程安全的配置管理消除双向同步机制直接使用core包作为唯一数据源
保持向后兼容的同时提供并发安全的访问
*/
// =============================================================================
// 版本信息
// =============================================================================
var version = "2.1.0"
// =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制)
// =============================================================================
// globalState已简化因为大部分管理函数未被使用
// 保留基本的时间记录用于向后兼容
var startTimeInit = time.Now()
// =============================================================================
// 核心扫描配置 - 直接使用core包变量单一数据源
// =============================================================================
var (
ScanMode string // 直接映射到base.ScanMode
ThreadNum int // 直接映射到base.ThreadNum
Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode
AliveOnly bool // 仅存活探测模式
)
// =============================================================================
// 基础认证配置 - 直接使用core包变量
// =============================================================================
var (
Username string // 直接映射到base.Username
Password string // 直接映射到base.Password
Userdict map[string][]string // 直接映射到base.Userdict
Passwords []string // 直接映射到base.Passwords
)
// =============================================================================
// 网络配置 - 直接使用core包变量
// =============================================================================
var (
HttpProxy string // 直接映射到base.HttpProxy
Socks5Proxy string // 直接映射到base.Socks5Proxy
)
// =============================================================================
// 显示控制 - 直接使用core包变量
// =============================================================================
var (
NoColor bool // 直接映射到base.NoColor
Language string // 直接映射到base.Language
LogLevel string // 直接映射到base.LogLevel
// 进度条控制
ShowProgress bool // 计算得出:!DisableProgress
)
// =============================================================================
// 端口映射 - 直接使用core包变量
// =============================================================================
var (
PortMap map[int][]string // 直接映射到base.PortMap
DefaultMap []string // 直接映射到base.DefaultMap
)
// =============================================================================
// 线程安全的输出状态管理(已移除未使用的函数)
// =============================================================================
// 注意GetOutputfile, SetOutputfile, GetOutputFormat, SetOutputFormat,
// GetProgressBar, SetProgressBar, GetStats, SetStats, IncrementNum等函数
// 已根据死代码分析移除,因为它们在代码库中没有被使用。
// 如有需要,可以通过直接访问向后兼容的全局变量实现相同功能。
// =============================================================================
// 核心配置同步(线程安全)
// =============================================================================
// SyncFromCore 从core包同步配置到common包读操作
func SyncFromCore() {
ScanMode = base.ScanMode
ThreadNum = base.ThreadNum
Timeout = base.Timeout
DisablePing = base.DisablePing
LocalMode = base.LocalMode
Username = base.Username
Password = base.Password
Userdict = base.Userdict
Passwords = base.Passwords
HttpProxy = base.HttpProxy
Socks5Proxy = base.Socks5Proxy
NoColor = base.NoColor
Language = base.Language
LogLevel = base.LogLevel
PortMap = base.PortMap
DefaultMap = base.DefaultMap
}
// SyncToCore 同步common包配置到core包写操作
func SyncToCore() {
base.ScanMode = ScanMode
base.ThreadNum = ThreadNum
base.Timeout = Timeout
base.DisablePing = DisablePing
base.LocalMode = LocalMode
base.Username = Username
base.Password = Password
base.Userdict = Userdict
base.Passwords = Passwords
base.HttpProxy = HttpProxy
base.Socks5Proxy = Socks5Proxy
base.NoColor = NoColor
base.Language = Language
base.LogLevel = LogLevel
base.PortMap = PortMap
base.DefaultMap = DefaultMap
}
// =============================================================================
// 向后兼容的全局变量
// =============================================================================
var (
// 输出配置(向后兼容)
Outputfile string
OutputFormat string
ProgressBar *progressbar.ProgressBar
OutputMutex sync.Mutex
// 统计信息(向后兼容)
Num, End int64
StartTime = time.Now()
)
// =============================================================================
// 日志级别常量
// =============================================================================
const (
LogLevelAll = string(logging.LevelAll)
LogLevelError = string(logging.LevelError)
LogLevelBase = string(logging.LevelBase)
LogLevelInfo = string(logging.LevelInfo)
LogLevelSuccess = string(logging.LevelSuccess)
LogLevelDebug = string(logging.LevelDebug)
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
)
// =============================================================================
// 初始化
// =============================================================================
func init() {
// 初始化core包配置
base.InitGlobalConfig()
// 从core包同步初始配置
SyncFromCore()
// 初始化向后兼容的时间变量
StartTime = startTimeInit
}

File diff suppressed because it is too large Load Diff

48
Common/i18n/init.go Normal file
View File

@ -0,0 +1,48 @@
package i18n
import (
"github.com/shadow1ng/fscan/common/i18n/messages"
)
/*
init.go - 国际化模块统一初始化
自动加载所有分类消息到全局管理器
确保所有消息在程序启动时正确注册
*/
// init 统一初始化所有国际化消息
func init() {
// 按类别依次加载所有消息
loadAllMessages()
}
// loadAllMessages 加载所有分类的消息
func loadAllMessages() {
// 加载核心系统消息
AddMessages(messages.CoreMessages)
// 加载解析相关消息
AddMessages(messages.ParseMessages)
// 加载配置相关消息
AddMessages(messages.ConfigMessages)
// 加载扫描相关消息
AddMessages(messages.ScanMessages)
// 加载网络相关消息
AddMessages(messages.NetworkMessages)
// 加载输出相关消息
AddMessages(messages.OutputMessages)
// 加载通用错误消息
AddMessages(messages.ErrorMessages)
// 加载命令行参数消息
AddMessages(messages.FlagMessages)
// 加载插件相关消息
AddMessages(messages.PluginMessages)
}

220
Common/i18n/manager.go Normal file
View File

@ -0,0 +1,220 @@
package i18n
import (
"fmt"
"sync"
)
/*
manager.go - 国际化管理器
提供统一的国际化文本管理支持多语言动态切换
包含完整的消息库和高效的文本查询机制
*/
// =============================================================================
// 常量定义
// =============================================================================
// 支持的语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)
// 默认配置
const (
DefaultLanguage = LangZH // 默认语言
FallbackLanguage = LangEN // 回退语言
)
// =============================================================================
// 国际化管理器
// =============================================================================
// Manager 国际化管理器
type Manager struct {
mu sync.RWMutex
currentLanguage string
fallbackLanguage string
messages map[string]map[string]string
enabled bool
}
// 全局管理器实例
var globalManager = NewManager()
// NewManager 创建新的国际化管理器
func NewManager() *Manager {
return &Manager{
currentLanguage: DefaultLanguage,
fallbackLanguage: FallbackLanguage,
messages: make(map[string]map[string]string),
enabled: true,
}
}
// =============================================================================
// 基础管理方法
// =============================================================================
// SetLanguage 设置当前语言
func (m *Manager) SetLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.currentLanguage = lang
}
// SetFallbackLanguage 设置回退语言
func (m *Manager) SetFallbackLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.fallbackLanguage = lang
}
// Enable 启用国际化
func (m *Manager) Enable() {
m.mu.Lock()
defer m.mu.Unlock()
m.enabled = true
}
// =============================================================================================
// 已删除的死代码函数未使用GetLanguage, Disable
// =============================================================================================
// IsEnabled 检查是否启用国际化
func (m *Manager) IsEnabled() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.enabled
}
// =============================================================================
// 消息管理方法
// =============================================================================
// AddMessages 批量添加消息
func (m *Manager) AddMessages(messages map[string]map[string]string) {
m.mu.Lock()
defer m.mu.Unlock()
for key, translations := range messages {
m.messages[key] = translations
}
}
// GetMessage 获取指定键和语言的消息
func (m *Manager) GetMessage(key, lang string) (string, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if translations, exists := m.messages[key]; exists {
if message, exists := translations[lang]; exists {
return message, true
}
}
return "", false
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// AddMessage, HasMessage, GetAllMessages, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 文本获取方法
// =============================================================================
// GetText 获取国际化文本(支持格式化)
func (m *Manager) GetText(key string, args ...interface{}) string {
if !m.IsEnabled() {
// 如果禁用国际化,返回原始键名
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
m.mu.RLock()
currentLang := m.currentLanguage
fallbackLang := m.fallbackLanguage
m.mu.RUnlock()
// 尝试获取当前语言的消息
if message, exists := m.GetMessage(key, currentLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
// 回退到回退语言
if currentLang != fallbackLang {
if message, exists := m.GetMessage(key, fallbackLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
}
// 如果都没找到,返回键名作为兜底
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
// =============================================================================================
// 已删除的死代码函数未使用GetTextWithLanguage
// =============================================================================================
// =============================================================================
// 全局访问函数
// =============================================================================
// SetLanguage 设置全局语言
func SetLanguage(lang string) {
globalManager.SetLanguage(lang)
}
// AddMessages 批量添加消息到全局管理器
func AddMessages(messages map[string]map[string]string) {
globalManager.AddMessages(messages)
}
// GetText 从全局管理器获取国际化文本
func GetText(key string, args ...interface{}) string {
return globalManager.GetText(key, args...)
}
// GetExploitMethodName 获取利用方法的本地化名称
func GetExploitMethodName(methodName string) string {
// 尝试获取本地化的方法名称
key := fmt.Sprintf("exploit_method_name_%s", methodName)
localizedName := globalManager.GetText(key)
// 如果没有找到对应的本地化名称,返回原始名称
if localizedName == key {
return methodName
}
return localizedName
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// GetGlobalManager, GetLanguage, AddMessage, GetTextWithLanguage,
// Enable, Disable, IsEnabled, HasMessage, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 初始化函数
// =============================================================================
// init 初始化全局国际化管理器
func init() {
// 设置默认配置
globalManager.SetLanguage(DefaultLanguage)
globalManager.SetFallbackLanguage(FallbackLanguage)
globalManager.Enable()
}

View File

@ -0,0 +1,79 @@
package messages
/*
config.go - 配置相关消息
包含配置管理验证同步等相关的
国际化消息定义
*/
// ConfigMessages 配置相关消息
var ConfigMessages = map[string]map[string]string{
// ========================= 配置相关消息 =========================
"config_sync_start": {
LangZH: "开始同步配置",
LangEN: "Starting configuration sync",
},
"config_sync_complete": {
LangZH: "配置同步完成",
LangEN: "Configuration sync completed",
},
"config_validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"config_validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"config_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"config_invalid_thread_num": {
LangZH: "无效的线程数: %d",
LangEN: "Invalid thread number: %d",
},
"config_invalid_timeout": {
LangZH: "无效的超时时间: %v",
LangEN: "Invalid timeout: %v",
},
"config_invalid_proxy": {
LangZH: "无效的代理配置: %s",
LangEN: "Invalid proxy configuration: %s",
},
"config_missing_required": {
LangZH: "缺少必需的配置项: %s",
LangEN: "Missing required configuration: %s",
},
"config_load_default": {
LangZH: "加载默认配置",
LangEN: "Loading default configuration",
},
"config_override_detected": {
LangZH: "检测到配置覆盖: %s",
LangEN: "Configuration override detected: %s",
},
"config_web_timeout_warning": {
LangZH: "Web超时时间大于普通超时时间可能导致不期望的行为",
LangEN: "Web timeout is larger than normal timeout, may cause unexpected behavior",
},
// ========================= 验证相关消息 =========================
"validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"validation_warning": {
LangZH: "验证警告: %s",
LangEN: "Validation warning: %s",
},
"validation_error": {
LangZH: "验证错误: %s",
LangEN: "Validation error: %s",
},
}

View File

@ -0,0 +1,13 @@
package messages
/*
constants.go - 消息包常量定义
包含消息包中使用的语言常量避免循环导入问题
*/
// 语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)

View File

@ -0,0 +1,93 @@
package messages
/*
core.go - 核心系统消息
包含系统核心功能的国际化消息包括扫描流程
系统状态通用错误等基础消息
*/
// CoreMessages 核心系统消息
var CoreMessages = map[string]map[string]string{
// ========================= 系统状态消息 =========================
"status_scan_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"status_scan_complete": {
LangZH: "扫描完成",
LangEN: "Scan completed",
},
"status_scan_progress": {
LangZH: "扫描进度: %d/%d",
LangEN: "Scan progress: %d/%d",
},
"status_target_found": {
LangZH: "发现目标: %s",
LangEN: "Target found: %s",
},
"status_service_detected": {
LangZH: "检测到服务: %s",
LangEN: "Service detected: %s",
},
"status_vuln_found": {
LangZH: "发现漏洞: %s",
LangEN: "Vulnerability found: %s",
},
"status_connection_failed": {
LangZH: "连接失败: %s",
LangEN: "Connection failed: %s",
},
"status_timeout": {
LangZH: "连接超时: %s",
LangEN: "Connection timeout: %s",
},
// ========================= 通用状态消息 =========================
"status_initializing": {
LangZH: "正在初始化...",
LangEN: "Initializing...",
},
"status_processing": {
LangZH: "正在处理...",
LangEN: "Processing...",
},
"status_completed": {
LangZH: "已完成",
LangEN: "Completed",
},
"status_failed": {
LangZH: "失败",
LangEN: "Failed",
},
"status_cancelled": {
LangZH: "已取消",
LangEN: "Cancelled",
},
"status_ready": {
LangZH: "就绪",
LangEN: "Ready",
},
// ========================= 文件操作消息 =========================
"file_read_start": {
LangZH: "开始读取文件: %s",
LangEN: "Starting to read file: %s",
},
"file_read_complete": {
LangZH: "文件读取完成: %s",
LangEN: "File reading completed: %s",
},
"file_read_error": {
LangZH: "读取文件错误: %s",
LangEN: "File reading error: %s",
},
"file_not_exist": {
LangZH: "文件不存在: %s",
LangEN: "File does not exist: %s",
},
"file_empty": {
LangZH: "文件为空: %s",
LangEN: "File is empty: %s",
},
}

View File

@ -0,0 +1,33 @@
package messages
/*
error.go - 通用错误消息
包含通用错误异常处理等相关的
国际化消息定义
*/
// ErrorMessages 通用错误消息
var ErrorMessages = map[string]map[string]string{
// ========================= 通用错误消息 =========================
"error_occurred": {
LangZH: "错误: %v",
LangEN: "Error: %v",
},
"error_unknown": {
LangZH: "未知错误",
LangEN: "Unknown error",
},
"error_not_implemented": {
LangZH: "功能未实现",
LangEN: "Feature not implemented",
},
"error_permission_denied": {
LangZH: "权限不足",
LangEN: "Permission denied",
},
"error_resource_busy": {
LangZH: "资源忙碌",
LangEN: "Resource busy",
},
}

View File

@ -0,0 +1,249 @@
package messages
/*
flag.go - 命令行参数消息
包含命令行参数帮助信息等相关的
国际化消息定义
*/
// FlagMessages 命令行参数消息
var FlagMessages = map[string]map[string]string{
// ========================= Flag参数帮助消息 =========================
"flag_host": {
LangZH: "目标主机: IP, IP段, IP段文件, 域名",
LangEN: "Target host: IP, IP range, IP file, domain",
},
"flag_exclude_hosts": {
LangZH: "排除主机",
LangEN: "Exclude hosts",
},
"flag_ports": {
LangZH: "端口: 默认1000个常用端口",
LangEN: "Ports: default 1000 common ports",
},
"flag_exclude_ports": {
LangZH: "排除端口",
LangEN: "Exclude ports",
},
"flag_hosts_file": {
LangZH: "主机文件",
LangEN: "Hosts file",
},
"flag_ports_file": {
LangZH: "端口文件",
LangEN: "Ports file",
},
"flag_scan_mode": {
LangZH: "扫描模式: all(全部), icmp(存活探测), 或指定插件名称",
LangEN: "Scan mode: all(all plugins), icmp(alive detection), or specific plugin names",
},
"flag_thread_num": {
LangZH: "端口扫描线程数",
LangEN: "Port scan thread count",
},
"flag_timeout": {
LangZH: "端口扫描超时时间",
LangEN: "Port scan timeout",
},
"flag_module_thread_num": {
LangZH: "模块线程数",
LangEN: "Module thread count",
},
"flag_global_timeout": {
LangZH: "全局超时时间",
LangEN: "Global timeout",
},
"flag_live_top": {
LangZH: "存活主机显示数量",
LangEN: "Live hosts display count",
},
"flag_disable_ping": {
LangZH: "禁用ping探测",
LangEN: "Disable ping detection",
},
"flag_enable_fingerprint": {
LangZH: "启用指纹识别",
LangEN: "Enable fingerprinting",
},
"flag_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
"flag_alive_only": {
LangZH: "仅进行存活探测",
LangEN: "Alive detection only",
},
"param_conflict_ao_icmp_both": {
LangZH: "提示: 同时指定了 -ao 和 -m icmp两者功能相同使用存活探测模式",
LangEN: "Note: Both -ao and -m icmp specified, both enable alive detection mode",
},
"flag_username": {
LangZH: "用户名",
LangEN: "Username",
},
"flag_password": {
LangZH: "密码",
LangEN: "Password",
},
"flag_add_users": {
LangZH: "额外用户名",
LangEN: "Additional usernames",
},
"flag_add_passwords": {
LangZH: "额外密码",
LangEN: "Additional passwords",
},
"flag_users_file": {
LangZH: "用户名字典文件",
LangEN: "Username dictionary file",
},
"flag_passwords_file": {
LangZH: "密码字典文件",
LangEN: "Password dictionary file",
},
"flag_hash_file": {
LangZH: "哈希文件",
LangEN: "Hash file",
},
"flag_hash_value": {
LangZH: "哈希值",
LangEN: "Hash value",
},
"flag_domain": {
LangZH: "域名",
LangEN: "Domain name",
},
"flag_ssh_key": {
LangZH: "SSH私钥文件",
LangEN: "SSH private key file",
},
"flag_target_url": {
LangZH: "目标URL",
LangEN: "Target URL",
},
"flag_urls_file": {
LangZH: "URL文件",
LangEN: "URLs file",
},
"flag_cookie": {
LangZH: "HTTP Cookie",
LangEN: "HTTP Cookie",
},
"flag_web_timeout": {
LangZH: "Web超时时间",
LangEN: "Web timeout",
},
"flag_http_proxy": {
LangZH: "HTTP代理",
LangEN: "HTTP proxy",
},
"flag_socks5_proxy": {
LangZH: "SOCKS5代理",
LangEN: "SOCKS5 proxy",
},
"flag_poc_path": {
LangZH: "POC脚本路径",
LangEN: "POC script path",
},
"flag_poc_name": {
LangZH: "POC名称",
LangEN: "POC name",
},
"flag_poc_full": {
LangZH: "全量POC扫描",
LangEN: "Full POC scan",
},
"flag_dns_log": {
LangZH: "DNS日志记录",
LangEN: "DNS logging",
},
"flag_poc_num": {
LangZH: "POC并发数",
LangEN: "POC concurrency",
},
"flag_no_poc": {
LangZH: "禁用POC扫描",
LangEN: "Disable POC scan",
},
"flag_redis_file": {
LangZH: "Redis文件",
LangEN: "Redis file",
},
"flag_redis_shell": {
LangZH: "Redis Shell",
LangEN: "Redis Shell",
},
"flag_disable_redis": {
LangZH: "禁用Redis扫描",
LangEN: "Disable Redis scan",
},
"flag_redis_write_path": {
LangZH: "Redis写入路径",
LangEN: "Redis write path",
},
"flag_redis_write_content": {
LangZH: "Redis写入内容",
LangEN: "Redis write content",
},
"flag_redis_write_file": {
LangZH: "Redis写入文件",
LangEN: "Redis write file",
},
"flag_disable_brute": {
LangZH: "禁用暴力破解",
LangEN: "Disable brute force",
},
"flag_disable_exploit": {
LangZH: "禁用利用攻击",
LangEN: "Disable exploit attacks",
},
"flag_max_retries": {
LangZH: "最大重试次数",
LangEN: "Maximum retries",
},
"flag_output_file": {
LangZH: "输出文件",
LangEN: "Output file",
},
"flag_output_format": {
LangZH: "输出格式: txt, json, csv",
LangEN: "Output format: txt, json, csv",
},
"flag_disable_save": {
LangZH: "禁用结果保存",
LangEN: "Disable result saving",
},
"flag_silent_mode": {
LangZH: "静默模式",
LangEN: "Silent mode",
},
"flag_no_color": {
LangZH: "禁用颜色输出",
LangEN: "Disable color output",
},
"flag_log_level": {
LangZH: "日志级别",
LangEN: "Log level",
},
"flag_disable_progress": {
LangZH: "禁用进度条",
LangEN: "Disable progress bar",
},
"flag_shellcode": {
LangZH: "Shellcode",
LangEN: "Shellcode",
},
"flag_language": {
LangZH: "语言: zh, en",
LangEN: "Language: zh, en",
},
"flag_help": {
LangZH: "显示帮助信息",
LangEN: "Show help information",
},
"flag_version": {
LangZH: "显示版本信息",
LangEN: "Show version information",
},
}

View File

@ -0,0 +1,67 @@
package messages
/*
network.go - 网络相关消息
包含网络配置代理设置连接管理等相关的
国际化消息定义
*/
// NetworkMessages 网络相关消息
var NetworkMessages = map[string]map[string]string{
// ========================= 网络配置消息 =========================
"network_http_proxy": {
LangZH: "HTTP代理: %s",
LangEN: "HTTP proxy: %s",
},
"network_socks5_proxy": {
LangZH: "Socks5代理: %s",
LangEN: "Socks5 proxy: %s",
},
"network_timeout": {
LangZH: "连接超时: %v",
LangEN: "Connection timeout: %v",
},
"network_web_timeout": {
LangZH: "Web超时: %v",
LangEN: "Web timeout: %v",
},
// ========================= 代理系统消息 =========================
"proxy_init_start": {
LangZH: "初始化代理系统",
LangEN: "Initializing proxy system",
},
"proxy_init_success": {
LangZH: "代理系统初始化成功",
LangEN: "Proxy system initialized successfully",
},
"proxy_init_failed": {
LangZH: "初始化代理配置失败: %v",
LangEN: "Failed to initialize proxy configuration: %v",
},
"proxy_config_sync_failed": {
LangZH: "代理配置同步失败: %v",
LangEN: "Failed to sync proxy configuration: %v",
},
"proxy_enabled": {
LangZH: "代理已启用: %s %s",
LangEN: "Proxy enabled: %s %s",
},
"proxy_disabled": {
LangZH: "代理已禁用",
LangEN: "Proxy disabled",
},
"proxy_connection_failed": {
LangZH: "代理连接失败: %v",
LangEN: "Proxy connection failed: %v",
},
"socks5_create_failed": {
LangZH: "创建SOCKS5连接失败: %v",
LangEN: "Failed to create SOCKS5 connection: %v",
},
"tls_conn_failed": {
LangZH: "TLS连接失败: %v",
LangEN: "TLS connection failed: %v",
},
}

View File

@ -0,0 +1,109 @@
package messages
/*
output.go - 输出相关消息
包含输出系统文件保存格式化等相关的
国际化消息定义
*/
// OutputMessages 输出相关消息
var OutputMessages = map[string]map[string]string{
// ========================= 输出系统消息 =========================
"output_init_start": {
LangZH: "初始化输出系统",
LangEN: "Initializing output system",
},
"output_init_success": {
LangZH: "输出系统初始化成功",
LangEN: "Output system initialized successfully",
},
"output_init_failed": {
LangZH: "输出系统初始化失败: %v",
LangEN: "Failed to initialize output system: %v",
},
"output_format_invalid": {
LangZH: "无效的输出格式: %s",
LangEN: "Invalid output format: %s",
},
"output_path_empty": {
LangZH: "输出路径为空",
LangEN: "Output path is empty",
},
"output_not_init": {
LangZH: "输出系统未初始化",
LangEN: "Output system not initialized",
},
"output_saving_result": {
LangZH: "保存扫描结果: %s -> %s",
LangEN: "Saving scan result: %s -> %s",
},
"output_save_failed": {
LangZH: "保存结果失败: %v",
LangEN: "Failed to save result: %v",
},
"output_closing": {
LangZH: "关闭输出系统",
LangEN: "Closing output system",
},
"output_closed": {
LangZH: "输出系统已关闭",
LangEN: "Output system closed",
},
"output_close_failed": {
LangZH: "关闭输出系统失败: %v",
LangEN: "Failed to close output system: %v",
},
"output_config_nil": {
LangZH: "配置不能为空",
LangEN: "Configuration cannot be nil",
},
"output_unsupported_format": {
LangZH: "不支持的输出格式: %s",
LangEN: "Unsupported output format: %s",
},
"output_writer_init_failed": {
LangZH: "初始化写入器失败: %v",
LangEN: "Failed to initialize writer: %v",
},
"output_writer_closed": {
LangZH: "写入器已关闭",
LangEN: "Writer is closed",
},
"output_manager_not_init": {
LangZH: "输出管理器未初始化",
LangEN: "Output manager not initialized",
},
"output_manager_closed": {
LangZH: "输出管理器已关闭",
LangEN: "Output manager is closed",
},
"output_write_failed": {
LangZH: "写入结果失败: %v",
LangEN: "Failed to write result: %v",
},
"output_flush_failed": {
LangZH: "刷新写入器失败: %v",
LangEN: "Failed to flush writer: %v",
},
"output_create_file_failed": {
LangZH: "创建%s文件失败: %v",
LangEN: "Failed to create %s file: %v",
},
"output_write_header_failed": {
LangZH: "写入CSV头部失败: %v",
LangEN: "Failed to write CSV header: %v",
},
"output_open_file_failed": {
LangZH: "打开CSV文件失败: %v",
LangEN: "Failed to open CSV file: %v",
},
"output_read_file_failed": {
LangZH: "读取CSV文件失败: %v",
LangEN: "Failed to read CSV file: %v",
},
"output_parse_time_failed": {
LangZH: "无法解析时间: %s",
LangEN: "Failed to parse time: %s",
},
}

View File

@ -0,0 +1,287 @@
package messages
/*
parse.go - 解析相关消息
包含参数解析目标解析凭据解析等相关的
国际化消息定义
*/
// ParseMessages 解析相关消息
var ParseMessages = map[string]map[string]string{
// ========================= 解析错误消息 =========================
"parse_error_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parse_error_config_failed": {
LangZH: "解析配置失败: %v",
LangEN: "Failed to parse configuration: %v",
},
"parse_error_parser_not_init": {
LangZH: "解析器未初始化",
LangEN: "Parser not initialized",
},
"parse_error_credential_failed": {
LangZH: "凭据解析失败: %v",
LangEN: "Failed to parse credentials: %v",
},
"parse_error_target_failed": {
LangZH: "目标解析失败: %v",
LangEN: "Failed to parse targets: %v",
},
"parse_error_network_failed": {
LangZH: "网络解析失败: %v",
LangEN: "Failed to parse network configuration: %v",
},
"parse_error_validation_failed": {
LangZH: "验证失败: %v",
LangEN: "Validation failed: %v",
},
"parse_error_update_vars_failed": {
LangZH: "更新全局变量失败: %v",
LangEN: "Failed to update global variables: %v",
},
"parse_error_target_empty": {
LangZH: "目标输入为空",
LangEN: "Target input is empty",
},
"parse_error_credential_empty": {
LangZH: "凭据输入为空",
LangEN: "Credential input is empty",
},
"parse_error_network_empty": {
LangZH: "网络配置为空",
LangEN: "Network configuration is empty",
},
"parse_error_invalid_ip": {
LangZH: "无效的IP地址: %s",
LangEN: "Invalid IP address: %s",
},
"parse_error_invalid_port": {
LangZH: "无效的端口: %s",
LangEN: "Invalid port: %s",
},
"parse_error_invalid_url": {
LangZH: "无效的URL: %s",
LangEN: "Invalid URL: %s",
},
"parse_error_file_not_found": {
LangZH: "文件未找到: %s",
LangEN: "File not found: %s",
},
"parse_error_file_read_failed": {
LangZH: "读取文件失败: %s",
LangEN: "Failed to read file: %s",
},
// ========================= 目标解析消息 =========================
"target_parse_start": {
LangZH: "开始解析目标",
LangEN: "Starting target parsing",
},
"target_parse_complete": {
LangZH: "目标解析完成",
LangEN: "Target parsing completed",
},
"target_hosts_found": {
LangZH: "目标主机: %s",
LangEN: "Target hosts: %s",
},
"target_hosts_count": {
LangZH: "目标主机: %s ... (共%d个)",
LangEN: "Target hosts: %s ... (total %d)",
},
"target_urls_found": {
LangZH: "目标URL: %s",
LangEN: "Target URLs: %s",
},
"target_urls_count": {
LangZH: "目标URL: %s ... (共%d个)",
LangEN: "Target URLs: %s ... (total %d)",
},
"target_ports_found": {
LangZH: "扫描端口: %s",
LangEN: "Scan ports: %s",
},
"target_ports_count": {
LangZH: "扫描端口: %s ... (共%d个)",
LangEN: "Scan ports: %s ... (total %d)",
},
"target_exclude_ports": {
LangZH: "排除端口: %s",
LangEN: "Exclude ports: %s",
},
"target_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
// ========================= 凭据相关消息 =========================
"credential_username_count": {
LangZH: "用户名数量: %d",
LangEN: "Username count: %d",
},
"credential_password_count": {
LangZH: "密码数量: %d",
LangEN: "Password count: %d",
},
"credential_hash_count": {
LangZH: "Hash数量: %d",
LangEN: "Hash count: %d",
},
// ========================= Parsers包专用消息 =========================
"parser_validation_input_empty": {
LangZH: "验证输入为空",
LangEN: "Validation input is empty",
},
"parser_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parser_file_empty": {
LangZH: "文件名为空",
LangEN: "File name is empty",
},
"parser_file_too_big": {
LangZH: "文件过大: %d bytes, 最大限制: %d bytes",
LangEN: "File too large: %d bytes, max limit: %d bytes",
},
"parser_cannot_open_file": {
LangZH: "无法打开文件",
LangEN: "Cannot open file",
},
"parser_file_not_exists": {
LangZH: "文件不存在或无法访问",
LangEN: "File does not exist or cannot be accessed",
},
"parser_file_read_timeout": {
LangZH: "文件读取超时",
LangEN: "File read timeout",
},
"parser_file_scan_failed": {
LangZH: "文件扫描失败",
LangEN: "File scan failed",
},
"parser_username_too_long": {
LangZH: "用户名过长: %d字符最大允许: %d",
LangEN: "Username too long: %d characters, max allowed: %d",
},
"parser_username_invalid_chars": {
LangZH: "用户名包含非法字符",
LangEN: "Username contains invalid characters",
},
"parser_password_empty": {
LangZH: "不允许空密码",
LangEN: "Empty passwords not allowed",
},
"parser_password_too_long": {
LangZH: "密码过长: %d字符最大允许: %d",
LangEN: "Password too long: %d characters, max allowed: %d",
},
"parser_hash_empty": {
LangZH: "哈希值为空",
LangEN: "Hash value is empty",
},
"parser_hash_invalid_format": {
LangZH: "哈希值格式无效需要32位十六进制字符",
LangEN: "Invalid hash format, requires 32-character hexadecimal",
},
"parser_proxy_url_invalid": {
LangZH: "代理URL格式无效: %v",
LangEN: "Invalid proxy URL format: %v",
},
"parser_proxy_protocol_unsupported": {
LangZH: "不支持的代理协议: %s",
LangEN: "Unsupported proxy protocol: %s",
},
"parser_proxy_host_empty": {
LangZH: "代理主机名为空",
LangEN: "Proxy hostname is empty",
},
"parser_proxy_port_invalid": {
LangZH: "代理端口号无效: %s",
LangEN: "Invalid proxy port: %s",
},
"parser_proxy_port_out_of_range": {
LangZH: "代理端口号超出范围: %d",
LangEN: "Proxy port out of range: %d",
},
"parser_proxy_insecure": {
LangZH: "不允许使用不安全的HTTP代理",
LangEN: "Insecure HTTP proxy not allowed",
},
"parser_user_agent_too_long": {
LangZH: "用户代理字符串过长",
LangEN: "User agent string too long",
},
"parser_user_agent_invalid_chars": {
LangZH: "用户代理包含非法字符",
LangEN: "User agent contains invalid characters",
},
"parser_cookie_too_long": {
LangZH: "Cookie字符串过长",
LangEN: "Cookie string too long",
},
"parser_error_count_limit": {
LangZH: "错误数量过多,仅显示前%d个",
LangEN: "Too many errors, showing only first %d",
},
"parser_no_scan_target": {
LangZH: "未指定任何扫描目标",
LangEN: "No scan targets specified",
},
"parser_no_target_default": {
LangZH: "未指定扫描目标,将使用默认配置",
LangEN: "No scan targets specified, using default configuration",
},
"parser_multiple_scan_modes": {
LangZH: "不能同时使用多种扫描模式",
LangEN: "Cannot use multiple scan modes simultaneously",
},
"parser_proxy_ping_warning": {
LangZH: "使用代理时建议禁用Ping检测",
LangEN: "Recommend disabling Ping detection when using proxy",
},
"parser_multiple_modes_conflict": {
LangZH: "不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)",
LangEN: "Cannot specify multiple scan modes (host scan, URL scan, local mode) simultaneously",
},
"parser_proxy_ping_fail": {
LangZH: "代理模式下Ping检测可能失效",
LangEN: "Ping detection may fail in proxy mode",
},
"parser_exclude_ports_invalid": {
LangZH: "排除端口无效",
LangEN: "Exclude ports invalid",
},
"parser_many_targets_warning": {
LangZH: "大量目标(%d),可能耗时较长",
LangEN: "Large number of targets (%d), may take a long time",
},
"parser_too_many_ports": {
LangZH: "端口数量过多",
LangEN: "Too many ports",
},
"parser_timeout_too_short": {
LangZH: "超时过短",
LangEN: "Timeout too short",
},
"parser_timeout_too_long": {
LangZH: "超时过长",
LangEN: "Timeout too long",
},
"parser_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"parse_error_invalid_target_format": {
LangZH: "无效的目标地址格式: %s",
LangEN: "Invalid target address format: %s",
},
"parse_error_no_hosts": {
LangZH: "解析后没有找到有效的目标主机",
LangEN: "No valid target hosts found after parsing",
},
}

View File

@ -0,0 +1,903 @@
package messages
/*
plugins.go - 插件相关消息
包含新插件架构中各种插件的国际化消息定义
包括扫描利用认证等相关消息
*/
// PluginMessages 插件相关消息
var PluginMessages = map[string]map[string]string{
// ========================= 通用插件消息 =========================
"plugin_init": {
LangZH: "初始化插件: %s",
LangEN: "Initializing plugin: %s",
},
"plugin_scan_start": {
LangZH: "开始%s插件扫描: %s",
LangEN: "Starting %s plugin scan: %s",
},
"plugin_scan_success": {
LangZH: "%s扫描成功: %s",
LangEN: "%s scan successful: %s",
},
"plugin_scan_failed": {
LangZH: "%s插件扫描失败: %v",
LangEN: "%s plugin scan failed: %v",
},
"plugin_exploit_start": {
LangZH: "开始%s自动利用: %s",
LangEN: "Starting %s auto exploitation: %s",
},
"plugin_exploit_success": {
LangZH: "%s利用成功: %s",
LangEN: "%s exploitation successful: %s",
},
"plugin_exploit_failed": {
LangZH: "%s利用失败: %v",
LangEN: "%s exploitation failed: %v",
},
// ========================= 通用成功消息模板 =========================
"plugin_login_success": {
LangZH: "%s弱密码: %s [%s:%s]",
LangEN: "%s weak password: %s [%s:%s]",
},
"plugin_login_success_passwd_only": {
LangZH: "%s弱密码: %s [%s]",
LangEN: "%s weak password: %s [%s]",
},
"plugin_unauthorized_access": {
LangZH: "%s未授权访问: %s",
LangEN: "%s unauthorized access: %s",
},
// ========================= 利用(Exploit)消息模板 =========================
"exploit_weak_password_success": {
LangZH: "%s %s 弱密码利用成功",
LangEN: "%s %s weak password exploit successful",
},
"exploit_unauthorized_success": {
LangZH: "%s %s 未授权访问利用成功",
LangEN: "%s %s unauthorized access exploit successful",
},
"exploit_command_exec_success": {
LangZH: "%s %s 命令执行利用成功",
LangEN: "%s %s command execution exploit successful",
},
"exploit_file_write_success": {
LangZH: "%s %s 文件写入利用成功",
LangEN: "%s %s file write exploit successful",
},
"exploit_sql_injection_success": {
LangZH: "%s %s SQL注入利用成功",
LangEN: "%s %s SQL injection exploit successful",
},
"exploit_data_extraction_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_generic_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_with_output": {
LangZH: " 输出: %s",
LangEN: " output: %s",
},
"exploit_files_created": {
LangZH: "创建/修改的文件: %v",
LangEN: "Files created/modified: %v",
},
"exploit_shell_obtained": {
LangZH: "获得Shell: %s %s:%d 用户:%s",
LangEN: "Shell obtained: %s %s:%d user:%s",
},
// ========================= 利用方法执行消息 =========================
"exploit_method_trying": {
LangZH: "尝试利用方法: %s",
LangEN: "Trying exploit method: %s",
},
"exploit_method_success": {
LangZH: "利用方法 %s 执行成功",
LangEN: "Exploit method %s executed successfully",
},
"exploit_method_failed": {
LangZH: "利用方法 %s 执行失败: %v",
LangEN: "Exploit method %s failed: %v",
},
"exploit_method_condition_not_met": {
LangZH: "利用方法 %s 前置条件不满足,跳过",
LangEN: "Exploit method %s prerequisites not met, skipping",
},
"exploit_all_methods_failed": {
LangZH: "所有利用方法都失败",
LangEN: "All exploit methods failed",
},
// ========================= MySQL利用方法消息 =========================
"mysql_version_info": {
LangZH: "MySQL版本: %s",
LangEN: "MySQL version: %s",
},
"mysql_current_user": {
LangZH: "当前用户: %s",
LangEN: "Current user: %s",
},
"mysql_current_database": {
LangZH: "当前数据库: %s",
LangEN: "Current database: %s",
},
"mysql_databases_found": {
LangZH: "发现数据库: %s",
LangEN: "Databases found: %s",
},
"mysql_tables_found": {
LangZH: "发现表: %v",
LangEN: "Tables found: %v",
},
"mysql_user_privileges": {
LangZH: "用户权限: %s",
LangEN: "User privileges: %s",
},
"mysql_file_privilege_detected": {
LangZH: "检测到FILE权限可能支持文件操作",
LangEN: "FILE privilege detected, file operations may be supported",
},
"mysql_file_read_success": {
LangZH: "读取文件 %s:\n%s",
LangEN: "File %s read:\n%s",
},
"mysql_file_write_success": {
LangZH: "成功写入文件: %s",
LangEN: "File written successfully: %s",
},
"mysql_no_file_privilege": {
LangZH: "无法读取任何文件可能没有FILE权限",
LangEN: "Cannot read any files, may lack FILE privilege",
},
// ========================= Redis利用方法消息 =========================
"redis_server_info": {
LangZH: "Redis服务器信息: %s",
LangEN: "Redis server info: %s",
},
"redis_config_info": {
LangZH: "Redis配置信息: %s",
LangEN: "Redis config info: %s",
},
"redis_keys_found": {
LangZH: "发现Redis键: %v",
LangEN: "Redis keys found: %v",
},
"redis_backup_created": {
LangZH: "Redis备份创建成功: %s",
LangEN: "Redis backup created: %s",
},
"redis_cron_job_written": {
LangZH: "Cron任务写入成功: %s",
LangEN: "Cron job written successfully: %s",
},
"redis_ssh_key_written": {
LangZH: "SSH密钥写入成功: %s",
LangEN: "SSH key written successfully: %s",
},
"redis_webshell_written": {
LangZH: "Webshell写入成功: %s",
LangEN: "Webshell written successfully: %s",
},
"redis_no_keys_found": {
LangZH: "未发现任何Redis键",
LangEN: "No Redis keys found",
},
"redis_write_failed": {
LangZH: "Redis写入操作失败",
LangEN: "Redis write operation failed",
},
// ========================= 插件架构消息 =========================
"plugin_new_arch_trying": {
LangZH: "尝试使用新插件架构: %s",
LangEN: "Trying new plugin architecture: %s",
},
"plugin_new_arch_success": {
LangZH: "新插件架构处理成功: %s",
LangEN: "New plugin architecture successful: %s",
},
"plugin_new_arch_fallback": {
LangZH: "新插件架构失败,回退到传统实现: %s - %v",
LangEN: "New plugin architecture failed, falling back to legacy: %s - %v",
},
"plugin_legacy_using": {
LangZH: "插件 %s 不支持新架构,使用传统实现",
LangEN: "Plugin %s not supported in new architecture, using legacy",
},
// ========================= MySQL插件消息 =========================
"mysql_scan_start": {
LangZH: "开始MySQL扫描: %s",
LangEN: "Starting MySQL scan: %s",
},
"mysql_scan_success": {
LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
LangEN: "MySQL weak password scan successful: %s [%s:%s]",
},
"mysql_service_identified": {
LangZH: "MySQL服务识别成功: %s - %s",
LangEN: "MySQL service identified: %s - %s",
},
"mysql_connection_failed": {
LangZH: "MySQL连接失败: %v",
LangEN: "MySQL connection failed: %v",
},
"mysql_auth_failed": {
LangZH: "MySQL认证失败: %v",
LangEN: "MySQL authentication failed: %v",
},
"mysql_exploit_info_gather": {
LangZH: "MySQL信息收集成功",
LangEN: "MySQL information gathering successful",
},
"mysql_exploit_db_enum": {
LangZH: "MySQL数据库枚举成功",
LangEN: "MySQL database enumeration successful",
},
"mysql_exploit_file_write": {
LangZH: "MySQL文件写入成功: %s",
LangEN: "MySQL file write successful: %s",
},
"mysql_exploit_file_read": {
LangZH: "MySQL文件读取成功: %s",
LangEN: "MySQL file read successful: %s",
},
// ========================= Redis插件消息 =========================
"redis_scan_start": {
LangZH: "开始Redis扫描: %s",
LangEN: "Starting Redis scan: %s",
},
"redis_unauth_success": {
LangZH: "Redis未授权访问: %s",
LangEN: "Redis unauthorized access: %s",
},
"redis_weak_pwd_success": {
LangZH: "Redis弱密码扫描成功: %s [%s]",
LangEN: "Redis weak password scan successful: %s [%s]",
},
"redis_service_identified": {
LangZH: "Redis服务识别成功: %s - %s",
LangEN: "Redis service identified: %s - %s",
},
"redis_connection_failed": {
LangZH: "Redis连接失败: %v",
LangEN: "Redis connection failed: %v",
},
"redis_auth_failed": {
LangZH: "Redis认证失败: %v",
LangEN: "Redis authentication failed: %v",
},
"redis_exploit_file_write": {
LangZH: "Redis任意文件写入成功: %s",
LangEN: "Redis arbitrary file write successful: %s",
},
"redis_exploit_ssh_key": {
LangZH: "Redis SSH密钥注入成功",
LangEN: "Redis SSH key injection successful",
},
"redis_exploit_crontab": {
LangZH: "Redis定时任务注入成功",
LangEN: "Redis crontab injection successful",
},
"redis_exploit_data_extract": {
LangZH: "Redis数据提取成功",
LangEN: "Redis data extraction successful",
},
// ========================= SSH插件消息 =========================
"ssh_scan_start": {
LangZH: "开始SSH扫描: %s",
LangEN: "Starting SSH scan: %s",
},
"ssh_key_auth_success": {
LangZH: "SSH密钥认证成功: %s [%s]",
LangEN: "SSH key authentication successful: %s [%s]",
},
"ssh_pwd_auth_success": {
LangZH: "SSH密码认证成功: %s [%s:%s]",
LangEN: "SSH password authentication successful: %s [%s:%s]",
},
"ssh_service_identified": {
LangZH: "SSH服务识别成功: %s - %s",
LangEN: "SSH service identified: %s - %s",
},
"ssh_connection_failed": {
LangZH: "SSH连接失败: %v",
LangEN: "SSH connection failed: %v",
},
"ssh_auth_failed": {
LangZH: "SSH认证失败: %v",
LangEN: "SSH authentication failed: %v",
},
"ssh_key_read_failed": {
LangZH: "读取SSH私钥失败: %v",
LangEN: "Failed to read SSH private key: %v",
},
// ========================= 通用错误消息 =========================
"plugin_brute_disabled": {
LangZH: "暴力破解已禁用",
LangEN: "Brute force disabled",
},
"plugin_no_credentials": {
LangZH: "没有可用的凭据",
LangEN: "No credentials available",
},
"plugin_all_creds_failed": {
LangZH: "所有凭据扫描失败",
LangEN: "All credential scans failed",
},
"plugin_invalid_port": {
LangZH: "无效的端口号: %s",
LangEN: "Invalid port number: %s",
},
"plugin_timeout": {
LangZH: "插件扫描超时",
LangEN: "Plugin scan timeout",
},
"plugin_vuln_found": {
LangZH: "%s发现漏洞: %s - %s",
LangEN: "%s vulnerability found: %s - %s",
},
// ========================= 利用方法名称i18n =========================
"exploit_method_name_information_gathering": {
LangZH: "信息收集",
LangEN: "information_gathering",
},
"exploit_method_name_database_enumeration": {
LangZH: "数据库枚举",
LangEN: "database_enumeration",
},
"exploit_method_name_privilege_check": {
LangZH: "权限检查",
LangEN: "privilege_check",
},
"exploit_method_name_file_read": {
LangZH: "文件读取",
LangEN: "file_read",
},
"exploit_method_name_file_write": {
LangZH: "文件写入",
LangEN: "file_write",
},
"exploit_method_name_arbitrary_file_write": {
LangZH: "任意文件写入",
LangEN: "arbitrary_file_write",
},
"exploit_method_name_ssh_key_write": {
LangZH: "SSH密钥写入",
LangEN: "ssh_key_write",
},
"exploit_method_name_crontab_injection": {
LangZH: "定时任务注入",
LangEN: "crontab_injection",
},
"exploit_method_name_data_extraction": {
LangZH: "数据提取",
LangEN: "data_extraction",
},
"exploit_method_name_system_info": {
LangZH: "系统信息收集",
LangEN: "system_info",
},
"exploit_method_name_command_test": {
LangZH: "命令执行测试",
LangEN: "command_test",
},
// ========================= SSH利用方法消息 =========================
"ssh_command_result": {
LangZH: "%s: %s",
LangEN: "%s: %s",
},
"ssh_test_command": {
LangZH: "执行命令 '%s': %s",
LangEN: "Executed command '%s': %s",
},
"ssh_sudo_check": {
LangZH: "Sudo权限: %s",
LangEN: "Sudo privileges: %s",
},
"ssh_root_access": {
LangZH: "检测到root权限访问",
LangEN: "Root access detected",
},
"ssh_user_groups": {
LangZH: "用户组: %s",
LangEN: "User groups: %s",
},
// ========================= 利用结果消息 =========================
"exploit_result_saved": {
LangZH: "利用结果已保存: %s",
LangEN: "Exploitation result saved: %s",
},
// ========================= ActiveMQ插件消息 =========================
"activemq_scan_start": {
LangZH: "开始ActiveMQ扫描: %s",
LangEN: "Starting ActiveMQ scan: %s",
},
"activemq_stomp_scan_success": {
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
},
"activemq_service_identified": {
LangZH: "ActiveMQ服务识别成功: %s (%s) - %s",
LangEN: "ActiveMQ service identified: %s (%s) - %s",
},
"activemq_stomp_auth_success": {
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
},
"activemq_connection_failed": {
LangZH: "ActiveMQ连接失败: %v",
LangEN: "ActiveMQ connection failed: %v",
},
"activemq_auth_failed": {
LangZH: "ActiveMQ认证失败: %v",
LangEN: "ActiveMQ authentication failed: %v",
},
"activemq_stomp_auth_failed": {
LangZH: "ActiveMQ STOMP认证失败: %v",
LangEN: "ActiveMQ STOMP authentication failed: %v",
},
// ActiveMQ利用方法消息
"activemq_exploit_info_gather": {
LangZH: "ActiveMQ信息收集成功",
LangEN: "ActiveMQ information gathering successful",
},
"activemq_exploit_message_enum": {
LangZH: "ActiveMQ消息枚举成功",
LangEN: "ActiveMQ message enumeration successful",
},
"activemq_exploit_queue_mgmt": {
LangZH: "ActiveMQ队列管理成功",
LangEN: "ActiveMQ queue management successful",
},
"activemq_exploit_config_dump": {
LangZH: "ActiveMQ配置转储成功",
LangEN: "ActiveMQ configuration dump successful",
},
"activemq_queues_found": {
LangZH: "发现ActiveMQ队列: %s",
LangEN: "ActiveMQ queues found: %s",
},
"activemq_topics_found": {
LangZH: "发现ActiveMQ主题: %s",
LangEN: "ActiveMQ topics found: %s",
},
"activemq_queue_created": {
LangZH: "成功创建测试队列: %s",
LangEN: "Test queue created successfully: %s",
},
"activemq_message_sent": {
LangZH: "消息发送成功到队列: %s",
LangEN: "Message sent successfully to queue: %s",
},
"activemq_version_info": {
LangZH: "ActiveMQ版本: %s",
LangEN: "ActiveMQ version: %s",
},
"activemq_broker_info": {
LangZH: "ActiveMQ Broker信息: %s",
LangEN: "ActiveMQ Broker info: %s",
},
"activemq_protocol_detected": {
LangZH: "检测到ActiveMQ协议: %s",
LangEN: "ActiveMQ protocol detected: %s",
},
// ActiveMQ利用方法名称
"exploit_method_name_activemq_info_gather": {
LangZH: "信息收集",
LangEN: "Information Gathering",
},
"exploit_method_name_activemq_message_enum": {
LangZH: "消息枚举",
LangEN: "Message Enumeration",
},
"exploit_method_name_activemq_queue_mgmt": {
LangZH: "队列管理",
LangEN: "Queue Management",
},
"exploit_method_name_activemq_config_dump": {
LangZH: "配置转储",
LangEN: "Configuration Dump",
},
// ========================= FTP插件消息 =========================
"ftp_scan_start": {
LangZH: "开始FTP扫描: %s",
LangEN: "Starting FTP scan: %s",
},
"ftp_anonymous_success": {
LangZH: "FTP匿名访问: %s",
LangEN: "FTP anonymous access: %s",
},
"ftp_weak_pwd_success": {
LangZH: "FTP弱密码: %s [%s:%s]",
LangEN: "FTP weak password: %s [%s:%s]",
},
"ftp_service_identified": {
LangZH: "FTP服务识别成功: %s - %s",
LangEN: "FTP service identified: %s - %s",
},
"ftp_connection_failed": {
LangZH: "FTP连接失败: %v",
LangEN: "FTP connection failed: %v",
},
"ftp_auth_failed": {
LangZH: "FTP认证失败: %v",
LangEN: "FTP authentication failed: %v",
},
// FTP利用方法消息
"ftp_exploit_dir_enum": {
LangZH: "FTP目录枚举成功",
LangEN: "FTP directory enumeration successful",
},
"ftp_exploit_file_download": {
LangZH: "FTP文件下载测试成功",
LangEN: "FTP file download test successful",
},
"ftp_exploit_file_upload": {
LangZH: "FTP文件上传测试成功",
LangEN: "FTP file upload test successful",
},
// ========================= IMAP插件消息 =========================
"imap_weak_pwd_success": {
LangZH: "IMAP弱密码: %s [%s:%s]",
LangEN: "IMAP weak password: %s [%s:%s]",
},
"imap_service_identified": {
LangZH: "IMAP服务识别成功: %s - %s",
LangEN: "IMAP service identified: %s - %s",
},
"imap_connection_failed": {
LangZH: "IMAP连接失败: %v",
LangEN: "IMAP connection failed: %v",
},
"imap_auth_failed": {
LangZH: "IMAP认证失败: %v",
LangEN: "IMAP authentication failed: %v",
},
// ========================= Kafka插件消息 =========================
"kafka_weak_pwd_success": {
LangZH: "Kafka弱密码: %s [%s:%s]",
LangEN: "Kafka weak password: %s [%s:%s]",
},
"kafka_unauth_access": {
LangZH: "Kafka服务 %s 无需认证即可访问",
LangEN: "Kafka service %s allows unauthorized access",
},
"kafka_service_identified": {
LangZH: "Kafka服务识别成功: %s - %s",
LangEN: "Kafka service identified: %s - %s",
},
"kafka_connection_failed": {
LangZH: "Kafka连接失败: %v",
LangEN: "Kafka connection failed: %v",
},
"kafka_auth_failed": {
LangZH: "Kafka认证失败: %v",
LangEN: "Kafka authentication failed: %v",
},
"ftp_directory_found": {
LangZH: "发现FTP目录: %s",
LangEN: "FTP directories found: %s",
},
"ftp_file_found": {
LangZH: "发现FTP文件: %s",
LangEN: "FTP files found: %s",
},
"ftp_upload_success": {
LangZH: "FTP文件上传成功: %s",
LangEN: "FTP file upload successful: %s",
},
"ftp_download_success": {
LangZH: "FTP文件下载成功: %s",
LangEN: "FTP file download successful: %s",
},
// FTP利用方法名称
"exploit_method_name_directory_enumeration": {
LangZH: "目录枚举",
LangEN: "Directory Enumeration",
},
"exploit_method_name_file_download_test": {
LangZH: "文件下载测试",
LangEN: "File Download Test",
},
"exploit_method_name_file_upload_test": {
LangZH: "文件上传测试",
LangEN: "File Upload Test",
},
// ========================= LDAP插件消息 =========================
"ldap_weak_pwd_success": {
LangZH: "LDAP弱密码: %s [%s:%s]",
LangEN: "LDAP weak password: %s [%s:%s]",
},
"ldap_anonymous_access": {
LangZH: "LDAP服务 %s 匿名访问成功",
LangEN: "LDAP service %s anonymous access successful",
},
"ldap_service_identified": {
LangZH: "LDAP服务识别成功: %s - %s",
LangEN: "LDAP service identified: %s - %s",
},
"ldap_connection_failed": {
LangZH: "LDAP连接失败: %v",
LangEN: "LDAP connection failed: %v",
},
"ldap_auth_failed": {
LangZH: "LDAP认证失败: %v",
LangEN: "LDAP authentication failed: %v",
},
// ========================= Memcached插件消息 =========================
"memcached_unauth_access": {
LangZH: "Memcached服务 %s 未授权访问成功",
LangEN: "Memcached service %s unauthorized access successful",
},
"memcached_service_identified": {
LangZH: "Memcached服务识别成功: %s - %s",
LangEN: "Memcached service identified: %s - %s",
},
"memcached_connection_failed": {
LangZH: "Memcached连接失败: %v",
LangEN: "Memcached connection failed: %v",
},
// ========================= Modbus插件消息 =========================
"modbus_unauth_access": {
LangZH: "Modbus服务 %s 无认证访问成功",
LangEN: "Modbus service %s unauthorized access successful",
},
"modbus_device_info": {
LangZH: "设备信息: %s",
LangEN: "Device info: %s",
},
"modbus_service_identified": {
LangZH: "Modbus服务识别成功: %s - %s",
LangEN: "Modbus service identified: %s - %s",
},
"modbus_connection_failed": {
LangZH: "Modbus连接失败: %v",
LangEN: "Modbus connection failed: %v",
},
// ========================= MongoDB插件消息 =========================
"mongodb_unauth_access": {
LangZH: "MongoDB服务 %s 未授权访问成功",
LangEN: "MongoDB service %s unauthorized access successful",
},
"mongodb_service_identified": {
LangZH: "MongoDB服务识别成功: %s - %s",
LangEN: "MongoDB service identified: %s - %s",
},
"mongodb_connection_failed": {
LangZH: "MongoDB连接失败: %v",
LangEN: "MongoDB connection failed: %v",
},
"mongodb_auth_failed": {
LangZH: "MongoDB认证失败: %v",
LangEN: "MongoDB authentication failed: %v",
},
// ========================= MSSQL插件消息 =========================
"mssql_auth_success": {
LangZH: "MSSQL服务 %s 认证成功 %s:%s",
LangEN: "MSSQL service %s authentication successful %s:%s",
},
"mssql_service_identified": {
LangZH: "MSSQL服务识别成功: %s - %s",
LangEN: "MSSQL service identified: %s - %s",
},
"mssql_connection_failed": {
LangZH: "MSSQL连接失败: %v",
LangEN: "MSSQL connection failed: %v",
},
"mssql_auth_failed": {
LangZH: "MSSQL认证失败 %s: %v",
LangEN: "MSSQL authentication failed %s: %v",
},
// ========================= Neo4j插件消息 =========================
"neo4j_unauth_access": {
LangZH: "Neo4j服务 %s 未授权访问成功",
LangEN: "Neo4j service %s unauthorized access successful",
},
"neo4j_default_creds": {
LangZH: "Neo4j服务 %s 默认凭证可用 %s:%s",
LangEN: "Neo4j service %s default credentials available %s:%s",
},
"neo4j_auth_success": {
LangZH: "Neo4j服务 %s 认证成功 %s:%s",
LangEN: "Neo4j service %s authentication successful %s:%s",
},
"neo4j_service_identified": {
LangZH: "Neo4j服务识别成功: %s - %s",
LangEN: "Neo4j service identified: %s - %s",
},
"neo4j_connection_failed": {
LangZH: "Neo4j连接失败: %v",
LangEN: "Neo4j connection failed: %v",
},
"neo4j_auth_failed": {
LangZH: "Neo4j认证失败 %s: %v",
LangEN: "Neo4j authentication failed %s: %v",
},
// ========================= PostgreSQL插件消息 =========================
"postgresql_auth_success": {
LangZH: "PostgreSQL服务 %s 认证成功 %s:%s",
LangEN: "PostgreSQL service %s authentication successful %s:%s",
},
"postgresql_service_identified": {
LangZH: "PostgreSQL服务识别成功: %s - %s",
LangEN: "PostgreSQL service identified: %s - %s",
},
"postgresql_connection_failed": {
LangZH: "PostgreSQL连接失败: %v",
LangEN: "PostgreSQL connection failed: %v",
},
"postgresql_auth_failed": {
LangZH: "PostgreSQL认证失败 %s: %v",
LangEN: "PostgreSQL authentication failed %s: %v",
},
// ========================= Oracle插件消息 =========================
"oracle_auth_success": {
LangZH: "Oracle服务 %s 认证成功 %s:%s",
LangEN: "Oracle service %s authentication successful %s:%s",
},
"oracle_sys_auth_success": {
LangZH: "Oracle服务 %s 高危用户认证成功 %s:%s (可能需要SYSDBA权限)",
LangEN: "Oracle service %s high-risk user authentication successful %s:%s (may require SYSDBA privilege)",
},
"oracle_service_identified": {
LangZH: "Oracle服务识别成功: %s - %s",
LangEN: "Oracle service identified: %s - %s",
},
"oracle_connection_failed": {
LangZH: "Oracle连接失败: %v",
LangEN: "Oracle connection failed: %v",
},
"oracle_auth_failed": {
LangZH: "Oracle认证失败 %s: %v",
LangEN: "Oracle authentication failed %s: %v",
},
// ========================= POP3插件消息 =========================
"pop3_weak_pwd_success": {
LangZH: "POP3弱密码: %s [%s:%s]",
LangEN: "POP3 weak password: %s [%s:%s]",
},
"pop3_service_identified": {
LangZH: "POP3服务识别成功: %s - %s",
LangEN: "POP3 service identified: %s - %s",
},
"pop3_connection_failed": {
LangZH: "POP3连接失败: %v",
LangEN: "POP3 connection failed: %v",
},
"pop3_auth_failed": {
LangZH: "POP3认证失败: %v",
LangEN: "POP3 authentication failed: %v",
},
// ========================= RabbitMQ插件消息 =========================
"rabbitmq_weak_pwd_success": {
LangZH: "RabbitMQ弱密码: %s [%s:%s]",
LangEN: "RabbitMQ weak password: %s [%s:%s]",
},
"rabbitmq_service_identified": {
LangZH: "RabbitMQ服务识别成功: %s - %s",
LangEN: "RabbitMQ service identified: %s - %s",
},
"rabbitmq_connection_failed": {
LangZH: "RabbitMQ连接失败: %v",
LangEN: "RabbitMQ connection failed: %v",
},
"rabbitmq_auth_failed": {
LangZH: "RabbitMQ认证失败: %v",
LangEN: "RabbitMQ authentication failed: %v",
},
// ========================= Rsync插件消息 =========================
"rsync_anonymous_success": {
LangZH: "Rsync匿名访问: %s",
LangEN: "Rsync anonymous access: %s",
},
"rsync_weak_pwd_success": {
LangZH: "Rsync弱密码: %s [%s:%s]",
LangEN: "Rsync weak password: %s [%s:%s]",
},
"rsync_service_identified": {
LangZH: "Rsync服务识别成功: %s - %s",
LangEN: "Rsync service identified: %s - %s",
},
"rsync_connection_failed": {
LangZH: "Rsync连接失败: %v",
LangEN: "Rsync connection failed: %v",
},
"rsync_auth_failed": {
LangZH: "Rsync认证失败: %v",
LangEN: "Rsync authentication failed: %v",
},
// ========================= SMTP插件消息 =========================
"smtp_anonymous_success": {
LangZH: "SMTP匿名访问: %s",
LangEN: "SMTP anonymous access: %s",
},
"smtp_weak_pwd_success": {
LangZH: "SMTP弱密码: %s [%s:%s]",
LangEN: "SMTP weak password: %s [%s:%s]",
},
"smtp_service_identified": {
LangZH: "SMTP服务识别成功: %s - %s",
LangEN: "SMTP service identified: %s - %s",
},
"smtp_connection_failed": {
LangZH: "SMTP连接失败: %v",
LangEN: "SMTP connection failed: %v",
},
"smtp_auth_failed": {
LangZH: "SMTP认证失败: %v",
LangEN: "SMTP authentication failed: %v",
},
// ========================= SNMP插件消息 =========================
"snmp_weak_community_success": {
LangZH: "SNMP弱community: %s [%s]",
LangEN: "SNMP weak community: %s [%s]",
},
"snmp_service_identified": {
LangZH: "SNMP服务识别成功: %s - %s",
LangEN: "SNMP service identified: %s - %s",
},
"snmp_connection_failed": {
LangZH: "SNMP连接失败: %v",
LangEN: "SNMP connection failed: %v",
},
"snmp_auth_failed": {
LangZH: "SNMP认证失败: %v",
LangEN: "SNMP authentication failed: %v",
},
// ========================= Telnet插件消息 =========================
"telnet_weak_password_success": {
LangZH: "Telnet弱密码: %s 用户名:%s 密码:%s",
LangEN: "Telnet weak password: %s username:%s password:%s",
},
"telnet_unauthorized_access": {
LangZH: "Telnet无需认证: %s",
LangEN: "Telnet unauthorized access: %s",
},
"telnet_connection_failed": {
LangZH: "Telnet连接失败: %v",
LangEN: "Telnet connection failed: %v",
},
"telnet_auth_failed": {
LangZH: "Telnet认证失败: %v",
LangEN: "Telnet authentication failed: %v",
},
}

View File

@ -0,0 +1,287 @@
package messages
/*
scan.go - 扫描相关消息
包含扫描流程模式选择插件管理等相关的
国际化消息定义
*/
// ScanMessages 扫描相关消息
var ScanMessages = map[string]map[string]string{
// ========================= 扫描流程消息 =========================
"scan_mode_service_selected": {
LangZH: "已选择服务扫描模式",
LangEN: "Service scan mode selected",
},
"scan_mode_alive_selected": {
LangZH: "已选择存活探测模式",
LangEN: "Alive detection mode selected",
},
"scan_mode_local_selected": {
LangZH: "已选择本地扫描模式",
LangEN: "Local scan mode selected",
},
"scan_mode_web_selected": {
LangZH: "已选择Web扫描模式",
LangEN: "Web scan mode selected",
},
"scan_info_start": {
LangZH: "开始信息扫描",
LangEN: "Starting information scan",
},
"scan_host_start": {
LangZH: "开始主机扫描",
LangEN: "Starting host scan",
},
"scan_vulnerability_start": {
LangZH: "开始漏洞扫描",
LangEN: "Starting vulnerability scan",
},
"scan_service_plugins": {
LangZH: "使用服务扫描插件: %s",
LangEN: "Using service scan plugins: %s",
},
"scan_no_service_plugins": {
LangZH: "未找到可用的服务插件",
LangEN: "No available service plugins found",
},
"scan_vulnerability_plugins": {
LangZH: "使用漏洞扫描插件: %s",
LangEN: "Using vulnerability scan plugins: %s",
},
"scan_no_vulnerability_plugins": {
LangZH: "未找到可用的漏洞扫描插件",
LangEN: "No available vulnerability scan plugins found",
},
"scan_complete_ports_found": {
LangZH: "扫描完成, 发现 %d 个开放端口",
LangEN: "Scan completed, found %d open ports",
},
"scan_alive_ports_count": {
LangZH: "存活端口数量: %d",
LangEN: "Alive ports count: %d",
},
"scan_snmp_udp_ports_added": {
LangZH: "检测到SNMP端口161添加UDP端口到扫描目标",
LangEN: "Detected SNMP port 161, adding UDP ports to scan targets",
},
"scan_task_complete": {
LangZH: "扫描已完成: %d/%d",
LangEN: "Scan completed: %d/%d",
},
// ========================= 扫描错误消息 =========================
"scan_plugin_panic": {
LangZH: "[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
LangEN: "[PANIC] Plugin %s crashed while scanning %s:%s: %v",
},
"scan_plugin_not_found": {
LangZH: "扫描类型 %v 无对应插件,已跳过",
LangEN: "No plugin found for scan type %v, skipped",
},
"scan_plugin_error": {
LangZH: "扫描错误 %v:%v - %v",
LangEN: "Scan error %v:%v - %v",
},
// ========================= 扫描器插件消息 =========================
"scan_local_start": {
LangZH: "开始本地信息收集",
LangEN: "Starting local information collection",
},
"scan_service_start": {
LangZH: "开始服务扫描",
LangEN: "Starting service scan",
},
"scan_web_start": {
LangZH: "开始Web扫描",
LangEN: "Starting web scan",
},
"scan_general_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"scan_mode_local_prefix": {
LangZH: "本地模式",
LangEN: "Local mode",
},
"scan_mode_service_prefix": {
LangZH: "服务模式",
LangEN: "Service mode",
},
"scan_mode_web_prefix": {
LangZH: "Web模式",
LangEN: "Web mode",
},
"scan_plugins_local": {
LangZH: "使用本地插件: %s",
LangEN: "Using local plugins: %s",
},
"scan_plugins_service": {
LangZH: "使用服务插件: %s",
LangEN: "Using service plugins: %s",
},
"scan_plugins_web": {
LangZH: "使用Web插件: %s",
LangEN: "Using web plugins: %s",
},
"scan_plugins_custom_specified": {
LangZH: "使用指定插件: %s",
LangEN: "Using specified plugins: %s",
},
"scan_no_local_plugins": {
LangZH: "未找到可用的本地插件",
LangEN: "No available local plugins found",
},
"scan_no_web_plugins": {
LangZH: "未找到可用的Web插件",
LangEN: "No available web plugins found",
},
"scan_strategy_local_name": {
LangZH: "本地扫描",
LangEN: "Local Scan",
},
"scan_strategy_local_desc": {
LangZH: "收集本地系统信息",
LangEN: "Collect local system information",
},
"scan_strategy_service_name": {
LangZH: "服务扫描",
LangEN: "Service Scan",
},
"scan_strategy_service_desc": {
LangZH: "扫描主机服务和漏洞",
LangEN: "Scan host services and vulnerabilities",
},
"scan_strategy_web_name": {
LangZH: "Web扫描",
LangEN: "Web Scan",
},
"scan_strategy_web_desc": {
LangZH: "扫描Web应用漏洞和信息",
LangEN: "Scan web application vulnerabilities and information",
},
"scan_alive_hosts_count": {
LangZH: "存活主机数量: %d",
LangEN: "Alive hosts count: %d",
},
"scan_strategy_alive_name": {
LangZH: "存活探测",
LangEN: "Alive Detection",
},
"scan_strategy_alive_desc": {
LangZH: "快速探测主机存活状态",
LangEN: "Fast detection of host alive status",
},
"scan_alive_start": {
LangZH: "开始存活探测",
LangEN: "Starting alive detection",
},
"scan_alive_single_target": {
LangZH: "目标主机: %s",
LangEN: "Target host: %s",
},
"scan_alive_multiple_targets": {
LangZH: "目标主机数量: %d (示例: %s)",
LangEN: "Target hosts count: %d (example: %s)",
},
"scan_alive_summary_title": {
LangZH: "存活探测结果摘要",
LangEN: "Alive Detection Summary",
},
"scan_alive_total_hosts": {
LangZH: "总主机数: %d",
LangEN: "Total hosts: %d",
},
"scan_alive_hosts_found": {
LangZH: "存活主机: %d",
LangEN: "Alive hosts: %d",
},
"scan_alive_dead_hosts": {
LangZH: "死亡主机: %d",
LangEN: "Dead hosts: %d",
},
"scan_alive_success_rate": {
LangZH: "存活率: %.2f%%",
LangEN: "Success rate: %.2f%%",
},
"scan_alive_duration": {
LangZH: "扫描耗时: %v",
LangEN: "Scan duration: %v",
},
"scan_alive_hosts_list": {
LangZH: "存活主机列表:",
LangEN: "Alive hosts list:",
},
"target_alive": {
LangZH: "存活主机: %s (%s)",
LangEN: "Alive host: %s (%s)",
},
// ========================= 进度条消息 =========================
"progress_scanning_description": {
LangZH: "扫描进度",
LangEN: "Scanning Progress",
},
"progress_port_scanning": {
LangZH: "端口扫描",
LangEN: "Port Scanning",
},
"progress_port_scanning_with_threads": {
LangZH: "端口扫描 (线程:%d)",
LangEN: "Port Scanning (Threads:%d)",
},
"progress_scan_completed": {
LangZH: "扫描完成:",
LangEN: "Scan Completed:",
},
"progress_port_scan_completed": {
LangZH: "端口扫描完成:",
LangEN: "Port Scan Completed:",
},
"progress_open_ports": {
LangZH: "开放端口",
LangEN: "Open Ports",
},
// ========================= 并发状态消息 =========================
"concurrency_plugin": {
LangZH: "插件",
LangEN: "Plugins",
},
"concurrency_connection": {
LangZH: "连接",
LangEN: "Conns",
},
"concurrency_plugin_tasks": {
LangZH: "活跃插件任务",
LangEN: "Active Plugin Tasks",
},
"concurrency_connection_details": {
LangZH: "连接详情",
LangEN: "Connection Details",
},
"concurrency_no_active_tasks": {
LangZH: "无活跃任务",
LangEN: "No Active Tasks",
},
// ========================= 扫描配置消息 =========================
"scan_config_thread_num": {
LangZH: "端口扫描线程数: %d",
LangEN: "Port scan threads: %d",
},
"scan_config_timeout": {
LangZH: "连接超时: %ds",
LangEN: "Connection timeout: %ds",
},
"scan_config_module_thread_num": {
LangZH: "插件内线程数: %d",
LangEN: "Plugin threads: %d",
},
"scan_config_global_timeout": {
LangZH: "单个插件全局超时: %ds",
LangEN: "Plugin global timeout: %ds",
},
}

View File

@ -0,0 +1,75 @@
package logging
import (
"fmt"
"time"
)
// StandardFormatter 标准日志格式化器
type StandardFormatter struct {
startTime time.Time
}
// NewStandardFormatter 创建标准格式化器
func NewStandardFormatter() *StandardFormatter {
return &StandardFormatter{
startTime: time.Now(),
}
}
// SetStartTime 设置开始时间
func (f *StandardFormatter) SetStartTime(startTime time.Time) {
f.startTime = startTime
}
// Format 格式化日志条目
func (f *StandardFormatter) Format(entry *LogEntry) string {
elapsed := time.Since(f.startTime)
timeStr := f.formatElapsedTime(elapsed)
prefix := f.getLevelPrefix(entry.Level)
return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content)
}
// formatElapsedTime 格式化经过的时间
func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
switch {
case elapsed < MaxMillisecondDisplay:
// 毫秒显示,不需要小数
return fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < MaxSecondDisplay:
// 秒显示,保留一位小数
return fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < MaxMinuteDisplay:
// 分钟和秒显示
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dm%ds", minutes, seconds)
default:
// 小时、分钟和秒显示
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
}
// getLevelPrefix 获取日志级别前缀
func (f *StandardFormatter) getLevelPrefix(level LogLevel) string {
switch level {
case LevelSuccess:
return PrefixSuccess
case LevelInfo:
return PrefixInfo
case LevelError:
return PrefixError
default:
return PrefixDefault
}
}
// =============================================================================================
// 已删除的死代码(未使用):
// DetailedFormatter 及其 Format() 方法
// JSONFormatter 及其 SetStartTime() 和 Format() 方法
// =============================================================================================

315
Common/logging/Logger.go Normal file
View File

@ -0,0 +1,315 @@
package logging
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/fatih/color"
)
// Logger 日志管理器
type Logger struct {
mu sync.RWMutex
config *LoggerConfig
formatter LogFormatter
handlers []LogHandler
scanStatus *ScanStatus
progressBar ProgressDisplay
outputMutex *sync.Mutex
initialized bool
}
// NewLogger 创建新的日志管理器
func NewLogger(config *LoggerConfig) *Logger {
if config == nil {
config = DefaultLoggerConfig()
}
logger := &Logger{
config: config,
formatter: NewStandardFormatter(),
handlers: make([]LogHandler, 0),
scanStatus: NewScanStatus(),
outputMutex: &sync.Mutex{},
initialized: true,
}
// 设置格式化器的开始时间
logger.formatter.SetStartTime(config.StartTime)
// 添加默认的控制台处理器
consoleHandler := NewConsoleHandler(config)
logger.AddHandler(consoleHandler)
return logger
}
// =============================================================================================
// 已删除的死代码未使用SetFormatter 方法
// =============================================================================================
// AddHandler 添加日志处理器
func (l *Logger) AddHandler(handler LogHandler) {
l.mu.Lock()
defer l.mu.Unlock()
l.handlers = append(l.handlers, handler)
}
// SetProgressBar 设置进度条显示
func (l *Logger) SetProgressBar(progressBar ProgressDisplay) {
l.mu.Lock()
defer l.mu.Unlock()
l.progressBar = progressBar
}
// SetOutputMutex 设置输出互斥锁
func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
l.mu.Lock()
defer l.mu.Unlock()
l.outputMutex = mutex
}
// SetCoordinatedOutput 设置协调输出函数(用于进度条协调)
func (l *Logger) SetCoordinatedOutput(fn func(string)) {
l.mu.RLock()
defer l.mu.RUnlock()
for _, handler := range l.handlers {
if consoleHandler, ok := handler.(*ConsoleHandler); ok {
consoleHandler.SetCoordinatedOutput(fn)
}
}
}
// Log 记录日志
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
if !l.shouldLog(level) {
return
}
entry := &LogEntry{
Level: level,
Time: time.Now(),
Content: content,
}
// 添加元数据
if len(metadata) > 0 {
entry.Metadata = metadata[0]
}
// 对于错误级别,自动添加调用者信息
if level == LevelError {
if _, file, line, ok := runtime.Caller(2); ok {
entry.Source = fmt.Sprintf("%s:%d", filepath.Base(file), line)
entry.Content = fmt.Sprintf("%s:%d - %s", filepath.Base(file), line, content)
}
}
l.handleLogEntry(entry)
// 更新扫描状态
if level == LevelSuccess {
l.scanStatus.UpdateSuccess()
} else if level == LevelError {
l.scanStatus.UpdateError()
}
}
// shouldLog 检查是否应该记录该级别的日志
func (l *Logger) shouldLog(level LogLevel) bool {
switch l.config.Level {
case LevelAll:
return true
case LevelBaseInfoSuccess:
return level == LevelBase || level == LevelInfo || level == LevelSuccess
case LevelInfoSuccess:
return level == LevelInfo || level == LevelSuccess
case LevelError:
return level == LevelError
case LevelBase:
return level == LevelBase
case LevelInfo:
return level == LevelInfo
case LevelSuccess:
return level == LevelSuccess
case LevelDebug:
return level == LevelDebug
default:
// 向后兼容:如果是字符串 "debug",显示所有
if l.config.Level == "debug" {
return true
}
// 默认显示base、info和success
return level == LevelBase || level == LevelInfo || level == LevelSuccess
}
}
// handleLogEntry 处理日志条目
func (l *Logger) handleLogEntry(entry *LogEntry) {
l.outputMutex.Lock()
defer l.outputMutex.Unlock()
// 清除进度条
l.clearProgress()
// 使用所有处理器处理日志
l.mu.RLock()
for _, handler := range l.handlers {
if handler.IsEnabled() {
handler.Handle(entry)
}
}
l.mu.RUnlock()
// 恢复进度条
l.restoreProgress()
}
// clearProgress 清除进度条
func (l *Logger) clearProgress() {
if l.progressBar != nil {
l.progressBar.Clear() // 忽略错误
time.Sleep(ProgressClearDelay)
}
}
// restoreProgress 恢复进度条
func (l *Logger) restoreProgress() {
if l.progressBar != nil {
l.progressBar.RenderBlank() // 忽略错误
}
}
// 便利方法
func (l *Logger) Debug(content string, metadata ...map[string]interface{}) {
l.Log(LevelDebug, content, metadata...)
}
func (l *Logger) Base(content string, metadata ...map[string]interface{}) {
l.Log(LevelBase, content, metadata...)
}
func (l *Logger) Info(content string, metadata ...map[string]interface{}) {
l.Log(LevelInfo, content, metadata...)
}
func (l *Logger) Success(content string, metadata ...map[string]interface{}) {
l.Log(LevelSuccess, content, metadata...)
}
func (l *Logger) Error(content string, metadata ...map[string]interface{}) {
l.Log(LevelError, content, metadata...)
}
// =============================================================================================
// 已删除的死代码未使用GetScanStatus 获取扫描状态管理器
// =============================================================================================
// Initialize 初始化日志系统(兼容原接口)
func (l *Logger) Initialize() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
// ConsoleHandler 控制台日志处理器
type ConsoleHandler struct {
config *LoggerConfig
formatter LogFormatter
enabled bool
coordinatedOutput func(string) // 协调输出函数
mu sync.RWMutex
}
// NewConsoleHandler 创建控制台处理器
func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler {
formatter := NewStandardFormatter()
formatter.SetStartTime(config.StartTime)
return &ConsoleHandler{
config: config,
formatter: formatter,
enabled: true,
}
}
// Handle 处理日志条目
func (h *ConsoleHandler) Handle(entry *LogEntry) {
h.mu.RLock()
defer h.mu.RUnlock()
if !h.enabled {
return
}
// 使用自己的格式化器格式化消息
logMsg := h.formatter.Format(entry)
// 使用协调输出函数,如果设置了的话
if h.coordinatedOutput != nil {
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
coloredMsg := color.New(attr).Sprint(logMsg)
h.coordinatedOutput(coloredMsg)
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
// 回到原来的直接输出方式
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
color.New(attr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
}
// 根据慢速输出设置决定是否添加延迟
if h.config.SlowOutput {
time.Sleep(SlowOutputDelay)
}
}
// SetEnabled 设置处理器启用状态
func (h *ConsoleHandler) SetEnabled(enabled bool) {
h.mu.Lock()
defer h.mu.Unlock()
h.enabled = enabled
}
// SetCoordinatedOutput 设置协调输出函数
func (h *ConsoleHandler) SetCoordinatedOutput(fn func(string)) {
h.mu.Lock()
defer h.mu.Unlock()
h.coordinatedOutput = fn
}
// IsEnabled 检查处理器是否启用
func (h *ConsoleHandler) IsEnabled() bool {
h.mu.RLock()
defer h.mu.RUnlock()
return h.enabled
}
// =============================================================================================
// 已删除的死代码未使用CheckErrs 错误检查函数
// =============================================================================================

103
Common/logging/Types.go Normal file
View File

@ -0,0 +1,103 @@
package logging
import (
"sync"
"time"
)
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level LogLevel `json:"level"` // 日志级别
Time time.Time `json:"time"` // 日志时间
Content string `json:"content"` // 日志内容
Source string `json:"source"` // 日志来源
Metadata map[string]interface{} `json:"metadata"` // 附加元数据
}
// LogFormatter 日志格式化器接口
type LogFormatter interface {
Format(entry *LogEntry) string
SetStartTime(startTime time.Time)
}
// LogHandler 日志处理器接口
type LogHandler interface {
Handle(entry *LogEntry)
SetEnabled(enabled bool)
IsEnabled() bool
}
// LoggerConfig 日志器配置
type LoggerConfig struct {
Level LogLevel `json:"level"` // 日志级别
EnableColor bool `json:"enable_color"` // 是否启用彩色输出
SlowOutput bool `json:"slow_output"` // 是否启用慢速输出
ShowProgress bool `json:"show_progress"` // 是否显示进度条
StartTime time.Time `json:"start_time"` // 开始时间
LevelColors map[LogLevel]interface{} `json:"-"` // 级别颜色映射
}
// DefaultLoggerConfig 默认日志器配置
func DefaultLoggerConfig() *LoggerConfig {
return &LoggerConfig{
Level: DefaultLevel,
EnableColor: DefaultEnableColor,
SlowOutput: DefaultSlowOutput,
ShowProgress: DefaultShowProgress,
StartTime: time.Now(),
LevelColors: convertColorMap(GetDefaultLevelColors()),
}
}
// convertColorMap 将color.Attribute映射转换为interface{}映射
func convertColorMap(colorMap map[LogLevel]interface{}) map[LogLevel]interface{} {
result := make(map[LogLevel]interface{})
for k, v := range colorMap {
result[k] = v
}
return result
}
// ScanStatus 扫描状态管理器
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// NewScanStatus 创建新的扫描状态管理器
func NewScanStatus() *ScanStatus {
now := time.Now()
return &ScanStatus{
lastSuccess: now,
lastError: now,
}
}
// UpdateSuccess 更新最后成功时间
func (s *ScanStatus) UpdateSuccess() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastSuccess = time.Now()
}
// UpdateError 更新最后错误时间
func (s *ScanStatus) UpdateError() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastError = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetLastSuccess, GetLastError, SetTotal, SetCompleted, GetProgress 方法
// =============================================================================================
// ProgressDisplay 进度条显示接口
type ProgressDisplay interface {
Clear() error
RenderBlank() error
}

View File

@ -0,0 +1,86 @@
package logging
/*
constants.go - 日志系统常量定义
统一管理common/logging包中的所有常量便于查看和编辑
*/
import (
"time"
"github.com/fatih/color"
)
// =============================================================================
// 日志级别常量 (从Types.go迁移)
// =============================================================================
// LogLevel 日志级别类型
type LogLevel string
// 定义系统支持的日志级别常量
const (
LevelAll LogLevel = "ALL" // 显示所有级别日志
LevelError LogLevel = "ERROR" // 仅显示错误日志
LevelBase LogLevel = "BASE" // 仅显示基础信息日志
LevelInfo LogLevel = "INFO" // 仅显示信息日志
LevelSuccess LogLevel = "SUCCESS" // 仅显示成功日志
LevelDebug LogLevel = "DEBUG" // 仅显示调试日志
LevelInfoSuccess LogLevel = "INFO_SUCCESS" // 仅显示信息和成功日志
LevelBaseInfoSuccess LogLevel = "BASE_INFO_SUCCESS" // 显示基础、信息和成功日志
)
// =============================================================================
// 时间显示常量 (从Formatter.go迁移)
// =============================================================================
const (
// 时间显示阈值
MaxMillisecondDisplay = time.Second // 毫秒显示的最大时长
MaxSecondDisplay = time.Minute // 秒显示的最大时长
MaxMinuteDisplay = time.Hour // 分钟显示的最大时长
// 慢速输出延迟
SlowOutputDelay = 50 * time.Millisecond
// 进度条清除延迟
ProgressClearDelay = 10 * time.Millisecond
)
// =============================================================================
// 日志前缀常量 (从Formatter.go迁移)
// =============================================================================
const (
PrefixSuccess = "[+]" // 成功日志前缀
PrefixInfo = "[*]" // 信息日志前缀
PrefixError = "[-]" // 错误日志前缀
PrefixDefault = " " // 默认日志前缀
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultLevel = LevelAll // 默认日志级别
DefaultEnableColor = true // 默认启用彩色输出
DefaultSlowOutput = false // 默认不启用慢速输出
DefaultShowProgress = true // 默认显示进度条
)
// =============================================================================
// 默认颜色映射
// =============================================================================
// GetDefaultLevelColors 获取默认的日志级别颜色映射
func GetDefaultLevelColors() map[LogLevel]interface{} {
return map[LogLevel]interface{}{
LevelError: color.FgBlue, // 错误日志显示蓝色
LevelBase: color.FgYellow, // 基础日志显示黄色
LevelInfo: color.FgGreen, // 信息日志显示绿色
LevelSuccess: color.FgRed, // 成功日志显示红色
LevelDebug: color.FgWhite, // 调试日志显示白色
}
}

256
Common/output/Manager.go Normal file
View File

@ -0,0 +1,256 @@
package output
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// Manager 输出管理器
type Manager struct {
mu sync.RWMutex
config *ManagerConfig
writer OutputWriter
reader OutputReader
statistics *Statistics
buffer []*ScanResult
bufferMutex sync.Mutex
flushTicker *time.Ticker
stopChan chan struct{}
initialized bool
closed bool
}
// NewManager 创建新的输出管理器
func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil {
return nil, fmt.Errorf(i18n.GetText("output_config_nil"))
}
// 验证输出格式
if err := validateFormat(config.Format); err != nil {
return nil, err
}
// 创建输出目录
if err := createOutputDir(config.OutputPath); err != nil {
return nil, err
}
manager := &Manager{
config: config,
statistics: NewStatistics(),
stopChan: make(chan struct{}),
}
// 初始化写入器
if err := manager.initializeWriter(); err != nil {
return nil, err
}
// 初始化读取器
manager.initializeReader()
// 如果启用缓冲,初始化缓冲区
if config.EnableBuffer {
manager.buffer = make([]*ScanResult, 0, config.BufferSize)
// 如果启用自动刷新,启动定时器
if config.AutoFlush {
manager.startAutoFlush()
}
}
manager.initialized = true
return manager, nil
}
// validateFormat 验证输出格式
func validateFormat(format OutputFormat) error {
switch format {
case FormatTXT, FormatJSON, FormatCSV:
return nil
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), format)
}
}
// createOutputDir 创建输出目录
func createOutputDir(outputPath string) error {
dir := filepath.Dir(outputPath)
return os.MkdirAll(dir, DefaultDirPermissions)
}
// initializeWriter 初始化写入器
func (m *Manager) initializeWriter() error {
var writer OutputWriter
var err error
switch m.config.Format {
case FormatTXT:
writer, err = NewTXTWriter(m.config.OutputPath)
case FormatJSON:
writer, err = NewJSONWriter(m.config.OutputPath)
case FormatCSV:
writer, err = NewCSVWriter(m.config.OutputPath)
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), m.config.Format)
}
if err != nil {
return fmt.Errorf(i18n.GetText("output_writer_init_failed"), err)
}
m.writer = writer
// 写入头部(如果需要)
return m.writer.WriteHeader()
}
// initializeReader 初始化读取器
func (m *Manager) initializeReader() {
// 目前只有CSV格式支持读取
if m.config.Format == FormatCSV {
m.reader = NewCSVReader(m.config.OutputPath)
}
}
// startAutoFlush 启动自动刷新
func (m *Manager) startAutoFlush() {
m.flushTicker = time.NewTicker(m.config.FlushInterval)
go func() {
for {
select {
case <-m.flushTicker.C:
m.flushBuffer()
case <-m.stopChan:
return
}
}
}()
}
// SaveResult 保存扫描结果
func (m *Manager) SaveResult(result *ScanResult) error {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.initialized {
return fmt.Errorf(i18n.GetText("output_manager_not_init"))
}
if m.closed {
return fmt.Errorf(i18n.GetText("output_manager_closed"))
}
// 更新统计信息
m.statistics.AddResult(result.Type)
// 如果启用缓冲,先添加到缓冲区
if m.config.EnableBuffer {
return m.addToBuffer(result)
}
// 直接写入
return m.writer.Write(result)
}
// addToBuffer 添加结果到缓冲区
func (m *Manager) addToBuffer(result *ScanResult) error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
m.buffer = append(m.buffer, result)
// 如果缓冲区已满,立即刷新
if len(m.buffer) >= m.config.BufferSize {
return m.flushBufferUnsafe()
}
return nil
}
// flushBuffer 刷新缓冲区(加锁版本)
func (m *Manager) flushBuffer() error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
return m.flushBufferUnsafe()
}
// flushBufferUnsafe 刷新缓冲区(无锁版本,内部使用)
func (m *Manager) flushBufferUnsafe() error {
if len(m.buffer) == 0 {
return nil
}
// 批量写入
for _, result := range m.buffer {
if err := m.writer.Write(result); err != nil {
return fmt.Errorf(i18n.GetText("output_write_failed"), err)
}
}
// 刷新写入器
if err := m.writer.Flush(); err != nil {
return fmt.Errorf(i18n.GetText("output_flush_failed"), err)
}
// 清空缓冲区
m.buffer = m.buffer[:0]
return nil
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetResults, GetResultsWithFilter, GetStatistics, Flush 方法
// =============================================================================================
// Close 关闭输出管理器
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return nil
}
// 停止自动刷新
if m.flushTicker != nil {
m.flushTicker.Stop()
close(m.stopChan)
}
// 最后一次刷新缓冲区
if m.config.EnableBuffer {
m.flushBufferUnsafe()
}
// 关闭写入器
var err error
if m.writer != nil {
err = m.writer.Close()
}
// 关闭读取器
if m.reader != nil {
if closeErr := m.reader.Close(); closeErr != nil && err == nil {
err = closeErr
}
}
m.closed = true
return err
}
// =============================================================================================
// 已删除的死代码(未使用):
// IsInitialized, IsClosed, GetConfig, UpdateConfig 方法
// =============================================================================================
// =============================================================================================
// 已删除的死代码未使用globalManager 全局变量及 SetGlobalManager 函数
// =============================================================================================

102
Common/output/Types.go Normal file
View File

@ -0,0 +1,102 @@
package output
import (
"sync"
"time"
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// OutputWriter 输出写入器接口
type OutputWriter interface {
Write(result *ScanResult) error
WriteHeader() error
Flush() error
Close() error
GetFormat() OutputFormat
}
// OutputReader 输出读取器接口
type OutputReader interface {
Read() ([]*ScanResult, error)
ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
Close() error
}
// ResultFilter 结果过滤器
type ResultFilter struct {
Types []ResultType `json:"types"` // 过滤的结果类型
Targets []string `json:"targets"` // 过滤的目标
TimeRange *TimeRange `json:"time_range"` // 时间范围
Limit int `json:"limit"` // 限制数量
Offset int `json:"offset"` // 偏移量
}
// TimeRange 时间范围
type TimeRange struct {
Start time.Time `json:"start"` // 开始时间
End time.Time `json:"end"` // 结束时间
}
// ManagerConfig 输出管理器配置
type ManagerConfig struct {
OutputPath string `json:"output_path"` // 输出路径
Format OutputFormat `json:"format"` // 输出格式
EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲
BufferSize int `json:"buffer_size"` // 缓冲区大小
AutoFlush bool `json:"auto_flush"` // 是否自动刷新
FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔
}
// DefaultManagerConfig 默认管理器配置
func DefaultManagerConfig(outputPath string, format OutputFormat) *ManagerConfig {
return &ManagerConfig{
OutputPath: outputPath,
Format: format,
EnableBuffer: DefaultEnableBuffer,
BufferSize: DefaultBufferSize,
AutoFlush: DefaultAutoFlush,
FlushInterval: DefaultFlushInterval,
}
}
// Statistics 输出统计信息
type Statistics struct {
mu sync.RWMutex
TotalResults int64 `json:"total_results"` // 总结果数
TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数
StartTime time.Time `json:"start_time"` // 开始时间
LastUpdate time.Time `json:"last_update"` // 最后更新时间
}
// NewStatistics 创建新的统计信息
func NewStatistics() *Statistics {
return &Statistics{
TypeCounts: make(map[ResultType]int64),
StartTime: time.Now(),
LastUpdate: time.Now(),
}
}
// AddResult 添加结果统计
func (s *Statistics) AddResult(resultType ResultType) {
s.mu.Lock()
defer s.mu.Unlock()
s.TotalResults++
s.TypeCounts[resultType]++
s.LastUpdate = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetTotalResults, GetTypeCounts, GetDuration, Reset 方法
// =============================================================================================

459
Common/output/Writers.go Normal file
View File

@ -0,0 +1,459 @@
package output
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// TXTWriter 文本格式写入器
type TXTWriter struct {
file *os.File
mu sync.Mutex
closed bool
}
// NewTXTWriter 创建文本写入器
func NewTXTWriter(filePath string) (*TXTWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "文本", err)
}
return &TXTWriter{
file: file,
}, nil
}
// WriteHeader 写入头部(文本格式无需头部)
func (w *TXTWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *TXTWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
}
details = strings.Join(pairs, TxtDetailsSeparator)
}
// 使用类似原有格式的文本输出
txt := fmt.Sprintf(TxtOutputTemplate,
result.Time.Format(TxtTimeFormat),
result.Type,
result.Target,
result.Status,
)
if details != "" {
txt += fmt.Sprintf(TxtDetailsFormat, details)
}
txt += TxtNewline
_, err := w.file.WriteString(txt)
return err
}
// Flush 刷新缓冲区
func (w *TXTWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *TXTWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *TXTWriter) GetFormat() OutputFormat {
return FormatTXT
}
// JSONWriter JSON格式写入器
type JSONWriter struct {
file *os.File
encoder *json.Encoder
mu sync.Mutex
closed bool
}
// NewJSONWriter 创建JSON写入器
func NewJSONWriter(filePath string) (*JSONWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "JSON", err)
}
encoder := json.NewEncoder(file)
encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
return &JSONWriter{
file: file,
encoder: encoder,
}, nil
}
// WriteHeader 写入头部JSON格式无需头部
func (w *JSONWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *JSONWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
return w.encoder.Encode(result)
}
// Flush 刷新缓冲区
func (w *JSONWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *JSONWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *JSONWriter) GetFormat() OutputFormat {
return FormatJSON
}
// CSVWriter CSV格式写入器
type CSVWriter struct {
file *os.File
csvWriter *csv.Writer
mu sync.Mutex
closed bool
headerWritten bool
}
// NewCSVWriter 创建CSV写入器
func NewCSVWriter(filePath string) (*CSVWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "CSV", err)
}
csvWriter := csv.NewWriter(file)
return &CSVWriter{
file: file,
csvWriter: csvWriter,
}, nil
}
// WriteHeader 写入CSV头部
func (w *CSVWriter) WriteHeader() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Write 写入扫描结果
func (w *CSVWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 确保头部已写入
if !w.headerWritten {
if err := w.writeHeaderUnsafe(); err != nil {
return err
}
}
// 序列化Details为JSON字符串
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte(EmptyJSONObject)
}
record := []string{
result.Time.Format(DefaultTimeFormat),
string(result.Type),
result.Target,
result.Status,
string(details),
}
err = w.csvWriter.Write(record)
if err != nil {
return err
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
func (w *CSVWriter) writeHeaderUnsafe() error {
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Flush 刷新缓冲区
func (w *CSVWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// Close 关闭写入器
func (w *CSVWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
err := w.csvWriter.Error()
w.closed = true
if fileErr := w.file.Close(); fileErr != nil && err == nil {
err = fileErr
}
return err
}
// GetFormat 获取格式类型
func (w *CSVWriter) GetFormat() OutputFormat {
return FormatCSV
}
// CSVReader CSV格式读取器
type CSVReader struct {
filePath string
mu sync.Mutex
}
// NewCSVReader 创建CSV读取器
func NewCSVReader(filePath string) *CSVReader {
return &CSVReader{
filePath: filePath,
}
}
// Read 读取所有结果
func (r *CSVReader) Read() ([]*ScanResult, error) {
return r.ReadWithFilter(nil)
}
// ReadWithFilter 带过滤条件读取结果
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
r.mu.Lock()
defer r.mu.Unlock()
file, err := os.Open(r.filePath)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_open_file_failed"), err)
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), err)
}
var results []*ScanResult
for i, row := range records {
// 跳过CSV头部
if i == CSVHeaderRowIndex {
continue
}
if len(row) < CSVMinColumns {
continue // 数据不完整
}
result, err := r.parseCSVRow(row)
if err != nil {
continue // 跳过解析失败的行
}
// 应用过滤器
if filter != nil && !r.matchFilter(result, filter) {
continue
}
results = append(results, result)
// 应用限制
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
break
}
}
return results, nil
}
// parseCSVRow 解析CSV行
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
// 解析时间
t, err := parseTime(row[CSVTimeColumnIndex])
if err != nil {
return nil, err
}
// 解析Details
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
details = make(map[string]interface{})
}
return &ScanResult{
Time: t,
Type: ResultType(row[CSVTypeColumnIndex]),
Target: row[CSVTargetColumnIndex],
Status: row[CSVStatusColumnIndex],
Details: details,
}, nil
}
// matchFilter 检查结果是否匹配过滤器
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
// 检查类型过滤
if len(filter.Types) > 0 {
found := false
for _, t := range filter.Types {
if result.Type == t {
found = true
break
}
}
if !found {
return false
}
}
// 检查目标过滤
if len(filter.Targets) > 0 {
found := false
for _, target := range filter.Targets {
if strings.Contains(result.Target, target) {
found = true
break
}
}
if !found {
return false
}
}
// 检查时间范围过滤
if filter.TimeRange != nil {
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
return false
}
}
return true
}
// Close 关闭读取器
func (r *CSVReader) Close() error {
return nil // CSV读取器无需特殊关闭操作
}
// parseTime 解析时间字符串
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := GetSupportedTimeFormats()
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf(i18n.GetText("output_parse_time_failed"), timeStr)
}

129
Common/output/constants.go Normal file
View File

@ -0,0 +1,129 @@
package output
import (
"os"
"time"
)
/*
constants.go - 输出系统常量定义
统一管理common/output包中的所有常量便于查看和编辑
*/
// =============================================================================
// 输出格式常量 (从Types.go迁移)
// =============================================================================
// OutputFormat 输出格式类型
type OutputFormat string
const (
FormatTXT OutputFormat = "txt" // 文本格式
FormatJSON OutputFormat = "json" // JSON格式
FormatCSV OutputFormat = "csv" // CSV格式
)
// =============================================================================
// 结果类型常量 (从Types.go迁移)
// =============================================================================
// ResultType 定义结果类型
type ResultType string
const (
TypeHost ResultType = "HOST" // 主机存活
TypePort ResultType = "PORT" // 端口开放
TypeService ResultType = "SERVICE" // 服务识别
TypeVuln ResultType = "VULN" // 漏洞发现
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
// 默认缓冲区配置
DefaultBufferSize = 100 // 默认缓冲区大小
DefaultEnableBuffer = true // 默认启用缓冲
DefaultAutoFlush = true // 默认启用自动刷新
DefaultFlushInterval = 5 * time.Second // 默认刷新间隔
// 文件权限常量
DefaultFilePermissions = 0644 // 默认文件权限
DefaultDirPermissions = 0755 // 默认目录权限
// 文件操作标志
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // 默认文件打开标志
)
// =============================================================================
// CSV格式常量 (从Writers.go迁移)
// =============================================================================
var (
// CSV头部字段
CSVHeaders = []string{"Time", "Type", "Target", "Status", "Details"}
)
// =============================================================================
// 时间格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 时间格式
DefaultTimeFormat = "2006-01-02 15:04:05" // 默认时间格式
ISO8601TimeFormat = "2006-01-02T15:04:05Z07:00" // ISO8601时间格式
ISO8601MilliFormat = "2006-01-02T15:04:05.000Z07:00" // ISO8601毫秒格式
)
// GetSupportedTimeFormats 获取支持的时间格式列表
func GetSupportedTimeFormats() []string {
return []string{
DefaultTimeFormat,
ISO8601TimeFormat,
ISO8601MilliFormat,
}
}
// =============================================================================
// JSON常量
// =============================================================================
const (
// JSON格式化
JSONIndentPrefix = "" // JSON缩进前缀
JSONIndentString = " " // JSON缩进字符串
// 空JSON对象
EmptyJSONObject = "{}"
)
// =============================================================================
// 文本格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 文本输出格式
TxtTimeFormat = "2006-01-02 15:04:05" // 文本时间格式
TxtOutputTemplate = "[%s] [%s] %s - %s" // 文本输出模板
TxtDetailsFormat = " (%s)" // 详细信息格式
TxtNewline = "\n" // 换行符
TxtDetailsSeparator = ", " // 详细信息分隔符
TxtKeyValueFormat = "%s=%v" // 键值对格式
)
// =============================================================================
// CSV相关常量
// =============================================================================
const (
CSVMinColumns = 5 // CSV最小列数
CSVHeaderRowIndex = 0 // CSV头部行索引
CSVTimeColumnIndex = 0 // 时间列索引
CSVTypeColumnIndex = 1 // 类型列索引
CSVTargetColumnIndex = 2 // 目标列索引
CSVStatusColumnIndex = 3 // 状态列索引
CSVDetailsColumnIndex = 4 // 详细信息列索引
)

View File

@ -0,0 +1,365 @@
package parsers
import (
"encoding/hex"
"fmt"
"regexp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// CredentialParser 凭据解析器
type CredentialParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
hashRegex *regexp.Regexp
options *CredentialParserOptions
}
// CredentialParserOptions 凭据解析器选项
type CredentialParserOptions struct {
MaxUsernameLength int `json:"max_username_length"`
MaxPasswordLength int `json:"max_password_length"`
AllowEmptyPasswords bool `json:"allow_empty_passwords"`
ValidateHashes bool `json:"validate_hashes"`
DeduplicateUsers bool `json:"deduplicate_users"`
DeduplicatePasswords bool `json:"deduplicate_passwords"`
EnableStatistics bool `json:"enable_statistics"`
}
// DefaultCredentialParserOptions 默认凭据解析器选项
func DefaultCredentialParserOptions() *CredentialParserOptions {
return &CredentialParserOptions{
MaxUsernameLength: DefaultMaxUsernameLength,
MaxPasswordLength: DefaultMaxPasswordLength,
AllowEmptyPasswords: DefaultAllowEmptyPasswords,
ValidateHashes: DefaultValidateHashes,
DeduplicateUsers: DefaultDeduplicateUsers,
DeduplicatePasswords: DefaultDeduplicatePasswords,
EnableStatistics: DefaultCredentialsEnableStatistics,
}
}
// NewCredentialParser 创建凭据解析器
func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser {
if options == nil {
options = DefaultCredentialParserOptions()
}
// 编译哈希验证正则表达式 (MD5: 32位十六进制)
hashRegex := CompiledHashRegex
return &CredentialParser{
fileReader: fileReader,
hashRegex: hashRegex,
options: options,
}
}
// CredentialInput 凭据输入参数
type CredentialInput struct {
// 直接输入
Username string `json:"username"`
Password string `json:"password"`
AddUsers string `json:"add_users"`
AddPasswords string `json:"add_passwords"`
HashValue string `json:"hash_value"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
// 文件输入
UsersFile string `json:"users_file"`
PasswordsFile string `json:"passwords_file"`
HashFile string `json:"hash_file"`
}
// Parse 解析凭据配置
func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "凭据输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Credentials: &CredentialConfig{
SshKeyPath: input.SshKeyPath,
Domain: input.Domain,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析用户名
usernames, userErrors, userWarnings := cp.parseUsernames(input)
errors = append(errors, userErrors...)
warnings = append(warnings, userWarnings...)
// 解析密码
passwords, passErrors, passWarnings := cp.parsePasswords(input)
errors = append(errors, passErrors...)
warnings = append(warnings, passWarnings...)
// 解析哈希值
hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input)
errors = append(errors, hashErrors...)
warnings = append(warnings, hashWarnings...)
// 更新配置
result.Config.Credentials.Usernames = usernames
result.Config.Credentials.Passwords = passwords
result.Config.Credentials.HashValues = hashValues
result.Config.Credentials.HashBytes = hashBytes
// 生成统计信息
if cp.options.EnableStatistics {
result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseUsernames 解析用户名
func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) {
var usernames []string
var errors []error
var warnings []string
// 解析命令行用户名
if input.Username != "" {
users := strings.Split(input.Username, ",")
for _, user := range users {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypeUsernameError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取用户名
if input.UsersFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.UsersFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取用户名文件失败", input.UsersFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedUser, valid, err := cp.validateUsername(line); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外用户名
if input.AddUsers != "" {
extraUsers := strings.Split(input.AddUsers, ",")
for _, user := range extraUsers {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicateUsers {
usernames = cp.removeDuplicateStrings(usernames)
}
return usernames, errors, warnings
}
// parsePasswords 解析密码
func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) {
var passwords []string
var errors []error
var warnings []string
// 解析命令行密码
if input.Password != "" {
passes := strings.Split(input.Password, ",")
for _, pass := range passes {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypePasswordError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取密码
if input.PasswordsFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取密码文件失败", input.PasswordsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedPass, valid, err := cp.validatePassword(line); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外密码
if input.AddPasswords != "" {
extraPasses := strings.Split(input.AddPasswords, ",")
for _, pass := range extraPasses {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicatePasswords {
passwords = cp.removeDuplicateStrings(passwords)
}
return passwords, errors, warnings
}
// parseHashes 解析哈希值
func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) {
var hashValues []string
var hashBytes [][]byte
var errors []error
var warnings []string
// 解析单个哈希值
if input.HashValue != "" {
if valid, err := cp.validateHash(input.HashValue); valid {
hashValues = append(hashValues, input.HashValue)
} else {
errors = append(errors, NewParseError(ErrorTypeHashError, err.Error(), "command line", 0, err))
}
}
// 从文件读取哈希值
if input.HashFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.HashFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取哈希文件失败", input.HashFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := cp.validateHash(line); valid {
hashValues = append(hashValues, line)
} else {
warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 转换哈希值为字节数组
for _, hash := range hashValues {
if hashByte, err := hex.DecodeString(hash); err == nil {
hashBytes = append(hashBytes, hashByte)
} else {
warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash))
}
}
return hashValues, hashBytes, errors, warnings
}
// validateUsername 验证用户名
func (cp *CredentialParser) validateUsername(username string) (string, bool, error) {
if len(username) == 0 {
return "", false, nil // 允许空用户名,但不添加到列表
}
if len(username) > cp.options.MaxUsernameLength {
return "", false, fmt.Errorf(i18n.GetText("parser_username_too_long"), len(username), cp.options.MaxUsernameLength)
}
// 检查特殊字符
if strings.ContainsAny(username, InvalidUsernameChars) {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_username_invalid_chars"))
}
return username, true, nil
}
// validatePassword 验证密码
func (cp *CredentialParser) validatePassword(password string) (string, bool, error) {
if len(password) == 0 && !cp.options.AllowEmptyPasswords {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_password_empty"))
}
if len(password) > cp.options.MaxPasswordLength {
return "", false, fmt.Errorf(i18n.GetText("parser_password_too_long"), len(password), cp.options.MaxPasswordLength)
}
return password, true, nil
}
// validateHash 验证哈希值
func (cp *CredentialParser) validateHash(hash string) (bool, error) {
if !cp.options.ValidateHashes {
return true, nil
}
hash = strings.TrimSpace(hash)
if len(hash) == 0 {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_empty"))
}
if !cp.hashRegex.MatchString(hash) {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_invalid_format"))
}
return true, nil
}
// removeDuplicateStrings 去重字符串切片
func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats {
return &CredentialStats{
TotalUsernames: len(usernames),
TotalPasswords: len(passwords),
TotalHashes: len(hashValues),
UniqueUsernames: len(cp.removeDuplicateStrings(usernames)),
UniquePasswords: len(cp.removeDuplicateStrings(passwords)),
ValidHashes: len(hashBytes),
InvalidHashes: len(hashValues) - len(hashBytes),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -0,0 +1,293 @@
package parsers
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// FileReader 高性能文件读取器
type FileReader struct {
mu sync.RWMutex
cache map[string]*FileResult // 文件缓存
maxCacheSize int // 最大缓存大小
enableCache bool // 是否启用缓存
maxFileSize int64 // 最大文件大小
timeout time.Duration // 读取超时
enableValidation bool // 是否启用内容验证
}
// FileResult 文件读取结果
type FileResult struct {
Lines []string `json:"lines"`
Source *FileSource `json:"source"`
ReadTime time.Duration `json:"read_time"`
ValidLines int `json:"valid_lines"`
Errors []error `json:"errors,omitempty"`
Cached bool `json:"cached"`
}
// NewFileReader 创建文件读取器
func NewFileReader(options *FileReaderOptions) *FileReader {
if options == nil {
options = DefaultFileReaderOptions()
}
return &FileReader{
cache: make(map[string]*FileResult),
maxCacheSize: options.MaxCacheSize,
enableCache: options.EnableCache,
maxFileSize: options.MaxFileSize,
timeout: options.Timeout,
enableValidation: options.EnableValidation,
}
}
// FileReaderOptions 文件读取器选项
type FileReaderOptions struct {
MaxCacheSize int // 最大缓存文件数
EnableCache bool // 启用文件缓存
MaxFileSize int64 // 最大文件大小(字节)
Timeout time.Duration // 读取超时
EnableValidation bool // 启用内容验证
TrimSpace bool // 自动清理空白字符
SkipEmpty bool // 跳过空行
SkipComments bool // 跳过注释行(#开头)
}
// DefaultFileReaderOptions 默认文件读取器选项
func DefaultFileReaderOptions() *FileReaderOptions {
return &FileReaderOptions{
MaxCacheSize: DefaultMaxCacheSize,
EnableCache: DefaultEnableCache,
MaxFileSize: DefaultFileReaderMaxFileSize,
Timeout: DefaultFileReaderTimeout,
EnableValidation: DefaultFileReaderEnableValidation,
TrimSpace: DefaultTrimSpace,
SkipEmpty: DefaultSkipEmpty,
SkipComments: DefaultSkipComments,
}
}
// ReadFile 读取文件内容
func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (*FileResult, error) {
if filename == "" {
return nil, NewParseError("FILE_ERROR", "文件名为空", filename, 0, ErrEmptyInput)
}
// 检查缓存
if fr.enableCache {
if result := fr.getFromCache(filename); result != nil {
result.Cached = true
return result, nil
}
}
// 合并选项
opts := fr.mergeOptions(options...)
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), fr.timeout)
defer cancel()
// 异步读取文件
resultChan := make(chan *FileResult, 1)
errorChan := make(chan error, 1)
go func() {
result, err := fr.readFileSync(filename, opts)
if err != nil {
errorChan <- err
} else {
resultChan <- result
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
// 添加到缓存
if fr.enableCache {
fr.addToCache(filename, result)
}
return result, nil
case err := <-errorChan:
return nil, err
case <-ctx.Done():
return nil, NewParseError(ErrorTypeTimeout, "文件读取超时", filename, 0, ctx.Err())
}
}
// =============================================================================================
// 已删除的死代码未使用ReadFiles 并发读取多个文件的方法
// =============================================================================================
// readFileSync 同步读取文件
func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) {
startTime := time.Now()
// 检查文件
fileInfo, err := os.Stat(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "文件不存在或无法访问", filename, 0, err)
}
// 检查文件大小
if fileInfo.Size() > fr.maxFileSize {
return nil, NewParseError("FILE_ERROR",
fmt.Sprintf("文件过大: %d bytes, 最大限制: %d bytes", fileInfo.Size(), fr.maxFileSize),
filename, 0, nil)
}
// 打开文件
file, err := os.Open(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "无法打开文件", filename, 0, err)
}
defer file.Close()
// 创建结果
result := &FileResult{
Lines: make([]string, 0),
Source: &FileSource{
Path: filename,
Size: fileInfo.Size(),
ModTime: fileInfo.ModTime(),
},
}
// 读取文件内容
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
lineNum := 0
validLines := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
// 处理行内容
if processedLine, valid := fr.processLine(line, options); valid {
result.Lines = append(result.Lines, processedLine)
validLines++
}
}
// 检查扫描错误
if err := scanner.Err(); err != nil {
return nil, NewParseError(ErrorTypeReadError, i18n.GetText("parser_file_scan_failed"), filename, lineNum, err)
}
// 更新统计信息
result.Source.LineCount = lineNum
result.Source.ValidLines = validLines
result.ValidLines = validLines
result.ReadTime = time.Since(startTime)
return result, nil
}
// processLine 处理单行内容
func (fr *FileReader) processLine(line string, options *FileReaderOptions) (string, bool) {
// 清理空白字符
if options.TrimSpace {
line = strings.TrimSpace(line)
}
// 跳过空行
if options.SkipEmpty && line == "" {
return "", false
}
// 跳过注释行
if options.SkipComments && strings.HasPrefix(line, CommentPrefix) {
return "", false
}
// 内容验证
if options.EnableValidation && fr.enableValidation {
if !fr.validateLine(line) {
return "", false
}
}
return line, true
}
// validateLine 验证行内容
func (fr *FileReader) validateLine(line string) bool {
// 基本验证:检查是否包含特殊字符或过长
if len(line) > MaxLineLength { // 单行最大字符数
return false
}
// 检查是否包含控制字符
for _, r := range line {
if r < MaxValidRune && r != TabRune && r != NewlineRune && r != CarriageReturnRune { // 排除tab、换行、回车
return false
}
}
return true
}
// mergeOptions 合并选项
func (fr *FileReader) mergeOptions(options ...*FileReaderOptions) *FileReaderOptions {
opts := DefaultFileReaderOptions()
if len(options) > 0 && options[0] != nil {
opts = options[0]
}
return opts
}
// getFromCache 从缓存获取结果
func (fr *FileReader) getFromCache(filename string) *FileResult {
fr.mu.RLock()
defer fr.mu.RUnlock()
if result, exists := fr.cache[filename]; exists {
// 检查文件是否有更新
if fileInfo, err := os.Stat(filename); err == nil {
if fileInfo.ModTime().After(result.Source.ModTime) {
// 文件已更新,从缓存中移除
delete(fr.cache, filename)
return nil
}
}
return result
}
return nil
}
// addToCache 添加到缓存
func (fr *FileReader) addToCache(filename string, result *FileResult) {
fr.mu.Lock()
defer fr.mu.Unlock()
// 检查缓存大小
if len(fr.cache) >= fr.maxCacheSize {
// 移除最旧的条目简单的LRU策略
var oldestFile string
var oldestTime time.Time
for file, res := range fr.cache {
if oldestFile == "" || res.Source.ModTime.Before(oldestTime) {
oldestFile = file
oldestTime = res.Source.ModTime
}
}
delete(fr.cache, oldestFile)
}
fr.cache[filename] = result
}
// =============================================================================================
// 已删除的死代码未使用ClearCache 和 GetCacheStats 方法
// =============================================================================================

View File

@ -0,0 +1,369 @@
package parsers
import (
"fmt"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// NetworkParser 网络配置解析器
type NetworkParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *NetworkParserOptions
}
// NetworkParserOptions 网络解析器选项
type NetworkParserOptions struct {
ValidateProxies bool `json:"validate_proxies"`
AllowInsecure bool `json:"allow_insecure"`
DefaultTimeout time.Duration `json:"default_timeout"`
DefaultWebTimeout time.Duration `json:"default_web_timeout"`
DefaultUserAgent string `json:"default_user_agent"`
}
// DefaultNetworkParserOptions 默认网络解析器选项
func DefaultNetworkParserOptions() *NetworkParserOptions {
return &NetworkParserOptions{
ValidateProxies: DefaultValidateProxies,
AllowInsecure: DefaultAllowInsecure,
DefaultTimeout: DefaultNetworkTimeout,
DefaultWebTimeout: DefaultWebTimeout,
DefaultUserAgent: DefaultUserAgent,
}
}
// NewNetworkParser 创建网络配置解析器
func NewNetworkParser(options *NetworkParserOptions) *NetworkParser {
if options == nil {
options = DefaultNetworkParserOptions()
}
return &NetworkParser{
options: options,
}
}
// NetworkInput 网络配置输入参数
type NetworkInput struct {
// 代理配置
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
// 超时配置
Timeout int64 `json:"timeout"`
WebTimeout int64 `json:"web_timeout"`
// 网络选项
DisablePing bool `json:"disable_ping"`
DnsLog bool `json:"dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// Parse 解析网络配置
func (np *NetworkParser) Parse(input *NetworkInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError("INPUT_ERROR", "网络配置输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Network: &NetworkConfig{
EnableDNSLog: input.DnsLog,
DisablePing: input.DisablePing,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析HTTP代理
httpProxy, httpErrors, httpWarnings := np.parseHttpProxy(input.HttpProxy)
errors = append(errors, httpErrors...)
warnings = append(warnings, httpWarnings...)
// 解析Socks5代理
socks5Proxy, socks5Errors, socks5Warnings := np.parseSocks5Proxy(input.Socks5Proxy)
errors = append(errors, socks5Errors...)
warnings = append(warnings, socks5Warnings...)
// 解析超时配置
timeout, webTimeout, timeoutErrors, timeoutWarnings := np.parseTimeouts(input.Timeout, input.WebTimeout)
errors = append(errors, timeoutErrors...)
warnings = append(warnings, timeoutWarnings...)
// 解析用户代理
userAgent, uaErrors, uaWarnings := np.parseUserAgent(input.UserAgent)
errors = append(errors, uaErrors...)
warnings = append(warnings, uaWarnings...)
// 解析Cookie
cookie, cookieErrors, cookieWarnings := np.parseCookie(input.Cookie)
errors = append(errors, cookieErrors...)
warnings = append(warnings, cookieWarnings...)
// 检查代理冲突
if httpProxy != "" && socks5Proxy != "" {
warnings = append(warnings, "同时配置了HTTP代理和Socks5代理Socks5代理将被优先使用")
}
// 更新配置
result.Config.Network.HttpProxy = httpProxy
result.Config.Network.Socks5Proxy = socks5Proxy
result.Config.Network.Timeout = timeout
result.Config.Network.WebTimeout = webTimeout
result.Config.Network.UserAgent = userAgent
result.Config.Network.Cookie = cookie
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHttpProxy 解析HTTP代理配置
func (np *NetworkParser) parseHttpProxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeHttpProxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "http_proxy", 0, err))
return "", errors, warnings
}
}
return normalizedProxy, errors, warnings
}
// parseSocks5Proxy 解析Socks5代理配置
func (np *NetworkParser) parseSocks5Proxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeSocks5Proxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "socks5_proxy", 0, err))
return "", errors, warnings
}
}
// 使用Socks5代理时建议禁用Ping
if normalizedProxy != "" {
warnings = append(warnings, "使用Socks5代理时建议禁用Ping检测")
}
return normalizedProxy, errors, warnings
}
// parseTimeouts 解析超时配置
func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration, time.Duration, []error, []string) {
var errors []error
var warnings []string
// 处理普通超时
finalTimeout := np.options.DefaultTimeout
if timeout > 0 {
if timeout > MaxTimeoutSeconds {
warnings = append(warnings, "超时时间过长建议不超过300秒")
}
finalTimeout = time.Duration(timeout) * time.Second
}
// 处理Web超时
finalWebTimeout := np.options.DefaultWebTimeout
if webTimeout > 0 {
if webTimeout > MaxWebTimeoutSeconds {
warnings = append(warnings, "Web超时时间过长建议不超过120秒")
}
finalWebTimeout = time.Duration(webTimeout) * time.Second
}
// 验证超时配置合理性
if finalWebTimeout > finalTimeout {
warnings = append(warnings, i18n.GetText("config_web_timeout_warning"))
}
return finalTimeout, finalWebTimeout, errors, warnings
}
// parseUserAgent 解析用户代理
func (np *NetworkParser) parseUserAgent(userAgent string) (string, []error, []string) {
var errors []error
var warnings []string
if userAgent == "" {
return np.options.DefaultUserAgent, errors, warnings
}
// 基本格式验证
if len(userAgent) > MaxUserAgentLength {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理字符串过长", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否包含特殊字符
if strings.ContainsAny(userAgent, InvalidUserAgentChars) {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理包含非法字符", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否为常见浏览器用户代理
if !np.isValidUserAgent(userAgent) {
warnings = append(warnings, "用户代理格式可能不被目标服务器识别")
}
return userAgent, errors, warnings
}
// parseCookie 解析Cookie
func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string) {
var errors []error
var warnings []string
if cookie == "" {
return "", errors, warnings
}
// 基本格式验证
if len(cookie) > MaxCookieLength { // HTTP Cookie长度限制
errors = append(errors, NewParseError(ErrorTypeCookieError, "Cookie字符串过长", "cookie", 0, nil))
return "", errors, warnings
}
// 检查Cookie格式
if !np.isValidCookie(cookie) {
warnings = append(warnings, "Cookie格式可能不正确")
}
return cookie, errors, warnings
}
// normalizeHttpProxy 规范化HTTP代理URL
func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
switch strings.ToLower(proxy) {
case ProxyShortcut1:
return ProxyShortcutHTTP
case ProxyShortcut2:
return ProxyShortcutSOCKS5
default:
// 如果没有协议前缀默认使用HTTP
if !strings.Contains(proxy, ProtocolPrefix) {
if strings.Contains(proxy, ":") {
return HTTPPrefix + proxy
} else {
return HTTPPrefix + "127.0.0.1:" + proxy
}
}
return proxy
}
}
// normalizeSocks5Proxy 规范化Socks5代理URL
func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string {
// 如果没有协议前缀添加SOCKS5协议
if !strings.HasPrefix(proxy, SOCKS5Prefix) {
if strings.Contains(proxy, ":") {
return SOCKS5Prefix + proxy
} else {
return SOCKS5Prefix + "127.0.0.1:" + proxy
}
}
return proxy
}
// validateProxyURL 验证代理URL格式
func (np *NetworkParser) validateProxyURL(proxyURL string) error {
if proxyURL == "" {
return nil
}
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return fmt.Errorf("代理URL格式无效: %v", err)
}
// 检查协议
switch parsedURL.Scheme {
case ProtocolHTTP, ProtocolHTTPS, ProtocolSOCKS5:
// 支持的协议
default:
return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
}
// 检查主机名
if parsedURL.Hostname() == "" {
return fmt.Errorf("代理主机名为空")
}
// 检查端口
portStr := parsedURL.Port()
if portStr != "" {
port, err := strconv.Atoi(portStr)
if err != nil {
return fmt.Errorf("代理端口号无效: %s", portStr)
}
if port < 1 || port > 65535 {
return fmt.Errorf("代理端口号超出范围: %d", port)
}
}
// 安全检查
if !np.options.AllowInsecure && parsedURL.Scheme == ProtocolHTTP {
return fmt.Errorf("不允许使用不安全的HTTP代理")
}
return nil
}
// isValidUserAgent 检查用户代理是否有效
func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
// 检查是否包含常见的浏览器标识
commonBrowsers := GetCommonBrowsers()
userAgentLower := strings.ToLower(userAgent)
for _, browser := range commonBrowsers {
if strings.Contains(userAgentLower, strings.ToLower(browser)) {
return true
}
}
return false
}
// isValidCookie 检查Cookie格式是否有效
func (np *NetworkParser) isValidCookie(cookie string) bool {
// 基本Cookie格式检查 (name=value; name2=value2)
return CompiledCookieRegex.MatchString(strings.TrimSpace(cookie))
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

370
Common/parsers/Simple.go Normal file
View File

@ -0,0 +1,370 @@
package parsers
import (
"bufio"
"fmt"
"net"
"os"
"sort"
"strconv"
"strings"
)
/*
Simple.go - 简化版本的解析器函数
这个文件提供了简化但功能完整的解析函数用于替代复杂的解析器架构
保持与现有代码的接口兼容性但大幅简化实现逻辑
*/
// =============================================================================
// 简化的IP/主机解析函数
// =============================================================================
// ParseIP 解析各种格式的IP地址
// 支持单个IP、IP范围、CIDR和文件输入
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
var hosts []string
// 如果提供了文件名,从文件读取主机列表
if filename != "" {
fileHosts, fileErr := readHostsFromFile(filename)
if fileErr != nil {
return nil, fmt.Errorf("读取主机文件失败: %v", fileErr)
}
hosts = append(hosts, fileHosts...)
}
// 解析主机参数
if host != "" {
hostList, hostErr := parseHostString(host)
if hostErr != nil {
return nil, fmt.Errorf("解析主机失败: %v", hostErr)
}
hosts = append(hosts, hostList...)
}
// 处理排除主机
if len(nohosts) > 0 && nohosts[0] != "" {
excludeList, excludeErr := parseHostString(nohosts[0])
if excludeErr != nil {
return nil, fmt.Errorf("解析排除主机失败: %v", excludeErr)
}
hosts = excludeHosts(hosts, excludeList)
}
// 去重和排序
hosts = removeDuplicates(hosts)
sort.Strings(hosts)
if len(hosts) == 0 {
return nil, fmt.Errorf("没有找到有效的主机")
}
return hosts, nil
}
// =============================================================================
// 简化的端口解析函数
// =============================================================================
// ParsePort 解析端口配置字符串为端口号列表
// 保持与 ParsePort 的接口兼容性
func ParsePort(ports string) []int {
if ports == "" {
return nil
}
var result []int
// 处理预定义端口组
ports = expandPortGroups(ports)
// 按逗号分割
for _, portStr := range strings.Split(ports, ",") {
portStr = strings.TrimSpace(portStr)
if portStr == "" {
continue
}
// 处理端口范围 (如 1-100)
if strings.Contains(portStr, "-") {
rangePorts := parsePortRange(portStr)
result = append(result, rangePorts...)
} else {
// 单个端口
if port, err := strconv.Atoi(portStr); err == nil {
if port >= MinPort && port <= MaxPort {
result = append(result, port)
}
}
}
}
// 去重和排序
result = removeDuplicatePorts(result)
sort.Ints(result)
return result
}
// ParsePortsFromString 从字符串解析端口列表
// 保持与 ParsePortsFromString 的接口兼容性
func ParsePortsFromString(portsStr string) []int {
return ParsePort(portsStr)
}
// =============================================================================
// 辅助函数
// =============================================================================
// readHostsFromFile 从文件读取主机列表
func readHostsFromFile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var hosts []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, CommentPrefix) {
hosts = append(hosts, line)
}
}
return hosts, scanner.Err()
}
// parseHostString 解析主机字符串
func parseHostString(host string) ([]string, error) {
var hosts []string
// 按逗号分割多个主机
for _, h := range strings.Split(host, ",") {
h = strings.TrimSpace(h)
if h == "" {
continue
}
// 检查是否为CIDR格式
if strings.Contains(h, "/") {
cidrHosts, err := parseCIDR(h)
if err != nil {
return nil, fmt.Errorf("解析CIDR %s 失败: %v", h, err)
}
hosts = append(hosts, cidrHosts...)
} else if strings.Contains(h, "-") && !strings.Contains(h, ":") {
// IP范围格式 (如 192.168.1.1-10)
rangeHosts, err := parseIPRange(h)
if err != nil {
return nil, fmt.Errorf("解析IP范围 %s 失败: %v", h, err)
}
hosts = append(hosts, rangeHosts...)
} else {
// 单个主机
hosts = append(hosts, h)
}
}
return hosts, nil
}
// parseCIDR 解析CIDR格式的网段
func parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var hosts []string
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); nextIP(ip) {
hosts = append(hosts, ip.String())
// 限制最大主机数量,防止内存溢出
if len(hosts) > SimpleMaxHosts {
break
}
}
// 移除网络地址和广播地址
if len(hosts) > 2 {
hosts = hosts[1 : len(hosts)-1]
}
return hosts, nil
}
// parseIPRange 解析IP范围
func parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式: %s", rangeStr)
}
startIP := strings.TrimSpace(parts[0])
endPart := strings.TrimSpace(parts[1])
// 检查是否为短格式 (如 192.168.1.1-10)
if !strings.Contains(endPart, ".") {
return parseShortIPRange(startIP, endPart)
}
// 完整IP范围
return parseFullIPRange(startIP, endPart)
}
// parseShortIPRange 解析短格式IP范围
func parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP: %s", startIPStr)
}
endNum, err := strconv.Atoi(endSuffix)
if err != nil {
return nil, fmt.Errorf("无效的结束数字: %s", endSuffix)
}
var hosts []string
startIPParts := strings.Split(startIPStr, ".")
if len(startIPParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP格式: %s", startIPStr)
}
baseIP := strings.Join(startIPParts[:3], ".")
startNum, _ := strconv.Atoi(startIPParts[3])
for i := startNum; i <= endNum && i <= RouterSwitchLastOctet; i++ {
hosts = append(hosts, fmt.Sprintf("%s.%d", baseIP, i))
}
return hosts, nil
}
// parseFullIPRange 解析完整IP范围
func parseFullIPRange(startIPStr, endIPStr string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
endIP := net.ParseIP(endIPStr)
if startIP == nil || endIP == nil {
return nil, fmt.Errorf("无效的IP地址")
}
var hosts []string
for ip := make(net.IP, len(startIP)); ; nextIP(ip) {
copy(ip, startIP)
hosts = append(hosts, ip.String())
if ip.Equal(endIP) {
break
}
// 限制最大主机数量
if len(hosts) > SimpleMaxHosts {
break
}
nextIP(startIP)
}
return hosts, nil
}
// parsePortRange 解析端口范围
func parsePortRange(rangeStr string) []int {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil
}
start, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil || start < MinPort || end > MaxPort || start > end {
return nil
}
var ports []int
for i := start; i <= end; i++ {
ports = append(ports, i)
}
return ports
}
// expandPortGroups 展开端口组
func expandPortGroups(ports string) string {
// 使用预定义的端口组
portGroups := GetPortGroups()
result := ports
for group, portList := range portGroups {
result = strings.ReplaceAll(result, group, portList)
}
return result
}
// excludeHosts 排除指定的主机
func excludeHosts(hosts, excludeList []string) []string {
if len(excludeList) == 0 {
return hosts
}
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, found := excludeMap[host]; !found {
result = append(result, host)
}
}
return result
}
// removeDuplicates 去除字符串重复项
func removeDuplicates(slice []string) []string {
keys := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去除端口重复项
func removeDuplicatePorts(slice []int) []int {
keys := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// nextIP 计算下一个IP地址
func nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

View File

@ -0,0 +1,866 @@
package parsers
import (
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// TargetParser 目标解析器
type TargetParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
ipRegex *regexp.Regexp
portRegex *regexp.Regexp
urlRegex *regexp.Regexp
options *TargetParserOptions
}
// TargetParserOptions 目标解析器选项
type TargetParserOptions struct {
MaxTargets int `json:"max_targets"`
MaxPortRange int `json:"max_port_range"`
AllowPrivateIPs bool `json:"allow_private_ips"`
AllowLoopback bool `json:"allow_loopback"`
ValidateURLs bool `json:"validate_urls"`
ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"`
DefaultPorts string `json:"default_ports"`
}
// DefaultTargetParserOptions 默认目标解析器选项
func DefaultTargetParserOptions() *TargetParserOptions {
return &TargetParserOptions{
MaxTargets: DefaultTargetMaxTargets,
MaxPortRange: DefaultMaxPortRange,
AllowPrivateIPs: DefaultAllowPrivateIPs,
AllowLoopback: DefaultAllowLoopback,
ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: DefaultPorts,
}
}
// NewTargetParser 创建目标解析器
func NewTargetParser(fileReader *FileReader, options *TargetParserOptions) *TargetParser {
if options == nil {
options = DefaultTargetParserOptions()
}
// 使用预编译的正则表达式
ipRegex := CompiledIPv4Regex
portRegex := CompiledPortRegex
urlRegex := CompiledURLRegex
return &TargetParser{
fileReader: fileReader,
ipRegex: ipRegex,
portRegex: portRegex,
urlRegex: urlRegex,
options: options,
}
}
// TargetInput 目标输入参数
type TargetInput struct {
// 主机相关
Host string `json:"host"`
HostsFile string `json:"hosts_file"`
ExcludeHosts string `json:"exclude_hosts"`
// 端口相关
Ports string `json:"ports"`
PortsFile string `json:"ports_file"`
AddPorts string `json:"add_ports"`
ExcludePorts string `json:"exclude_ports"`
// URL相关
TargetURL string `json:"target_url"`
URLsFile string `json:"urls_file"`
// 主机端口组合
HostPort []string `json:"host_port"`
// 模式标识
LocalMode bool `json:"local_mode"`
}
// Parse 解析目标配置
func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "目标输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Targets: &TargetConfig{
LocalMode: input.LocalMode,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析主机
hosts, hostErrors, hostWarnings := tp.parseHosts(input)
errors = append(errors, hostErrors...)
warnings = append(warnings, hostWarnings...)
// 解析URL
urls, urlErrors, urlWarnings := tp.parseURLs(input)
errors = append(errors, urlErrors...)
warnings = append(warnings, urlWarnings...)
// 解析端口
ports, portErrors, portWarnings := tp.parsePorts(input)
errors = append(errors, portErrors...)
warnings = append(warnings, portWarnings...)
// 解析排除端口
excludePorts, excludeErrors, excludeWarnings := tp.parseExcludePorts(input)
errors = append(errors, excludeErrors...)
warnings = append(warnings, excludeWarnings...)
// 解析主机端口组合
hostPorts, hpErrors, hpWarnings := tp.parseHostPorts(input)
errors = append(errors, hpErrors...)
warnings = append(warnings, hpWarnings...)
// 更新配置
result.Config.Targets.Hosts = hosts
result.Config.Targets.URLs = urls
result.Config.Targets.Ports = ports
result.Config.Targets.ExcludePorts = excludePorts
result.Config.Targets.HostPorts = hostPorts
// 生成统计信息
if tp.options.EnableStatistics {
result.Config.Targets.Statistics = tp.generateStatistics(hosts, urls, ports, excludePorts)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHosts 解析主机
func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []string) {
var hosts []string
var errors []error
var warnings []string
// 解析命令行主机
if input.Host != "" {
hostList, err := tp.parseHostList(input.Host)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeHostError, err.Error(), "command line", 0, err))
} else {
hosts = append(hosts, hostList...)
}
}
// 从文件读取主机
if input.HostsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.HostsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取主机文件失败", input.HostsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
hostList, err := tp.parseHostList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("主机文件第%d行解析失败: %s", i+1, err.Error()))
} else {
hosts = append(hosts, hostList...)
}
}
}
}
// 处理排除主机
if input.ExcludeHosts != "" {
excludeList, err := tp.parseHostList(input.ExcludeHosts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("排除主机解析失败: %s", err.Error()))
} else {
hosts = tp.excludeHosts(hosts, excludeList)
}
}
// 去重和验证
hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts))
for _, host := range hosts {
if valid, err := tp.validateHost(host); valid {
validHosts = append(validHosts, host)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
}
}
// 检查目标数量限制
if len(validHosts) > tp.options.MaxTargets {
warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets))
validHosts = validHosts[:tp.options.MaxTargets]
}
return validHosts, errors, warnings
}
// parseURLs 解析URL
func (tp *TargetParser) parseURLs(input *TargetInput) ([]string, []error, []string) {
var urls []string
var errors []error
var warnings []string
// 解析命令行URL
if input.TargetURL != "" {
urlList := strings.Split(input.TargetURL, ",")
for _, rawURL := range urlList {
rawURL = strings.TrimSpace(rawURL)
if rawURL != "" {
if valid, err := tp.validateURL(rawURL); valid {
urls = append(urls, rawURL)
} else {
warnings = append(warnings, fmt.Sprintf("无效URL: %s - %s", rawURL, err.Error()))
}
}
}
}
// 从文件读取URL
if input.URLsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.URLsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取URL文件失败", input.URLsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := tp.validateURL(line); valid {
urls = append(urls, line)
} else {
warnings = append(warnings, fmt.Sprintf("URL文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 去重
urls = tp.removeDuplicateStrings(urls)
return urls, errors, warnings
}
// parsePorts 解析端口
func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string) {
var ports []int
var errors []error
var warnings []string
// 解析命令行端口
if input.Ports != "" {
portList, err := tp.parsePortList(input.Ports)
if err != nil {
errors = append(errors, NewParseError(ErrorTypePortError, err.Error(), "command line", 0, err))
} else {
ports = append(ports, portList...)
}
}
// 从文件读取端口
if input.PortsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.PortsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取端口文件失败", input.PortsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
portList, err := tp.parsePortList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("端口文件第%d行解析失败: %s", i+1, err.Error()))
} else {
ports = append(ports, portList...)
}
}
}
}
// 处理额外端口
if input.AddPorts != "" {
addPortList, err := tp.parsePortList(input.AddPorts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("额外端口解析失败: %s", err.Error()))
} else {
ports = append(ports, addPortList...)
}
}
// 去重和排序
ports = tp.removeDuplicatePorts(ports)
return ports, errors, warnings
}
// parseExcludePorts 解析排除端口
func (tp *TargetParser) parseExcludePorts(input *TargetInput) ([]int, []error, []string) {
var excludePorts []int
var errors []error
var warnings []string
if input.ExcludePorts != "" {
portList, err := tp.parsePortList(input.ExcludePorts)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeExcludePortError, err.Error(), "command line", 0, err))
} else {
excludePorts = portList
}
}
return excludePorts, errors, warnings
}
// parseHostPorts 解析主机端口组合
func (tp *TargetParser) parseHostPorts(input *TargetInput) ([]string, []error, []string) {
var hostPorts []string
var errors []error
var warnings []string
for _, hp := range input.HostPort {
if hp != "" {
if valid, err := tp.validateHostPort(hp); valid {
hostPorts = append(hostPorts, hp)
} else {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", hp, err.Error()))
}
}
}
return hostPorts, errors, warnings
}
// parseHostList 解析主机列表
func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
if hostStr == "" {
return nil, nil
}
var hosts []string
hostItems := strings.Split(hostStr, ",")
for _, item := range hostItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
// 检查各种IP格式
switch {
case item == PrivateNetwork192:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork192CIDR)
if err != nil {
return nil, fmt.Errorf("192网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork172:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork172CIDR)
if err != nil {
return nil, fmt.Errorf("172网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork10:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork10CIDR)
if err != nil {
return nil, fmt.Errorf("10网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case strings.HasSuffix(item, "/8"):
// 处理/8网段使用采样方式
sampledHosts := tp.parseSubnet8(item)
hosts = append(hosts, sampledHosts...)
case strings.Contains(item, "/"):
// CIDR表示法
cidrHosts, err := tp.parseCIDR(item)
if err != nil {
return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err)
}
hosts = append(hosts, cidrHosts...)
case strings.Contains(item, "-"):
// IP范围表示法
rangeHosts, err := tp.parseIPRange(item)
if err != nil {
return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err)
}
hosts = append(hosts, rangeHosts...)
default:
// 单个IP或域名
hosts = append(hosts, item)
}
}
return hosts, nil
}
// parsePortList 解析端口列表,支持预定义端口组
func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
if portStr == "" {
return nil, nil
}
// 检查是否为预定义端口组
portStr = tp.expandPortGroups(portStr)
var ports []int
portItems := strings.Split(portStr, ",")
for _, item := range portItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
if strings.Contains(item, "-") {
// 端口范围
rangePorts, err := tp.parsePortRange(item)
if err != nil {
return nil, fmt.Errorf("端口范围解析失败 %s: %v", item, err)
}
// 检查范围大小
if len(rangePorts) > tp.options.MaxPortRange {
return nil, fmt.Errorf("端口范围过大: %d, 最大允许: %d", len(rangePorts), tp.options.MaxPortRange)
}
ports = append(ports, rangePorts...)
} else {
// 单个端口
port, err := strconv.Atoi(item)
if err != nil {
return nil, fmt.Errorf("无效端口号: %s", item)
}
if port < MinPort || port > MaxPort {
return nil, fmt.Errorf("端口号超出范围: %d", port)
}
ports = append(ports, port)
}
}
return ports, nil
}
// expandPortGroups 展开预定义端口组
func (tp *TargetParser) expandPortGroups(portStr string) string {
// 使用预定义的端口组
portGroups := GetTargetPortGroups()
if expandedPorts, exists := portGroups[portStr]; exists {
return expandedPorts
}
return portStr
}
// parseCIDR 解析CIDR网段
func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
ip := make(net.IP, len(ipNet.IP))
copy(ip, ipNet.IP)
count := 0
for ipNet.Contains(ip) {
ips = append(ips, ip.String())
count++
// 防止生成过多IP
if count >= tp.options.MaxTargets {
break
}
tp.nextIP(ip)
}
return ips, nil
}
// parseIPRange 解析IP范围支持简写格式
func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式")
}
startIPStr := strings.TrimSpace(parts[0])
endIPStr := strings.TrimSpace(parts[1])
// 验证起始IP
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP地址: %s", startIPStr)
}
// 处理简写格式 (如: 192.168.1.1-100)
if len(endIPStr) < 4 || !strings.Contains(endIPStr, ".") {
return tp.parseShortIPRange(startIPStr, endIPStr)
}
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
endIP := net.ParseIP(endIPStr)
if endIP == nil {
return nil, fmt.Errorf("无效的结束IP地址: %s", endIPStr)
}
return tp.parseFullIPRange(startIP, endIP)
}
// parseShortIPRange 解析简写格式的IP范围
func (tp *TargetParser) parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
// 将结束段转换为数字
endNum, err := strconv.Atoi(endSuffix)
if err != nil || endNum > MaxIPv4OctetValue {
return nil, fmt.Errorf("无效的IP范围结束值: %s", endSuffix)
}
// 分解起始IP
ipParts := strings.Split(startIPStr, ".")
if len(ipParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP地址格式: %s", startIPStr)
}
// 获取前缀和起始IP的最后一部分
prefixIP := strings.Join(ipParts[0:3], ".")
startNum, err := strconv.Atoi(ipParts[3])
if err != nil || startNum > endNum {
return nil, fmt.Errorf("无效的IP范围: %s-%s", startIPStr, endSuffix)
}
// 生成IP范围
var allIP []string
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
}
return allIP, nil
}
// parseFullIPRange 解析完整格式的IP范围
func (tp *TargetParser) parseFullIPRange(startIP, endIP net.IP) ([]string, error) {
// 转换为IPv4
start4 := startIP.To4()
end4 := endIP.To4()
if start4 == nil || end4 == nil {
return nil, fmt.Errorf("仅支持IPv4地址范围")
}
// 计算IP地址的整数表示
startInt := (int(start4[0]) << IPFirstOctetShift) | (int(start4[1]) << IPSecondOctetShift) | (int(start4[2]) << IPThirdOctetShift) | int(start4[3])
endInt := (int(end4[0]) << IPFirstOctetShift) | (int(end4[1]) << IPSecondOctetShift) | (int(end4[2]) << IPThirdOctetShift) | int(end4[3])
// 检查范围的有效性
if startInt > endInt {
return nil, fmt.Errorf("起始IP大于结束IP")
}
// 限制IP范围的大小防止生成过多IP导致内存问题
if endInt-startInt > tp.options.MaxTargets {
return nil, fmt.Errorf("IP范围过大超过最大限制: %d", tp.options.MaxTargets)
}
// 生成IP范围
var allIP []string
for ipInt := startInt; ipInt <= endInt; ipInt++ {
ip := fmt.Sprintf("%d.%d.%d.%d",
(ipInt>>IPFirstOctetShift)&IPOctetMask,
(ipInt>>IPSecondOctetShift)&IPOctetMask,
(ipInt>>IPThirdOctetShift)&IPOctetMask,
ipInt&IPOctetMask)
allIP = append(allIP, ip)
}
return allIP, nil
}
// parseSubnet8 解析/8网段的IP地址生成采样IP列表
func (tp *TargetParser) parseSubnet8(subnet string) []string {
// 去除CIDR后缀获取基础IP
baseIP := subnet[:len(subnet)-2]
if net.ParseIP(baseIP) == nil {
return nil
}
// 获取/8网段的第一段
firstOctet := strings.Split(baseIP, ".")[0]
var sampleIPs []string
// 对常用网段进行更全面的扫描
commonSecondOctets := GetCommonSecondOctets()
// 对于每个选定的第二段,采样部分第三段
for _, secondOctet := range commonSecondOctets {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8ThirdOctetStep {
// 添加常见的网关和服务器IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet)) // 默认网关
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, RouterSwitchLastOctet)) // 通常用于路由器/交换机
// 随机采样不同范围的主机IP
fourthOctet := tp.randomInt(SamplingMinHost, SamplingMaxHost)
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
}
}
// 对其他二级网段进行稀疏采样
for secondOctet := 0; secondOctet < 256; secondOctet += Subnet8SamplingStep {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8SamplingStep {
// 对于采样的网段取几个代表性IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet))
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, tp.randomInt(SamplingMinHost, SamplingMaxHost)))
}
}
// 限制采样数量
if len(sampleIPs) > tp.options.MaxTargets {
sampleIPs = sampleIPs[:tp.options.MaxTargets]
}
return sampleIPs
}
// randomInt 生成指定范围内的随机整数
func (tp *TargetParser) randomInt(min, max int) int {
if min >= max || min < 0 || max <= 0 {
return max
}
return min + (max-min)/2 // 简化版本避免依赖rand
}
// parsePortRange 解析端口范围
func (tp *TargetParser) parsePortRange(rangeStr string) ([]int, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的端口范围格式")
}
startPort, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
endPort, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("无效的端口号")
}
if startPort > endPort {
startPort, endPort = endPort, startPort
}
if startPort < MinPort || endPort > MaxPort {
return nil, fmt.Errorf("端口号超出范围")
}
var ports []int
for port := startPort; port <= endPort; port++ {
ports = append(ports, port)
}
return ports, nil
}
// nextIP 获取下一个IP地址
func (tp *TargetParser) nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
// validateHost 验证主机地址
func (tp *TargetParser) validateHost(host string) (bool, error) {
if host == "" {
return false, fmt.Errorf("主机地址为空")
}
// 检查是否为IP地址
if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip)
}
// 检查是否为域名
if tp.isValidDomain(host) {
return true, nil
}
return false, fmt.Errorf("无效的主机地址格式")
}
// validateIP 验证IP地址
func (tp *TargetParser) validateIP(ip net.IP) (bool, error) {
if ip == nil {
return false, fmt.Errorf("IP地址为空")
}
// 检查是否为私有IP
if !tp.options.AllowPrivateIPs && tp.isPrivateIP(ip) {
return false, fmt.Errorf("不允许私有IP地址")
}
// 检查是否为回环地址
if !tp.options.AllowLoopback && ip.IsLoopback() {
return false, fmt.Errorf("不允许回环地址")
}
return true, nil
}
// validateURL 验证URL
func (tp *TargetParser) validateURL(rawURL string) (bool, error) {
if rawURL == "" {
return false, fmt.Errorf("URL为空")
}
if !tp.options.ValidateURLs {
return true, nil
}
if !tp.urlRegex.MatchString(rawURL) {
return false, fmt.Errorf("URL格式无效")
}
// 进一步验证URL格式
_, err := url.Parse(rawURL)
if err != nil {
return false, fmt.Errorf("URL解析失败: %v", err)
}
return true, nil
}
// validateHostPort 验证主机端口组合
func (tp *TargetParser) validateHostPort(hostPort string) (bool, error) {
parts := strings.Split(hostPort, ":")
if len(parts) != 2 {
return false, fmt.Errorf("主机端口格式无效,应为 host:port")
}
host := strings.TrimSpace(parts[0])
portStr := strings.TrimSpace(parts[1])
// 验证主机
if valid, err := tp.validateHost(host); !valid {
return false, fmt.Errorf("主机无效: %v", err)
}
// 验证端口
port, err := strconv.Atoi(portStr)
if err != nil {
return false, fmt.Errorf("端口号无效: %s", portStr)
}
if port < MinPort || port > MaxPort {
return false, fmt.Errorf("端口号超出范围: %d", port)
}
return true, nil
}
// isPrivateIP 检查是否为私有IP
func (tp *TargetParser) isPrivateIP(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
// 10.0.0.0/8
if ip4[0] == 10 {
return true
}
// 172.16.0.0/12
if ip4[0] == 172 && ip4[1] >= Private172StartSecondOctet && ip4[1] <= Private172EndSecondOctet {
return true
}
// 192.168.0.0/16
if ip4[0] == 192 && ip4[1] == Private192SecondOctet {
return true
}
}
return false
}
// isValidDomain 检查是否为有效域名
func (tp *TargetParser) isValidDomain(domain string) bool {
return CompiledDomainRegex.MatchString(domain) && len(domain) <= MaxDomainLength
}
// excludeHosts 排除指定主机
func (tp *TargetParser) excludeHosts(hosts, excludeList []string) []string {
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, excluded := excludeMap[host]; !excluded {
result = append(result, host)
}
}
return result
}
// removeDuplicateStrings 去重字符串切片
func (tp *TargetParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去重端口切片
func (tp *TargetParser) removeDuplicatePorts(slice []int) []int {
seen := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (tp *TargetParser) generateStatistics(hosts, urls []string, ports, excludePorts []int) *TargetStatistics {
return &TargetStatistics{
TotalHosts: len(hosts),
TotalURLs: len(urls),
TotalPorts: len(ports),
ExcludedPorts: len(excludePorts),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

163
Common/parsers/Types.go Normal file
View File

@ -0,0 +1,163 @@
package parsers
import (
"errors"
"fmt"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// ParsedConfig 解析后的完整配置
type ParsedConfig struct {
Targets *TargetConfig `json:"targets"`
Credentials *CredentialConfig `json:"credentials"`
Network *NetworkConfig `json:"network"`
Validation *ValidationConfig `json:"validation"`
}
// TargetConfig 目标配置
type TargetConfig struct {
Hosts []string `json:"hosts"`
URLs []string `json:"urls"`
Ports []int `json:"ports"`
ExcludePorts []int `json:"exclude_ports"`
HostPorts []string `json:"host_ports"`
LocalMode bool `json:"local_mode"`
Statistics *TargetStatistics `json:"statistics,omitempty"`
}
// TargetStatistics 目标解析统计
type TargetStatistics struct {
TotalHosts int `json:"total_hosts"`
TotalURLs int `json:"total_urls"`
TotalPorts int `json:"total_ports"`
ExcludedHosts int `json:"excluded_hosts"`
ExcludedPorts int `json:"excluded_ports"`
}
// CredentialConfig 认证配置
type CredentialConfig struct {
Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"`
HashValues []string `json:"hash_values"`
HashBytes [][]byte `json:"hash_bytes,omitempty"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
Statistics *CredentialStats `json:"statistics,omitempty"`
}
// CredentialStats 认证配置统计
type CredentialStats struct {
TotalUsernames int `json:"total_usernames"`
TotalPasswords int `json:"total_passwords"`
TotalHashes int `json:"total_hashes"`
UniqueUsernames int `json:"unique_usernames"`
UniquePasswords int `json:"unique_passwords"`
ValidHashes int `json:"valid_hashes"`
InvalidHashes int `json:"invalid_hashes"`
}
// NetworkConfig 网络配置
type NetworkConfig struct {
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
Timeout time.Duration `json:"timeout"`
WebTimeout time.Duration `json:"web_timeout"`
DisablePing bool `json:"disable_ping"`
EnableDNSLog bool `json:"enable_dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// ValidationConfig 验证配置
type ValidationConfig struct {
ScanMode string `json:"scan_mode"`
ConflictChecked bool `json:"conflict_checked"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// ParseResult 解析结果
type ParseResult struct {
Config *ParsedConfig `json:"config"`
Success bool `json:"success"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
ParseTime time.Duration `json:"parse_time"`
}
// 预定义错误类型
var (
ErrEmptyInput = errors.New(i18n.GetText("parser_empty_input"))
)
// ParserOptions 解析器选项
type ParserOptions struct {
EnableConcurrency bool // 启用并发解析
MaxWorkers int // 最大工作协程数
Timeout time.Duration // 解析超时时间
EnableValidation bool // 启用详细验证
EnableStatistics bool // 启用统计信息
IgnoreErrors bool // 忽略非致命错误
FileMaxSize int64 // 文件最大大小限制
MaxTargets int // 最大目标数量限制
}
// DefaultParserOptions 返回默认解析器选项
func DefaultParserOptions() *ParserOptions {
return &ParserOptions{
EnableConcurrency: DefaultEnableConcurrency,
MaxWorkers: DefaultMaxWorkers,
Timeout: DefaultTimeout,
EnableValidation: DefaultEnableValidation,
EnableStatistics: DefaultEnableStatistics,
IgnoreErrors: DefaultIgnoreErrors,
FileMaxSize: DefaultFileMaxSize,
MaxTargets: DefaultMaxTargets,
}
}
// Parser 解析器接口
type Parser interface {
Parse(options *ParserOptions) (*ParseResult, error)
Validate() error
GetStatistics() interface{}
}
// FileSource 文件源
type FileSource struct {
Path string `json:"path"`
Size int64 `json:"size"`
ModTime time.Time `json:"mod_time"`
LineCount int `json:"line_count"`
ValidLines int `json:"valid_lines"`
}
// ParseError 解析错误,包含详细上下文
type ParseError struct {
Type string `json:"type"`
Message string `json:"message"`
Source string `json:"source"`
Line int `json:"line,omitempty"`
Context string `json:"context,omitempty"`
Original error `json:"original,omitempty"`
}
func (e *ParseError) Error() string {
if e.Line > 0 {
return fmt.Sprintf("%s:%d - %s: %s", e.Source, e.Line, e.Type, e.Message)
}
return fmt.Sprintf("%s - %s: %s", e.Source, e.Type, e.Message)
}
// NewParseError 创建解析错误
func NewParseError(errType, message, source string, line int, original error) *ParseError {
return &ParseError{
Type: errType,
Message: message,
Source: source,
Line: line,
Original: original,
}
}

View File

@ -0,0 +1,293 @@
package parsers
import (
"fmt"
"sync"
"time"
)
// ValidationParser 参数验证解析器
type ValidationParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *ValidationParserOptions
}
// ValidationParserOptions 验证解析器选项
type ValidationParserOptions struct {
StrictMode bool `json:"strict_mode"` // 严格模式
AllowEmpty bool `json:"allow_empty"` // 允许空配置
CheckConflicts bool `json:"check_conflicts"` // 检查参数冲突
ValidateTargets bool `json:"validate_targets"` // 验证目标有效性
ValidateNetwork bool `json:"validate_network"` // 验证网络配置
MaxErrorCount int `json:"max_error_count"` // 最大错误数量
}
// DefaultValidationParserOptions 默认验证解析器选项
func DefaultValidationParserOptions() *ValidationParserOptions {
return &ValidationParserOptions{
StrictMode: DefaultStrictMode,
AllowEmpty: DefaultAllowEmpty,
CheckConflicts: DefaultCheckConflicts,
ValidateTargets: DefaultValidateTargets,
ValidateNetwork: DefaultValidateNetwork,
MaxErrorCount: DefaultMaxErrorCount,
}
}
// NewValidationParser 创建验证解析器
func NewValidationParser(options *ValidationParserOptions) *ValidationParser {
if options == nil {
options = DefaultValidationParserOptions()
}
return &ValidationParser{
options: options,
}
}
// ValidationInput 验证输入参数
type ValidationInput struct {
// 扫描模式
ScanMode string `json:"scan_mode"`
LocalMode bool `json:"local_mode"`
// 目标配置
HasHosts bool `json:"has_hosts"`
HasURLs bool `json:"has_urls"`
HasPorts bool `json:"has_ports"`
// 网络配置
HasProxy bool `json:"has_proxy"`
DisablePing bool `json:"disable_ping"`
// 凭据配置
HasCredentials bool `json:"has_credentials"`
// 特殊模式
PocScan bool `json:"poc_scan"`
BruteScan bool `json:"brute_scan"`
LocalScan bool `json:"local_scan"`
}
// ConflictRule 冲突规则
type ConflictRule struct {
Name string `json:"name"`
Description string `json:"description"`
Fields []string `json:"fields"`
Severity string `json:"severity"` // error, warning, info
}
// ValidationRule 验证规则
type ValidationRule struct {
Name string `json:"name"`
Description string `json:"description"`
Validator func(input *ValidationInput) error `json:"-"`
Severity string `json:"severity"`
}
// Parse 执行参数验证
func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "验证输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Validation: &ValidationConfig{
ScanMode: input.ScanMode,
ConflictChecked: true,
},
},
Success: true,
}
var errors []error
var warnings []string
// 基础验证
basicErrors, basicWarnings := vp.validateBasic(input)
errors = append(errors, basicErrors...)
warnings = append(warnings, basicWarnings...)
// 冲突检查
if vp.options.CheckConflicts {
conflictErrors, conflictWarnings := vp.checkConflicts(input)
errors = append(errors, conflictErrors...)
warnings = append(warnings, conflictWarnings...)
}
// 逻辑验证
logicErrors, logicWarnings := vp.validateLogic(input, config)
errors = append(errors, logicErrors...)
warnings = append(warnings, logicWarnings...)
// 性能建议
performanceWarnings := vp.checkPerformance(input, config)
warnings = append(warnings, performanceWarnings...)
// 检查错误数量限制
if len(errors) > vp.options.MaxErrorCount {
errors = errors[:vp.options.MaxErrorCount]
warnings = append(warnings, fmt.Sprintf("错误数量过多,仅显示前%d个", vp.options.MaxErrorCount))
}
// 更新结果
result.Config.Validation.Errors = errors
result.Config.Validation.Warnings = warnings
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// validateBasic 基础验证
func (vp *ValidationParser) validateBasic(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 检查是否有任何目标
if !input.HasHosts && !input.HasURLs && !input.LocalMode {
if !vp.options.AllowEmpty {
errors = append(errors, NewParseError("VALIDATION_ERROR", "未指定任何扫描目标", "basic", 0, nil))
} else {
warnings = append(warnings, "未指定扫描目标,将使用默认配置")
}
}
// 检查扫描模式
if input.ScanMode != "" {
if err := vp.validateScanMode(input.ScanMode); err != nil {
if vp.options.StrictMode {
errors = append(errors, err)
} else {
warnings = append(warnings, err.Error())
}
}
}
return errors, warnings
}
// checkConflicts 检查参数冲突
func (vp *ValidationParser) checkConflicts(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 定义冲突规则 (预留用于扩展)
_ = []ConflictRule{
{
Name: "multiple_scan_modes",
Description: "不能同时使用多种扫描模式",
Fields: []string{"hosts", "urls", "local_mode"},
Severity: "error",
},
{
Name: "proxy_with_ping",
Description: "使用代理时建议禁用Ping检测",
Fields: []string{"proxy", "ping"},
Severity: "warning",
},
}
// 检查扫描模式冲突
scanModes := 0
if input.HasHosts {
scanModes++
}
if input.HasURLs {
scanModes++
}
if input.LocalMode {
scanModes++
}
if scanModes > 1 {
errors = append(errors, NewParseError("CONFLICT_ERROR",
"不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)", "validation", 0, nil))
}
// 检查代理和Ping冲突
if input.HasProxy && !input.DisablePing {
warnings = append(warnings, "代理模式下Ping检测可能失效")
}
return errors, warnings
}
// validateLogic 逻辑验证
func (vp *ValidationParser) validateLogic(input *ValidationInput, config *ParsedConfig) ([]error, []string) {
var errors []error
var warnings []string
// 验证目标配置逻辑
if vp.options.ValidateTargets && config != nil && config.Targets != nil {
// 检查排除端口配置
if len(config.Targets.ExcludePorts) > 0 && len(config.Targets.Ports) == 0 {
warnings = append(warnings, "排除端口无效")
}
}
return errors, warnings
}
// checkPerformance 性能检查
func (vp *ValidationParser) checkPerformance(input *ValidationInput, config *ParsedConfig) []string {
var warnings []string
if config == nil {
return warnings
}
// 检查目标数量
if config.Targets != nil {
totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports)
if totalTargets > MaxTargetsThreshold {
warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets))
}
// 检查端口范围
if len(config.Targets.Ports) > DefaultMaxPortRange {
warnings = append(warnings, "端口数量过多")
}
}
// 检查超时配置
if config.Network != nil {
if config.Network.Timeout < MinTimeoutThreshold {
warnings = append(warnings, "超时过短")
}
if config.Network.Timeout > MaxTimeoutThreshold {
warnings = append(warnings, "超时过长")
}
}
return warnings
}
// validateScanMode 验证扫描模式
func (vp *ValidationParser) validateScanMode(scanMode string) error {
validModes := []string{"all", "icmp"}
// 检查是否为预定义模式
for _, mode := range validModes {
if scanMode == mode {
return nil
}
}
// 允许插件名称作为扫描模式,实际插件验证在运行时进行
// 这里不做严格验证,避免维护两套插件列表
return nil
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

279
Common/parsers/constants.go Normal file
View File

@ -0,0 +1,279 @@
package parsers
import (
"regexp"
"time"
)
/*
constants.go - 解析器系统常量定义
统一管理common/parsers包中的所有常量便于查看和编辑
*/
// =============================================================================
// 默认解析器选项常量 (从Types.go迁移)
// =============================================================================
const (
// 解析器默认配置
DefaultEnableConcurrency = true
DefaultMaxWorkers = 4
DefaultTimeout = 30 * time.Second
DefaultEnableValidation = true
DefaultEnableStatistics = true
DefaultIgnoreErrors = false
DefaultFileMaxSize = 100 * 1024 * 1024 // 100MB
DefaultMaxTargets = 10000 // 10K targets
)
// =============================================================================
// 文件读取器常量 (从FileReader.go迁移)
// =============================================================================
const (
// 文件读取器默认配置
DefaultMaxCacheSize = 10
DefaultEnableCache = true
DefaultFileReaderMaxFileSize = 50 * 1024 * 1024 // 50MB
DefaultFileReaderTimeout = 30 * time.Second
DefaultFileReaderEnableValidation = true
DefaultTrimSpace = true
DefaultSkipEmpty = true
DefaultSkipComments = true
// 文件内容验证
MaxLineLength = 1000 // 单行最大字符数
MaxValidRune = 32 // 最小有效字符ASCII值
TabRune = 9 // Tab字符
NewlineRune = 10 // 换行符
CarriageReturnRune = 13 // 回车符
CommentPrefix = "#" // 注释前缀
)
// =============================================================================
// 凭据解析器常量 (从CredentialParser.go迁移)
// =============================================================================
const (
// 凭据验证限制
DefaultMaxUsernameLength = 64
DefaultMaxPasswordLength = 128
DefaultAllowEmptyPasswords = true
DefaultValidateHashes = true
DefaultDeduplicateUsers = true
DefaultDeduplicatePasswords = true
DefaultCredentialsEnableStatistics = true
// 哈希验证
HashRegexPattern = `^[a-fA-F0-9]{32}$` // MD5哈希正则表达式
HashValidationLength = 32 // 有效哈希长度
InvalidUsernameChars = "\r\n\t" // 无效用户名字符
)
// =============================================================================
// 网络解析器常量 (从NetworkParser.go迁移)
// =============================================================================
const (
// 网络配置默认值
DefaultValidateProxies = true
DefaultAllowInsecure = false
DefaultNetworkTimeout = 30 * time.Second
DefaultWebTimeout = 10 * time.Second
DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
// 超时限制
MaxTimeoutSeconds = 300 // 最大超时5分钟
MaxWebTimeoutSeconds = 120 // 最大Web超时2分钟
// 字符串长度限制
MaxUserAgentLength = 512 // 最大用户代理长度
MaxCookieLength = 4096 // 最大Cookie长度
// 代理快捷配置
ProxyShortcut1 = "1"
ProxyShortcut2 = "2"
ProxyShortcutHTTP = "http://127.0.0.1:8080"
ProxyShortcutSOCKS5 = "socks5://127.0.0.1:1080"
// 协议支持
ProtocolHTTP = "http"
ProtocolHTTPS = "https"
ProtocolSOCKS5 = "socks5"
ProtocolPrefix = "://"
SOCKS5Prefix = "socks5://"
HTTPPrefix = "http://"
// 端口范围
MinPort = 1
MaxPort = 65535
// 无效字符集
InvalidUserAgentChars = "\r\n\t"
)
// GetCommonBrowsers 获取常见浏览器标识列表
func GetCommonBrowsers() []string {
return []string{
"Mozilla", "Chrome", "Safari", "Firefox", "Edge", "Opera",
"AppleWebKit", "Gecko", "Trident", "Presto",
}
}
// =============================================================================
// 目标解析器常量 (从TargetParser.go迁移)
// =============================================================================
const (
// 目标解析器默认配置
DefaultTargetMaxTargets = 10000
DefaultMaxPortRange = 1000
DefaultAllowPrivateIPs = true
DefaultAllowLoopback = true
DefaultValidateURLs = true
DefaultResolveDomains = false
DefaultTargetEnableStatistics = true
DefaultPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
// 正则表达式模式
IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$`
PortRangeRegexPattern = `^(\d+)(-(\d+))?$`
URLValidationRegexPattern = `^https?://[^\s/$.?#].[^\s]*$`
DomainRegexPattern = `^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`
CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
// IP地址限制
MaxIPv4OctetValue = 255
IPv4OctetCount = 4
MaxDomainLength = 253
// CIDR网段简写
PrivateNetwork192 = "192"
PrivateNetwork172 = "172"
PrivateNetwork10 = "10"
PrivateNetwork192CIDR = "192.168.0.0/16"
PrivateNetwork172CIDR = "172.16.0.0/12"
PrivateNetwork10CIDR = "10.0.0.0/8"
// 私有网络范围
Private172StartSecondOctet = 16
Private172EndSecondOctet = 31
Private192SecondOctet = 168
// /8网段采样配置
Subnet8SamplingStep = 32
Subnet8ThirdOctetStep = 10
// IP地址计算位移
IPFirstOctetShift = 24
IPSecondOctetShift = 16
IPThirdOctetShift = 8
IPOctetMask = 0xFF
)
// GetCommonSecondOctets 获取常用第二段IP
func GetCommonSecondOctets() []int {
return []int{0, 1, 2, 10, 100, 200, 254}
}
// =============================================================================
// 简化解析器常量 (从Simple.go迁移)
// =============================================================================
const (
// 端口和主机限制
SimpleMaxHosts = 10000
// 网段简写展开
DefaultGatewayLastOctet = 1
RouterSwitchLastOctet = 254
SamplingMinHost = 2
SamplingMaxHost = 253
)
// GetPortGroups 获取预定义端口组映射
func GetPortGroups() map[string]string {
return map[string]string{
"web": "80,81,82,83,84,85,86,87,88,89,90,443,8000,8001,8002,8003,8004,8005,8006,8007,8008,8009,8010,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8443,9000,9001,9002,9080,9090",
"main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
"database": "1433,1521,3306,5432,6379,11211,27017",
"common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986",
}
}
// GetTargetPortGroups 获取目标解析器端口组映射
func GetTargetPortGroups() map[string]string {
return map[string]string{
"service": "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613",
"db": "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616",
"web": "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018",
"all": "1-65535",
"main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
}
}
// =============================================================================
// 验证解析器常量 (从ValidationParser.go迁移)
// =============================================================================
const (
// 验证解析器默认配置
DefaultMaxErrorCount = 100
DefaultStrictMode = false
DefaultAllowEmpty = true
DefaultCheckConflicts = true
DefaultValidateTargets = true
DefaultValidateNetwork = true
// 性能警告阈值
MaxTargetsThreshold = 100000 // 最大目标数量阈值
MinTimeoutThreshold = 1 * time.Second // 最小超时阈值
MaxTimeoutThreshold = 60 * time.Second // 最大超时阈值
)
// =============================================================================
// 错误类型常量
// =============================================================================
const (
// 解析错误类型
ErrorTypeInputError = "INPUT_ERROR"
ErrorTypeFileError = "FILE_ERROR"
ErrorTypeTimeout = "TIMEOUT"
ErrorTypeReadError = "READ_ERROR"
ErrorTypeUsernameError = "USERNAME_ERROR"
ErrorTypePasswordError = "PASSWORD_ERROR"
ErrorTypeHashError = "HASH_ERROR"
ErrorTypeProxyError = "PROXY_ERROR"
ErrorTypeUserAgentError = "USERAGENT_ERROR"
ErrorTypeCookieError = "COOKIE_ERROR"
ErrorTypeHostError = "HOST_ERROR"
ErrorTypePortError = "PORT_ERROR"
ErrorTypeExcludePortError = "EXCLUDE_PORT_ERROR"
)
// =============================================================================
// 编译时正则表达式
// =============================================================================
var (
// 预编译的正则表达式,提高性能
CompiledHashRegex *regexp.Regexp
CompiledIPv4Regex *regexp.Regexp
CompiledPortRegex *regexp.Regexp
CompiledURLRegex *regexp.Regexp
CompiledDomainRegex *regexp.Regexp
CompiledCookieRegex *regexp.Regexp
)
// 在包初始化时编译正则表达式
func init() {
CompiledHashRegex = regexp.MustCompile(HashRegexPattern)
CompiledIPv4Regex = regexp.MustCompile(IPv4RegexPattern)
CompiledPortRegex = regexp.MustCompile(PortRangeRegexPattern)
CompiledURLRegex = regexp.MustCompile(URLValidationRegexPattern)
CompiledDomainRegex = regexp.MustCompile(DomainRegexPattern)
CompiledCookieRegex = regexp.MustCompile(CookieRegexPattern)
}

14
Common/proxy/Factory.go Normal file
View File

@ -0,0 +1,14 @@
package proxy
// 已清理未使用的导入
// =============================================================================================
// 已删除的死代码(未使用):
// - ParseProxyURL: 解析代理URL
// - CreateProxyManager: 创建代理管理器
// - ValidateProxyConfig: 验证代理配置
// - GetProxyTypeFromString: 从字符串获取代理类型
// - BuildProxyURL: 构建代理URL
// - IsProxyEnabled: 检查是否启用了代理
// - GetDefaultProxyConfigForType: 获取指定类型的默认配置
// =============================================================================================

22
Common/proxy/Global.go Normal file
View File

@ -0,0 +1,22 @@
package proxy
// 已清理未使用的导入和全局变量
// =============================================================================================
// 已删除的死代码(未使用):
// - globalManager: 全局代理管理器变量
// - globalMutex: 全局互斥锁
// - once: 全局初始化once变量
// - InitGlobalProxy: 初始化全局代理管理器
// - GetGlobalProxy: 获取全局代理管理器
// - UpdateGlobalProxyConfig: 更新全局代理配置
// - CloseGlobalProxy: 关闭全局代理管理器
// - GetGlobalProxyStats: 获取全局代理统计信息
// - DialWithProxy: 使用全局代理拨号
// - DialContextWithProxy: 使用全局代理和上下文拨号
// - DialTLSWithProxy: 使用全局代理建立TLS连接
// - DialTLSContextWithProxy: 使用全局代理和上下文建立TLS连接
// - IsProxyEnabledGlobally: 检查全局是否启用了代理
// - GetGlobalProxyType: 获取全局代理类型
// - GetGlobalProxyAddress: 获取全局代理地址
// =============================================================================================

112
Common/proxy/HTTPDialer.go Normal file
View File

@ -0,0 +1,112 @@
package proxy
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"sync/atomic"
"time"
)
// httpDialer HTTP代理拨号器
type httpDialer struct {
config *ProxyConfig
stats *ProxyStats
baseDial *net.Dialer
}
func (h *httpDialer) Dial(network, address string) (net.Conn, error) {
return h.DialContext(context.Background(), network, address)
}
func (h *httpDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&h.stats.TotalConnections, 1)
// 连接到HTTP代理服务器
proxyConn, err := h.baseDial.DialContext(ctx, NetworkTCP, h.config.Address)
if err != nil {
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgHTTPConnFailed, ErrCodeHTTPConnFailed, err)
}
// 发送CONNECT请求
if err := h.sendConnectRequest(proxyConn, address); err != nil {
proxyConn.Close()
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, err
}
duration := time.Since(start)
h.stats.LastConnectTime = start
atomic.AddInt64(&h.stats.ActiveConnections, 1)
h.updateAverageConnectTime(duration)
return &trackedConn{
Conn: proxyConn,
stats: h.stats,
}, nil
}
// sendConnectRequest 发送HTTP CONNECT请求
func (h *httpDialer) sendConnectRequest(conn net.Conn, address string) error {
// 构建CONNECT请求
req := fmt.Sprintf(HTTPConnectRequestFormat, address, address)
// 添加认证头
if h.config.Username != "" {
auth := base64.StdEncoding.EncodeToString(
[]byte(h.config.Username + AuthSeparator + h.config.Password))
req += fmt.Sprintf(HTTPAuthHeaderFormat, auth)
}
req += HTTPRequestEndFormat
// 设置写超时
if err := conn.SetWriteDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetWriteTimeout, ErrCodeHTTPSetWriteTimeout, err)
}
// 发送请求
if _, err := conn.Write([]byte(req)); err != nil {
return NewProxyError(ErrTypeConnection, ErrMsgHTTPSendConnectFail, ErrCodeHTTPSendConnectFail, err)
}
// 设置读超时
if err := conn.SetReadDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetReadTimeout, ErrCodeHTTPSetReadTimeout, err)
}
// 读取响应
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
return NewProxyError(ErrTypeProtocol, ErrMsgHTTPReadRespFailed, ErrCodeHTTPReadRespFailed, err)
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != HTTPStatusOK {
return NewProxyError(ErrTypeAuth,
fmt.Sprintf(ErrMsgHTTPProxyAuthFailed, resp.StatusCode), ErrCodeHTTPProxyAuthFailed, nil)
}
// 清除deadline
conn.SetDeadline(time.Time{})
return nil
}
// updateAverageConnectTime 更新平均连接时间
func (h *httpDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if h.stats.AverageConnectTime == 0 {
h.stats.AverageConnectTime = duration
} else {
h.stats.AverageConnectTime = (h.stats.AverageConnectTime + duration) / 2
}
}

337
Common/proxy/Manager.go Normal file
View File

@ -0,0 +1,337 @@
package proxy
import (
"context"
"fmt"
"net"
"net/url"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/proxy"
)
// manager 代理管理器实现
type manager struct {
config *ProxyConfig
stats *ProxyStats
mu sync.RWMutex
// 连接池
dialerCache map[string]Dialer
cacheExpiry time.Time
cacheMu sync.RWMutex
}
// NewProxyManager 创建新的代理管理器
func NewProxyManager(config *ProxyConfig) ProxyManager {
if config == nil {
config = DefaultProxyConfig()
}
return &manager{
config: config,
stats: &ProxyStats{
ProxyType: config.Type.String(),
ProxyAddress: config.Address,
},
dialerCache: make(map[string]Dialer),
cacheExpiry: time.Now().Add(DefaultCacheExpiry),
}
}
// GetDialer 获取普通拨号器
func (m *manager) GetDialer() (Dialer, error) {
m.mu.RLock()
config := m.config
m.mu.RUnlock()
switch config.Type {
case ProxyTypeNone:
return m.createDirectDialer(), nil
case ProxyTypeSOCKS5:
return m.createSOCKS5Dialer()
case ProxyTypeHTTP, ProxyTypeHTTPS:
return m.createHTTPDialer()
default:
return nil, NewProxyError(ErrTypeConfig, ErrMsgUnsupportedProxyType, ErrCodeUnsupportedProxyType, nil)
}
}
// GetTLSDialer 获取TLS拨号器
func (m *manager) GetTLSDialer() (TLSDialer, error) {
dialer, err := m.GetDialer()
if err != nil {
return nil, err
}
return &tlsDialerWrapper{
dialer: dialer,
config: m.config,
stats: m.stats,
}, nil
}
// UpdateConfig 更新配置
func (m *manager) UpdateConfig(config *ProxyConfig) error {
if config == nil {
return NewProxyError(ErrTypeConfig, ErrMsgEmptyConfig, ErrCodeEmptyConfig, nil)
}
m.mu.Lock()
defer m.mu.Unlock()
m.config = config
m.stats.ProxyType = config.Type.String()
m.stats.ProxyAddress = config.Address
// 清理缓存
m.cacheMu.Lock()
m.dialerCache = make(map[string]Dialer)
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return nil
}
// Close 关闭管理器
func (m *manager) Close() error {
m.cacheMu.Lock()
defer m.cacheMu.Unlock()
m.dialerCache = make(map[string]Dialer)
return nil
}
// Stats 获取统计信息
func (m *manager) Stats() *ProxyStats {
m.mu.RLock()
defer m.mu.RUnlock()
// 返回副本以避免并发问题
statsCopy := *m.stats
return &statsCopy
}
// createDirectDialer 创建直连拨号器
func (m *manager) createDirectDialer() Dialer {
return &directDialer{
timeout: m.config.Timeout,
stats: m.stats,
}
}
// createSOCKS5Dialer 创建SOCKS5拨号器
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeySOCKS5, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
// 解析代理地址
proxyURL := fmt.Sprintf(SOCKS5URLFormat, m.config.Address)
if m.config.Username != "" {
proxyURL = fmt.Sprintf(SOCKS5URLAuthFormat,
m.config.Username, m.config.Password, m.config.Address)
}
u, err := url.Parse(proxyURL)
if err != nil {
return nil, NewProxyError(ErrTypeConfig, ErrMsgSOCKS5ParseFailed, ErrCodeSOCKS5ParseFailed, err)
}
// 创建基础拨号器
baseDial := &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
}
// 创建SOCKS5拨号器
var auth *proxy.Auth
if u.User != nil {
auth = &proxy.Auth{
User: u.User.Username(),
}
if password, hasPassword := u.User.Password(); hasPassword {
auth.Password = password
}
}
socksDialer, err := proxy.SOCKS5(NetworkTCP, u.Host, auth, baseDial)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
}
dialer := &socks5Dialer{
dialer: socksDialer,
config: m.config,
stats: m.stats,
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// createHTTPDialer 创建HTTP代理拨号器
func (m *manager) createHTTPDialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeyHTTP, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
dialer := &httpDialer{
config: m.config,
stats: m.stats,
baseDial: &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
},
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// directDialer 直连拨号器
type directDialer struct {
timeout time.Duration
stats *ProxyStats
}
func (d *directDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *directDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&d.stats.TotalConnections, 1)
dialer := &net.Dialer{
Timeout: d.timeout,
}
conn, err := dialer.DialContext(ctx, network, address)
duration := time.Since(start)
d.stats.LastConnectTime = start
if err != nil {
atomic.AddInt64(&d.stats.FailedConnections, 1)
d.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgDirectConnFailed, ErrCodeDirectConnFailed, err)
}
atomic.AddInt64(&d.stats.ActiveConnections, 1)
d.updateAverageConnectTime(duration)
return &trackedConn{
Conn: conn,
stats: d.stats,
}, nil
}
// socks5Dialer SOCKS5拨号器
type socks5Dialer struct {
dialer proxy.Dialer
config *ProxyConfig
stats *ProxyStats
}
func (s *socks5Dialer) Dial(network, address string) (net.Conn, error) {
return s.DialContext(context.Background(), network, address)
}
func (s *socks5Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&s.stats.TotalConnections, 1)
// 创建一个带超时的上下文
dialCtx, cancel := context.WithTimeout(ctx, s.config.Timeout)
defer cancel()
// 使用goroutine处理拨号以支持取消
connChan := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := s.dialer.Dial(network, address)
select {
case <-dialCtx.Done():
if conn != nil {
conn.Close()
}
case connChan <- struct {
conn net.Conn
err error
}{conn, err}:
}
}()
select {
case <-dialCtx.Done():
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = dialCtx.Err().Error()
return nil, NewProxyError(ErrTypeTimeout, ErrMsgSOCKS5ConnTimeout, ErrCodeSOCKS5ConnTimeout, dialCtx.Err())
case result := <-connChan:
duration := time.Since(start)
s.stats.LastConnectTime = start
if result.err != nil {
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = result.err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5ConnFailed, ErrCodeSOCKS5ConnFailed, result.err)
}
atomic.AddInt64(&s.stats.ActiveConnections, 1)
s.updateAverageConnectTime(duration)
return &trackedConn{
Conn: result.conn,
stats: s.stats,
}, nil
}
}
// updateAverageConnectTime 更新平均连接时间
func (d *directDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if d.stats.AverageConnectTime == 0 {
d.stats.AverageConnectTime = duration
} else {
d.stats.AverageConnectTime = (d.stats.AverageConnectTime + duration) / 2
}
}
func (s *socks5Dialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if s.stats.AverageConnectTime == 0 {
s.stats.AverageConnectTime = duration
} else {
s.stats.AverageConnectTime = (s.stats.AverageConnectTime + duration) / 2
}
}

157
Common/proxy/TLSDialer.go Normal file
View File

@ -0,0 +1,157 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"sync/atomic"
"time"
)
// tlsDialerWrapper TLS拨号器包装器
type tlsDialerWrapper struct {
dialer Dialer
config *ProxyConfig
stats *ProxyStats
}
func (t *tlsDialerWrapper) Dial(network, address string) (net.Conn, error) {
return t.dialer.Dial(network, address)
}
func (t *tlsDialerWrapper) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return t.dialer.DialContext(ctx, network, address)
}
func (t *tlsDialerWrapper) DialTLS(network, address string, config *tls.Config) (net.Conn, error) {
return t.DialTLSContext(context.Background(), network, address, config)
}
func (t *tlsDialerWrapper) DialTLSContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
start := time.Now()
// 首先建立TCP连接
tcpConn, err := t.dialer.DialContext(ctx, network, address)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSTCPConnFailed, ErrCodeTLSTCPConnFailed, err)
}
// 创建TLS连接
tlsConn := tls.Client(tcpConn, tlsConfig)
// 设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
} else {
tlsConn.SetDeadline(time.Now().Add(t.config.Timeout))
}
// 进行TLS握手
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
atomic.AddInt64(&t.stats.FailedConnections, 1)
t.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSHandshakeFailed, ErrCodeTLSHandshakeFailed, err)
}
// 清除deadline让上层代码管理超时
tlsConn.SetDeadline(time.Time{})
duration := time.Since(start)
t.updateAverageConnectTime(duration)
return &trackedTLSConn{
trackedConn: &trackedConn{
Conn: tlsConn,
stats: t.stats,
},
isTLS: true,
}, nil
}
// updateAverageConnectTime 更新平均连接时间
func (t *tlsDialerWrapper) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if t.stats.AverageConnectTime == 0 {
t.stats.AverageConnectTime = duration
} else {
t.stats.AverageConnectTime = (t.stats.AverageConnectTime + duration) / 2
}
}
// trackedConn 带统计的连接
type trackedConn struct {
net.Conn
stats *ProxyStats
bytesSent int64
bytesRecv int64
}
func (tc *trackedConn) Read(b []byte) (n int, err error) {
n, err = tc.Conn.Read(b)
if n > 0 {
atomic.AddInt64(&tc.bytesRecv, int64(n))
}
return n, err
}
func (tc *trackedConn) Write(b []byte) (n int, err error) {
n, err = tc.Conn.Write(b)
if n > 0 {
atomic.AddInt64(&tc.bytesSent, int64(n))
}
return n, err
}
func (tc *trackedConn) Close() error {
atomic.AddInt64(&tc.stats.ActiveConnections, -1)
return tc.Conn.Close()
}
// trackedTLSConn 带统计的TLS连接
type trackedTLSConn struct {
*trackedConn
isTLS bool
}
func (ttc *trackedTLSConn) ConnectionState() tls.ConnectionState {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.ConnectionState()
}
return tls.ConnectionState{}
}
func (ttc *trackedTLSConn) Handshake() error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.Handshake()
}
return nil
}
func (ttc *trackedTLSConn) OCSPResponse() []byte {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.OCSPResponse()
}
return nil
}
func (ttc *trackedTLSConn) PeerCertificates() []*tls.Certificate {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
state := tlsConn.ConnectionState()
var certs []*tls.Certificate
for _, cert := range state.PeerCertificates {
certs = append(certs, &tls.Certificate{
Certificate: [][]byte{cert.Raw},
})
}
return certs
}
return nil
}
func (ttc *trackedTLSConn) VerifyHostname(host string) error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.VerifyHostname(host)
}
return nil
}

134
Common/proxy/Types.go Normal file
View File

@ -0,0 +1,134 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"time"
)
// ProxyType 代理类型
type ProxyType int
const (
ProxyTypeNone ProxyType = iota
ProxyTypeHTTP
ProxyTypeHTTPS
ProxyTypeSOCKS5
)
// String 返回代理类型的字符串表示
func (pt ProxyType) String() string {
switch pt {
case ProxyTypeNone:
return ProxyTypeStringNone
case ProxyTypeHTTP:
return ProxyTypeStringHTTP
case ProxyTypeHTTPS:
return ProxyTypeStringHTTPS
case ProxyTypeSOCKS5:
return ProxyTypeStringSOCKS5
default:
return ProxyTypeStringUnknown
}
}
// ProxyConfig 代理配置
type ProxyConfig struct {
Type ProxyType `json:"type"`
Address string `json:"address"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Timeout time.Duration `json:"timeout"`
MaxRetries int `json:"max_retries"`
KeepAlive time.Duration `json:"keep_alive"`
IdleTimeout time.Duration `json:"idle_timeout"`
MaxIdleConns int `json:"max_idle_conns"`
}
// DefaultProxyConfig 返回默认代理配置
func DefaultProxyConfig() *ProxyConfig {
return &ProxyConfig{
Type: ProxyTypeNone,
Timeout: DefaultProxyTimeout,
MaxRetries: DefaultProxyMaxRetries,
KeepAlive: DefaultProxyKeepAlive,
IdleTimeout: DefaultProxyIdleTimeout,
MaxIdleConns: DefaultProxyMaxIdleConns,
}
}
// Dialer 拨号器接口
type Dialer interface {
Dial(network, address string) (net.Conn, error)
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
// TLSDialer TLS拨号器接口
type TLSDialer interface {
Dialer
DialTLS(network, address string, config *tls.Config) (net.Conn, error)
DialTLSContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error)
}
// ProxyManager 代理管理器接口
type ProxyManager interface {
GetDialer() (Dialer, error)
GetTLSDialer() (TLSDialer, error)
UpdateConfig(config *ProxyConfig) error
Close() error
Stats() *ProxyStats
}
// ProxyStats 代理统计信息
type ProxyStats struct {
TotalConnections int64 `json:"total_connections"`
ActiveConnections int64 `json:"active_connections"`
FailedConnections int64 `json:"failed_connections"`
AverageConnectTime time.Duration `json:"average_connect_time"`
LastConnectTime time.Time `json:"last_connect_time"`
LastError string `json:"last_error,omitempty"`
ProxyType string `json:"proxy_type"`
ProxyAddress string `json:"proxy_address"`
}
// ConnectionInfo 连接信息
type ConnectionInfo struct {
ID string `json:"id"`
RemoteAddr string `json:"remote_addr"`
LocalAddr string `json:"local_addr"`
ProxyAddr string `json:"proxy_addr,omitempty"`
ConnectTime time.Time `json:"connect_time"`
Duration time.Duration `json:"duration"`
BytesSent int64 `json:"bytes_sent"`
BytesRecv int64 `json:"bytes_recv"`
IsTLS bool `json:"is_tls"`
Error string `json:"error,omitempty"`
}
// ProxyError 代理错误类型
type ProxyError struct {
Type string `json:"type"`
Message string `json:"message"`
Code int `json:"code"`
Cause error `json:"cause,omitempty"`
}
func (e *ProxyError) Error() string {
if e.Cause != nil {
return e.Message + ": " + e.Cause.Error()
}
return e.Message
}
// NewProxyError 创建代理错误
func NewProxyError(errType, message string, code int, cause error) *ProxyError {
return &ProxyError{
Type: errType,
Message: message,
Code: code,
Cause: cause,
}
}
// 预定义错误类型已迁移到constants.go

179
Common/proxy/constants.go Normal file
View File

@ -0,0 +1,179 @@
package proxy
import (
"time"
)
/*
constants.go - 代理系统常量定义
统一管理common/proxy包中的所有常量便于查看和编辑
*/
// =============================================================================
// 代理类型常量 (从Types.go迁移)
// =============================================================================
const (
// 代理类型字符串
ProxyTypeStringNone = "none"
ProxyTypeStringHTTP = "http"
ProxyTypeStringHTTPS = "https"
ProxyTypeStringSOCKS5 = "socks5"
ProxyTypeStringUnknown = "unknown"
)
// =============================================================================
// 默认配置常量 (从Types.go迁移)
// =============================================================================
const (
// 默认代理配置值
DefaultProxyTimeout = 30 * time.Second // 默认超时时间
DefaultProxyMaxRetries = 3 // 默认最大重试次数
DefaultProxyKeepAlive = 30 * time.Second // 默认保持连接时间
DefaultProxyIdleTimeout = 90 * time.Second // 默认空闲超时时间
DefaultProxyMaxIdleConns = 10 // 默认最大空闲连接数
)
// =============================================================================
// 错误类型常量 (从Types.go迁移)
// =============================================================================
const (
// 预定义错误类型
ErrTypeConfig = "config_error"
ErrTypeConnection = "connection_error"
ErrTypeAuth = "auth_error"
ErrTypeTimeout = "timeout_error"
ErrTypeProtocol = "protocol_error"
)
// =============================================================================
// 缓存管理常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存配置
DefaultCacheExpiry = 5 * time.Minute // 默认缓存过期时间
)
// =============================================================================
// 错误代码常量 (从Manager.go和其他文件迁移)
// =============================================================================
const (
// Manager错误代码
ErrCodeUnsupportedProxyType = 1001
ErrCodeEmptyConfig = 1002
// SOCKS5错误代码
ErrCodeSOCKS5ParseFailed = 2001
ErrCodeSOCKS5CreateFailed = 2002
// 直连错误代码
ErrCodeDirectConnFailed = 3001
ErrCodeSOCKS5ConnTimeout = 3002
ErrCodeSOCKS5ConnFailed = 3003
// HTTP代理错误代码
ErrCodeHTTPConnFailed = 4001
ErrCodeHTTPSetWriteTimeout = 4002
ErrCodeHTTPSendConnectFail = 4003
ErrCodeHTTPSetReadTimeout = 4004
ErrCodeHTTPReadRespFailed = 4005
ErrCodeHTTPProxyAuthFailed = 4006
// TLS错误代码
ErrCodeTLSTCPConnFailed = 5001
ErrCodeTLSHandshakeFailed = 5002
)
// =============================================================================
// HTTP协议常量 (从HTTPDialer.go迁移)
// =============================================================================
const (
// HTTP响应状态码
HTTPStatusOK = 200
// HTTP协议常量
HTTPVersion = "HTTP/1.1"
HTTPMethodConnect = "CONNECT"
// HTTP头部常量
HTTPHeaderHost = "Host"
HTTPHeaderProxyAuth = "Proxy-Authorization"
HTTPHeaderAuthBasic = "Basic"
)
// =============================================================================
// 网络协议常量 (从各文件迁移)
// =============================================================================
const (
// 网络协议
NetworkTCP = "tcp"
// 代理协议前缀
ProxyProtocolSOCKS5 = "socks5"
// 认证分隔符
AuthSeparator = ":"
)
// =============================================================================
// 错误消息常量
// =============================================================================
const (
// Manager错误消息
ErrMsgUnsupportedProxyType = "不支持的代理类型"
ErrMsgEmptyConfig = "配置不能为空"
// SOCKS5错误消息
ErrMsgSOCKS5ParseFailed = "SOCKS5代理地址解析失败"
ErrMsgSOCKS5CreateFailed = "SOCKS5拨号器创建失败"
ErrMsgSOCKS5ConnTimeout = "SOCKS5连接超时"
ErrMsgSOCKS5ConnFailed = "SOCKS5连接失败"
// 直连错误消息
ErrMsgDirectConnFailed = "直连失败"
// HTTP代理错误消息
ErrMsgHTTPConnFailed = "连接HTTP代理服务器失败"
ErrMsgHTTPSetWriteTimeout = "设置写超时失败"
ErrMsgHTTPSendConnectFail = "发送CONNECT请求失败"
ErrMsgHTTPSetReadTimeout = "设置读超时失败"
ErrMsgHTTPReadRespFailed = "读取HTTP响应失败"
ErrMsgHTTPProxyAuthFailed = "HTTP代理连接失败状态码: %d"
// TLS错误消息
ErrMsgTLSTCPConnFailed = "建立TCP连接失败"
ErrMsgTLSHandshakeFailed = "TLS握手失败"
)
// =============================================================================
// 缓存键前缀常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存键前缀
CacheKeySOCKS5 = "socks5_%s"
CacheKeyHTTP = "http_%s"
)
// =============================================================================
// 格式化字符串常量 (从各文件迁移)
// =============================================================================
const (
// SOCKS5 URL格式
SOCKS5URLFormat = "socks5://%s"
SOCKS5URLAuthFormat = "socks5://%s:%s@%s"
// HTTP CONNECT请求格式
HTTPConnectRequestFormat = "CONNECT %s HTTP/1.1\r\nHost: %s\r\n"
HTTPAuthHeaderFormat = "Proxy-Authorization: Basic %s\r\n"
HTTPRequestEndFormat = "\r\n"
)

View File

@ -0,0 +1,315 @@
package utils
import (
"fmt"
"runtime"
"strings"
"testing"
"time"
)
// BenchmarkStringJoinOriginal 原始字符串连接方法
func BenchmarkStringJoinOriginal(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
}
// BenchmarkStringJoinOptimized 优化后的字符串连接方法
func BenchmarkStringJoinOptimized(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinStrings(slice, ",")
_ = result
}
}
// BenchmarkIntJoinOriginal 原始整数连接方法
func BenchmarkIntJoinOriginal(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = fmt.Sprintf("%d", slice[0])
for j := 1; j < len(slice); j++ {
result += "," + fmt.Sprintf("%d", slice[j])
}
}
_ = result
}
}
// BenchmarkIntJoinOptimized 优化后的整数连接方法
func BenchmarkIntJoinOptimized(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinInts(slice, ",")
_ = result
}
}
// BenchmarkDeduplicateOriginal 原始去重方法
func BenchmarkDeduplicateOriginal(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
temp := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
_ = result
}
}
// BenchmarkDeduplicateOptimized 优化后的去重方法
func BenchmarkDeduplicateOptimized(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := DeduplicateStrings(slice)
_ = result
}
}
// BenchmarkSliceAllocationOriginal 原始切片分配方法
func BenchmarkSliceAllocationOriginal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result []string
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// BenchmarkSliceAllocationOptimized 优化后的切片分配方法
func BenchmarkSliceAllocationOptimized(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := make([]string, 0, 50)
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// TestMemoryUsage 内存使用情况对比测试
func TestMemoryUsage(t *testing.T) {
// 测试字符串连接的内存使用
t.Run("StringJoin", func(t *testing.T) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("test-string-%d", i)
}
// 测试原始方法
runtime.GC()
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
origAlloc := m2.TotalAlloc - m1.TotalAlloc
// 测试优化方法
runtime.GC()
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
result := JoinStrings(slice, ",")
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
optAlloc := m2.TotalAlloc - m1.TotalAlloc
t.Logf("原始方法内存分配: %d bytes", origAlloc)
t.Logf("优化方法内存分配: %d bytes", optAlloc)
t.Logf("内存减少: %.2f%%", float64(origAlloc-optAlloc)/float64(origAlloc)*100)
})
}
// TestPoolReuse 测试对象池复用效果StringBuilderPool
func TestPoolReuse(t *testing.T) {
// 重置计数器
pool := NewStringBuilderPool(1024)
// 执行多次操作
slice := []string{"a", "b", "c", "d", "e"}
for i := 0; i < 100; i++ {
result := pool.JoinStrings(slice, ",")
_ = result
}
// GetReusedCount 方法已移除,无法测试复用次数
t.Log("字符串构建器池测试完成")
// 切片池相关功能已移除,跳过该测试
t.Log("切片池功能已移除")
}
// TestStringBuilderCorrectness 测试字符串构建器正确性
func TestStringBuilderCorrectness(t *testing.T) {
testCases := []struct {
slice []string
sep string
want string
}{
{[]string{}, ",", ""},
{[]string{"a"}, ",", "a"},
{[]string{"a", "b"}, ",", "a,b"},
{[]string{"hello", "world", "test"}, " ", "hello world test"},
{[]string{"1", "2", "3", "4", "5"}, "-", "1-2-3-4-5"},
}
for _, tc := range testCases {
got := JoinStrings(tc.slice, tc.sep)
want := strings.Join(tc.slice, tc.sep)
if got != want {
t.Errorf("JoinStrings(%v, %q) = %q, want %q", tc.slice, tc.sep, got, want)
}
}
}
// TestIntJoinCorrectness 测试整数连接正确性
func TestIntJoinCorrectness(t *testing.T) {
testCases := []struct {
slice []int
sep string
want string
}{
{[]int{}, ",", ""},
{[]int{1}, ",", "1"},
{[]int{1, 2}, ",", "1,2"},
{[]int{10, 20, 30}, "-", "10-20-30"},
}
for _, tc := range testCases {
got := JoinInts(tc.slice, tc.sep)
if got != tc.want {
t.Errorf("JoinInts(%v, %q) = %q, want %q", tc.slice, tc.sep, got, tc.want)
}
}
}
// BenchmarkCredentialGeneration 模拟SSH凭证生成的性能测试
func BenchmarkCredentialGeneration(b *testing.B) {
users := []string{"admin", "root", "user", "test", "guest"}
passwords := make([]string, 20)
for i := range passwords {
passwords[i] = fmt.Sprintf("password%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 模拟SSH凭证生成过程
totalCreds := len(users) * len(passwords)
credentials := make([]struct{ user, pass string }, 0, totalCreds)
for _, user := range users {
for _, pass := range passwords {
credentials = append(credentials, struct{ user, pass string }{user, pass})
}
}
_ = credentials
}
}
// Example 展示如何使用优化后的工具
func ExampleJoinStrings() {
slice := []string{"apple", "banana", "cherry"}
result := JoinStrings(slice, ", ")
fmt.Println(result)
// Output: apple, banana, cherry
}
func ExampleJoinInts() {
ports := []int{80, 443, 8080, 9090}
result := JoinInts(ports, ",")
fmt.Println(result)
// Output: 80,443,8080,9090
}
// 运行时长度测试 - 验证在不同规模下的表现
func TestScalability(t *testing.T) {
sizes := []int{10, 100, 1000, 5000}
for _, size := range sizes {
t.Run(fmt.Sprintf("Size%d", size), func(t *testing.T) {
// 准备数据
slice := make([]string, size)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
start := time.Now()
result := JoinStrings(slice, ",")
duration := time.Since(start)
t.Logf("规模 %d: 耗时 %v, 结果长度 %d", size, duration, len(result))
// 验证结果正确性(只检查开头和结尾)
expected := strings.Join(slice, ",")
if result != expected {
t.Errorf("结果不匹配")
}
})
}
}

140
Common/utils/memmonitor.go Normal file
View File

@ -0,0 +1,140 @@
package utils
import (
"log"
"runtime"
"time"
)
// MemoryMonitor 内存监控器
type MemoryMonitor struct {
maxHeapMB uint64 // 最大堆内存阈值(MB)
maxGoroutines int // 最大goroutine数量阈值
checkInterval time.Duration // 检查间隔
running bool // 是否运行中
stopChan chan bool
}
// NewMemoryMonitor 创建新的内存监控器
func NewMemoryMonitor(maxHeapMB uint64, maxGoroutines int, checkInterval time.Duration) *MemoryMonitor {
return &MemoryMonitor{
maxHeapMB: maxHeapMB,
maxGoroutines: maxGoroutines,
checkInterval: checkInterval,
stopChan: make(chan bool, 1),
}
}
// Start 启动内存监控
func (mm *MemoryMonitor) Start() {
if mm.running {
return
}
mm.running = true
go mm.monitor()
}
// Stop 停止内存监控
func (mm *MemoryMonitor) Stop() {
if !mm.running {
return
}
mm.running = false
select {
case mm.stopChan <- true:
default:
}
}
// monitor 监控循环
func (mm *MemoryMonitor) monitor() {
ticker := time.NewTicker(mm.checkInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
mm.checkMemory()
case <-mm.stopChan:
return
}
}
}
// checkMemory 检查内存使用情况
func (mm *MemoryMonitor) checkMemory() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
heapMB := m.HeapInuse / 1024 / 1024
goroutineCount := runtime.NumGoroutine()
// 检查堆内存使用
if heapMB > mm.maxHeapMB {
log.Printf("[WARN] 内存使用警告: 堆内存使用过高 %d MB (阈值: %d MB)", heapMB, mm.maxHeapMB)
// 尝试触发GC
runtime.GC()
// 再次检查
runtime.ReadMemStats(&m)
heapMBAfterGC := m.HeapInuse / 1024 / 1024
log.Printf("[INFO] GC后堆内存: %d MB", heapMBAfterGC)
}
// 检查goroutine数量
if goroutineCount > mm.maxGoroutines {
log.Printf("[WARN] Goroutine数量警告: 当前数量 %d (阈值: %d)", goroutineCount, mm.maxGoroutines)
}
}
// GetMemoryStats 获取当前内存统计信息
func (mm *MemoryMonitor) GetMemoryStats() map[string]interface{} {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return map[string]interface{}{
"heap_inuse_mb": m.HeapInuse / 1024 / 1024,
"heap_alloc_mb": m.HeapAlloc / 1024 / 1024,
"sys_mb": m.Sys / 1024 / 1024,
"num_gc": m.NumGC,
"num_goroutines": runtime.NumGoroutine(),
"last_gc_time": time.Unix(0, int64(m.LastGC)),
}
}
// ForceGC 强制执行垃圾回收
func (mm *MemoryMonitor) ForceGC() {
before := mm.getHeapSize()
runtime.GC()
after := mm.getHeapSize()
// 使用log包直接输出避免undefined common错误
log.Printf("[INFO] 强制GC: 释放内存 %d MB", (before-after)/1024/1024)
}
// getHeapSize 获取当前堆大小
func (mm *MemoryMonitor) getHeapSize() uint64 {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return m.HeapInuse
}
// 默认内存监控器实例
var DefaultMemMonitor = NewMemoryMonitor(
512, // 最大堆内存512MB
1000, // 最大1000个goroutines
30*time.Second, // 30秒检查一次
)
// StartDefaultMonitor 启动默认内存监控器
func StartDefaultMonitor() {
DefaultMemMonitor.Start()
}
// StopDefaultMonitor 停止默认内存监控器
func StopDefaultMonitor() {
DefaultMemMonitor.Stop()
}

26
Common/utils/slicepool.go Normal file
View File

@ -0,0 +1,26 @@
package utils
// DeduplicateStrings 高效字符串去重 - 简化版本
func DeduplicateStrings(slice []string) []string {
if len(slice) <= 1 {
return slice
}
// 使用最简单高效的实现
seen := make(map[string]struct{}, len(slice))
result := make([]string, 0, len(slice))
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -0,0 +1,128 @@
package utils
import (
"strconv"
"strings"
"sync"
"sync/atomic"
)
// StringBuilderPool 字符串构建器池
type StringBuilderPool struct {
pool sync.Pool
reused int64 // 复用计数器
maxSize int // 最大保留大小,防止内存无限增长
}
// NewStringBuilderPool 创建新的字符串构建器池
func NewStringBuilderPool(maxSize int) *StringBuilderPool {
if maxSize <= 0 {
maxSize = 64 * 1024 // 默认64KB最大保留大小
}
return &StringBuilderPool{
pool: sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
},
maxSize: maxSize,
}
}
// Get 获取字符串构建器
func (p *StringBuilderPool) Get() *strings.Builder {
builder := p.pool.Get().(*strings.Builder)
builder.Reset() // 清空内容但保留容量
atomic.AddInt64(&p.reused, 1)
return builder
}
// Put 归还字符串构建器
func (p *StringBuilderPool) Put(builder *strings.Builder) {
if builder == nil {
return
}
// 如果容量超过最大限制,不放回池中以避免内存泄露
if builder.Cap() > p.maxSize {
return
}
p.pool.Put(builder)
}
// JoinStrings 高效连接字符串切片
func (p *StringBuilderPool) JoinStrings(slice []string, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return slice[0]
}
builder := p.Get()
defer p.Put(builder)
// 预估容量:所有字符串长度 + 分隔符长度
var totalLen int
for _, s := range slice {
totalLen += len(s)
}
totalLen += len(sep) * (len(slice) - 1)
// 如果当前容量不足,预先增长
if builder.Cap() < totalLen {
builder.Grow(totalLen)
}
builder.WriteString(slice[0])
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(slice[i])
}
return builder.String()
}
// JoinInts 高效连接整数切片
func (p *StringBuilderPool) JoinInts(slice []int, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return strconv.Itoa(slice[0])
}
builder := p.Get()
defer p.Put(builder)
// 预估容量平均每个整数4字符 + 分隔符
estimatedLen := len(slice)*4 + len(sep)*(len(slice)-1)
if builder.Cap() < estimatedLen {
builder.Grow(estimatedLen)
}
builder.WriteString(strconv.Itoa(slice[0]))
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(strconv.Itoa(slice[i]))
}
return builder.String()
}
// 全局字符串构建器池实例
var GlobalStringBuilderPool = NewStringBuilderPool(64 * 1024)
// 便捷函数,使用全局池
func JoinStrings(slice []string, sep string) string {
return GlobalStringBuilderPool.JoinStrings(slice, sep)
}
func JoinInts(slice []int, sep string) string {
return GlobalStringBuilderPool.JoinInts(slice, sep)
}

156
Core/AliveScanner.go Normal file
View File

@ -0,0 +1,156 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
"strings"
"sync"
"time"
)
/*
AliveScanner.go - 存活探测扫描器
专门用于主机存活探测仅执行ICMP/Ping检测
快速识别网络中的存活主机不进行端口扫描
*/
// AliveScanStrategy 存活探测扫描策略
type AliveScanStrategy struct {
*BaseScanStrategy
startTime time.Time
stats AliveStats
}
// AliveStats 存活探测统计信息
type AliveStats struct {
TotalHosts int // 总主机数
AliveHosts int // 存活主机数
DeadHosts int // 死亡主机数
ScanDuration time.Duration // 扫描耗时
SuccessRate float64 // 成功率
AliveHostList []string // 存活主机列表
}
// NewAliveScanStrategy 创建新的存活探测扫描策略
func NewAliveScanStrategy() *AliveScanStrategy {
return &AliveScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("存活探测", FilterNone),
startTime: time.Now(),
}
}
// Name 返回策略名称
func (s *AliveScanStrategy) Name() string {
return i18n.GetText("scan_strategy_alive_name")
}
// Description 返回策略描述
func (s *AliveScanStrategy) Description() string {
return i18n.GetText("scan_strategy_alive_desc")
}
// Execute 执行存活探测扫描策略
func (s *AliveScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标
if info.Host == "" {
common.LogError(i18n.GetText("parse_error_target_empty"))
return
}
// 输出存活探测开始信息
common.LogBase(i18n.GetText("scan_alive_start"))
// 执行存活探测
s.performAliveScan(info)
// 输出统计信息
s.outputStats()
}
// performAliveScan 执行存活探测
func (s *AliveScanStrategy) performAliveScan(info common.HostInfo) {
// 解析目标主机
hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts)
if err != nil {
common.LogError(i18n.GetText("parse_error_target_failed", err))
return
}
if len(hosts) == 0 {
common.LogError(i18n.GetText("parse_error_no_hosts"))
return
}
// 初始化统计信息
s.stats.TotalHosts = len(hosts)
s.stats.AliveHosts = 0
s.stats.DeadHosts = 0
// 显示扫描信息
if len(hosts) == 1 {
common.LogBase(i18n.GetText("scan_alive_single_target", hosts[0]))
} else {
common.LogBase(i18n.GetText("scan_alive_multiple_targets", len(hosts), hosts[0]))
}
// 执行存活检测
aliveList := CheckLive(hosts, false) // 使用ICMP探测
// 更新统计信息
s.stats.AliveHosts = len(aliveList)
s.stats.DeadHosts = s.stats.TotalHosts - s.stats.AliveHosts
s.stats.ScanDuration = time.Since(s.startTime)
s.stats.AliveHostList = aliveList // 存储存活主机列表
if s.stats.TotalHosts > 0 {
s.stats.SuccessRate = float64(s.stats.AliveHosts) / float64(s.stats.TotalHosts) * 100
}
}
// outputStats 输出详细统计信息
func (s *AliveScanStrategy) outputStats() {
// 输出分隔线
common.LogBase("=" + strings.Repeat("=", 60))
// 输出扫描结果摘要
common.LogBase(i18n.GetText("scan_alive_summary_title"))
// 基础统计
common.LogBase(i18n.GetText("scan_alive_total_hosts", s.stats.TotalHosts))
common.LogBase(i18n.GetText("scan_alive_hosts_found", s.stats.AliveHosts))
common.LogBase(i18n.GetText("scan_alive_dead_hosts", s.stats.DeadHosts))
common.LogBase(i18n.GetText("scan_alive_success_rate", s.stats.SuccessRate))
common.LogBase(i18n.GetText("scan_alive_duration", s.stats.ScanDuration.Round(time.Millisecond)))
// 如果有存活主机,显示详细列表
if s.stats.AliveHosts > 0 {
common.LogBase("")
common.LogBase(i18n.GetText("scan_alive_hosts_list"))
for i, host := range s.stats.AliveHostList {
common.LogSuccess(fmt.Sprintf(" [%d] %s", i+1, host))
}
}
// 输出分隔线
common.LogBase("=" + strings.Repeat("=", 60))
}
// PrepareTargets 存活探测不需要准备扫描目标
func (s *AliveScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 存活探测不需要返回目标列表,因为它不进行后续扫描
return nil
}
// GetPlugins 存活探测不使用插件
func (s *AliveScanStrategy) GetPlugins() ([]string, bool) {
return []string{}, false
}
// IsPluginApplicable 存活探测不适用任何插件
func (s *AliveScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
return false
}

295
Core/BaseScanStrategy.go Normal file
View File

@ -0,0 +1,295 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strings"
)
/*
BaseScanStrategy.go - 扫描策略基础类
提供所有扫描策略的通用功能包括插件管理验证
日志输出等减少代码重复并提升维护性
*/
// PluginFilterType 插件过滤类型
type PluginFilterType int
const (
FilterNone PluginFilterType = iota // 不过滤
FilterLocal // 仅本地插件
FilterService // 仅服务插件(排除本地)
FilterWeb // 仅Web插件
)
// BaseScanStrategy 扫描策略基础类
type BaseScanStrategy struct {
strategyName string
filterType PluginFilterType
}
// NewBaseScanStrategy 创建基础扫描策略
func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStrategy {
return &BaseScanStrategy{
strategyName: name,
filterType: filterType,
}
}
// =============================================================================
// 插件管理通用方法
// =============================================================================
// GetPlugins 获取插件列表(通用实现)
func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了特定插件且不是"all"
if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(common.ScanMode)
if len(requestedPlugins) == 0 {
requestedPlugins = []string{common.ScanMode}
}
// 验证插件是否存在(使用新插件系统)
var validPlugins []string
for _, name := range requestedPlugins {
if GlobalPluginAdapter.PluginExists(name) {
validPlugins = append(validPlugins, name)
}
}
return validPlugins, true
}
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
return GlobalPluginAdapter.GetAllPluginNames(), false
}
// GetApplicablePlugins 获取适用的插件列表(用于日志显示)
func (b *BaseScanStrategy) GetApplicablePlugins(allPlugins []string, isCustomMode bool) []string {
if isCustomMode {
return allPlugins
}
var applicablePlugins []string
for _, pluginName := range allPlugins {
if !GlobalPluginAdapter.PluginExists(pluginName) {
continue
}
if b.isPluginTypeMatchedByName(pluginName) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
return applicablePlugins
}
// isPluginTypeMatchedByName 根据插件名称检查类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
switch b.filterType {
case FilterLocal:
return metadata.Category == "local"
case FilterService:
return metadata.Category == "service"
case FilterWeb:
return metadata.Category == "web"
default:
return true
}
}
// isPluginTypeMatched 检查插件类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
switch b.filterType {
case FilterLocal:
return plugin.HasType(common.PluginTypeLocal)
case FilterService:
return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb:
return plugin.HasType(common.PluginTypeWeb)
default:
return true
}
}
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetPort) {
return true
}
// 检查类型匹配
if !b.isPluginTypeMatchedByName(pluginName) {
return false
}
// 检查端口匹配(如果指定了端口)
if targetPort > 0 && len(metadata.Ports) > 0 {
for _, port := range metadata.Ports {
if port == targetPort {
return true
}
}
return false
}
return true
}
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool {
// 只对服务扫描策略启用Web插件智能检测
if b.filterType != FilterService {
return false
}
// 只对Web类别的插件进行检测
if metadata.Category != "web" {
return false
}
// 如果没有指定端口,跳过检测
if targetPort <= 0 {
return false
}
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测
return globalWebDetector.IsCommonWebPort(targetPort)
}
// globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 检查插件类型过滤
if !b.isPluginTypeMatched(plugin) {
return false
}
// 对于服务扫描,还需检查端口匹配
if b.filterType == FilterService {
// 无端口限制的插件适用于所有端口
if len(plugin.Ports) == 0 {
return true
}
// 有端口限制的插件:检查端口是否匹配
if targetPort > 0 {
return plugin.HasPort(targetPort)
}
// 如果没有提供目标端口,则不执行有端口限制的插件
return false
}
return true
}
// =============================================================================
// 日志输出通用方法
// =============================================================================
// LogPluginInfo 输出插件信息(通用实现)
func (b *BaseScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := b.GetPlugins()
applicablePlugins := b.GetApplicablePlugins(allPlugins, isCustomMode)
// 生成日志消息
var messageKey, prefix string
switch b.filterType {
case FilterLocal:
messageKey = "scan_plugins_local"
prefix = i18n.GetText("scan_mode_local_prefix")
case FilterService:
messageKey = "scan_plugins_service"
prefix = i18n.GetText("scan_mode_service_prefix")
case FilterWeb:
messageKey = "scan_plugins_web"
prefix = i18n.GetText("scan_mode_web_prefix")
default:
messageKey = "scan_plugins_custom"
prefix = ""
}
if len(applicablePlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText("scan_plugins_custom_specified", strings.Join(applicablePlugins, ", "))))
} else {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText(messageKey, strings.Join(applicablePlugins, ", "))))
}
} else {
noPluginsKey := fmt.Sprintf("scan_no_%s_plugins", b.getFilterTypeName())
common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText(noPluginsKey)))
}
}
// getFilterTypeName 获取过滤器类型名称
func (b *BaseScanStrategy) getFilterTypeName() string {
switch b.filterType {
case FilterLocal:
return "local"
case FilterService:
return "service"
case FilterWeb:
return "web"
default:
return "general"
}
}
// =============================================================================
// 验证通用方法
// =============================================================================
// ValidateConfiguration 验证扫描配置(通用实现)
func (b *BaseScanStrategy) ValidateConfiguration() error {
return validateScanPlugins()
}
// =============================================================================
// 通用辅助方法
// =============================================================================
// LogScanStart 输出扫描开始信息
func (b *BaseScanStrategy) LogScanStart() {
switch b.filterType {
case FilterLocal:
common.LogBase(i18n.GetText("scan_local_start"))
case FilterService:
common.LogBase(i18n.GetText("scan_service_start"))
case FilterWeb:
common.LogBase(i18n.GetText("scan_web_start"))
default:
common.LogBase(i18n.GetText("scan_general_start"))
}
}

View File

@ -1,9 +1,11 @@
package Core package core
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"net" "net"
"os/exec" "os/exec"
@ -14,25 +16,27 @@ import (
) )
var ( var (
AliveHosts []string // 存活主机列表
ExistHosts = make(map[string]struct{}) // 已发现主机记录
livewg sync.WaitGroup // 存活检测等待组 livewg sync.WaitGroup // 存活检测等待组
) )
// CheckLive 检测主机存活状态 // CheckLive 检测主机存活状态
func CheckLive(hostslist []string, Ping bool) []string { func CheckLive(hostslist []string, Ping bool) []string {
// 创建局部存活主机列表,预分配容量避免频繁扩容
aliveHosts := make([]string, 0, len(hostslist))
existHosts := make(map[string]struct{}, len(hostslist))
// 创建主机通道 // 创建主机通道
chanHosts := make(chan string, len(hostslist)) chanHosts := make(chan string, len(hostslist))
// 处理存活主机 // 处理存活主机
go handleAliveHosts(chanHosts, hostslist, Ping) go handleAliveHosts(chanHosts, hostslist, Ping, &aliveHosts, existHosts)
// 根据Ping参数选择检测方式 // 根据Ping参数选择检测方式
if Ping { if Ping {
// 使用ping方式探测 // 使用ping方式探测
RunPing(hostslist, chanHosts) RunPing(hostslist, chanHosts)
} else { } else {
probeWithICMP(hostslist, chanHosts) probeWithICMP(hostslist, chanHosts, &aliveHosts)
} }
// 等待所有检测完成 // 等待所有检测完成
@ -40,9 +44,9 @@ func CheckLive(hostslist []string, Ping bool) []string {
close(chanHosts) close(chanHosts)
// 输出存活统计信息 // 输出存活统计信息
printAliveStats(hostslist) printAliveStats(aliveHosts, hostslist)
return AliveHosts return aliveHosts
} }
// IsContain 检查切片中是否包含指定元素 // IsContain 检查切片中是否包含指定元素
@ -55,11 +59,11 @@ func IsContain(items []string, item string) bool {
return false return false
} }
func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) { func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool, aliveHosts *[]string, existHosts map[string]struct{}) {
for ip := range chanHosts { for ip := range chanHosts {
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) { if _, ok := existHosts[ip]; !ok && IsContain(hostslist, ip) {
ExistHosts[ip] = struct{}{} existHosts[ip] = struct{}{}
AliveHosts = append(AliveHosts, ip) *aliveHosts = append(*aliveHosts, ip)
// 使用Output系统保存存活主机信息 // 使用Output系统保存存活主机信息
protocol := "ICMP" protocol := "ICMP"
@ -67,20 +71,20 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
protocol = "PING" protocol = "PING"
} }
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.HOST, Type: output.TypeHost,
Target: ip, Target: ip,
Status: "alive", Status: "alive",
Details: map[string]interface{}{ Details: map[string]interface{}{
"protocol": protocol, "protocol": protocol,
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
// 保留原有的控制台输出 // 保留原有的控制台输出
if !Common.Silent { if !common.Silent {
Common.LogInfo(Common.GetText("target_alive", ip, protocol)) common.LogInfo(i18n.GetText("target_alive", ip, protocol))
} }
} }
livewg.Done() livewg.Done()
@ -88,16 +92,16 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
} }
// probeWithICMP 使用ICMP方式探测 // probeWithICMP 使用ICMP方式探测
func probeWithICMP(hostslist []string, chanHosts chan string) { func probeWithICMP(hostslist []string, chanHosts chan string, aliveHosts *[]string) {
// 尝试监听本地ICMP // 尝试监听本地ICMP
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err == nil { if err == nil {
RunIcmp1(hostslist, conn, chanHosts) RunIcmp1(hostslist, conn, chanHosts, aliveHosts)
return return
} }
Common.LogError(Common.GetText("icmp_listen_failed", err)) common.LogError(i18n.GetText("icmp_listen_failed", err))
Common.LogBase(Common.GetText("trying_no_listen_icmp")) common.LogBase(i18n.GetText("trying_no_listen_icmp"))
// 尝试无监听ICMP探测 // 尝试无监听ICMP探测
conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second) conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
@ -107,49 +111,93 @@ func probeWithICMP(hostslist []string, chanHosts chan string) {
return return
} }
Common.LogBase(Common.GetText("icmp_connect_failed", err)) common.LogBase(i18n.GetText("icmp_connect_failed", err))
Common.LogBase(Common.GetText("insufficient_privileges")) common.LogBase(i18n.GetText("insufficient_privileges"))
Common.LogBase(Common.GetText("switching_to_ping")) common.LogBase(i18n.GetText("switching_to_ping"))
// 降级使用ping探测 // 降级使用ping探测
RunPing(hostslist, chanHosts) RunPing(hostslist, chanHosts)
} }
// getOptimalTopCount 根据扫描规模智能决定显示数量
func getOptimalTopCount(totalHosts int) int {
switch {
case totalHosts > 50000: // 超大规模扫描
return 20
case totalHosts > 10000: // 大规模扫描
return 15
case totalHosts > 1000: // 中等规模扫描
return 10
case totalHosts > 256: // 小规模扫描
return 5
default:
return 3
}
}
// printAliveStats 打印存活统计信息 // printAliveStats 打印存活统计信息
func printAliveStats(hostslist []string) { func printAliveStats(aliveHosts []string, hostslist []string) {
// 智能计算显示数量
topCount := getOptimalTopCount(len(hostslist))
// 大规模扫描时输出 /16 网段统计 // 大规模扫描时输出 /16 网段统计
if len(hostslist) > 1000 { if len(hostslist) > 1000 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, true) arrTop, arrLen := ArrayCountValueTop(aliveHosts, topCount, true)
for i := 0; i < len(arrTop); i++ { for i := 0; i < len(arrTop); i++ {
Common.LogInfo(Common.GetText("subnet_16_alive", arrTop[i], arrLen[i])) common.LogInfo(i18n.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
} }
} }
// 输出 /24 网段统计 // 输出 /24 网段统计
if len(hostslist) > 256 { if len(hostslist) > 256 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false) arrTop, arrLen := ArrayCountValueTop(aliveHosts, topCount, false)
for i := 0; i < len(arrTop); i++ { for i := 0; i < len(arrTop); i++ {
Common.LogInfo(Common.GetText("subnet_24_alive", arrTop[i], arrLen[i])) common.LogInfo(i18n.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
} }
} }
} }
// RunIcmp1 使用ICMP批量探测主机存活(监听模式) // RunIcmp1 使用ICMP批量探测主机存活(监听模式)
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) { func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string, aliveHosts *[]string) {
endflag := false endflag := false
// 启动监听协程 // 启动监听协程
go func() { go func() {
defer func() {
if r := recover(); r != nil {
common.LogError(fmt.Sprintf("ICMP监听协程异常: %v", r))
}
}()
for { for {
if endflag { if endflag {
return return
} }
// 设置读取超时避免无限期阻塞
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
// 接收ICMP响应 // 接收ICMP响应
msg := make([]byte, 100) msg := make([]byte, 100)
_, sourceIP, _ := conn.ReadFrom(msg) _, sourceIP, err := conn.ReadFrom(msg)
if err != nil {
// 超时错误正常,其他错误则退出
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
return
}
if sourceIP != nil { if sourceIP != nil {
livewg.Add(1) livewg.Add(1)
chanHosts <- sourceIP.String() select {
case chanHosts <- sourceIP.String():
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
} }
} }
}() }()
@ -165,7 +213,7 @@ func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string)
start := time.Now() start := time.Now()
for { for {
// 所有主机都已响应则退出 // 所有主机都已响应则退出
if len(AliveHosts) == len(hostslist) { if len(*aliveHosts) == len(hostslist) {
break break
} }
@ -209,7 +257,13 @@ func RunIcmp2(hostslist []string, chanHosts chan string) {
if icmpalive(host) { if icmpalive(host) {
livewg.Add(1) livewg.Add(1)
chanHosts <- host select {
case chanHosts <- host:
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
} }
}(host) }(host)
} }
@ -268,7 +322,13 @@ func RunPing(hostslist []string, chanHosts chan string) {
if ExecCommandPing(host) { if ExecCommandPing(host) {
livewg.Add(1) livewg.Add(1)
chanHosts <- host select {
case chanHosts <- host:
// 成功发送
default:
// channel已满或已关闭丢弃数据并减少计数
livewg.Done()
}
} }
}(host) }(host)
} }
@ -379,8 +439,8 @@ func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []strin
return return
} }
// 统计各网段出现次数 // 统计各网段出现次数,预分配容量
segmentCounts := make(map[string]int) segmentCounts := make(map[string]int, len(arrInit)/4)
for _, ip := range arrInit { for _, ip := range arrInit {
segments := strings.Split(ip, ".") segments := strings.Split(ip, ".")
if len(segments) != 4 { if len(segments) != 4 {

View File

@ -1,37 +1,41 @@
package Core package core
import ( import (
"fmt" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common/i18n"
"strings"
"sync" "sync"
) )
// LocalScanStrategy 本地扫描策略 // LocalScanStrategy 本地扫描策略
type LocalScanStrategy struct{} type LocalScanStrategy struct {
*BaseScanStrategy
}
// NewLocalScanStrategy 创建新的本地扫描策略 // NewLocalScanStrategy 创建新的本地扫描策略
func NewLocalScanStrategy() *LocalScanStrategy { func NewLocalScanStrategy() *LocalScanStrategy {
return &LocalScanStrategy{} return &LocalScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("本地扫描", FilterLocal),
}
} }
// Name 返回策略名称 // Name 返回策略名称
func (s *LocalScanStrategy) Name() string { func (s *LocalScanStrategy) Name() string {
return "本地扫描" return i18n.GetText("scan_strategy_local_name")
} }
// Description 返回策略描述 // Description 返回策略描述
func (s *LocalScanStrategy) Description() string { func (s *LocalScanStrategy) Description() string {
return "收集本地系统信息" return i18n.GetText("scan_strategy_local_desc")
} }
// Execute 执行本地扫描策略 // Execute 执行本地扫描策略
func (s *LocalScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { func (s *LocalScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogBase("执行本地信息收集") // 输出扫描开始信息
s.LogScanStart()
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := s.ValidateConfiguration(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
@ -46,67 +50,7 @@ func (s *LocalScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg
} }
// PrepareTargets 准备本地扫描目标 // PrepareTargets 准备本地扫描目标
func (s *LocalScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo { func (s *LocalScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 本地扫描只使用传入的目标信息,不做额外处理 // 本地扫描只使用传入的目标信息,不做额外处理
return []Common.HostInfo{info} 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.LogBase(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.LogBase(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", ")))
} else {
Common.LogBase("本地模式: 未找到可用的本地插件")
}
}
// IsPluginApplicable 判断插件是否适用于本地扫描
func (s *LocalScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 非自定义模式下只运行Local类型插件
return plugin.HasType(Common.PluginTypeLocal)
} }

135
Core/PluginAdapter.go Normal file
View File

@ -0,0 +1,135 @@
package core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PluginAdapter 插件适配器
// 提供从新插件系统到旧扫描接口的适配
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// 全局插件适配器实例
var GlobalPluginAdapter = NewPluginAdapter()
// GetAllPluginNames 获取所有插件名称
func (pa *PluginAdapter) GetAllPluginNames() []string {
return pa.registry.GetAll()
}
// PluginExists 检查插件是否存在
func (pa *PluginAdapter) PluginExists(name string) bool {
metadata := pa.registry.GetMetadata(name)
return metadata != nil
}
// GetPluginPorts 获取插件支持的端口
func (pa *PluginAdapter) GetPluginPorts(name string) []int {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
return metadata.Ports
}
return []int{}
}
// GetPluginsByPort 根据端口获取支持的插件
func (pa *PluginAdapter) GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// GetPluginsByType 根据类型获取插件
func (pa *PluginAdapter) GetPluginsByType(pluginType string) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
if metadata.Category == pluginType {
plugins = append(plugins, name)
}
}
}
return plugins
}
// ScanWithPlugin 使用插件进行扫描
func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo) error {
common.LogDebug(fmt.Sprintf("使用新插件架构扫描: %s", pluginName))
// 创建插件实例
plugin, err := pa.registry.Create(pluginName)
if err != nil {
return fmt.Errorf("创建插件 %s 失败: %v", pluginName, err)
}
// 执行扫描
result, err := plugin.Scan(context.Background(), info)
if err != nil {
return fmt.Errorf("插件 %s 扫描失败: %v", pluginName, err)
}
// 处理扫描结果
if result != nil && result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
}
return nil
}
// FilterPluginsByType 按类型过滤插件名称
func FilterPluginsByType(pluginType string) func(name string) bool {
return func(name string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(name)
if metadata == nil {
return false
}
switch pluginType {
case common.PluginTypeService:
return metadata.Category == "service"
case common.PluginTypeWeb:
return metadata.Category == "web"
case common.PluginTypeLocal:
return metadata.Category == "local"
default:
return true
}
}
}
// GetServicePlugins 获取所有服务插件
func GetServicePlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("service")
}
// GetWebPlugins 获取所有Web插件
func GetWebPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("web")
}
// GetLocalPlugins 获取所有本地插件
func GetLocalPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("local")
}

View File

@ -1,8 +1,8 @@
package Core package core
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"strings" "strings"
) )
@ -32,20 +32,20 @@ func parsePluginList(pluginStr string) []string {
// 验证扫描插件的有效性 // 验证扫描插件的有效性
func validateScanPlugins() error { func validateScanPlugins() error {
// 如果未指定扫描模式或使用All模式则无需验证 // 如果未指定扫描模式或使用All模式则无需验证
if Common.ScanMode == "" || Common.ScanMode == "all" { if common.ScanMode == "" || common.ScanMode == "all" {
return nil return nil
} }
// 解析插件列表 // 解析插件列表
plugins := parsePluginList(Common.ScanMode) plugins := parsePluginList(common.ScanMode)
if len(plugins) == 0 { if len(plugins) == 0 {
plugins = []string{Common.ScanMode} plugins = []string{common.ScanMode}
} }
// 验证每个插件是否有效 // 验证每个插件是否有效(使用新插件系统)
var invalidPlugins []string var invalidPlugins []string
for _, plugin := range plugins { for _, plugin := range plugins {
if _, exists := Common.PluginManager[plugin]; !exists { if !GlobalPluginAdapter.PluginExists(plugin) {
invalidPlugins = append(invalidPlugins, plugin) invalidPlugins = append(invalidPlugins, plugin)
} }
} }

View File

@ -0,0 +1,132 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
"strings"
)
/*
PortDiscoveryService.go - 端口发现服务
负责主机存活检测端口扫描等端口发现相关功能
从ServiceScanner中分离出来提高代码可维护性
*/
// PortDiscoveryService 端口发现服务
type PortDiscoveryService struct{}
// NewPortDiscoveryService 创建端口发现服务
func NewPortDiscoveryService() *PortDiscoveryService {
return &PortDiscoveryService{}
}
// DiscoverTargets 发现目标主机和端口
func (p *PortDiscoveryService) DiscoverTargets(hostInput string, baseInfo common.HostInfo) ([]common.HostInfo, error) {
// 解析目标主机
hosts, err := parsers.ParseIP(hostInput, common.HostsFile, common.ExcludeHosts)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("parse_error_target_failed"), err)
}
var targetInfos []common.HostInfo
// 主机存活性检测和端口扫描
if len(hosts) > 0 || len(common.HostPort) > 0 {
// 主机存活检测
if p.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, false)
common.LogBase(i18n.GetText("scan_alive_hosts_count", len(hosts)))
}
// 端口扫描
alivePorts := p.discoverAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = p.convertToTargetInfos(alivePorts, baseInfo)
}
}
return targetInfos, nil
}
// shouldPerformLivenessCheck 判断是否需要执行存活性检测
func (p *PortDiscoveryService) shouldPerformLivenessCheck(hosts []string) bool {
return common.DisablePing == false && len(hosts) > 1
}
// discoverAlivePorts 发现存活的端口
func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
var alivePorts []string
// 根据扫描模式选择端口扫描方式
if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// UDP端口特殊处理当前仅支持SNMP的161端口
udpPorts := p.handleUDPPorts(hosts)
if len(udpPorts) > 0 {
alivePorts = append(alivePorts, udpPorts...)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// 合并额外指定的端口
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.HostPort = nil
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
return alivePorts
}
// convertToTargetInfos 将端口列表转换为目标信息
func (p *PortDiscoveryService) 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(i18n.GetText("parse_error_invalid_target_format", targetIP))
continue
}
info := baseInfo
info.Host = hostParts[0]
info.Ports = hostParts[1]
infos = append(infos, info)
}
return infos
}
// handleUDPPorts 处理UDP端口的特殊逻辑
func (p *PortDiscoveryService) handleUDPPorts(hosts []string) []string {
var udpPorts []string
// 检查是否包含SNMP端口161
portList := parsers.ParsePort(common.Ports)
hasPort161 := false
for _, port := range portList {
if port == 161 {
hasPort161 = true
break
}
}
// 如果端口列表包含161则为每个主机添加UDP 161端口
if hasPort161 {
for _, host := range hosts {
udpPorts = append(udpPorts, fmt.Sprintf("%s:161", host))
}
if len(udpPorts) > 0 {
common.LogBase(i18n.GetText("scan_snmp_udp_ports_added"))
}
}
return udpPorts
}

View File

@ -1,877 +1,40 @@
package Core package core
// 重新导出portfinger包的类型和函数保持向后兼容性
import ( import (
_ "embed" "github.com/shadow1ng/fscan/core/portfinger"
"encoding/hex" "github.com/shadow1ng/fscan/common"
"fmt"
"github.com/shadow1ng/fscan/Common"
"regexp"
"strconv"
"strings"
) )
//go:embed nmap-service-probes.txt // 重新导出类型定义
var ProbeString string type VScan = portfinger.VScan
type Probe = portfinger.Probe
type Match = portfinger.Match
type Directive = portfinger.Directive
type Extras = portfinger.Extras
type Target = portfinger.Target
var v VScan // 改为VScan类型而不是指针 // 兼容原有的全局变量访问模式
var v *VScan
type VScan struct { var null *Probe
Exclude string var commonProbe *Probe
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
func init() { func init() {
Common.LogDebug("开始初始化全局变量") common.LogDebug("初始化PortFinger兼容层")
v = VScan{} // 直接初始化VScan结构体 // 初始化兼容性全局变量
v.Init() v = portfinger.GetGlobalVScan()
null = portfinger.GetNullProbe()
commonProbe = portfinger.GetCommonProbe()
// 获取并检查 NULL 探测器 common.LogDebug("PortFinger兼容层初始化完成")
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
Common.LogDebug("警告: 未找到NULL探测器")
} }
// 获取并检查 GenericLines 探测器 // 重新导出编码函数
if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok { var DecodeData = portfinger.DecodeData
Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(commonProbe.Data))) var DecodePattern = portfinger.DecodePattern
common = &commonProbe
} else {
Common.LogDebug("警告: 未找到GenericLines探测器")
}
Common.LogDebug("全局变量初始化完成")
}
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
Common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
Common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
Common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
Common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
Common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
Common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
Common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
Common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
Common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
Common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
Common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
Common.LogDebug("回退配置: " + p.Fallback)
}
// 判断是否为十六进制编码
func isHexCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
return matchRe.Match(b)
}
// 判断是否为八进制编码
func isOctalCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
return matchRe.Match(b)
}
// 判断是否为结构化转义字符
func isStructCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[aftnrv]`)
return matchRe.Match(b)
}
// 判断是否为正则表达式特殊字符
func isReChar(n int64) bool {
reChars := `.*?+{}()^$|\`
for _, char := range reChars {
if n == int64(char) {
return true
}
}
return false
}
// 判断是否为其他转义序列
func isOtherEscapeCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[^\\]`)
return matchRe.Match(b)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
Common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
Common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
Common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
Common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
Common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
Common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
Common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
Common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
Common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
Common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
Common.LogDebug("开始解析match指令" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
Common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// 解码模式字符串,处理转义序列
func DecodePattern(s string) ([]byte, error) {
Common.LogDebug("开始解码pattern: " + s)
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
var replace []byte
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
if isReChar(byteNum) {
replace = []byte{'\\', uint8(byteNum)}
} else {
replace = []byte{uint8(byteNum)}
}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
replace = structCodeMap[int(match[1])]
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
replace = []byte{uint8(byteNum)}
}
return replace
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
if isOtherEscapeCode(match) {
return match
}
return match
})
Common.LogDebug("pattern解码完成")
return sByteDec2, nil
}
// ProbesRarity 用于按稀有度排序的探测器切片
type ProbesRarity []Probe
// Len 返回切片长度,实现 sort.Interface 接口
func (ps ProbesRarity) Len() int {
return len(ps)
}
// Swap 交换切片中的两个元素,实现 sort.Interface 接口
func (ps ProbesRarity) Swap(i, j int) {
ps[i], ps[j] = ps[j], ps[i]
}
// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
func (ps ProbesRarity) Less(i, j int) bool {
return ps[i].Rarity < ps[j].Rarity
}
// Target 定义目标结构体
type Target struct {
IP string // 目标IP地址
Port int // 目标端口
Protocol string // 协议类型
}
// ContainsPort 检查指定端口是否在探测器的端口范围内
func (p *Probe) ContainsPort(testPort int) bool {
Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
// 检查单个端口
ports := strings.Split(p.Ports, ",")
for _, port := range ports {
port = strings.TrimSpace(port)
cmpPort, err := strconv.Atoi(port)
if err == nil && testPort == cmpPort {
Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
return true
}
}
// 检查端口范围
for _, port := range ports {
port = strings.TrimSpace(port)
if strings.Contains(port, "-") {
portRange := strings.Split(port, "-")
if len(portRange) != 2 {
Common.LogDebug("无效的端口范围格式: " + port)
continue
}
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
if err1 != nil || err2 != nil {
Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
continue
}
if testPort >= start && testPort <= end {
Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
return true
}
}
}
Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
return false
}
// MatchPattern 使用正则表达式匹配响应内容
func (m *Match) MatchPattern(response []byte) bool {
// 将响应转换为字符串并进行匹配
responseStr := string([]rune(string(response)))
foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
if len(foundItems) > 0 {
m.FoundItems = foundItems
Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
return true
}
return false
}
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
Common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 替换版本信息中的占位符
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
Common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
Common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
Common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
func DecodeData(s string) ([]byte, error) {
if len(s) == 0 {
Common.LogDebug("输入数据为空")
return nil, fmt.Errorf("empty input")
}
Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
if replace, ok := structCodeMap[int(match[1])]; ok {
return replace
}
return match
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
return match
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
if len(match) < 2 {
return match
}
if isOtherEscapeCode(match) {
return []byte{match[1]}
}
return match
})
if len(sByteDec2) == 0 {
Common.LogDebug("解码后数据为空")
return nil, fmt.Errorf("decoded data is empty")
}
Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2)) // 重新导出探测器字符串
return sByteDec2, nil var ProbeString = portfinger.ProbeString
}
// GetAddress 获取目标的完整地址IP:端口)
func (t *Target) GetAddress() string {
addr := t.IP + ":" + strconv.Itoa(t.Port)
Common.LogDebug("获取目标地址: " + addr)
return addr
}
// trimBanner 处理和清理横幅数据
func trimBanner(buf []byte) string {
Common.LogDebug("开始处理横幅数据")
bufStr := string(buf)
// 特殊处理SMB协议
if strings.Contains(bufStr, "SMB") {
banner := hex.EncodeToString(buf)
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
Common.LogDebug("检测到SMB协议数据")
plain := banner[0xa2:]
data, err := hex.DecodeString(plain)
if err != nil {
Common.LogDebug("SMB数据解码失败: " + err.Error())
return bufStr
}
// 解析domain
var domain string
var index int
for i, s := range data {
if s != 0 {
domain += string(s)
} else if i+1 < len(data) && data[i+1] == 0 {
index = i + 2
break
}
}
// 解析hostname
var hostname string
remainData := data[index:]
for i, h := range remainData {
if h != 0 {
hostname += string(h)
}
if i+1 < len(remainData) && remainData[i+1] == 0 {
break
}
}
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
Common.LogDebug("SMB横幅: " + smbBanner)
return smbBanner
}
}
// 处理常规数据
var src string
for _, ch := range bufStr {
if ch > 32 && ch < 125 {
src += string(ch)
} else {
src += " "
}
}
// 清理多余空白
re := regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, ".")
result := strings.TrimSpace(src)
Common.LogDebug("处理后的横幅: " + result)
return result
}
// Init 初始化VScan对象
func (v *VScan) Init() {
Common.LogDebug("开始初始化VScan")
v.parseProbesFromContent(ProbeString)
v.parseProbesToMapKName()
v.SetusedProbes()
Common.LogDebug("VScan初始化完成")
}

View File

@ -1,8 +1,9 @@
package Core package core
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core/portfinger"
"io" "io"
"net" "net"
"strings" "strings"
@ -50,11 +51,7 @@ type PortInfoScanner struct {
info *Info // 探测上下文 info *Info // 探测上下文
} }
// 预定义的基础探测器 // 预定义的基础探测器已在PortFinger.go中定义这里不再重复定义
var (
null = new(Probe) // 空探测器,用于基本协议识别
common = new(Probe) // 通用探测器,用于常见服务识别
)
// NewPortInfoScanner 创建新的端口服务识别器实例 // NewPortInfoScanner 创建新的端口服务识别器实例
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner { func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
@ -76,7 +73,7 @@ func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Durat
// Identify 执行服务识别,返回识别结果 // Identify 执行服务识别,返回识别结果
func (s *PortInfoScanner) Identify() (*ServiceInfo, error) { func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
Common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port)) common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
s.info.PortInfo() s.info.PortInfo()
// 构造返回结果 // 构造返回结果
@ -92,7 +89,7 @@ func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
serviceInfo.Extras[k] = v serviceInfo.Extras[k] = v
} }
Common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name)) common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
return serviceInfo, nil return serviceInfo, nil
} }
@ -100,41 +97,41 @@ func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
func (i *Info) PortInfo() { func (i *Info) PortInfo() {
// 1. 首先尝试读取服务的初始响应 // 1. 首先尝试读取服务的初始响应
if response, err := i.Read(); err == nil && len(response) > 0 { if response, err := i.Read(); err == nil && len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response))) common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
// 使用基础探测器检查响应 // 使用基础探测器检查响应
Common.LogDebug("尝试使用基础探测器(null/common)检查响应") common.LogDebug("尝试使用基础探测器(null/common)检查响应")
if i.tryProbes(response, []*Probe{null, common}) { if i.tryProbes(response, []*Probe{null, commonProbe}) {
Common.LogDebug("基础探测器匹配成功") common.LogDebug("基础探测器匹配成功")
return return
} }
Common.LogDebug("基础探测器未匹配") common.LogDebug("基础探测器未匹配")
} else if err != nil { } else if err != nil {
Common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
} }
// 记录已使用的探测器,避免重复使用 // 记录已使用的探测器,避免重复使用
usedProbes := make(map[string]struct{}) usedProbes := make(map[string]struct{})
// 2. 尝试使用端口专用探测器 // 2. 尝试使用端口专用探测器
Common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port)) common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
if i.processPortMapProbes(usedProbes) { if i.processPortMapProbes(usedProbes) {
Common.LogDebug("端口专用探测器匹配成功") common.LogDebug("端口专用探测器匹配成功")
return return
} }
Common.LogDebug("端口专用探测器未匹配") common.LogDebug("端口专用探测器未匹配")
// 3. 使用默认探测器列表 // 3. 使用默认探测器列表
Common.LogDebug("尝试使用默认探测器列表") common.LogDebug("尝试使用默认探测器列表")
if i.processDefaultProbes(usedProbes) { if i.processDefaultProbes(usedProbes) {
Common.LogDebug("默认探测器匹配成功") common.LogDebug("默认探测器匹配成功")
return return
} }
Common.LogDebug("默认探测器未匹配") common.LogDebug("默认探测器未匹配")
// 4. 如果所有探测都失败,标记为未知服务 // 4. 如果所有探测都失败,标记为未知服务
if strings.TrimSpace(i.Result.Service.Name) == "" { if strings.TrimSpace(i.Result.Service.Name) == "" {
Common.LogDebug("未识别出服务,标记为 unknown") common.LogDebug("未识别出服务,标记为 unknown")
i.Result.Service.Name = "unknown" i.Result.Service.Name = "unknown"
} }
} }
@ -142,10 +139,10 @@ func (i *Info) PortInfo() {
// tryProbes 尝试使用指定的探测器列表检查响应 // tryProbes 尝试使用指定的探测器列表检查响应
func (i *Info) tryProbes(response []byte, probes []*Probe) bool { func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
for _, probe := range probes { for _, probe := range probes {
Common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name)) common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
i.GetInfo(response, probe) i.GetInfo(response, probe)
if i.Found { if i.Found {
Common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name)) common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
return true return true
} }
} }
@ -155,28 +152,28 @@ func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
// processPortMapProbes 处理端口映射中的专用探测器 // processPortMapProbes 处理端口映射中的专用探测器
func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool { func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
// 检查是否存在端口专用探测器 // 检查是否存在端口专用探测器
if len(Common.PortMap[i.Port]) == 0 { if len(common.PortMap[i.Port]) == 0 {
Common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port)) common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
return false return false
} }
// 遍历端口专用探测器 // 遍历端口专用探测器
for _, name := range Common.PortMap[i.Port] { for _, name := range common.PortMap[i.Port] {
Common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name)) common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
usedProbes[name] = struct{}{} usedProbes[name] = struct{}{}
probe := v.ProbesMapKName[name] probe := v.ProbesMapKName[name]
// 解码探测数据 // 解码探测数据
probeData, err := DecodeData(probe.Data) probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 { if err != nil || len(probeData) == 0 {
Common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name)) common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
continue continue
} }
// 发送探测数据并获取响应 // 发送探测数据并获取响应
Common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData))) common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
if response := i.Connect(probeData); len(response) > 0 { if response := i.Connect(probeData); len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response))) common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
// 使用当前探测器检查响应 // 使用当前探测器检查响应
i.GetInfo(response, &probe) i.GetInfo(response, &probe)
@ -193,7 +190,7 @@ func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
case "NULL": case "NULL":
continue continue
default: default:
if i.tryProbes(response, []*Probe{common}) { if i.tryProbes(response, []*Probe{commonProbe}) {
return true return true
} }
} }
@ -208,7 +205,7 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
const maxFailures = 10 // 最大失败次数 const maxFailures = 10 // 最大失败次数
// 遍历默认探测器列表 // 遍历默认探测器列表
for _, name := range Common.DefaultMap { for _, name := range common.DefaultMap {
// 跳过已使用的探测器 // 跳过已使用的探测器
if _, used := usedProbes[name]; used { if _, used := usedProbes[name]; used {
continue continue
@ -245,14 +242,14 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
case "NULL": case "NULL":
continue continue
default: default:
if i.tryProbes(response, []*Probe{common}) { if i.tryProbes(response, []*Probe{commonProbe}) {
return true return true
} }
} }
// 尝试使用端口映射中的其他探测器 // 尝试使用端口映射中的其他探测器
if len(Common.PortMap[i.Port]) > 0 { if len(common.PortMap[i.Port]) > 0 {
for _, mappedName := range Common.PortMap[i.Port] { for _, mappedName := range common.PortMap[i.Port] {
usedProbes[mappedName] = struct{}{} usedProbes[mappedName] = struct{}{}
mappedProbe := v.ProbesMapKName[mappedName] mappedProbe := v.ProbesMapKName[mappedName]
i.GetInfo(response, &mappedProbe) i.GetInfo(response, &mappedProbe)
@ -267,11 +264,11 @@ func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
// GetInfo 分析响应数据并提取服务信息 // GetInfo 分析响应数据并提取服务信息
func (i *Info) GetInfo(response []byte, probe *Probe) { func (i *Info) GetInfo(response []byte, probe *Probe) {
Common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response))) common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
// 响应数据有效性检查 // 响应数据有效性检查
if len(response) <= 0 { if len(response) <= 0 {
Common.LogDebug("响应数据为空") common.LogDebug("响应数据为空")
return return
} }
@ -282,25 +279,25 @@ func (i *Info) GetInfo(response []byte, probe *Probe) {
) )
// 处理主要匹配规则 // 处理主要匹配规则
Common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name)) common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
if matched, match := i.processMatches(response, probe.Matchs); matched { if matched, match := i.processMatches(response, probe.Matchs); matched {
Common.LogDebug("找到硬匹配") common.LogDebug("找到硬匹配")
return return
} else if match != nil { } else if match != nil {
Common.LogDebug("找到软匹配") common.LogDebug("找到软匹配")
softFound = true softFound = true
softMatch = *match softMatch = *match
} }
// 处理回退匹配规则 // 处理回退匹配规则
if probe.Fallback != "" { if probe.Fallback != "" {
Common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback)) common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok { if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok {
if matched, match := i.processMatches(response, fbProbe.Matchs); matched { if matched, match := i.processMatches(response, fbProbe.Matchs); matched {
Common.LogDebug("回退匹配成功") common.LogDebug("回退匹配成功")
return return
} else if match != nil { } else if match != nil {
Common.LogDebug("找到回退软匹配") common.LogDebug("找到回退软匹配")
softFound = true softFound = true
softMatch = *match softMatch = *match
} }
@ -309,14 +306,14 @@ func (i *Info) GetInfo(response []byte, probe *Probe) {
// 处理未找到匹配的情况 // 处理未找到匹配的情况
if !i.Found { if !i.Found {
Common.LogDebug("未找到硬匹配,处理未匹配情况") common.LogDebug("未找到硬匹配,处理未匹配情况")
i.handleNoMatch(response, result, softFound, softMatch) i.handleNoMatch(response, result, softFound, softMatch)
} }
} }
// processMatches 处理匹配规则集 // processMatches 处理匹配规则集
func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) { func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) {
Common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches))) common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
var softMatch *Match var softMatch *Match
for _, match := range *matches { for _, match := range *matches {
@ -325,11 +322,11 @@ func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match)
} }
if !match.IsSoft { if !match.IsSoft {
Common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service)) common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
i.handleHardMatch(response, &match) i.handleHardMatch(response, &match)
return true, nil return true, nil
} else if softMatch == nil { } else if softMatch == nil {
Common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service)) common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
tmpMatch := match tmpMatch := match
softMatch = &tmpMatch softMatch = &tmpMatch
} }
@ -340,48 +337,48 @@ func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match)
// handleHardMatch 处理硬匹配结果 // handleHardMatch 处理硬匹配结果
func (i *Info) handleHardMatch(response []byte, match *Match) { func (i *Info) handleHardMatch(response []byte, match *Match) {
Common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service)) common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
result := &i.Result result := &i.Result
extras := match.ParseVersionInfo(response) extras := match.ParseVersionInfo(response)
extrasMap := extras.ToMap() extrasMap := extras.ToMap()
result.Service.Name = match.Service result.Service.Name = match.Service
result.Extras = extrasMap result.Extras = extrasMap
result.Banner = trimBanner(response) result.Banner = portfinger.TrimBanner(string(response))
result.Service.Extras = extrasMap result.Service.Extras = extrasMap
// 特殊处理 microsoft-ds 服务 // 特殊处理 microsoft-ds 服务
if result.Service.Name == "microsoft-ds" { if result.Service.Name == "microsoft-ds" {
Common.LogDebug("特殊处理 microsoft-ds 服务") common.LogDebug("特殊处理 microsoft-ds 服务")
result.Service.Extras["hostname"] = result.Banner result.Service.Extras["hostname"] = result.Banner
} }
i.Found = true i.Found = true
Common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner)) common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
} }
// handleNoMatch 处理未找到匹配的情况 // handleNoMatch 处理未找到匹配的情况
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) { func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
Common.LogDebug("处理未匹配情况") common.LogDebug("处理未匹配情况")
result.Banner = trimBanner(response) result.Banner = portfinger.TrimBanner(string(response))
if !softFound { if !softFound {
// 尝试识别 HTTP 服务 // 尝试识别 HTTP 服务
if strings.Contains(result.Banner, "HTTP/") || if strings.Contains(result.Banner, "HTTP/") ||
strings.Contains(result.Banner, "html") { strings.Contains(result.Banner, "html") {
Common.LogDebug("识别为HTTP服务") common.LogDebug("识别为HTTP服务")
result.Service.Name = "http" result.Service.Name = "http"
} else { } else {
Common.LogDebug("未知服务") common.LogDebug("未知服务")
result.Service.Name = "unknown" result.Service.Name = "unknown"
} }
} else { } else {
Common.LogDebug("使用软匹配结果") common.LogDebug("使用软匹配结果")
extras := softMatch.ParseVersionInfo(response) extras := softMatch.ParseVersionInfo(response)
result.Service.Extras = extras.ToMap() result.Service.Extras = extras.ToMap()
result.Service.Name = softMatch.Service result.Service.Name = softMatch.Service
i.Found = true i.Found = true
Common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name)) common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
} }
} }
@ -408,7 +405,7 @@ func (i *Info) Write(msg []byte) error {
if err != nil && strings.Contains(err.Error(), "close") { if err != nil && strings.Contains(err.Error(), "close") {
i.Conn.Close() i.Conn.Close()
// 连接关闭时重试 - 支持SOCKS5代理 // 连接关闭时重试 - 支持SOCKS5代理
i.Conn, err = Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second) i.Conn, err = common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
if err == nil { if err == nil {
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout))) i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
_, err = i.Conn.Write(msg) _, err = i.Conn.Write(msg)

View File

@ -1,9 +1,12 @@
package Core package core
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/parsers"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"strings" "strings"
@ -15,22 +18,40 @@ import (
// EnhancedPortScan 高性能端口扫描函数 // EnhancedPortScan 高性能端口扫描函数
func EnhancedPortScan(hosts []string, ports string, timeout int64) []string { func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
// 解析端口和排除端口 // 解析端口和排除端口
portList := Common.ParsePort(ports) portList := parsers.ParsePort(ports)
if len(portList) == 0 { if len(portList) == 0 {
Common.LogError("无效端口: " + ports) common.LogError("无效端口: " + ports)
return nil return nil
} }
exclude := make(map[int]struct{}) // 预估排除端口数量通常不会超过100个
for _, p := range Common.ParsePort(Common.ExcludePorts) { excludePorts := parsers.ParsePort(common.ExcludePorts)
exclude := make(map[int]struct{}, len(excludePorts))
for _, p := range excludePorts {
exclude[p] = struct{}{} exclude[p] = struct{}{}
} }
// 计算总扫描数量
totalTasks := 0
for range hosts {
for _, port := range portList {
if _, excluded := exclude[port]; !excluded {
totalTasks++
}
}
}
// 初始化端口扫描进度条
if totalTasks > 0 && common.ShowProgress {
description := i18n.GetText("progress_port_scanning_with_threads", common.ThreadNum)
common.InitProgressBar(int64(totalTasks), description)
}
// 初始化并发控制 // 初始化并发控制
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
to := time.Duration(timeout) * time.Second to := time.Duration(timeout) * time.Second
sem := semaphore.NewWeighted(int64(Common.ThreadNum)) sem := semaphore.NewWeighted(int64(common.ThreadNum))
var count int64 var count int64
var aliveMap sync.Map var aliveMap sync.Map
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
@ -50,10 +71,14 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
} }
g.Go(func() error { g.Go(func() error {
defer sem.Release(1) defer func() {
sem.Release(1)
// 更新端口扫描进度
common.UpdateProgressBar(1)
}()
// 连接测试 - 支持SOCKS5代理 // 连接测试 - 支持SOCKS5代理
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, to) conn, err := common.WrapperTcpWithTimeout("tcp", addr, to)
if err != nil { if err != nil {
return nil return nil
} }
@ -62,14 +87,14 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
// 记录开放端口 // 记录开放端口
atomic.AddInt64(&count, 1) atomic.AddInt64(&count, 1)
aliveMap.Store(addr, struct{}{}) aliveMap.Store(addr, struct{}{})
Common.LogInfo("端口开放 " + addr) common.LogInfo("端口开放 " + addr)
Common.SaveResult(&Common.ScanResult{ common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: Common.PORT, Target: host, Time: time.Now(), Type: output.TypePort, Target: host,
Status: "open", Details: map[string]interface{}{"port": port}, Status: "open", Details: map[string]interface{}{"port": port},
}) })
// 服务识别 // 服务识别
if Common.EnableFingerprint { if common.EnableFingerprint {
if info, err := NewPortInfoScanner(host, port, conn, to).Identify(); err == nil { if info, err := NewPortInfoScanner(host, port, conn, to).Identify(); err == nil {
// 构建结果详情 // 构建结果详情
details := map[string]interface{}{"port": port, "service": info.Name} details := map[string]interface{}{"port": port, "service": info.Name}
@ -94,8 +119,8 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
} }
// 保存服务结果 // 保存服务结果
Common.SaveResult(&Common.ScanResult{ common.SaveResult(&output.ScanResult{
Time: time.Now(), Type: Common.SERVICE, Target: host, Time: time.Now(), Type: output.TypeService, Target: host,
Status: "identified", Details: details, Status: "identified", Details: details,
}) })
@ -127,7 +152,7 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
sb.WriteString(" Banner:[" + strings.TrimSpace(info.Banner) + "]") sb.WriteString(" Banner:[" + strings.TrimSpace(info.Banner) + "]")
} }
Common.LogInfo(sb.String()) common.LogInfo(sb.String())
} }
} }
@ -145,6 +170,12 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
return true return true
}) })
Common.LogBase(fmt.Sprintf("扫描完成, 发现 %d 个开放端口", count)) // 完成端口扫描进度条
if common.IsProgressActive() {
common.FinishProgressBar()
}
common.LogBase(i18n.GetText("scan_complete_ports_found", count))
return aliveAddrs return aliveAddrs
} }

View File

@ -1,289 +1,125 @@
package Core package core
import ( import (
"github.com/shadow1ng/fscan/Common" "fmt"
"github.com/shadow1ng/fscan/Plugins" "github.com/shadow1ng/fscan/common"
"sort" "github.com/shadow1ng/fscan/plugins/base"
// 导入新架构插件,触发自动注册
_ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "github.com/shadow1ng/fscan/plugins/services/ftp"
_ "github.com/shadow1ng/fscan/plugins/services/imap"
_ "github.com/shadow1ng/fscan/plugins/services/kafka"
_ "github.com/shadow1ng/fscan/plugins/services/ldap"
_ "github.com/shadow1ng/fscan/plugins/services/memcached"
_ "github.com/shadow1ng/fscan/plugins/services/modbus"
_ "github.com/shadow1ng/fscan/plugins/services/mongodb"
_ "github.com/shadow1ng/fscan/plugins/services/mssql"
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/neo4j"
_ "github.com/shadow1ng/fscan/plugins/services/oracle"
_ "github.com/shadow1ng/fscan/plugins/services/pop3"
_ "github.com/shadow1ng/fscan/plugins/services/postgresql"
_ "github.com/shadow1ng/fscan/plugins/services/rabbitmq"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/rsync"
_ "github.com/shadow1ng/fscan/plugins/services/smtp"
_ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet"
// 导入Legacy插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios"
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
// 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
) )
// init 初始化并注册所有扫描插件 // =============================================================================
// 包括标准端口服务扫描、特殊扫描类型和本地信息收集等 // 新一代插件注册系统 (New Architecture)
func init() { // 完全基于工厂模式和自动发现的现代化插件架构
// 1. 标准网络服务扫描插件 // =============================================================================
// 文件传输和远程访问服务
Common.RegisterPlugin("ftp", Common.ScanPlugin{
Name: "FTP",
Ports: []int{21},
ScanFunc: Plugins.FtpScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("ssh", Common.ScanPlugin{ // InitializePluginSystem 初始化插件系统
Name: "SSH", func InitializePluginSystem() error {
Ports: []int{22, 2222}, common.LogInfo("初始化新一代插件系统...")
ScanFunc: Plugins.SshScan,
Types: []string{Common.PluginTypeService},
})
Common.RegisterPlugin("telnet", Common.ScanPlugin{ // 统计已注册的插件
Name: "Telnet", registeredPlugins := base.GlobalPluginRegistry.GetAll()
Ports: []int{23}, common.LogInfo(fmt.Sprintf("已注册插件数量: %d", len(registeredPlugins)))
ScanFunc: Plugins.TelnetScan,
Types: []string{Common.PluginTypeService},
})
// Windows网络服务 // 显示已注册的插件列表
Common.RegisterPlugin("findnet", Common.ScanPlugin{ if len(registeredPlugins) > 0 {
Name: "FindNet", common.LogInfo("已注册插件:")
Ports: []int{135}, for _, name := range registeredPlugins {
ScanFunc: Plugins.Findnet, metadata := base.GlobalPluginRegistry.GetMetadata(name)
Types: []string{Common.PluginTypeService}, if metadata != nil {
}) common.LogInfo(fmt.Sprintf(" - %s v%s (%s)",
metadata.Name, metadata.Version, metadata.Category))
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},
})
// 数据库服务
Common.RegisterPlugin("mssql", Common.ScanPlugin{
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},
})
// 中间件和消息队列服务
Common.RegisterPlugin("elasticsearch", Common.ScanPlugin{
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},
})
// 目录和认证服务
Common.RegisterPlugin("ldap", Common.ScanPlugin{
Name: "LDAP",
Ports: []int{389, 636},
ScanFunc: Plugins.LDAPScan,
Types: []string{Common.PluginTypeService},
})
// 邮件服务
Common.RegisterPlugin("smtp", Common.ScanPlugin{
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},
})
// 网络管理和监控服务
Common.RegisterPlugin("snmp", Common.ScanPlugin{
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},
})
// 数据同步和备份服务
Common.RegisterPlugin("rsync", Common.ScanPlugin{
Name: "Rsync",
Ports: []int{873},
ScanFunc: Plugins.RsyncScan,
Types: []string{Common.PluginTypeService},
})
// NoSQL数据库
Common.RegisterPlugin("cassandra", Common.ScanPlugin{
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},
})
// 远程桌面和显示服务
Common.RegisterPlugin("rdp", Common.ScanPlugin{
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},
})
// 缓存和键值存储服务
Common.RegisterPlugin("redis", Common.ScanPlugin{
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. 特殊漏洞扫描插件
Common.RegisterPlugin("ms17010", Common.ScanPlugin{
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应用扫描插件
Common.RegisterPlugin("webtitle", Common.ScanPlugin{
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系统专用插件
Common.RegisterPlugin("smb2", Common.ScanPlugin{
Name: "SMBScan2",
Ports: []int{445},
ScanFunc: Plugins.SmbScan2,
Types: []string{Common.PluginTypeService},
})
// 5. 本地信息收集插件
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
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 返回所有已注册插件的名称列表 common.LogInfo("插件系统初始化完成")
return nil
}
// GetAllPlugins 获取所有已注册插件名称
func GetAllPlugins() []string { func GetAllPlugins() []string {
pluginNames := make([]string, 0, len(Common.PluginManager)) return base.GlobalPluginRegistry.GetAll()
for name := range Common.PluginManager { }
pluginNames = append(pluginNames, name)
// GetPluginMetadata 获取插件元数据
func GetPluginMetadata(name string) *base.PluginMetadata {
return base.GlobalPluginRegistry.GetMetadata(name)
}
// CreatePlugin 创建插件实例
func CreatePlugin(name string) (base.Plugin, error) {
return base.GlobalPluginRegistry.Create(name)
}
// GetPluginsByCategory 按类别获取插件
func GetPluginsByCategory(category string) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
if metadata.Category == category {
plugins = append(plugins, name)
}
}
}
return plugins
}
// GetPluginsByPort 按端口获取插件
func GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// init 自动初始化插件系统
func init() {
if err := InitializePluginSystem(); err != nil {
common.LogError("插件系统初始化失败: " + err.Error())
} }
sort.Strings(pluginNames)
return pluginNames
} }

View File

@ -1,122 +1,93 @@
package Core package core
import ( import (
"fmt" "fmt"
"github.com/schollz/progressbar/v3" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/WebScan/lib" "github.com/shadow1ng/fscan/webscan/lib"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
) )
// ScanTask 表示单个扫描任务
type ScanTask struct {
pluginName string // 插件名称
target Common.HostInfo // 目标信息
}
// ScanStrategy 定义扫描策略接口 // ScanStrategy 定义扫描策略接口(简化版)
type ScanStrategy interface { type ScanStrategy interface {
// 名称和描述
Name() string
Description() string
// 执行扫描的主要方法 // 执行扫描的主要方法
Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup)
// 插件管理方法 // 插件管理方法
GetPlugins() ([]string, bool) GetPlugins() ([]string, bool)
LogPluginInfo() IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool
// 任务准备方法
PrepareTargets(info Common.HostInfo) []Common.HostInfo
IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool
}
// Scanner 扫描器结构体
type Scanner struct {
strategy ScanStrategy
}
// NewScanner 创建新的扫描器并选择合适的策略
func NewScanner(info Common.HostInfo) *Scanner {
scanner := &Scanner{}
scanner.selectStrategy(info)
return scanner
} }
// selectStrategy 根据扫描配置选择适当的扫描策略 // selectStrategy 根据扫描配置选择适当的扫描策略
func (s *Scanner) selectStrategy(info Common.HostInfo) { func selectStrategy(info common.HostInfo) ScanStrategy {
switch { switch {
case Common.LocalMode: case common.AliveOnly || common.ScanMode == "icmp":
s.strategy = NewLocalScanStrategy() common.LogBase(i18n.GetText("scan_mode_alive_selected"))
Common.LogBase("已选择本地扫描模式") return NewAliveScanStrategy()
case len(Common.URLs) > 0: case common.LocalMode:
s.strategy = NewWebScanStrategy() common.LogBase(i18n.GetText("scan_mode_local_selected"))
Common.LogBase("已选择Web扫描模式") return NewLocalScanStrategy()
case len(common.URLs) > 0:
common.LogBase(i18n.GetText("scan_mode_web_selected"))
return NewWebScanStrategy()
default: default:
s.strategy = NewServiceScanStrategy() common.LogBase(i18n.GetText("scan_mode_service_selected"))
Common.LogBase("已选择服务扫描模式") return NewServiceScanStrategy()
} }
} }
// Scan 执行整体扫描流程 // RunScan 执行整体扫描流程(简化版,移除不必要的包装)
func (s *Scanner) Scan(info Common.HostInfo) { func RunScan(info common.HostInfo) {
Common.LogBase("开始信息扫描") common.LogBase(i18n.GetText("scan_info_start"))
lib.Inithttp() lib.Inithttp()
// 选择策略
strategy := selectStrategy(info)
// 并发控制初始化 // 并发控制初始化
ch := make(chan struct{}, Common.ThreadNum) ch := make(chan struct{}, common.ThreadNum)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
// 执行策略 // 执行策略
s.strategy.Execute(info, &ch, &wg) strategy.Execute(info, &ch, &wg)
// 等待所有扫描完成 // 等待所有扫描完成
wg.Wait() wg.Wait()
s.finishScan()
// 完成扫描
finishScan()
} }
// finishScan 完成扫描并输出结果 // finishScan 完成扫描并输出结果
func (s *Scanner) finishScan() { func finishScan() {
if Common.ProgressBar != nil { // 确保进度条正确完成
Common.ProgressBar.Finish() if common.IsProgressActive() {
fmt.Println() common.FinishProgressBar()
}
Common.LogBase(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
} }
// 任务执行通用框架 // 输出扫描完成信息
func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) { common.LogBase(i18n.GetText("scan_task_complete", common.End, common.Num))
}
// ExecuteScanTasks 任务执行通用框架(流式处理,无预构建任务列表)
func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) {
// 获取要执行的插件 // 获取要执行的插件
pluginsToRun, isCustomMode := strategy.GetPlugins() pluginsToRun, isCustomMode := strategy.GetPlugins()
// 准备扫描任务 // 预计算任务数量用于进度条
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy) taskCount := countApplicableTasks(targets, pluginsToRun, isCustomMode, strategy)
// 输出扫描计划
if Common.ShowScanPlan && len(tasks) > 0 {
logScanPlan(tasks)
}
// 初始化进度条 // 初始化进度条
if len(tasks) > 0 && Common.ShowProgress { if taskCount > 0 && common.ShowProgress {
initProgressBar(len(tasks)) description := i18n.GetText("progress_scanning_description")
common.InitProgressBar(int64(taskCount), description)
} }
// 执行所有任务 // 流式执行任务,避免预构建大量任务对象
for _, task := range tasks {
scheduleScanTask(task.pluginName, task.target, ch, wg)
}
}
// 准备扫描任务列表
func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) []ScanTask {
var tasks []ScanTask
for _, target := range targets { for _, target := range targets {
targetPort := 0 targetPort := 0
if target.Ports != "" { if target.Ports != "" {
@ -124,123 +95,74 @@ func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustom
} }
for _, pluginName := range pluginsToRun { for _, pluginName := range pluginsToRun {
plugin, exists := Common.PluginManager[pluginName] if !GlobalPluginAdapter.PluginExists(pluginName) {
if !exists {
continue continue
} }
// 检查插件是否适用于当前目标 (通过策略判断) // 检查插件是否适用于当前目标
if strategy.IsPluginApplicable(plugin, targetPort, isCustomMode) { if strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
tasks = append(tasks, ScanTask{ executeScanTask(pluginName, target, ch, wg)
pluginName: pluginName, }
target: target,
})
} }
} }
} }
return tasks // countApplicableTasks 计算适用的任务数量(用于进度条初始化)
func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) int {
count := 0
for _, target := range targets {
targetPort := 0
if target.Ports != "" {
targetPort, _ = strconv.Atoi(target.Ports)
} }
// logScanPlan 输出扫描计划信息 for _, pluginName := range pluginsToRun {
func logScanPlan(tasks []ScanTask) { if GlobalPluginAdapter.PluginExists(pluginName) &&
// 统计每个插件的目标数量 strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
pluginCounts := make(map[string]int) count++
for _, task := range tasks { }
pluginCounts[task.pluginName]++ }
}
return count
} }
// 构建扫描计划信息
var planInfo strings.Builder
planInfo.WriteString("扫描计划:\n")
for plugin, count := range pluginCounts { // executeScanTask 执行单个扫描任务(合并原 scheduleScanTask 和 executeSingleScan
planInfo.WriteString(fmt.Sprintf(" - %s: %d 个目标\n", plugin, count)) func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
}
Common.LogBase(planInfo.String())
}
// 初始化进度条
func initProgressBar(totalTasks int) {
Common.ProgressBar = progressbar.NewOptions(totalTasks,
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowCount(),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("[cyan]扫描进度:[reset]"),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionUseANSICodes(true),
progressbar.OptionSetRenderBlankState(true),
)
}
// 调度单个扫描任务
func scheduleScanTask(pluginName string, target Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
wg.Add(1) wg.Add(1)
*ch <- struct{}{} // 获取并发槽位 *ch <- struct{}{} // 获取并发槽位
go func() { go func() {
startTime := time.Now() // 开始监控插件任务
monitor := common.GetConcurrencyMonitor()
monitor.StartPluginTask()
defer func() { defer func() {
// 捕获并记录任何可能的panic // 捕获并记录任何可能的panic
if r := recover(); r != nil { if r := recover(); r != nil {
Common.LogError(fmt.Sprintf("[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v", common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_panic"),
pluginName, target.Host, target.Ports, r)) pluginName, target.Host, target.Ports, r))
} }
// 完成任务,释放资源 // 完成任务,释放资源
duration := time.Since(startTime) monitor.FinishPluginTask()
if Common.ShowScanPlan {
Common.LogBase(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)",
pluginName, target.Host, target.Ports, duration.Seconds()))
}
wg.Done() wg.Done()
<-*ch // 释放并发槽位 <-*ch // 释放并发槽位
}() }()
atomic.AddInt64(&Common.Num, 1) // 更新统计和进度
executeSingleScan(pluginName, target) atomic.AddInt64(&common.Num, 1)
updateProgress() common.UpdateProgressBar(1)
// 执行扫描(使用新插件系统)
if err := GlobalPluginAdapter.ScanWithPlugin(pluginName, &target); err != nil {
common.LogError(fmt.Sprintf(i18n.GetText("scan_plugin_error"), target.Host, target.Ports, err))
}
}() }()
} }
// 执行单个扫描
func executeSingleScan(pluginName string, info Common.HostInfo) {
plugin, exists := Common.PluginManager[pluginName]
if !exists {
Common.LogBase(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", pluginName))
return
}
if err := plugin.ScanFunc(&info); err != nil { // Scan 入口函数,向后兼容旧的调用方式
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err)) func Scan(info common.HostInfo) {
} RunScan(info)
}
// 更新扫描进度
func updateProgress() {
Common.OutputMutex.Lock()
defer Common.OutputMutex.Unlock()
atomic.AddInt64(&Common.End, 1)
if Common.ProgressBar != nil {
fmt.Print("\033[2K\r")
Common.ProgressBar.Add(1)
}
}
// 入口函数,向后兼容旧的调用方式
func Scan(info Common.HostInfo) {
scanner := NewScanner(info)
scanner.Scan(info)
} }

View File

@ -1,218 +1,189 @@
package Core package core
import ( import (
"fmt" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strconv"
"strings" "strings"
"sync" "sync"
) )
// ServiceScanStrategy 服务扫描策略 // ServiceScanStrategy 服务扫描策略
type ServiceScanStrategy struct{} type ServiceScanStrategy struct {
*BaseScanStrategy
portDiscovery *PortDiscoveryService
}
// NewServiceScanStrategy 创建新的服务扫描策略 // NewServiceScanStrategy 创建新的服务扫描策略
func NewServiceScanStrategy() *ServiceScanStrategy { func NewServiceScanStrategy() *ServiceScanStrategy {
return &ServiceScanStrategy{} return &ServiceScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService),
portDiscovery: NewPortDiscoveryService(),
}
} }
// Name 返回策略名称 // Name 返回策略名称
func (s *ServiceScanStrategy) Name() string { func (s *ServiceScanStrategy) Name() string {
return "服务扫描" return i18n.GetText("scan_strategy_service_name")
} }
// Description 返回策略描述 // Description 返回策略描述
func (s *ServiceScanStrategy) Description() string { func (s *ServiceScanStrategy) Description() string {
return "扫描主机服务和漏洞" return i18n.GetText("scan_strategy_service_desc")
} }
// Execute 执行服务扫描策略 // Execute 执行服务扫描策略
func (s *ServiceScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { func (s *ServiceScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标 // 验证扫描目标
if info.Host == "" { if info.Host == "" {
Common.LogError("未指定扫描目标") common.LogError(i18n.GetText("parse_error_target_empty"))
return return
} }
// 输出扫描开始信息
s.LogScanStart()
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := s.ValidateConfiguration(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
// 解析目标主机 common.LogBase(i18n.GetText("scan_host_start"))
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return
}
Common.LogBase("开始主机扫描")
// 输出插件信息 // 输出插件信息
s.LogPluginInfo() s.LogPluginInfo()
// 执行主机扫描流程 // 执行主机扫描流程
s.performHostScan(hosts, info, ch, wg) s.performHostScan(info, ch, wg)
} }
// performHostScan 执行主机扫描的完整流程 // performHostScan 执行主机扫描的完整流程
func (s *ServiceScanStrategy) performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { func (s *ServiceScanStrategy) performHostScan(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
var targetInfos []Common.HostInfo // 使用端口发现服务发现目标
targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
// 主机存活性检测和端口扫描 if err != nil {
if len(hosts) > 0 || len(Common.HostPort) > 0 { common.LogError(err.Error())
// 主机存活检测 return
if s.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, Common.UsePing)
Common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
}
// 端口扫描
alivePorts := s.discoverAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = s.convertToTargetInfos(alivePorts, info)
}
} }
// 执行漏洞扫描 // 执行漏洞扫描
if len(targetInfos) > 0 { if len(targetInfos) > 0 {
Common.LogBase("开始漏洞扫描") common.LogBase(i18n.GetText("scan_vulnerability_start"))
// 显示即将使用的漏洞扫描插件
s.LogVulnerabilityPluginInfo(targetInfos)
ExecuteScanTasks(targetInfos, s, ch, wg) 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 = EnhancedPortScan(hosts, Common.Ports, Common.Timeout)
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
}
// 合并额外指定的端口
if len(Common.HostPort) > 0 {
alivePorts = append(alivePorts, Common.HostPort...)
alivePorts = Common.RemoveDuplicate(alivePorts)
Common.HostPort = nil
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
}
return alivePorts
}
// PrepareTargets 准备目标信息 // PrepareTargets 准备目标信息
func (s *ServiceScanStrategy) PrepareTargets(info Common.HostInfo) []Common.HostInfo { func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 解析目标主机 // 使用端口发现服务发现目标
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts) targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) common.LogError(err.Error())
return nil 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 return targetInfos
} }
// convertToTargetInfos 将端口列表转换为目标信息 // LogVulnerabilityPluginInfo 输出服务扫描插件信息
func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo { func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostInfo) {
var infos []Common.HostInfo allPlugins, isCustomMode := s.GetPlugins()
for _, targetIP := range ports { // 收集所有目标端口用于插件适用性检查
hostParts := strings.Split(targetIP, ":") portSet := make(map[int]bool)
if len(hostParts) != 2 { for _, target := range targets {
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP)) if target.Ports != "" {
if port, err := strconv.Atoi(target.Ports); err == nil {
portSet[port] = true
}
}
}
// 获取实际会被使用的插件列表(包括新插件架构和传统插件)
var servicePlugins []string
// 检查新插件架构
for _, pluginName := range allPlugins {
// 首先检查新插件架构
if factory := base.GlobalPluginRegistry.GetFactory(pluginName); factory != nil {
// 获取插件元数据检查端口匹配
metadata := factory.GetMetadata()
if s.isNewPluginApplicableToAnyPort(metadata, portSet, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
}
continue continue
} }
info := baseInfo // 然后检查传统插件系统
info.Host = hostParts[0] plugin, exists := common.PluginManager[pluginName]
info.Ports = hostParts[1] if !exists {
infos = append(infos, info) continue
} }
return infos // 检查传统插件是否对任何目标端口适用
} if s.isPluginApplicableToAnyPort(plugin, portSet, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
// 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.LogBase(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.LogBase(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", "))) if len(servicePlugins) > 0 {
common.LogBase(i18n.GetText("scan_service_plugins", strings.Join(servicePlugins, ", ")))
} else { } else {
Common.LogBase("未找到可用的服务插件") common.LogBase(i18n.GetText("scan_no_service_plugins"))
} }
} }
// IsPluginApplicable 判断插件是否适用于服务扫描 // isPluginApplicableToAnyPort 检查插件是否对任何端口适用(性能优化)
func (s *ServiceScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlugin, portSet map[int]bool, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true
} }
// 非自定义模式下,排除本地插件 // 非自定义模式下,排除本地插件
if plugin.HasType(Common.PluginTypeLocal) { if plugin.HasType(common.PluginTypeLocal) {
return false return false
} }
// 检查端口是否匹配 // 无端口限制的插件适用于所有端口
if len(plugin.Ports) > 0 && targetPort > 0 { if len(plugin.Ports) == 0 {
return plugin.HasPort(targetPort) return true
} }
// 无端口限制的插件或适用于服务扫描的插件 // 有端口限制的插件:检查是否匹配任何目标端口
return len(plugin.Ports) == 0 || plugin.HasType(Common.PluginTypeService) for port := range portSet {
if plugin.HasPort(port) {
return true
}
}
return false
}
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 无端口限制的插件适用于所有端口
if len(metadata.Ports) == 0 {
return true
}
// 有端口限制的插件:检查是否匹配任何目标端口
for port := range portSet {
for _, pluginPort := range metadata.Ports {
if pluginPort == port {
return true
}
}
}
return false
} }

145
Core/WebDetection.go Normal file
View File

@ -0,0 +1,145 @@
package core
import (
"context"
"net"
"net/http"
"strings"
"time"
)
// WebPortDetector Web端口检测器
type WebPortDetector struct {
// 常见Web端口列表
commonWebPorts map[int]bool
// HTTP检测超时时间
httpTimeout time.Duration
}
// NewWebPortDetector 创建Web端口检测器
func NewWebPortDetector() *WebPortDetector {
// 定义常见Web端口
commonPorts := map[int]bool{
80: true, // HTTP
443: true, // HTTPS
8080: true, // HTTP alternate
8443: true, // HTTPS alternate
8000: true, // Development server
8888: true, // Common dev port
9000: true, // Common dev port
9090: true, // Common dev port
3000: true, // Node.js dev server
4000: true, // Ruby dev server
5000: true, // Python dev server
8081: true, // HTTP alternate
8082: true, // HTTP alternate
8083: true, // HTTP alternate
8084: true, // HTTP alternate
8085: true, // HTTP alternate
8086: true, // HTTP alternate
8087: true, // HTTP alternate
8088: true, // HTTP alternate
8089: true, // HTTP alternate
}
return &WebPortDetector{
commonWebPorts: commonPorts,
httpTimeout: 2 * time.Second, // 2秒超时快速检测
}
}
// IsWebService 检测指定主机端口是否为Web服务
func (w *WebPortDetector) IsWebService(host string, port int) bool {
// 1. 首先检查是否为常见Web端口
if w.commonWebPorts[port] {
return true
}
// 2. 对于非常见端口进行HTTP协议检测
return w.detectHTTPService(host, port)
}
// IsCommonWebPort 检查是否为常见Web端口
func (w *WebPortDetector) IsCommonWebPort(port int) bool {
return w.commonWebPorts[port]
}
// detectHTTPService 检测HTTP服务轻量级探测
func (w *WebPortDetector) detectHTTPService(host string, port int) bool {
// 创建检测上下文,避免长时间阻塞
ctx, cancel := context.WithTimeout(context.Background(), w.httpTimeout)
defer cancel()
// 尝试HTTP连接
if w.tryHTTPConnection(ctx, host, port, "http") {
return true
}
// 尝试HTTPS连接对于高端口常见
if w.tryHTTPConnection(ctx, host, port, "https") {
return true
}
return false
}
// tryHTTPConnection 尝试HTTP连接
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
url := ""
if port == 80 && protocol == "http" {
url = "http://" + host
} else if port == 443 && protocol == "https" {
url = "https://" + host
} else {
url = protocol + "://" + host + ":" + string(rune(port))
}
// 创建HTTP客户端快速检测
client := &http.Client{
Timeout: w.httpTimeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: w.httpTimeout,
}).DialContext,
TLSHandshakeTimeout: w.httpTimeout,
ResponseHeaderTimeout: w.httpTimeout,
DisableKeepAlives: true,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不跟随重定向,加快检测速度
},
}
// 发送HEAD请求最小化网络开销
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false
}
req.Header.Set("User-Agent", "fscan-web-detector/2.0")
resp, err := client.Do(req)
if err != nil {
// 检查错误类型某些错误也表明是HTTP服务
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "http") ||
strings.Contains(errStr, "malformed http") ||
strings.Contains(errStr, "server closed") {
return true
}
return false
}
defer resp.Body.Close()
// 任何HTTP响应都表明这是Web服务
return resp.StatusCode > 0
}
// GetCommonWebPorts 获取常见Web端口列表
func (w *WebPortDetector) GetCommonWebPorts() []int {
ports := make([]int, 0, len(w.commonWebPorts))
for port := range w.commonWebPorts {
ports = append(ports, port)
}
return ports
}

View File

@ -1,37 +1,42 @@
package Core package core
import ( import (
"fmt" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common/i18n"
"strings" "strings"
"sync" "sync"
) )
// WebScanStrategy Web扫描策略 // WebScanStrategy Web扫描策略
type WebScanStrategy struct{} type WebScanStrategy struct {
*BaseScanStrategy
}
// NewWebScanStrategy 创建新的Web扫描策略 // NewWebScanStrategy 创建新的Web扫描策略
func NewWebScanStrategy() *WebScanStrategy { func NewWebScanStrategy() *WebScanStrategy {
return &WebScanStrategy{} return &WebScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("Web扫描", FilterWeb),
}
} }
// Name 返回策略名称 // Name 返回策略名称
func (s *WebScanStrategy) Name() string { func (s *WebScanStrategy) Name() string {
return "Web扫描" return i18n.GetText("scan_strategy_web_name")
} }
// Description 返回策略描述 // Description 返回策略描述
func (s *WebScanStrategy) Description() string { func (s *WebScanStrategy) Description() string {
return "扫描Web应用漏洞和信息" return i18n.GetText("scan_strategy_web_desc")
} }
// Execute 执行Web扫描策略 // Execute 执行Web扫描策略
func (s *WebScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogBase("开始Web扫描") // 输出扫描开始信息
s.LogScanStart()
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := s.ValidateConfiguration(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
@ -46,10 +51,10 @@ func (s *WebScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, wg *s
} }
// PrepareTargets 准备URL目标列表 // PrepareTargets 准备URL目标列表
func (s *WebScanStrategy) PrepareTargets(baseInfo Common.HostInfo) []Common.HostInfo { func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.HostInfo {
var targetInfos []Common.HostInfo var targetInfos []common.HostInfo
for _, url := range Common.URLs { for _, url := range common.URLs {
urlInfo := baseInfo urlInfo := baseInfo
// 确保URL包含协议头 // 确保URL包含协议头
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
@ -62,64 +67,3 @@ func (s *WebScanStrategy) PrepareTargets(baseInfo Common.HostInfo) []Common.Host
return targetInfos 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.LogBase(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.LogBase(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", ")))
} else {
Common.LogBase("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)
}

View File

@ -0,0 +1,105 @@
package portfinger
import (
"encoding/hex"
"strconv"
)
// DecodePattern 解码匹配模式
func DecodePattern(s string) ([]byte, error) {
b := []byte(s)
var result []byte
for i := 0; i < len(b); {
if b[i] == '\\' && i+1 < len(b) {
// 处理转义序列
switch b[i+1] {
case 'x':
// 十六进制编码 \xNN
if i+3 < len(b) {
if hexStr := string(b[i+2:i+4]); isValidHex(hexStr) {
if decoded, err := hex.DecodeString(hexStr); err == nil {
result = append(result, decoded...)
i += 4
continue
}
}
}
case 'a':
result = append(result, '\a')
i += 2
continue
case 'f':
result = append(result, '\f')
i += 2
continue
case 't':
result = append(result, '\t')
i += 2
continue
case 'n':
result = append(result, '\n')
i += 2
continue
case 'r':
result = append(result, '\r')
i += 2
continue
case 'v':
result = append(result, '\v')
i += 2
continue
case '\\':
result = append(result, '\\')
i += 2
continue
default:
// 八进制编码 \NNN
if i+1 < len(b) && b[i+1] >= '0' && b[i+1] <= '7' {
octalStr := ""
j := i + 1
for j < len(b) && j < i+4 && b[j] >= '0' && b[j] <= '7' {
octalStr += string(b[j])
j++
}
if octal, err := strconv.ParseInt(octalStr, 8, 8); err == nil {
result = append(result, byte(octal))
i = j
continue
}
}
}
}
// 普通字符
result = append(result, b[i])
i++
}
return result, nil
}
// DecodeData 解码探测数据
func DecodeData(s string) ([]byte, error) {
// 移除首尾的分隔符
if len(s) > 0 && (s[0] == '"' || s[0] == '\'') {
s = s[1:]
}
if len(s) > 0 && (s[len(s)-1] == '"' || s[len(s)-1] == '\'') {
s = s[:len(s)-1]
}
return DecodePattern(s)
}
// isValidHex 检查字符串是否为有效的十六进制
func isValidHex(s string) bool {
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
return false
}
}
return len(s) == 2
}

View File

@ -0,0 +1,120 @@
package portfinger
import (
"fmt"
"regexp"
"strings"
"github.com/shadow1ng/fscan/common"
)
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
common.LogDebug("开始解析match指令" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// MatchPattern 检查响应是否与匹配规则匹配
func (m *Match) MatchPattern(response []byte) bool {
if m.PatternCompiled == nil {
common.LogDebug("警告: 匹配规则的正则表达式未编译")
return false
}
matched := m.PatternCompiled.Match(response)
if matched {
// 提取匹配到的子组
submatches := m.PatternCompiled.FindStringSubmatch(string(response))
if len(submatches) > 1 {
m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组
common.LogDebug(fmt.Sprintf("模式匹配成功,提取到 %d 个分组", len(m.FoundItems)))
} else {
common.LogDebug("模式匹配成功,但没有分组匹配")
}
}
return matched
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,292 @@
package portfinger
import (
"fmt"
"strconv"
"strings"
"github.com/shadow1ng/fscan/common"
)
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
common.LogDebug("回退配置: " + p.Fallback)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}

View File

@ -0,0 +1,71 @@
package portfinger
import (
_ "embed"
"fmt"
"github.com/shadow1ng/fscan/common"
)
//go:embed nmap-service-probes.txt
var ProbeString string
var v VScan
var null *Probe
var commonProbe *Probe
// Init 初始化VScan对象
func (vs *VScan) Init() {
common.LogDebug("开始初始化VScan")
vs.parseProbesFromContent(ProbeString)
vs.parseProbesToMapKName()
vs.SetusedProbes()
common.LogDebug("VScan初始化完成")
}
// InitializeGlobalVScan 初始化全局VScan实例
func InitializeGlobalVScan() {
common.LogDebug("开始初始化全局变量")
v = VScan{}
v.Init()
// 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
common.LogDebug("警告: 未找到NULL探测器")
}
// 获取并检查 GenericLines 探测器
if genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(genericProbe.Data)))
commonProbe = &genericProbe
} else {
common.LogDebug("警告: 未找到GenericLines探测器")
}
common.LogDebug("全局变量初始化完成")
}
// GetGlobalVScan 获取全局VScan实例
func GetGlobalVScan() *VScan {
return &v
}
// GetNullProbe 获取NULL探测器
func GetNullProbe() *Probe {
return null
}
// GetCommonProbe 获取通用探测器
func GetCommonProbe() *Probe {
return commonProbe
}
func init() {
InitializeGlobalVScan()
}

67
Core/portfinger/types.go Normal file
View File

@ -0,0 +1,67 @@
package portfinger
import (
"regexp"
)
// VScan 主扫描器结构体
type VScan struct {
Exclude string
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
// Probe 探测器结构体
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
// Match 匹配规则结构体
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
// Directive 指令结构体
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
// Extras 额外信息结构体
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
// Target 目标结构体
type Target struct {
Host string
Port int
Timeout int
}

View File

@ -0,0 +1,131 @@
package portfinger
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/shadow1ng/fscan/common"
)
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 确保有匹配项
if len(m.FoundItems) == 0 {
common.LogDebug("没有匹配项,无法解析版本信息")
return extras
}
// 替换版本信息中的占位符
foundItems := m.FoundItems
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
// TrimBanner 清理横幅数据,移除不可打印字符
func TrimBanner(banner string) string {
// 移除开头和结尾的空白字符
banner = strings.TrimSpace(banner)
// 移除控制字符,但保留换行符和制表符
var result strings.Builder
for _, r := range banner {
if r >= 32 && r <= 126 { // 可打印ASCII字符
result.WriteRune(r)
} else if r == '\n' || r == '\t' { // 保留换行符和制表符
result.WriteRune(r)
} else {
result.WriteRune(' ') // 其他控制字符替换为空格
}
}
// 压缩多个连续空格为单个空格
resultStr := result.String()
spaceRe := regexp.MustCompile(`\s+`)
resultStr = spaceRe.ReplaceAllString(resultStr, " ")
return strings.TrimSpace(resultStr)
}

View File

@ -1,317 +0,0 @@
package Plugins
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// ActiveMQCredential 表示一个ActiveMQ凭据
type ActiveMQCredential struct {
Username string
Password string
}
// ActiveMQScanResult 表示扫描结果
type ActiveMQScanResult struct {
Success bool
Error error
Credential ActiveMQCredential
}
func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试默认账户
Common.LogDebug("尝试默认账户 admin:admin")
defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"}
defaultResult := tryActiveCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries)
if defaultResult.Success {
saveActiveMQSuccess(info, target, defaultResult.Credential)
return nil
}
// 生成所有凭据组合
credentials := generateActiveMQCredentials(Common.Userdict["activemq"], Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["activemq"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentActiveMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveActiveMQSuccess(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("ActiveMQ扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据
return nil
}
}
// generateActiveMQCredentials 生成ActiveMQ的用户名密码组合
func generateActiveMQCredentials(users, passwords []string) []ActiveMQCredential {
var credentials []ActiveMQCredential
for _, user := range users {
for _, pass := range passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, ActiveMQCredential{
Username: user,
Password: actualPass,
})
}
}
return credentials
}
// concurrentActiveMQScan 并发扫描ActiveMQ服务
func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *ActiveMQScanResult, 1)
workChan := make(chan ActiveMQCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryActiveCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("ActiveMQ并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryActiveCredential 尝试单个ActiveMQ凭据
func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &ActiveMQScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := ActiveMQConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &ActiveMQScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &ActiveMQScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// ActiveMQConn 尝试ActiveMQ连接
func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
addr := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 使用上下文创建带超时的连接
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, err
}
defer conn.Close()
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中处理认证
go func() {
// STOMP协议的CONNECT命令
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass)
// 发送认证请求
conn.SetWriteDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
respBuf := make([]byte, 1024)
n, err := conn.Read(respBuf)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 检查认证结果
response := string(respBuf[:n])
var success bool
var resultErr error
if strings.Contains(response, "CONNECTED") {
success = true
resultErr = nil
} else if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") {
success = false
resultErr = fmt.Errorf("认证失败")
} else {
success = false
resultErr = fmt.Errorf("未知响应: %s", response)
}
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{success, resultErr}:
}
}()
// 等待认证结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveActiveMQSuccess 记录并保存ActiveMQ成功结果
func saveActiveMQSuccess(info *Common.HostInfo, target string, credential ActiveMQCredential) {
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "activemq",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(result)
}

View File

@ -1,364 +0,0 @@
package Plugins
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common"
)
// CassandraProxyDialer 实现gocql.Dialer接口支持代理连接
type CassandraProxyDialer struct {
timeout time.Duration
}
func (d *CassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return Common.WrapperTcpWithContext(ctx, network, fmt.Sprintf("%s:%s", host, port))
}
// CassandraCredential 表示一个Cassandra凭据
type CassandraCredential struct {
Username string
Password string
}
// CassandraScanResult 表示扫描结果
type CassandraScanResult struct {
Success bool
IsAnonymous bool
Error error
Credential CassandraCredential
}
func CassandraScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试无认证访问
Common.LogDebug("尝试无认证访问...")
anonymousCredential := CassandraCredential{Username: "", Password: ""}
anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries)
if anonymousResult.Success {
saveCassandraSuccess(info, target, anonymousResult.Credential, true)
return nil
}
// 生成所有凭据组合
credentials := generateCassandraCredentials(Common.Userdict["cassandra"], Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentCassandraScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveCassandraSuccess(info, target, result.Credential, false)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Cassandra扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// generateCassandraCredentials 生成Cassandra的用户名密码组合
func generateCassandraCredentials(users, passwords []string) []CassandraCredential {
var credentials []CassandraCredential
for _, user := range users {
for _, pass := range passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, CassandraCredential{
Username: user,
Password: actualPass,
})
}
}
return credentials
}
// concurrentCassandraScan 并发扫描Cassandra服务
func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *CassandraScanResult, 1)
workChan := make(chan CassandraCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryCassandraCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("Cassandra并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryCassandraCredential 尝试单个Cassandra凭据
func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &CassandraScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := CassandraConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &CassandraScanResult{
Success: true,
IsAnonymous: credential.Username == "" && credential.Password == "",
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &CassandraScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// CassandraConn 尝试Cassandra连接支持上下文超时
func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
cluster := gocql.NewCluster(host)
cluster.Port, _ = strconv.Atoi(port)
cluster.Timeout = timeout
cluster.ConnectTimeout = timeout
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
// 如果配置了代理设置自定义Dialer
if Common.Socks5Proxy != "" {
cluster.Dialer = &CassandraProxyDialer{
timeout: timeout,
}
}
if user != "" || pass != "" {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: user,
Password: pass,
}
}
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
// 创建会话通道
sessionChan := make(chan struct {
session *gocql.Session
err error
}, 1)
// 在后台创建会话,以便可以通过上下文取消
go func() {
session, err := cluster.CreateSession()
select {
case <-ctx.Done():
if session != nil {
session.Close()
}
case sessionChan <- struct {
session *gocql.Session
err error
}{session, err}:
}
}()
// 等待会话创建或上下文取消
var session *gocql.Session
var err error
select {
case result := <-sessionChan:
session, err = result.session, result.err
if err != nil {
return false, err
}
case <-ctx.Done():
return false, ctx.Err()
}
defer session.Close()
// 尝试执行查询,测试连接是否成功
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
var version string
var err error
// 尝试两种查询,确保至少一种成功
err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(&version)
if err != nil {
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&version)
}
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{err == nil, err}:
}
}()
// 等待查询结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveCassandraSuccess 记录并保存Cassandra成功结果
func saveCassandraSuccess(info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) {
var successMsg string
var details map[string]interface{}
if isAnonymous {
successMsg = fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"auth_type": "anonymous",
"type": "unauthorized-access",
"description": "数据库允许无认证访问",
}
} else {
successMsg = fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
}
}
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(result)
}

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/go-ldap/ldap/v3/gssapi" "github.com/go-ldap/ldap/v3/gssapi"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
@ -24,7 +24,7 @@ func (d *DomainInfo) Close() {
} }
func (d *DomainInfo) GetCAComputers() ([]string, error) { func (d *DomainInfo) GetCAComputers() ([]string, error) {
Common.LogDebug("开始查询域内CA服务器...") common.LogDebug("开始查询域内CA服务器...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"CN=Configuration,"+d.baseDN, "CN=Configuration,"+d.baseDN,
@ -40,7 +40,7 @@ func (d *DomainInfo) GetCAComputers() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询CA服务器失败: %v", err)) common.LogError(fmt.Sprintf("查询CA服务器失败: %v", err))
return nil, err return nil, err
} }
@ -49,21 +49,21 @@ func (d *DomainInfo) GetCAComputers() ([]string, error) {
cn := entry.GetAttributeValue("cn") cn := entry.GetAttributeValue("cn")
if cn != "" { if cn != "" {
caComputers = append(caComputers, cn) caComputers = append(caComputers, cn)
Common.LogDebug(fmt.Sprintf("发现CA服务器: %s", cn)) common.LogDebug(fmt.Sprintf("发现CA服务器: %s", cn))
} }
} }
if len(caComputers) > 0 { if len(caComputers) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个CA服务器", len(caComputers))) common.LogSuccess(fmt.Sprintf("共发现 %d 个CA服务器", len(caComputers)))
} else { } else {
Common.LogDebug("未发现CA服务器") common.LogDebug("未发现CA服务器")
} }
return caComputers, nil return caComputers, nil
} }
func (d *DomainInfo) GetExchangeServers() ([]string, error) { func (d *DomainInfo) GetExchangeServers() ([]string, error) {
Common.LogDebug("开始查询Exchange服务器...") common.LogDebug("开始查询Exchange服务器...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -79,7 +79,7 @@ func (d *DomainInfo) GetExchangeServers() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询Exchange服务器失败: %v", err)) common.LogError(fmt.Sprintf("查询Exchange服务器失败: %v", err))
return nil, err return nil, err
} }
@ -88,7 +88,7 @@ func (d *DomainInfo) GetExchangeServers() ([]string, error) {
for _, member := range entry.GetAttributeValues("member") { for _, member := range entry.GetAttributeValues("member") {
if member != "" { if member != "" {
exchangeServers = append(exchangeServers, member) exchangeServers = append(exchangeServers, member)
Common.LogDebug(fmt.Sprintf("发现Exchange服务器成员: %s", member)) common.LogDebug(fmt.Sprintf("发现Exchange服务器成员: %s", member))
} }
} }
} }
@ -96,20 +96,20 @@ func (d *DomainInfo) GetExchangeServers() ([]string, error) {
// 移除第一个条目(如果存在) // 移除第一个条目(如果存在)
if len(exchangeServers) > 1 { if len(exchangeServers) > 1 {
exchangeServers = exchangeServers[1:] exchangeServers = exchangeServers[1:]
Common.LogDebug("移除第一个条目") common.LogDebug("移除第一个条目")
} }
if len(exchangeServers) > 0 { if len(exchangeServers) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个Exchange服务器", len(exchangeServers))) common.LogSuccess(fmt.Sprintf("共发现 %d 个Exchange服务器", len(exchangeServers)))
} else { } else {
Common.LogDebug("未发现Exchange服务器") common.LogDebug("未发现Exchange服务器")
} }
return exchangeServers, nil return exchangeServers, nil
} }
func (d *DomainInfo) GetMsSqlServers() ([]string, error) { func (d *DomainInfo) GetMsSqlServers() ([]string, error) {
Common.LogDebug("开始查询SQL Server服务器...") common.LogDebug("开始查询SQL Server服务器...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -125,7 +125,7 @@ func (d *DomainInfo) GetMsSqlServers() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询SQL Server失败: %v", err)) common.LogError(fmt.Sprintf("查询SQL Server失败: %v", err))
return nil, err return nil, err
} }
@ -134,43 +134,43 @@ func (d *DomainInfo) GetMsSqlServers() ([]string, error) {
name := entry.GetAttributeValue("name") name := entry.GetAttributeValue("name")
if name != "" { if name != "" {
sqlServers = append(sqlServers, name) sqlServers = append(sqlServers, name)
Common.LogDebug(fmt.Sprintf("发现SQL Server: %s", name)) common.LogDebug(fmt.Sprintf("发现SQL Server: %s", name))
} }
} }
if len(sqlServers) > 0 { if len(sqlServers) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个SQL Server", len(sqlServers))) common.LogSuccess(fmt.Sprintf("共发现 %d 个SQL Server", len(sqlServers)))
} else { } else {
Common.LogDebug("未发现SQL Server") common.LogDebug("未发现SQL Server")
} }
return sqlServers, nil return sqlServers, nil
} }
func (d *DomainInfo) GetSpecialComputers() (map[string][]string, error) { func (d *DomainInfo) GetSpecialComputers() (map[string][]string, error) {
Common.LogDebug("开始查询特殊计算机...") common.LogDebug("开始查询特殊计算机...")
results := make(map[string][]string) results := make(map[string][]string)
// 获取SQL Server // 获取SQL Server
Common.LogDebug("正在查询SQL Server...") common.LogDebug("正在查询SQL Server...")
sqlServers, err := d.GetMsSqlServers() sqlServers, err := d.GetMsSqlServers()
if err == nil && len(sqlServers) > 0 { if err == nil && len(sqlServers) > 0 {
results["SQL服务器"] = sqlServers results["SQL服务器"] = sqlServers
} else if err != nil { } else if err != nil {
Common.LogError(fmt.Sprintf("查询SQL Server时出错: %v", err)) common.LogError(fmt.Sprintf("查询SQL Server时出错: %v", err))
} }
// 获取CA服务器 // 获取CA服务器
Common.LogDebug("正在查询CA服务器...") common.LogDebug("正在查询CA服务器...")
caComputers, err := d.GetCAComputers() caComputers, err := d.GetCAComputers()
if err == nil && len(caComputers) > 0 { if err == nil && len(caComputers) > 0 {
results["CA服务器"] = caComputers results["CA服务器"] = caComputers
} else if err != nil { } else if err != nil {
Common.LogError(fmt.Sprintf("查询CA服务器时出错: %v", err)) common.LogError(fmt.Sprintf("查询CA服务器时出错: %v", err))
} }
// 获取域控制器 // 获取域控制器
Common.LogDebug("正在查询域控制器...") common.LogDebug("正在查询域控制器...")
dcQuery := ldap.NewSearchRequest( dcQuery := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
@ -189,42 +189,42 @@ func (d *DomainInfo) GetSpecialComputers() (map[string][]string, error) {
name := entry.GetAttributeValue("cn") name := entry.GetAttributeValue("cn")
if name != "" { if name != "" {
dcs = append(dcs, name) dcs = append(dcs, name)
Common.LogDebug(fmt.Sprintf("发现域控制器: %s", name)) common.LogDebug(fmt.Sprintf("发现域控制器: %s", name))
} }
} }
if len(dcs) > 0 { if len(dcs) > 0 {
results["域控制器"] = dcs results["域控制器"] = dcs
Common.LogSuccess(fmt.Sprintf("共发现 %d 个域控制器", len(dcs))) common.LogSuccess(fmt.Sprintf("共发现 %d 个域控制器", len(dcs)))
} else { } else {
Common.LogDebug("未发现域控制器") common.LogDebug("未发现域控制器")
} }
} else { } else {
Common.LogError(fmt.Sprintf("查询域控制器时出错: %v", err)) common.LogError(fmt.Sprintf("查询域控制器时出错: %v", err))
} }
// 获取Exchange服务器 // 获取Exchange服务器
Common.LogDebug("正在查询Exchange服务器...") common.LogDebug("正在查询Exchange服务器...")
exchangeServers, err := d.GetExchangeServers() exchangeServers, err := d.GetExchangeServers()
if err == nil && len(exchangeServers) > 0 { if err == nil && len(exchangeServers) > 0 {
results["Exchange服务器"] = exchangeServers results["Exchange服务器"] = exchangeServers
} else if err != nil { } else if err != nil {
Common.LogError(fmt.Sprintf("查询Exchange服务器时出错: %v", err)) common.LogError(fmt.Sprintf("查询Exchange服务器时出错: %v", err))
} }
if len(results) > 0 { if len(results) > 0 {
Common.LogSuccess(fmt.Sprintf("特殊计算机查询完成,共发现 %d 类服务器", len(results))) common.LogSuccess(fmt.Sprintf("特殊计算机查询完成,共发现 %d 类服务器", len(results)))
for serverType, servers := range results { for serverType, servers := range results {
Common.LogDebug(fmt.Sprintf("%s: %d 台", serverType, len(servers))) common.LogDebug(fmt.Sprintf("%s: %d 台", serverType, len(servers)))
} }
} else { } else {
Common.LogDebug("未发现任何特殊计算机") common.LogDebug("未发现任何特殊计算机")
} }
return results, nil return results, nil
} }
func (d *DomainInfo) GetDomainUsers() ([]string, error) { func (d *DomainInfo) GetDomainUsers() ([]string, error) {
Common.LogDebug("开始查询域用户...") common.LogDebug("开始查询域用户...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -240,7 +240,7 @@ func (d *DomainInfo) GetDomainUsers() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询域用户失败: %v", err)) common.LogError(fmt.Sprintf("查询域用户失败: %v", err))
return nil, err return nil, err
} }
@ -249,21 +249,21 @@ func (d *DomainInfo) GetDomainUsers() ([]string, error) {
username := entry.GetAttributeValue("sAMAccountName") username := entry.GetAttributeValue("sAMAccountName")
if username != "" { if username != "" {
users = append(users, username) users = append(users, username)
Common.LogDebug(fmt.Sprintf("发现用户: %s", username)) common.LogDebug(fmt.Sprintf("发现用户: %s", username))
} }
} }
if len(users) > 0 { if len(users) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个域用户", len(users))) common.LogSuccess(fmt.Sprintf("共发现 %d 个域用户", len(users)))
} else { } else {
Common.LogDebug("未发现域用户") common.LogDebug("未发现域用户")
} }
return users, nil return users, nil
} }
func (d *DomainInfo) GetDomainAdmins() ([]string, error) { func (d *DomainInfo) GetDomainAdmins() ([]string, error) {
Common.LogDebug("开始查询域管理员...") common.LogDebug("开始查询域管理员...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -279,14 +279,14 @@ func (d *DomainInfo) GetDomainAdmins() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询Domain Admins组失败: %v", err)) common.LogError(fmt.Sprintf("查询Domain Admins组失败: %v", err))
return nil, err return nil, err
} }
var admins []string var admins []string
if len(sr.Entries) > 0 { if len(sr.Entries) > 0 {
members := sr.Entries[0].GetAttributeValues("member") members := sr.Entries[0].GetAttributeValues("member")
Common.LogDebug(fmt.Sprintf("发现 %d 个Domain Admins组成员", len(members))) common.LogDebug(fmt.Sprintf("发现 %d 个Domain Admins组成员", len(members)))
for _, memberDN := range members { for _, memberDN := range members {
memberSearch := ldap.NewSearchRequest( memberSearch := ldap.NewSearchRequest(
@ -303,7 +303,7 @@ func (d *DomainInfo) GetDomainAdmins() ([]string, error) {
memberResult, err := d.conn.Search(memberSearch) memberResult, err := d.conn.Search(memberSearch)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询成员 %s 失败: %v", memberDN, err)) common.LogError(fmt.Sprintf("查询成员 %s 失败: %v", memberDN, err))
continue continue
} }
@ -311,23 +311,23 @@ func (d *DomainInfo) GetDomainAdmins() ([]string, error) {
samAccountName := memberResult.Entries[0].GetAttributeValue("sAMAccountName") samAccountName := memberResult.Entries[0].GetAttributeValue("sAMAccountName")
if samAccountName != "" { if samAccountName != "" {
admins = append(admins, samAccountName) admins = append(admins, samAccountName)
Common.LogDebug(fmt.Sprintf("发现域管理员: %s", samAccountName)) common.LogDebug(fmt.Sprintf("发现域管理员: %s", samAccountName))
} }
} }
} }
} }
if len(admins) > 0 { if len(admins) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个域管理员", len(admins))) common.LogSuccess(fmt.Sprintf("共发现 %d 个域管理员", len(admins)))
} else { } else {
Common.LogDebug("未发现域管理员") common.LogDebug("未发现域管理员")
} }
return admins, nil return admins, nil
} }
func (d *DomainInfo) GetOUs() ([]string, error) { func (d *DomainInfo) GetOUs() ([]string, error) {
Common.LogDebug("开始查询组织单位(OU)...") common.LogDebug("开始查询组织单位(OU)...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -343,7 +343,7 @@ func (d *DomainInfo) GetOUs() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询OU失败: %v", err)) common.LogError(fmt.Sprintf("查询OU失败: %v", err))
return nil, err return nil, err
} }
@ -352,21 +352,21 @@ func (d *DomainInfo) GetOUs() ([]string, error) {
ou := entry.GetAttributeValue("ou") ou := entry.GetAttributeValue("ou")
if ou != "" { if ou != "" {
ous = append(ous, ou) ous = append(ous, ou)
Common.LogDebug(fmt.Sprintf("发现OU: %s", ou)) common.LogDebug(fmt.Sprintf("发现OU: %s", ou))
} }
} }
if len(ous) > 0 { if len(ous) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个组织单位", len(ous))) common.LogSuccess(fmt.Sprintf("共发现 %d 个组织单位", len(ous)))
} else { } else {
Common.LogDebug("未发现组织单位") common.LogDebug("未发现组织单位")
} }
return ous, nil return ous, nil
} }
func (d *DomainInfo) GetComputers() ([]Computer, error) { func (d *DomainInfo) GetComputers() ([]Computer, error) {
Common.LogDebug("开始查询域内计算机...") common.LogDebug("开始查询域内计算机...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -382,7 +382,7 @@ func (d *DomainInfo) GetComputers() ([]Computer, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询计算机失败: %v", err)) common.LogError(fmt.Sprintf("查询计算机失败: %v", err))
return nil, err return nil, err
} }
@ -394,14 +394,14 @@ func (d *DomainInfo) GetComputers() ([]Computer, error) {
DNSHostName: entry.GetAttributeValue("dNSHostName"), DNSHostName: entry.GetAttributeValue("dNSHostName"),
} }
computers = append(computers, computer) computers = append(computers, computer)
Common.LogDebug(fmt.Sprintf("发现计算机: %s (OS: %s, DNS: %s)", common.LogDebug(fmt.Sprintf("发现计算机: %s (OS: %s, DNS: %s)",
computer.Name, computer.Name,
computer.OperatingSystem, computer.OperatingSystem,
computer.DNSHostName)) computer.DNSHostName))
} }
if len(computers) > 0 { if len(computers) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 台计算机", len(computers))) common.LogSuccess(fmt.Sprintf("共发现 %d 台计算机", len(computers)))
// 统计操作系统分布 // 统计操作系统分布
osCount := make(map[string]int) osCount := make(map[string]int)
@ -412,10 +412,10 @@ func (d *DomainInfo) GetComputers() ([]Computer, error) {
} }
for os, count := range osCount { for os, count := range osCount {
Common.LogDebug(fmt.Sprintf("操作系统 %s: %d 台", os, count)) common.LogDebug(fmt.Sprintf("操作系统 %s: %d 台", os, count))
} }
} else { } else {
Common.LogDebug("未发现计算机") common.LogDebug("未发现计算机")
} }
return computers, nil return computers, nil
@ -429,7 +429,7 @@ type Computer struct {
} }
func (d *DomainInfo) GetTrustDomains() ([]string, error) { func (d *DomainInfo) GetTrustDomains() ([]string, error) {
Common.LogDebug("开始查询域信任关系...") common.LogDebug("开始查询域信任关系...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -445,7 +445,7 @@ func (d *DomainInfo) GetTrustDomains() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询信任域失败: %v", err)) common.LogError(fmt.Sprintf("查询信任域失败: %v", err))
return nil, err return nil, err
} }
@ -454,21 +454,21 @@ func (d *DomainInfo) GetTrustDomains() ([]string, error) {
cn := entry.GetAttributeValue("cn") cn := entry.GetAttributeValue("cn")
if cn != "" { if cn != "" {
trustInfo = append(trustInfo, cn) trustInfo = append(trustInfo, cn)
Common.LogDebug(fmt.Sprintf("发现信任域: %s", cn)) common.LogDebug(fmt.Sprintf("发现信任域: %s", cn))
} }
} }
if len(trustInfo) > 0 { if len(trustInfo) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个信任域", len(trustInfo))) common.LogSuccess(fmt.Sprintf("共发现 %d 个信任域", len(trustInfo)))
} else { } else {
Common.LogDebug("未发现信任域关系") common.LogDebug("未发现信任域关系")
} }
return trustInfo, nil return trustInfo, nil
} }
func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) { func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) {
Common.LogDebug("开始查询管理员组信息...") common.LogDebug("开始查询管理员组信息...")
adminGroups := map[string]string{ adminGroups := map[string]string{
"Domain Admins": "(&(objectClass=group)(cn=Domain Admins))", "Domain Admins": "(&(objectClass=group)(cn=Domain Admins))",
@ -479,7 +479,7 @@ func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) {
results := make(map[string][]string) results := make(map[string][]string)
for groupName, filter := range adminGroups { for groupName, filter := range adminGroups {
Common.LogDebug(fmt.Sprintf("正在查询 %s 组...", groupName)) common.LogDebug(fmt.Sprintf("正在查询 %s 组...", groupName))
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -495,7 +495,7 @@ func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询 %s 组失败: %v", groupName, err)) common.LogError(fmt.Sprintf("查询 %s 组失败: %v", groupName, err))
continue continue
} }
@ -503,27 +503,27 @@ func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) {
members := sr.Entries[0].GetAttributeValues("member") members := sr.Entries[0].GetAttributeValues("member")
if len(members) > 0 { if len(members) > 0 {
results[groupName] = members results[groupName] = members
Common.LogDebug(fmt.Sprintf("%s 组成员数量: %d", groupName, len(members))) common.LogDebug(fmt.Sprintf("%s 组成员数量: %d", groupName, len(members)))
for _, member := range members { for _, member := range members {
Common.LogDebug(fmt.Sprintf("- %s: %s", groupName, member)) common.LogDebug(fmt.Sprintf("- %s: %s", groupName, member))
} }
} else { } else {
Common.LogDebug(fmt.Sprintf("%s 组未发现成员", groupName)) common.LogDebug(fmt.Sprintf("%s 组未发现成员", groupName))
} }
} }
} }
if len(results) > 0 { if len(results) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个管理员组", len(results))) common.LogSuccess(fmt.Sprintf("共发现 %d 个管理员组", len(results)))
} else { } else {
Common.LogDebug("未发现管理员组信息") common.LogDebug("未发现管理员组信息")
} }
return results, nil return results, nil
} }
func (d *DomainInfo) GetDelegation() (map[string][]string, error) { func (d *DomainInfo) GetDelegation() (map[string][]string, error) {
Common.LogDebug("开始查询委派信息...") common.LogDebug("开始查询委派信息...")
delegationQueries := map[string]string{ delegationQueries := map[string]string{
"非约束委派": "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))", "非约束委派": "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))",
@ -534,7 +534,7 @@ func (d *DomainInfo) GetDelegation() (map[string][]string, error) {
results := make(map[string][]string) results := make(map[string][]string)
for delegationType, query := range delegationQueries { for delegationType, query := range delegationQueries {
Common.LogDebug(fmt.Sprintf("正在查询%s...", delegationType)) common.LogDebug(fmt.Sprintf("正在查询%s...", delegationType))
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -550,7 +550,7 @@ func (d *DomainInfo) GetDelegation() (map[string][]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询%s失败: %v", delegationType, err)) common.LogError(fmt.Sprintf("查询%s失败: %v", delegationType, err))
continue continue
} }
@ -559,22 +559,22 @@ func (d *DomainInfo) GetDelegation() (map[string][]string, error) {
cn := entry.GetAttributeValue("cn") cn := entry.GetAttributeValue("cn")
if cn != "" { if cn != "" {
entries = append(entries, cn) entries = append(entries, cn)
Common.LogDebug(fmt.Sprintf("发现%s: %s", delegationType, cn)) common.LogDebug(fmt.Sprintf("发现%s: %s", delegationType, cn))
} }
} }
if len(entries) > 0 { if len(entries) > 0 {
results[delegationType] = entries results[delegationType] = entries
Common.LogSuccess(fmt.Sprintf("%s: 发现 %d 条记录", delegationType, len(entries))) common.LogSuccess(fmt.Sprintf("%s: 发现 %d 条记录", delegationType, len(entries)))
} else { } else {
Common.LogDebug(fmt.Sprintf("未发现%s记录", delegationType)) common.LogDebug(fmt.Sprintf("未发现%s记录", delegationType))
} }
} }
if len(results) > 0 { if len(results) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 类委派配置", len(results))) common.LogSuccess(fmt.Sprintf("共发现 %d 类委派配置", len(results)))
} else { } else {
Common.LogDebug("未发现任何委派配置") common.LogDebug("未发现任何委派配置")
} }
return results, nil return results, nil
@ -582,7 +582,7 @@ func (d *DomainInfo) GetDelegation() (map[string][]string, error) {
// 获取AS-REP Roasting漏洞用户 // 获取AS-REP Roasting漏洞用户
func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) { func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) {
Common.LogDebug("开始查询AS-REP Roasting漏洞用户...") common.LogDebug("开始查询AS-REP Roasting漏洞用户...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -598,7 +598,7 @@ func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询AS-REP Roasting漏洞用户失败: %v", err)) common.LogError(fmt.Sprintf("查询AS-REP Roasting漏洞用户失败: %v", err))
return nil, err return nil, err
} }
@ -607,21 +607,21 @@ func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) {
name := entry.GetAttributeValue("sAMAccountName") name := entry.GetAttributeValue("sAMAccountName")
if name != "" { if name != "" {
users = append(users, name) users = append(users, name)
Common.LogDebug(fmt.Sprintf("发现存在AS-REP Roasting漏洞的用户: %s", name)) common.LogDebug(fmt.Sprintf("发现存在AS-REP Roasting漏洞的用户: %s", name))
} }
} }
if len(users) > 0 { if len(users) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个存在AS-REP Roasting漏洞的用户", len(users))) common.LogSuccess(fmt.Sprintf("共发现 %d 个存在AS-REP Roasting漏洞的用户", len(users)))
} else { } else {
Common.LogDebug("未发现存在AS-REP Roasting漏洞的用户") common.LogDebug("未发现存在AS-REP Roasting漏洞的用户")
} }
return users, nil return users, nil
} }
func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) { func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) {
Common.LogDebug("开始查询域密码策略...") common.LogDebug("开始查询域密码策略...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -645,12 +645,12 @@ func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) {
sr, err := d.conn.Search(searchRequest) sr, err := d.conn.Search(searchRequest)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询密码策略失败: %v", err)) common.LogError(fmt.Sprintf("查询密码策略失败: %v", err))
return nil, err return nil, err
} }
if len(sr.Entries) == 0 { if len(sr.Entries) == 0 {
Common.LogError("未找到密码策略信息") common.LogError("未找到密码策略信息")
return nil, fmt.Errorf("未找到密码策略信息") return nil, fmt.Errorf("未找到密码策略信息")
} }
@ -663,47 +663,47 @@ func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) {
if maxAgeInt != 0 { if maxAgeInt != 0 {
days := float64(maxAgeInt) * -1 / float64(864000000000) days := float64(maxAgeInt) * -1 / float64(864000000000)
policy["最大密码期限"] = fmt.Sprintf("%.0f天", days) policy["最大密码期限"] = fmt.Sprintf("%.0f天", days)
Common.LogDebug(fmt.Sprintf("最大密码期限: %.0f天", days)) common.LogDebug(fmt.Sprintf("最大密码期限: %.0f天", days))
} }
} }
if minLength := entry.GetAttributeValue("minPwdLength"); minLength != "" { if minLength := entry.GetAttributeValue("minPwdLength"); minLength != "" {
policy["最小密码长度"] = minLength + "个字符" policy["最小密码长度"] = minLength + "个字符"
Common.LogDebug(fmt.Sprintf("最小密码长度: %s个字符", minLength)) common.LogDebug(fmt.Sprintf("最小密码长度: %s个字符", minLength))
} }
if historyLength := entry.GetAttributeValue("pwdHistoryLength"); historyLength != "" { if historyLength := entry.GetAttributeValue("pwdHistoryLength"); historyLength != "" {
policy["密码历史长度"] = historyLength + "个" policy["密码历史长度"] = historyLength + "个"
Common.LogDebug(fmt.Sprintf("密码历史长度: %s个", historyLength)) common.LogDebug(fmt.Sprintf("密码历史长度: %s个", historyLength))
} }
if lockoutThreshold := entry.GetAttributeValue("lockoutThreshold"); lockoutThreshold != "" { if lockoutThreshold := entry.GetAttributeValue("lockoutThreshold"); lockoutThreshold != "" {
policy["账户锁定阈值"] = lockoutThreshold + "次" policy["账户锁定阈值"] = lockoutThreshold + "次"
Common.LogDebug(fmt.Sprintf("账户锁定阈值: %s次", lockoutThreshold)) common.LogDebug(fmt.Sprintf("账户锁定阈值: %s次", lockoutThreshold))
} }
if len(policy) > 0 { if len(policy) > 0 {
Common.LogSuccess(fmt.Sprintf("成功获取域密码策略,共 %d 项配置", len(policy))) common.LogSuccess(fmt.Sprintf("成功获取域密码策略,共 %d 项配置", len(policy)))
// 安全性评估 // 安全性评估
minLengthInt, _ := strconv.Atoi(strings.TrimSuffix(policy["最小密码长度"], "个字符")) minLengthInt, _ := strconv.Atoi(strings.TrimSuffix(policy["最小密码长度"], "个字符"))
if minLengthInt < 8 { if minLengthInt < 8 {
Common.LogDebug("警告密码最小长度小于8个字符存在安全风险") common.LogDebug("警告密码最小长度小于8个字符存在安全风险")
} }
lockoutThresholdInt, _ := strconv.Atoi(strings.TrimSuffix(policy["账户锁定阈值"], "次")) lockoutThresholdInt, _ := strconv.Atoi(strings.TrimSuffix(policy["账户锁定阈值"], "次"))
if lockoutThresholdInt == 0 { if lockoutThresholdInt == 0 {
Common.LogDebug("警告:未启用账户锁定策略,存在暴力破解风险") common.LogDebug("警告:未启用账户锁定策略,存在暴力破解风险")
} }
} else { } else {
Common.LogDebug("未获取到任何密码策略配置") common.LogDebug("未获取到任何密码策略配置")
} }
return policy, nil return policy, nil
} }
func (d *DomainInfo) GetSPNs() (map[string][]string, error) { func (d *DomainInfo) GetSPNs() (map[string][]string, error) {
Common.LogDebug("开始查询SPN信息...") common.LogDebug("开始查询SPN信息...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
d.baseDN, d.baseDN,
@ -719,7 +719,7 @@ func (d *DomainInfo) GetSPNs() (map[string][]string, error) {
sr, err := d.conn.SearchWithPaging(searchRequest, 10000) sr, err := d.conn.SearchWithPaging(searchRequest, 10000)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查询SPN失败: %v", err)) common.LogError(fmt.Sprintf("查询SPN失败: %v", err))
return nil, err return nil, err
} }
@ -732,53 +732,53 @@ func (d *DomainInfo) GetSPNs() (map[string][]string, error) {
if len(spnList) > 0 { if len(spnList) > 0 {
key := fmt.Sprintf("SPN%s", dn) key := fmt.Sprintf("SPN%s", dn)
spns[key] = spnList spns[key] = spnList
Common.LogDebug(fmt.Sprintf("发现SPN - CN: %s", cn)) common.LogDebug(fmt.Sprintf("发现SPN - CN: %s", cn))
for _, spn := range spnList { for _, spn := range spnList {
Common.LogDebug(fmt.Sprintf(" - %s", spn)) common.LogDebug(fmt.Sprintf(" - %s", spn))
} }
} }
} }
if len(spns) > 0 { if len(spns) > 0 {
Common.LogSuccess(fmt.Sprintf("共发现 %d 个SPN配置", len(spns))) common.LogSuccess(fmt.Sprintf("共发现 %d 个SPN配置", len(spns)))
} else { } else {
Common.LogDebug("未发现SPN配置") common.LogDebug("未发现SPN配置")
} }
return spns, nil return spns, nil
} }
func getDomainController() (string, error) { func getDomainController() (string, error) {
Common.LogDebug("开始查询域控制器地址...") common.LogDebug("开始查询域控制器地址...")
// 尝试使用wmic获取当前域名 // 尝试使用wmic获取当前域名
Common.LogDebug("正在使用wmic获取域名...") common.LogDebug("正在使用wmic获取域名...")
cmd := exec.Command("wmic", "computersystem", "get", "domain") cmd := exec.Command("wmic", "computersystem", "get", "domain")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取域名失败: %v", err)) common.LogError(fmt.Sprintf("获取域名失败: %v", err))
return "", fmt.Errorf("获取域名失败: %v", err) return "", fmt.Errorf("获取域名失败: %v", err)
} }
lines := strings.Split(string(output), "\n") lines := strings.Split(string(output), "\n")
if len(lines) < 2 { if len(lines) < 2 {
Common.LogError("wmic输出格式异常未找到域名") common.LogError("wmic输出格式异常未找到域名")
return "", fmt.Errorf("未找到域名") return "", fmt.Errorf("未找到域名")
} }
domain := strings.TrimSpace(lines[1]) domain := strings.TrimSpace(lines[1])
if domain == "" { if domain == "" {
Common.LogError("获取到的域名为空") common.LogError("获取到的域名为空")
return "", fmt.Errorf("域名为空") return "", fmt.Errorf("域名为空")
} }
Common.LogDebug(fmt.Sprintf("获取到域名: %s", domain)) common.LogDebug(fmt.Sprintf("获取到域名: %s", domain))
// 使用nslookup查询域控制器 // 使用nslookup查询域控制器
Common.LogDebug(fmt.Sprintf("正在使用nslookup查询域控制器 (_ldap._tcp.dc._msdcs.%s)...", domain)) common.LogDebug(fmt.Sprintf("正在使用nslookup查询域控制器 (_ldap._tcp.dc._msdcs.%s)...", domain))
cmd = exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)) cmd = exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain))
output, err = cmd.Output() output, err = cmd.Output()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("nslookup查询失败: %v", err)) common.LogError(fmt.Sprintf("nslookup查询失败: %v", err))
return "", fmt.Errorf("查询域控制器失败: %v", err) return "", fmt.Errorf("查询域控制器失败: %v", err)
} }
@ -790,68 +790,68 @@ func getDomainController() (string, error) {
if len(parts) > 1 { if len(parts) > 1 {
dcHost := strings.TrimSpace(parts[1]) dcHost := strings.TrimSpace(parts[1])
dcHost = strings.TrimSuffix(dcHost, ".") dcHost = strings.TrimSuffix(dcHost, ".")
Common.LogSuccess(fmt.Sprintf("找到域控制器: %s", dcHost)) common.LogSuccess(fmt.Sprintf("找到域控制器: %s", dcHost))
return dcHost, nil return dcHost, nil
} }
} }
} }
// 尝试使用域名前缀加DC后缀 // 尝试使用域名前缀加DC后缀
Common.LogDebug("未从nslookup获取到域控制器尝试使用域名前缀...") common.LogDebug("未从nslookup获取到域控制器尝试使用域名前缀...")
domainParts := strings.Split(domain, ".") domainParts := strings.Split(domain, ".")
if len(domainParts) > 0 { if len(domainParts) > 0 {
dcHost := fmt.Sprintf("dc.%s", domain) dcHost := fmt.Sprintf("dc.%s", domain)
Common.LogDebug(fmt.Sprintf("使用备选域控制器地址: %s", dcHost)) common.LogDebug(fmt.Sprintf("使用备选域控制器地址: %s", dcHost))
return dcHost, nil return dcHost, nil
} }
Common.LogError("无法获取域控制器地址") common.LogError("无法获取域控制器地址")
return "", fmt.Errorf("无法获取域控制器地址") return "", fmt.Errorf("无法获取域控制器地址")
} }
func NewDomainInfo() (*DomainInfo, error) { func NewDomainInfo() (*DomainInfo, error) {
Common.LogDebug("开始初始化域信息...") common.LogDebug("开始初始化域信息...")
// 获取域控制器地址 // 获取域控制器地址
Common.LogDebug("正在获取域控制器地址...") common.LogDebug("正在获取域控制器地址...")
dcHost, err := getDomainController() dcHost, err := getDomainController()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取域控制器失败: %v", err)) common.LogError(fmt.Sprintf("获取域控制器失败: %v", err))
return nil, fmt.Errorf("获取域控制器失败: %v", err) return nil, fmt.Errorf("获取域控制器失败: %v", err)
} }
Common.LogDebug(fmt.Sprintf("成功获取域控制器地址: %s", dcHost)) common.LogDebug(fmt.Sprintf("成功获取域控制器地址: %s", dcHost))
// 创建SSPI客户端 // 创建SSPI客户端
Common.LogDebug("正在创建SSPI客户端...") common.LogDebug("正在创建SSPI客户端...")
ldapClient, err := gssapi.NewSSPIClient() ldapClient, err := gssapi.NewSSPIClient()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("创建SSPI客户端失败: %v", err)) common.LogError(fmt.Sprintf("创建SSPI客户端失败: %v", err))
return nil, fmt.Errorf("创建SSPI客户端失败: %v", err) return nil, fmt.Errorf("创建SSPI客户端失败: %v", err)
} }
defer ldapClient.Close() defer ldapClient.Close()
Common.LogDebug("SSPI客户端创建成功") common.LogDebug("SSPI客户端创建成功")
// 创建LDAP连接 // 创建LDAP连接
Common.LogDebug(fmt.Sprintf("正在连接LDAP服务器 ldap://%s:389", dcHost)) common.LogDebug(fmt.Sprintf("正在连接LDAP服务器 ldap://%s:389", dcHost))
conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost))
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("LDAP连接失败: %v", err)) common.LogError(fmt.Sprintf("LDAP连接失败: %v", err))
return nil, fmt.Errorf("LDAP连接失败: %v", err) return nil, fmt.Errorf("LDAP连接失败: %v", err)
} }
Common.LogDebug("LDAP连接建立成功") common.LogDebug("LDAP连接建立成功")
// 使用GSSAPI进行绑定 // 使用GSSAPI进行绑定
Common.LogDebug(fmt.Sprintf("正在进行GSSAPI绑定 (ldap/%s)...", dcHost)) common.LogDebug(fmt.Sprintf("正在进行GSSAPI绑定 (ldap/%s)...", dcHost))
err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "") err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "")
if err != nil { if err != nil {
conn.Close() conn.Close()
Common.LogError(fmt.Sprintf("GSSAPI绑定失败: %v", err)) common.LogError(fmt.Sprintf("GSSAPI绑定失败: %v", err))
return nil, fmt.Errorf("GSSAPI绑定失败: %v", err) return nil, fmt.Errorf("GSSAPI绑定失败: %v", err)
} }
Common.LogDebug("GSSAPI绑定成功") common.LogDebug("GSSAPI绑定成功")
// 获取defaultNamingContext // 获取defaultNamingContext
Common.LogDebug("正在查询defaultNamingContext...") common.LogDebug("正在查询defaultNamingContext...")
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"", "",
ldap.ScopeBaseObject, ldap.ScopeBaseObject,
@ -865,23 +865,23 @@ func NewDomainInfo() (*DomainInfo, error) {
result, err := conn.Search(searchRequest) result, err := conn.Search(searchRequest)
if err != nil { if err != nil {
conn.Close() conn.Close()
Common.LogError(fmt.Sprintf("获取defaultNamingContext失败: %v", err)) common.LogError(fmt.Sprintf("获取defaultNamingContext失败: %v", err))
return nil, fmt.Errorf("获取defaultNamingContext失败: %v", err) return nil, fmt.Errorf("获取defaultNamingContext失败: %v", err)
} }
if len(result.Entries) == 0 { if len(result.Entries) == 0 {
conn.Close() conn.Close()
Common.LogError("未找到defaultNamingContext") common.LogError("未找到defaultNamingContext")
return nil, fmt.Errorf("未找到defaultNamingContext") return nil, fmt.Errorf("未找到defaultNamingContext")
} }
baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext") baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext")
if baseDN == "" { if baseDN == "" {
Common.LogDebug("defaultNamingContext为空使用备选方法获取BaseDN") common.LogDebug("defaultNamingContext为空使用备选方法获取BaseDN")
baseDN = getDomainDN(dcHost) // 使用备选方法 baseDN = getDomainDN(dcHost) // 使用备选方法
} }
Common.LogSuccess(fmt.Sprintf("初始化完成使用BaseDN: %s", baseDN)) common.LogSuccess(fmt.Sprintf("初始化完成使用BaseDN: %s", baseDN))
return &DomainInfo{ return &DomainInfo{
conn: conn, conn: conn,
@ -889,13 +889,13 @@ func NewDomainInfo() (*DomainInfo, error) {
}, nil }, nil
} }
func DCInfoScan(info *Common.HostInfo) (err error) { func DCInfoScan(info *common.HostInfo) (err error) {
// 创建DomainInfo实例 // 创建DomainInfo实例
Common.LogDebug("正在初始化域信息...") common.LogDebug("正在初始化域信息...")
di, err := NewDomainInfo() di, err := NewDomainInfo()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("初始化域信息失败: %v", err)) common.LogError(fmt.Sprintf("初始化域信息失败: %v", err))
return err return err
} }
defer di.Close() defer di.Close()
@ -903,7 +903,7 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
// 获取特殊计算机列表 // 获取特殊计算机列表
specialComputers, err := di.GetSpecialComputers() specialComputers, err := di.GetSpecialComputers()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取特殊计算机失败: %v", err)) common.LogError(fmt.Sprintf("获取特殊计算机失败: %v", err))
} else { } else {
categories := []string{ categories := []string{
"SQL服务器", "SQL服务器",
@ -912,12 +912,12 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
"Exchange服务器", "Exchange服务器",
} }
Common.LogSuccess("[*] 特殊计算机信息:") common.LogSuccess("[*] 特殊计算机信息:")
for _, category := range categories { for _, category := range categories {
if computers, ok := specialComputers[category]; ok { if computers, ok := specialComputers[category]; ok {
Common.LogSuccess(fmt.Sprintf("[+] %s:", category)) common.LogSuccess(fmt.Sprintf("[+] %s:", category))
for _, computer := range computers { for _, computer := range computers {
Common.LogSuccess(fmt.Sprintf(" %s", computer)) common.LogSuccess(fmt.Sprintf(" %s", computer))
} }
} }
} }
@ -926,47 +926,47 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
// 获取域用户 // 获取域用户
users, err := di.GetDomainUsers() users, err := di.GetDomainUsers()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取域用户失败: %v", err)) common.LogError(fmt.Sprintf("获取域用户失败: %v", err))
} else { } else {
Common.LogSuccess("[*] 域用户列表:") common.LogSuccess("[*] 域用户列表:")
for _, user := range users { for _, user := range users {
Common.LogSuccess(fmt.Sprintf(" %s", user)) common.LogSuccess(fmt.Sprintf(" %s", user))
} }
} }
// 获取域管理员 // 获取域管理员
admins, err := di.GetDomainAdmins() admins, err := di.GetDomainAdmins()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取域管理员失败: %v", err)) common.LogError(fmt.Sprintf("获取域管理员失败: %v", err))
} else { } else {
Common.LogSuccess("[*] 域管理员列表:") common.LogSuccess("[*] 域管理员列表:")
for _, admin := range admins { for _, admin := range admins {
Common.LogSuccess(fmt.Sprintf(" %s", admin)) common.LogSuccess(fmt.Sprintf(" %s", admin))
} }
} }
// 获取组织单位 // 获取组织单位
ous, err := di.GetOUs() ous, err := di.GetOUs()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取组织单位失败: %v", err)) common.LogError(fmt.Sprintf("获取组织单位失败: %v", err))
} else { } else {
Common.LogSuccess("[*] 组织单位:") common.LogSuccess("[*] 组织单位:")
for _, ou := range ous { for _, ou := range ous {
Common.LogSuccess(fmt.Sprintf(" %s", ou)) common.LogSuccess(fmt.Sprintf(" %s", ou))
} }
} }
// 获取域计算机 // 获取域计算机
computers, err := di.GetComputers() computers, err := di.GetComputers()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取域计算机失败: %v", err)) common.LogError(fmt.Sprintf("获取域计算机失败: %v", err))
} else { } else {
Common.LogSuccess("[*] 域计算机:") common.LogSuccess("[*] 域计算机:")
for _, computer := range computers { for _, computer := range computers {
if computer.OperatingSystem != "" { if computer.OperatingSystem != "" {
Common.LogSuccess(fmt.Sprintf(" %s --> %s", computer.Name, computer.OperatingSystem)) common.LogSuccess(fmt.Sprintf(" %s --> %s", computer.Name, computer.OperatingSystem))
} else { } else {
Common.LogSuccess(fmt.Sprintf(" %s", computer.Name)) common.LogSuccess(fmt.Sprintf(" %s", computer.Name))
} }
} }
} }
@ -974,20 +974,20 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
// 获取信任域关系 // 获取信任域关系
trustDomains, err := di.GetTrustDomains() trustDomains, err := di.GetTrustDomains()
if err == nil && len(trustDomains) > 0 { if err == nil && len(trustDomains) > 0 {
Common.LogSuccess("[*] 信任域关系:") common.LogSuccess("[*] 信任域关系:")
for _, domain := range trustDomains { for _, domain := range trustDomains {
Common.LogSuccess(fmt.Sprintf(" %s", domain)) common.LogSuccess(fmt.Sprintf(" %s", domain))
} }
} }
// 获取域管理员组信息 // 获取域管理员组信息
adminGroups, err := di.GetAdminGroups() adminGroups, err := di.GetAdminGroups()
if err == nil && len(adminGroups) > 0 { if err == nil && len(adminGroups) > 0 {
Common.LogSuccess("[*] 管理员组信息:") common.LogSuccess("[*] 管理员组信息:")
for groupName, members := range adminGroups { for groupName, members := range adminGroups {
Common.LogSuccess(fmt.Sprintf("[+] %s成员:", groupName)) common.LogSuccess(fmt.Sprintf("[+] %s成员:", groupName))
for _, member := range members { for _, member := range members {
Common.LogSuccess(fmt.Sprintf(" %s", member)) common.LogSuccess(fmt.Sprintf(" %s", member))
} }
} }
} }
@ -995,11 +995,11 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
// 获取委派信息 // 获取委派信息
delegations, err := di.GetDelegation() delegations, err := di.GetDelegation()
if err == nil && len(delegations) > 0 { if err == nil && len(delegations) > 0 {
Common.LogSuccess("[*] 委派信息:") common.LogSuccess("[*] 委派信息:")
for delegationType, entries := range delegations { for delegationType, entries := range delegations {
Common.LogSuccess(fmt.Sprintf("[+] %s:", delegationType)) common.LogSuccess(fmt.Sprintf("[+] %s:", delegationType))
for _, entry := range entries { for _, entry := range entries {
Common.LogSuccess(fmt.Sprintf(" %s", entry)) common.LogSuccess(fmt.Sprintf(" %s", entry))
} }
} }
} }
@ -1007,31 +1007,31 @@ func DCInfoScan(info *Common.HostInfo) (err error) {
// 获取AS-REP Roasting漏洞用户 // 获取AS-REP Roasting漏洞用户
asrepUsers, err := di.GetAsrepRoastUsers() asrepUsers, err := di.GetAsrepRoastUsers()
if err == nil && len(asrepUsers) > 0 { if err == nil && len(asrepUsers) > 0 {
Common.LogSuccess("[*] AS-REP弱口令账户:") common.LogSuccess("[*] AS-REP弱口令账户:")
for _, user := range asrepUsers { for _, user := range asrepUsers {
Common.LogSuccess(fmt.Sprintf(" %s", user)) common.LogSuccess(fmt.Sprintf(" %s", user))
} }
} }
// 获取域密码策略 // 获取域密码策略
passwordPolicy, err := di.GetPasswordPolicy() passwordPolicy, err := di.GetPasswordPolicy()
if err == nil && len(passwordPolicy) > 0 { if err == nil && len(passwordPolicy) > 0 {
Common.LogSuccess("[*] 域密码策略:") common.LogSuccess("[*] 域密码策略:")
for key, value := range passwordPolicy { for key, value := range passwordPolicy {
Common.LogSuccess(fmt.Sprintf(" %s: %s", key, value)) common.LogSuccess(fmt.Sprintf(" %s: %s", key, value))
} }
} }
// 获取SPN信息 // 获取SPN信息
spns, err := di.GetSPNs() spns, err := di.GetSPNs()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取SPN信息失败: %v", err)) common.LogError(fmt.Sprintf("获取SPN信息失败: %v", err))
} else if len(spns) > 0 { } else if len(spns) > 0 {
Common.LogSuccess("[*] SPN信息:") common.LogSuccess("[*] SPN信息:")
for dn, spnList := range spns { for dn, spnList := range spns {
Common.LogSuccess(fmt.Sprintf("[+] %s", dn)) common.LogSuccess(fmt.Sprintf("[+] %s", dn))
for _, spn := range spnList { for _, spn := range spnList {
Common.LogSuccess(fmt.Sprintf(" %s", spn)) common.LogSuccess(fmt.Sprintf(" %s", spn))
} }
} }
} }

View File

@ -2,8 +2,9 @@
package Plugins package Plugins
import "github.com/shadow1ng/fscan/Common" import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func DCInfoScan(info *Common.HostInfo) (err error) { func DCInfoScan(info *common.HostInfo) (err error) {
return nil return nil
} }

View File

@ -1,345 +0,0 @@
package Plugins
import (
"context"
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
"time"
)
// FtpCredential 表示一个FTP凭据
type FtpCredential struct {
Username string
Password string
}
// FtpScanResult 表示FTP扫描结果
type FtpScanResult struct {
Success bool
Error error
Credential FtpCredential
Directories []string
IsAnonymous bool
}
func FtpScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 首先尝试匿名登录
Common.LogDebug("尝试匿名登录...")
anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, Common.Timeout, Common.MaxRetries)
if anonymousResult.Success {
// 匿名登录成功
saveFtpResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []FtpCredential
for _, user := range Common.Userdict["ftp"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, FtpCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ftp"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentFtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 保存成功结果
saveFtpResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("FTP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录
return nil
}
}
// concurrentFtpScan 并发扫描FTP服务
func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *FtpScanResult, 1)
workChan := make(chan FtpCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryFtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("FTP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryFtpCredential 尝试单个FTP凭据
func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &FtpScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建结果通道
resultChan := make(chan struct {
success bool
directories []string
err error
}, 1)
// 在协程中尝试连接
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
go func() {
defer cancel()
success, dirs, err := FtpConn(info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
case resultChan <- struct {
success bool
directories []string
err error
}{success, dirs, err}:
}
}()
// 等待结果或超时
var success bool
var dirs []string
var err error
select {
case result := <-resultChan:
success = result.success
dirs = result.directories
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
return &FtpScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
if success {
isAnonymous := credential.Username == "anonymous" && credential.Password == ""
return &FtpScanResult{
Success: true,
Credential: credential,
Directories: dirs,
IsAnonymous: isAnonymous,
}
}
lastErr = err
if err != nil {
// 登录错误不需要重试
if strings.Contains(err.Error(), "Login incorrect") {
break
}
// 连接数过多需要等待
if strings.Contains(err.Error(), "too many connections") {
Common.LogDebug("连接数过多等待5秒...")
time.Sleep(5 * time.Second)
continue
}
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break
}
}
}
}
return &FtpScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// FtpConn 建立FTP连接并尝试登录
func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, directories []string, err error) {
Host, Port := info.Host, info.Ports
// 建立FTP连接
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, nil, err
}
defer func() {
if conn != nil {
conn.Quit()
}
}()
// 尝试登录
if err = conn.Login(user, pass); err != nil {
return false, nil, err
}
// 获取目录信息
dirs, err := conn.List("")
if err == nil && len(dirs) > 0 {
directories = make([]string, 0, min(6, len(dirs)))
for i := 0; i < len(dirs) && i < 6; i++ {
name := dirs[i].Name
if len(name) > 50 {
name = name[:50]
}
directories = append(directories, name)
}
}
return true, directories, nil
}
// saveFtpResult 保存FTP扫描结果
func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("FTP服务 %s 匿名登录成功!", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": "anonymous",
"password": "",
"type": "anonymous-login",
"directories": result.Directories,
}
} else {
successMsg = fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": result.Credential.Username,
"password": result.Credential.Password,
"type": "weak-password",
"directories": result.Directories,
}
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
}
// min 返回两个整数中的较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -1,326 +0,0 @@
package Plugins
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/Common"
)
// IMAPCredential 表示一个IMAP凭据
type IMAPCredential struct {
Username string
Password string
}
// IMAPScanResult 表示IMAP扫描结果
type IMAPScanResult struct {
Success bool
Error error
Credential IMAPCredential
}
// IMAPScan 主扫描函数
func IMAPScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []IMAPCredential
for _, user := range Common.Userdict["imap"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, IMAPCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["imap"]), len(Common.Passwords), len(credentials)))
// 并发扫描
result := concurrentIMAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveIMAPResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("IMAP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentIMAPScan 并发扫描IMAP服务
func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *IMAPScanResult, 1)
workChan := make(chan IMAPCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryIMAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("IMAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryIMAPCredential 尝试单个IMAP凭据
func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &IMAPScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := IMAPConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &IMAPScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &IMAPScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// IMAPConn 连接测试函数
func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", host, port)
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中尝试连接
go func() {
// 先尝试普通连接
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", addr)
if err == nil {
flag, authErr := tryIMAPAuth(conn, user, pass, timeout)
conn.Close()
if authErr == nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{flag, nil}:
}
return
}
}
// 如果普通连接失败或认证失败尝试TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
// 使用支持代理的TLS连接
tlsConn, tlsErr := Common.WrapperTlsWithContext(ctx, "tcp", addr, tlsConfig)
if tlsErr != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, fmt.Errorf("TLS连接失败: %v", tlsErr)}:
}
return
}
defer tlsConn.Close()
flag, authErr := tryIMAPAuth(tlsConn, user, pass, timeout)
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{flag, authErr}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// tryIMAPAuth 尝试IMAP认证
func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
conn.SetDeadline(time.Now().Add(timeout))
reader := bufio.NewReader(conn)
_, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
}
loginCmd := fmt.Sprintf("a001 LOGIN \"%s\" \"%s\"\r\n", user, pass)
_, err = conn.Write([]byte(loginCmd))
if err != nil {
return false, fmt.Errorf("发送登录命令失败: %v", err)
}
for {
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return false, fmt.Errorf("认证失败")
}
return false, fmt.Errorf("读取响应失败: %v", err)
}
if strings.Contains(response, "a001 OK") {
return true, nil
}
if strings.Contains(response, "a001 NO") || strings.Contains(response, "a001 BAD") {
return false, fmt.Errorf("认证失败")
}
}
}
// saveIMAPResult 保存IMAP扫描结果
func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredential) {
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "imap",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
}

View File

@ -1,327 +0,0 @@
package Plugins
import (
"context"
"fmt"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/Common"
"strings"
"sync"
"time"
)
// KafkaCredential 表示Kafka凭据
type KafkaCredential struct {
Username string
Password string
}
// KafkaScanResult 表示扫描结果
type KafkaScanResult struct {
Success bool
IsUnauth bool
Error error
Credential KafkaCredential
}
func KafkaScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 先尝试无认证访问
Common.LogDebug("尝试无认证访问...")
unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, Common.Timeout, Common.MaxRetries)
if unauthResult.Success {
// 无认证访问成功
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
// 保存无认证访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "unauthorized-access",
},
}
Common.SaveResult(result)
return nil
}
// 构建凭据列表
var credentials []KafkaCredential
for _, user := range Common.Userdict["kafka"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, KafkaCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["kafka"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentKafkaScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
},
}
Common.SaveResult(vulnResult)
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s",
target, result.Credential.Username, result.Credential.Password))
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Kafka扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证
return nil
}
}
// concurrentKafkaScan 并发扫描Kafka服务
func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *KafkaScanResult, 1)
workChan := make(chan KafkaCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryKafkaCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("Kafka并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryKafkaCredential 尝试单个Kafka凭据
func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &KafkaScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 在协程中执行Kafka连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := KafkaConn(info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
// 连接超时或被取消
case resultChan <- struct {
success bool
err error
}{success, err}:
// 发送结果
}
}()
// 等待结果或超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
cancel()
return &KafkaScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
cancel() // 清理单个连接上下文
if success {
isUnauth := credential.Username == "" && credential.Password == ""
return &KafkaScanResult{
Success: true,
IsUnauth: isUnauth,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 记录错误
Common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v",
credential.Username, credential.Password, err))
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &KafkaScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// KafkaConn 尝试 Kafka 连接
func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
config := sarama.NewConfig()
config.Net.DialTimeout = timeout
config.Net.ReadTimeout = timeout
config.Net.WriteTimeout = timeout
config.Net.TLS.Enable = false
config.Version = sarama.V2_0_0_0
// 设置 SASL 配置
if user != "" || pass != "" {
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.SASL.User = user
config.Net.SASL.Password = pass
config.Net.SASL.Handshake = true
}
brokers := []string{fmt.Sprintf("%s:%s", host, port)}
// 尝试作为消费者连接测试
consumer, err := sarama.NewConsumer(brokers, config)
if err == nil {
defer consumer.Close()
return true, nil
}
// 如果消费者连接失败,尝试作为客户端连接
client, err := sarama.NewClient(brokers, config)
if err == nil {
defer client.Close()
return true, nil
}
// 检查错误类型
if strings.Contains(err.Error(), "SASL") ||
strings.Contains(err.Error(), "authentication") ||
strings.Contains(err.Error(), "credentials") {
return false, fmt.Errorf("认证失败")
}
return false, err
}

View File

@ -1,307 +0,0 @@
package Plugins
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common"
)
// LDAPCredential 表示一个LDAP凭据
type LDAPCredential struct {
Username string
Password string
}
// LDAPScanResult 表示LDAP扫描结果
type LDAPScanResult struct {
Success bool
Error error
Credential LDAPCredential
IsAnonymous bool
}
func LDAPScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 首先尝试匿名访问
Common.LogDebug("尝试匿名访问...")
anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, Common.Timeout, 1)
if anonymousResult.Success {
// 匿名访问成功
saveLDAPResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []LDAPCredential
for _, user := range Common.Userdict["ldap"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, LDAPCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ldap"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentLDAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveLDAPResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("LDAP扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// concurrentLDAPScan 并发扫描LDAP服务
func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *LDAPScanResult, 1)
workChan := make(chan LDAPCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryLDAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("LDAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryLDAPCredential 尝试单个LDAP凭据
func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &LDAPScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := LDAPConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
isAnonymous := credential.Username == "" && credential.Password == ""
return &LDAPScanResult{
Success: true,
Credential: credential,
IsAnonymous: isAnonymous,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &LDAPScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// LDAPConn 尝试LDAP连接
func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 使用上下文控制的拨号过程
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", address)
if err != nil {
return false, err
}
// 使用已连接的TCP连接创建LDAP连接
l := ldap.NewConn(conn, false)
defer l.Close()
// 在单独的协程中启动LDAP连接
go l.Start()
// 创建一个完成通道
done := make(chan error, 1)
// 在协程中进行绑定和搜索操作,确保可以被上下文取消
go func() {
// 尝试绑定
var err error
if user != "" {
// 使用更通用的绑定DN模式
bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user)
err = l.Bind(bindDN, pass)
} else {
// 匿名绑定
err = l.UnauthenticatedBind("")
}
if err != nil {
done <- err
return
}
// 尝试简单搜索以验证权限
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(objectClass=*)",
[]string{"dn"},
nil,
)
_, err = l.Search(searchRequest)
done <- err
}()
// 等待操作完成或上下文取消
select {
case err := <-done:
if err != nil {
return false, err
}
return true, nil
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveLDAPResult 保存LDAP扫描结果
func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("LDAP服务 %s 匿名访问成功", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"type": "anonymous-access",
}
} else {
successMsg = fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"username": result.Credential.Username,
"password": result.Credential.Password,
"type": "weak-password",
}
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
}

View File

@ -2,7 +2,7 @@ package Plugins
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -91,13 +91,13 @@ var (
) )
// LocalInfoScan 本地信息收集主函数 // LocalInfoScan 本地信息收集主函数
func LocalInfoScan(info *Common.HostInfo) (err error) { func LocalInfoScan(info *common.HostInfo) (err error) {
Common.LogBase("开始本地信息收集...") common.LogBase("开始本地信息收集...")
// 获取用户主目录 // 获取用户主目录
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err)) common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err))
return err return err
} }
@ -107,7 +107,7 @@ func LocalInfoScan(info *Common.HostInfo) (err error) {
// 根据规则搜索敏感文件 // 根据规则搜索敏感文件
searchSensitiveFiles() searchSensitiveFiles()
Common.LogBase("本地信息收集完成") common.LogBase("本地信息收集完成")
return nil return nil
} }
@ -154,7 +154,7 @@ func scanFixedLocations(home string) {
// checkAndLogFile 检查并记录敏感文件 // checkAndLogFile 检查并记录敏感文件
func checkAndLogFile(path string) { func checkAndLogFile(path string) {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
Common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path)) common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path))
} }
} }
@ -208,7 +208,7 @@ func searchSensitiveFiles() {
for _, white := range whitelist { for _, white := range whitelist {
fileName := strings.ToLower(info.Name()) fileName := strings.ToLower(info.Name())
if strings.Contains(fileName, white) { if strings.Contains(fileName, white) {
Common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path)) common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
break break
} }
} }

View File

@ -1,332 +0,0 @@
package Plugins
import (
"context"
"database/sql"
"fmt"
"net"
"strings"
"sync"
"time"
mssql "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/Common"
)
// MSSQLProxyDialer 自定义dialer结构体
type MSSQLProxyDialer struct {
timeout time.Duration
}
// DialContext 实现mssql.Dialer接口支持socks代理
func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, network, addr)
}
// MssqlCredential 表示一个MSSQL凭据
type MssqlCredential struct {
Username string
Password string
}
// MssqlScanResult 表示MSSQL扫描结果
type MssqlScanResult struct {
Success bool
Error error
Credential MssqlCredential
}
// MssqlScan 执行MSSQL服务扫描
func MssqlScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []MssqlCredential
for _, user := range Common.Userdict["mssql"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, MssqlCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["mssql"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentMssqlScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveMssqlResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("MSSQL扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentMssqlScan 并发扫描MSSQL服务
func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials []MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *MssqlScanResult, 1)
workChan := make(chan MssqlCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryMssqlCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("MSSQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryMssqlCredential 尝试单个MSSQL凭据
func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &MssqlScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := MssqlConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &MssqlScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &MssqlScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// MssqlConn 尝试MSSQL连接
func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf(
"server=%s;user id=%s;password=%s;port=%v;encrypt=disable;",
host, username, password, port,
)
// 检查是否需要使用socks代理
if Common.Socks5Proxy != "" {
// 使用自定义dialer创建连接器
connector, err := mssql.NewConnector(connStr)
if err != nil {
return false, err
}
// 设置自定义dialer
connector.Dialer = &MSSQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
// 使用连接器创建数据库连接
db := sql.OpenDB(connector)
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 通过上下文执行ping操作以支持超时控制
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
defer pingCancel()
errChan := make(chan error, 1)
go func() {
errChan <- db.PingContext(pingCtx)
}()
// 等待ping结果或者超时
select {
case err := <-errChan:
if err != nil {
return false, err
}
return true, nil
case <-ctx.Done():
// 全局超时或取消
return false, ctx.Err()
case <-pingCtx.Done():
if pingCtx.Err() == context.DeadlineExceeded {
// 单个连接超时
return false, fmt.Errorf("连接超时")
}
return false, pingCtx.Err()
}
}
// 使用标准连接方式
db, err := sql.Open("mssql", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 通过上下文执行ping操作以支持超时控制
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
defer pingCancel()
errChan := make(chan error, 1)
go func() {
errChan <- db.PingContext(pingCtx)
}()
// 等待ping结果或者超时
select {
case err := <-errChan:
if err != nil {
return false, err
}
return true, nil
case <-ctx.Done():
// 全局超时或取消
return false, ctx.Err()
case <-pingCtx.Done():
if pingCtx.Err() == context.DeadlineExceeded {
// 单个连接超时
return false, fmt.Errorf("连接超时")
}
return false, pingCtx.Err()
}
}
// saveMssqlResult 保存MSSQL扫描结果
func saveMssqlResult(info *Common.HostInfo, target string, credential MssqlCredential) {
successMsg := fmt.Sprintf("MSSQL %s %v %v", target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mssql",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
}

View File

@ -1,160 +0,0 @@
package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MemcachedScanResult 表示Memcached扫描结果
type MemcachedScanResult struct {
Success bool
Error error
Stats string
}
// MemcachedScan 检测Memcached未授权访问
func MemcachedScan(info *Common.HostInfo) error {
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost))
// 尝试连接并检查未授权访问
result := tryMemcachedConnection(ctx, info, Common.Timeout)
if result.Success {
// 保存成功结果
scanResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "memcached",
"type": "unauthorized-access",
"description": "Memcached unauthorized access",
"stats": result.Stats,
},
}
Common.SaveResult(scanResult)
Common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost))
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded {
Common.LogDebug("Memcached扫描全局超时")
return fmt.Errorf("全局超时")
}
default:
}
Common.LogDebug(fmt.Sprintf("Memcached扫描完成: %s", realhost))
return result.Error
}
// tryMemcachedConnection 尝试连接Memcached并检查未授权访问
func tryMemcachedConnection(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64) *MemcachedScanResult {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
timeout := time.Duration(timeoutSeconds) * time.Second
// 创建结果通道
resultChan := make(chan *MemcachedScanResult, 1)
// 创建连接上下文,带超时
connCtx, connCancel := context.WithTimeout(ctx, timeout)
defer connCancel()
// 在协程中尝试连接
go func() {
// 构建结果结构
result := &MemcachedScanResult{
Success: false,
Error: nil,
Stats: "",
}
// 建立TCP连接
client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout)
if err != nil {
result.Error = err
select {
case <-connCtx.Done():
case resultChan <- result:
}
return
}
defer client.Close()
// 设置操作截止时间
if err := client.SetDeadline(time.Now().Add(timeout)); err != nil {
result.Error = err
select {
case <-connCtx.Done():
case resultChan <- result:
}
return
}
// 发送stats命令
if _, err := client.Write([]byte("stats\n")); err != nil {
result.Error = err
select {
case <-connCtx.Done():
case resultChan <- result:
}
return
}
// 读取响应
rev := make([]byte, 1024)
n, err := client.Read(rev)
if err != nil {
result.Error = err
select {
case <-connCtx.Done():
case resultChan <- result:
}
return
}
// 检查响应是否包含统计信息
response := string(rev[:n])
if strings.Contains(response, "STAT") {
result.Success = true
result.Stats = response
}
// 发送结果
select {
case <-connCtx.Done():
case resultChan <- result:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局上下文取消
return &MemcachedScanResult{
Success: false,
Error: ctx.Err(),
}
}
// 连接超时
return &MemcachedScanResult{
Success: false,
Error: fmt.Errorf("连接超时"),
}
}
}

View File

@ -4,7 +4,7 @@ package Plugins
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"os" "os"
"path/filepath" "path/filepath"
@ -276,33 +276,33 @@ func IsAdmin() bool {
return err == nil && member return err == nil && member
} }
func MiniDump(info *Common.HostInfo) (err error) { func MiniDump(info *common.HostInfo) (err error) {
// 先检查管理员权限 // 先检查管理员权限
if !IsAdmin() { if !IsAdmin() {
Common.LogError("需要管理员权限才能执行此操作") common.LogError("需要管理员权限才能执行此操作")
return fmt.Errorf("需要管理员权限才能执行此操作") return fmt.Errorf("需要管理员权限才能执行此操作")
} }
pm, err := NewProcessManager() pm, err := NewProcessManager()
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err)) common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err))
return fmt.Errorf("初始化进程管理器失败: %v", err) return fmt.Errorf("初始化进程管理器失败: %v", err)
} }
// 查找 lsass.exe // 查找 lsass.exe
pid, err := pm.FindProcess("lsass.exe") pid, err := pm.FindProcess("lsass.exe")
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("查找进程失败: %v", err)) common.LogError(fmt.Sprintf("查找进程失败: %v", err))
return fmt.Errorf("查找进程失败: %v", err) return fmt.Errorf("查找进程失败: %v", err)
} }
Common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid)) common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid))
// 提升权限 // 提升权限
if err := pm.ElevatePrivileges(); err != nil { if err := pm.ElevatePrivileges(); err != nil {
Common.LogError(fmt.Sprintf("提升权限失败: %v", err)) common.LogError(fmt.Sprintf("提升权限失败: %v", err))
return fmt.Errorf("提升权限失败: %v", err) return fmt.Errorf("提升权限失败: %v", err)
} }
Common.LogSuccess("成功提升进程权限") common.LogSuccess("成功提升进程权限")
// 创建输出路径 // 创建输出路径
outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid)) outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid))
@ -310,10 +310,10 @@ func MiniDump(info *Common.HostInfo) (err error) {
// 执行转储 // 执行转储
if err := pm.DumpProcess(pid, outputPath); err != nil { if err := pm.DumpProcess(pid, outputPath); err != nil {
os.Remove(outputPath) os.Remove(outputPath)
Common.LogError(fmt.Sprintf("进程转储失败: %v", err)) common.LogError(fmt.Sprintf("进程转储失败: %v", err))
return fmt.Errorf("进程转储失败: %v", err) return fmt.Errorf("进程转储失败: %v", err)
} }
Common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath)) common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath))
return nil return nil
} }

View File

@ -2,8 +2,9 @@
package Plugins package Plugins
import "github.com/shadow1ng/fscan/Common" import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func MiniDump(info *Common.HostInfo) (err error) { func MiniDump(info *common.HostInfo) (err error) {
return nil return nil
} }

View File

@ -1,273 +0,0 @@
package Plugins
import (
"context"
"encoding/binary"
"fmt"
"time"
"github.com/shadow1ng/fscan/Common"
)
// ModbusScanResult 表示 Modbus 扫描结果
type ModbusScanResult struct {
Success bool
DeviceInfo string
Error error
}
// ModbusScan 执行 Modbus 服务扫描
func ModbusScan(info *Common.HostInfo) error {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 执行扫描
result := tryModbusScan(ctx, info, Common.Timeout, Common.MaxRetries)
if result.Success {
// 保存扫描结果
saveModbusResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Modbus 扫描全局超时")
return fmt.Errorf("全局超时")
default:
if result.Error != nil {
Common.LogDebug(fmt.Sprintf("Modbus 扫描失败: %v", result.Error))
return result.Error
}
Common.LogDebug("Modbus 扫描完成,未发现服务")
return nil
}
}
// tryModbusScan 尝试单个 Modbus 扫描
func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64, maxRetries int) *ModbusScanResult {
var lastErr error
host, port := info.Host, info.Ports
target := fmt.Sprintf("%s:%s", host, port)
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &ModbusScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试 Modbus 扫描: %s", retry+1, target))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, connCancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 创建结果通道
resultChan := make(chan *ModbusScanResult, 1)
// 在协程中执行扫描
go func() {
// 尝试建立连接
conn, err := Common.WrapperTcpWithContext(connCtx, "tcp", target)
if err != nil {
select {
case <-connCtx.Done():
case resultChan <- &ModbusScanResult{Success: false, Error: err}:
}
return
}
defer conn.Close()
// 构造 Modbus TCP 请求包 - 读取设备ID
request := buildModbusRequest()
// 设置读写超时
conn.SetDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second))
// 发送请求
_, err = conn.Write(request)
if err != nil {
select {
case <-connCtx.Done():
case resultChan <- &ModbusScanResult{
Success: false,
Error: fmt.Errorf("发送Modbus请求失败: %v", err),
}:
}
return
}
// 读取响应
response := make([]byte, 256)
n, err := conn.Read(response)
if err != nil {
select {
case <-connCtx.Done():
case resultChan <- &ModbusScanResult{
Success: false,
Error: fmt.Errorf("读取Modbus响应失败: %v", err),
}:
}
return
}
// 验证响应
if isValidModbusResponse(response[:n]) {
// 获取设备信息
deviceInfo := parseModbusResponse(response[:n])
select {
case <-connCtx.Done():
case resultChan <- &ModbusScanResult{
Success: true,
DeviceInfo: deviceInfo,
}:
}
return
}
select {
case <-connCtx.Done():
case resultChan <- &ModbusScanResult{
Success: false,
Error: fmt.Errorf("非Modbus服务或访问被拒绝"),
}:
}
}()
// 等待扫描结果或超时
var result *ModbusScanResult
select {
case res := <-resultChan:
result = res
case <-connCtx.Done():
if ctx.Err() != nil {
connCancel()
return &ModbusScanResult{
Success: false,
Error: ctx.Err(),
}
}
result = &ModbusScanResult{
Success: false,
Error: fmt.Errorf("连接超时"),
}
}
connCancel()
if result.Success {
return result
}
lastErr = result.Error
if result.Error != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(result.Error); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &ModbusScanResult{
Success: false,
Error: lastErr,
}
}
// buildModbusRequest 构建Modbus TCP请求包
func buildModbusRequest() []byte {
request := make([]byte, 12)
// Modbus TCP头部
binary.BigEndian.PutUint16(request[0:], 0x0001) // 事务标识符
binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符
binary.BigEndian.PutUint16(request[4:], 0x0006) // 长度
request[6] = 0x01 // 单元标识符
// Modbus 请求
request[7] = 0x01 // 功能码: Read Coils
binary.BigEndian.PutUint16(request[8:], 0x0000) // 起始地址
binary.BigEndian.PutUint16(request[10:], 0x0001) // 读取数量
return request
}
// isValidModbusResponse 验证Modbus响应是否有效
func isValidModbusResponse(response []byte) bool {
if len(response) < 9 {
return false
}
// 检查协议标识符
protocolID := binary.BigEndian.Uint16(response[2:])
if protocolID != 0 {
return false
}
// 检查功能码
funcCode := response[7]
if funcCode == 0x81 { // 错误响应
return false
}
return true
}
// parseModbusResponse 解析Modbus响应获取设备信息
func parseModbusResponse(response []byte) string {
if len(response) < 9 {
return ""
}
// 提取更多设备信息
unitID := response[6]
funcCode := response[7]
// 简单的设备信息提取,实际应用中可以提取更多信息
info := fmt.Sprintf("Unit ID: %d, Function: 0x%02X", unitID, funcCode)
// 如果是读取线圈响应,尝试解析线圈状态
if funcCode == 0x01 && len(response) >= 10 {
byteCount := response[8]
if byteCount > 0 && len(response) >= 9+int(byteCount) {
coilValue := response[9] & 0x01 // 获取第一个线圈状态
info += fmt.Sprintf(", Coil Status: %d", coilValue)
}
}
return info
}
// saveModbusResult 保存Modbus扫描结果
func saveModbusResult(info *Common.HostInfo, target string, result *ModbusScanResult) {
// 保存扫描结果
scanResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "modbus",
"type": "unauthorized-access",
"device_info": result.DeviceInfo,
},
}
Common.SaveResult(scanResult)
// 控制台输出
Common.LogSuccess(fmt.Sprintf("Modbus服务 %s 无认证访问", target))
if result.DeviceInfo != "" {
Common.LogSuccess(fmt.Sprintf("设备信息: %s", result.DeviceInfo))
}
}

View File

@ -1,190 +0,0 @@
package Plugins
import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/shadow1ng/fscan/Common"
)
// MongodbScan 执行MongoDB未授权扫描
func MongodbScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 创建结果通道
resultChan := make(chan struct {
isUnauth bool
err error
}, 1)
// 在协程中执行扫描
go func() {
isUnauth, err := MongodbUnauth(ctx, info)
select {
case <-ctx.Done():
case resultChan <- struct {
isUnauth bool
err error
}{isUnauth, err}:
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.err != nil {
errlog := fmt.Sprintf("MongoDB %v %v", target, result.err)
Common.LogError(errlog)
return result.err
} else if result.isUnauth {
// 记录控制台输出
Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
// 保存未授权访问结果
scanResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mongodb",
"type": "unauthorized-access",
"protocol": "mongodb",
},
}
Common.SaveResult(scanResult)
} else {
Common.LogDebug(fmt.Sprintf("MongoDB %v 需要认证", target))
}
return nil
case <-ctx.Done():
Common.LogError(fmt.Sprintf("MongoDB扫描超时: %s", target))
return fmt.Errorf("全局超时")
}
}
// MongodbUnauth 检测MongoDB未授权访问
func MongodbUnauth(ctx context.Context, info *Common.HostInfo) (bool, error) {
msgPacket := createOpMsgPacket()
queryPacket := createOpQueryPacket()
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("检测MongoDB未授权访问: %s", realhost))
// 尝试OP_MSG查询
Common.LogDebug("尝试使用OP_MSG协议")
reply, err := checkMongoAuth(ctx, realhost, msgPacket)
if err != nil {
Common.LogDebug(fmt.Sprintf("OP_MSG查询失败: %v, 尝试使用OP_QUERY协议", err))
// 失败则尝试OP_QUERY查询
reply, err = checkMongoAuth(ctx, realhost, queryPacket)
if err != nil {
Common.LogDebug(fmt.Sprintf("OP_QUERY查询也失败: %v", err))
return false, err
}
}
// 检查响应结果
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply)))
if strings.Contains(reply, "totalLinesWritten") {
Common.LogDebug("响应中包含totalLinesWritten确认未授权访问")
return true, nil
}
Common.LogDebug("响应未包含预期内容,可能需要认证")
return false, nil
}
// checkMongoAuth 检查MongoDB认证状态
func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) {
Common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address))
// 使用带超时的连接
conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return "", fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 检查上下文是否已取消
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// 设置读写超时
if err := conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return "", fmt.Errorf("设置超时失败: %v", err)
}
// 发送查询包
Common.LogDebug("发送查询包")
if _, err := conn.Write(packet); err != nil {
return "", fmt.Errorf("发送查询失败: %v", err)
}
// 再次检查上下文是否已取消
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// 读取响应
Common.LogDebug("读取响应")
reply := make([]byte, 2048)
count, err := conn.Read(reply)
if err != nil && err != io.EOF {
return "", fmt.Errorf("读取响应失败: %v", err)
}
if count == 0 {
return "", fmt.Errorf("收到空响应")
}
Common.LogDebug(fmt.Sprintf("成功接收响应,字节数: %d", count))
return string(reply[:count]), nil
}
// createOpMsgPacket 创建OP_MSG查询包
func createOpMsgPacket() []byte {
return []byte{
0x69, 0x00, 0x00, 0x00, // messageLength
0x39, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG
0x00, 0x00, 0x00, 0x00, // flagBits
// sections db.adminCommand({getLog: "startupWarnings"})
0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00,
}
}
// createOpQueryPacket 创建OP_QUERY查询包
func createOpQueryPacket() []byte {
return []byte{
0x48, 0x00, 0x00, 0x00, // messageLength
0x02, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY
0x00, 0x00, 0x00, 0x00, // flags
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd
0x00, 0x00, 0x00, 0x00, // numberToSkip
0x01, 0x00, 0x00, 0x00, // numberToReturn
// query db.adminCommand({getLog: "startupWarnings"})
0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00,
}
}

View File

@ -1,344 +0,0 @@
package Plugins
import (
"context"
"database/sql"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/Common"
)
// MySQLProxyDialer 自定义dialer结构体
type MySQLProxyDialer struct {
timeout time.Duration
}
// Dial 实现mysql.Dialer接口支持socks代理
func (d *MySQLProxyDialer) Dial(ctx context.Context, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, "tcp", addr)
}
// registerMySQLDialer 注册MySQL自定义dialer
func registerMySQLDialer() {
// 创建自定义dialer
dialer := &MySQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
// 注册自定义dialer到go-sql-driver/mysql
mysql.RegisterDialContext("tcp-proxy", func(ctx context.Context, addr string) (net.Conn, error) {
return dialer.Dial(ctx, addr)
})
}
// MySQLCredential 表示一个MySQL凭据
type MySQLCredential struct {
Username string
Password string
}
// MySQLScanResult 表示MySQL扫描结果
type MySQLScanResult struct {
Success bool
Error error
Credential MySQLCredential
}
// MysqlScan 执行MySQL服务扫描
func MysqlScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []MySQLCredential
for _, user := range Common.Userdict["mysql"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, MySQLCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["mysql"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentMySQLScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveMySQLResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("MySQL扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentMySQLScan 并发扫描MySQL服务
func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials []MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *MySQLScanResult, 1)
workChan := make(chan MySQLCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryMySQLCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("MySQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryMySQLCredential 尝试单个MySQL凭据
func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &MySQLScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建独立的超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := MysqlConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &MySQLScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// Access denied 表示用户名或密码错误,无需重试
if strings.Contains(err.Error(), "Access denied") {
break
}
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &MySQLScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// MysqlConn 尝试MySQL连接
func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 检查是否需要使用socks代理
var connStr string
if Common.Socks5Proxy != "" {
// 注册自定义dialer
registerMySQLDialer()
// 使用自定义网络类型的连接字符串
connStr = fmt.Sprintf(
"%v:%v@tcp-proxy(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
} else {
// 标准连接字符串
connStr = fmt.Sprintf(
"%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
}
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中尝试连接
go func() {
// 建立数据库连接
db, err := sql.Open("mysql", connStr)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
// 添加上下文支持
conn, err := db.Conn(ctx)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
defer conn.Close()
// 测试连接
err = conn.PingContext(ctx)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 连接成功
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{true, nil}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveMySQLResult 保存MySQL扫描结果
func saveMySQLResult(info *Common.HostInfo, target string, credential MySQLCredential) {
successMsg := fmt.Sprintf("MySQL %s %v %v", target, credential.Username, credential.Password)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mysql",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
}

View File

@ -1,360 +0,0 @@
package Plugins
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/Common"
)
// Neo4jCredential 表示一个Neo4j凭据
type Neo4jCredential struct {
Username string
Password string
}
// Neo4jScanResult 表示Neo4j扫描结果
type Neo4jScanResult struct {
Success bool
Error error
Credential Neo4jCredential
IsUnauth bool
IsDefaultCreds bool
}
func Neo4jScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
defer cancel()
// 初始检查列表 - 无认证和默认凭证
initialCredentials := []Neo4jCredential{
{"", ""}, // 无认证
{"neo4j", "neo4j"}, // 默认凭证
}
// 先检查无认证和默认凭证
Common.LogDebug("尝试默认凭证...")
for _, credential := range initialCredentials {
Common.LogDebug(fmt.Sprintf("尝试: %s:%s", credential.Username, credential.Password))
result := tryNeo4jCredential(ctx, info, credential, Common.Timeout, 1)
if result.Success {
// 标记结果类型
if credential.Username == "" && credential.Password == "" {
result.IsUnauth = true
} else {
result.IsDefaultCreds = true
}
// 保存结果
saveNeo4jResult(info, target, result)
return nil
}
}
// 构建凭据列表
var credentials []Neo4jCredential
for _, user := range Common.Userdict["neo4j"] {
for _, pass := range Common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, Neo4jCredential{
Username: user,
Password: actualPass,
})
}
}
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["neo4j"]), len(Common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentNeo4jScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
if result != nil {
// 记录成功结果
saveNeo4jResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
Common.LogDebug("Neo4j扫描全局超时")
return fmt.Errorf("全局超时")
default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(initialCredentials)))
return nil
}
}
// concurrentNeo4jScan 并发扫描Neo4j服务
func concurrentNeo4jScan(ctx context.Context, info *Common.HostInfo, credentials []Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *Neo4jScanResult, 1)
workChan := make(chan Neo4jCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryNeo4jCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
Common.LogDebug("Neo4j并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryNeo4jCredential 尝试单个Neo4j凭据
func tryNeo4jCredential(ctx context.Context, info *Common.HostInfo, credential Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &Neo4jScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中尝试连接
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
go func() {
defer cancel()
success, err := Neo4jConn(info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
case resultChan <- struct {
success bool
err error
}{success, err}:
}
}()
// 等待结果或超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
return &Neo4jScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
if success {
return &Neo4jScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &Neo4jScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// Neo4jConn 尝试Neo4j连接
func Neo4jConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 构造Neo4j URL
uri := fmt.Sprintf("bolt://%s:%s", host, port)
// 配置驱动选项
config := func(c *neo4j.Config) {
c.SocketConnectTimeout = timeout
c.ConnectionAcquisitionTimeout = timeout
// 注意Neo4j驱动可能不支持代理配置
// 如果需要代理支持,可能需要使用更底层的连接方式
}
var driver neo4j.Driver
var err error
// 尝试建立连接
if user != "" || pass != "" {
// 有认证信息时使用认证
driver, err = neo4j.NewDriver(uri, neo4j.BasicAuth(user, pass, ""), config)
} else {
// 无认证时使用NoAuth
driver, err = neo4j.NewDriver(uri, neo4j.NoAuth(), config)
}
if err != nil {
return false, err
}
defer driver.Close()
// 测试连接有效性
err = driver.VerifyConnectivity()
if err != nil {
return false, err
}
// 尝试执行简单查询以确认权限
session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()
_, err = session.Run("MATCH (n) RETURN count(n) LIMIT 1", nil)
if err != nil {
return false, err
}
return true, nil
}
// saveNeo4jResult 保存Neo4j扫描结果
func saveNeo4jResult(info *Common.HostInfo, target string, result *Neo4jScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsUnauth {
// 无认证访问
successMsg = fmt.Sprintf("Neo4j服务 %s 无需认证即可访问", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "unauthorized-access",
}
} else if result.IsDefaultCreds {
// 默认凭证
successMsg = fmt.Sprintf("Neo4j服务 %s 默认凭证可用 用户名: %s 密码: %s",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "default-credentials",
"username": result.Credential.Username,
"password": result.Credential.Password,
}
} else {
// 弱密码
successMsg = fmt.Sprintf("Neo4j服务 %s 爆破成功 用户名: %s 密码: %s",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
}
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
}

Some files were not shown because too many files have changed in this diff Show More