Compare commits

..

29 Commits

Author SHA1 Message Date
ZacharyZcR
da981cdbce refactor: 正确组织平台特定插件注册
- 将avdetect从跨平台插件移至Windows特有插件
- 将dcinfo从跨平台插件移至Windows特有插件
- 优化Registry.go插件分类和注释
- 确保平台特定功能在正确的registry文件中注册

插件分布:
- Registry.go: 真正跨平台的服务和本地插件
- registry_windows.go: Windows特有的系统功能和持久化插件
- registry_linux.go: Linux特有的持久化插件
2025-08-12 18:16:07 +08:00
ZacharyZcR
75a056809c refactor: 移除无效的-noredis参数
- 删除DisableRedis字段定义和flag声明
- 移除相关i18n消息
- 更新PARAMETERS.md文档,删除-noredis参数说明
- 该参数已失效且在代码中无任何实际作用

现在如需禁用Redis扫描,应通过扫描模式或端口排除实现
2025-08-12 18:09:44 +08:00
ZacharyZcR
4473d1a28f refactor: 将-fingerprint参数缩短为-fp
- 优化命令行参数长度,提升用户体验
- 更新PARAMETERS.md文档中的参数说明
- 同时清理了已移除的-ne参数文档
2025-08-12 18:02:45 +08:00
ZacharyZcR
074aebfeec refactor: 简化插件利用架构,移除不必要的exploit控制参数
- 移除-ne (DisableExploit)参数及其相关控制逻辑
- 统一服务插件exploit实现为空函数,保持接口一致性
- MySQL/FTP/Cassandra/ActiveMQ插件exploit方法改为空实现
- Redis插件移除-ne控制,利用功能由专用参数控制(-rwp/-rf/-rs)
- VNC插件exploit功能改为空实现,专注于服务检测和弱密码扫描
- 更新插件描述,准确反映实际功能范围

所有exploit功能现在由明确的用户参数控制,无需通用禁用开关
2025-08-12 17:56:06 +08:00
ZacharyZcR
1cb1e8b2aa docs: 更新PARAMETERS.md以反映v2.2.1版本新增功能
- 更新版本信息至v2.2.1
- 添加host:port格式支持的说明和示例
- 新增SOCKS5代理参数到HTTP设置部分
- 增加新增功能改进章节,详细说明智能目标识别功能
- 更新使用示例,包含新的host:port格式用法
- 添加版本更新说明章节,记录v2.2.1的主要改进
2025-08-12 17:23:12 +08:00
ZacharyZcR
2703ddd9ed feat: 增强目标解析功能支持host:port格式
- 修复目标地址验证逻辑,支持host:port格式的直接输入
- 优化端口扫描流程,对明确指定的host:port跳过不必要的端口扫描
- 改进主机地址解析,智能识别和处理host:port格式
- 统一端口扫描结果显示,避免显示不一致问题
- 增强用户体验,支持 -h 127.0.0.1:135 等便捷格式
2025-08-12 17:20:59 +08:00
ZacharyZcR
cc4f55374a fix: 修复Web扫描功能无法正常工作的问题
- 修复URL正则表达式支持IP地址格式
- 修复URL解析后正确设置到HostInfo.Url字段
- 修复Web扫描目标Host和Port字段的提取
- 修复Web插件适用性检查逻辑,允许Web扫描策略直接执行Web插件
- 完善URL到目标信息的转换,包含协议默认端口处理
2025-08-12 17:03:38 +08:00
ZacharyZcR
0eb0a8f14a chore: 更新版本号到v2.2.1
- 版本从2.2.0更新到2.2.1
- 反映新增的内存监控功能
2025-08-12 16:31:31 +08:00
ZacharyZcR
245e3d0a12 feat: 在进度条中添加内存使用量监控
- 添加实时内存使用量显示(MB)
- 实现颜色编码:绿色(<50MB)、黄色(50-100MB)、红色(>100MB)
- 优化性能:限制内存统计更新频率为每秒一次
- 支持无颜色模式兼容性
- 在所有进度条状态下显示内存信息
2025-08-12 16:27:39 +08:00
ZacharyZcR
701306ee5f feat: 增强进度条活跃指示器,防止用户误判程序卡死
- 添加动态活跃指示器:活跃时显示●,长时间无更新时显示旋转动画
- 实现智能更新机制:2秒内有更新显示静态圆点,超时切换到旋转指示
- 独立goroutine处理活跃指示器,500ms更新间隔
- 优化渲染逻辑:避免频繁更新时重复渲染,提升性能
- 完善资源清理:正确停止ticker和关闭channel
- 改善用户体验:明确表明程序正在运行,消除卡死疑虑
2025-08-12 16:16:51 +08:00
ZacharyZcR
defe5b0733 refactor: 清理未使用的死代码函数
- 移除hostinfo_ext.go中7个未使用函数
- 移除target.go中7个未使用函数
- 清理多余空行,提升代码简洁性
- 保持核心功能完整,仅删除确认未被调用的函数
- 消除所有死代码编译警告
2025-08-12 16:12:47 +08:00
ZacharyZcR
792a075172 fix: 修复目标主机重复显示的问题
- 解决updateGlobalVariables中主机列表重复追加的逻辑错误
- 当info.Host已有值时不再重复设置解析结果
- 修复容器管理日志中显示"127.0.0.1,127.0.0.1"的问题
- 保持从文件读取主机时的正常功能
2025-08-12 16:09:06 +08:00
ZacharyZcR
c6bfb5f064 feat: 优化智能Web检测性能,避免重复调用
- 添加检测结果缓存机制,避免同一端口重复HTTP探测
- 使用线程安全的读写锁保护缓存访问
- 显著减少网络请求次数,提升扫描速度
- 支持多主机扫描的缓存键设计
- 保持架构兼容性的同时优化性能表现
2025-08-12 16:05:20 +08:00
ZacharyZcR
b706fb46bb refactor: 统一端口组定义并支持多种端口组扫描
- 新增多个预定义端口组常量到base/Constants.go
- 实现-p web/main/db/service/common/all参数支持
- 删除重复的DefaultPorts常量和database端口组
- 合并GetPortGroups和GetTargetPortGroups函数避免重复
- 统一所有端口组定义到统一位置便于维护
2025-08-12 15:56:09 +08:00
ZacharyZcR
a206e2c16e fix: 从默认端口列表中移除161端口
- 将SNMP端口161从MainPorts常量中移除避免默认扫描
- 保持用户可通过-p 161手动指定扫描SNMP服务
- 默认扫描端口数量从58个减少至57个
- 提升扫描效率减少不必要的SNMP端口探测
2025-08-12 14:55:00 +08:00
ZacharyZcR
b463984e78 refactor: 重构main.go入口文件并引入依赖注入架构
- 创建app包实现依赖注入容器和初始化器模式
- 重构main.go为六阶段清晰的初始化流程
- 新增结构化错误处理替代简陋的os.Exit调用
- 为HostInfo添加辅助函数增强功能但保持向后兼容
- 引入TargetInfo包装器支持上下文和元数据管理
- 优化代码组织提升可维护性和可测试性
2025-08-12 14:37:28 +08:00
ZacharyZcR
338dd60c3e feat: 实现智能Web服务检测系统
- 扩展Web端口识别范围至40+个常见端口
- 新增HTTP/HTTPS协议智能探测机制
- 实现并发协议检测提升性能
- 添加精确错误分析避免误判
- 完善插件过滤确保Web插件仅在检测到Web服务时执行
- 支持端口模式匹配和响应头分析
2025-08-12 14:20:57 +08:00
ZacharyZcR
0cc29afbeb fix: 修复服务扫描插件列表显示本地插件的问题
修复了服务扫描模式下插件列表错误包含本地插件的显示问题:

- 在ServiceScanner的插件过滤逻辑中正确排除本地插件
- 统一新插件架构和传统插件系统的过滤规则
- 保留Web插件的智能检测功能(当检测到Web端口时自动包含)

修复前显示包含:cleaner, keylogger, winschtask, minidump 等本地插件
修复后只显示:ssh, mysql, redis, vnc 等真正的服务插件和适用的Web插件

提升了用户体验的准确性和扫描日志的可读性。
2025-08-12 13:51:26 +08:00
ZacharyZcR
7e4e3dbd1b refactor: 清理死代码函数以提升代码质量
移除了36个静态分析工具识别的未使用函数,减少代码体积534行:

核心模块:
- core/PluginAdapter.go: 移除7个未使用的插件适配器方法
- core/Registry.go: 移除5个未使用的注册器方法
- core/Scanner.go: 移除1个未使用的扫描方法
- core/WebDetection.go: 移除4个未使用的Web检测方法

通用模块:
- common/ConcurrencyMonitor.go: 移除3个未使用的并发监控方法
- common/common.go: 移除1个未使用的插件注册方法
- common/base/Plugin.go: 移除4个未使用的插件管理方法
- common/parsers/Simple.go: 移除1个未使用的端口解析方法
- common/utils/memmonitor.go: 移除9个未使用的内存监控方法
- common/utils/slicepool.go: 移除1个未使用的切片去重方法

插件模块:
- plugins/services/vnc/plugin.go: 移除1个未使用的服务识别方法

所有移除的函数均替换为中文注释标记,保持代码可读性。
2025-08-12 13:22:12 +08:00
ZacharyZcR
4a3f281b6b refactor: 统一Plugins目录大小写为小写
- 将所有Plugins路径重命名为plugins
- 修复Git索引与实际文件系统大小写不一致问题
- 确保跨平台兼容性和路径一致性
2025-08-12 13:08:06 +08:00
ZacharyZcR
cddbb9e7e4 refactor: 统一Core目录大小写为小写 2025-08-12 13:04:14 +08:00
ZacharyZcR
3e4cd2466e feat: 实现完整的VNC插件支持
- 新增VNC远程桌面协议检测和利用插件
- 实现RFB协议连接器支持版本识别和认证
- 支持无认证访问检测和弱密码暴力破解
- 添加VNC服务风险评估和信息收集功能
- 支持标准VNC端口范围(5900-5909)
- 在插件注册系统中集成VNC服务扫描

功能特性: 服务识别、安全检测、利用验证、风险评估
2025-08-12 12:08:30 +08:00
ZacharyZcR
f097d2812a refactor: 清理项目死代码和未使用函数
- 移除所有未使用的generateCredentials方法
- 删除插件适配器中的过时函数
- 清理MySQL连接器中的无用方法
- 移除Redis利用器中的未调用函数
- 删除遗留加密函数和基础扫描器无用方法
- 完全移除未注册的VNC插件
- 优化代码结构,提升项目可维护性

清理统计: 移除25+个死代码函数,减少400+行无用代码
2025-08-12 11:51:36 +08:00
ZacharyZcR
b89bf4b0da refactor: 规范本地插件编译标签和注册架构
- 为所有平台特定插件添加正确的编译标签
  - Linux插件添加 //go:build linux 标签
  - Windows插件添加 //go:build windows 标签
- 重构本地插件注册方式
  - 从main.go移至core包统一管理
  - 创建平台特定的注册文件实现条件编译
- 修正参数文档中minidump平台支持描述
- 优化插件注册架构,提升代码组织性
2025-08-11 21:36:14 +08:00
ZacharyZcR
7ca20cbebe docs: 更新SOCKS5代理支持文档
- 明确说明代理支持范围:HTTP代理仅Web扫描,SOCKS5代理支持所有服务
- 添加代理失败自动回退的说明
- 更新使用示例,展示SOCKS5代理在不同场景的应用
- 重新组织代理相关参数的文档结构
2025-08-11 21:17:42 +08:00
ZacharyZcR
60eb65766a fix: 为WrapperTcpWithContext和WrapperTlsWithContext添加SOCKS5代理支持
- 修复核心TCP包装函数,检测并使用SOCKS5代理配置
- 添加TLS连接的代理支持
- 实现优雅降级:代理失败时自动回退到直连
- 添加详细的调试日志记录代理使用情况
- 修复影响PostgreSQL、Cassandra、Telnet、LDAP、IMAP等服务插件的代理功能

解决的问题:
- 之前只有WebScan和MySQL支持SOCKS5代理
- 现在所有使用WrapperTcpWithContext的服务都支持代理
- 保持与现有MySQL自定义实现的兼容性
2025-08-11 21:16:28 +08:00
ZacharyZcR
3b6f2083de feat: 优化SOCKS5参数命名以提升清晰度
- 将 -socks5-port 重命名为 -start-socks5,更明确表达启动服务器的意图
- 修正 -socks5 参数描述为使用SOCKS5代理(客户端模式)
- 添加新的i18n消息 flag_start_socks5_server 区分两种用途
- 更新文档说明两个参数的不同用途和使用示例

参数对比:
  旧方式: -socks5-port 1080 (容易混淆)
  新方式: -start-socks5 1080 (启动代理服务器)
  区别于: -socks5 127.0.0.1:1080 (使用外部代理)
2025-08-11 21:02:47 +08:00
ZacharyZcR
285358772f refactor: 简化本地插件参数设计
- 移除 -localplugin 参数,使用 -local 直接指定插件名称
- 简化用户体验:go run main.go -local cleaner
- 保持相同的验证逻辑和错误提示
- 更新文档和帮助信息
- 减少参数数量:从71个减少到70个

使用示例:
  新方式: go run main.go -local cleaner
  旧方式: go run main.go -local -localplugin cleaner (已移除)
2025-08-11 20:55:50 +08:00
ZacharyZcR
c85370a7d9 fix: 替换硬编码插件列表为动态注册表查询
- 在main.go中添加initLocalPlugins函数,从插件注册表动态获取本地插件列表
- 修改Common/Flag.go使用动态插件列表进行验证
- 消除硬编码插件名称,支持自动发现新插件
- 插件列表按字母顺序排序,保持一致性
- 修复循环导入问题
- 添加cleaner插件自毁脚本到.gitignore
- 创建完整的参数说明文档
2025-08-11 20:42:02 +08:00
181 changed files with 2027 additions and 1770 deletions

5
.gitignore vendored
View File

@ -70,3 +70,8 @@ Todo列表.md
# Claude documentation / Claude文档 # Claude documentation / Claude文档
.claude_docs/ .claude_docs/
# Cleaner plugin artifacts / 清理插件产物
cleanup.bat
cleanup.sh
cleanup_script_*

View File

@ -99,28 +99,7 @@ func (m *ConcurrencyMonitor) FinishConnection(pluginName, target string) {
} }
} }
// GetConnectionStats 获取所有插件连接统计 // 已移除未使用的 GetConnectionStats 方法
func (m *ConcurrencyMonitor) GetConnectionStats() map[string]*PluginConnectionInfo {
stats := make(map[string]*PluginConnectionInfo)
m.pluginConnections.Range(func(key, value interface{}) bool {
keyStr := key.(string)
info := value.(*PluginConnectionInfo)
// 只返回当前活跃的连接
if atomic.LoadInt64(&info.ActiveConnections) > 0 {
stats[keyStr] = &PluginConnectionInfo{
PluginName: info.PluginName,
Target: info.Target,
ActiveConnections: atomic.LoadInt64(&info.ActiveConnections),
TotalConnections: atomic.LoadInt64(&info.TotalConnections),
}
}
return true
})
return stats
}
// GetTotalActiveConnections 获取总活跃连接数 // GetTotalActiveConnections 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 { func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
@ -135,16 +114,7 @@ func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
return total return total
} }
// Reset 重置监控器 // 已移除未使用的 Reset 方法
func (m *ConcurrencyMonitor) Reset() {
atomic.StoreInt64(&m.activePluginTasks, 0)
atomic.StoreInt64(&m.totalPluginTasks, 0)
m.pluginConnections.Range(func(key, value interface{}) bool {
m.pluginConnections.Delete(key)
return true
})
}
// GetConcurrencyStatus 获取并发状态字符串 // GetConcurrencyStatus 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string { func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
@ -164,28 +134,4 @@ func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
i18n.GetText("concurrency_connection"), totalConnections) i18n.GetText("concurrency_connection"), totalConnections)
} }
// GetDetailedStatus 获取详细的并发状态 // 已移除未使用的 GetDetailedStatus 方法
func (m *ConcurrencyMonitor) GetDetailedStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
connectionStats := m.GetConnectionStats()
if activePlugins == 0 && len(connectionStats) == 0 {
return i18n.GetText("concurrency_no_active_tasks")
}
status := fmt.Sprintf("%s: %d", i18n.GetText("concurrency_plugin_tasks"), activePlugins)
if len(connectionStats) > 0 {
status += " | " + i18n.GetText("concurrency_connection_details") + ": "
first := true
for _, info := range connectionStats {
if !first {
status += ", "
}
status += fmt.Sprintf("%s@%s:%d", info.PluginName, info.Target, info.ActiveConnections)
first = false
}
}
return status
}

View File

@ -20,6 +20,9 @@ var (
HostsFile string HostsFile string
PortsFile string PortsFile string
// 本地插件列表(由外部初始化)
LocalPluginsList []string
ModuleThreadNum int ModuleThreadNum int
GlobalTimeout int64 GlobalTimeout int64
EnableFingerprint bool EnableFingerprint bool
@ -48,13 +51,11 @@ var (
RedisFile string RedisFile string
RedisShell string RedisShell string
DisableRedis bool
RedisWritePath string RedisWritePath string
RedisWriteContent string RedisWriteContent string
RedisWriteFile string RedisWriteFile string
DisableBrute bool DisableBrute bool
DisableExploit bool
MaxRetries int MaxRetries int
DisableSave bool DisableSave bool
@ -172,9 +173,8 @@ func Flag(Info *HostInfo) {
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout")) flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制 // LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping")) flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint")) flag.BoolVar(&EnableFingerprint, "fp", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode")) flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)")
flag.StringVar(&LocalPlugin, "localplugin", "", i18n.GetText("flag_local_plugin"))
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only")) flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
@ -216,7 +216,6 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file")) flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell")) flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, i18n.GetText("flag_disable_redis"))
flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path")) flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content")) flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file")) flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
@ -225,7 +224,6 @@ func Flag(Info *HostInfo) {
// 暴力破解控制参数 // 暴力破解控制参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute")) flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.BoolVar(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries")) flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
@ -244,7 +242,7 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode")) flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target")) flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy")) flag.IntVar(&Socks5ProxyPort, "start-socks5", 0, i18n.GetText("flag_start_socks5_server"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port")) flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file")) flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file"))
flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file")) flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file"))
@ -395,18 +393,14 @@ func checkParameterConflicts() {
LogBase(i18n.GetText("param_conflict_ao_icmp_both")) LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
} }
// 检查本地模式和本地插件参数 // 检查本地插件参数
if LocalMode { if LocalPlugin != "" {
if LocalPlugin == "" { // 自动启用本地模式
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n") LocalMode = true
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
os.Exit(1)
}
// 验证本地插件名称 // 验证本地插件名称
validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi", "keylogger", "downloader", "cleaner"} // 已重构的插件
isValid := false isValid := false
for _, valid := range validPlugins { for _, valid := range LocalPluginsList {
if LocalPlugin == valid { if LocalPlugin == valid {
isValid = true isValid = true
break break
@ -415,14 +409,10 @@ func checkParameterConflicts() {
if !isValid { if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n") if len(LocalPluginsList) > 0 {
fmt.Printf("可用的本地插件: %s\n", strings.Join(LocalPluginsList, ", "))
}
os.Exit(1) os.Exit(1)
} }
} }
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
} }

View File

@ -263,15 +263,19 @@ func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
// 更新目标相关全局变量 // 更新目标相关全局变量
if config.Targets != nil { if config.Targets != nil {
if len(config.Targets.Hosts) > 0 { if len(config.Targets.Hosts) > 0 {
// 如果info.Host已经有值说明解析结果来自info.Host不需要重复设置
// 只有当info.Host为空时才设置如从文件读取的情况
if info.Host == "" { if info.Host == "" {
info.Host = joinStrings(config.Targets.Hosts, ",") info.Host = joinStrings(config.Targets.Hosts, ",")
} else {
info.Host += "," + joinStrings(config.Targets.Hosts, ",")
} }
} }
if len(config.Targets.URLs) > 0 { if len(config.Targets.URLs) > 0 {
URLs = config.Targets.URLs URLs = config.Targets.URLs
// 如果info.Url为空且只有一个URL将其设置到info.Url
if info.Url == "" && len(config.Targets.URLs) == 1 {
info.Url = config.Targets.URLs[0]
}
} }
if len(config.Targets.Ports) > 0 { if len(config.Targets.Ports) > 0 {

View File

@ -8,8 +8,12 @@ Ports.go - 端口常量(向后兼容层)
此文件保持向后兼容实际常量定义已迁移到Core/Constants.go 此文件保持向后兼容实际常量定义已迁移到Core/Constants.go
*/ */
// 向后兼容的端口常量 - 引用Core包中的定义 // 向后兼容的端口常量 - 引用base包中的定义
var ( var (
WebPorts = base.WebPorts // Web服务端口组 WebPorts = base.WebPorts // Web服务端口组
MainPorts = base.MainPorts // 主要服务端口组 MainPorts = base.MainPorts // 主要服务端口组
DbPorts = base.DbPorts // 数据库端口组
ServicePorts = base.ServicePorts // 服务端口组
CommonPorts = base.CommonPorts // 常用端口组
AllPorts = base.AllPorts // 全部端口
) )

View File

@ -3,6 +3,7 @@ package common
import ( import (
"fmt" "fmt"
"os" "os"
"runtime"
"sync" "sync"
"time" "time"
@ -31,11 +32,27 @@ type ProgressManager struct {
// 输出缓冲相关 // 输出缓冲相关
outputMutex sync.Mutex outputMutex sync.Mutex
// 活跃指示器相关
spinnerIndex int
lastActivity time.Time
activityTicker *time.Ticker
stopActivityChan chan struct{}
// 内存监控相关
lastMemUpdate time.Time
memStats runtime.MemStats
} }
var ( var (
globalProgressManager *ProgressManager globalProgressManager *ProgressManager
progressMutex sync.Mutex progressMutex sync.Mutex
// 活跃指示器字符序列(旋转动画)
spinnerChars = []string{"|", "/", "-", "\\"}
// 活跃指示器更新间隔
activityUpdateInterval = 500 * time.Millisecond
) )
// GetProgressManager 获取全局进度条管理器 // GetProgressManager 获取全局进度条管理器
@ -69,10 +86,16 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
pm.startTime = time.Now() pm.startTime = time.Now()
pm.isActive = true pm.isActive = true
pm.enabled = true pm.enabled = true
pm.lastActivity = time.Now()
pm.spinnerIndex = 0
pm.lastMemUpdate = time.Now().Add(-2 * time.Second) // 强制首次更新内存
// 为进度条保留空间 // 为进度条保留空间
pm.setupProgressSpace() pm.setupProgressSpace()
// 启动活跃指示器
pm.startActivityIndicator()
// 初始显示进度条 // 初始显示进度条
pm.renderProgress() pm.renderProgress()
} }
@ -91,6 +114,9 @@ func (pm *ProgressManager) UpdateProgress(increment int64) {
pm.current = pm.total pm.current = pm.total
} }
// 更新活跃时间
pm.lastActivity = time.Now()
pm.renderProgress() pm.renderProgress()
} }
@ -110,6 +136,9 @@ func (pm *ProgressManager) FinishProgress() {
pm.current = pm.total pm.current = pm.total
pm.renderProgress() pm.renderProgress()
// 停止活跃指示器
pm.stopActivityIndicator()
// 显示完成信息 // 显示完成信息
pm.showCompletionInfo() pm.showCompletionInfo()
@ -140,7 +169,9 @@ func (pm *ProgressManager) renderProgress() {
// generateProgressBar 生成进度条字符串 // generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string { func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 { if pm.total == 0 {
return fmt.Sprintf("%s: 等待中...", pm.description) spinner := pm.getActivityIndicator()
memInfo := pm.getMemoryInfo()
return fmt.Sprintf("%s %s 等待中... %s", pm.description, spinner, memInfo)
} }
percentage := float64(pm.current) / float64(pm.total) * 100 percentage := float64(pm.current) / float64(pm.total) * 100
@ -197,16 +228,22 @@ func (pm *ProgressManager) generateProgressBar() string {
bar += "|" bar += "|"
} }
// 生成活跃指示器
spinner := pm.getActivityIndicator()
// 构建基础进度条 // 构建基础进度条
baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s", baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s",
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta) pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加内存信息
memInfo := pm.getMemoryInfo()
// 添加并发状态 // 添加并发状态
if concurrencyStatus != "" { if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s]", baseProgress, concurrencyStatus) return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo)
} }
return baseProgress return fmt.Sprintf("%s %s", baseProgress, memInfo)
} }
// showCompletionInfo 显示完成信息 // showCompletionInfo 显示完成信息
@ -326,4 +363,95 @@ func (pm *ProgressManager) renderProgressUnsafe() {
// 刷新输出 // 刷新输出
os.Stdout.Sync() os.Stdout.Sync()
}
// =============================================================================
// 活跃指示器相关方法
// =============================================================================
// startActivityIndicator 启动活跃指示器
func (pm *ProgressManager) startActivityIndicator() {
// 防止重复启动
if pm.activityTicker != nil {
return
}
pm.activityTicker = time.NewTicker(activityUpdateInterval)
pm.stopActivityChan = make(chan struct{})
go func() {
for {
select {
case <-pm.activityTicker.C:
// 只有在活跃状态下才更新指示器
if pm.isActive && pm.enabled {
pm.mu.Lock()
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
pm.mu.Unlock()
// 只有在长时间没有进度更新时才重新渲染
// 这样可以避免频繁更新时的性能问题
if time.Since(pm.lastActivity) > 2*time.Second {
pm.renderProgress()
}
}
case <-pm.stopActivityChan:
return
}
}
}()
}
// stopActivityIndicator 停止活跃指示器
func (pm *ProgressManager) stopActivityIndicator() {
if pm.activityTicker != nil {
pm.activityTicker.Stop()
pm.activityTicker = nil
}
if pm.stopActivityChan != nil {
close(pm.stopActivityChan)
pm.stopActivityChan = nil
}
}
// getActivityIndicator 获取当前活跃指示器字符
func (pm *ProgressManager) getActivityIndicator() string {
// 如果最近有活动2秒内显示静态指示器
if time.Since(pm.lastActivity) <= 2*time.Second {
return "●" // 实心圆表示活跃
}
// 如果长时间没有活动,显示旋转指示器表明程序仍在运行
return spinnerChars[pm.spinnerIndex]
}
// getMemoryInfo 获取内存使用信息
func (pm *ProgressManager) getMemoryInfo() string {
// 限制内存统计更新频率以提高性能(每秒最多一次)
now := time.Now()
if now.Sub(pm.lastMemUpdate) >= time.Second {
runtime.ReadMemStats(&pm.memStats)
pm.lastMemUpdate = now
}
// 获取当前使用的内存以MB为单位
memUsedMB := float64(pm.memStats.Alloc) / 1024 / 1024
// 根据内存使用量选择颜色
var colorCode string
if NoColor {
return fmt.Sprintf("内存:%.1fMB", memUsedMB)
}
// 根据内存使用量设置颜色
if memUsedMB < 50 {
colorCode = "\033[32m" // 绿色 - 内存使用较低
} else if memUsedMB < 100 {
colorCode = "\033[33m" // 黄色 - 内存使用中等
} else {
colorCode = "\033[31m" // 红色 - 内存使用较高
}
return fmt.Sprintf("%s内存:%.1fMB\033[0m", colorCode, memUsedMB)
} }

View File

@ -12,8 +12,12 @@ Constants.go - 核心常量定义
// 预定义端口组常量 // 预定义端口组常量
var ( 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" 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,25,80,81,110,135,139,143,161,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614" MainPorts = "21,22,23,25,80,81,110,135,139,143,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
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"
CommonPorts = "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986"
AllPorts = "1-65535"
) )
// ============================================================================= // =============================================================================

View File

@ -1,7 +1,6 @@
package base package base
import ( import (
"fmt"
"sync" "sync"
) )
@ -126,41 +125,7 @@ func (p *ScanPlugin) GetInfo() map[string]interface{} {
// 插件管理器方法 // 插件管理器方法
// ============================================================================= // =============================================================================
// RegisterPlugin 注册插件 // 已移除未使用的 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
}
// ======================================================================================== // ========================================================================================
// 未使用的插件管理器方法已删除(死代码清理) // 未使用的插件管理器方法已删除(死代码清理)
@ -172,89 +137,11 @@ func (pm *PluginManager) RegisterPlugin(plugin *ScanPlugin) error {
// 全局插件管理函数 (保持向后兼容) // 全局插件管理函数 (保持向后兼容)
// ============================================================================= // =============================================================================
// RegisterPlugin 注册插件到全局管理器 // 已移除未使用的 RegisterPlugin 方法
func RegisterPlugin(name string, plugin ScanPlugin) error {
// 转换为新的插件结构
newPlugin := &ScanPlugin{
Name: name,
Ports: plugin.Ports,
Types: plugin.Types,
Enabled: true,
ScanFunc: plugin.ScanFunc,
}
// 注册到新的插件管理器
err := globalPluginManager.RegisterPlugin(newPlugin)
if err != nil {
return err
}
// 同时更新Legacy管理器以保持向后兼容
LegacyPluginManager[name] = plugin
return nil
}
// Clear 清理所有插件(防止内存泄漏) // 已移除未使用的 Clear 方法
func (pm *PluginManager) Clear() {
pm.mu.Lock()
defer pm.mu.Unlock()
// 清理插件实例
for name, plugin := range pm.plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
_ = plugin // 避免未使用变量警告
delete(pm.plugins, name)
}
// 清理索引
for typeKey := range pm.types {
delete(pm.types, typeKey)
}
for portKey := range pm.ports {
delete(pm.ports, portKey)
}
// 清理Legacy管理器
for k := range LegacyPluginManager {
delete(LegacyPluginManager, k)
}
}
// ClearPluginsByType 清理指定类型的插件 // 已移除未使用的 ClearPluginsByType 方法
func (pm *PluginManager) ClearPluginsByType(pluginType string) {
pm.mu.Lock()
defer pm.mu.Unlock()
plugins := pm.types[pluginType]
for _, plugin := range plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
delete(pm.plugins, plugin.Name)
}
delete(pm.types, pluginType)
// 同步清理端口索引
for port, portPlugins := range pm.ports {
filtered := make([]*ScanPlugin, 0)
for _, plugin := range portPlugins {
found := false
for _, typePlugin := range plugins {
if plugin == typePlugin {
found = true
break
}
}
if !found {
filtered = append(filtered, plugin)
}
}
if len(filtered) == 0 {
delete(pm.ports, port)
} else {
pm.ports[port] = filtered
}
}
}
// GetGlobalPluginManager 方法已删除(死代码清理) // GetGlobalPluginManager 方法已删除(死代码清理)

View File

@ -18,6 +18,7 @@ import (
"github.com/shadow1ng/fscan/common/base" "github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging" "github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output" "github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/proxy"
) )
// ============================================================================= // =============================================================================
@ -29,12 +30,8 @@ type ScanPlugin = base.ScanPlugin
// 插件类型常量 // 插件类型常量
const ( const (
PluginTypeService = base.PluginTypeService PluginTypeWeb = base.PluginTypeWeb
PluginTypeWeb = base.PluginTypeWeb PluginTypeLocal = base.PluginTypeLocal
PluginTypeLocal = base.PluginTypeLocal
PluginTypeBrute = base.PluginTypeBrute
PluginTypePoc = base.PluginTypePoc
PluginTypeScan = base.PluginTypeScan
) )
// 全局插件管理器 // 全局插件管理器
@ -44,12 +41,7 @@ var PluginManager = base.LegacyPluginManager
// 核心功能导出 - 直接调用对应模块 // 核心功能导出 - 直接调用对应模块
// ============================================================================= // =============================================================================
// 插件系统 // 已移除未使用的 RegisterPlugin 方法
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := base.RegisterPlugin(name, plugin); err != nil {
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
// GetGlobalPluginManager 函数已删除(死代码清理) // GetGlobalPluginManager 函数已删除(死代码清理)
@ -63,22 +55,22 @@ var loggerMutex sync.Mutex
func getGlobalLogger() *logging.Logger { func getGlobalLogger() *logging.Logger {
loggerMutex.Lock() loggerMutex.Lock()
defer loggerMutex.Unlock() defer loggerMutex.Unlock()
if globalLogger == nil { if globalLogger == nil {
level := getLogLevelFromString(LogLevel) level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{ config := &logging.LoggerConfig{
Level: level, Level: level,
EnableColor: !NoColor, EnableColor: !NoColor,
SlowOutput: false, SlowOutput: false,
ShowProgress: ShowProgress, ShowProgress: ShowProgress,
StartTime: StartTime, StartTime: StartTime,
} }
globalLogger = logging.NewLogger(config) globalLogger = logging.NewLogger(config)
if ProgressBar != nil { if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar) globalLogger.SetProgressBar(ProgressBar)
} }
globalLogger.SetOutputMutex(&OutputMutex) globalLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress // 设置协调输出函数使用LogWithProgress
globalLogger.SetCoordinatedOutput(LogWithProgress) globalLogger.SetCoordinatedOutput(LogWithProgress)
} }
@ -109,18 +101,18 @@ func getLogLevelFromString(levelStr string) logging.LogLevel {
} }
// 日志函数 // 日志函数
func InitLogger() { func InitLogger() {
loggerMutex.Lock() loggerMutex.Lock()
globalLogger = nil globalLogger = nil
loggerMutex.Unlock() loggerMutex.Unlock()
getGlobalLogger().Initialize() getGlobalLogger().Initialize()
} }
func LogDebug(msg string) { getGlobalLogger().Debug(msg) } func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) } func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) } func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) } func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) } func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
// ============================================================================= // =============================================================================
// 输出系统简化接口 // 输出系统简化接口
@ -132,7 +124,7 @@ func InitOutput() error {
if Outputfile == "" { if Outputfile == "" {
return fmt.Errorf("output file not specified") return fmt.Errorf("output file not specified")
} }
var format output.OutputFormat var format output.OutputFormat
switch OutputFormat { switch OutputFormat {
case "txt": case "txt":
@ -177,14 +169,58 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.
return net.DialTimeout(network, address, timeout) return net.DialTimeout(network, address, timeout)
} }
// WrapperTcpWithContext TCP连接包装器带上下文 // WrapperTcpWithContext TCP连接包装器带上下文和代理支持
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) { func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
dialer, err := proxyManager.GetDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理连接失败回退到直连: %v", err))
// 代理失败时回退到直连
var d net.Dialer
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理连接: %s -> %s", Socks5Proxy, address))
return dialer.DialContext(ctx, network, address)
}
// 没有配置代理,使用直连
var d net.Dialer var d net.Dialer
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
} }
// WrapperTlsWithContext TLS连接包装器带上下文 // WrapperTlsWithContext TLS连接包装器带上下文和代理支持
func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) { func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
tlsDialer, err := proxyManager.GetTLSDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理TLS连接失败回退到直连: %v", err))
// 代理失败时回退到直连
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理TLS连接: %s -> %s", Socks5Proxy, address))
return tlsDialer.DialTLSContext(ctx, network, address, config)
}
// 没有配置代理,使用直连
d := &tls.Dialer{Config: config} d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
} }
@ -196,4 +232,4 @@ func WrapperTlsWithContext(ctx context.Context, network, address string, config
// CheckErrs 检查单个错误 - 简化版本 // CheckErrs 检查单个错误 - 简化版本
func CheckErrs(err error) error { func CheckErrs(err error) error {
return err return err
} }

View File

@ -86,7 +86,6 @@ type VulnExploitConfig struct {
// Redis利用 // Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件 RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令 RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
DisableRedis bool `json:"disable_redis"` // 是否禁用Redis利用测试
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径 RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容 RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容
RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件 RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件

View File

@ -20,7 +20,7 @@ globals.go - 全局变量定义
// 版本信息 // 版本信息
// ============================================================================= // =============================================================================
var version = "2.2.0" var version = "2.2.1"
// ============================================================================= // =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制) // 简化的全局状态管理(仅保留必要的同步机制)

58
Common/hostinfo_ext.go Normal file
View File

@ -0,0 +1,58 @@
package common
import (
"fmt"
"strconv"
)
// HostInfoHelper 提供HostInfo的辅助方法
// 使用函数而不是方法,保持向后兼容
// GetPort 获取端口号(转换为整数)
func GetPort(h *HostInfo) (int, error) {
if h.Ports == "" {
return 0, fmt.Errorf("端口未设置")
}
return strconv.Atoi(h.Ports)
}
// IsWebTarget 判断是否为Web目标
func IsWebTarget(h *HostInfo) bool {
return h.Url != ""
}
// HasPort 检查是否设置了端口
func HasPort(h *HostInfo) bool {
return h.Ports != ""
}
// ValidateHostInfo 验证HostInfo的有效性
func ValidateHostInfo(h *HostInfo) error {
if h.Host == "" && h.Url == "" {
return fmt.Errorf("主机地址或URL必须至少指定一个")
}
// 验证端口格式(如果指定了)
if h.Ports != "" {
if _, err := GetPort(h); err != nil {
return fmt.Errorf("端口格式无效: %v", err)
}
}
return nil
}
// HostInfoString 返回HostInfo的字符串表示
func HostInfoString(h *HostInfo) string {
if IsWebTarget(h) {
return h.Url
}
if HasPort(h) {
return fmt.Sprintf("%s:%s", h.Host, h.Ports)
}
return h.Host
}

View File

@ -170,10 +170,6 @@ var FlagMessages = map[string]map[string]string{
LangZH: "Redis Shell", LangZH: "Redis Shell",
LangEN: "Redis Shell", LangEN: "Redis Shell",
}, },
"flag_disable_redis": {
LangZH: "禁用Redis扫描",
LangEN: "Disable Redis scan",
},
"flag_redis_write_path": { "flag_redis_write_path": {
LangZH: "Redis写入路径", LangZH: "Redis写入路径",
LangEN: "Redis write path", LangEN: "Redis write path",
@ -190,10 +186,6 @@ var FlagMessages = map[string]map[string]string{
LangZH: "禁用暴力破解", LangZH: "禁用暴力破解",
LangEN: "Disable brute force", LangEN: "Disable brute force",
}, },
"flag_disable_exploit": {
LangZH: "禁用利用攻击",
LangEN: "Disable exploit attacks",
},
"flag_max_retries": { "flag_max_retries": {
LangZH: "最大重试次数", LangZH: "最大重试次数",
LangEN: "Maximum retries", LangEN: "Maximum retries",
@ -235,6 +227,10 @@ var FlagMessages = map[string]map[string]string{
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)", LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
}, },
"flag_socks5_proxy": { "flag_socks5_proxy": {
LangZH: "使用SOCKS5代理 (如: 127.0.0.1:1080)",
LangEN: "Use SOCKS5 proxy (e.g.: 127.0.0.1:1080)",
},
"flag_start_socks5_server": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)", LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)", LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
}, },

View File

@ -108,11 +108,7 @@ func ParsePort(ports string) []int {
return result return result
} }
// ParsePortsFromString 从字符串解析端口列表 // 已移除未使用的 ParsePortsFromString 方法
// 保持与 ParsePortsFromString 的接口兼容性
func ParsePortsFromString(portsStr string) []int {
return ParsePort(portsStr)
}
// ============================================================================= // =============================================================================
// 辅助函数 // 辅助函数

View File

@ -30,7 +30,6 @@ type TargetParserOptions struct {
ValidateURLs bool `json:"validate_urls"` ValidateURLs bool `json:"validate_urls"`
ResolveDomains bool `json:"resolve_domains"` ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"` EnableStatistics bool `json:"enable_statistics"`
DefaultPorts string `json:"default_ports"`
} }
// DefaultTargetParserOptions 默认目标解析器选项 // DefaultTargetParserOptions 默认目标解析器选项
@ -43,7 +42,6 @@ func DefaultTargetParserOptions() *TargetParserOptions {
ValidateURLs: DefaultValidateURLs, ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains, ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics, EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: DefaultPorts,
} }
} }
@ -199,17 +197,42 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
} }
} }
// 去重和验证 // 去重和验证同时分离host:port格式
hosts = tp.removeDuplicateStrings(hosts) hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts)) validHosts := make([]string, 0, len(hosts))
hostPorts := make([]string, 0)
for _, host := range hosts { for _, host := range hosts {
// 检查是否为host:port格式
if strings.Contains(host, ":") {
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 验证主机部分
if valid, hostErr := tp.validateHost(h); valid {
// 这是有效的host:port组合添加到hostPorts
hostPorts = append(hostPorts, host)
continue
} else if hostErr != nil {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", host, hostErr.Error()))
continue
}
}
}
}
// 作为普通主机验证
if valid, err := tp.validateHost(host); valid { if valid, err := tp.validateHost(host); valid {
validHosts = append(validHosts, host) validHosts = append(validHosts, host)
} else if err != nil { } else if err != nil {
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error())) warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
} }
} }
// 将找到的hostPorts合并到输入结果中通过修改input结构
if len(hostPorts) > 0 {
input.HostPort = append(input.HostPort, hostPorts...)
}
// 检查目标数量限制 // 检查目标数量限制
if len(validHosts) > tp.options.MaxTargets { if len(validHosts) > tp.options.MaxTargets {
@ -406,8 +429,26 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
} }
hosts = append(hosts, rangeHosts...) hosts = append(hosts, rangeHosts...)
default: default:
// 单个IP或域名 // 检查是否为host:port格式
hosts = append(hosts, item) if strings.Contains(item, ":") {
if _, portStr, err := net.SplitHostPort(item); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 这是有效的host:port格式但在这里仍然作为主机处理
// 在后续的processHostPorts函数中会被正确处理
hosts = append(hosts, item)
} else {
// 端口无效,作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 不是有效的host:port格式作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 单个IP或域名
hosts = append(hosts, item)
}
} }
} }
@ -697,6 +738,19 @@ func (tp *TargetParser) validateHost(host string) (bool, error) {
return false, fmt.Errorf("主机地址为空") return false, fmt.Errorf("主机地址为空")
} }
// 检查是否为host:port格式
if strings.Contains(host, ":") {
// 可能是host:port格式尝试分离
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 递归验证主机部分(不包含端口)
return tp.validateHost(h)
}
}
// 如果不是有效的host:port格式继续按普通主机地址处理
}
// 检查是否为IP地址 // 检查是否为IP地址
if ip := net.ParseIP(host); ip != nil { if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip) return tp.validateIP(ip)

View File

@ -3,6 +3,8 @@ package parsers
import ( import (
"regexp" "regexp"
"time" "time"
"github.com/shadow1ng/fscan/common/base"
) )
/* /*
@ -134,12 +136,11 @@ const (
DefaultValidateURLs = true DefaultValidateURLs = true
DefaultResolveDomains = false DefaultResolveDomains = false
DefaultTargetEnableStatistics = true 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}$` IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$`
PortRangeRegexPattern = `^(\d+)(-(\d+))?$` PortRangeRegexPattern = `^(\d+)(-(\d+))?$`
URLValidationRegexPattern = `^https?://[^\s/$.?#].[^\s]*$` URLValidationRegexPattern = `^https?://[^\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])?)*$` 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]*)?)*$` CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
@ -192,25 +193,21 @@ const (
SamplingMaxHost = 253 SamplingMaxHost = 253
) )
// GetPortGroups 获取预定义端口组映射 // GetPortGroups 获取预定义端口组映射(统一的端口组定义)
func GetPortGroups() map[string]string { func GetPortGroups() map[string]string {
return 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", "web": base.WebPorts, // 使用实际的WebPorts常量
"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", "main": base.MainPorts, // 使用实际的MainPorts常量
"database": "1433,1521,3306,5432,6379,11211,27017", "db": base.DbPorts, // 使用实际的DbPorts常量
"common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986", "service": base.ServicePorts, // 使用实际的ServicePorts常量
"common": base.CommonPorts, // 使用实际的CommonPorts常量
"all": base.AllPorts, // 使用实际的AllPorts常量
} }
} }
// GetTargetPortGroups 获取目标解析器端口组映射 // GetTargetPortGroups 获取目标解析器端口组映射(向后兼容,调用统一函数)
func GetTargetPortGroups() map[string]string { func GetTargetPortGroups() map[string]string {
return map[string]string{ return GetPortGroups()
"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",
}
} }
// ============================================================================= // =============================================================================

62
Common/target.go Normal file
View File

@ -0,0 +1,62 @@
package common
import (
"context"
)
// TargetInfo 包装HostInfo提供更丰富的功能
type TargetInfo struct {
*HostInfo // 嵌入HostInfo保持向后兼容
context context.Context
metadata map[string]interface{}
}
// NewTargetInfo 创建新的目标信息
func NewTargetInfo(hostInfo HostInfo) *TargetInfo {
return &TargetInfo{
HostInfo: &hostInfo,
context: context.Background(),
metadata: make(map[string]interface{}),
}
}
// WithContext 设置上下文
func (t *TargetInfo) WithContext(ctx context.Context) *TargetInfo {
t.context = ctx
return t
}
// SetMetadata 设置元数据
func (t *TargetInfo) SetMetadata(key string, value interface{}) *TargetInfo {
if t.metadata == nil {
t.metadata = make(map[string]interface{})
}
t.metadata[key] = value
return t
}
// GetMetadata 获取元数据
func (t *TargetInfo) GetMetadata(key string) (interface{}, bool) {
if t.metadata == nil {
return nil, false
}
value, exists := t.metadata[key]
return value, exists
}
// String 返回字符串表示
func (t *TargetInfo) String() string {
return HostInfoString(t.HostInfo)
}
// HasMetadata 检查是否有指定的元数据
func (t *TargetInfo) HasMetadata(key string) bool {
_, exists := t.GetMetadata(key)
return exists
}

View File

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

View File

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

View File

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

View File

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

262
PARAMETERS.md Normal file
View File

@ -0,0 +1,262 @@
# Fscan 参数说明文档
**版本**: v2.2.1
**分支**: v2.2.1
## 基础扫描参数
### 目标指定
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-h` | 目标主机: IP, IP段, IP段文件, 域名, **支持host:port格式** | - | `-h 192.168.1.1/24``-h 127.0.0.1:135` |
| `-hf` | 主机文件 | - | `-hf hosts.txt` |
| `-u` | 目标URL | - | `-u http://example.com` |
| `-uf` | URL文件 | - | `-uf urls.txt` |
| `-domain` | 域名 | - | `-domain example.com` |
### 端口扫描
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-p` | 端口扫描范围 | 1000个常用端口 | `-p 1-65535` |
| `-pf` | 端口文件 | - | `-pf ports.txt` |
| `-ep` | 排除端口 | - | `-ep 80,443` |
| `-t` | 端口扫描线程数 | 600 | `-t 1000` |
| `-time` | 端口扫描超时时间 | 3秒 | `-time 5` |
### 扫描模式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-m` | 扫描模式: all(全部), icmp(存活探测), 或指定插件名称 | all | `-m icmp` |
| `-ao` | 仅进行存活探测 | - | `-ao` |
| `-full` | 全量POC扫描 | - | `-full` |
| `-np` | 禁用ping探测 | - | `-np` |
## 认证参数
### 用户凭据
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-user` | 用户名 | - | `-user admin` |
| `-usera` | 额外用户名 | - | `-usera root,administrator` |
| `-userf` | 用户名字典文件 | - | `-userf users.txt` |
| `-pwd` | 密码 | - | `-pwd 123456` |
| `-pwda` | 额外密码 | - | `-pwda password,admin` |
| `-pwdf` | 密码字典文件 | - | `-pwdf pass.txt` |
| `-hash` | 哈希值 | - | `-hash ntlm_hash` |
| `-hashf` | 哈希文件 | - | `-hashf hashes.txt` |
| `-sshkey` | SSH私钥文件 | - | `-sshkey id_rsa` |
## 网络配置
### HTTP设置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-cookie` | HTTP Cookie | - | `-cookie "session=abc123"` |
| `-proxy` | HTTP代理 | - | `-proxy http://127.0.0.1:8080` |
| `-socks5` | SOCKS5代理支持所有服务扫描 | - | `-socks5 127.0.0.1:1080` |
| `-start-socks5` | 启动SOCKS5代理服务器端口 | - | `-start-socks5 1080` |
| `-wt` | Web超时时间 | 5秒 | `-wt 10` |
## POC扫描
### POC配置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-pocname` | POC名称 | - | `-pocname ms17-010` |
| `-pocpath` | POC脚本路径 | - | `-pocpath ./pocs/` |
| `-num` | POC并发数 | 20 | `-num 50` |
| `-nopoc` | 禁用POC扫描 | - | `-nopoc` |
## 高级功能
### Redis操作
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-rf` | Redis文件 | - | `-rf redis.txt` |
| `-rs` | Redis Shell | - | `-rs "echo test"` |
| `-rwf` | Redis写入文件 | - | `-rwf /tmp/test.txt` |
| `-rwp` | Redis写入路径 | - | `-rwp /var/www/html/` |
| `-rwc` | Redis写入内容 | - | `-rwc "<?php phpinfo(); ?>"` |
### Shell功能
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-rsh` | 反弹Shell目标地址:端口 | - | `-rsh 192.168.1.100:4444` |
| `-fsh-port` | 启动正向Shell服务器端口 | 4444 | `-fsh-port 8888` |
| `-sc` | Shellcode | - | `-sc payload` |
### 新增功能改进
- **智能目标识别**: `-h`参数现在支持直接指定`host:port`格式,如`-h 192.168.1.1:3306`
- **优化扫描逻辑**: 当使用`host:port`格式时,自动跳过不必要的端口扫描,直接进行服务检测
- **SOCKS5全面支持**: SOCKS5代理现已支持所有服务扫描类型包括数据库和SSH等服务
**代理支持范围:**
- **HTTP代理** (`-proxy`): 仅支持Web扫描
- **SOCKS5代理** (`-socks5`): 支持所有服务扫描MySQL、PostgreSQL、Redis、SSH等和Web扫描
- **代理失败处理**: 当代理连接失败时,自动回退到直连模式
## 本地插件系统
### 本地模式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-local` | 指定本地插件名称 | - | `-local cleaner` |
### 可用本地插件
| 插件名 | 功能说明 | 平台支持 |
|--------|----------|----------|
| `avdetect` | 杀毒软件检测 | Windows/Linux/macOS |
| `fileinfo` | 文件信息收集 | Windows/Linux/macOS |
| `dcinfo` | 域控信息收集 | Windows/Linux/macOS |
| `minidump` | 内存转储 | Windows |
| `reverseshell` | 反弹Shell | Windows/Linux/macOS |
| `socks5proxy` | SOCKS5代理 | Windows/Linux/macOS |
| `forwardshell` | 正向Shell | Windows/Linux/macOS |
| `ldpreload` | Linux LD_PRELOAD持久化 | Linux |
| `shellenv` | Shell环境变量持久化 | Linux |
| `crontask` | Cron计划任务持久化 | Linux |
| `systemdservice` | Systemd服务持久化 | Linux |
| `winregistry` | Windows注册表持久化 | Windows |
| `winstartup` | Windows启动文件夹持久化 | Windows |
| `winschtask` | Windows计划任务持久化 | Windows |
| `winservice` | Windows服务持久化 | Windows |
| `winwmi` | Windows WMI事件订阅持久化 | Windows |
| `keylogger` | 跨平台键盘记录 | Windows/Linux/macOS |
| `downloader` | 跨平台文件下载 | Windows/Linux/macOS |
| `cleaner` | 跨平台系统痕迹清理 | Windows/Linux/macOS |
### 插件特定参数
| 参数 | 说明 | 适用插件 | 示例 |
|------|------|----------|------|
| `-keylog-output` | 键盘记录输出文件路径 | keylogger | `-keylog-output keylog.txt` |
| `-download-url` | 要下载的文件URL | downloader | `-download-url http://example.com/file.exe` |
| `-download-path` | 下载文件保存路径 | downloader | `-download-path /tmp/` |
| `-persistence-file` | Linux持久化目标文件路径 | ldpreload, shellenv 等 | `-persistence-file target.elf` |
| `-win-pe` | Windows持久化目标PE文件路径 | Windows持久化插件 | `-win-pe target.exe` |
## 输出控制
### 输出格式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-o` | 输出文件 | result.txt | `-o scan_result.txt` |
| `-f` | 输出格式: txt, json, csv | txt | `-f json` |
### 显示控制
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-silent` | 静默模式 | - | `-silent` |
| `-nocolor` | 禁用颜色输出 | - | `-nocolor` |
| `-nopg` | 禁用进度条 | - | `-nopg` |
| `-no` | 禁用结果保存 | - | `-no` |
## 系统配置
### 线程与超时
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-mt` | 模块线程数 | 50 | `-mt 100` |
| `-gt` | 全局超时时间 | 180秒 | `-gt 300` |
| `-retry` | 最大重试次数 | 3 | `-retry 5` |
### 功能开关
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-nobr` | 禁用暴力破解 | - | `-nobr` |
| `-fp` | 启用指纹识别 | - | `-fp` |
| `-dns` | DNS日志记录 | - | `-dns` |
### 排除设置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-eh` | 排除主机 | - | `-eh 192.168.1.1,192.168.1.2` |
### 语言与帮助
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-lang` | 语言: zh, en | zh | `-lang en` |
| `-log` | 日志级别 | BASE_INFO_SUCCESS | `-log DEBUG` |
| `-help` | 显示帮助信息 | - | `-help` |
## 使用示例
### 基础扫描
```bash
# 扫描单个IP
go run main.go -h 192.168.1.1
# 扫描IP段
go run main.go -h 192.168.1.1/24
# 直接扫描指定主机和端口 (新功能)
go run main.go -h 127.0.0.1:135
# 从文件读取目标
go run main.go -hf targets.txt
# 仅存活探测
go run main.go -h 192.168.1.1/24 -ao
```
### 本地插件使用
```bash
# 系统痕迹清理
go run main.go -local cleaner
# 杀毒软件检测
go run main.go -local avdetect
# 键盘记录
go run main.go -local keylogger -keylog-output my_keylog.txt
# 文件下载
go run main.go -local downloader -download-url http://example.com/file.exe -download-path /tmp/
# Windows注册表持久化
go run main.go -local winregistry -win-pe target.exe
```
### 高级扫描
```bash
# 全量POC扫描
go run main.go -h 192.168.1.1/24 -full
# 使用HTTP代理
go run main.go -h 192.168.1.1 -proxy http://127.0.0.1:8080
# 使用SOCKS5代理进行服务扫描
go run main.go -h 192.168.1.1 -socks5 127.0.0.1:1080
# 直接扫描MySQL服务 (新功能 - 优化扫描流程)
go run main.go -h 192.168.1.1:3306 -socks5 127.0.0.1:1080
# 启动SOCKS5代理服务器
go run main.go -start-socks5 1080
# Web应用扫描
go run main.go -u http://127.0.0.1:8080
# 暴力破解
go run main.go -h 192.168.1.1 -user admin -pwd password
# 输出JSON格式
go run main.go -h 192.168.1.1 -f json -o result.json
```
---
## 版本更新说明 (v2.2.1)
### 新增功能
- **智能目标解析**: `-h`参数现在支持`host:port`格式,如`-h 127.0.0.1:135`
- **优化扫描流程**: 对于明确指定的host:port目标跳过常规端口扫描直接进行服务检测
- **统一结果显示**: 修复了端口扫描结果显示不一致的问题
### 改进项目
- 提升用户体验,支持更灵活的目标指定方式
- 优化扫描性能,减少不必要的网络请求
- 增强目标地址验证逻辑,支持更多输入格式
**总计参数数量**: 70个命令行参数 + 19个本地插件
**更新时间**: 2025-08-12
**版本**: v2.2.1

View File

@ -1,165 +0,0 @@
package adapters
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/plugins/base"
"time"
// 导入新插件以触发注册
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
)
// PluginAdapter 插件适配器,将新插件架构与旧系统集成
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// AdaptPluginScan 适配插件扫描调用
// 将传统的插件扫描函数调用转换为新架构的插件调用
func (a *PluginAdapter) AdaptPluginScan(pluginName string, info *common.HostInfo) error {
// 创建插件实例
plugin, err := a.registry.Create(pluginName)
if err != nil {
// 如果新架构中没有该插件,返回错误让旧系统处理
return fmt.Errorf("plugin %s not found in new architecture: %v", pluginName, err)
}
// 初始化插件
if err := plugin.Initialize(); err != nil {
return fmt.Errorf("plugin %s initialization failed: %v", pluginName, err)
}
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 执行扫描
result, err := plugin.Scan(ctx, info)
if err != nil {
common.LogError(fmt.Sprintf("Plugin %s scan failed: %v", pluginName, err))
return err
}
// 如果扫描成功,记录结果
if result != nil && result.Success {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 适配器层不输出扫描结果,由插件层负责输出
// 这避免了重复输出的问题
common.LogDebug(fmt.Sprintf("插件 %s 适配成功: %s", pluginName, target))
// 保存结果到文件
a.saveResult(info, result, pluginName)
// 如果有漏洞信息,也记录下来
for _, vuln := range result.Vulnerabilities {
common.LogError(fmt.Sprintf("%s vulnerability found: %s - %s",
pluginName, vuln.ID, vuln.Description))
}
}
return nil
}
// saveResult 保存扫描结果
func (a *PluginAdapter) saveResult(info *common.HostInfo, result *base.ScanResult, pluginName string) {
// 使用原有的结果保存机制
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"plugin": pluginName,
"port": info.Ports,
"host": info.Host,
},
}
if len(result.Credentials) > 0 {
cred := result.Credentials[0]
if cred.Username != "" {
vulnResult.Details["username"] = cred.Username
vulnResult.Details["password"] = cred.Password
vulnResult.Details["credentials"] = fmt.Sprintf("%s:%s", cred.Username, cred.Password)
} else {
vulnResult.Details["password"] = cred.Password
vulnResult.Details["credentials"] = cred.Password
}
} else {
// 未授权访问
vulnResult.Details["type"] = "unauthorized"
}
// 保存结果
common.SaveResult(vulnResult)
}
// IsPluginSupported 检查插件是否在新架构中支持
func (a *PluginAdapter) IsPluginSupported(pluginName string) bool {
plugins := a.registry.GetAll()
for _, name := range plugins {
if name == pluginName {
return true
}
}
return false
}
// GetSupportedPlugins 获取新架构支持的插件列表
func (a *PluginAdapter) GetSupportedPlugins() []string {
return a.registry.GetAll()
}
// GetPluginMetadata 获取插件元数据
func (a *PluginAdapter) GetPluginMetadata(pluginName string) (*base.PluginMetadata, error) {
metadata := a.registry.GetMetadata(pluginName)
if metadata == nil {
return nil, fmt.Errorf("plugin %s not found", pluginName)
}
return metadata, nil
}
// =============================================================================
// 全局适配器实例
// =============================================================================
// GlobalAdapter 全局插件适配器实例
var GlobalAdapter = NewPluginAdapter()
// =============================================================================
// 便捷函数
// =============================================================================
// TryNewArchitecture 尝试使用新架构执行插件扫描
// 如果新架构支持该插件则使用新架构否则返回false让调用方使用旧插件
func TryNewArchitecture(pluginName string, info *common.HostInfo) bool {
if !GlobalAdapter.IsPluginSupported(pluginName) {
common.LogDebug(i18n.GetText("plugin_legacy_using", pluginName))
return false
}
common.LogDebug(i18n.GetText("plugin_new_arch_trying", pluginName))
err := GlobalAdapter.AdaptPluginScan(pluginName, info)
if err != nil {
common.LogError(i18n.GetText("plugin_new_arch_fallback", pluginName, err))
return false
}
common.LogDebug(i18n.GetText("plugin_new_arch_success", pluginName))
return true
}

View File

@ -1,111 +0,0 @@
package vnc
import (
"context"
"fmt"
"net"
"time"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCConnector VNC服务连接器
type VNCConnector struct{}
// NewVNCConnector 创建新的VNC连接器
func NewVNCConnector() *VNCConnector {
return &VNCConnector{}
}
// Connect 连接到VNC服务
func (c *VNCConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 使用带上下文的TCP连接
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("vnc_connection_failed"), err)
}
// 设置读写超时
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to set connection deadline: %v", err)
}
return conn, nil
}
// Authenticate 认证VNC服务
func (c *VNCConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
netConn, ok := conn.(net.Conn)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 检查上下文是否已取消
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// VNC只使用密码认证忽略用户名
password := cred.Password
if password == "" && cred.Username != "" {
// 如果密码为空但用户名不为空,尝试使用用户名作为密码
password = cred.Username
}
// 创建完成通道
doneChan := make(chan error, 1)
// 在协程中处理VNC认证
go func() {
// 配置VNC客户端
config := &vnc.ClientConfig{
Auth: []vnc.ClientAuth{
&vnc.PasswordAuth{
Password: password,
},
},
}
// 尝试VNC认证
client, err := vnc.Client(netConn, config)
if err != nil {
select {
case <-ctx.Done():
case doneChan <- err:
}
return
}
// 认证成功,立即关闭客户端
client.Close()
select {
case <-ctx.Done():
case doneChan <- nil:
}
}()
// 等待认证结果或上下文取消
select {
case err := <-doneChan:
return err
case <-ctx.Done():
return ctx.Err()
}
}
// Close 关闭连接
func (c *VNCConnector) Close(conn interface{}) error {
if closer, ok := conn.(interface{ Close() error }); ok && closer != nil {
return closer.Close()
}
return nil
}

View File

@ -1,25 +0,0 @@
package vnc
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCExploiter VNC服务利用器
// 遵循新架构设计模式,当前为空实现
type VNCExploiter struct{}
// NewVNCExploiter 创建新的VNC利用器
func NewVNCExploiter() *VNCExploiter {
return &VNCExploiter{}
}
// Exploit 执行VNC服务利用
// 当前为空实现,遵循其他插件的一致性设计
func (e *VNCExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 空实现 - 遵循新架构中其他服务插件的模式
// 主要功能集中在连接器和插件主体中实现
return nil, nil
}

View File

@ -1,187 +0,0 @@
package vnc
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCPlugin VNC服务插件
type VNCPlugin struct {
*base.ServicePlugin
exploiter *VNCExploiter
}
// NewVNCPlugin 创建VNC插件
func NewVNCPlugin() *VNCPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "vnc",
Version: "2.0.0",
Author: "fscan-team",
Description: "VNC远程桌面协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{5900, 5901, 5902, 5903}, // VNC常用端口
Protocols: []string{"tcp"},
Tags: []string{"vnc", "remote-desktop", "weak-password"},
}
// 创建连接器和服务插件
connector := NewVNCConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建VNC插件
plugin := &VNCPlugin{
ServicePlugin: servicePlugin,
exploiter: NewVNCExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
})
return plugin
}
// init 自动注册VNC插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "vnc",
Version: "2.0.0",
Author: "fscan-team",
Description: "VNC远程桌面协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{5900, 5901, 5902, 5903},
Protocols: []string{"tcp"},
Tags: []string{"vnc", "remote-desktop", "weak-password"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewVNCPlugin()
})
base.GlobalPluginRegistry.Register("vnc", factory)
}
// Scan 重写扫描方法进行VNC服务扫描
func (p *VNCPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用了暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 生成凭据进行暴力破解
credentials := p.generateCredentials()
if len(credentials) == 0 {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("no credentials available"),
}, nil
}
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("vnc_weak_password_success", target, cred.Password))
return &base.ScanResult{
Success: true,
Service: "VNC",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "VNC",
"port": info.Ports,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 没有找到有效凭据
return &base.ScanResult{
Success: false,
Service: "VNC",
Error: fmt.Errorf("authentication failed for all credentials"),
}, nil
}
// performServiceIdentification 执行服务识别
func (p *VNCPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 尝试连接到服务进行基本识别
conn, err := p.GetServiceConnector().Connect(ctx, info)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer p.GetServiceConnector().Close(conn)
// 服务识别成功
return &base.ScanResult{
Success: true,
Service: "VNC",
Banner: "VNC service detected",
Extra: map[string]interface{}{
"service": "VNC",
"port": info.Ports,
"type": "service-identification",
},
}, nil
}
// generateCredentials 生成VNC认证凭据
func (p *VNCPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// VNC只使用密码认证不需要用户名
passwords := common.Passwords
if len(passwords) == 0 {
// 使用默认VNC密码
passwords = []string{"", "123456", "password", "vnc", "admin", "root", "888888", "123123"}
}
// 生成密码凭据VNC不使用用户名
for _, password := range passwords {
credentials = append(credentials, &base.Credential{
Username: "", // VNC不需要用户名
Password: password,
})
}
// 额外尝试常见的VNC密码组合
commonVNCPasswords := []string{
"vnc", "password", "123456", "admin", "root", "guest",
"1234", "12345", "qwerty", "abc123", "888888", "000000",
}
for _, password := range commonVNCPasswords {
// 避免重复添加
found := false
for _, existing := range credentials {
if existing.Password == password {
found = true
break
}
}
if !found {
credentials = append(credentials, &base.Credential{
Username: "",
Password: password,
})
}
}
return credentials
}

107
app/container.go Normal file
View File

@ -0,0 +1,107 @@
package app
import (
"context"
"fmt"
"sync"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core"
)
// Container 依赖注入容器
type Container struct {
services map[string]interface{}
initializers []Initializer
mu sync.RWMutex
initialized bool
}
// NewContainer 创建新的容器
func NewContainer() *Container {
container := &Container{
services: make(map[string]interface{}),
}
// 注册默认初始化器
container.AddInitializer(&PluginInitializer{})
return container
}
// AddInitializer 添加初始化器
func (c *Container) AddInitializer(init Initializer) {
c.initializers = append(c.initializers, init)
}
// Register 注册服务
func (c *Container) Register(name string, service interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.services[name] = service
}
// Get 获取服务
func (c *Container) Get(name string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
service, exists := c.services[name]
return service, exists
}
// Initialize 初始化容器和所有服务
func (c *Container) Initialize() error {
if c.initialized {
return nil
}
// 执行所有初始化器
for _, initializer := range c.initializers {
if err := initializer.Initialize(); err != nil {
return WrapError(ErrInitFailed, err)
}
}
c.initialized = true
return nil
}
// RunScan 执行扫描包装现有的core.RunScan
func (c *Container) RunScan(ctx context.Context, info common.HostInfo) error {
// 使用新的验证函数
if err := common.ValidateHostInfo(&info); err != nil {
return WrapError(ErrScanFailed, err)
}
// 创建目标信息(展示新功能,但保持兼容)
target := common.NewTargetInfo(info)
target.WithContext(ctx)
target.SetMetadata("container_managed", true)
target.SetMetadata("validation_passed", true)
// 记录扫描信息
c.logScanInfo(target)
// 调用现有的扫描逻辑
core.RunScan(info)
return nil
}
// logScanInfo 记录扫描信息
func (c *Container) logScanInfo(target *common.TargetInfo) {
targetStr := target.String()
if targetStr != "" {
common.LogDebug(fmt.Sprintf("容器管理的扫描目标: %s", targetStr))
}
if target.HasMetadata("validation_passed") {
common.LogDebug("目标验证通过")
}
}
// Cleanup 清理资源
func (c *Container) Cleanup() {
// 清理输出资源
common.CloseOutput()
}

43
app/errors.go Normal file
View File

@ -0,0 +1,43 @@
package app
import "fmt"
// AppError 应用程序错误类型
type AppError struct {
Code int
Message string
Cause error
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Cause)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
// 预定义错误类型
var (
ErrInitFailed = &AppError{Code: 1, Message: "初始化失败"}
ErrParseFailed = &AppError{Code: 2, Message: "参数解析失败"}
ErrOutputFailed = &AppError{Code: 3, Message: "输出初始化失败"}
ErrScanFailed = &AppError{Code: 4, Message: "扫描执行失败"}
)
// NewAppError 创建新的应用程序错误
func NewAppError(code int, message string, cause error) *AppError {
return &AppError{
Code: code,
Message: message,
Cause: cause,
}
}
// WrapError 包装错误为应用程序错误
func WrapError(baseErr *AppError, cause error) *AppError {
return &AppError{
Code: baseErr.Code,
Message: baseErr.Message,
Cause: cause,
}
}

66
app/initializer.go Normal file
View File

@ -0,0 +1,66 @@
package app
import (
"sort"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// Initializer 初始化器接口
type Initializer interface {
Initialize() error
Name() string
}
// PluginInitializer 插件初始化器
type PluginInitializer struct{}
func (p *PluginInitializer) Name() string {
return "PluginInitializer"
}
func (p *PluginInitializer) Initialize() error {
var localPlugins []string
// 获取所有注册的插件
allPlugins := base.GlobalPluginRegistry.GetAll()
for _, pluginName := range allPlugins {
metadata := base.GlobalPluginRegistry.GetMetadata(pluginName)
if metadata != nil && metadata.Category == "local" {
localPlugins = append(localPlugins, pluginName)
}
}
// 排序以保持一致性
sort.Strings(localPlugins)
// 设置全局变量
common.LocalPluginsList = localPlugins
return nil
}
// LoggerInitializer 日志初始化器
type LoggerInitializer struct{}
func (l *LoggerInitializer) Name() string {
return "LoggerInitializer"
}
func (l *LoggerInitializer) Initialize() error {
common.InitLogger()
return nil
}
// OutputInitializer 输出初始化器
type OutputInitializer struct{}
func (o *OutputInitializer) Name() string {
return "OutputInitializer"
}
func (o *OutputInitializer) Initialize() error {
return common.InitOutput()
}

View File

@ -108,7 +108,8 @@ func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
case FilterLocal: case FilterLocal:
return metadata.Category == "local" return metadata.Category == "local"
case FilterService: case FilterService:
return metadata.Category == "service" // 服务扫描允许service类型以及通过智能检测的web类型在上层逻辑中处理
return metadata.Category == "service" || metadata.Category == "web"
case FilterWeb: case FilterWeb:
return metadata.Category == "web" return metadata.Category == "web"
default: default:
@ -122,6 +123,7 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
case FilterLocal: case FilterLocal:
return plugin.HasType(common.PluginTypeLocal) return plugin.HasType(common.PluginTypeLocal)
case FilterService: case FilterService:
// 服务扫描排除本地插件允许服务和Web插件
return !plugin.HasType(common.PluginTypeLocal) return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb: case FilterWeb:
return plugin.HasType(common.PluginTypeWeb) return plugin.HasType(common.PluginTypeWeb)
@ -131,7 +133,7 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
} }
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法) // IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool { func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true
@ -143,7 +145,7 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
} }
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件 // 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetPort) { if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) {
return true return true
} }
@ -162,11 +164,21 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
return false return false
} }
// 对于Web插件的特殊处理
if metadata.Category == "web" {
// Web扫描策略下直接允许Web插件执行用户明确指定了Web目标
if b.filterType == FilterWeb {
return true
}
// 其他策略下必须通过智能检测才能执行
return false
}
return true return true
} }
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测 // shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool { func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetHost string, targetPort int) bool {
// 只对服务扫描策略启用Web插件智能检测 // 只对服务扫描策略启用Web插件智能检测
if b.filterType != FilterService { if b.filterType != FilterService {
return false return false
@ -187,14 +199,14 @@ func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata,
globalWebDetector = NewWebPortDetector() globalWebDetector = NewWebPortDetector()
} }
// 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测) // 检测是否为Web服务使用完整的智能检测包括HTTP协议探测)
return globalWebDetector.IsCommonWebPort(targetPort) return globalWebDetector.IsWebService(targetHost, targetPort)
} }
// globalWebDetector Web检测器全局实例 // globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现 // IsPluginApplicable 判断插件是否适用(通用实现,传统插件系统
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
@ -206,6 +218,21 @@ func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPo
return false return false
} }
// 对于服务扫描中的Web插件需要进行智能检测传统插件系统
if b.filterType == FilterService && plugin.HasType(common.PluginTypeWeb) {
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 注意传统插件系统无法传递host参数使用空字符串
// 这是传统插件系统的限制,新插件系统已经解决了这个问题
if targetPort > 0 {
return globalWebDetector.IsWebService("", targetPort)
}
return false
}
// 对于服务扫描,还需检查端口匹配 // 对于服务扫描,还需检查端口匹配
if b.filterType == FilterService { if b.filterType == FilterService {
// 无端口限制的插件适用于所有端口 // 无端口限制的插件适用于所有端口

78
core/PluginAdapter.go Normal file
View File

@ -0,0 +1,78 @@
package core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PluginAdapter 插件适配器
// 提供从新插件系统到旧扫描接口的适配
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// 全局插件适配器实例
var GlobalPluginAdapter = NewPluginAdapter()
// GetAllPluginNames 获取所有插件名称
func (pa *PluginAdapter) GetAllPluginNames() []string {
return pa.registry.GetAll()
}
// PluginExists 检查插件是否存在
func (pa *PluginAdapter) PluginExists(name string) bool {
metadata := pa.registry.GetMetadata(name)
return metadata != nil
}
// 已移除未使用的 GetPluginPorts 方法
// 已移除未使用的 GetPluginsByPort 方法
// 已移除未使用的 GetPluginsByType 方法
// ScanWithPlugin 使用插件进行扫描
func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo) error {
common.LogDebug(fmt.Sprintf("使用新插件架构扫描: %s", pluginName))
// 创建插件实例
plugin, err := pa.registry.Create(pluginName)
if err != nil {
return fmt.Errorf("创建插件 %s 失败: %v", pluginName, err)
}
// 执行扫描
result, err := plugin.Scan(context.Background(), info)
if err != nil {
return fmt.Errorf("插件 %s 扫描失败: %v", pluginName, err)
}
// 处理扫描结果
if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
}
return nil
}
// 已移除未使用的 FilterPluginsByType 方法
// 已移除未使用的 GetServicePlugins 方法
// 已移除未使用的 GetWebPlugins 方法
// 已移除未使用的 GetLocalPlugins 方法

View File

@ -60,6 +60,15 @@ func (p *PortDiscoveryService) shouldPerformLivenessCheck(hosts []string) bool {
func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string { func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
var alivePorts []string var alivePorts []string
// 如果已经有明确指定的host:port则优先使用并跳过常规端口扫描
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
common.HostPort = nil
return alivePorts
}
// 根据扫描模式选择端口扫描方式 // 根据扫描模式选择端口扫描方式
if len(hosts) > 0 { if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout) alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout)
@ -73,14 +82,6 @@ func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
} }
// 合并额外指定的端口
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.HostPort = nil
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
return alivePorts return alivePorts
} }

View File

@ -5,7 +5,7 @@ import (
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base" "github.com/shadow1ng/fscan/plugins/base"
// 导入新架构插件,触发自动注册 // 导入跨平台服务插件(可在所有平台上运行)
_ "github.com/shadow1ng/fscan/plugins/services/activemq" _ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra" _ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "github.com/shadow1ng/fscan/plugins/services/ftp" _ "github.com/shadow1ng/fscan/plugins/services/ftp"
@ -28,20 +28,30 @@ import (
_ "github.com/shadow1ng/fscan/plugins/services/snmp" _ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh" _ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet" _ "github.com/shadow1ng/fscan/plugins/services/telnet"
_ "github.com/shadow1ng/fscan/plugins/services/vnc"
// 导入Legacy插件适配器 // 导入跨平台Legacy插件
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" _ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" _ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" _ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞但扫描器可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" _ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议主要Windows但可跨平台扫描
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" _ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" _ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" _ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" _ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" // SMBGhost漏洞扫描可跨平台
// 导入Web插件适配器 // 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle" _ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc" _ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
// 导入跨平台本地插件(可在所有平台上运行)
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 系统痕迹清理
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 文件下载
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 文件信息收集
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 正向Shell
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 键盘记录主要Windows但支持跨平台
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 反弹Shell
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // SOCKS5代理
) )
// ============================================================================= // =============================================================================
@ -73,49 +83,15 @@ func InitializePluginSystem() error {
return nil return nil
} }
// GetAllPlugins 获取所有已注册插件名称 // 已移除未使用的 GetAllPlugins 方法
func GetAllPlugins() []string {
return base.GlobalPluginRegistry.GetAll()
}
// GetPluginMetadata 获取插件元数据 // 已移除未使用的 GetPluginMetadata 方法
func GetPluginMetadata(name string) *base.PluginMetadata {
return base.GlobalPluginRegistry.GetMetadata(name)
}
// CreatePlugin 创建插件实例 // 已移除未使用的 CreatePlugin 方法
func CreatePlugin(name string) (base.Plugin, error) {
return base.GlobalPluginRegistry.Create(name)
}
// GetPluginsByCategory 按类别获取插件 // 已移除未使用的 GetPluginsByCategory 方法
func GetPluginsByCategory(category string) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
if metadata.Category == category {
plugins = append(plugins, name)
}
}
}
return plugins
}
// GetPluginsByPort 按端口获取插件 // 已移除未使用的 GetPluginsByPort 方法
func GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// init 自动初始化插件系统 // init 自动初始化插件系统
func init() { func init() {

View File

@ -19,7 +19,7 @@ type ScanStrategy interface {
// 插件管理方法 // 插件管理方法
GetPlugins() ([]string, bool) GetPlugins() ([]string, bool)
IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool
} }
// selectStrategy 根据扫描配置选择适当的扫描策略 // selectStrategy 根据扫描配置选择适当的扫描策略
@ -125,7 +125,7 @@ func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan
} }
// 检查插件是否适用于当前目标 // 检查插件是否适用于当前目标
if strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) { if strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
executeScanTask(pluginName, target, ch, wg) executeScanTask(pluginName, target, ch, wg)
} }
} }
@ -143,7 +143,7 @@ func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCu
for _, pluginName := range pluginsToRun { for _, pluginName := range pluginsToRun {
if GlobalPluginAdapter.PluginExists(pluginName) && if GlobalPluginAdapter.PluginExists(pluginName) &&
strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) { strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
count++ count++
} }
} }
@ -187,7 +187,4 @@ func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{
} }
// Scan 入口函数,向后兼容旧的调用方式 // 已移除未使用的 Scan 方法
func Scan(info common.HostInfo) {
RunScan(info)
}

View File

@ -144,7 +144,7 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
return true return true
} }
// 非自定义模式下,排除本地插件 // 服务扫描排除本地插件但保留Web插件有智能检测
if plugin.HasType(common.PluginTypeLocal) { if plugin.HasType(common.PluginTypeLocal) {
return false return false
} }
@ -166,6 +166,11 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用 // isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool { func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
// 服务扫描排除本地插件但保留service和web类型web有智能检测
if metadata.Category == "local" {
return false
}
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true

377
core/WebDetection.go Normal file
View File

@ -0,0 +1,377 @@
package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
)
// WebPortDetector Web端口检测器
type WebPortDetector struct {
// 常见Web端口列表
commonWebPorts map[int]bool
// HTTP检测超时时间
httpTimeout time.Duration
// HTTP客户端
httpClient *http.Client
// HTTPS客户端
httpsClient *http.Client
// 检测结果缓存(避免重复检测)
detectionCache map[string]bool
// 缓存互斥锁
cacheMutex sync.RWMutex
}
// NewWebPortDetector 创建Web端口检测器
func NewWebPortDetector() *WebPortDetector {
timeout := 3 * time.Second
// 定义常见Web端口扩展列表
commonPorts := map[int]bool{
// 标准Web端口
80: true, // HTTP
443: true, // HTTPS
8080: true, // HTTP alternate
8443: true, // HTTPS alternate
// 开发服务器端口
3000: true, // Node.js dev server
4000: true, // Ruby dev server
5000: true, // Python dev server
8000: true, // Development server
8888: true, // Common dev port
9000: true, // Common dev port
9090: true, // Common dev port
// Web服务器alternate端口
8081: true, 8082: true, 8083: true, 8084: true, 8085: true,
8086: true, 8087: true, 8088: true, 8089: true,
// 企业级Web端口
9080: true, // WebSphere
9443: true, // WebSphere SSL
7001: true, // WebLogic
7002: true, // WebLogic SSL
// 应用服务器端口
8180: true, // Tomcat alternate
8181: true, // Tomcat alternate
8282: true, // Common alternate
8383: true, // Common alternate
8484: true, // Common alternate
8585: true, // Common alternate
// 代理和负载均衡端口
3128: true, // Squid proxy
8008: true, // HTTP proxy
8118: true, // Privoxy
// 监控和管理界面
9200: true, // Elasticsearch
5601: true, // Kibana
3001: true, // Grafana alternate
9091: true, // Prometheus
}
// 创建HTTP客户端
httpClient := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: timeout,
}).DialContext,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableKeepAlives: true,
MaxConnsPerHost: 5,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不跟随重定向,减少检测时间
},
}
// 创建HTTPS客户端跳过证书验证
httpsClient := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: timeout,
}).DialContext,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableKeepAlives: true,
MaxConnsPerHost: 5,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return &WebPortDetector{
commonWebPorts: commonPorts,
httpTimeout: timeout,
httpClient: httpClient,
httpsClient: httpsClient,
detectionCache: make(map[string]bool),
}
}
// IsWebService 智能检测端口是否运行Web服务
func (w *WebPortDetector) IsWebService(host string, port int) bool {
// 1. 快速路径常见Web端口直接返回true
if w.IsCommonWebPort(port) {
common.LogDebug(fmt.Sprintf("端口 %d 是常见Web端口启用Web插件", port))
return true
}
// 2. 缓存检查:避免重复检测同一端口
cacheKey := fmt.Sprintf("%s:%d", host, port)
w.cacheMutex.RLock()
if cached, exists := w.detectionCache[cacheKey]; exists {
w.cacheMutex.RUnlock()
common.LogDebug(fmt.Sprintf("端口 %d 使用缓存结果: %v", port, cached))
return cached
}
w.cacheMutex.RUnlock()
// 3. 智能路径对非常见端口进行HTTP协议探测
common.LogDebug(fmt.Sprintf("对端口 %d 进行智能Web检测", port))
result := w.detectHTTPService(host, port)
// 4. 缓存结果
w.cacheMutex.Lock()
w.detectionCache[cacheKey] = result
w.cacheMutex.Unlock()
common.LogDebug(fmt.Sprintf("端口 %d 智能检测结果: %v", port, result))
return result
}
// IsCommonWebPort 检查是否为常见Web端口
func (w *WebPortDetector) IsCommonWebPort(port int) bool {
return w.commonWebPorts[port]
}
// detectHTTPService 检测HTTP服务真正的智能检测
func (w *WebPortDetector) detectHTTPService(host string, port int) bool {
// 创建检测上下文,避免长时间阻塞
ctx, cancel := context.WithTimeout(context.Background(), w.httpTimeout)
defer cancel()
// 并发检测HTTP和HTTPS
httpChan := make(chan bool, 1)
httpsChan := make(chan bool, 1)
// 检测HTTP
go func() {
httpChan <- w.tryHTTPConnection(ctx, host, port, "http")
}()
// 检测HTTPS对于高端口常见
go func() {
httpsChan <- w.tryHTTPConnection(ctx, host, port, "https")
}()
// 等待任一协议检测成功
select {
case result := <-httpChan:
if result {
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTP服务", port))
return true
}
case <-ctx.Done():
return false
}
select {
case result := <-httpsChan:
if result {
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTPS服务", port))
return true
}
case <-ctx.Done():
return false
}
return false
}
// tryHTTPConnection 尝试HTTP连接
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
// 构造URL
var url string
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
url = fmt.Sprintf("%s://%s", protocol, host)
} else {
url = fmt.Sprintf("%s://%s:%d", protocol, host, port)
}
// 选择客户端
var client *http.Client
if protocol == "https" {
client = w.httpsClient
} else {
client = w.httpClient
}
// 发送HEAD请求最小化网络开销
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false
}
req.Header.Set("User-Agent", "fscan-web-detector/2.2")
req.Header.Set("Accept", "*/*")
resp, err := client.Do(req)
if err != nil {
// 检查错误类型某些错误也表明是HTTP服务
return w.analyzeHTTPError(err)
}
defer resp.Body.Close()
// 分析HTTP响应
return w.analyzeHTTPResponse(resp, url)
}
// analyzeHTTPError 分析HTTP错误判断是否表明存在HTTP服务
func (w *WebPortDetector) analyzeHTTPError(err error) bool {
errStr := strings.ToLower(err.Error())
// 先检查明确的非Web服务错误
nonWebErrors := []string{
"connection refused",
"no such host",
"network unreachable",
"timeout",
"deadline exceeded",
"connection reset",
"eof",
}
for _, nonWebErr := range nonWebErrors {
if strings.Contains(errStr, nonWebErr) {
return false
}
}
// 这些错误表明连接到了HTTP服务器只是协议或其他问题
webIndicators := []string{
"malformed http response",
"unexpected http response",
"ssl handshake",
"certificate",
"tls",
"bad request",
"method not allowed",
"server gave http response",
}
for _, indicator := range webIndicators {
if strings.Contains(errStr, indicator) {
return true
}
}
return false
}
// analyzeHTTPResponse 分析HTTP响应判断是否为Web服务
func (w *WebPortDetector) analyzeHTTPResponse(resp *http.Response, url string) bool {
// 任何HTTP状态码都表明这是Web服务
if resp.StatusCode > 0 {
// 分析响应头获取更多信息
serverHeader := resp.Header.Get("Server")
contentType := resp.Header.Get("Content-Type")
// 记录检测到的Web服务器信息
if serverHeader != "" {
common.LogDebug(fmt.Sprintf("检测到Web服务器: %s (Server: %s)", url, serverHeader))
}
// 特殊处理某些非Web服务也会返回HTTP响应
if w.isNonWebHTTPService(serverHeader, contentType) {
common.LogDebug(fmt.Sprintf("端口返回HTTP响应但可能不是Web服务: %s", url))
return false
}
return true
}
return false
}
// isNonWebHTTPService 判断是否为非Web的HTTP服务
func (w *WebPortDetector) isNonWebHTTPService(serverHeader, contentType string) bool {
// 某些服务使用HTTP协议但不是传统Web服务
nonWebServices := []string{
"docker",
"kubernetes",
"consul",
"etcd",
"vault",
"nomad",
}
serverLower := strings.ToLower(serverHeader)
for _, service := range nonWebServices {
if strings.Contains(serverLower, service) {
return true
}
}
return false
}
// GetWebPortRange 获取可能的Web端口范围用于高级扫描
func (w *WebPortDetector) GetWebPortRange() []int {
// 返回扩展的Web端口列表用于目标发现
var ports []int
for port := range w.commonWebPorts {
ports = append(ports, port)
}
// 添加一些常见的自定义端口范围
customRanges := []int{
8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099,
9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009,
10000, 10001, 10002, 10003, 10004, 10005,
}
ports = append(ports, customRanges...)
return ports
}
// IsWebPortByPattern 基于端口模式判断是否可能是Web端口
func (w *WebPortDetector) IsWebPortByPattern(port int) bool {
portStr := strconv.Itoa(port)
// Web端口的常见模式
webPatterns := []*regexp.Regexp{
regexp.MustCompile(`^80\d{2}$`), // 80xx
regexp.MustCompile(`^90\d{2}$`), // 90xx
regexp.MustCompile(`^300\d$`), // 300x
regexp.MustCompile(`^[3-9]000$`), // x000
regexp.MustCompile(`^1[0-9]000$`), // 1x000
}
for _, pattern := range webPatterns {
if pattern.MatchString(portStr) {
return true
}
}
return false
}

View File

@ -1,6 +1,8 @@
package core package core
import ( import (
"fmt"
"net/url"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"strings" "strings"
@ -54,16 +56,55 @@ func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *s
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 { // 首先从common.URLs获取目标
urlInfo := baseInfo for _, urlStr := range common.URLs {
// 确保URL包含协议头 urlInfo := s.createTargetFromURL(baseInfo, urlStr)
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { if urlInfo != nil {
url = "http://" + url targetInfos = append(targetInfos, *urlInfo)
}
}
// 如果common.URLs为空但baseInfo.Url有值使用baseInfo.Url
if len(targetInfos) == 0 && baseInfo.Url != "" {
urlInfo := s.createTargetFromURL(baseInfo, baseInfo.Url)
if urlInfo != nil {
targetInfos = append(targetInfos, *urlInfo)
} }
urlInfo.Url = url
targetInfos = append(targetInfos, urlInfo)
} }
return targetInfos return targetInfos
} }
// createTargetFromURL 从URL创建目标信息
func (s *WebScanStrategy) createTargetFromURL(baseInfo common.HostInfo, urlStr string) *common.HostInfo {
// 确保URL包含协议头
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
urlStr = "http://" + urlStr
}
// 解析URL获取Host和Port信息
parsedURL, err := url.Parse(urlStr)
if err != nil {
common.LogError(fmt.Sprintf("解析URL失败: %s - %v", urlStr, err))
return nil
}
urlInfo := baseInfo
urlInfo.Url = urlStr
urlInfo.Host = parsedURL.Hostname()
// 设置端口
port := parsedURL.Port()
if port == "" {
// 根据协议设置默认端口
if parsedURL.Scheme == "https" {
port = "443"
} else {
port = "80"
}
}
urlInfo.Ports = port
return &urlInfo
}

11
core/registry_linux.go Normal file
View File

@ -0,0 +1,11 @@
//go:build linux
package core
import (
// Linux持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/ldpreload" // Linux LD_PRELOAD持久化
_ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化
_ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化
)

17
core/registry_windows.go Normal file
View File

@ -0,0 +1,17 @@
//go:build windows
package core
import (
// Windows特有系统功能插件
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // Windows 杀毒软件检测
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo" // Windows 域控信息收集
_ "github.com/shadow1ng/fscan/plugins/local/minidump" // Windows 内存转储
// Windows持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化
_ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化
_ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化
)

97
main.go
View File

@ -1,61 +1,62 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"github.com/shadow1ng/fscan/app"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core"
// 引入本地插件以触发注册
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/minidump" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 新增,可用
// Linux持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/ldpreload" // Linux LD_PRELOAD持久化
_ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化
_ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化
// Windows持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化
_ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化
_ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化
// 监控插件
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 跨平台键盘记录
// 实用工具插件
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 跨平台文件下载
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 跨平台系统痕迹清理
) )
func main() { func main() {
var Info common.HostInfo // 创建应用容器
common.Flag(&Info) container := app.NewContainer()
// 在flag解析后初始化logger确保LogLevel参数生效 // 第一阶段:基础初始化(插件系统)
common.InitLogger() if err := container.Initialize(); err != nil {
handleError("基础初始化失败", err)
// 解析 CLI 参数
if err := common.Parse(&Info); err != nil {
os.Exit(1)
} }
defer container.Cleanup()
// 初始化输出系统,如果失败则直接退出
if err := common.InitOutput(); err != nil { // 第二阶段:解析配置
common.LogError(fmt.Sprintf("初始化输出系统失败: %v", err)) var info common.HostInfo
os.Exit(1) common.Flag(&info)
// 第三阶段日志初始化依赖于flag解析
logInit := &app.LoggerInitializer{}
if err := logInit.Initialize(); err != nil {
handleError("日志初始化失败", err)
}
// 第四阶段:参数解析和验证
if err := common.Parse(&info); err != nil {
handleError("参数解析失败", err)
}
// 第五阶段:输出系统初始化
outputInit := &app.OutputInitializer{}
if err := outputInit.Initialize(); err != nil {
handleError("输出初始化失败", err)
}
// 第六阶段:执行扫描
ctx := context.Background()
if err := container.RunScan(ctx, info); err != nil {
handleError("扫描失败", err)
}
}
func handleError(msg string, err error) {
// 检查是否是应用程序错误
if appErr, ok := err.(*app.AppError); ok {
common.LogError(fmt.Sprintf("%s: %s", msg, appErr.Message))
if appErr.Cause != nil {
common.LogError(fmt.Sprintf("详细错误: %v", appErr.Cause))
}
os.Exit(appErr.Code)
} else {
common.LogError(fmt.Sprintf("%s: %v", msg, err))
os.Exit(1)
} }
defer common.CloseOutput()
// 执行 CLI 扫描逻辑
core.RunScan(Info)
} }

View File

@ -0,0 +1,7 @@
// Package adapters provides plugin compatibility layers.
// This package contains legacy adapter code that was part of a transition architecture.
// The adapter functions were not being used in the current codebase.
package adapters
// Legacy plugin adapter functionality has been removed as it was unused.
// This file is preserved for future compatibility needs if required.

View File

@ -264,46 +264,4 @@ func GenerateCredentials(usernames []string, passwords []string) []*Credential {
return credentials return credentials
} }
// GeneratePasswordOnlyCredentials 生成仅密码的凭据列表如Redis // 已移除未使用的 GeneratePasswordOnlyCredentials 方法
func GeneratePasswordOnlyCredentials(passwords []string) []*Credential {
var credentials []*Credential
for _, password := range passwords {
credentials = append(credentials, &Credential{
Password: password,
Extra: make(map[string]string),
})
}
return credentials
}
// =============================================================================
// 结果处理工具
// =============================================================================
// SaveScanResult 保存扫描结果到通用输出系统
func SaveScanResult(info *common.HostInfo, result *ScanResult, pluginName string) {
if result == nil || !result.Success {
return
}
target := fmt.Sprintf("%s:%d", info.Host, info.Ports)
// 保存成功的凭据
for _, cred := range result.Credentials {
var message string
if cred.Username != "" && cred.Password != "" {
message = fmt.Sprintf("%s %s %s %s", pluginName, target, cred.Username, cred.Password)
} else if cred.Password != "" {
message = fmt.Sprintf("%s %s [密码] %s", pluginName, target, cred.Password)
} else {
message = fmt.Sprintf("%s %s 未授权访问", pluginName, target)
}
common.LogSuccess(message)
// 保存到输出系统的详细实现...
// 这里可以调用common.SaveResult等函数
}
}

View File

@ -1,7 +1,6 @@
package Plugins package Plugins
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"encoding/base64" "encoding/base64"
@ -44,32 +43,6 @@ func ReadBytes(conn net.Conn) ([]byte, error) {
// 默认AES加密密钥 // 默认AES加密密钥
var key = "0123456789abcdef" var key = "0123456789abcdef"
// AesEncrypt 使用AES-CBC模式加密字符串
func AesEncrypt(orig string, key string) (string, error) {
// 转为字节数组
origData := []byte(orig)
keyBytes := []byte(key)
// 创建加密块,要求密钥长度必须为16/24/32字节
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建加密块失败: %v", err)
}
// 获取块大小并填充数据
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
// 创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
// 加密数据
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
// base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// AesDecrypt 使用AES-CBC模式解密字符串 // AesDecrypt 使用AES-CBC模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) { func AesDecrypt(crypted string, key string) (string, error) {
@ -104,12 +77,6 @@ func AesDecrypt(crypted string, key string) (string, error) {
return string(origData), nil return string(origData), nil
} }
// PKCS7Padding 对数据进行PKCS7填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS7UnPadding 去除PKCS7填充 // PKCS7UnPadding 去除PKCS7填充
func PKCS7UnPadding(data []byte) ([]byte, error) { func PKCS7UnPadding(data []byte) ([]byte, error) {

View File

@ -1,3 +1,5 @@
//go:build linux
package crontask package crontask
import ( import (

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