Compare commits

...

21 Commits

Author SHA1 Message Date
ZacharyZcR
143801ff58 feat: 实现Web插件智能检测,增强服务扫描模式
当服务扫描模式检测到常见Web端口时,自动包含WebPoc和WebTitle插件,
提升Web服务扫描覆盖率的同时保持完全向后兼容性。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- 更新国际化消息和插件注册机制
- 两个插件均通过完整功能测试验证
2025-08-09 13:46:46 +08:00
ZacharyZcR
3e71e7e4c9 feat: RabbitMQ消息队列服务插件迁移到新架构完成
- 实现AMQP协议连接器,支持RabbitMQ消息队列服务扫描
- 支持弱密码检测和服务识别功能
- 优先检测默认凭据admin/123456和guest/guest
- 服务识别使用TCP连接检测,避免AMQP协议复杂度
- 简化实现,不提供利用功能,专注于安全扫描
- 集成国际化消息系统
- 测试通过:服务识别和弱密码检测功能
2025-08-09 13:30:24 +08:00
ZacharyZcR
532daf16ed feat: POP3邮件服务插件迁移到新架构完成
- 实现POP3协议连接器,支持普通和TLS连接(端口110/995)
- 支持弱密码检测和邮件服务识别功能
- 简化实现,不提供利用功能,专注于安全扫描
- 集成国际化消息系统
- 测试通过:服务识别和弱密码检测功能
2025-08-09 13:16:47 +08:00
ZacharyZcR
3de7b21fe0 feat: Oracle数据库插件迁移到新架构完成
- 实现Oracle TNS协议连接器,支持多种服务名
- 支持高危用户(SYS/SYSTEM)检测和SYSDBA权限
- 实现服务识别和弱密码检测功能
- 集成国际化消息系统
- 测试通过:服务识别和高危用户认证功能
2025-08-09 13:02:11 +08:00
ZacharyZcR
dc8579f554 feat: 实现Neo4j图数据库专业扫描插件
- 新增Neo4j Bolt协议识别和弱密码检测
- 支持未授权访问和默认凭据检测
- 实现ServiceConnector三层架构模式
- 添加Neo4j专用国际化消息
- 支持7474/7687端口扫描
- 自动注册到插件系统
2025-08-09 11:57:35 +08:00
ZacharyZcR
a71092b514 feat: 实现Microsoft SQL Server数据库专业扫描插件
- 新增MSSQL协议识别和弱密码检测
- 支持sa等管理员账户暴力破解
- 实现ServiceConnector三层架构模式
- 添加MSSQL专用国际化消息
- 支持SOCKS代理连接
- 自动注册到插件系统
2025-08-09 11:46:07 +08:00
ZacharyZcR
7e4d5a0bcd feat: 实现MongoDB NoSQL数据库专业扫描插件
- 新增MongoDB协议识别和未授权访问检测
- 支持Wire Protocol(OP_MSG和OP_QUERY)
- 实现connector/exploiter/plugin三层架构
- 添加MongoDB专用国际化消息
- 自动注册到插件系统
2025-08-08 14:02:13 +08:00
ZacharyZcR
fbe141cc80 feat: 实现Memcached和Modbus专业扫描插件 2025-08-08 13:37:58 +08:00
ZacharyZcR
7b8d2b4add feat: 实现LDAP轻量级目录访问协议专业扫描插件 2025-08-08 12:54:03 +08:00
115 changed files with 9128 additions and 6409 deletions

View File

@ -628,4 +628,276 @@ var PluginMessages = map[string]map[string]string{
LangZH: "文件上传测试",
LangEN: "File Upload Test",
},
// ========================= LDAP插件消息 =========================
"ldap_weak_pwd_success": {
LangZH: "LDAP弱密码: %s [%s:%s]",
LangEN: "LDAP weak password: %s [%s:%s]",
},
"ldap_anonymous_access": {
LangZH: "LDAP服务 %s 匿名访问成功",
LangEN: "LDAP service %s anonymous access successful",
},
"ldap_service_identified": {
LangZH: "LDAP服务识别成功: %s - %s",
LangEN: "LDAP service identified: %s - %s",
},
"ldap_connection_failed": {
LangZH: "LDAP连接失败: %v",
LangEN: "LDAP connection failed: %v",
},
"ldap_auth_failed": {
LangZH: "LDAP认证失败: %v",
LangEN: "LDAP authentication failed: %v",
},
// ========================= Memcached插件消息 =========================
"memcached_unauth_access": {
LangZH: "Memcached服务 %s 未授权访问成功",
LangEN: "Memcached service %s unauthorized access successful",
},
"memcached_service_identified": {
LangZH: "Memcached服务识别成功: %s - %s",
LangEN: "Memcached service identified: %s - %s",
},
"memcached_connection_failed": {
LangZH: "Memcached连接失败: %v",
LangEN: "Memcached connection failed: %v",
},
// ========================= Modbus插件消息 =========================
"modbus_unauth_access": {
LangZH: "Modbus服务 %s 无认证访问成功",
LangEN: "Modbus service %s unauthorized access successful",
},
"modbus_device_info": {
LangZH: "设备信息: %s",
LangEN: "Device info: %s",
},
"modbus_service_identified": {
LangZH: "Modbus服务识别成功: %s - %s",
LangEN: "Modbus service identified: %s - %s",
},
"modbus_connection_failed": {
LangZH: "Modbus连接失败: %v",
LangEN: "Modbus connection failed: %v",
},
// ========================= MongoDB插件消息 =========================
"mongodb_unauth_access": {
LangZH: "MongoDB服务 %s 未授权访问成功",
LangEN: "MongoDB service %s unauthorized access successful",
},
"mongodb_service_identified": {
LangZH: "MongoDB服务识别成功: %s - %s",
LangEN: "MongoDB service identified: %s - %s",
},
"mongodb_connection_failed": {
LangZH: "MongoDB连接失败: %v",
LangEN: "MongoDB connection failed: %v",
},
"mongodb_auth_failed": {
LangZH: "MongoDB认证失败: %v",
LangEN: "MongoDB authentication failed: %v",
},
// ========================= MSSQL插件消息 =========================
"mssql_auth_success": {
LangZH: "MSSQL服务 %s 认证成功 %s:%s",
LangEN: "MSSQL service %s authentication successful %s:%s",
},
"mssql_service_identified": {
LangZH: "MSSQL服务识别成功: %s - %s",
LangEN: "MSSQL service identified: %s - %s",
},
"mssql_connection_failed": {
LangZH: "MSSQL连接失败: %v",
LangEN: "MSSQL connection failed: %v",
},
"mssql_auth_failed": {
LangZH: "MSSQL认证失败 %s: %v",
LangEN: "MSSQL authentication failed %s: %v",
},
// ========================= Neo4j插件消息 =========================
"neo4j_unauth_access": {
LangZH: "Neo4j服务 %s 未授权访问成功",
LangEN: "Neo4j service %s unauthorized access successful",
},
"neo4j_default_creds": {
LangZH: "Neo4j服务 %s 默认凭证可用 %s:%s",
LangEN: "Neo4j service %s default credentials available %s:%s",
},
"neo4j_auth_success": {
LangZH: "Neo4j服务 %s 认证成功 %s:%s",
LangEN: "Neo4j service %s authentication successful %s:%s",
},
"neo4j_service_identified": {
LangZH: "Neo4j服务识别成功: %s - %s",
LangEN: "Neo4j service identified: %s - %s",
},
"neo4j_connection_failed": {
LangZH: "Neo4j连接失败: %v",
LangEN: "Neo4j connection failed: %v",
},
"neo4j_auth_failed": {
LangZH: "Neo4j认证失败 %s: %v",
LangEN: "Neo4j authentication failed %s: %v",
},
// ========================= PostgreSQL插件消息 =========================
"postgresql_auth_success": {
LangZH: "PostgreSQL服务 %s 认证成功 %s:%s",
LangEN: "PostgreSQL service %s authentication successful %s:%s",
},
"postgresql_service_identified": {
LangZH: "PostgreSQL服务识别成功: %s - %s",
LangEN: "PostgreSQL service identified: %s - %s",
},
"postgresql_connection_failed": {
LangZH: "PostgreSQL连接失败: %v",
LangEN: "PostgreSQL connection failed: %v",
},
"postgresql_auth_failed": {
LangZH: "PostgreSQL认证失败 %s: %v",
LangEN: "PostgreSQL authentication failed %s: %v",
},
// ========================= Oracle插件消息 =========================
"oracle_auth_success": {
LangZH: "Oracle服务 %s 认证成功 %s:%s",
LangEN: "Oracle service %s authentication successful %s:%s",
},
"oracle_sys_auth_success": {
LangZH: "Oracle服务 %s 高危用户认证成功 %s:%s (可能需要SYSDBA权限)",
LangEN: "Oracle service %s high-risk user authentication successful %s:%s (may require SYSDBA privilege)",
},
"oracle_service_identified": {
LangZH: "Oracle服务识别成功: %s - %s",
LangEN: "Oracle service identified: %s - %s",
},
"oracle_connection_failed": {
LangZH: "Oracle连接失败: %v",
LangEN: "Oracle connection failed: %v",
},
"oracle_auth_failed": {
LangZH: "Oracle认证失败 %s: %v",
LangEN: "Oracle authentication failed %s: %v",
},
// ========================= POP3插件消息 =========================
"pop3_weak_pwd_success": {
LangZH: "POP3弱密码: %s [%s:%s]",
LangEN: "POP3 weak password: %s [%s:%s]",
},
"pop3_service_identified": {
LangZH: "POP3服务识别成功: %s - %s",
LangEN: "POP3 service identified: %s - %s",
},
"pop3_connection_failed": {
LangZH: "POP3连接失败: %v",
LangEN: "POP3 connection failed: %v",
},
"pop3_auth_failed": {
LangZH: "POP3认证失败: %v",
LangEN: "POP3 authentication failed: %v",
},
// ========================= RabbitMQ插件消息 =========================
"rabbitmq_weak_pwd_success": {
LangZH: "RabbitMQ弱密码: %s [%s:%s]",
LangEN: "RabbitMQ weak password: %s [%s:%s]",
},
"rabbitmq_service_identified": {
LangZH: "RabbitMQ服务识别成功: %s - %s",
LangEN: "RabbitMQ service identified: %s - %s",
},
"rabbitmq_connection_failed": {
LangZH: "RabbitMQ连接失败: %v",
LangEN: "RabbitMQ connection failed: %v",
},
"rabbitmq_auth_failed": {
LangZH: "RabbitMQ认证失败: %v",
LangEN: "RabbitMQ authentication failed: %v",
},
// ========================= Rsync插件消息 =========================
"rsync_anonymous_success": {
LangZH: "Rsync匿名访问: %s",
LangEN: "Rsync anonymous access: %s",
},
"rsync_weak_pwd_success": {
LangZH: "Rsync弱密码: %s [%s:%s]",
LangEN: "Rsync weak password: %s [%s:%s]",
},
"rsync_service_identified": {
LangZH: "Rsync服务识别成功: %s - %s",
LangEN: "Rsync service identified: %s - %s",
},
"rsync_connection_failed": {
LangZH: "Rsync连接失败: %v",
LangEN: "Rsync connection failed: %v",
},
"rsync_auth_failed": {
LangZH: "Rsync认证失败: %v",
LangEN: "Rsync authentication failed: %v",
},
// ========================= SMTP插件消息 =========================
"smtp_anonymous_success": {
LangZH: "SMTP匿名访问: %s",
LangEN: "SMTP anonymous access: %s",
},
"smtp_weak_pwd_success": {
LangZH: "SMTP弱密码: %s [%s:%s]",
LangEN: "SMTP weak password: %s [%s:%s]",
},
"smtp_service_identified": {
LangZH: "SMTP服务识别成功: %s - %s",
LangEN: "SMTP service identified: %s - %s",
},
"smtp_connection_failed": {
LangZH: "SMTP连接失败: %v",
LangEN: "SMTP connection failed: %v",
},
"smtp_auth_failed": {
LangZH: "SMTP认证失败: %v",
LangEN: "SMTP authentication failed: %v",
},
// ========================= SNMP插件消息 =========================
"snmp_weak_community_success": {
LangZH: "SNMP弱community: %s [%s]",
LangEN: "SNMP weak community: %s [%s]",
},
"snmp_service_identified": {
LangZH: "SNMP服务识别成功: %s - %s",
LangEN: "SNMP service identified: %s - %s",
},
"snmp_connection_failed": {
LangZH: "SNMP连接失败: %v",
LangEN: "SNMP connection failed: %v",
},
"snmp_auth_failed": {
LangZH: "SNMP认证失败: %v",
LangEN: "SNMP authentication failed: %v",
},
// ========================= Telnet插件消息 =========================
"telnet_weak_password_success": {
LangZH: "Telnet弱密码: %s 用户名:%s 密码:%s",
LangEN: "Telnet weak password: %s username:%s password:%s",
},
"telnet_unauthorized_access": {
LangZH: "Telnet无需认证: %s",
LangEN: "Telnet unauthorized access: %s",
},
"telnet_connection_failed": {
LangZH: "Telnet连接失败: %v",
LangEN: "Telnet connection failed: %v",
},
"telnet_auth_failed": {
LangZH: "Telnet认证失败: %v",
LangEN: "Telnet authentication failed: %v",
},
}

View File

@ -62,6 +62,10 @@ var ScanMessages = map[string]map[string]string{
LangZH: "存活端口数量: %d",
LangEN: "Alive ports count: %d",
},
"scan_snmp_udp_ports_added": {
LangZH: "检测到SNMP端口161添加UDP端口到扫描目标",
LangEN: "Detected SNMP port 161, adding UDP ports to scan targets",
},
"scan_task_complete": {
LangZH: "扫描已完成: %d/%d",
LangEN: "Scan completed: %d/%d",

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strings"
)
@ -131,6 +132,11 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
return false
}
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetPort) {
return true
}
// 检查类型匹配
if !b.isPluginTypeMatchedByName(pluginName) {
return false
@ -149,6 +155,35 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
return true
}
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool {
// 只对服务扫描策略启用Web插件智能检测
if b.filterType != FilterService {
return false
}
// 只对Web类别的插件进行检测
if metadata.Category != "web" {
return false
}
// 如果没有指定端口,跳过检测
if targetPort <= 0 {
return false
}
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测
return globalWebDetector.IsCommonWebPort(targetPort)
}
// globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件

View File

@ -66,6 +66,13 @@ func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// UDP端口特殊处理当前仅支持SNMP的161端口
udpPorts := p.handleUDPPorts(hosts)
if len(udpPorts) > 0 {
alivePorts = append(alivePorts, udpPorts...)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
// 合并额外指定的端口
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
@ -96,3 +103,30 @@ func (p *PortDiscoveryService) convertToTargetInfos(ports []string, baseInfo com
return infos
}
// handleUDPPorts 处理UDP端口的特殊逻辑
func (p *PortDiscoveryService) handleUDPPorts(hosts []string) []string {
var udpPorts []string
// 检查是否包含SNMP端口161
portList := parsers.ParsePort(common.Ports)
hasPort161 := false
for _, port := range portList {
if port == 161 {
hasPort161 = true
break
}
}
// 如果端口列表包含161则为每个主机添加UDP 161端口
if hasPort161 {
for _, host := range hosts {
udpPorts = append(udpPorts, fmt.Sprintf("%s:161", host))
}
if len(udpPorts) > 0 {
common.LogBase(i18n.GetText("scan_snmp_udp_ports_added"))
}
}
return udpPorts
}

View File

@ -11,9 +11,37 @@ import (
_ "github.com/shadow1ng/fscan/plugins/services/ftp"
_ "github.com/shadow1ng/fscan/plugins/services/imap"
_ "github.com/shadow1ng/fscan/plugins/services/kafka"
_ "github.com/shadow1ng/fscan/plugins/services/ldap"
_ "github.com/shadow1ng/fscan/plugins/services/memcached"
_ "github.com/shadow1ng/fscan/plugins/services/modbus"
_ "github.com/shadow1ng/fscan/plugins/services/mongodb"
_ "github.com/shadow1ng/fscan/plugins/services/mssql"
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/neo4j"
_ "github.com/shadow1ng/fscan/plugins/services/oracle"
_ "github.com/shadow1ng/fscan/plugins/services/pop3"
_ "github.com/shadow1ng/fscan/plugins/services/postgresql"
_ "github.com/shadow1ng/fscan/plugins/services/rabbitmq"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/rsync"
_ "github.com/shadow1ng/fscan/plugins/services/smtp"
_ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet"
// 导入Legacy插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios"
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
// 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
)
// =============================================================================

145
Core/WebDetection.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +0,0 @@
package Plugins
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/adapter"
)
// MysqlScan 执行MySQL服务扫描
// 现在完全使用新的插件架构
func MysqlScan(info *common.HostInfo) error {
// 使用新的插件架构
if adapter.TryNewArchitecture("mysql", info) {
return nil // 新架构处理成功
}
// 如果新架构不支持,记录错误(理论上不应该发生)
common.LogError("MySQL插件新架构不可用请检查插件注册")
return nil
}

View File

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

View File

@ -1,436 +0,0 @@
package Plugins
import (
"context"
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
_ "github.com/sijms/go-ora/v2"
"strings"
"sync"
"time"
)
// OracleCredential 表示一个Oracle凭据
type OracleCredential struct {
Username string
Password string
}
// OracleScanResult 表示Oracle扫描结果
type OracleScanResult struct {
Success bool
Error error
Credential OracleCredential
ServiceName string
}
// 常见Oracle服务名列表
var commonServiceNames = []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"}
func OracleScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 构建常见高危凭据列表(优先测试)
highRiskCredentials := []OracleCredential{
{Username: "SYS", Password: "123456"},
{Username: "SYSTEM", Password: "123456"},
{Username: "SYS", Password: "oracle"},
{Username: "SYSTEM", Password: "oracle"},
{Username: "SYS", Password: "password"},
{Username: "SYSTEM", Password: "password"},
{Username: "SYS", Password: "sys123"},
{Username: "SYS", Password: "change_on_install"},
{Username: "SYSTEM", Password: "manager"},
}
// 先尝试常见高危凭据
common.LogDebug("尝试常见高危凭据...")
for _, cred := range highRiskCredentials {
result := tryAllServiceNames(ctx, info, cred, common.Timeout, 1)
if result != nil && result.Success {
saveOracleResult(info, target, result.Credential, result.ServiceName)
return nil
}
}
// 构建完整凭据列表
var credentials []OracleCredential
for _, user := range common.Userdict["oracle"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
// 转换用户名为大写,提高匹配率
credentials = append(credentials, OracleCredential{
Username: strings.ToUpper(user),
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["oracle"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentOracleScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
saveOracleResult(info, target, result.Credential, result.ServiceName)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("Oracle扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(highRiskCredentials)))
return nil
}
}
// tryAllServiceNames 尝试所有常见服务名
func tryAllServiceNames(ctx context.Context, info *common.HostInfo, credential OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
for _, serviceName := range commonServiceNames {
result := tryOracleCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries)
if result.Success {
result.ServiceName = serviceName
return result
}
// 对SYS用户尝试SYSDBA模式
if strings.ToUpper(credential.Username) == "SYS" {
result = tryOracleSysCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries)
if result.Success {
result.ServiceName = serviceName
return result
}
}
}
return nil
}
// concurrentOracleScan 并发扫描Oracle服务
func concurrentOracleScan(ctx context.Context, info *common.HostInfo, credentials []OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *OracleScanResult, 1)
workChan := make(chan OracleCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 尝试所有常见服务名
result := tryAllServiceNames(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result != nil && result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("Oracle并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryOracleCredential 尝试单个Oracle凭据
func tryOracleCredential(ctx context.Context, info *common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &OracleScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 在协程中执行数据库连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, false)
select {
case <-connCtx.Done():
// 已超时或取消,不发送结果
case resultChan <- struct {
success bool
err error
}{success, err}:
}
}()
// 等待结果或连接超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
err = connCtx.Err()
}
// 取消连接超时上下文
cancel()
if success {
return &OracleScanResult{
Success: true,
Credential: credential,
ServiceName: serviceName,
}
}
lastErr = err
if err != nil {
// 如果是认证错误,不需要重试
if strings.Contains(err.Error(), "ORA-01017") {
break // 认证失败
}
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &OracleScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// tryOracleSysCredential 尝试SYS用户SYSDBA模式连接
func tryOracleSysCredential(ctx context.Context, info *common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &OracleScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试SYS用户SYSDBA模式: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 在协程中执行数据库连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, true)
select {
case <-connCtx.Done():
// 已超时或取消,不发送结果
case resultChan <- struct {
success bool
err error
}{success, err}:
}
}()
// 等待结果或连接超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
err = connCtx.Err()
}
// 取消连接超时上下文
cancel()
if success {
return &OracleScanResult{
Success: true,
Credential: credential,
ServiceName: serviceName,
}
}
lastErr = err
if err != nil {
// 如果是认证错误,不需要重试
if strings.Contains(err.Error(), "ORA-01017") {
break // 认证失败
}
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &OracleScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// OracleConn 尝试Oracle连接
func OracleConn(ctx context.Context, info *common.HostInfo, user string, pass string, serviceName string, asSysdba bool) (bool, error) {
host, port := info.Host, info.Ports
// 构造连接字符串,添加更多参数
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d",
user, pass, host, port, serviceName, common.Timeout)
// 对SYS用户使用SYSDBA权限
if asSysdba {
connStr += "&sysdba=1"
}
// 建立数据库连接
db, err := sql.Open("oracle", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(common.Timeout) * time.Second)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 使用上下文测试连接
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 测试连接
err = db.PingContext(pingCtx)
if err != nil {
return false, err
}
// 不需要额外的查询验证,连接成功即可
return true, nil
}
// saveOracleResult 保存Oracle扫描结果
func saveOracleResult(info *common.HostInfo, target string, credential OracleCredential, serviceName string) {
var successMsg string
if strings.ToUpper(credential.Username) == "SYS" {
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s (可能需要SYSDBA权限)",
target, credential.Username, credential.Password, serviceName)
} else {
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s",
target, credential.Username, credential.Password, serviceName)
}
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "oracle",
"username": credential.Username,
"password": credential.Password,
"service_name": serviceName,
"type": "weak-password",
},
}
common.SaveResult(vulnResult)
}

View File

@ -1,414 +0,0 @@
package Plugins
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// POP3Credential 表示一个POP3凭据
type POP3Credential struct {
Username string
Password string
}
// POP3ScanResult 表示POP3扫描结果
type POP3ScanResult struct {
Success bool
Error error
Credential POP3Credential
IsTLS bool
}
func POP3Scan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []POP3Credential
for _, user := range common.Userdict["pop3"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, POP3Credential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["pop3"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描,但需要限制速率
result := concurrentPOP3Scan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
savePOP3Result(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("POP3扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentPOP3Scan 并发扫描POP3服务包含速率限制
func concurrentPOP3Scan(ctx context.Context, info *common.HostInfo, credentials []POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
// 不使用ModuleThreadNum控制并发数必须单线程
maxConcurrent := 1
if maxConcurrent <= 0 {
maxConcurrent = 1 // POP3默认并发更低
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *POP3ScanResult, 1)
// 创建限速通道,控制请求频率
// 每次发送前需要从中获取令牌,确保请求间隔
rateLimiter := make(chan struct{}, maxConcurrent)
// 初始填充令牌
for i := 0; i < maxConcurrent; i++ {
rateLimiter <- struct{}{}
}
// 使用动态的请求间隔
requestInterval := 1500 * time.Millisecond // 默认间隔1.5秒
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 创建任务队列
taskQueue := make(chan POP3Credential, len(credentials))
for _, cred := range credentials {
taskQueue <- cred
}
close(taskQueue)
// 记录已处理的凭据数
var processedCount int32
processedCountMutex := &sync.Mutex{}
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for credential := range taskQueue {
select {
case <-scanCtx.Done():
return
case <-rateLimiter:
// 获取令牌,可以发送请求
processedCountMutex.Lock()
processedCount++
currentCount := processedCount
processedCountMutex.Unlock()
common.LogDebug(fmt.Sprintf("[%d/%d] 工作线程 %d 尝试: %s:%s",
currentCount, len(credentials), workerID, credential.Username, credential.Password))
result := tryPOP3Credential(scanCtx, info, credential, timeoutSeconds, maxRetries)
// 尝试完成后添加延迟,然后归还令牌
time.Sleep(requestInterval)
// 未被取消的情况下归还令牌
select {
case <-scanCtx.Done():
// 如果已经取消,不再归还令牌
default:
rateLimiter <- struct{}{}
}
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}(i)
}
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("POP3并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryPOP3Credential 尝试单个POP3凭据
func tryPOP3Credential(ctx context.Context, info *common.HostInfo, credential POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &POP3ScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
// 重试间隔时间增加,避免触发服务器限制
retryDelay := time.Duration(retry*2000) * time.Millisecond
time.Sleep(retryDelay)
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, isTLS, err := POP3Conn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &POP3ScanResult{
Success: true,
Credential: credential,
IsTLS: isTLS,
}
}
lastErr = err
if err != nil {
// 处理特定错误情况
if strings.Contains(strings.ToLower(err.Error()), "too many connections") ||
strings.Contains(strings.ToLower(err.Error()), "connection refused") ||
strings.Contains(strings.ToLower(err.Error()), "timeout") {
// 服务器可能限制连接,增加等待时间
waitTime := time.Duration((retry+1)*3000) * time.Millisecond
common.LogDebug(fmt.Sprintf("服务器可能限制连接,等待 %v 后重试", waitTime))
time.Sleep(waitTime)
continue
}
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &POP3ScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// POP3Conn 尝试POP3连接
func POP3Conn(ctx context.Context, info *common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) {
timeout := time.Duration(common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 创建结果通道
resultChan := make(chan struct {
success bool
isTLS bool
err error
}, 1)
// 在协程中尝试连接,支持取消
go func() {
// 首先尝试普通连接
conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err == nil {
flag, authErr := tryPOP3Auth(conn, user, pass, timeout)
conn.Close()
if authErr == nil && flag {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
isTLS bool
err error
}{flag, false, nil}:
}
return
}
}
// 如果普通连接失败尝试TLS连接
select {
case <-ctx.Done():
return
default:
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
// 对于TLS连接暂时使用标准dialer
// TODO: 实现通过socks代理的TLS连接
tempDialer := &net.Dialer{Timeout: timeout}
tlsConn, tlsErr := tls.DialWithDialer(tempDialer, "tcp", addr, tlsConfig)
if tlsErr != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
isTLS bool
err error
}{false, false, fmt.Errorf("连接失败: %v", tlsErr)}:
}
return
}
defer tlsConn.Close()
flag, authErr := tryPOP3Auth(tlsConn, user, pass, timeout)
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
isTLS bool
err error
}{flag, true, authErr}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.isTLS, result.err
case <-ctx.Done():
return false, false, ctx.Err()
}
}
// tryPOP3Auth 尝试POP3认证
func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
reader := bufio.NewReader(conn)
// 设置较长的超时时间以适应一些较慢的服务器
conn.SetDeadline(time.Now().Add(timeout))
// 读取欢迎信息
response, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
}
// 检查是否有错误信息
if strings.Contains(strings.ToLower(response), "error") ||
strings.Contains(strings.ToLower(response), "too many") {
return false, fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response))
}
// 发送用户名前等待一小段时间
time.Sleep(300 * time.Millisecond)
// 发送用户名
conn.SetDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(fmt.Sprintf("USER %s\r\n", user)))
if err != nil {
return false, fmt.Errorf("发送用户名失败: %v", err)
}
// 读取用户名响应
conn.SetDeadline(time.Now().Add(timeout))
response, err = reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取用户名响应失败: %v", err)
}
if !strings.Contains(response, "+OK") {
return false, fmt.Errorf("用户名无效: %s", strings.TrimSpace(response))
}
// 发送密码前等待一小段时间
time.Sleep(300 * time.Millisecond)
// 发送密码
conn.SetDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", pass)))
if err != nil {
return false, fmt.Errorf("发送密码失败: %v", err)
}
// 读取密码响应
conn.SetDeadline(time.Now().Add(timeout))
response, err = reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取密码响应失败: %v", err)
}
if strings.Contains(response, "+OK") {
return true, nil
}
return false, fmt.Errorf("认证失败: %s", strings.TrimSpace(response))
}
// savePOP3Result 保存POP3扫描结果
func savePOP3Result(info *common.HostInfo, target string, result *POP3ScanResult) {
tlsStatus := ""
if result.IsTLS {
tlsStatus = " (TLS)"
}
successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v%s",
target, result.Credential.Username, result.Credential.Password, tlsStatus)
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "pop3",
"username": result.Credential.Username,
"password": result.Credential.Password,
"type": "weak-password",
"tls": result.IsTLS,
},
}
common.SaveResult(vulnResult)
}

View File

@ -1,320 +0,0 @@
package Plugins
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/lib/pq"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// PostgresProxyDialer 自定义dialer结构体
type PostgresProxyDialer struct {
timeout time.Duration
}
// Dial 实现pq.Dialer接口支持socks代理
func (d *PostgresProxyDialer) Dial(network, address string) (net.Conn, error) {
return common.WrapperTcpWithTimeout(network, address, d.timeout)
}
// DialTimeout 实现具有超时的连接
func (d *PostgresProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return common.WrapperTcpWithTimeout(network, address, timeout)
}
// PostgresCredential 表示一个PostgreSQL凭据
type PostgresCredential struct {
Username string
Password string
}
// PostgresScanResult 表示PostgreSQL扫描结果
type PostgresScanResult struct {
Success bool
Error error
Credential PostgresCredential
}
// PostgresScan 执行PostgreSQL服务扫描
func PostgresScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []PostgresCredential
for _, user := range common.Userdict["postgresql"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, PostgresCredential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["postgresql"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentPostgresScan(ctx, info, credentials, common.Timeout+10, common.MaxRetries)
if result != nil {
// 记录成功结果
savePostgresResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("PostgreSQL扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentPostgresScan 并发扫描PostgreSQL服务
func concurrentPostgresScan(ctx context.Context, info *common.HostInfo, credentials []PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *PostgresScanResult, 1)
workChan := make(chan PostgresCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryPostgresCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("PostgreSQL并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryPostgresCredential 尝试单个PostgreSQL凭据
func tryPostgresCredential(ctx context.Context, info *common.HostInfo, credential PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &PostgresScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建单个连接超时的上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := PostgresConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &PostgresScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &PostgresScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// PostgresConn 尝试PostgreSQL连接
func PostgresConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error) {
// 构造连接字符串
connStr := fmt.Sprintf(
"postgres://%v:%v@%v:%v/postgres?sslmode=disable&connect_timeout=%d",
user, pass, info.Host, info.Ports, common.Timeout/1000, // 转换为秒
)
// 检查是否需要使用socks代理
if common.Socks5Proxy != "" {
// 使用自定义dialer通过socks代理连接
dialer := &PostgresProxyDialer{
timeout: time.Duration(common.Timeout) * time.Millisecond,
}
// 使用pq.DialOpen通过自定义dialer建立连接
conn, err := pq.DialOpen(dialer, connStr)
if err != nil {
return false, err
}
defer conn.Close()
// 转换为sql.DB进行测试
db := sql.OpenDB(&postgresConnector{conn: conn})
defer db.Close()
// 使用上下文测试连接
err = db.PingContext(ctx)
if err != nil {
return false, err
}
// 简单查询测试权限
var version string
err = db.QueryRowContext(ctx, "SELECT version()").Scan(&version)
if err != nil {
return false, err
}
return true, nil
}
// 使用标准连接方式
db, err := sql.Open("postgres", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Millisecond)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 使用上下文测试连接
err = db.PingContext(ctx)
if err != nil {
return false, err
}
// 简单查询测试权限
var version string
err = db.QueryRowContext(ctx, "SELECT version()").Scan(&version)
if err != nil {
return false, err
}
return true, nil
}
// postgresConnector 封装driver.Conn为sql.driver.Connector
type postgresConnector struct {
conn driver.Conn
}
func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) {
return c.conn, nil
}
func (c *postgresConnector) Driver() driver.Driver {
return &pq.Driver{}
}
// savePostgresResult 保存PostgreSQL扫描结果
func savePostgresResult(info *common.HostInfo, target string, credential PostgresCredential) {
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "postgresql",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
common.SaveResult(vulnResult)
}

View File

@ -1,309 +0,0 @@
package Plugins
import (
"context"
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"net"
"strings"
"sync"
"time"
)
// RabbitMQCredential 表示一个RabbitMQ凭据
type RabbitMQCredential struct {
Username string
Password string
}
// RabbitMQScanResult 表示扫描结果
type RabbitMQScanResult struct {
Success bool
Error error
Credential RabbitMQCredential
ErrorMsg string // 保存详细的错误信息
}
// RabbitMQScan 执行 RabbitMQ 服务扫描
func RabbitMQScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 先测试默认账号 guest/guest
common.LogDebug("尝试默认账号 guest/guest")
defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"}
defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, common.Timeout, common.MaxRetries)
if defaultResult.Success {
saveRabbitMQResult(info, target, defaultResult.Credential)
return nil
} else if defaultResult.Error != nil {
// 打印默认账号的详细错误信息
common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg))
}
// 构建其他凭据列表
var credentials []RabbitMQCredential
for _, user := range common.Userdict["rabbitmq"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, RabbitMQCredential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["rabbitmq"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentRabbitMQScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
saveRabbitMQResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("RabbitMQ扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号
return nil
}
}
// concurrentRabbitMQScan 并发扫描RabbitMQ服务
func concurrentRabbitMQScan(ctx context.Context, info *common.HostInfo, credentials []RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *RabbitMQScanResult, 1)
workChan := make(chan RabbitMQCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryRabbitMQCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("RabbitMQ并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryRabbitMQCredential 尝试单个RabbitMQ凭据
func tryRabbitMQCredential(ctx context.Context, info *common.HostInfo, credential RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
var lastErr error
var errorMsg string
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &RabbitMQScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
ErrorMsg: "全局超时",
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err, detailErr := RabbitMQConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
return &RabbitMQScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
errorMsg = detailErr
// 打印详细的错误信息,包括所有原始错误信息
common.LogDebug(fmt.Sprintf("凭据 %s:%s 失败,错误详情: %s",
credential.Username, credential.Password, errorMsg))
if err != nil {
// 可以根据错误信息类型来决定是否需要重试
// 例如,如果错误是认证错误,则无需重试
if strings.Contains(errorMsg, "ACCESS_REFUSED") {
common.LogDebug("认证错误,无需重试")
break
}
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &RabbitMQScanResult{
Success: false,
Error: lastErr,
Credential: credential,
ErrorMsg: errorMsg,
}
}
// RabbitMQConn 尝试 RabbitMQ 连接
func RabbitMQConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error, string) {
host, port := info.Host, info.Ports
// 构造 AMQP URL
amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/", user, pass, host, port)
// 创建结果通道
resultChan := make(chan struct {
success bool
err error
detailErr string
}, 1)
// 在协程中尝试连接
go func() {
// 配置连接
config := amqp.Config{
Dial: func(network, addr string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: time.Duration(common.Timeout) * time.Second}
return dialer.DialContext(ctx, network, addr)
},
}
// 尝试连接
conn, err := amqp.DialConfig(amqpURL, config)
if err != nil {
detailErr := err.Error()
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
detailErr string
}{false, err, detailErr}:
}
return
}
defer conn.Close()
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
detailErr string
}{true, nil, ""}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.err, result.detailErr
case <-ctx.Done():
return false, ctx.Err(), ctx.Err().Error()
}
}
// saveRabbitMQResult 保存RabbitMQ扫描结果
func saveRabbitMQResult(info *common.HostInfo, target string, credential RabbitMQCredential) {
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v",
target, credential.Username, credential.Password)
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "rabbitmq",
"username": credential.Username,
"password": credential.Password,
"type": "weak-password",
},
}
common.SaveResult(vulnResult)
}

View File

@ -1,19 +0,0 @@
package Plugins
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/adapter"
)
// RedisScan 执行Redis服务扫描
// 现在完全使用新的插件架构
func RedisScan(info *common.HostInfo) error {
// 使用新的插件架构
if adapter.TryNewArchitecture("redis", info) {
return nil // 新架构处理成功
}
// 如果新架构不支持,记录错误(理论上不应该发生)
common.LogError("Redis插件新架构不可用请检查插件注册")
return nil
}

View File

@ -1,477 +0,0 @@
package Plugins
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// RsyncCredential 表示一个Rsync凭据
type RsyncCredential struct {
Username string
Password string
}
// RsyncScanResult 表示Rsync扫描结果
type RsyncScanResult struct {
Success bool
Error error
Credential RsyncCredential
IsAnonymous bool
ModuleName string
}
func RsyncScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 首先尝试匿名访问
common.LogDebug("尝试匿名访问...")
anonymousResult := tryRsyncCredential(ctx, info, RsyncCredential{"", ""}, common.Timeout, common.MaxRetries)
if anonymousResult.Success {
// 匿名访问成功
saveRsyncResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []RsyncCredential
for _, user := range common.Userdict["rsync"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, RsyncCredential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["rsync"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentRsyncScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 保存成功结果
saveRsyncResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("Rsync扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// concurrentRsyncScan 并发扫描Rsync服务
func concurrentRsyncScan(ctx context.Context, info *common.HostInfo, credentials []RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *RsyncScanResult, 1)
workChan := make(chan RsyncCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryRsyncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("Rsync并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryRsyncCredential 尝试单个Rsync凭据
func tryRsyncCredential(ctx context.Context, info *common.HostInfo, credential RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &RsyncScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, moduleName, err := RsyncConn(connCtx, info, credential.Username, credential.Password)
cancel()
if success {
isAnonymous := credential.Username == "" && credential.Password == ""
return &RsyncScanResult{
Success: true,
Credential: credential,
IsAnonymous: isAnonymous,
ModuleName: moduleName,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &RsyncScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// RsyncConn 尝试Rsync连接
func RsyncConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, string, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(common.Timeout) * time.Second
// 建立连接
conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil {
return false, "", err
}
defer conn.Close()
// 创建结果通道用于超时控制
resultChan := make(chan struct {
success bool
moduleName string
err error
}, 1)
// 在协程中处理连接,以支持上下文取消
go func() {
buffer := make([]byte, 1024)
// 1. 读取服务器初始greeting
conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buffer)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{false, "", err}:
}
return
}
greeting := string(buffer[:n])
if !strings.HasPrefix(greeting, "@RSYNCD:") {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{false, "", fmt.Errorf("不是Rsync服务")}:
}
return
}
// 获取服务器版本号
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 2. 回应相同的版本号
conn.SetWriteDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{false, "", err}:
}
return
}
// 3. 选择模块 - 先列出可用模块
conn.SetWriteDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte("#list\n"))
if err != nil {
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{false, "", err}:
}
return
}
// 4. 读取模块列表
var moduleList strings.Builder
for {
// 检查上下文是否取消
select {
case <-ctx.Done():
return
default:
}
conn.SetReadDeadline(time.Now().Add(timeout))
n, err = conn.Read(buffer)
if err != nil {
break
}
chunk := string(buffer[:n])
moduleList.WriteString(chunk)
if strings.Contains(chunk, "@RSYNCD: EXIT") {
break
}
}
modules := strings.Split(moduleList.String(), "\n")
for _, module := range modules {
if strings.HasPrefix(module, "@RSYNCD") || module == "" {
continue
}
// 获取模块名
moduleName := strings.Fields(module)[0]
// 检查上下文是否取消
select {
case <-ctx.Done():
return
default:
}
// 5. 为每个模块创建新连接尝试认证
authConn, err := common.WrapperTcpWithTimeout(host, port, timeout)
if err != nil {
continue
}
defer authConn.Close() // 重复初始握手
authConn.SetReadDeadline(time.Now().Add(timeout))
_, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
authConn.Close()
continue
}
// 6. 选择模块
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(moduleName + "\n"))
if err != nil {
authConn.Close()
continue
}
// 7. 等待认证挑战
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
authResponse := string(buffer[:n])
if strings.Contains(authResponse, "@RSYNCD: OK") {
// 模块不需要认证
if user == "" && pass == "" {
authConn.Close()
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{true, moduleName, nil}:
}
return
}
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
if user != "" && pass != "" {
// 8. 发送认证信息
authString := fmt.Sprintf("%s %s\n", user, pass)
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(authString))
if err != nil {
authConn.Close()
continue
}
// 9. 读取认证结果
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
if !strings.Contains(string(buffer[:n]), "@ERROR") {
authConn.Close()
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{true, moduleName, nil}:
}
return
}
}
}
authConn.Close()
}
// 如果执行到这里,没有找到成功的认证
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
moduleName string
err error
}{false, "", fmt.Errorf("认证失败或无可用模块")}:
}
}()
// 等待结果或上下文取消
select {
case result := <-resultChan:
return result.success, result.moduleName, result.err
case <-ctx.Done():
return false, "", ctx.Err()
}
}
// saveRsyncResult 保存Rsync扫描结果
func saveRsyncResult(info *common.HostInfo, target string, result *RsyncScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("Rsync服务 %s 匿名访问成功 模块: %s", target, result.ModuleName)
details = map[string]interface{}{
"port": info.Ports,
"service": "rsync",
"type": "anonymous-access",
"module": result.ModuleName,
}
} else {
successMsg = fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v 模块: %s",
target, result.Credential.Username, result.Credential.Password, result.ModuleName)
details = map[string]interface{}{
"port": info.Ports,
"service": "rsync",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
"module": result.ModuleName,
}
}
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
common.SaveResult(vulnResult)
}

View File

@ -1,326 +0,0 @@
package Plugins
import (
"context"
"fmt"
"net/smtp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// SmtpCredential 表示一个SMTP凭据
type SmtpCredential struct {
Username string
Password string
}
// SmtpScanResult 表示SMTP扫描结果
type SmtpScanResult struct {
Success bool
Error error
Credential SmtpCredential
IsAnonymous bool
}
// SmtpScan 执行 SMTP 服务扫描
func SmtpScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 先测试匿名访问
common.LogDebug("尝试匿名访问...")
anonymousResult := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, common.Timeout, common.MaxRetries)
if anonymousResult.Success {
// 匿名访问成功
saveSmtpResult(info, target, anonymousResult)
return nil
}
// 构建凭据列表
var credentials []SmtpCredential
for _, user := range common.Userdict["smtp"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, SmtpCredential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["smtp"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentSmtpScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
saveSmtpResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("SMTP扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
return nil
}
}
// concurrentSmtpScan 并发扫描SMTP服务
func concurrentSmtpScan(ctx context.Context, info *common.HostInfo, credentials []SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *SmtpScanResult, 1)
workChan := make(chan SmtpCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := trySmtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("SMTP并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// trySmtpCredential 尝试单个SMTP凭据
func trySmtpCredential(ctx context.Context, info *common.HostInfo, credential SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &SmtpScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
// 在协程中尝试连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := SmtpConn(info, credential.Username, credential.Password, timeoutSeconds)
select {
case <-connCtx.Done():
case resultChan <- struct {
success bool
err error
}{success, err}:
}
}()
// 等待结果或超时
var success bool
var err error
select {
case result := <-resultChan:
success = result.success
err = result.err
case <-connCtx.Done():
cancel()
if ctx.Err() != nil {
// 全局超时
return &SmtpScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
cancel() // 释放连接上下文
if success {
isAnonymous := credential.Username == "" && credential.Password == ""
return &SmtpScanResult{
Success: true,
Credential: credential,
IsAnonymous: isAnonymous,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &SmtpScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// SmtpConn 尝试 SMTP 连接
func SmtpConn(info *common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(timeoutSeconds) * time.Second
addr := fmt.Sprintf("%s:%s", host, port)
// 设置连接超时
conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err != nil {
return false, err
}
defer conn.Close()
// 设置读写超时
conn.SetDeadline(time.Now().Add(timeout))
client, err := smtp.NewClient(conn, host)
if err != nil {
return false, err
}
defer client.Close()
// 尝试认证
if user != "" {
auth := smtp.PlainAuth("", user, pass, host)
err = client.Auth(auth)
if err != nil {
return false, err
}
}
// 尝试发送邮件(测试权限)
err = client.Mail("test@test.com")
if err != nil {
return false, err
}
return true, nil
}
// saveSmtpResult 保存SMTP扫描结果
func saveSmtpResult(info *common.HostInfo, target string, result *SmtpScanResult) {
var successMsg string
var details map[string]interface{}
if result.IsAnonymous {
successMsg = fmt.Sprintf("SMTP服务 %s 允许匿名访问", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "smtp",
"type": "anonymous-access",
"anonymous": true,
}
} else {
successMsg = fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "smtp",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
}
}
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
common.SaveResult(vulnResult)
}

View File

@ -1,145 +0,0 @@
package Plugins
import (
"fmt"
"github.com/gosnmp/gosnmp"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"strconv"
"strings"
"time"
)
// SNMPScan 执行SNMP服务扫描
func SNMPScan(info *common.HostInfo) (tmperr error) {
if common.DisableBrute {
return
}
maxRetries := common.MaxRetries
portNum, _ := strconv.Atoi(info.Ports)
defaultCommunities := []string{"public", "private", "cisco", "community"}
timeout := time.Duration(common.Timeout) * time.Second
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
common.LogDebug(fmt.Sprintf("尝试默认 community 列表 (总数: %d)", len(defaultCommunities)))
tried := 0
total := len(defaultCommunities)
for _, community := range defaultCommunities {
tried++
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试 community: %s", tried, total, community))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: community: %s", retryCount+1, community))
}
done := make(chan struct {
success bool
sysDesc string
err error
}, 1)
go func(community string) {
success, sysDesc, err := SNMPConnect(info, community, portNum)
select {
case done <- struct {
success bool
sysDesc string
err error
}{success, sysDesc, err}:
default:
}
}(community)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("SNMP服务 %s community: %v 连接成功", target, community)
if result.sysDesc != "" {
successMsg += fmt.Sprintf(" System: %v", result.sysDesc)
}
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "snmp",
"community": community,
"type": "weak-community",
"system": result.sysDesc,
},
}
common.SaveResult(vulnResult)
return nil
}
case <-time.After(timeout):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("SNMP服务 %s 尝试失败 community: %v 错误: %v",
target, community, err)
common.LogError(errlog)
if retryErr := common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个 community", tried))
return tmperr
}
// SNMPConnect 尝试SNMP连接
func SNMPConnect(info *common.HostInfo, community string, portNum int) (bool, string, error) {
host := info.Host
timeout := time.Duration(common.Timeout) * time.Second
snmp := &gosnmp.GoSNMP{
Target: host,
Port: uint16(portNum),
Community: community,
Version: gosnmp.Version2c,
Timeout: timeout,
Retries: 1,
}
err := snmp.Connect()
if err != nil {
return false, "", err
}
defer snmp.Conn.Close()
oids := []string{"1.3.6.1.2.1.1.1.0"}
result, err := snmp.Get(oids)
if err != nil {
return false, "", err
}
if len(result.Variables) > 0 {
var sysDesc string
if result.Variables[0].Type != gosnmp.NoSuchObject {
sysDesc = strings.TrimSpace(string(result.Variables[0].Value.([]byte)))
}
return true, sysDesc, nil
}
return false, "", fmt.Errorf("认证失败")
}

View File

@ -1,19 +0,0 @@
package Plugins
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/adapter"
)
// SshScan 扫描SSH服务弱密码
// 现在完全使用新的插件架构
func SshScan(info *common.HostInfo) error {
// 使用新的插件架构
if adapter.TryNewArchitecture("ssh", info) {
return nil // 新架构处理成功
}
// 如果新架构不支持,记录错误(理论上不应该发生)
common.LogError("SSH插件新架构不可用请检查插件注册")
return nil
}

View File

@ -1,274 +0,0 @@
package Plugins
import (
"context"
"fmt"
"sync"
"time"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// VncCredential 表示VNC凭据
type VncCredential struct {
Password string
}
// VncScanResult 表示VNC扫描结果
type VncScanResult struct {
Success bool
Error error
Credential VncCredential
}
func VncScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 构建密码列表
var credentials []VncCredential
for _, pass := range common.Passwords {
credentials = append(credentials, VncCredential{Password: pass})
}
common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials)))
// 使用工作池并发扫描
result := concurrentVncScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
saveVncResult(info, target, result.Credential)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("VNC扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", len(credentials)))
return nil
}
}
// concurrentVncScan 并发扫描VNC服务
func concurrentVncScan(ctx context.Context, info *common.HostInfo, credentials []VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *VncScanResult, 1)
workChan := make(chan VncCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryVncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("VNC并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryVncCredential 尝试单个VNC凭据
func tryVncCredential(ctx context.Context, info *common.HostInfo, credential VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &VncScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
success, err := VncConn(connCtx, info, credential.Password)
cancel()
if success {
return &VncScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &VncScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// VncConn 尝试建立VNC连接
func VncConn(ctx context.Context, info *common.HostInfo, pass string) (bool, error) {
Host, Port := info.Host, info.Ports
timeout := time.Duration(common.Timeout) * time.Second
// 使用带上下文的TCP连接
conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), timeout)
if err != nil {
return false, err
}
defer conn.Close()
// 设置读写超时
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
return false, err
}
// 创建完成通道
doneChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中处理VNC认证
go func() {
// 配置VNC客户端
config := &vnc.ClientConfig{
Auth: []vnc.ClientAuth{
&vnc.PasswordAuth{
Password: pass,
},
},
}
// 尝试VNC认证
client, err := vnc.Client(conn, config)
if err != nil {
select {
case <-ctx.Done():
case doneChan <- struct {
success bool
err error
}{false, err}:
}
return
}
// 认证成功
defer client.Close()
select {
case <-ctx.Done():
case doneChan <- struct {
success bool
err error
}{true, nil}:
}
}()
// 等待认证结果或上下文取消
select {
case result := <-doneChan:
return result.success, result.err
case <-ctx.Done():
return false, ctx.Err()
}
}
// saveVncResult 保存VNC扫描结果
func saveVncResult(info *common.HostInfo, target string, credential VncCredential) {
successLog := fmt.Sprintf("vnc://%s 密码: %v", target, credential.Password)
common.LogSuccess(successLog)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "vnc",
"password": credential.Password,
"type": "weak-password",
},
}
common.SaveResult(vulnResult)
}

View File

@ -0,0 +1,181 @@
package adapters
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// LegacyPluginFunc 老版本插件的函数签名
type LegacyPluginFunc func(*common.HostInfo) error
// LegacyPlugin 老版本插件适配器
type LegacyPlugin struct {
metadata *base.PluginMetadata
legacyFunc LegacyPluginFunc
options *LegacyPluginOptions
}
// LegacyPluginOptions 老版本插件选项
type LegacyPluginOptions struct {
// 是否需要检查暴力破解开关
CheckBruteFlag bool
// 是否为漏洞检测类插件
IsVulnPlugin bool
// 是否为信息收集类插件
IsInfoPlugin bool
// 自定义端口如果不使用metadata中的端口
CustomPorts []int
}
// NewLegacyPlugin 创建老版本插件适配器
func NewLegacyPlugin(metadata *base.PluginMetadata, legacyFunc LegacyPluginFunc, options *LegacyPluginOptions) *LegacyPlugin {
if options == nil {
options = &LegacyPluginOptions{
CheckBruteFlag: true,
IsVulnPlugin: true,
}
}
return &LegacyPlugin{
metadata: metadata,
legacyFunc: legacyFunc,
options: options,
}
}
// GetMetadata 实现Plugin接口
func (p *LegacyPlugin) GetMetadata() *base.PluginMetadata {
return p.metadata
}
// GetName 实现Scanner接口
func (p *LegacyPlugin) GetName() string {
return p.metadata.Name
}
// Initialize 实现Plugin接口
func (p *LegacyPlugin) Initialize() error {
return nil
}
// Scan 实现Plugin接口 - 适配老版本插件调用
func (p *LegacyPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 检查上下文是否取消
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// 如果需要检查暴力破解标志且已禁用暴力破解
if p.options.CheckBruteFlag && common.DisableBrute {
if p.options.IsVulnPlugin {
// 漏洞检测类插件在禁用暴力破解时仍然运行
// 这里不返回,继续执行
} else {
// 非漏洞检测类插件在禁用暴力破解时跳过
return &base.ScanResult{
Success: false,
Service: p.metadata.Name,
Error: fmt.Errorf("brute force disabled"),
}, nil
}
}
// 调用老版本插件函数
err := p.legacyFunc(info)
if err != nil {
// 插件执行失败
return &base.ScanResult{
Success: false,
Service: p.metadata.Name,
Error: err,
}, nil
}
// 插件执行成功
// 老版本插件通常自己处理日志和结果输出,所以这里返回基本成功信息
return &base.ScanResult{
Success: true,
Service: p.metadata.Name,
Banner: fmt.Sprintf("%s scan completed", p.metadata.Name),
Extra: map[string]interface{}{
"plugin_type": "legacy",
"category": p.metadata.Category,
},
}, nil
}
// ScanCredential 实现Plugin接口 - 老版本插件不支持单独的凭据测试
func (p *LegacyPlugin) ScanCredential(ctx context.Context, info *common.HostInfo, cred *base.Credential) (*base.ScanResult, error) {
// 老版本插件通常内部处理凭据所以这里直接调用Scan
return p.Scan(ctx, info)
}
// =============================================================================
// Exploiter接口实现 - 老版本插件通常不支持独立的利用功能
// =============================================================================
// Exploit 实现Exploiter接口 - 老版本插件通常不支持单独利用
func (p *LegacyPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 老版本插件通常在Scan中完成所有操作不支持独立利用
return nil, fmt.Errorf("legacy plugin does not support separate exploitation")
}
// GetExploitMethods 实现Exploiter接口 - 返回空的利用方法列表
func (p *LegacyPlugin) GetExploitMethods() []base.ExploitMethod {
// 老版本插件不支持独立的利用方法
return []base.ExploitMethod{}
}
// IsExploitSupported 实现Exploiter接口 - 老版本插件不支持独立利用
func (p *LegacyPlugin) IsExploitSupported(method base.ExploitType) bool {
// 老版本插件不支持独立的利用方法
return false
}
// GetCapabilities 实现Plugin接口
func (p *LegacyPlugin) GetCapabilities() []base.Capability {
capabilities := []base.Capability{}
// 根据插件类型分配合适的能力
if p.options.IsVulnPlugin {
// 漏洞检测插件通常涉及信息泄露检测
capabilities = append(capabilities, base.CapInformationLeak)
}
if p.options.IsInfoPlugin {
// 信息收集插件
capabilities = append(capabilities, base.CapInformationLeak)
}
// 大多数老版本插件都支持弱密码检测
if p.options.CheckBruteFlag {
capabilities = append(capabilities, base.CapWeakPassword)
}
return capabilities
}
// SetCapabilities 实现Plugin接口 - 老版本插件不支持动态设置能力
func (p *LegacyPlugin) SetCapabilities(capabilities []base.Capability) {
// 老版本插件的能力是固定的,这里不做任何操作
}
// GetDefaultPorts 获取默认端口
func (p *LegacyPlugin) GetDefaultPorts() []int {
if len(p.options.CustomPorts) > 0 {
return p.options.CustomPorts
}
return p.metadata.Ports
}
// Cleanup 清理资源
func (p *LegacyPlugin) Cleanup() error {
// 老版本插件通常没有需要清理的资源
return nil
}

View File

@ -1,4 +1,4 @@
package adapter
package adapters
import (
"context"

127
Plugins/legacy/Base.go Normal file
View File

@ -0,0 +1,127 @@
package Plugins
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"net"
)
// ReadBytes 从连接读取数据直到EOF或错误
func ReadBytes(conn net.Conn) ([]byte, error) {
size := 4096 // 缓冲区大小
buf := make([]byte, size)
var result []byte
var lastErr error
// 循环读取数据
for {
count, err := conn.Read(buf)
if err != nil {
lastErr = err
break
}
result = append(result, buf[0:count]...)
// 如果读取的数据小于缓冲区,说明已经读完
if count < size {
break
}
}
// 如果读到了数据,则忽略错误
if len(result) > 0 {
return result, nil
}
return result, lastErr
}
// 默认AES加密密钥
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模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) {
// base64解码
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
if err != nil {
return "", fmt.Errorf("base64解码失败: %v", err)
}
keyBytes := []byte(key)
// 创建解密块
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建解密块失败: %v", err)
}
// 创建CBC解密模式
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
// 解密数据
origData := make([]byte, len(cryptedBytes))
blockMode.CryptBlocks(origData, cryptedBytes)
// 去除填充
origData, err = PKCS7UnPadding(origData)
if err != nil {
return "", fmt.Errorf("去除PKCS7填充失败: %v", err)
}
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填充
func PKCS7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("数据长度为0")
}
padding := int(data[length-1])
if padding > length {
return nil, errors.New("填充长度无效")
}
return data[:length-padding], nil
}

View File

@ -214,14 +214,14 @@ func read(text []byte, host string) error {
if len(ipv4Addrs) > 0 {
output.WriteString("\n IPv4地址:")
for _, addr := range ipv4Addrs {
output.WriteString(fmt.Sprintf("\n - %s", addr))
output.WriteString(fmt.Sprintf("\n └─ %s", addr))
}
}
if len(ipv6Addrs) > 0 {
output.WriteString("\n IPv6地址:")
for _, addr := range ipv6Addrs {
output.WriteString(fmt.Sprintf("\n - %s", addr))
output.WriteString(fmt.Sprintf("\n └─ %s", addr))
}
}

47
Plugins/legacy/README.md Normal file
View File

@ -0,0 +1,47 @@
# Legacy Plugins
这个目录包含了已迁移到新架构的老版本插件代码。这些插件通过适配器接入新的插件系统。
## 包含的插件
### 漏洞检测类
- **MS17010.go** - MS17010 SMB远程代码执行漏洞检测 (EternalBlue)
- **MS17010-Exp.go** - MS17010漏洞利用模块
- **SmbGhost.go** - CVE-2020-0796 SMBGhost远程代码执行漏洞检测
### 服务检测类
- **SMB.go** - SMB服务弱密码检测和共享枚举
- **SMB2.go** - SMB2服务弱密码检测 (支持NTLM哈希)
- **RDP.go** - RDP远程桌面服务弱密码检测
- **Elasticsearch.go** - Elasticsearch弱密码检测和未授权访问检测
### 信息收集类
- **NetBIOS.go** - NetBIOS信息收集和主机名解析
### 工具模块
- **Base.go** - 通用工具函数 (ReadBytes, AES加密解密, PKCS7填充等)
## 使用方式
这些插件通过适配器自动集成到新的插件系统中:
```
plugins/legacy/ # 老版本插件代码 (此目录)
adapters/legacy_plugins/ # 适配器实现
plugins/base/ # 新架构插件系统
```
## 维护说明
- 这些插件代码保持不变,确保兼容性
- 所有功能通过 `adapters/legacy_plugins/` 中的适配器访问
- 用户使用时与新插件完全相同,无需关心底层实现
## 注意事项
- 本地信息收集插件 (DCInfo, FindNet, LocalInfo, MiniDump)
- Web相关插件 (WebPoc, WebTitle)
这些插件暂未包含在此目录中,后续会进行单独整理。

View File

@ -0,0 +1,54 @@
package elasticsearch
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewElasticsearchPlugin 创建Elasticsearch弱密码检测插件
func NewElasticsearchPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "elasticsearch",
Version: "1.0.0",
Author: "fscan-team",
Description: "Elasticsearch搜索引擎弱密码检测和未授权访问检测",
Category: "service",
Ports: []int{9200, 9300}, // Elasticsearch端口
Protocols: []string{"tcp"},
Tags: []string{"elasticsearch", "weak-password", "unauthorized", "service"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: true, // Elasticsearch依赖暴力破解标志
IsVulnPlugin: false, // 这是服务检测插件,虽然包含安全检查
IsInfoPlugin: true, // 包含信息收集功能
CustomPorts: []int{9200, 9300}, // Elasticsearch端口
}
// 创建适配器直接使用老版本的ElasticScan函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.ElasticScan, options)
}
// init 自动注册Elasticsearch插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "elasticsearch",
Version: "1.0.0",
Author: "fscan-team",
Description: "Elasticsearch搜索引擎弱密码检测和未授权访问检测",
Category: "service",
Ports: []int{9200, 9300},
Protocols: []string{"tcp"},
Tags: []string{"elasticsearch", "weak-password", "unauthorized", "service"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewElasticsearchPlugin()
})
base.GlobalPluginRegistry.Register("elasticsearch", factory)
}

View File

@ -0,0 +1,54 @@
package findnet
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewFindNetPlugin 创建FindNet网络发现插件
func NewFindNetPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "findnet",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows网络接口发现和主机名解析 (通过RPC)",
Category: "service",
Ports: []int{135}, // RPC端口
Protocols: []string{"tcp"},
Tags: []string{"findnet", "rpc", "information-gathering", "windows", "network-discovery"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // FindNet不依赖暴力破解标志
IsVulnPlugin: false, // 这不是漏洞检测插件
IsInfoPlugin: true, // 这是信息收集插件
CustomPorts: []int{135}, // RPC端口
}
// 创建适配器直接使用老版本的Findnet函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.Findnet, options)
}
// init 自动注册FindNet插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "findnet",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows网络接口发现和主机名解析 (通过RPC)",
Category: "service",
Ports: []int{135},
Protocols: []string{"tcp"},
Tags: []string{"findnet", "rpc", "information-gathering", "windows", "network-discovery"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewFindNetPlugin()
})
base.GlobalPluginRegistry.Register("findnet", factory)
}

3
Plugins/legacy/init.go Normal file
View File

@ -0,0 +1,3 @@
// Package Plugins contains legacy plugin implementations that are adapted to new architecture
// These plugins are accessed through adapters to maintain compatibility
package Plugins

View File

@ -0,0 +1,54 @@
package ms17010
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewMS17010Plugin 创建MS17010漏洞检测插件
func NewMS17010Plugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "ms17010",
Version: "1.0.0",
Author: "fscan-team",
Description: "MS17010 SMB远程代码执行漏洞检测 (EternalBlue)",
Category: "service",
Ports: []int{445}, // SMB端口
Protocols: []string{"tcp"},
Tags: []string{"smb", "ms17010", "eternalblue", "vulnerability"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // MS17010不依赖暴力破解标志
IsVulnPlugin: true, // 这是漏洞检测插件
IsInfoPlugin: false,
CustomPorts: []int{445}, // 固定使用SMB端口
}
// 创建适配器直接使用老版本的MS17010函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.MS17010, options)
}
// init 自动注册MS17010插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "ms17010",
Version: "1.0.0",
Author: "fscan-team",
Description: "MS17010 SMB远程代码执行漏洞检测 (EternalBlue)",
Category: "service",
Ports: []int{445},
Protocols: []string{"tcp"},
Tags: []string{"smb", "ms17010", "eternalblue", "vulnerability"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewMS17010Plugin()
})
base.GlobalPluginRegistry.Register("ms17010", factory)
}

View File

@ -0,0 +1,54 @@
package netbios
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewNetBiosPlugin 创建NetBIOS信息收集插件
func NewNetBiosPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "netbios",
Version: "1.0.0",
Author: "fscan-team",
Description: "NetBIOS信息收集和主机名解析",
Category: "service",
Ports: []int{139, 445}, // NetBIOS端口
Protocols: []string{"tcp", "udp"},
Tags: []string{"netbios", "information-gathering", "hostname", "smb"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // NetBIOS信息收集不依赖暴力破解标志
IsVulnPlugin: false, // 这不是漏洞检测插件
IsInfoPlugin: true, // 这是信息收集插件
CustomPorts: []int{139, 445}, // NetBIOS/SMB端口
}
// 创建适配器直接使用老版本的NetBIOS函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.NetBIOS, options)
}
// init 自动注册NetBIOS插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "netbios",
Version: "1.0.0",
Author: "fscan-team",
Description: "NetBIOS信息收集和主机名解析",
Category: "service",
Ports: []int{139, 445},
Protocols: []string{"tcp", "udp"},
Tags: []string{"netbios", "information-gathering", "hostname", "smb"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewNetBiosPlugin()
})
base.GlobalPluginRegistry.Register("netbios", factory)
}

View File

@ -0,0 +1,54 @@
package rdp
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewRdpPlugin 创建RDP弱密码检测插件
func NewRdpPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "rdp",
Version: "1.0.0",
Author: "fscan-team",
Description: "RDP远程桌面服务弱密码检测",
Category: "service",
Ports: []int{3389}, // RDP端口
Protocols: []string{"tcp"},
Tags: []string{"rdp", "remote-desktop", "weak-password", "service", "brute-force"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: true, // RDP依赖暴力破解标志
IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测
IsInfoPlugin: false, // 主要是弱密码检测
CustomPorts: []int{3389}, // RDP端口
}
// 创建适配器直接使用老版本的RdpScan函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.RdpScan, options)
}
// init 自动注册RDP插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "rdp",
Version: "1.0.0",
Author: "fscan-team",
Description: "RDP远程桌面服务弱密码检测",
Category: "service",
Ports: []int{3389},
Protocols: []string{"tcp"},
Tags: []string{"rdp", "remote-desktop", "weak-password", "service", "brute-force"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewRdpPlugin()
})
base.GlobalPluginRegistry.Register("rdp", factory)
}

View File

@ -0,0 +1,54 @@
package smb
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewSmbPlugin 创建SMB弱密码检测插件
func NewSmbPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "smb",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMB服务弱密码检测和共享枚举",
Category: "service",
Ports: []int{445, 139}, // SMB端口
Protocols: []string{"tcp"},
Tags: []string{"smb", "weak-password", "service", "brute-force"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: true, // SMB依赖暴力破解标志
IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测
IsInfoPlugin: true, // 包含信息收集功能
CustomPorts: []int{445, 139}, // SMB端口
}
// 创建适配器直接使用老版本的SmbScan函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbScan, options)
}
// init 自动注册SMB插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "smb",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMB服务弱密码检测和共享枚举",
Category: "service",
Ports: []int{445, 139},
Protocols: []string{"tcp"},
Tags: []string{"smb", "weak-password", "service", "brute-force"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewSmbPlugin()
})
base.GlobalPluginRegistry.Register("smb", factory)
}

View File

@ -0,0 +1,54 @@
package smb2
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewSmb2Plugin 创建SMB2弱密码检测插件
func NewSmb2Plugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "smb2",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMB2服务弱密码检测和共享枚举 (支持NTLM哈希)",
Category: "service",
Ports: []int{445}, // SMB2端口
Protocols: []string{"tcp"},
Tags: []string{"smb2", "weak-password", "service", "brute-force", "ntlm"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: true, // SMB2依赖暴力破解标志
IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测
IsInfoPlugin: true, // 包含信息收集功能
CustomPorts: []int{445}, // SMB2端口
}
// 创建适配器直接使用老版本的SmbScan2函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbScan2, options)
}
// init 自动注册SMB2插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "smb2",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMB2服务弱密码检测和共享枚举 (支持NTLM哈希)",
Category: "service",
Ports: []int{445},
Protocols: []string{"tcp"},
Tags: []string{"smb2", "weak-password", "service", "brute-force", "ntlm"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewSmb2Plugin()
})
base.GlobalPluginRegistry.Register("smb2", factory)
}

View File

@ -0,0 +1,54 @@
package smbghost
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewSmbGhostPlugin 创建SMBGhost漏洞检测插件
func NewSmbGhostPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "smbghost",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMBGhost (CVE-2020-0796) 远程代码执行漏洞检测",
Category: "service",
Ports: []int{445}, // SMB端口
Protocols: []string{"tcp"},
Tags: []string{"smb", "smbghost", "cve-2020-0796", "vulnerability"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // SMBGhost不依赖暴力破解标志
IsVulnPlugin: true, // 这是漏洞检测插件
IsInfoPlugin: false,
CustomPorts: []int{445}, // 固定使用SMB端口
}
// 创建适配器直接使用老版本的SmbGhost函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbGhost, options)
}
// init 自动注册SmbGhost插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "smbghost",
Version: "1.0.0",
Author: "fscan-team",
Description: "SMBGhost (CVE-2020-0796) 远程代码执行漏洞检测",
Category: "service",
Ports: []int{445},
Protocols: []string{"tcp"},
Tags: []string{"smb", "smbghost", "cve-2020-0796", "vulnerability"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewSmbGhostPlugin()
})
base.GlobalPluginRegistry.Register("smbghost", factory)
}

View File

@ -0,0 +1,54 @@
package webpoc
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewWebPocPlugin 创建WebPoc漏洞扫描插件
func NewWebPocPlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "webpoc",
Version: "1.0.0",
Author: "fscan-team",
Description: "Web应用漏洞POC扫描检测",
Category: "web",
Ports: []int{}, // Web插件不限制端口支持任意端口的URL
Protocols: []string{"http", "https"},
Tags: []string{"web", "poc", "vulnerability", "exploit"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // WebPoc不依赖暴力破解标志
IsVulnPlugin: true, // 这是漏洞检测插件
IsInfoPlugin: false,
CustomPorts: []int{}, // Web插件不限制端口支持任意端口的URL
}
// 创建适配器使用老版本的WebPoc函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.WebPoc, options)
}
// init 自动注册WebPoc插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "webpoc",
Version: "1.0.0",
Author: "fscan-team",
Description: "Web应用漏洞POC扫描检测",
Category: "web",
Ports: []int{}, // Web插件不限制端口支持任意端口的URL
Protocols: []string{"http", "https"},
Tags: []string{"web", "poc", "vulnerability", "exploit"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewWebPocPlugin()
})
base.GlobalPluginRegistry.Register("webpoc", factory)
}

View File

@ -0,0 +1,54 @@
package webtitle
import (
"github.com/shadow1ng/fscan/plugins/adapters"
"github.com/shadow1ng/fscan/plugins/base"
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
)
// NewWebTitlePlugin 创建WebTitle网站标题获取插件
func NewWebTitlePlugin() base.Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "webtitle",
Version: "1.0.0",
Author: "fscan-team",
Description: "Web网站标题和指纹识别扫描",
Category: "web",
Ports: []int{}, // Web插件不限制端口支持任意端口的URL
Protocols: []string{"http", "https"},
Tags: []string{"web", "title", "fingerprint", "information-gathering"},
}
// 适配器选项
options := &adapters.LegacyPluginOptions{
CheckBruteFlag: false, // WebTitle不依赖暴力破解标志
IsVulnPlugin: false, // 这不是漏洞检测插件
IsInfoPlugin: true, // 这是信息收集插件
CustomPorts: []int{}, // Web插件不限制端口支持任意端口的URL
}
// 创建适配器使用老版本的WebTitle函数
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.WebTitle, options)
}
// init 自动注册WebTitle插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "webtitle",
Version: "1.0.0",
Author: "fscan-team",
Description: "Web网站标题和指纹识别扫描",
Category: "web",
Ports: []int{}, // Web插件不限制端口支持任意端口的URL
Protocols: []string{"http", "https"},
Tags: []string{"web", "title", "fingerprint", "information-gathering"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewWebTitlePlugin()
})
base.GlobalPluginRegistry.Register("webtitle", factory)
}

View File

@ -0,0 +1,124 @@
package ldap
import (
"context"
"fmt"
"strings"
ldaplib "github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// LDAPConnection LDAP连接包装器
type LDAPConnection struct {
client *ldaplib.Conn
target string
}
// LDAPConnector LDAP连接器实现
type LDAPConnector struct {
host string
port string
}
// NewLDAPConnector 创建LDAP连接器
func NewLDAPConnector() *LDAPConnector {
return &LDAPConnector{}
}
// Connect 连接到LDAP服务
func (c *LDAPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
c.host = info.Host
c.port = info.Ports
target := fmt.Sprintf("%s:%s", c.host, c.port)
// 使用Context控制的TCP连接
conn, err := common.WrapperTcpWithContext(ctx, "tcp", target)
if err != nil {
return nil, fmt.Errorf("LDAP连接失败: %v", err)
}
// 创建LDAP连接
ldapConn := ldaplib.NewConn(conn, false)
go ldapConn.Start() // 在goroutine中启动连接处理
return &LDAPConnection{
client: ldapConn,
target: target,
}, nil
}
// Authenticate 认证
func (c *LDAPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
ldapConn, ok := conn.(*LDAPConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 在goroutine中进行认证操作支持Context取消
resultChan := make(chan error, 1)
go func() {
var err error
if cred.Username == "" && cred.Password == "" {
// 匿名绑定
err = ldapConn.client.UnauthenticatedBind("")
} else {
// 构建绑定DN - 支持多种常见格式优先管理员DN
bindDNs := []string{
fmt.Sprintf("cn=%s,dc=example,dc=com", cred.Username), // 管理员绑定格式
fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", cred.Username), // 用户绑定格式
fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", cred.Username),
fmt.Sprintf("uid=%s,dc=example,dc=com", cred.Username),
cred.Username, // 直接使用用户名作为DN
}
// 尝试不同的绑定DN格式
var bindErr error
for _, bindDN := range bindDNs {
bindErr = ldapConn.client.Bind(bindDN, cred.Password)
if bindErr == nil {
break
}
}
err = bindErr
}
// 绑定成功即表示认证成功,不需要额外搜索验证
// 因为某些LDAP配置下普通用户没有搜索权限
select {
case resultChan <- err:
case <-ctx.Done():
}
}()
// 等待认证结果或Context取消
select {
case err := <-resultChan:
if err != nil {
// 检查是否是认证失败
if strings.Contains(strings.ToLower(err.Error()), "bind") ||
strings.Contains(strings.ToLower(err.Error()), "authentication") ||
strings.Contains(strings.ToLower(err.Error()), "invalid credentials") ||
strings.Contains(strings.ToLower(err.Error()), "49") { // LDAP错误码49表示认证失败
return fmt.Errorf("LDAP认证失败")
}
return fmt.Errorf("LDAP操作失败: %v", err)
}
return nil
case <-ctx.Done():
return fmt.Errorf("LDAP认证超时: %v", ctx.Err())
}
}
// Close 关闭连接
func (c *LDAPConnector) Close(conn interface{}) error {
if ldapConn, ok := conn.(*LDAPConnection); ok && ldapConn.client != nil {
ldapConn.client.Close()
}
return nil
}

View File

@ -0,0 +1,36 @@
package ldap
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// LDAPExploiter LDAP利用器实现 - 最小化版本,不提供利用功能
type LDAPExploiter struct {
*base.BaseExploiter
}
// NewLDAPExploiter 创建LDAP利用器
func NewLDAPExploiter() *LDAPExploiter {
exploiter := &LDAPExploiter{
BaseExploiter: base.NewBaseExploiter("ldap"),
}
// LDAP插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *LDAPExploiter) setupExploitMethods() {
// LDAP插件不提供利用功能仅进行弱密码扫描和匿名访问检测
}
// Exploit 利用接口实现 - 空实现
func (e *LDAPExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// LDAP插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,222 @@
package ldap
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// LDAPPlugin LDAP插件实现
type LDAPPlugin struct {
*base.ServicePlugin
exploiter *LDAPExploiter
}
// NewLDAPPlugin 创建LDAP插件
func NewLDAPPlugin() *LDAPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "ldap",
Version: "2.0.0",
Author: "fscan-team",
Description: "LDAP轻量级目录访问协议扫描和利用插件",
Category: "service",
Ports: []int{389, 636, 3268, 3269}, // 389: LDAP, 636: LDAPS, 3268/3269: Global Catalog
Protocols: []string{"tcp"},
Tags: []string{"ldap", "directory", "bruteforce", "anonymous"},
}
// 创建连接器和服务插件
connector := NewLDAPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建LDAP插件
plugin := &LDAPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewLDAPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法,先检测匿名访问
func (p *LDAPPlugin) 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)
// 先尝试匿名访问
anonymousCred := &base.Credential{Username: "", Password: ""}
anonymousResult, err := p.ScanCredential(ctx, info, anonymousCred)
if err == nil && anonymousResult.Success {
// 匿名访问成功
common.LogSuccess(i18n.GetText("ldap_anonymous_access", target))
return &base.ScanResult{
Success: true,
Service: "LDAP",
Credentials: []*base.Credential{anonymousCred},
Extra: map[string]interface{}{
"service": "LDAP",
"port": info.Ports,
"unauthorized": true,
"access_type": "anonymous",
},
}, nil
}
// 执行基础的密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
// 记录成功的弱密码发现
cred := result.Credentials[0]
common.LogSuccess(i18n.GetText("ldap_weak_pwd_success", target, cred.Username, cred.Password))
return result, nil
}
// generateCredentials 重写凭据生成方法
func (p *LDAPPlugin) generateCredentials() []*base.Credential {
// 获取LDAP专用的用户名字典
usernames := common.Userdict["ldap"]
if len(usernames) == 0 {
// 默认LDAP用户名
usernames = []string{"admin", "administrator", "ldap", "root", "manager", "directory", "test", "user"}
}
return base.GenerateCredentials(usernames, common.Passwords)
}
// Exploit 使用exploiter执行利用
func (p *LDAPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *LDAPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *LDAPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行LDAP服务识别-nobr模式
func (p *LDAPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接LDAP服务获取基本信息
ldapInfo, isLDAP := p.identifyLDAPService(ctx, info)
if isLDAP {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("ldap_service_identified", target, ldapInfo))
return &base.ScanResult{
Success: true,
Service: "LDAP",
Banner: ldapInfo,
Extra: map[string]interface{}{
"service": "LDAP",
"port": info.Ports,
"info": ldapInfo,
},
}, nil
}
// 如果无法识别为LDAP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为LDAP服务"),
}, nil
}
// identifyLDAPService 通过连接识别LDAP服务
func (p *LDAPPlugin) identifyLDAPService(ctx context.Context, info *common.HostInfo) (string, bool) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试建立TCP连接
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return "", false
}
defer conn.Close()
// 尝试创建LDAP连接并获取基本信息
connector := NewLDAPConnector()
// 使用context包装TCP连接
ldapConn, err := connector.Connect(ctx, info)
if err != nil {
return "", false
}
defer connector.Close(ldapConn)
// 尝试匿名绑定以确认LDAP服务
anonymousCred := &base.Credential{Username: "", Password: ""}
err = connector.Authenticate(ctx, ldapConn, anonymousCred)
if err != nil {
// 检查错误是否表明这是LDAP服务但需要认证
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "ldap") ||
strings.Contains(errStr, "bind") ||
strings.Contains(errStr, "authentication") ||
strings.Contains(errStr, "49") { // LDAP认证失败错误码
return fmt.Sprintf("LDAP服务 (需要认证): %v", err), true
}
return "", false
}
// 匿名绑定成功这是LDAP服务
return "LDAP服务 (支持匿名访问)", true
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterLDAPPlugin 注册LDAP插件
func RegisterLDAPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "ldap",
Version: "2.0.0",
Author: "fscan-team",
Description: "LDAP轻量级目录访问协议扫描和利用插件",
Category: "service",
Ports: []int{389, 636, 3268, 3269}, // 389: LDAP, 636: LDAPS, 3268/3269: Global Catalog
Protocols: []string{"tcp"},
Tags: []string{"ldap", "directory", "bruteforce", "anonymous"},
},
func() base.Plugin {
return NewLDAPPlugin()
},
)
base.GlobalPluginRegistry.Register("ldap", factory)
}
// 自动注册
func init() {
RegisterLDAPPlugin()
}

View File

@ -0,0 +1,109 @@
package memcached
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MemcachedConnection Memcached连接包装器
type MemcachedConnection struct {
client net.Conn
target string
}
// MemcachedConnector Memcached连接器实现
type MemcachedConnector struct {
host string
port string
}
// NewMemcachedConnector 创建Memcached连接器
func NewMemcachedConnector() *MemcachedConnector {
return &MemcachedConnector{}
}
// Connect 连接到Memcached服务
func (c *MemcachedConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
c.host = info.Host
c.port = info.Ports
target := fmt.Sprintf("%s:%s", c.host, c.port)
timeout := time.Duration(common.Timeout) * time.Second
// 建立TCP连接
client, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf("Memcached连接失败: %v", err)
}
return &MemcachedConnection{
client: client,
target: target,
}, nil
}
// Authenticate 认证 - Memcached通常无认证检查未授权访问
func (c *MemcachedConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
memcachedConn, ok := conn.(*MemcachedConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 在goroutine中进行操作支持Context取消
resultChan := make(chan error, 1)
go func() {
// 设置操作超时
timeout := time.Duration(common.Timeout) * time.Second
if err := memcachedConn.client.SetDeadline(time.Now().Add(timeout)); err != nil {
resultChan <- fmt.Errorf("设置超时失败: %v", err)
return
}
// 发送stats命令测试连接
if _, err := memcachedConn.client.Write([]byte("stats\n")); err != nil {
resultChan <- fmt.Errorf("发送命令失败: %v", err)
return
}
// 读取响应
buffer := make([]byte, 1024)
n, err := memcachedConn.client.Read(buffer)
if err != nil {
resultChan <- fmt.Errorf("读取响应失败: %v", err)
return
}
// 检查响应是否包含Memcached统计信息
response := string(buffer[:n])
if strings.Contains(response, "STAT") {
// 未授权访问成功
resultChan <- nil
return
}
resultChan <- fmt.Errorf("Memcached服务无响应或不可访问")
}()
// 等待操作结果或Context取消
select {
case err := <-resultChan:
return err
case <-ctx.Done():
return fmt.Errorf("Memcached操作超时: %v", ctx.Err())
}
}
// Close 关闭连接
func (c *MemcachedConnector) Close(conn interface{}) error {
if memcachedConn, ok := conn.(*MemcachedConnection); ok && memcachedConn.client != nil {
return memcachedConn.client.Close()
}
return nil
}

View File

@ -0,0 +1,36 @@
package memcached
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MemcachedExploiter Memcached利用器实现 - 最小化版本,不提供利用功能
type MemcachedExploiter struct {
*base.BaseExploiter
}
// NewMemcachedExploiter 创建Memcached利用器
func NewMemcachedExploiter() *MemcachedExploiter {
exploiter := &MemcachedExploiter{
BaseExploiter: base.NewBaseExploiter("memcached"),
}
// Memcached插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *MemcachedExploiter) setupExploitMethods() {
// Memcached插件不提供利用功能仅进行未授权访问检测
}
// Exploit 利用接口实现 - 空实现
func (e *MemcachedExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// Memcached插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,229 @@
package memcached
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// MemcachedPlugin Memcached插件实现
type MemcachedPlugin struct {
*base.ServicePlugin
exploiter *MemcachedExploiter
}
// NewMemcachedPlugin 创建Memcached插件
func NewMemcachedPlugin() *MemcachedPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "memcached",
Version: "2.0.0",
Author: "fscan-team",
Description: "Memcached分布式内存缓存系统扫描和利用插件",
Category: "service",
Ports: []int{11211}, // 默认Memcached端口
Protocols: []string{"tcp"},
Tags: []string{"memcached", "cache", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewMemcachedConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Memcached插件
plugin := &MemcachedPlugin{
ServicePlugin: servicePlugin,
exploiter: NewMemcachedExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapUnauthorized,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法,检测未授权访问
func (p *MemcachedPlugin) 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)
// Memcached通常无认证直接检查未授权访问
unauthCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, unauthCred)
if err == nil && result.Success {
// 未授权访问成功
common.LogSuccess(i18n.GetText("memcached_unauth_access", target))
return &base.ScanResult{
Success: true,
Service: "Memcached",
Credentials: []*base.Credential{unauthCred},
Extra: map[string]interface{}{
"service": "Memcached",
"port": info.Ports,
"unauthorized": true,
"access_type": "no_authentication",
},
}, nil
}
// 如果未授权访问失败,返回失败结果
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("Memcached服务不可访问或需要认证"),
}, nil
}
// generateCredentials Memcached通常无需凭据返回空凭据
func (p *MemcachedPlugin) generateCredentials() []*base.Credential {
// Memcached通常无认证机制只返回空凭据用于未授权访问检测
return []*base.Credential{
{Username: "", Password: ""},
}
}
// Exploit 使用exploiter执行利用
func (p *MemcachedPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *MemcachedPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *MemcachedPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Memcached服务识别-nobr模式
func (p *MemcachedPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接Memcached服务获取基本信息
memcachedInfo, isMemcached := p.identifyMemcachedService(ctx, info)
if isMemcached {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("memcached_service_identified", target, memcachedInfo))
return &base.ScanResult{
Success: true,
Service: "Memcached",
Banner: memcachedInfo,
Extra: map[string]interface{}{
"service": "Memcached",
"port": info.Ports,
"info": memcachedInfo,
},
}, nil
}
// 如果无法识别为Memcached返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Memcached服务"),
}, nil
}
// identifyMemcachedService 通过连接识别Memcached服务
func (p *MemcachedPlugin) identifyMemcachedService(ctx context.Context, info *common.HostInfo) (string, bool) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return "", false
}
defer conn.Close()
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
// 发送version命令获取版本信息
if _, err := conn.Write([]byte("version\n")); err != nil {
return "", false
}
// 读取响应
buffer := make([]byte, 512)
n, err := conn.Read(buffer)
if err != nil {
return "", false
}
response := strings.TrimSpace(string(buffer[:n]))
// 检查是否是Memcached版本响应
if strings.HasPrefix(response, "VERSION") {
// 提取版本信息
parts := strings.Fields(response)
if len(parts) >= 2 {
return fmt.Sprintf("Memcached %s", parts[1]), true
}
return "Memcached服务", true
}
// 尝试stats命令进行二次确认
if _, err := conn.Write([]byte("stats\n")); err != nil {
return "", false
}
n, err = conn.Read(buffer)
if err != nil {
return "", false
}
response = string(buffer[:n])
// 检查是否包含STAT关键字
if strings.Contains(response, "STAT") {
return "Memcached服务", true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterMemcachedPlugin 注册Memcached插件
func RegisterMemcachedPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "memcached",
Version: "2.0.0",
Author: "fscan-team",
Description: "Memcached分布式内存缓存系统扫描和利用插件",
Category: "service",
Ports: []int{11211}, // 默认Memcached端口
Protocols: []string{"tcp"},
Tags: []string{"memcached", "cache", "unauthorized"},
},
func() base.Plugin {
return NewMemcachedPlugin()
},
)
base.GlobalPluginRegistry.Register("memcached", factory)
}
// 自动注册
func init() {
RegisterMemcachedPlugin()
}

View File

@ -0,0 +1,160 @@
package modbus
import (
"context"
"encoding/binary"
"fmt"
"net"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// ModbusConnection Modbus连接包装器
type ModbusConnection struct {
client net.Conn
target string
deviceID uint8
}
// ModbusConnector Modbus连接器实现
type ModbusConnector struct {
host string
port string
}
// NewModbusConnector 创建Modbus连接器
func NewModbusConnector() *ModbusConnector {
return &ModbusConnector{}
}
// Connect 连接到Modbus服务
func (c *ModbusConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
c.host = info.Host
c.port = info.Ports
target := fmt.Sprintf("%s:%s", c.host, c.port)
timeout := time.Duration(common.Timeout) * time.Second
// 建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf("Modbus连接失败: %v", err)
}
return &ModbusConnection{
client: conn,
target: target,
}, nil
}
// Authenticate 认证 - Modbus通常无认证检查协议响应
func (c *ModbusConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
modbusConn, ok := conn.(*ModbusConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 在goroutine中进行操作支持Context取消
resultChan := make(chan error, 1)
go func() {
// 设置操作超时
timeout := time.Duration(common.Timeout) * time.Second
if err := modbusConn.client.SetDeadline(time.Now().Add(timeout)); err != nil {
resultChan <- fmt.Errorf("设置超时失败: %v", err)
return
}
// 构建Modbus TCP请求包 - 读取设备ID
request := buildModbusReadCoilsRequest()
// 发送请求
if _, err := modbusConn.client.Write(request); err != nil {
resultChan <- fmt.Errorf("发送Modbus请求失败: %v", err)
return
}
// 读取响应
response := make([]byte, 256)
n, err := modbusConn.client.Read(response)
if err != nil {
resultChan <- fmt.Errorf("读取Modbus响应失败: %v", err)
return
}
// 验证是否为有效Modbus响应
if isValidModbusResponse(response[:n]) {
// 提取设备ID
if len(response) >= 7 {
modbusConn.deviceID = response[6] // Unit ID
}
resultChan <- nil
return
}
resultChan <- fmt.Errorf("非Modbus服务或访问被拒绝")
}()
// 等待操作结果或Context取消
select {
case err := <-resultChan:
return err
case <-ctx.Done():
return fmt.Errorf("Modbus操作超时: %v", ctx.Err())
}
}
// Close 关闭连接
func (c *ModbusConnector) Close(conn interface{}) error {
if modbusConn, ok := conn.(*ModbusConnection); ok && modbusConn.client != nil {
return modbusConn.client.Close()
}
return nil
}
// buildModbusReadCoilsRequest 构建Modbus TCP读取线圈请求包
func buildModbusReadCoilsRequest() []byte {
request := make([]byte, 12)
// Modbus TCP头部
binary.BigEndian.PutUint16(request[0:], 0x0001) // 事务标识符
binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符
binary.BigEndian.PutUint16(request[4:], 0x0006) // 长度
request[6] = 0x01 // 单元标识符
// Modbus 功能码和数据
request[7] = 0x01 // 功能码: Read Coils
binary.BigEndian.PutUint16(request[8:], 0x0000) // 起始地址
binary.BigEndian.PutUint16(request[10:], 0x0001) // 读取数量
return request
}
// isValidModbusResponse 验证Modbus响应是否有效
func isValidModbusResponse(response []byte) bool {
if len(response) < 8 {
return false
}
// 检查协议标识符 (应为0)
protocolID := binary.BigEndian.Uint16(response[2:])
if protocolID != 0 {
return false
}
// 检查功能码是否为错误响应
funcCode := response[7]
if funcCode >= 0x80 { // 错误响应的功能码都大于等于0x80
return false
}
// 基本长度检查
length := binary.BigEndian.Uint16(response[4:])
if int(length) != len(response)-6 {
return false
}
return true
}

View File

@ -0,0 +1,36 @@
package modbus
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// ModbusExploiter Modbus利用器实现 - 最小化版本,不提供利用功能
type ModbusExploiter struct {
*base.BaseExploiter
}
// NewModbusExploiter 创建Modbus利用器
func NewModbusExploiter() *ModbusExploiter {
exploiter := &ModbusExploiter{
BaseExploiter: base.NewBaseExploiter("modbus"),
}
// Modbus插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *ModbusExploiter) setupExploitMethods() {
// Modbus插件不提供利用功能仅进行协议识别和未授权访问检测
}
// Exploit 利用接口实现 - 空实现
func (e *ModbusExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// Modbus插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,297 @@
package modbus
import (
"context"
"encoding/binary"
"fmt"
"net"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// ModbusPlugin Modbus插件实现
type ModbusPlugin struct {
*base.ServicePlugin
exploiter *ModbusExploiter
}
// NewModbusPlugin 创建Modbus插件
func NewModbusPlugin() *ModbusPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "modbus",
Version: "2.0.0",
Author: "fscan-team",
Description: "Modbus工业协议扫描和利用插件",
Category: "service",
Ports: []int{502, 5020}, // 502: 标准Modbus TCP端口, 5020: 测试端口
Protocols: []string{"tcp"},
Tags: []string{"modbus", "industrial", "scada", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewModbusConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Modbus插件
plugin := &ModbusPlugin{
ServicePlugin: servicePlugin,
exploiter: NewModbusExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapUnauthorized,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法检测Modbus协议和未授权访问
func (p *ModbusPlugin) 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)
// Modbus协议通常无认证直接检查协议响应
unauthCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, unauthCred)
if err == nil && result.Success {
// 获取设备信息
deviceInfo := p.getDeviceInfo(ctx, info)
// 未授权访问成功
common.LogSuccess(i18n.GetText("modbus_unauth_access", target))
if deviceInfo != "" {
common.LogSuccess(i18n.GetText("modbus_device_info", deviceInfo))
}
return &base.ScanResult{
Success: true,
Service: "Modbus",
Credentials: []*base.Credential{unauthCred},
Extra: map[string]interface{}{
"service": "Modbus",
"port": info.Ports,
"unauthorized": true,
"access_type": "no_authentication",
"device_info": deviceInfo,
},
}, nil
}
// 如果Modbus协议检测失败返回失败结果
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("非Modbus服务或连接失败"),
}, nil
}
// generateCredentials Modbus通常无需凭据返回空凭据
func (p *ModbusPlugin) generateCredentials() []*base.Credential {
// Modbus协议通常无认证机制只返回空凭据用于协议检测
return []*base.Credential{
{Username: "", Password: ""},
}
}
// getDeviceInfo 获取Modbus设备信息
func (p *ModbusPlugin) getDeviceInfo(ctx context.Context, info *common.HostInfo) string {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试建立连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return ""
}
defer conn.Close()
// 设置超时
conn.SetDeadline(time.Now().Add(timeout))
// 发送读取设备标识请求 (功能码0x11)
request := buildModbusDeviceIdentifyRequest()
if _, err := conn.Write(request); err != nil {
return ""
}
// 读取响应
response := make([]byte, 256)
n, err := conn.Read(response)
if err != nil {
return ""
}
return parseDeviceInfo(response[:n])
}
// buildModbusDeviceIdentifyRequest 构建Modbus设备标识请求
func buildModbusDeviceIdentifyRequest() []byte {
request := make([]byte, 8)
// Modbus TCP头部
binary.BigEndian.PutUint16(request[0:], 0x0002) // 事务标识符
binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符
binary.BigEndian.PutUint16(request[4:], 0x0002) // 长度
request[6] = 0x01 // 单元标识符
request[7] = 0x11 // 功能码: Report Server ID
return request
}
// parseDeviceInfo 解析设备信息
func parseDeviceInfo(response []byte) string {
if len(response) < 8 {
return "Unknown Device"
}
// 检查是否为有效响应
if !isValidModbusResponse(response) {
return "Unknown Device"
}
unitID := response[6]
funcCode := response[7]
info := fmt.Sprintf("Unit ID: %d, Function: 0x%02X", unitID, funcCode)
// 如果是设备标识响应,尝试解析设备信息
if funcCode == 0x11 && len(response) > 9 {
byteCount := response[8]
if byteCount > 0 && len(response) >= 9+int(byteCount) {
// 提取设备ID信息
deviceData := response[9 : 9+int(byteCount)]
if len(deviceData) > 0 {
info += fmt.Sprintf(", Device Data: %x", deviceData)
}
}
} else if funcCode == 0x01 && len(response) >= 10 {
// 读取线圈响应,显示线圈状态
byteCount := response[8]
if byteCount > 0 && len(response) >= 9+int(byteCount) {
coilValue := response[9] & 0x01
info += fmt.Sprintf(", Coil Status: %d", coilValue)
}
}
return info
}
// Exploit 使用exploiter执行利用
func (p *ModbusPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *ModbusPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *ModbusPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Modbus服务识别-nobr模式
func (p *ModbusPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别Modbus服务
modbusInfo, isModbus := p.identifyModbusService(ctx, info)
if isModbus {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("modbus_service_identified", target, modbusInfo))
return &base.ScanResult{
Success: true,
Service: "Modbus",
Banner: modbusInfo,
Extra: map[string]interface{}{
"service": "Modbus",
"port": info.Ports,
"info": modbusInfo,
},
}, nil
}
// 如果无法识别为Modbus返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Modbus服务"),
}, nil
}
// identifyModbusService 通过协议识别Modbus服务
func (p *ModbusPlugin) identifyModbusService(ctx context.Context, info *common.HostInfo) (string, bool) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return "", false
}
defer conn.Close()
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
// 发送Modbus读取线圈请求
request := buildModbusReadCoilsRequest()
if _, err := conn.Write(request); err != nil {
return "", false
}
// 读取响应
response := make([]byte, 256)
n, err := conn.Read(response)
if err != nil {
return "", false
}
// 检查是否为有效Modbus响应
if isValidModbusResponse(response[:n]) {
deviceInfo := parseDeviceInfo(response[:n])
return fmt.Sprintf("Modbus TCP服务: %s", deviceInfo), true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterModbusPlugin 注册Modbus插件
func RegisterModbusPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "modbus",
Version: "2.0.0",
Author: "fscan-team",
Description: "Modbus工业协议扫描和利用插件",
Category: "service",
Ports: []int{502, 5020}, // 502: 标准Modbus TCP端口, 5020: 测试端口
Protocols: []string{"tcp"},
Tags: []string{"modbus", "industrial", "scada", "unauthorized"},
},
func() base.Plugin {
return NewModbusPlugin()
},
)
base.GlobalPluginRegistry.Register("modbus", factory)
}
// 自动注册
func init() {
RegisterModbusPlugin()
}

View File

@ -0,0 +1,194 @@
package mongodb
import (
"context"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MongoDBConnection MongoDB连接包装器
type MongoDBConnection struct {
client net.Conn
target string
}
// MongoDBConnector MongoDB连接器实现
type MongoDBConnector struct {
host string
port string
}
// NewMongoDBConnector 创建MongoDB连接器
func NewMongoDBConnector() *MongoDBConnector {
return &MongoDBConnector{}
}
// Connect 连接到MongoDB服务
func (c *MongoDBConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
c.host = info.Host
c.port = info.Ports
target := fmt.Sprintf("%s:%s", c.host, c.port)
timeout := time.Duration(common.Timeout) * time.Second
// 建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf("MongoDB连接失败: %v", err)
}
return &MongoDBConnection{
client: conn,
target: target,
}, nil
}
// Authenticate 认证 - MongoDB通常检查未授权访问
func (c *MongoDBConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
mongoConn, ok := conn.(*MongoDBConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 在goroutine中进行操作支持Context取消
resultChan := make(chan error, 1)
go func() {
// 设置操作超时
timeout := time.Duration(common.Timeout) * time.Second
if err := mongoConn.client.SetDeadline(time.Now().Add(timeout)); err != nil {
resultChan <- fmt.Errorf("设置超时失败: %v", err)
return
}
// 尝试未授权访问检测
isUnauth, err := c.checkMongoDBUnauth(ctx, mongoConn)
if err != nil {
resultChan <- fmt.Errorf("MongoDB检测失败: %v", err)
return
}
if isUnauth {
// 未授权访问成功
resultChan <- nil
return
}
// 如果没有未授权访问,尝试使用提供的凭据(如果有的话)
if cred.Username != "" && cred.Password != "" {
// TODO: 实现MongoDB认证
resultChan <- fmt.Errorf("MongoDB需要认证但暂不支持密码认证")
return
}
resultChan <- fmt.Errorf("MongoDB服务需要认证")
}()
// 等待操作结果或Context取消
select {
case err := <-resultChan:
return err
case <-ctx.Done():
return fmt.Errorf("MongoDB操作超时: %v", ctx.Err())
}
}
// checkMongoDBUnauth 检测MongoDB未授权访问
func (c *MongoDBConnector) checkMongoDBUnauth(ctx context.Context, mongoConn *MongoDBConnection) (bool, error) {
// 先尝试OP_MSG查询
msgPacket := createOpMsgPacket()
reply, err := c.sendMongoQuery(ctx, mongoConn, msgPacket)
if err != nil {
// 失败则尝试OP_QUERY查询
queryPacket := createOpQueryPacket()
reply, err = c.sendMongoQuery(ctx, mongoConn, queryPacket)
if err != nil {
return false, err
}
}
// 检查响应结果
if strings.Contains(reply, "totalLinesWritten") {
return true, nil
}
return false, nil
}
// sendMongoQuery 发送MongoDB查询
func (c *MongoDBConnector) sendMongoQuery(ctx context.Context, mongoConn *MongoDBConnection, packet []byte) (string, error) {
// 检查上下文是否已取消
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// 发送查询包
if _, err := mongoConn.client.Write(packet); err != nil {
return "", fmt.Errorf("发送查询失败: %v", err)
}
// 再次检查上下文是否已取消
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// 读取响应
reply := make([]byte, 2048)
count, err := mongoConn.client.Read(reply)
if err != nil && err != io.EOF {
return "", fmt.Errorf("读取响应失败: %v", err)
}
if count == 0 {
return "", fmt.Errorf("收到空响应")
}
return string(reply[:count]), nil
}
// Close 关闭连接
func (c *MongoDBConnector) Close(conn interface{}) error {
if mongoConn, ok := conn.(*MongoDBConnection); ok && mongoConn.client != nil {
return mongoConn.client.Close()
}
return nil
}
// createOpMsgPacket 创建OP_MSG查询包
func createOpMsgPacket() []byte {
return []byte{
0x69, 0x00, 0x00, 0x00, // messageLength
0x39, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG
0x00, 0x00, 0x00, 0x00, // flagBits
// sections db.adminCommand({getLog: "startupWarnings"})
0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00,
}
}
// createOpQueryPacket 创建OP_QUERY查询包
func createOpQueryPacket() []byte {
return []byte{
0x48, 0x00, 0x00, 0x00, // messageLength
0x02, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY
0x00, 0x00, 0x00, 0x00, // flags
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd
0x00, 0x00, 0x00, 0x00, // numberToSkip
0x01, 0x00, 0x00, 0x00, // numberToReturn
// query db.adminCommand({getLog: "startupWarnings"})
0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00,
}
}

View File

@ -0,0 +1,36 @@
package mongodb
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MongoDBExploiter MongoDB利用器实现 - 最小化版本,不提供利用功能
type MongoDBExploiter struct {
*base.BaseExploiter
}
// NewMongoDBExploiter 创建MongoDB利用器
func NewMongoDBExploiter() *MongoDBExploiter {
exploiter := &MongoDBExploiter{
BaseExploiter: base.NewBaseExploiter("mongodb"),
}
// MongoDB插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *MongoDBExploiter) setupExploitMethods() {
// MongoDB插件不提供利用功能仅进行未授权访问检测
}
// Exploit 利用接口实现 - 空实现
func (e *MongoDBExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// MongoDB插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,247 @@
package mongodb
import (
"context"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// MongoDBPlugin MongoDB插件实现
type MongoDBPlugin struct {
*base.ServicePlugin
exploiter *MongoDBExploiter
}
// NewMongoDBPlugin 创建MongoDB插件
func NewMongoDBPlugin() *MongoDBPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "mongodb",
Version: "2.0.0",
Author: "fscan-team",
Description: "MongoDB NoSQL数据库扫描和利用插件",
Category: "service",
Ports: []int{27017, 27018, 27019}, // 默认MongoDB端口
Protocols: []string{"tcp"},
Tags: []string{"mongodb", "nosql", "database", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewMongoDBConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建MongoDB插件
plugin := &MongoDBPlugin{
ServicePlugin: servicePlugin,
exploiter: NewMongoDBExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapUnauthorized,
base.CapDataExtraction,
base.CapWeakPassword, // 将来可能支持弱密码扫描
})
return plugin
}
// Scan 重写扫描方法,检测未授权访问
func (p *MongoDBPlugin) 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)
// MongoDB主要检查未授权访问
unauthCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, unauthCred)
if err == nil && result.Success {
// 未授权访问成功
common.LogSuccess(i18n.GetText("mongodb_unauth_access", target))
return &base.ScanResult{
Success: true,
Service: "MongoDB",
Credentials: []*base.Credential{unauthCred},
Extra: map[string]interface{}{
"service": "MongoDB",
"port": info.Ports,
"unauthorized": true,
"access_type": "no_authentication",
},
}, nil
}
// 如果未授权访问失败,返回失败结果
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("MongoDB服务需要认证或连接失败"),
}, nil
}
// generateCredentials MongoDB主要用于未授权访问检测
func (p *MongoDBPlugin) generateCredentials() []*base.Credential {
// MongoDB主要检查未授权访问但也可以尝试一些默认凭据
credentials := []*base.Credential{
{Username: "", Password: ""}, // 未授权访问
}
// 如果有MongoDB专用字典添加常见凭据
usernames := common.Userdict["mongodb"]
if len(usernames) == 0 {
usernames = []string{"admin", "root", "mongo", "mongodb"}
}
// 添加一些常见的MongoDB凭据
for _, username := range usernames {
credentials = append(credentials, &base.Credential{
Username: username,
Password: "", // MongoDB常见空密码
})
credentials = append(credentials, &base.Credential{
Username: username,
Password: username, // 用户名等于密码
})
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *MongoDBPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *MongoDBPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *MongoDBPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行MongoDB服务识别-nobr模式
func (p *MongoDBPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别MongoDB服务
mongoInfo, isMongo := p.identifyMongoDBService(ctx, info)
if isMongo {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("mongodb_service_identified", target, mongoInfo))
return &base.ScanResult{
Success: true,
Service: "MongoDB",
Banner: mongoInfo,
Extra: map[string]interface{}{
"service": "MongoDB",
"port": info.Ports,
"info": mongoInfo,
},
}, nil
}
// 如果无法识别为MongoDB返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为MongoDB服务"),
}, nil
}
// identifyMongoDBService 通过协议识别MongoDB服务
func (p *MongoDBPlugin) identifyMongoDBService(ctx context.Context, info *common.HostInfo) (string, bool) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return "", false
}
defer conn.Close()
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
// 尝试发送MongoDB查询来识别服务
msgPacket := createOpMsgPacket()
if _, err := conn.Write(msgPacket); err != nil {
return "", false
}
// 读取响应
reply := make([]byte, 1024)
n, err := conn.Read(reply)
if err != nil && err != io.EOF {
// 尝试OP_QUERY查询
queryPacket := createOpQueryPacket()
if _, err := conn.Write(queryPacket); err != nil {
return "", false
}
n, err = conn.Read(reply)
if err != nil && err != io.EOF {
return "", false
}
}
if n > 0 {
response := string(reply[:n])
// 检查是否包含MongoDB相关内容
if strings.Contains(response, "totalLinesWritten") ||
strings.Contains(response, "MongoDB") ||
strings.Contains(response, "WiredTiger") ||
strings.Contains(response, "unauthorized") {
// 尝试提取版本信息
if strings.Contains(response, "MongoDB") {
return "MongoDB服务 (已识别协议响应)", true
}
return "MongoDB服务", true
}
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterMongoDBPlugin 注册MongoDB插件
func RegisterMongoDBPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "mongodb",
Version: "2.0.0",
Author: "fscan-team",
Description: "MongoDB NoSQL数据库扫描和利用插件",
Category: "service",
Ports: []int{27017, 27018, 27019}, // 默认MongoDB端口
Protocols: []string{"tcp"},
Tags: []string{"mongodb", "nosql", "database", "unauthorized"},
},
func() base.Plugin {
return NewMongoDBPlugin()
},
)
base.GlobalPluginRegistry.Register("mongodb", factory)
}
// 自动注册
func init() {
RegisterMongoDBPlugin()
}

View File

@ -0,0 +1,210 @@
package mssql
import (
"context"
"database/sql"
"fmt"
"net"
"strings"
"time"
mssqlDriver "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MSSQLProxyDialer 自定义MSSQL代理拨号器
type MSSQLProxyDialer struct {
timeout time.Duration
}
// DialContext 实现mssql.Dialer接口支持socks代理
func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return common.WrapperTcpWithContext(ctx, network, addr)
}
// MSSQLConnection MSSQL连接包装器
type MSSQLConnection struct {
db *sql.DB
target string
info string
}
// MSSQLConnector MSSQL连接器实现
type MSSQLConnector struct{}
// NewMSSQLConnector 创建MSSQL连接器
func NewMSSQLConnector() *MSSQLConnector {
return &MSSQLConnector{}
}
// Connect 连接到MSSQL服务器不进行认证
func (c *MSSQLConnector) 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
// 尝试建立连接但不进行认证,使用空凭据进行连接尝试
db, dbInfo, err := c.createConnection(ctx, info.Host, info.Ports, "", "", timeout)
if err != nil {
// 检查是否是MSSQL服务相关错误
if c.isMSSQLError(err) {
// 即使连接失败但可以识别为MSSQL服务
return &MSSQLConnection{
db: nil,
target: target,
info: "Microsoft SQL Server (Service Detected)",
}, nil
}
return nil, err
}
return &MSSQLConnection{
db: db,
target: target,
info: dbInfo,
}, nil
}
// Authenticate 使用凭据进行认证
func (c *MSSQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
mssqlConn, ok := conn.(*MSSQLConnection)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 解析目标地址
parts := strings.Split(mssqlConn.target, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid target format")
}
host := parts[0]
port := parts[1]
timeout := time.Duration(common.Timeout) * time.Second
// 使用提供的凭据创建新连接
db, info, err := c.createConnection(ctx, host, port, cred.Username, cred.Password, timeout)
if err != nil {
return err
}
// 更新连接信息
if mssqlConn.db != nil {
mssqlConn.db.Close()
}
mssqlConn.db = db
mssqlConn.info = info
return nil
}
// Close 关闭连接
func (c *MSSQLConnector) Close(conn interface{}) error {
if mssqlConn, ok := conn.(*MSSQLConnection); ok && mssqlConn.db != nil {
return mssqlConn.db.Close()
}
return nil
}
// createConnection 创建MSSQL数据库连接
func (c *MSSQLConnector) createConnection(ctx context.Context, host, port, username, password string, timeout time.Duration) (*sql.DB, string, error) {
// 构造连接字符串
connStr := fmt.Sprintf(
"server=%s;user id=%s;password=%s;port=%s;encrypt=disable;timeout=%d",
host, username, password, port, int(timeout.Seconds()),
)
var db *sql.DB
var err error
// 检查是否需要使用socks代理
if common.Socks5Proxy != "" {
connector, connErr := mssqlDriver.NewConnector(connStr)
if connErr != nil {
return nil, "", connErr
}
connector.Dialer = &MSSQLProxyDialer{
timeout: timeout,
}
db = sql.OpenDB(connector)
} else {
db, err = sql.Open("mssql", connStr)
if err != nil {
return nil, "", err
}
}
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 创建ping上下文
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
defer pingCancel()
// 执行ping测试连接
err = db.PingContext(pingCtx)
if err != nil {
db.Close()
return nil, "", err
}
// 获取数据库信息
info := c.getDatabaseInfo(db)
return db, info, nil
}
// getDatabaseInfo 获取数据库版本信息
func (c *MSSQLConnector) getDatabaseInfo(db *sql.DB) string {
query := "SELECT @@VERSION"
var version string
err := db.QueryRow(query).Scan(&version)
if err != nil {
return "Microsoft SQL Server"
}
// 提取版本信息的关键部分
if strings.Contains(version, "Microsoft SQL Server") {
lines := strings.Split(version, "\n")
if len(lines) > 0 {
return strings.TrimSpace(lines[0])
}
}
return fmt.Sprintf("Microsoft SQL Server - %s", version)
}
// isMSSQLError 检查是否是MSSQL相关错误
func (c *MSSQLConnector) isMSSQLError(err error) bool {
if err == nil {
return false
}
errorStr := strings.ToLower(err.Error())
mssqlErrorIndicators := []string{
"login failed",
"cannot open database",
"invalid object",
"mssql:",
"sql server",
"sqlserver:",
"database",
"authentication failed",
"server principal",
"user does not have permission",
"the login is from an untrusted domain",
}
for _, indicator := range mssqlErrorIndicators {
if strings.Contains(errorStr, indicator) {
return true
}
}
return false
}

View File

@ -0,0 +1,42 @@
package mssql
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MSSQLExploiter MSSQL利用器实现
type MSSQLExploiter struct{}
// NewMSSQLExploiter 创建MSSQL利用器
func NewMSSQLExploiter() *MSSQLExploiter {
return &MSSQLExploiter{}
}
// Exploit 执行MSSQL利用
func (e *MSSQLExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// MSSQL插件主要用于服务识别和认证测试不进行进一步利用
return &base.ExploitResult{
Success: false,
Error: fmt.Errorf("MSSQL插件不支持进一步利用"),
}, nil
}
// GetExploitMethods 获取支持的利用方法
func (e *MSSQLExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Name: "信息收集",
Type: base.ExploitDataExtraction,
Description: "收集MSSQL服务信息",
},
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *MSSQLExploiter) IsExploitSupported(method base.ExploitType) bool {
return method == base.ExploitDataExtraction
}

View File

@ -0,0 +1,199 @@
package mssql
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// MSSQLPlugin MSSQL插件实现
type MSSQLPlugin struct {
*base.ServicePlugin
exploiter *MSSQLExploiter
}
// NewMSSQLPlugin 创建MSSQL插件
func NewMSSQLPlugin() *MSSQLPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "mssql",
Version: "2.0.0",
Author: "fscan-team",
Description: "Microsoft SQL Server扫描和利用插件",
Category: "service",
Ports: []int{1433, 1434}, // 默认MSSQL端口
Protocols: []string{"tcp"},
Tags: []string{"mssql", "sqlserver", "database", "weak-password"},
}
// 创建连接器和服务插件
connector := NewMSSQLConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建MSSQL插件
plugin := &MSSQLPlugin{
ServicePlugin: servicePlugin,
exploiter: NewMSSQLExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法进行MSSQL服务扫描
func (p *MSSQLPlugin) 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()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("mssql_auth_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "Microsoft SQL Server",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Microsoft SQL Server",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
},
}, nil
}
}
// 所有凭据都失败但可能识别到了MSSQL服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成MSSQL凭据
func (p *MSSQLPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取MSSQL用户名字典
usernames := common.Userdict["mssql"]
if len(usernames) == 0 {
usernames = []string{"sa", "admin", "administrator", "root", "mssql"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "sa", "admin", "password", "123456", "root"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *MSSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *MSSQLPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *MSSQLPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行MSSQL服务识别-nobr模式
func (p *MSSQLPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别MSSQL服务
connector := NewMSSQLConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if mssqlConn, ok := conn.(*MSSQLConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("mssql_service_identified", target, mssqlConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "Microsoft SQL Server",
Banner: mssqlConn.info,
Extra: map[string]interface{}{
"service": "Microsoft SQL Server",
"port": info.Ports,
"info": mssqlConn.info,
},
}, nil
}
}
// 如果无法识别为MSSQL返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为MSSQL服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterMSSQLPlugin 注册MSSQL插件
func RegisterMSSQLPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "mssql",
Version: "2.0.0",
Author: "fscan-team",
Description: "Microsoft SQL Server扫描和利用插件",
Category: "service",
Ports: []int{1433, 1434}, // 默认MSSQL端口
Protocols: []string{"tcp"},
Tags: []string{"mssql", "sqlserver", "database", "weak-password"},
},
func() base.Plugin {
return NewMSSQLPlugin()
},
)
base.GlobalPluginRegistry.Register("mssql", factory)
}
// 自动注册
func init() {
RegisterMSSQLPlugin()
}

View File

@ -0,0 +1,196 @@
package neo4j
import (
"context"
"fmt"
"strings"
"time"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// Neo4jConnection Neo4j连接包装器
type Neo4jConnection struct {
driver neo4j.Driver
target string
info string
isAuth bool
}
// Neo4jConnector Neo4j连接器实现
type Neo4jConnector struct{}
// NewNeo4jConnector 创建Neo4j连接器
func NewNeo4jConnector() *Neo4jConnector {
return &Neo4jConnector{}
}
// Connect 连接到Neo4j服务器不进行认证
func (c *Neo4jConnector) 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
// 构造Neo4j URL
uri := fmt.Sprintf("bolt://%s:%s", info.Host, info.Ports)
// 先尝试无认证连接
driver, dbInfo, isAuth, err := c.createConnection(uri, "", "", timeout)
if err != nil {
// 检查是否是Neo4j相关错误
if c.isNeo4jError(err) {
// 即使连接失败但可以识别为Neo4j服务
return &Neo4jConnection{
driver: nil,
target: target,
info: "Neo4j Graph Database (Service Detected)",
isAuth: true,
}, nil
}
return nil, err
}
return &Neo4jConnection{
driver: driver,
target: target,
info: dbInfo,
isAuth: isAuth,
}, nil
}
// Authenticate 使用凭据进行认证
func (c *Neo4jConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
neo4jConn, ok := conn.(*Neo4jConnection)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 解析目标地址
parts := strings.Split(neo4jConn.target, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid target format")
}
host := parts[0]
port := parts[1]
uri := fmt.Sprintf("bolt://%s:%s", host, port)
timeout := time.Duration(common.Timeout) * time.Second
// 使用提供的凭据创建新连接
driver, info, isAuth, err := c.createConnection(uri, cred.Username, cred.Password, timeout)
if err != nil {
return err
}
// 更新连接信息
if neo4jConn.driver != nil {
neo4jConn.driver.Close()
}
neo4jConn.driver = driver
neo4jConn.info = info
neo4jConn.isAuth = isAuth
return nil
}
// Close 关闭连接
func (c *Neo4jConnector) Close(conn interface{}) error {
if neo4jConn, ok := conn.(*Neo4jConnection); ok && neo4jConn.driver != nil {
return neo4jConn.driver.Close()
}
return nil
}
// createConnection 创建Neo4j连接
func (c *Neo4jConnector) createConnection(uri, username, password string, timeout time.Duration) (neo4j.Driver, string, bool, error) {
// 配置驱动选项
config := func(c *neo4j.Config) {
c.SocketConnectTimeout = timeout
c.ConnectionAcquisitionTimeout = timeout
// Neo4j驱动默认不支持代理这里暂不处理Socks代理
}
var driver neo4j.Driver
var err error
isAuth := true
// 尝试建立连接
if username != "" || password != "" {
// 有认证信息时使用认证
driver, err = neo4j.NewDriver(uri, neo4j.BasicAuth(username, password, ""), config)
} else {
// 无认证时使用NoAuth
driver, err = neo4j.NewDriver(uri, neo4j.NoAuth(), config)
isAuth = false
}
if err != nil {
return nil, "", isAuth, err
}
// 测试连接有效性
err = driver.VerifyConnectivity()
if err != nil {
driver.Close()
return nil, "", isAuth, err
}
// 获取数据库信息
info := c.getDatabaseInfo(driver)
return driver, info, isAuth, nil
}
// getDatabaseInfo 获取Neo4j数据库信息
func (c *Neo4jConnector) getDatabaseInfo(driver neo4j.Driver) string {
session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()
// 尝试获取版本信息
result, err := session.Run("CALL dbms.components() YIELD name, versions, edition RETURN name, versions[0] as version, edition", nil)
if err != nil {
return "Neo4j Graph Database"
}
if result.Next() {
record := result.Record()
if name, ok := record.Get("name"); ok {
if version, ok := record.Get("version"); ok {
if edition, ok := record.Get("edition"); ok {
return fmt.Sprintf("%s %s (%s)", name, version, edition)
}
return fmt.Sprintf("%s %s", name, version)
}
}
}
return "Neo4j Graph Database"
}
// isNeo4jError 检查是否是Neo4j相关错误
func (c *Neo4jConnector) isNeo4jError(err error) bool {
if err == nil {
return false
}
errorStr := strings.ToLower(err.Error())
neo4jErrorIndicators := []string{
"neo4j",
"bolt",
"authentication failed",
"credentials",
"unauthorized",
"connection refused",
"graph database",
"cypher",
}
for _, indicator := range neo4jErrorIndicators {
if strings.Contains(errorStr, indicator) {
return true
}
}
return false
}

View File

@ -0,0 +1,42 @@
package neo4j
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// Neo4jExploiter Neo4j利用器实现
type Neo4jExploiter struct{}
// NewNeo4jExploiter 创建Neo4j利用器
func NewNeo4jExploiter() *Neo4jExploiter {
return &Neo4jExploiter{}
}
// Exploit 执行Neo4j利用
func (e *Neo4jExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// Neo4j插件主要用于服务识别和认证测试不进行进一步利用
return &base.ExploitResult{
Success: false,
Error: fmt.Errorf("Neo4j插件不支持进一步利用"),
}, nil
}
// GetExploitMethods 获取支持的利用方法
func (e *Neo4jExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Name: "信息收集",
Type: base.ExploitDataExtraction,
Description: "收集Neo4j图数据库信息",
},
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *Neo4jExploiter) IsExploitSupported(method base.ExploitType) bool {
return method == base.ExploitDataExtraction
}

View File

@ -0,0 +1,244 @@
package neo4j
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// Neo4jPlugin Neo4j插件实现
type Neo4jPlugin struct {
*base.ServicePlugin
exploiter *Neo4jExploiter
}
// NewNeo4jPlugin 创建Neo4j插件
func NewNeo4jPlugin() *Neo4jPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "neo4j",
Version: "2.0.0",
Author: "fscan-team",
Description: "Neo4j图数据库扫描和利用插件",
Category: "service",
Ports: []int{7474, 7687}, // Neo4j HTTP端口和Bolt端口
Protocols: []string{"tcp", "bolt"},
Tags: []string{"neo4j", "graph-database", "database", "weak-password", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewNeo4jConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Neo4j插件
plugin := &Neo4jPlugin{
ServicePlugin: servicePlugin,
exploiter: NewNeo4jExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法进行Neo4j服务扫描
func (p *Neo4jPlugin) 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)
// 先检查未授权访问
unauthCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, unauthCred)
if err == nil && result.Success {
// 未授权访问成功
common.LogSuccess(i18n.GetText("neo4j_unauth_access", target))
return &base.ScanResult{
Success: true,
Service: "Neo4j",
Credentials: []*base.Credential{unauthCred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Neo4j",
"port": info.Ports,
"unauthorized": true,
"access_type": "no_authentication",
},
}, nil
}
// 检查默认凭据
defaultCred := &base.Credential{Username: "neo4j", Password: "neo4j"}
result, err = p.ScanCredential(ctx, info, defaultCred)
if err == nil && result.Success {
// 默认凭据成功
common.LogSuccess(i18n.GetText("neo4j_default_creds", target, defaultCred.Username, defaultCred.Password))
return &base.ScanResult{
Success: true,
Service: "Neo4j",
Credentials: []*base.Credential{defaultCred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Neo4j",
"port": info.Ports,
"username": defaultCred.Username,
"password": defaultCred.Password,
"type": "default-credentials",
},
}, nil
}
// 生成凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("neo4j_auth_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "Neo4j",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Neo4j",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了Neo4j服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成Neo4j凭据
func (p *Neo4jPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取Neo4j用户名字典
usernames := common.Userdict["neo4j"]
if len(usernames) == 0 {
usernames = []string{"neo4j", "admin", "administrator", "root"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "neo4j", "admin", "password", "123456", "root"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *Neo4jPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *Neo4jPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *Neo4jPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Neo4j服务识别-nobr模式
func (p *Neo4jPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别Neo4j服务
connector := NewNeo4jConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if neo4jConn, ok := conn.(*Neo4jConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("neo4j_service_identified", target, neo4jConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "Neo4j",
Banner: neo4jConn.info,
Extra: map[string]interface{}{
"service": "Neo4j",
"port": info.Ports,
"info": neo4jConn.info,
},
}, nil
}
}
// 如果无法识别为Neo4j返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Neo4j服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterNeo4jPlugin 注册Neo4j插件
func RegisterNeo4jPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "neo4j",
Version: "2.0.0",
Author: "fscan-team",
Description: "Neo4j图数据库扫描和利用插件",
Category: "service",
Ports: []int{7474, 7687}, // Neo4j HTTP端口和Bolt端口
Protocols: []string{"tcp", "bolt"},
Tags: []string{"neo4j", "graph-database", "database", "weak-password", "unauthorized"},
},
func() base.Plugin {
return NewNeo4jPlugin()
},
)
base.GlobalPluginRegistry.Register("neo4j", factory)
}
// 自动注册
func init() {
RegisterNeo4jPlugin()
}

View File

@ -0,0 +1,207 @@
package oracle
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/sijms/go-ora/v2"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// OracleConnection Oracle连接包装器
type OracleConnection struct {
db *sql.DB
target string
info string
serviceName string
}
// OracleConnector Oracle连接器实现
type OracleConnector struct{}
// NewOracleConnector 创建Oracle连接器
func NewOracleConnector() *OracleConnector {
return &OracleConnector{}
}
// Connect 连接到Oracle服务器不进行认证
func (c *OracleConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试建立连接但不进行认证,使用空凭据进行连接尝试
db, dbInfo, serviceName, err := c.createConnection(ctx, info.Host, info.Ports, "", "", "")
if err != nil {
// 检查是否是Oracle服务相关错误
if c.isOracleError(err) {
// 即使连接失败但可以识别为Oracle服务
return &OracleConnection{
db: nil,
target: target,
info: "Oracle Database (Service Detected)",
serviceName: "",
}, nil
}
return nil, err
}
return &OracleConnection{
db: db,
target: target,
info: dbInfo,
serviceName: serviceName,
}, nil
}
// Authenticate 使用凭据进行认证
func (c *OracleConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
oracleConn, ok := conn.(*OracleConnection)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 解析目标地址
parts := strings.Split(oracleConn.target, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid target format")
}
host := parts[0]
port := parts[1]
// 使用提供的凭据创建新连接
db, info, serviceName, err := c.createConnection(ctx, host, port, cred.Username, cred.Password, "")
if err != nil {
return err
}
// 更新连接信息
if oracleConn.db != nil {
oracleConn.db.Close()
}
oracleConn.db = db
oracleConn.info = info
oracleConn.serviceName = serviceName
return nil
}
// Close 关闭连接
func (c *OracleConnector) Close(conn interface{}) error {
if oracleConn, ok := conn.(*OracleConnection); ok && oracleConn.db != nil {
return oracleConn.db.Close()
}
return nil
}
// createConnection 创建Oracle数据库连接
func (c *OracleConnector) createConnection(ctx context.Context, host, port, username, password, serviceName string) (*sql.DB, string, string, error) {
timeout := time.Duration(common.Timeout) * time.Second
// 常见Oracle服务名列表
commonServiceNames := []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"}
// 如果未指定服务名,尝试所有常见服务名
serviceNamesToTry := []string{serviceName}
if serviceName == "" {
serviceNamesToTry = commonServiceNames
}
var lastErr error
for _, svcName := range serviceNamesToTry {
if svcName == "" {
continue
}
// 构造连接字符串
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d",
username, password, host, port, svcName, int(timeout.Seconds()))
// 对SYS用户使用SYSDBA权限
if strings.ToUpper(username) == "SYS" {
connStr += "&sysdba=1"
}
// 建立数据库连接
db, err := sql.Open("oracle", connStr)
if err != nil {
lastErr = err
continue
}
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 创建ping上下文
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
// 测试连接
err = db.PingContext(pingCtx)
pingCancel()
if err != nil {
db.Close()
lastErr = err
// 如果是认证错误,继续尝试下一个服务名
if strings.Contains(err.Error(), "ORA-01017") {
continue
}
continue
}
// 获取数据库信息
info := c.getDatabaseInfo(db, ctx)
return db, info, svcName, nil
}
return nil, "", "", lastErr
}
// getDatabaseInfo 获取Oracle数据库信息
func (c *OracleConnector) getDatabaseInfo(db *sql.DB, ctx context.Context) string {
var version string
err := db.QueryRowContext(ctx, "SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1").Scan(&version)
if err != nil {
// 尝试其他查询
err = db.QueryRowContext(ctx, "SELECT VERSION FROM PRODUCT_COMPONENT_VERSION WHERE PRODUCT LIKE 'Oracle%' AND ROWNUM = 1").Scan(&version)
if err != nil {
return "Oracle Database"
}
}
return version
}
// isOracleError 检查是否是Oracle相关错误
func (c *OracleConnector) isOracleError(err error) bool {
if err == nil {
return false
}
errorStr := strings.ToLower(err.Error())
oracleErrorIndicators := []string{
"ora-",
"oracle",
"tns:",
"listener",
"database",
"connection refused",
"invalid authorization specification",
"service name",
"sid",
}
for _, indicator := range oracleErrorIndicators {
if strings.Contains(errorStr, indicator) {
return true
}
}
return false
}

View File

@ -0,0 +1,42 @@
package oracle
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// OracleExploiter Oracle利用器实现
type OracleExploiter struct{}
// NewOracleExploiter 创建Oracle利用器
func NewOracleExploiter() *OracleExploiter {
return &OracleExploiter{}
}
// Exploit 执行Oracle利用
func (e *OracleExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// Oracle插件主要用于服务识别和认证测试不进行进一步利用
return &base.ExploitResult{
Success: false,
Error: fmt.Errorf("Oracle插件不支持进一步利用"),
}, nil
}
// GetExploitMethods 获取支持的利用方法
func (e *OracleExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Name: "信息收集",
Type: base.ExploitDataExtraction,
Description: "收集Oracle数据库信息",
},
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *OracleExploiter) IsExploitSupported(method base.ExploitType) bool {
return method == base.ExploitDataExtraction
}

View File

@ -0,0 +1,244 @@
package oracle
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// OraclePlugin Oracle插件实现
type OraclePlugin struct {
*base.ServicePlugin
exploiter *OracleExploiter
}
// NewOraclePlugin 创建Oracle插件
func NewOraclePlugin() *OraclePlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "oracle",
Version: "2.0.0",
Author: "fscan-team",
Description: "Oracle数据库扫描和利用插件",
Category: "service",
Ports: []int{1521, 1522, 1525}, // Oracle常用端口
Protocols: []string{"tcp"},
Tags: []string{"oracle", "database", "weak-password", "sysdba"},
}
// 创建连接器和服务插件
connector := NewOracleConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Oracle插件
plugin := &OraclePlugin{
ServicePlugin: servicePlugin,
exploiter: NewOracleExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法进行Oracle服务扫描
func (p *OraclePlugin) 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)
// 先尝试高危凭据
highRiskCredentials := p.getHighRiskCredentials()
for _, cred := range highRiskCredentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
if strings.ToUpper(cred.Username) == "SYS" {
common.LogSuccess(i18n.GetText("oracle_sys_auth_success", target, cred.Username, cred.Password))
} else {
common.LogSuccess(i18n.GetText("oracle_auth_success", target, cred.Username, cred.Password))
}
return &base.ScanResult{
Success: true,
Service: "Oracle",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Oracle",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "high-risk-credentials",
},
}, nil
}
}
// 生成凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("oracle_auth_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "Oracle",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Oracle",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了Oracle服务
return p.performServiceIdentification(ctx, info)
}
// getHighRiskCredentials 获取高危凭据列表
func (p *OraclePlugin) getHighRiskCredentials() []*base.Credential {
return []*base.Credential{
{Username: "SYS", Password: "123456"},
{Username: "SYSTEM", Password: "123456"},
{Username: "SYS", Password: "oracle"},
{Username: "SYSTEM", Password: "oracle"},
{Username: "SYS", Password: "password"},
{Username: "SYSTEM", Password: "password"},
{Username: "SYS", Password: "sys123"},
{Username: "SYS", Password: "change_on_install"},
{Username: "SYSTEM", Password: "manager"},
}
}
// generateCredentials 生成Oracle凭据
func (p *OraclePlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取Oracle用户名字典
usernames := common.Userdict["oracle"]
if len(usernames) == 0 {
usernames = []string{"oracle", "sys", "system", "admin", "scott", "hr", "oe"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "oracle", "admin", "password", "123456", "manager", "tiger"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: strings.ToUpper(username), // Oracle用户名通常大写
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *OraclePlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *OraclePlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *OraclePlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Oracle服务识别-nobr模式
func (p *OraclePlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别Oracle服务
connector := NewOracleConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if oracleConn, ok := conn.(*OracleConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("oracle_service_identified", target, oracleConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "Oracle",
Banner: oracleConn.info,
Extra: map[string]interface{}{
"service": "Oracle",
"port": info.Ports,
"info": oracleConn.info,
"service_name": oracleConn.serviceName,
},
}, nil
}
}
// 如果无法识别为Oracle返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Oracle服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterOraclePlugin 注册Oracle插件
func RegisterOraclePlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "oracle",
Version: "2.0.0",
Author: "fscan-team",
Description: "Oracle数据库扫描和利用插件",
Category: "service",
Ports: []int{1521, 1522, 1525}, // Oracle常用端口
Protocols: []string{"tcp"},
Tags: []string{"oracle", "database", "weak-password", "sysdba"},
},
func() base.Plugin {
return NewOraclePlugin()
},
)
base.GlobalPluginRegistry.Register("oracle", factory)
}
// 自动注册
func init() {
RegisterOraclePlugin()
}

View File

@ -0,0 +1,269 @@
package pop3
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// POP3Connector POP3连接器实现
type POP3Connector struct {
host string
port int
}
// POP3Connection POP3连接结构
type POP3Connection struct {
conn net.Conn
reader *bufio.Reader
isTLS bool
banner string
username string
password string
}
// NewPOP3Connector 创建POP3连接器
func NewPOP3Connector() *POP3Connector {
return &POP3Connector{}
}
// Connect 建立POP3连接
func (c *POP3Connector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *POP3Connection
isTLS bool
banner string
err error
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 首先尝试普通连接
conn, err := common.WrapperTcpWithTimeout("tcp", address, timeout)
if err == nil {
banner, authErr := c.readBanner(conn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: conn,
reader: bufio.NewReader(conn),
isTLS: false,
banner: banner,
}
select {
case <-ctx.Done():
conn.Close()
case resultChan <- connResult{pop3Conn, false, banner, nil}:
}
return
}
conn.Close()
}
// 如果普通连接失败尝试TLS连接(端口995)
if info.Ports == "995" {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
dialer := &net.Dialer{Timeout: timeout}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", address, tlsConfig)
if tlsErr == nil {
banner, authErr := c.readBanner(tlsConn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: tlsConn,
reader: bufio.NewReader(tlsConn),
isTLS: true,
banner: banner,
}
select {
case <-ctx.Done():
tlsConn.Close()
case resultChan <- connResult{pop3Conn, true, banner, nil}:
}
return
}
tlsConn.Close()
}
}
select {
case <-ctx.Done():
case resultChan <- connResult{nil, false, "", fmt.Errorf(i18n.GetText("pop3_connection_failed"), err)}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行POP3认证
func (c *POP3Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
pop3Conn, ok := conn.(*POP3Connection)
if !ok {
return fmt.Errorf("无效的POP3连接类型")
}
timeout := time.Duration(common.Timeout) * time.Second
// 发送用户名
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err := pop3Conn.conn.Write([]byte(fmt.Sprintf("USER %s\r\n", cred.Username)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取用户名响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err := pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if !strings.Contains(response, "+OK") {
return fmt.Errorf("用户名无效: %s", strings.TrimSpace(response))
}
// 短暂延迟
time.Sleep(300 * time.Millisecond)
// 发送密码
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err = pop3Conn.conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", cred.Password)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取密码响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err = pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if strings.Contains(response, "+OK") {
// 认证成功,保存凭据信息
pop3Conn.username = cred.Username
pop3Conn.password = cred.Password
return nil
}
return fmt.Errorf("认证失败: %s", strings.TrimSpace(response))
}
// Close 关闭POP3连接
func (c *POP3Connector) Close(conn interface{}) error {
if pop3Conn, ok := conn.(*POP3Connection); ok {
// 尝试优雅退出
if pop3Conn.conn != nil {
pop3Conn.conn.Write([]byte("QUIT\r\n"))
pop3Conn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的POP3连接类型")
}
// readBanner 读取POP3服务器横幅信息
func (c *POP3Connector) readBanner(conn net.Conn, timeout time.Duration) (string, error) {
reader := bufio.NewReader(conn)
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取横幅失败: %v", err)
}
// 检查是否为正常的POP3响应
if strings.Contains(response, "+OK") {
return strings.TrimSpace(response), nil
}
// 检查错误响应
if strings.Contains(strings.ToLower(response), "error") ||
strings.Contains(strings.ToLower(response), "too many") {
return "", fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response))
}
return strings.TrimSpace(response), nil
}
// GetConnectionInfo 获取连接信息
func (conn *POP3Connection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "POP3",
"tls": conn.isTLS,
"banner": conn.banner,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *POP3Connection) IsAlive() bool {
if conn.conn == nil {
return false
}
// 尝试发送NOOP命令检查连接
conn.conn.SetDeadline(time.Now().Add(5 * time.Second))
_, err := conn.conn.Write([]byte("NOOP\r\n"))
if err != nil {
return false
}
response, err := conn.reader.ReadString('\n')
if err != nil {
return false
}
return strings.Contains(response, "+OK")
}
// ExecuteCommand 执行POP3命令
func (conn *POP3Connection) ExecuteCommand(command string) (string, error) {
if conn.conn == nil {
return "", fmt.Errorf("连接未建立")
}
timeout := time.Duration(common.Timeout) * time.Second
conn.conn.SetDeadline(time.Now().Add(timeout))
// 发送命令
_, err := conn.conn.Write([]byte(command + "\r\n"))
if err != nil {
return "", fmt.Errorf("发送命令失败: %v", err)
}
// 读取响应
response, err := conn.reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
return strings.TrimSpace(response), nil
}

View File

@ -0,0 +1,36 @@
package pop3
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// POP3Exploiter POP3利用器实现 - 最小化版本,不提供利用功能
type POP3Exploiter struct {
*base.BaseExploiter
}
// NewPOP3Exploiter 创建POP3利用器
func NewPOP3Exploiter() *POP3Exploiter {
exploiter := &POP3Exploiter{
BaseExploiter: base.NewBaseExploiter("pop3"),
}
// POP3插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *POP3Exploiter) setupExploitMethods() {
// POP3插件不提供利用功能仅进行弱密码扫描和服务识别
}
// Exploit 利用接口实现 - 空实现
func (e *POP3Exploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// POP3插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,210 @@
package pop3
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// POP3Plugin POP3插件实现
type POP3Plugin struct {
*base.ServicePlugin
exploiter *POP3Exploiter
}
// NewPOP3Plugin 创建POP3插件
func NewPOP3Plugin() *POP3Plugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "pop3",
Version: "2.0.0",
Author: "fscan-team",
Description: "POP3邮件服务扫描和利用插件",
Category: "service",
Ports: []int{110, 995}, // POP3常用端口
Protocols: []string{"tcp"},
Tags: []string{"pop3", "email", "weak-password", "tls"},
}
// 创建连接器和服务插件
connector := NewPOP3Connector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建POP3插件
plugin := &POP3Plugin{
ServicePlugin: servicePlugin,
exploiter: NewPOP3Exploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
})
return plugin
}
// Scan 重写扫描方法进行POP3服务扫描
func (p *POP3Plugin) 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()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
tlsStatus := ""
if result.Extra["tls"] == true {
tlsStatus = " (TLS)"
}
common.LogSuccess(i18n.GetText("pop3_weak_pwd_success", target, cred.Username, cred.Password) + tlsStatus)
return &base.ScanResult{
Success: true,
Service: "POP3",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "POP3",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
"tls": result.Extra["tls"],
},
}, nil
}
}
// 所有凭据都失败但可能识别到了POP3服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成POP3凭据
func (p *POP3Plugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取POP3用户名字典
usernames := common.Userdict["pop3"]
if len(usernames) == 0 {
usernames = []string{"admin", "root", "test", "mail", "postmaster", "user"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "admin", "password", "123456", "admin123", "test123", "root123"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *POP3Plugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *POP3Plugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *POP3Plugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行POP3服务识别-nobr模式
func (p *POP3Plugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别POP3服务
connector := NewPOP3Connector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if pop3Conn, ok := conn.(*POP3Connection); ok {
// 记录服务识别成功
tlsStatus := ""
if pop3Conn.isTLS {
tlsStatus = " (TLS)"
}
common.LogSuccess(i18n.GetText("pop3_service_identified", target, pop3Conn.banner) + tlsStatus)
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "POP3",
Banner: pop3Conn.banner,
Extra: map[string]interface{}{
"service": "POP3",
"port": info.Ports,
"banner": pop3Conn.banner,
"tls": pop3Conn.isTLS,
},
}, nil
}
}
// 如果无法识别为POP3返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为POP3服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterPOP3Plugin 注册POP3插件
func RegisterPOP3Plugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "pop3",
Version: "2.0.0",
Author: "fscan-team",
Description: "POP3邮件服务扫描和利用插件",
Category: "service",
Ports: []int{110, 995}, // POP3常用端口
Protocols: []string{"tcp"},
Tags: []string{"pop3", "email", "weak-password", "tls"},
},
func() base.Plugin {
return NewPOP3Plugin()
},
)
base.GlobalPluginRegistry.Register("pop3", factory)
}
// 自动注册
func init() {
RegisterPOP3Plugin()
}

View File

@ -0,0 +1,229 @@
package postgresql
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"net"
"strings"
"time"
"github.com/lib/pq"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PostgreSQLProxyDialer 自定义PostgreSQL代理拨号器
type PostgreSQLProxyDialer struct {
timeout time.Duration
}
// Dial 实现pq.Dialer接口支持socks代理
func (d *PostgreSQLProxyDialer) Dial(network, address string) (net.Conn, error) {
return common.WrapperTcpWithTimeout(network, address, d.timeout)
}
// DialTimeout 实现具有超时的连接
func (d *PostgreSQLProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return common.WrapperTcpWithTimeout(network, address, timeout)
}
// PostgreSQLConnection PostgreSQL连接包装器
type PostgreSQLConnection struct {
db *sql.DB
target string
info string
}
// PostgreSQLConnector PostgreSQL连接器实现
type PostgreSQLConnector struct{}
// NewPostgreSQLConnector 创建PostgreSQL连接器
func NewPostgreSQLConnector() *PostgreSQLConnector {
return &PostgreSQLConnector{}
}
// Connect 连接到PostgreSQL服务器不进行认证
func (c *PostgreSQLConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试建立连接但不进行认证,使用空凭据进行连接尝试
db, dbInfo, err := c.createConnection(ctx, info.Host, info.Ports, "", "")
if err != nil {
// 检查是否是PostgreSQL服务相关错误
if c.isPostgreSQLError(err) {
// 即使连接失败但可以识别为PostgreSQL服务
return &PostgreSQLConnection{
db: nil,
target: target,
info: "PostgreSQL Database (Service Detected)",
}, nil
}
return nil, err
}
return &PostgreSQLConnection{
db: db,
target: target,
info: dbInfo,
}, nil
}
// Authenticate 使用凭据进行认证
func (c *PostgreSQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
pgConn, ok := conn.(*PostgreSQLConnection)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 解析目标地址
parts := strings.Split(pgConn.target, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid target format")
}
host := parts[0]
port := parts[1]
// 使用提供的凭据创建新连接
db, info, err := c.createConnection(ctx, host, port, cred.Username, cred.Password)
if err != nil {
return err
}
// 更新连接信息
if pgConn.db != nil {
pgConn.db.Close()
}
pgConn.db = db
pgConn.info = info
return nil
}
// Close 关闭连接
func (c *PostgreSQLConnector) Close(conn interface{}) error {
if pgConn, ok := conn.(*PostgreSQLConnection); ok && pgConn.db != nil {
return pgConn.db.Close()
}
return nil
}
// createConnection 创建PostgreSQL数据库连接
func (c *PostgreSQLConnector) createConnection(ctx context.Context, host, port, username, password string) (*sql.DB, string, error) {
timeout := time.Duration(common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf(
"postgres://%s:%s@%s:%s/postgres?sslmode=disable&connect_timeout=%d",
username, password, host, port, int(timeout.Seconds()),
)
var db *sql.DB
var err error
// 检查是否需要使用socks代理
if common.Socks5Proxy != "" {
// 使用自定义dialer通过socks代理连接
dialer := &PostgreSQLProxyDialer{
timeout: timeout,
}
// 使用pq.DialOpen通过自定义dialer建立连接
conn, err := pq.DialOpen(dialer, connStr)
if err != nil {
return nil, "", err
}
// 转换为sql.DB进行测试
db = sql.OpenDB(&postgresConnector{conn: conn})
} else {
// 使用标准连接方式
db, err = sql.Open("postgres", connStr)
if err != nil {
return nil, "", err
}
}
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 创建ping上下文
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
defer pingCancel()
// 使用上下文测试连接
err = db.PingContext(pingCtx)
if err != nil {
db.Close()
return nil, "", err
}
// 获取数据库信息
info := c.getDatabaseInfo(db, pingCtx)
return db, info, nil
}
// getDatabaseInfo 获取PostgreSQL数据库信息
func (c *PostgreSQLConnector) getDatabaseInfo(db *sql.DB, ctx context.Context) string {
var version string
err := db.QueryRowContext(ctx, "SELECT version()").Scan(&version)
if err != nil {
return "PostgreSQL Database"
}
// 提取版本信息的关键部分
if strings.Contains(version, "PostgreSQL") {
parts := strings.Fields(version)
if len(parts) >= 2 {
return fmt.Sprintf("%s %s", parts[0], parts[1])
}
}
return version
}
// isPostgreSQLError 检查是否是PostgreSQL相关错误
func (c *PostgreSQLConnector) isPostgreSQLError(err error) bool {
if err == nil {
return false
}
errorStr := strings.ToLower(err.Error())
postgresErrorIndicators := []string{
"postgres",
"postgresql",
"authentication failed",
"password authentication failed",
"database",
"connection refused",
"pq:",
"invalid authorization specification",
"role",
"does not exist",
}
for _, indicator := range postgresErrorIndicators {
if strings.Contains(errorStr, indicator) {
return true
}
}
return false
}
// postgresConnector 封装driver.Conn为sql.driver.Connector
type postgresConnector struct {
conn driver.Conn
}
func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) {
return c.conn, nil
}
func (c *postgresConnector) Driver() driver.Driver {
return &pq.Driver{}
}

View File

@ -0,0 +1,42 @@
package postgresql
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PostgreSQLExploiter PostgreSQL利用器实现
type PostgreSQLExploiter struct{}
// NewPostgreSQLExploiter 创建PostgreSQL利用器
func NewPostgreSQLExploiter() *PostgreSQLExploiter {
return &PostgreSQLExploiter{}
}
// Exploit 执行PostgreSQL利用
func (e *PostgreSQLExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// PostgreSQL插件主要用于服务识别和认证测试不进行进一步利用
return &base.ExploitResult{
Success: false,
Error: fmt.Errorf("PostgreSQL插件不支持进一步利用"),
}, nil
}
// GetExploitMethods 获取支持的利用方法
func (e *PostgreSQLExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Name: "信息收集",
Type: base.ExploitDataExtraction,
Description: "收集PostgreSQL数据库信息",
},
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *PostgreSQLExploiter) IsExploitSupported(method base.ExploitType) bool {
return method == base.ExploitDataExtraction
}

View File

@ -0,0 +1,200 @@
package postgresql
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// PostgreSQLPlugin PostgreSQL插件实现
type PostgreSQLPlugin struct {
*base.ServicePlugin
exploiter *PostgreSQLExploiter
}
// NewPostgreSQLPlugin 创建PostgreSQL插件
func NewPostgreSQLPlugin() *PostgreSQLPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "postgresql",
Version: "2.0.0",
Author: "fscan-team",
Description: "PostgreSQL数据库扫描和利用插件",
Category: "service",
Ports: []int{5432}, // 默认PostgreSQL端口
Protocols: []string{"tcp"},
Tags: []string{"postgresql", "postgres", "database", "weak-password"},
}
// 创建连接器和服务插件
connector := NewPostgreSQLConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建PostgreSQL插件
plugin := &PostgreSQLPlugin{
ServicePlugin: servicePlugin,
exploiter: NewPostgreSQLExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法进行PostgreSQL服务扫描
func (p *PostgreSQLPlugin) 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()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("postgresql_auth_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "PostgreSQL",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "PostgreSQL",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了PostgreSQL服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成PostgreSQL凭据
func (p *PostgreSQLPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取PostgreSQL用户名字典
usernames := common.Userdict["postgresql"]
if len(usernames) == 0 {
usernames = []string{"postgres", "admin", "administrator", "root", "user"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "postgres", "admin", "password", "123456", "root"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *PostgreSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *PostgreSQLPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *PostgreSQLPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行PostgreSQL服务识别-nobr模式
func (p *PostgreSQLPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别PostgreSQL服务
connector := NewPostgreSQLConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if pgConn, ok := conn.(*PostgreSQLConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("postgresql_service_identified", target, pgConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "PostgreSQL",
Banner: pgConn.info,
Extra: map[string]interface{}{
"service": "PostgreSQL",
"port": info.Ports,
"info": pgConn.info,
},
}, nil
}
}
// 如果无法识别为PostgreSQL返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为PostgreSQL服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterPostgreSQLPlugin 注册PostgreSQL插件
func RegisterPostgreSQLPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "postgresql",
Version: "2.0.0",
Author: "fscan-team",
Description: "PostgreSQL数据库扫描和利用插件",
Category: "service",
Ports: []int{5432}, // 默认PostgreSQL端口
Protocols: []string{"tcp"},
Tags: []string{"postgresql", "postgres", "database", "weak-password"},
},
func() base.Plugin {
return NewPostgreSQLPlugin()
},
)
base.GlobalPluginRegistry.Register("postgresql", factory)
}
// 自动注册
func init() {
RegisterPostgreSQLPlugin()
}

View File

@ -0,0 +1,195 @@
package rabbitmq
import (
"context"
"fmt"
"net"
"strconv"
"time"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// RabbitMQConnector RabbitMQ连接器实现
type RabbitMQConnector struct {
host string
port int
}
// RabbitMQConnection RabbitMQ连接结构
type RabbitMQConnection struct {
conn *amqp.Connection
amqpURL string
username string
password string
info string
}
// NewRabbitMQConnector 创建RabbitMQ连接器
func NewRabbitMQConnector() *RabbitMQConnector {
return &RabbitMQConnector{}
}
// Connect 建立RabbitMQ连接
func (c *RabbitMQConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
c.host = info.Host
c.port = port
// 对于服务识别只需要检查端口连通性不需要AMQP认证
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *RabbitMQConnection
err error
banner string
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 首先检查TCP连通性
tcpConn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
tcpConn.Close()
// TCP连接成功创建一个基础的连接对象用于服务识别
rabbitConn := &RabbitMQConnection{
conn: nil, // 服务识别阶段不需要真正的AMQP连接
amqpURL: fmt.Sprintf("amqp://guest:guest@%s:%s/", info.Host, info.Ports),
info: "RabbitMQ Service (Detected)",
}
select {
case <-ctx.Done():
case resultChan <- connResult{rabbitConn, nil, "RabbitMQ Service (Detected)"}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行RabbitMQ认证
func (c *RabbitMQConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
timeout := time.Duration(common.Timeout) * time.Second
amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%d/", cred.Username, cred.Password, c.host, c.port)
// 结果通道
type authResult struct {
conn *amqp.Connection
err error
}
resultChan := make(chan authResult, 1)
// 在协程中尝试认证连接
go func() {
// 配置连接
config := amqp.Config{
Dial: func(network, addr string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: timeout}
return dialer.DialContext(ctx, network, addr)
},
}
// 尝试连接
authConn, err := amqp.DialConfig(amqpURL, config)
select {
case <-ctx.Done():
if authConn != nil {
authConn.Close()
}
case resultChan <- authResult{authConn, err}:
}
}()
// 等待认证结果
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf(i18n.GetText("rabbitmq_auth_failed"), result.err)
}
// 更新连接信息
if rabbitConn, ok := conn.(*RabbitMQConnection); ok {
// 关闭旧连接
if rabbitConn.conn != nil {
rabbitConn.conn.Close()
}
// 更新为认证后的连接
rabbitConn.conn = result.conn
rabbitConn.username = cred.Username
rabbitConn.password = cred.Password
rabbitConn.amqpURL = amqpURL
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// Close 关闭RabbitMQ连接
func (c *RabbitMQConnector) Close(conn interface{}) error {
if rabbitConn, ok := conn.(*RabbitMQConnection); ok {
if rabbitConn.conn != nil {
rabbitConn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的RabbitMQ连接类型")
}
// GetConnectionInfo 获取连接信息
func (conn *RabbitMQConnection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "AMQP",
"service": "RabbitMQ",
"info": conn.info,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *RabbitMQConnection) IsAlive() bool {
if conn.conn == nil {
return false
}
return !conn.conn.IsClosed()
}
// GetServerInfo 获取服务器信息
func (conn *RabbitMQConnection) GetServerInfo() string {
return conn.info
}

View File

@ -0,0 +1,36 @@
package rabbitmq
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// RabbitMQExploiter RabbitMQ利用器实现 - 最小化版本,不提供利用功能
type RabbitMQExploiter struct {
*base.BaseExploiter
}
// NewRabbitMQExploiter 创建RabbitMQ利用器
func NewRabbitMQExploiter() *RabbitMQExploiter {
exploiter := &RabbitMQExploiter{
BaseExploiter: base.NewBaseExploiter("rabbitmq"),
}
// RabbitMQ插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *RabbitMQExploiter) setupExploitMethods() {
// RabbitMQ插件不提供利用功能仅进行弱密码扫描和服务识别
}
// Exploit 利用接口实现 - 空实现
func (e *RabbitMQExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// RabbitMQ插件不提供利用功能
return nil, nil
}

View File

@ -0,0 +1,230 @@
package rabbitmq
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// RabbitMQPlugin RabbitMQ插件实现
type RabbitMQPlugin struct {
*base.ServicePlugin
exploiter *RabbitMQExploiter
}
// NewRabbitMQPlugin 创建RabbitMQ插件
func NewRabbitMQPlugin() *RabbitMQPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "rabbitmq",
Version: "2.0.0",
Author: "fscan-team",
Description: "RabbitMQ消息队列服务扫描插件",
Category: "service",
Ports: []int{5672, 5671, 15672}, // AMQP端口、AMQPS端口、管理端口
Protocols: []string{"tcp"},
Tags: []string{"rabbitmq", "amqp", "message-queue", "weak-password"},
}
// 创建连接器和服务插件
connector := NewRabbitMQConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建RabbitMQ插件
plugin := &RabbitMQPlugin{
ServicePlugin: servicePlugin,
exploiter: NewRabbitMQExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
})
return plugin
}
// Scan 重写扫描方法进行RabbitMQ服务扫描
func (p *RabbitMQPlugin) 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)
// 优先尝试默认凭据
defaultCredentials := []*base.Credential{
{Username: "admin", Password: "123456"},
{Username: "guest", Password: "guest"},
{Username: "admin", Password: "admin"},
}
// 先测试默认凭据
for _, cred := range defaultCredentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("rabbitmq_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "RabbitMQ",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "RabbitMQ",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "default-credentials",
},
}, nil
}
}
// 生成其他凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("rabbitmq_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "RabbitMQ",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "RabbitMQ",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了RabbitMQ服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成RabbitMQ凭据
func (p *RabbitMQPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取RabbitMQ用户名字典
usernames := common.Userdict["rabbitmq"]
if len(usernames) == 0 {
usernames = []string{"admin", "root", "rabbitmq", "user", "test"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "admin", "password", "123456", "rabbitmq", "root123", "admin123"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *RabbitMQPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *RabbitMQPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *RabbitMQPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行RabbitMQ服务识别-nobr模式
func (p *RabbitMQPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别RabbitMQ服务
connector := NewRabbitMQConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if rabbitConn, ok := conn.(*RabbitMQConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("rabbitmq_service_identified", target, rabbitConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "RabbitMQ",
Banner: rabbitConn.info,
Extra: map[string]interface{}{
"service": "RabbitMQ",
"port": info.Ports,
"info": rabbitConn.info,
"protocol": "AMQP",
},
}, nil
}
}
// 如果无法识别为RabbitMQ返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterRabbitMQPlugin 注册RabbitMQ插件
func RegisterRabbitMQPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "rabbitmq",
Version: "2.0.0",
Author: "fscan-team",
Description: "RabbitMQ消息队列服务扫描插件",
Category: "service",
Ports: []int{5672, 5671, 15672}, // AMQP端口、AMQPS端口、管理端口
Protocols: []string{"tcp"},
Tags: []string{"rabbitmq", "amqp", "message-queue", "weak-password"},
},
func() base.Plugin {
return NewRabbitMQPlugin()
},
)
base.GlobalPluginRegistry.Register("rabbitmq", factory)
}
// 自动注册
func init() {
RegisterRabbitMQPlugin()
}

View File

@ -0,0 +1,377 @@
package rsync
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// RsyncConnector Rsync连接器实现
type RsyncConnector struct {
host string
port int
}
// RsyncConnection Rsync连接结构
type RsyncConnection struct {
conn net.Conn
username string
password string
info string
modules []string
}
// NewRsyncConnector 创建Rsync连接器
func NewRsyncConnector() *RsyncConnector {
return &RsyncConnector{}
}
// Connect 建立Rsync连接
func (c *RsyncConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
c.host = info.Host
c.port = port
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *RsyncConnection
err error
banner string
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 建立TCP连接
tcpConn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
buffer := make([]byte, 1024)
// 读取服务器初始greeting
tcpConn.SetReadDeadline(time.Now().Add(timeout))
n, err := tcpConn.Read(buffer)
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
greeting := strings.TrimSpace(string(buffer[:n]))
if !strings.HasPrefix(greeting, "@RSYNCD:") {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, fmt.Errorf("不是Rsync服务"), ""}:
}
return
}
// 获取服务器版本号
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 回应相同的版本号
tcpConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = tcpConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 获取模块列表
modules, err := c.getModuleList(tcpConn, timeout)
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 创建连接对象
rsyncConn := &RsyncConnection{
conn: tcpConn,
info: fmt.Sprintf("Rsync Service %s (Modules: %s)", version, strings.Join(modules, ",")),
modules: modules,
}
select {
case <-ctx.Done():
tcpConn.Close()
case resultChan <- connResult{rsyncConn, nil, rsyncConn.info}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// getModuleList 获取Rsync模块列表
func (c *RsyncConnector) getModuleList(conn net.Conn, timeout time.Duration) ([]string, error) {
// 请求模块列表
conn.SetWriteDeadline(time.Now().Add(timeout))
_, err := conn.Write([]byte("#list\n"))
if err != nil {
return nil, err
}
buffer := make([]byte, 4096)
var moduleList strings.Builder
// 读取模块列表
for {
conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buffer)
if err != nil {
break
}
chunk := string(buffer[:n])
moduleList.WriteString(chunk)
if strings.Contains(chunk, "@RSYNCD: EXIT") {
break
}
}
// 解析模块名
var modules []string
lines := strings.Split(moduleList.String(), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "@RSYNCD") {
continue
}
// 提取模块名(第一个字段)
fields := strings.Fields(line)
if len(fields) > 0 {
modules = append(modules, fields[0])
}
}
return modules, nil
}
// Authenticate 进行Rsync认证
func (c *RsyncConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
rsyncConn, ok := conn.(*RsyncConnection)
if !ok {
return fmt.Errorf("无效的Rsync连接类型")
}
timeout := time.Duration(common.Timeout) * time.Second
// 结果通道
type authResult struct {
success bool
module string
err error
}
resultChan := make(chan authResult, 1)
// 在协程中尝试认证
go func() {
success, module, err := c.tryModuleAuthentication(ctx, rsyncConn, cred.Username, cred.Password, timeout)
select {
case <-ctx.Done():
case resultChan <- authResult{success, module, err}:
}
}()
// 等待认证结果
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf(i18n.GetText("rsync_auth_failed"), result.err)
}
if !result.success {
return fmt.Errorf(i18n.GetText("rsync_auth_failed"), "认证失败")
}
// 更新连接信息
rsyncConn.username = cred.Username
rsyncConn.password = cred.Password
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// tryModuleAuthentication 尝试模块认证
func (c *RsyncConnector) tryModuleAuthentication(ctx context.Context, rsyncConn *RsyncConnection, username, password string, timeout time.Duration) (bool, string, error) {
// 为每个模块尝试认证
for _, moduleName := range rsyncConn.modules {
select {
case <-ctx.Done():
return false, "", ctx.Err()
default:
}
// 为每个模块创建新连接进行认证测试
success, err := c.testModuleAuth(ctx, moduleName, username, password, timeout)
if err != nil {
continue // 尝试下一个模块
}
if success {
return true, moduleName, nil
}
}
return false, "", fmt.Errorf("所有模块认证失败")
}
// testModuleAuth 测试单个模块的认证
func (c *RsyncConnector) testModuleAuth(ctx context.Context, moduleName, username, password string, timeout time.Duration) (bool, error) {
address := fmt.Sprintf("%s:%d", c.host, c.port)
// 建立新连接
authConn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return false, err
}
defer authConn.Close()
buffer := make([]byte, 1024)
// 读取greeting
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err := authConn.Read(buffer)
if err != nil {
return false, err
}
greeting := strings.TrimSpace(string(buffer[:n]))
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 回应版本号
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
return false, err
}
// 选择模块
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(moduleName + "\n"))
if err != nil {
return false, err
}
// 等待认证挑战
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
return false, err
}
authResponse := string(buffer[:n])
if strings.Contains(authResponse, "@RSYNCD: OK") {
// 模块不需要认证,匿名访问成功
return username == "" && password == "", nil
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
if username != "" && password != "" {
// 发送认证信息
authString := fmt.Sprintf("%s %s\n", username, password)
authConn.SetWriteDeadline(time.Now().Add(timeout))
_, err = authConn.Write([]byte(authString))
if err != nil {
return false, err
}
// 读取认证结果
authConn.SetReadDeadline(time.Now().Add(timeout))
n, err = authConn.Read(buffer)
if err != nil {
return false, err
}
// 检查认证结果
return !strings.Contains(string(buffer[:n]), "@ERROR"), nil
}
}
return false, nil
}
// Close 关闭Rsync连接
func (c *RsyncConnector) Close(conn interface{}) error {
if rsyncConn, ok := conn.(*RsyncConnection); ok {
if rsyncConn.conn != nil {
rsyncConn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的Rsync连接类型")
}
// GetConnectionInfo 获取连接信息
func (conn *RsyncConnection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "RSYNCD",
"service": "Rsync",
"info": conn.info,
"modules": conn.modules,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *RsyncConnection) IsAlive() bool {
if conn.conn == nil {
return false
}
// 简单的连接测试
conn.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
_, err := conn.conn.Read(make([]byte, 1))
return err == nil
}
// GetServerInfo 获取服务器信息
func (conn *RsyncConnection) GetServerInfo() string {
return conn.info
}

View File

@ -0,0 +1,31 @@
package rsync
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// RsyncExploiter Rsync利用器实现
type RsyncExploiter struct{}
// NewRsyncExploiter 创建Rsync利用器
func NewRsyncExploiter() *RsyncExploiter {
return &RsyncExploiter{}
}
// Exploit 执行Rsync利用
func (e *RsyncExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return nil, nil
}
// GetExploitMethods 获取可用的利用方法
func (e *RsyncExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{}
}
// IsExploitSupported 检查是否支持特定的利用类型
func (e *RsyncExploiter) IsExploitSupported(method base.ExploitType) bool {
return false
}

View File

@ -0,0 +1,254 @@
package rsync
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// RsyncPlugin Rsync插件实现
type RsyncPlugin struct {
*base.ServicePlugin
exploiter *RsyncExploiter
}
// NewRsyncPlugin 创建Rsync插件
func NewRsyncPlugin() *RsyncPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "rsync",
Version: "2.0.0",
Author: "fscan-team",
Description: "Rsync文件同步服务扫描插件",
Category: "service",
Ports: []int{873}, // Rsync默认端口
Protocols: []string{"tcp"},
Tags: []string{"rsync", "file-sync", "weak-password", "unauthorized-access"},
}
// 创建连接器和服务插件
connector := NewRsyncConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Rsync插件
plugin := &RsyncPlugin{
ServicePlugin: servicePlugin,
exploiter: NewRsyncExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
})
return plugin
}
// Scan 重写扫描方法进行Rsync服务扫描
func (p *RsyncPlugin) 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)
// 首先尝试匿名访问
anonymousCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, anonymousCred)
if err == nil && result.Success {
// 匿名访问成功
common.LogSuccess(i18n.GetText("rsync_anonymous_success", target))
return &base.ScanResult{
Success: true,
Service: "Rsync",
Credentials: []*base.Credential{anonymousCred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Rsync",
"port": info.Ports,
"type": "anonymous-access",
},
}, nil
}
// 优先尝试默认凭据
defaultCredentials := []*base.Credential{
{Username: "root", Password: "root123"},
{Username: "admin", Password: "123456"},
{Username: "testuser", Password: "123456"},
{Username: "backup", Password: "backup"},
{Username: "rsync", Password: "rsync"},
}
// 先测试默认凭据
for _, cred := range defaultCredentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("rsync_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "Rsync",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Rsync",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "default-credentials",
},
}, nil
}
}
// 生成其他凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("rsync_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "Rsync",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Rsync",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了Rsync服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成Rsync凭据
func (p *RsyncPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取Rsync用户名字典
usernames := common.Userdict["rsync"]
if len(usernames) == 0 {
usernames = []string{"root", "admin", "rsync", "backup", "user", "test", "testuser"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "admin", "password", "123456", "rsync", "root123", "admin123", "backup", "test123"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *RsyncPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *RsyncPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *RsyncPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Rsync服务识别-nobr模式
func (p *RsyncPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别Rsync服务
connector := NewRsyncConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if rsyncConn, ok := conn.(*RsyncConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("rsync_service_identified", target, rsyncConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "Rsync",
Banner: rsyncConn.info,
Extra: map[string]interface{}{
"service": "Rsync",
"port": info.Ports,
"info": rsyncConn.info,
"protocol": "RSYNCD",
"modules": rsyncConn.modules,
},
}, nil
}
}
// 如果无法识别为Rsync返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Rsync服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterRsyncPlugin 注册Rsync插件
func RegisterRsyncPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "rsync",
Version: "2.0.0",
Author: "fscan-team",
Description: "Rsync文件同步服务扫描插件",
Category: "service",
Ports: []int{873}, // Rsync默认端口
Protocols: []string{"tcp"},
Tags: []string{"rsync", "file-sync", "weak-password", "unauthorized-access"},
},
func() base.Plugin {
return NewRsyncPlugin()
},
)
base.GlobalPluginRegistry.Register("rsync", factory)
}
// 自动注册
func init() {
RegisterRsyncPlugin()
}

View File

@ -0,0 +1,219 @@
package smtp
import (
"context"
"fmt"
"net"
"net/smtp"
"strconv"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// SMTPConnector SMTP连接器实现
type SMTPConnector struct {
host string
port int
}
// SMTPConnection SMTP连接结构
type SMTPConnection struct {
client *smtp.Client
username string
password string
info string
host string
}
// NewSMTPConnector 创建SMTP连接器
func NewSMTPConnector() *SMTPConnector {
return &SMTPConnector{}
}
// Connect 建立SMTP连接
func (c *SMTPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
c.host = info.Host
c.port = port
timeout := time.Duration(common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *SMTPConnection
err error
banner string
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 建立TCP连接
tcpConn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 设置连接超时
tcpConn.SetDeadline(time.Now().Add(timeout))
// 创建SMTP客户端
client, err := smtp.NewClient(tcpConn, info.Host)
if err != nil {
tcpConn.Close()
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 获取服务器信息
banner := "SMTP Service (Detected)"
// 创建连接对象
smtpConn := &SMTPConnection{
client: client,
info: banner,
host: info.Host,
}
select {
case <-ctx.Done():
client.Close()
case resultChan <- connResult{smtpConn, nil, banner}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行SMTP认证
func (c *SMTPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
smtpConn, ok := conn.(*SMTPConnection)
if !ok {
return fmt.Errorf("无效的SMTP连接类型")
}
// 如果是空用户名和密码,测试匿名访问
if cred.Username == "" && cred.Password == "" {
// 尝试匿名发送测试邮件
return c.testAnonymousAccess(smtpConn)
}
// 结果通道
type authResult struct {
err error
}
resultChan := make(chan authResult, 1)
// 在协程中尝试认证
go func() {
// 尝试PLAIN认证
auth := smtp.PlainAuth("", cred.Username, cred.Password, smtpConn.host)
err := smtpConn.client.Auth(auth)
if err == nil {
// 认证成功测试MAIL FROM权限
mailErr := smtpConn.client.Mail("test@test.com")
if mailErr != nil {
err = fmt.Errorf("认证成功但缺少发送权限: %v", mailErr)
}
}
select {
case <-ctx.Done():
case resultChan <- authResult{err}:
}
}()
// 等待认证结果
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf(i18n.GetText("smtp_auth_failed"), result.err)
}
// 更新连接信息
smtpConn.username = cred.Username
smtpConn.password = cred.Password
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// testAnonymousAccess 测试匿名访问
func (c *SMTPConnector) testAnonymousAccess(smtpConn *SMTPConnection) error {
// 尝试匿名发送测试邮件
err := smtpConn.client.Mail("test@test.com")
if err != nil {
return fmt.Errorf("SMTP服务不支持匿名访问: %v", err)
}
return nil
}
// Close 关闭SMTP连接
func (c *SMTPConnector) Close(conn interface{}) error {
if smtpConn, ok := conn.(*SMTPConnection); ok {
if smtpConn.client != nil {
smtpConn.client.Close()
}
return nil
}
return fmt.Errorf("无效的SMTP连接类型")
}
// GetConnectionInfo 获取连接信息
func (conn *SMTPConnection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "SMTP",
"service": "SMTP",
"info": conn.info,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *SMTPConnection) IsAlive() bool {
if conn.client == nil {
return false
}
// 简单的NOOP命令测试连接
err := conn.client.Noop()
return err == nil
}
// GetServerInfo 获取服务器信息
func (conn *SMTPConnection) GetServerInfo() string {
return conn.info
}

View File

@ -0,0 +1,31 @@
package smtp
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// SMTPExploiter SMTP利用器实现
type SMTPExploiter struct{}
// NewSMTPExploiter 创建SMTP利用器
func NewSMTPExploiter() *SMTPExploiter {
return &SMTPExploiter{}
}
// Exploit 执行SMTP利用
func (e *SMTPExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return nil, nil
}
// GetExploitMethods 获取可用的利用方法
func (e *SMTPExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{}
}
// IsExploitSupported 检查是否支持特定的利用类型
func (e *SMTPExploiter) IsExploitSupported(method base.ExploitType) bool {
return false
}

View File

@ -0,0 +1,253 @@
package smtp
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// SMTPPlugin SMTP插件实现
type SMTPPlugin struct {
*base.ServicePlugin
exploiter *SMTPExploiter
}
// NewSMTPPlugin 创建SMTP插件
func NewSMTPPlugin() *SMTPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "smtp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SMTP邮件服务扫描插件",
Category: "service",
Ports: []int{25, 465, 587}, // SMTP端口、SMTPS端口、MSA端口
Protocols: []string{"tcp"},
Tags: []string{"smtp", "email", "weak-password", "unauthorized-access"},
}
// 创建连接器和服务插件
connector := NewSMTPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建SMTP插件
plugin := &SMTPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewSMTPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
})
return plugin
}
// Scan 重写扫描方法进行SMTP服务扫描
func (p *SMTPPlugin) 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)
// 首先尝试匿名访问
anonymousCred := &base.Credential{Username: "", Password: ""}
result, err := p.ScanCredential(ctx, info, anonymousCred)
if err == nil && result.Success {
// 匿名访问成功
common.LogSuccess(i18n.GetText("smtp_anonymous_success", target))
return &base.ScanResult{
Success: true,
Service: "SMTP",
Credentials: []*base.Credential{anonymousCred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SMTP",
"port": info.Ports,
"type": "anonymous-access",
},
}, nil
}
// 优先尝试默认凭据
defaultCredentials := []*base.Credential{
{Username: "admin", Password: "admin123"},
{Username: "test", Password: "123456"},
{Username: "root", Password: "root123"},
{Username: "mail", Password: "mail123"},
{Username: "postmaster", Password: "postmaster"},
}
// 先测试默认凭据
for _, cred := range defaultCredentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("smtp_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "SMTP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SMTP",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "default-credentials",
},
}, nil
}
}
// 生成其他凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("smtp_weak_pwd_success", target, cred.Username, cred.Password))
return &base.ScanResult{
Success: true,
Service: "SMTP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SMTP",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了SMTP服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成SMTP凭据
func (p *SMTPPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取SMTP用户名字典
usernames := common.Userdict["smtp"]
if len(usernames) == 0 {
usernames = []string{"admin", "root", "mail", "postmaster", "user", "test", "smtp"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
passwords = []string{"", "admin", "password", "123456", "admin123", "root123", "mail123", "postmaster", "smtp"}
}
// 生成用户名密码组合
for _, username := range usernames {
for _, password := range passwords {
// 替换密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *SMTPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *SMTPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *SMTPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行SMTP服务识别-nobr模式
func (p *SMTPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别SMTP服务
connector := NewSMTPConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if smtpConn, ok := conn.(*SMTPConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("smtp_service_identified", target, smtpConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "SMTP",
Banner: smtpConn.info,
Extra: map[string]interface{}{
"service": "SMTP",
"port": info.Ports,
"info": smtpConn.info,
"protocol": "SMTP",
},
}, nil
}
}
// 如果无法识别为SMTP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为SMTP服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterSMTPPlugin 注册SMTP插件
func RegisterSMTPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "smtp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SMTP邮件服务扫描插件",
Category: "service",
Ports: []int{25, 465, 587}, // SMTP端口、SMTPS端口、MSA端口
Protocols: []string{"tcp"},
Tags: []string{"smtp", "email", "weak-password", "unauthorized-access"},
},
func() base.Plugin {
return NewSMTPPlugin()
},
)
base.GlobalPluginRegistry.Register("smtp", factory)
}
// 自动注册
func init() {
RegisterSMTPPlugin()
}

View File

@ -0,0 +1,271 @@
package snmp
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/gosnmp/gosnmp"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// SNMPConnector SNMP连接器实现
type SNMPConnector struct {
host string
port int
}
// SNMPConnection SNMP连接结构
type SNMPConnection struct {
client *gosnmp.GoSNMP
community string
sysDesc string
info string
}
// NewSNMPConnector 创建SNMP连接器
func NewSNMPConnector() *SNMPConnector {
return &SNMPConnector{}
}
// Connect 建立SNMP连接
func (c *SNMPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
c.host = info.Host
c.port = port
timeout := time.Duration(common.Timeout) * time.Second
// 结果通道
type connResult struct {
conn *SNMPConnection
err error
banner string
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 尝试使用默认的public community进行连接
client := &gosnmp.GoSNMP{
Target: info.Host,
Port: uint16(port),
Community: "public",
Version: gosnmp.Version2c,
Timeout: timeout,
Retries: 1,
}
err := client.Connect()
if err != nil {
select {
case <-ctx.Done():
case resultChan <- connResult{nil, err, ""}:
}
return
}
// 尝试获取系统描述信息
oids := []string{"1.3.6.1.2.1.1.1.0"} // sysDescr OID
result, err := client.Get(oids)
var sysDesc string
var banner string
if err == nil && len(result.Variables) > 0 {
if result.Variables[0].Type != gosnmp.NoSuchObject {
switch v := result.Variables[0].Value.(type) {
case []byte:
sysDesc = strings.TrimSpace(string(v))
case string:
sysDesc = strings.TrimSpace(v)
}
}
}
if sysDesc != "" {
banner = fmt.Sprintf("SNMP Service (Version: %s, System: %s)", client.Version.String(), sysDesc)
} else {
banner = fmt.Sprintf("SNMP Service (Version: %s)", client.Version.String())
}
// 创建连接对象
snmpConn := &SNMPConnection{
client: client,
community: "public",
sysDesc: sysDesc,
info: banner,
}
select {
case <-ctx.Done():
client.Conn.Close()
case resultChan <- connResult{snmpConn, nil, banner}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行SNMP认证通过community字符串
func (c *SNMPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
snmpConn, ok := conn.(*SNMPConnection)
if !ok {
return fmt.Errorf("无效的SNMP连接类型")
}
// 对于SNMP将用户名作为community字符串使用
community := cred.Username
if community == "" {
community = cred.Password // 如果用户名为空尝试使用密码作为community
}
timeout := time.Duration(common.Timeout) * time.Second
// 结果通道
type authResult struct {
client *gosnmp.GoSNMP
sysDesc string
err error
}
resultChan := make(chan authResult, 1)
// 在协程中尝试认证
go func() {
// 关闭旧连接
if snmpConn.client != nil && snmpConn.client.Conn != nil {
snmpConn.client.Conn.Close()
}
// 创建新的SNMP客户端
client := &gosnmp.GoSNMP{
Target: c.host,
Port: uint16(c.port),
Community: community,
Version: gosnmp.Version2c,
Timeout: timeout,
Retries: 1,
}
err := client.Connect()
if err != nil {
select {
case <-ctx.Done():
case resultChan <- authResult{nil, "", err}:
}
return
}
// 尝试获取系统描述信息验证认证
oids := []string{"1.3.6.1.2.1.1.1.0"} // sysDescr OID
result, err := client.Get(oids)
if err != nil {
client.Conn.Close()
select {
case <-ctx.Done():
case resultChan <- authResult{nil, "", err}:
}
return
}
var sysDesc string
if len(result.Variables) > 0 && result.Variables[0].Type != gosnmp.NoSuchObject {
switch v := result.Variables[0].Value.(type) {
case []byte:
sysDesc = strings.TrimSpace(string(v))
case string:
sysDesc = strings.TrimSpace(v)
}
}
select {
case <-ctx.Done():
client.Conn.Close()
case resultChan <- authResult{client, sysDesc, nil}:
}
}()
// 等待认证结果
select {
case result := <-resultChan:
if result.err != nil {
return fmt.Errorf(i18n.GetText("snmp_auth_failed"), result.err)
}
// 更新连接信息
snmpConn.client = result.client
snmpConn.community = community
snmpConn.sysDesc = result.sysDesc
if result.sysDesc != "" {
snmpConn.info = fmt.Sprintf("SNMP Service (Community: %s, System: %s)", community, result.sysDesc)
} else {
snmpConn.info = fmt.Sprintf("SNMP Service (Community: %s)", community)
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// Close 关闭SNMP连接
func (c *SNMPConnector) Close(conn interface{}) error {
if snmpConn, ok := conn.(*SNMPConnection); ok {
if snmpConn.client != nil && snmpConn.client.Conn != nil {
snmpConn.client.Conn.Close()
}
return nil
}
return fmt.Errorf("无效的SNMP连接类型")
}
// GetConnectionInfo 获取连接信息
func (conn *SNMPConnection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "SNMP",
"service": "SNMP",
"info": conn.info,
"community": conn.community,
}
if conn.sysDesc != "" {
info["system"] = conn.sysDesc
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *SNMPConnection) IsAlive() bool {
if conn.client == nil || conn.client.Conn == nil {
return false
}
// 简单的SNMP GET测试连接
oids := []string{"1.3.6.1.2.1.1.1.0"}
_, err := conn.client.Get(oids)
return err == nil
}
// GetServerInfo 获取服务器信息
func (conn *SNMPConnection) GetServerInfo() string {
return conn.info
}

View File

@ -0,0 +1,31 @@
package snmp
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// SNMPExploiter SNMP利用器实现
type SNMPExploiter struct{}
// NewSNMPExploiter 创建SNMP利用器
func NewSNMPExploiter() *SNMPExploiter {
return &SNMPExploiter{}
}
// Exploit 执行SNMP利用
func (e *SNMPExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return nil, nil
}
// GetExploitMethods 获取可用的利用方法
func (e *SNMPExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{}
}
// IsExploitSupported 检查是否支持特定的利用类型
func (e *SNMPExploiter) IsExploitSupported(method base.ExploitType) bool {
return false
}

View File

@ -0,0 +1,240 @@
package snmp
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// SNMPPlugin SNMP插件实现
type SNMPPlugin struct {
*base.ServicePlugin
exploiter *SNMPExploiter
}
// NewSNMPPlugin 创建SNMP插件
func NewSNMPPlugin() *SNMPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "snmp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SNMP网络管理协议服务扫描插件",
Category: "service",
Ports: []int{161}, // SNMP默认端口
Protocols: []string{"udp"},
Tags: []string{"snmp", "network-management", "weak-community", "information-disclosure"},
}
// 创建连接器和服务插件
connector := NewSNMPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建SNMP插件
plugin := &SNMPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewSNMPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapInformationLeak,
})
return plugin
}
// Scan 重写扫描方法进行SNMP服务扫描
func (p *SNMPPlugin) 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)
// 优先尝试默认community字符串
defaultCommunities := []*base.Credential{
{Username: "public", Password: ""},
{Username: "private", Password: ""},
{Username: "cisco", Password: ""},
{Username: "community", Password: ""},
}
// 先测试默认community
for _, cred := range defaultCommunities {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("snmp_weak_community_success", target, cred.Username))
return &base.ScanResult{
Success: true,
Service: "SNMP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"community": cred.Username,
"type": "weak-community",
},
}, nil
}
}
// 生成其他凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("snmp_weak_community_success", target, cred.Username))
return &base.ScanResult{
Success: true,
Service: "SNMP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"community": cred.Username,
"type": "weak-community",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了SNMP服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成SNMP凭据community字符串
func (p *SNMPPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取SNMP community字典
communities := common.Userdict["snmp"]
if len(communities) == 0 {
// 常见的SNMP community字符串
communities = []string{
"admin", "manager", "secret", "read", "write", "test",
"monitor", "guest", "default", "root", "snmp", "router",
"switch", "network", "public1", "private1", "v1", "v2c",
}
}
// 生成community凭据
for _, community := range communities {
credentials = append(credentials, &base.Credential{
Username: community,
Password: "",
})
}
// 如果有密码字典也作为community使用
passwords := common.Passwords
if len(passwords) > 0 {
for _, password := range passwords {
// 替换密码中的占位符
actualPassword := strings.Replace(password, "{user}", "snmp", -1)
if actualPassword != "" && actualPassword != "snmp" {
credentials = append(credentials, &base.Credential{
Username: actualPassword,
Password: "",
})
}
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *SNMPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *SNMPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *SNMPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行SNMP服务识别-nobr模式
func (p *SNMPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别SNMP服务
connector := NewSNMPConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if snmpConn, ok := conn.(*SNMPConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("snmp_service_identified", target, snmpConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "SNMP",
Banner: snmpConn.info,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"info": snmpConn.info,
"protocol": "SNMP",
"community": snmpConn.community,
},
}, nil
}
}
// 如果无法识别为SNMP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为SNMP服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterSNMPPlugin 注册SNMP插件
func RegisterSNMPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "snmp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SNMP网络管理协议服务扫描插件",
Category: "service",
Ports: []int{161}, // SNMP默认端口
Protocols: []string{"udp"},
Tags: []string{"snmp", "network-management", "weak-community", "information-disclosure"},
},
func() base.Plugin {
return NewSNMPPlugin()
},
)
base.GlobalPluginRegistry.Register("snmp", factory)
}
// 自动注册
func init() {
RegisterSNMPPlugin()
}

View File

@ -1,327 +1,86 @@
package Plugins
package telnet
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"regexp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// TelnetCredential 表示一个Telnet凭据
type TelnetCredential struct {
Username string
Password string
// TelnetConnector Telnet服务连接器
type TelnetConnector struct{}
// NewTelnetConnector 创建新的Telnet连接器
func NewTelnetConnector() *TelnetConnector {
return &TelnetConnector{}
}
// TelnetScanResult 表示Telnet扫描结果
type TelnetScanResult struct {
Success bool
Error error
Credential TelnetCredential
NoAuth bool
}
// Connect 连接到Telnet服务
func (c *TelnetConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// TelnetScan 执行Telnet服务扫描和密码爆破
func TelnetScan(info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 构建凭据列表
var credentials []TelnetCredential
for _, user := range common.Userdict["telnet"] {
for _, pass := range common.Passwords {
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, TelnetCredential{
Username: user,
Password: actualPass,
})
}
}
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["telnet"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
result := concurrentTelnetScan(ctx, info, credentials, common.Timeout, common.MaxRetries)
if result != nil {
// 记录成功结果
saveTelnetResult(info, target, result)
return nil
}
// 检查是否因为全局超时而退出
select {
case <-ctx.Done():
common.LogDebug("Telnet扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentTelnetScan 并发扫描Telnet服务
func concurrentTelnetScan(ctx context.Context, info *common.HostInfo, credentials []TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *TelnetScanResult, 1)
workChan := make(chan TelnetCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
result := tryTelnetCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
if result.Success || result.NoAuth {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据或无需认证,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && (result.Success || result.NoAuth) {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("Telnet并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// tryTelnetCredential 尝试单个Telnet凭据
func tryTelnetCredential(ctx context.Context, info *common.HostInfo, credential TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ {
select {
case <-ctx.Done():
return &TelnetScanResult{
Success: false,
Error: fmt.Errorf("全局超时"),
Credential: credential,
}
default:
if retry > 0 {
common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
time.Sleep(500 * time.Millisecond) // 重试前等待
}
// 创建结果通道
resultChan := make(chan struct {
success bool
noAuth bool
err error
}, 1)
// 设置单个连接超时
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
go func() {
defer cancel()
noAuth, err := telnetConnWithContext(connCtx, info, credential.Username, credential.Password)
select {
case <-connCtx.Done():
// 连接已超时或取消
case resultChan <- struct {
success bool
noAuth bool
err error
}{err == nil, noAuth, err}:
}
}()
// 等待结果或超时
var success bool
var noAuth bool
var err error
select {
case result := <-resultChan:
success = result.success
noAuth = result.noAuth
err = result.err
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
return &TelnetScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err = fmt.Errorf("连接超时")
}
if noAuth {
return &TelnetScanResult{
Success: false,
NoAuth: true,
Credential: credential,
}
}
if success {
return &TelnetScanResult{
Success: true,
Credential: credential,
}
}
lastErr = err
// 创建TCP连接
conn, err := common.WrapperTcpWithContext(ctx, "tcp", target)
if err != nil {
// 检查是否需要重试
if retryErr := common.CheckErrs(err); retryErr == nil {
break // 不需要重试的错误
}
}
}
}
return &TelnetScanResult{
Success: false,
Error: lastErr,
Credential: credential,
}
}
// telnetConnWithContext 带上下文的Telnet连接尝试
func telnetConnWithContext(ctx context.Context, info *common.HostInfo, user, pass string) (bool, error) {
// 创建TCP连接(使用支持context的socks代理)
conn, err := common.WrapperTcpWithContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports))
if err != nil {
return false, err
return nil, fmt.Errorf(i18n.GetText("telnet_connection_failed"), err)
}
// 创建Telnet客户端
client := &TelnetClient{
IPAddr: info.Host,
Port: info.Ports,
UserName: user,
Password: pass,
conn: conn,
}
// 设置连接关闭
defer client.Close()
// 检查上下文是否已取消
select {
case <-ctx.Done():
return false, ctx.Err()
default:
}
// 初始化连接
client.init()
client.ServerType = client.MakeServerType()
if client.ServerType == UnauthorizedAccess {
return true, nil
}
err = client.Login()
return false, err
return client, nil
}
// saveTelnetResult 保存Telnet扫描结果
func saveTelnetResult(info *common.HostInfo, target string, result *TelnetScanResult) {
var successMsg string
var details map[string]interface{}
if result.NoAuth {
successMsg = fmt.Sprintf("Telnet服务 %s 无需认证", target)
details = map[string]interface{}{
"port": info.Ports,
"service": "telnet",
"type": "unauthorized-access",
}
} else {
successMsg = fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v",
target, result.Credential.Username, result.Credential.Password)
details = map[string]interface{}{
"port": info.Ports,
"service": "telnet",
"type": "weak-password",
"username": result.Credential.Username,
"password": result.Credential.Password,
}
// Authenticate 认证Telnet服务
func (c *TelnetConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
client, ok := conn.(*TelnetClient)
if !ok {
return fmt.Errorf("invalid connection type")
}
common.LogSuccess(successMsg)
// 保存结果
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: details,
// 检查上下文是否已取消
select {
case <-ctx.Done():
return ctx.Err()
default:
}
common.SaveResult(vulnResult)
// 设置凭据
client.UserName = cred.Username
client.Password = cred.Password
// 判断服务器类型
client.ServerType = client.MakeServerType()
// 处理无需认证的情况
if client.ServerType == UnauthorizedAccess {
return nil // 认证成功
}
// 尝试登录
return client.Login()
}
// Close 关闭连接
func (c *TelnetConnector) Close(conn interface{}) error {
if client, ok := conn.(*TelnetClient); ok && client != nil {
client.Close()
}
return nil
}
// TelnetClient Telnet客户端结构体
@ -393,11 +152,6 @@ func (c *TelnetClient) ReadContext() string {
return c.LastResponse
}
// Netloc 获取网络地址字符串
func (c *TelnetClient) Netloc() string {
return fmt.Sprintf("%s:%s", c.IPAddr, c.Port)
}
// Close 关闭Telnet连接
func (c *TelnetClient) Close() {
if c.conn != nil {
@ -483,7 +237,6 @@ func (c *TelnetClient) MakeReply(command []byte) []byte {
return []byte{IAC, DONT, option}
case SB:
// 处理子协商命令
// 命令格式: IAC + SB + option + modifier + IAC + SE
if len(command) >= 4 {
modifier := command[3]
if modifier == ECHO {
@ -534,7 +287,7 @@ func (c *TelnetClient) write(buf []byte) error {
func (c *TelnetClient) Login() error {
switch c.ServerType {
case Closed:
return errors.New("service is disabled")
return fmt.Errorf("service is disabled")
case UnauthorizedAccess:
return nil
case OnlyPassword:
@ -542,7 +295,7 @@ func (c *TelnetClient) Login() error {
case UsernameAndPassword:
return c.LogBaserUsernameAndPassword()
default:
return errors.New("unknown server type")
return fmt.Errorf("unknown server type")
}
}
@ -580,32 +333,6 @@ func (c *TelnetClient) MakeServerType() int {
return Closed
}
// 辅助函数:检查字符串是否包含任意给定子串
func containsAny(s string, substrings []string) bool {
for _, sub := range substrings {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// 辅助函数:检查是否无需认证
func isNoAuthRequired(line string) bool {
patterns := []string{
`^/ #.*`,
`^<[A-Za-z0-9_]+>`,
`^#`,
}
for _, pattern := range patterns {
if regexp.MustCompile(pattern).MatchString(line) {
return true
}
}
return false
}
// LogBaserOnlyPassword 处理只需密码的登录
func (c *TelnetClient) LogBaserOnlyPassword() error {
c.Clear() // 清空之前的响应
@ -617,13 +344,13 @@ func (c *TelnetClient) LogBaserOnlyPassword() error {
// 验证登录结果
responseString := c.ReadContext()
if c.isLoginFailed(responseString) {
return errors.New("login failed")
return fmt.Errorf("login failed")
}
if c.isLoginSucceed(responseString) {
return nil
}
return errors.New("login failed")
return fmt.Errorf("login failed")
}
// LogBaserUsernameAndPassword 处理需要用户名和密码的登录
@ -640,13 +367,13 @@ func (c *TelnetClient) LogBaserUsernameAndPassword() error {
// 验证登录结果
responseString := c.ReadContext()
if c.isLoginFailed(responseString) {
return errors.New("login failed")
return fmt.Errorf("login failed")
}
if c.isLoginSucceed(responseString) {
return nil
}
return errors.New("login failed")
return fmt.Errorf("login failed")
}
// Clear 清空最近一次响应
@ -734,6 +461,32 @@ func (c *TelnetClient) isLoginSucceed(responseString string) bool {
return false
}
// 辅助函数:检查字符串是否包含任意给定子串
func containsAny(s string, substrings []string) bool {
for _, sub := range substrings {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// 辅助函数:检查是否无需认证
func isNoAuthRequired(line string) bool {
patterns := []string{
`^/ #.*`,
`^<[A-Za-z0-9_]+>`,
`^#`,
}
for _, pattern := range patterns {
if regexp.MustCompile(pattern).MatchString(line) {
return true
}
}
return false
}
// Telnet协议常量定义
const (
// 写入操作后的延迟时间

View File

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

View File

@ -0,0 +1,184 @@
package telnet
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// TelnetPlugin Telnet服务插件
type TelnetPlugin struct {
*base.ServicePlugin
exploiter *TelnetExploiter
}
// NewTelnetPlugin 创建Telnet插件
func NewTelnetPlugin() *TelnetPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "telnet",
Version: "2.0.0",
Author: "fscan-team",
Description: "Telnet远程终端协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{23}, // Telnet默认端口
Protocols: []string{"tcp"},
Tags: []string{"telnet", "remote-access", "weak-password", "unauthorized-access"},
}
// 创建连接器和服务插件
connector := NewTelnetConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Telnet插件
plugin := &TelnetPlugin{
ServicePlugin: servicePlugin,
exploiter: NewTelnetExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
})
return plugin
}
// init 自动注册Telnet插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "telnet",
Version: "2.0.0",
Author: "fscan-team",
Description: "Telnet远程终端协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{23},
Protocols: []string{"tcp"},
Tags: []string{"telnet", "remote-access", "weak-password", "unauthorized-access"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewTelnetPlugin()
})
base.GlobalPluginRegistry.Register("telnet", factory)
}
// Scan 重写扫描方法进行Telnet服务扫描
func (p *TelnetPlugin) 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 {
// 检查是否无需认证
if result.Extra != nil && result.Extra["type"] == "unauthorized-access" {
common.LogSuccess(i18n.GetText("telnet_unauthorized_access", target))
} else {
// 认证成功
common.LogSuccess(i18n.GetText("telnet_weak_password_success", target, cred.Username, cred.Password))
}
return &base.ScanResult{
Success: true,
Service: "Telnet",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "Telnet",
"port": info.Ports,
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 没有找到有效凭据
return &base.ScanResult{
Success: false,
Service: "Telnet",
Error: fmt.Errorf("authentication failed for all credentials"),
}, nil
}
// performServiceIdentification 执行服务识别
func (p *TelnetPlugin) 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: "Telnet",
Banner: "Telnet service detected",
Extra: map[string]interface{}{
"service": "Telnet",
"port": info.Ports,
"type": "service-identification",
},
}, nil
}
// generateCredentials 生成Telnet认证凭据
func (p *TelnetPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取用户名字典
usernames := common.Userdict["telnet"]
if len(usernames) == 0 {
// 使用默认用户名
usernames = []string{"admin", "root", "user", "test", "guest"}
}
// 获取密码字典
passwords := common.Passwords
if len(passwords) == 0 {
// 使用默认密码
passwords = []string{"", "admin", "root", "123456", "password", "test", "guest"}
}
// 生成凭据组合
for _, username := range usernames {
for _, password := range passwords {
// 处理密码中的用户名占位符
actualPassword := strings.Replace(password, "{user}", username, -1)
credentials = append(credentials, &base.Credential{
Username: username,
Password: actualPassword,
})
}
}
return credentials
}

View File

@ -0,0 +1,111 @@
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

@ -0,0 +1,25 @@
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

@ -0,0 +1,187 @@
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
}

View File

@ -0,0 +1,9 @@
version: '3.8'
services:
rsync:
build: .
ports:
- "873:873"
container_name: rsync_test
restart: unless-stopped

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