refactor: 重构插件架构,实现单文件插件系统

将复杂的三文件插件架构(connector/exploiter/plugin)重构为简化的单文件插件架构,
大幅减少代码重复和维护成本,提升插件开发效率。

主要改进:
• 将每个服务插件从3个文件简化为1个文件
• 删除过度设计的工厂模式、适配器模式等抽象层
• 消除plugins/services/、plugins/adapters/、plugins/base/复杂目录结构
• 实现直接的插件注册机制,提升系统简洁性
• 保持完全向后兼容,所有扫描功能和输出格式不变

重构统计:
• 删除文件:100+个复杂架构文件
• 新增文件:20个简化的单文件插件
• 代码减少:每个插件减少60-80%代码量
• 功能增强:所有插件包含完整扫描和利用功能

已重构插件: MySQL, SSH, Redis, MongoDB, PostgreSQL, MSSQL, Oracle,
Neo4j, Memcached, RabbitMQ, ActiveMQ, Cassandra, FTP, Kafka, LDAP,
Rsync, SMTP, SNMP, Telnet, VNC

验证通过: 新系统编译运行正常,所有插件功能验证通过
This commit is contained in:
ZacharyZcR 2025-08-25 23:57:00 +08:00
parent e3c14e9f8e
commit 678d750c8a
135 changed files with 8333 additions and 12754 deletions

View File

@ -4,7 +4,7 @@ import (
"sort"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins"
)
// Initializer 初始化器接口
@ -24,14 +24,13 @@ func (p *PluginInitializer) Initialize() error {
var localPlugins []string
// 获取所有注册的插件
allPlugins := base.GlobalPluginRegistry.GetAll()
allPlugins := plugins.GetAllPlugins()
for _, pluginName := range allPlugins {
metadata := base.GlobalPluginRegistry.GetMetadata(pluginName)
if metadata != nil && metadata.Category == "local" {
// 新插件系统中local插件在plugins/local目录中暂时跳过分类
// 后续可以通过插件名或其他方式区分
localPlugins = append(localPlugins, pluginName)
}
}
// 排序以保持一致性
sort.Strings(localPlugins)

View File

@ -3,18 +3,10 @@ package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins"
"strings"
)
/*
BaseScanStrategy.go - 扫描策略基础类
提供所有扫描策略的通用功能包括插件管理验证
日志输出等减少代码重复并提升维护性
*/
// PluginFilterType 插件过滤类型
type PluginFilterType int
@ -39,22 +31,8 @@ func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStra
}
}
// =============================================================================
// 插件管理通用方法
// =============================================================================
// GetPlugins 获取插件列表(通用实现)
// GetPlugins 获取插件列表(简化版)
func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
// 本地模式优先使用LocalPlugin参数
if b.filterType == FilterLocal && common.LocalPlugin != "" {
if GlobalPluginAdapter.PluginExists(common.LocalPlugin) {
return []string{common.LocalPlugin}, true
} else {
common.LogError(fmt.Sprintf("指定的本地插件 '%s' 不存在", common.LocalPlugin))
return []string{}, true
}
}
// 如果指定了特定插件且不是"all"
if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(common.ScanMode)
@ -62,10 +40,10 @@ func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
requestedPlugins = []string{common.ScanMode}
}
// 验证插件是否存在(使用新插件系统)
// 验证插件是否存在
var validPlugins []string
for _, name := range requestedPlugins {
if GlobalPluginAdapter.PluginExists(name) {
if plugins.GetPlugin(name) != nil {
validPlugins = append(validPlugins, name)
}
}
@ -73,52 +51,18 @@ func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
return validPlugins, true
}
// 未指定或使用"all":获取所有插件由IsPluginApplicable做类型过滤
return GlobalPluginAdapter.GetAllPluginNames(), false
// 未指定或使用"all":获取所有插件
return plugins.GetAllPlugins(), false
}
// GetApplicablePlugins 获取适用的插件列表(用于日志显示)
func (b *BaseScanStrategy) GetApplicablePlugins(allPlugins []string, isCustomMode bool) []string {
// IsPluginApplicable 判断插件是否适用(传统接口兼容)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return allPlugins
}
var applicablePlugins []string
for _, pluginName := range allPlugins {
if !GlobalPluginAdapter.PluginExists(pluginName) {
continue
}
if b.isPluginTypeMatchedByName(pluginName) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
return applicablePlugins
}
// isPluginTypeMatchedByName 根据插件名称检查类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
switch b.filterType {
case FilterLocal:
return metadata.Category == "local"
case FilterService:
// 服务扫描允许service类型以及通过智能检测的web类型在上层逻辑中处理
return metadata.Category == "service" || metadata.Category == "web"
case FilterWeb:
return metadata.Category == "web"
default:
return true
}
}
// isPluginTypeMatched 检查插件类型是否匹配过滤器
func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
// 检查插件类型过滤
switch b.filterType {
case FilterLocal:
return plugin.HasType(common.PluginTypeLocal)
@ -132,201 +76,99 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
}
}
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法
// IsPluginApplicableByName 根据插件名称判断是否适用(新接口
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName)
if metadata == nil {
return false
}
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) {
return true
}
// 检查类型匹配
if !b.isPluginTypeMatchedByName(pluginName) {
// 获取插件实例
plugin := plugins.GetPlugin(pluginName)
if plugin == nil {
return false
}
// 检查端口匹配(如果指定了端口)
if targetPort > 0 && len(metadata.Ports) > 0 {
for _, port := range metadata.Ports {
if targetPort > 0 {
pluginPorts := plugin.GetPorts()
if len(pluginPorts) > 0 {
found := false
for _, port := range pluginPorts {
if port == targetPort {
return true
found = true
break
}
}
if !found {
return false
}
// 对于Web插件的特殊处理
if metadata.Category == "web" {
// Web扫描策略下直接允许Web插件执行用户明确指定了Web目标
if b.filterType == FilterWeb {
return true
}
// 其他策略下必须通过智能检测才能执行
return false
}
return true
}
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetHost string, 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.IsWebService(targetHost, targetPort)
}
// globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现,传统插件系统)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 检查插件类型过滤
if !b.isPluginTypeMatched(plugin) {
return false
}
// 对于服务扫描中的Web插件需要进行智能检测传统插件系统
if b.filterType == FilterService && plugin.HasType(common.PluginTypeWeb) {
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 注意传统插件系统无法传递host参数使用空字符串
// 这是传统插件系统的限制,新插件系统已经解决了这个问题
if targetPort > 0 {
return globalWebDetector.IsWebService("", targetPort)
}
return false
}
// 对于服务扫描,还需检查端口匹配
if b.filterType == FilterService {
// 无端口限制的插件适用于所有端口
if len(plugin.Ports) == 0 {
return true
}
// 有端口限制的插件:检查端口是否匹配
if targetPort > 0 {
return plugin.HasPort(targetPort)
}
// 如果没有提供目标端口,则不执行有端口限制的插件
return false
}
return true
}
// =============================================================================
// 日志输出通用方法
// =============================================================================
// LogPluginInfo 输出插件信息(通用实现)
// LogPluginInfo 输出插件信息(简化版)
func (b *BaseScanStrategy) LogPluginInfo() {
allPlugins, isCustomMode := b.GetPlugins()
applicablePlugins := b.GetApplicablePlugins(allPlugins, isCustomMode)
// 生成日志消息
var messageKey, prefix string
var prefix string
switch b.filterType {
case FilterLocal:
messageKey = "scan_plugins_local"
prefix = i18n.GetText("scan_mode_local_prefix")
prefix = "本地插件"
case FilterService:
messageKey = "scan_plugins_service"
prefix = i18n.GetText("scan_mode_service_prefix")
prefix = "服务插件"
case FilterWeb:
messageKey = "scan_plugins_web"
prefix = i18n.GetText("scan_mode_web_prefix")
prefix = "Web插件"
default:
messageKey = "scan_plugins_custom"
prefix = ""
prefix = "插件"
}
if len(applicablePlugins) > 0 {
if len(allPlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText("scan_plugins_custom_specified", strings.Join(applicablePlugins, ", "))))
common.LogBase(fmt.Sprintf("%s: 自定义指定 (%s)", prefix, strings.Join(allPlugins, ", ")))
} else {
common.LogBase(fmt.Sprintf("%s: %s", prefix,
i18n.GetText(messageKey, strings.Join(applicablePlugins, ", "))))
common.LogBase(fmt.Sprintf("%s: %s", prefix, strings.Join(allPlugins, ", ")))
}
} else {
noPluginsKey := fmt.Sprintf("scan_no_%s_plugins", b.getFilterTypeName())
common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText(noPluginsKey)))
common.LogBase(fmt.Sprintf("%s: 无可用插件", prefix))
}
}
// getFilterTypeName 获取过滤器类型名称
func (b *BaseScanStrategy) getFilterTypeName() string {
switch b.filterType {
case FilterLocal:
return "local"
case FilterService:
return "service"
case FilterWeb:
return "web"
default:
return "general"
}
}
// =============================================================================
// 验证通用方法
// =============================================================================
// ValidateConfiguration 验证扫描配置(通用实现)
// ValidateConfiguration 验证扫描配置
func (b *BaseScanStrategy) ValidateConfiguration() error {
return validateScanPlugins()
return nil
}
// =============================================================================
// 通用辅助方法
// =============================================================================
// LogScanStart 输出扫描开始信息
func (b *BaseScanStrategy) LogScanStart() {
switch b.filterType {
case FilterLocal:
common.LogBase(i18n.GetText("scan_local_start"))
common.LogBase("开始本地扫描")
case FilterService:
common.LogBase(i18n.GetText("scan_service_start"))
common.LogBase("开始服务扫描")
case FilterWeb:
common.LogBase(i18n.GetText("scan_web_start"))
common.LogBase("开始Web扫描")
default:
common.LogBase(i18n.GetText("scan_general_start"))
common.LogBase("开始扫描")
}
}
// parsePluginList 解析插件列表字符串
func parsePluginList(pluginStr string) []string {
if pluginStr == "" {
return []string{}
}
// 支持逗号分隔的插件列表
plugins := strings.Split(pluginStr, ",")
var result []string
for _, plugin := range plugins {
plugin = strings.TrimSpace(plugin)
if plugin != "" {
result = append(result, plugin)
}
}
return result
}

View File

@ -4,20 +4,16 @@ import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins"
)
// PluginAdapter 插件适配器
// 提供从新插件系统到旧扫描接口的适配
type PluginAdapter struct {
registry *base.PluginRegistry
}
type PluginAdapter struct{}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
return &PluginAdapter{}
}
// 全局插件适配器实例
@ -25,13 +21,13 @@ var GlobalPluginAdapter = NewPluginAdapter()
// GetAllPluginNames 获取所有插件名称
func (pa *PluginAdapter) GetAllPluginNames() []string {
return pa.registry.GetAll()
return plugins.GetAllPlugins()
}
// PluginExists 检查插件是否存在
func (pa *PluginAdapter) PluginExists(name string) bool {
metadata := pa.registry.GetMetadata(name)
return metadata != nil
plugin := plugins.GetPlugin(name)
return plugin != nil
}
// 已移除未使用的 GetPluginPorts 方法
@ -44,24 +40,32 @@ func (pa *PluginAdapter) PluginExists(name string) bool {
func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo) error {
common.LogDebug(fmt.Sprintf("使用新插件架构扫描: %s", pluginName))
// 创建插件实例
plugin, err := pa.registry.Create(pluginName)
if err != nil {
return fmt.Errorf("创建插件 %s 失败: %v", pluginName, err)
// 获取插件实例
plugin := plugins.GetPlugin(pluginName)
if plugin == nil {
return fmt.Errorf("插件 %s 不存在", pluginName)
}
// 执行扫描
result, err := plugin.Scan(context.Background(), info)
if err != nil {
return fmt.Errorf("插件 %s 扫描失败: %v", pluginName, err)
}
result := plugin.Scan(context.Background(), info)
// 处理扫描结果
if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
// 如果插件支持利用功能且发现了弱密码,执行利用
if exploiter, ok := plugin.(plugins.Exploiter); ok && result.Username != "" {
creds := plugins.Credential{
Username: result.Username,
Password: result.Password,
}
exploitResult := exploiter.Exploit(context.Background(), info, creds)
if exploitResult != nil && exploitResult.Success {
common.LogDebug(fmt.Sprintf("插件 %s 利用成功", pluginName))
}
}
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
}

View File

@ -1,58 +0,0 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"strings"
)
// 插件列表解析和验证
func parsePluginList(pluginStr string) []string {
if pluginStr == "" {
return nil
}
// 按逗号分割并去除每个插件名称两端的空白
plugins := strings.Split(pluginStr, ",")
for i, p := range plugins {
plugins[i] = strings.TrimSpace(p)
}
// 过滤空字符串
var result []string
for _, p := range plugins {
if p != "" {
result = append(result, p)
}
}
return result
}
// 验证扫描插件的有效性
func validateScanPlugins() error {
// 如果未指定扫描模式或使用All模式则无需验证
if common.ScanMode == "" || common.ScanMode == "all" {
return nil
}
// 解析插件列表
plugins := parsePluginList(common.ScanMode)
if len(plugins) == 0 {
plugins = []string{common.ScanMode}
}
// 验证每个插件是否有效(使用新插件系统)
var invalidPlugins []string
for _, plugin := range plugins {
if !GlobalPluginAdapter.PluginExists(plugin) {
invalidPlugins = append(invalidPlugins, plugin)
}
}
if len(invalidPlugins) > 0 {
return fmt.Errorf("无效的插件: %s", strings.Join(invalidPlugins, ", "))
}
return nil
}

View File

@ -1,102 +0,0 @@
package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
// 导入跨平台服务插件(可在所有平台上运行)
_ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "github.com/shadow1ng/fscan/plugins/services/ftp"
_ "github.com/shadow1ng/fscan/plugins/services/imap"
_ "github.com/shadow1ng/fscan/plugins/services/kafka"
_ "github.com/shadow1ng/fscan/plugins/services/ldap"
_ "github.com/shadow1ng/fscan/plugins/services/memcached"
_ "github.com/shadow1ng/fscan/plugins/services/modbus"
_ "github.com/shadow1ng/fscan/plugins/services/mongodb"
_ "github.com/shadow1ng/fscan/plugins/services/mssql"
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/neo4j"
_ "github.com/shadow1ng/fscan/plugins/services/oracle"
_ "github.com/shadow1ng/fscan/plugins/services/pop3"
_ "github.com/shadow1ng/fscan/plugins/services/postgresql"
_ "github.com/shadow1ng/fscan/plugins/services/rabbitmq"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/rsync"
_ "github.com/shadow1ng/fscan/plugins/services/smtp"
_ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet"
_ "github.com/shadow1ng/fscan/plugins/services/vnc"
// 导入跨平台Legacy插件
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞但扫描器可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议主要Windows但可跨平台扫描
_ "github.com/shadow1ng/fscan/plugins/legacy/smbinfo" // SMB信息收集主要Windows但可跨平台扫描
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" // SMBGhost漏洞扫描可跨平台
// 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
// 导入跨平台本地插件(可在所有平台上运行)
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 系统痕迹清理
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 文件下载
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 文件信息收集
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 正向Shell
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 键盘记录主要Windows但支持跨平台
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 反弹Shell
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // SOCKS5代理
)
// =============================================================================
// 新一代插件注册系统 (New Architecture)
// 完全基于工厂模式和自动发现的现代化插件架构
// =============================================================================
// InitializePluginSystem 初始化插件系统
func InitializePluginSystem() error {
common.LogInfo("初始化新一代插件系统...")
// 统计已注册的插件
registeredPlugins := base.GlobalPluginRegistry.GetAll()
common.LogInfo(fmt.Sprintf("已注册插件数量: %d", len(registeredPlugins)))
// 显示已注册的插件列表
if len(registeredPlugins) > 0 {
common.LogInfo("已注册插件:")
for _, name := range registeredPlugins {
metadata := base.GlobalPluginRegistry.GetMetadata(name)
if metadata != nil {
common.LogInfo(fmt.Sprintf(" - %s v%s (%s)",
metadata.Name, metadata.Version, metadata.Category))
}
}
}
common.LogInfo("插件系统初始化完成")
return nil
}
// 已移除未使用的 GetAllPlugins 方法
// 已移除未使用的 GetPluginMetadata 方法
// 已移除未使用的 CreatePlugin 方法
// 已移除未使用的 GetPluginsByCategory 方法
// 已移除未使用的 GetPluginsByPort 方法
// init 自动初始化插件系统
func init() {
if err := InitializePluginSystem(); err != nil {
common.LogError("插件系统初始化失败: " + err.Error())
}
}

View File

@ -3,7 +3,7 @@ package core
import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins"
"strconv"
"strings"
"sync"
@ -108,10 +108,9 @@ func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostIn
// 检查新插件架构
for _, pluginName := range allPlugins {
// 首先检查新插件架构
if factory := base.GlobalPluginRegistry.GetFactory(pluginName); factory != nil {
// 获取插件元数据检查端口匹配
metadata := factory.GetMetadata()
if s.isNewPluginApplicableToAnyPort(metadata, portSet, isCustomMode) {
if plugin := plugins.GetPlugin(pluginName); plugin != nil {
// 检查端口匹配
if s.isNewPluginApplicableToAnyPort(plugin, portSet, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
}
continue
@ -165,25 +164,23 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
}
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
// 服务扫描排除本地插件但保留service和web类型web有智能检测
if metadata.Category == "local" {
return false
}
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(plugin plugins.Plugin, portSet map[int]bool, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 获取插件支持的端口
pluginPorts := plugin.GetPorts()
// 无端口限制的插件适用于所有端口
if len(metadata.Ports) == 0 {
if len(pluginPorts) == 0 {
return true
}
// 有端口限制的插件:检查是否匹配任何目标端口
for port := range portSet {
for _, pluginPort := range metadata.Ports {
for _, pluginPort := range pluginPorts {
if pluginPort == port {
return true
}

View File

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

View File

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

View File

@ -7,6 +7,9 @@ import (
"github.com/shadow1ng/fscan/app"
"github.com/shadow1ng/fscan/common"
// 导入新的单文件插件架构
_ "github.com/shadow1ng/fscan/plugins"
)
func main() {

250
plugins/README.md Normal file
View File

@ -0,0 +1,250 @@
# FScan 插件开发规范
## 概述
FScan 采用简化的单文件插件架构,每个插件一个 `.go` 文件,消除了过度设计的多文件结构。
## 设计原则 (Linus Torvalds "好品味" 原则)
1. **简洁至上**:消除所有不必要的抽象层
2. **直击本质**:专注于解决实际问题,不为架构而架构
3. **向后兼容**:不破坏用户接口和现有功能
4. **消除特殊情况**:统一处理逻辑,减少 if/else 分支
## 插件架构
### 核心接口
```go
// Plugin 插件接口 - 只保留必要的方法
type Plugin interface {
GetName() string // 插件名称
GetPorts() []int // 支持的端口
Scan(ctx context.Context, info *common.HostInfo) *ScanResult // 扫描功能
}
// 可选接口:如果插件支持利用功能
type Exploiter interface {
Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult
}
```
### 数据结构
```go
// ScanResult 扫描结果 - 删除所有冗余字段
type ScanResult struct {
Success bool // 扫描是否成功
Service string // 服务类型
Username string // 发现的用户名(弱密码)
Password string // 发现的密码(弱密码)
Banner string // 服务版本信息
Error error // 错误信息(如果失败)
}
// ExploitResult 利用结果(仅有利用功能的插件需要)
type ExploitResult struct {
Success bool // 利用是否成功
Output string // 命令执行输出
Error error // 错误信息
}
// Credential 凭据结构
type Credential struct {
Username string
Password string
KeyData []byte // SSH私钥等
}
```
## 插件开发模板
### 1. 纯扫描插件如MySQL
```go
package plugins
import (
"context"
"fmt"
// 其他必要导入
)
// PluginName服务扫描插件
type PluginNamePlugin struct {
name string
ports []int
}
// 构造函数
func NewPluginNamePlugin() *PluginNamePlugin {
return &PluginNamePlugin{
name: "plugin_name",
ports: []int{default_port},
}
}
// 实现Plugin接口
func (p *PluginNamePlugin) GetName() string { return p.name }
func (p *PluginNamePlugin) GetPorts() []int { return p.ports }
func (p *PluginNamePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(info)
}
// 生成测试凭据
credentials := GenerateCredentials("plugin_name")
// 逐个测试凭据
for _, cred := range credentials {
select {
case <-ctx.Done():
return &ScanResult{Success: false, Error: ctx.Err()}
default:
}
if p.testCredential(ctx, info, cred) {
return &ScanResult{
Success: true,
Service: "plugin_name",
Username: cred.Username,
Password: cred.Password,
}
}
}
return &ScanResult{Success: false, Service: "plugin_name"}
}
// 核心认证逻辑
func (p *PluginNamePlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 实现具体的认证测试逻辑
return false
}
// 服务识别(-nobr模式
func (p *PluginNamePlugin) identifyService(info *common.HostInfo) *ScanResult {
// 实现服务识别逻辑
return &ScanResult{Success: false, Service: "plugin_name"}
}
// 自动注册
func init() {
RegisterPlugin("plugin_name", func() Plugin {
return NewPluginNamePlugin()
})
}
```
### 2. 带利用功能的插件如SSH
```go
package plugins
// SSH插件结构
type SSHPlugin struct {
name string
ports []int
}
// 同时实现Plugin和Exploiter接口
func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
// 扫描逻辑(同上)
}
func (p *SSHPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立SSH连接
client, err := p.connectSSH(info, creds)
if err != nil {
return &ExploitResult{Success: false, Error: err}
}
defer client.Close()
// 执行命令或其他利用操作
output, err := p.executeCommand(client, "whoami")
return &ExploitResult{
Success: err == nil,
Output: output,
Error: err,
}
}
// 辅助方法
func (p *SSHPlugin) connectSSH(info *common.HostInfo, creds Credential) (*ssh.Client, error) {
// SSH连接实现
}
func (p *SSHPlugin) executeCommand(client *ssh.Client, cmd string) (string, error) {
// 命令执行实现
}
```
## 开发规范
### 文件组织
```
plugins/
├── base.go # 核心接口和注册系统
├── mysql.go # MySQL插件
├── ssh.go # SSH插件
├── redis.go # Redis插件
└── README.md # 开发文档(本文件)
```
### 命名规范
- **插件文件**`{service_name}.go`
- **插件结构体**`{ServiceName}Plugin`
- **构造函数**`New{ServiceName}Plugin()`
- **插件名称**:小写,与文件名一致
### 代码规范
1. **错误处理**始终使用Context进行超时控制
2. **日志输出**:成功时使用 `common.LogSuccess`,调试用 `common.LogDebug`
3. **凭据生成**:使用 `GenerateCredentials(service_name)` 生成测试凭据
4. **资源管理**:及时关闭连接,使用 defer 确保清理
### 测试要求
每个插件必须支持:
1. **暴力破解模式**`common.DisableBrute = false`
2. **服务识别模式**`common.DisableBrute = true`
3. **Context超时处理**:正确响应 `ctx.Done()`
4. **代理支持**:如果 `common.Socks5Proxy` 不为空
## 迁移指南
### 从三文件架构迁移
1. **提取核心逻辑**:从 connector.go 提取认证逻辑
2. **合并实现**:将 plugin.go 中的组装逻辑内联
3. **删除垃圾**:删除空的 exploiter.go
4. **简化数据结构**:只保留必要的字段
### 从Legacy插件迁移
1. **保留核心逻辑**:复制扫描和认证的核心算法
2. **标准化接口**实现统一的Plugin接口
3. **移除全局依赖**:通过返回值而不是全局变量传递结果
4. **统一日志**:使用统一的日志接口
## 性能优化
1. **连接复用**:在同一次扫描中复用连接
2. **内存管理**:及时释放不需要的资源
3. **并发控制**通过Context控制并发度
4. **超时设置**:合理设置各阶段超时时间
## 示例
参考 `mysql.go` 作为标准的纯扫描插件实现
参考 `ssh.go` 作为带利用功能的插件实现
---
**记住:好的代码不是写出来的,是重构出来的。消除所有不必要的复杂性,直击问题本质。**

244
plugins/activemq.go Normal file
View File

@ -0,0 +1,244 @@
package plugins
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// ActiveMQPlugin ActiveMQ消息队列扫描插件 - 基于STOMP协议
type ActiveMQPlugin struct {
name string
ports []int
}
// NewActiveMQPlugin 创建ActiveMQ插件
func NewActiveMQPlugin() *ActiveMQPlugin {
return &ActiveMQPlugin{
name: "activemq",
ports: []int{61616, 61617, 61618, 8161}, // STOMP端口 + Web管理界面
}
}
// GetName 实现Plugin接口
func (p *ActiveMQPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *ActiveMQPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行ActiveMQ扫描 - STOMP协议弱密码检测
func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(info)
}
// 生成测试凭据
credentials := GenerateCredentials("activemq")
if len(credentials) == 0 {
// ActiveMQ默认凭据
credentials = []Credential{
{Username: "admin", Password: "admin"},
{Username: "admin", Password: ""},
{Username: "admin", Password: "password"},
{Username: "user", Password: "user"},
{Username: "guest", Password: "guest"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "activemq",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// ActiveMQ认证成功
common.LogSuccess(i18n.GetText("activemq_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "activemq",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据 - STOMP协议认证
func (p *ActiveMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
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 false
}
defer conn.Close()
// 使用STOMP协议进行认证
return p.authenticateSTOMP(conn, cred.Username, cred.Password)
}
// authenticateSTOMP 使用STOMP协议进行身份验证
// STOMP (Simple Text Oriented Messaging Protocol) 是ActiveMQ支持的文本协议
func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string) bool {
timeout := time.Duration(common.Timeout) * time.Second
// 构造STOMP CONNECT帧
// STOMP是基于帧的协议每个帧以NULL字符结尾
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00",
username, password)
// 设置写超时并发送认证请求
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
return false
}
// 设置读超时并读取响应
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil || n == 0 {
return false
}
responseStr := string(response[:n])
// 检查STOMP响应
// 成功响应应该包含"CONNECTED"帧
// 失败响应包含"ERROR"帧
if strings.Contains(responseStr, "CONNECTED") {
return true
} else if strings.Contains(responseStr, "ERROR") {
// 错误响应,认证失败
return false
}
// 未知响应格式
return false
}
// identifyService 服务识别 - 检测STOMP协议
func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试连接
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
return &ScanResult{
Success: false,
Service: "activemq",
Error: err,
}
}
defer conn.Close()
// 发送简单的STOMP CONNECT帧无认证信息
stompConnect := "CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\n\n\x00"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法发送STOMP请求: %v", err),
}
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil || n == 0 {
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法读取响应"),
}
}
responseStr := string(response[:n])
// 检查是否为STOMP协议响应
if strings.Contains(responseStr, "CONNECTED") || strings.Contains(responseStr, "ERROR") {
banner := p.extractSTOMPVersion(responseStr)
common.LogSuccess(i18n.GetText("activemq_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "activemq",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法识别为ActiveMQ STOMP服务"),
}
}
// extractSTOMPVersion 从STOMP响应中提取版本信息
func (p *ActiveMQPlugin) extractSTOMPVersion(response string) string {
lines := strings.Split(response, "\n")
for _, line := range lines {
// 查找version头
if strings.HasPrefix(line, "version:") {
version := strings.TrimPrefix(line, "version:")
return fmt.Sprintf("ActiveMQ STOMP %s", version)
}
// 查找server头
if strings.HasPrefix(line, "server:") {
server := strings.TrimPrefix(line, "server:")
return fmt.Sprintf("ActiveMQ %s", server)
}
}
// 如果没有找到版本信息,返回通用描述
if strings.Contains(response, "CONNECTED") {
return "ActiveMQ STOMP (版本未知)"
} else if strings.Contains(response, "ERROR") {
return "ActiveMQ STOMP (需要认证)"
}
return "ActiveMQ STOMP"
}
// init 自动注册插件
func init() {
RegisterPlugin("activemq", func() Plugin {
return NewActiveMQPlugin()
})
}

View File

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

105
plugins/base.go Normal file
View File

@ -0,0 +1,105 @@
package plugins
import (
"context"
"github.com/shadow1ng/fscan/common"
)
// =============================================================================
// 简化的插件基础架构 - 删除所有过度设计
// =============================================================================
// Plugin 插件接口 - 只保留必要的方法
type Plugin interface {
GetName() string
GetPorts() []int
Scan(ctx context.Context, info *common.HostInfo) *ScanResult
}
// Exploiter 利用器接口 - 可选实现,用于有利用功能的插件
type Exploiter interface {
Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult
}
// ScanResult 扫描结果 - 删除所有冗余字段
type ScanResult struct {
Success bool // 扫描是否成功
Service string // 服务类型
Username string // 发现的用户名(弱密码)
Password string // 发现的密码(弱密码)
Banner string // 服务版本信息
Error error // 错误信息(如果失败)
}
// ExploitResult 利用结果 - 仅有利用功能的插件需要
type ExploitResult struct {
Success bool // 利用是否成功
Output string // 命令执行输出或操作结果
Error error // 错误信息
}
// Credential 凭据结构 - 只保留必要字段
type Credential struct {
Username string
Password string
KeyData []byte // SSH私钥等二进制数据
}
// =============================================================================
// 简化的注册系统 - 删除工厂模式垃圾
// =============================================================================
// PluginCreator 插件创建函数类型
type PluginCreator func() Plugin
// 全局插件注册表
var registry = make(map[string]PluginCreator)
// RegisterPlugin 注册插件 - 直接注册函数,不要工厂
func RegisterPlugin(name string, creator PluginCreator) {
registry[name] = creator
}
// GetPlugin 获取插件实例
func GetPlugin(name string) Plugin {
if creator, exists := registry[name]; exists {
return creator()
}
return nil
}
// GetAllPlugins 获取所有已注册插件名称
func GetAllPlugins() []string {
names := make([]string, 0, len(registry))
for name := range registry {
names = append(names, name)
}
return names
}
// =============================================================================
// 通用辅助函数
// =============================================================================
// GenerateCredentials 生成测试凭据列表
func GenerateCredentials(service string) []Credential {
// 获取用户名字典
usernames := common.Userdict[service]
if len(usernames) == 0 {
// 默认用户名
usernames = []string{"root", "admin", service}
}
// 生成用户名密码组合
var credentials []Credential
for _, username := range usernames {
for _, password := range common.Passwords {
credentials = append(credentials, Credential{
Username: username,
Password: password,
})
}
}
return credentials
}

View File

@ -1,249 +0,0 @@
package base
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"sort"
)
// =============================================================================
// 通用利用器基础实现
// =============================================================================
// BaseExploiter 基础利用器,提供通用的利用逻辑
type BaseExploiter struct {
Name string
exploitMethods []ExploitMethod
}
// NewBaseExploiter 创建基础利用器
func NewBaseExploiter(name string) *BaseExploiter {
return &BaseExploiter{
Name: name,
exploitMethods: make([]ExploitMethod, 0),
}
}
// AddExploitMethod 添加利用方法
func (e *BaseExploiter) AddExploitMethod(method ExploitMethod) {
e.exploitMethods = append(e.exploitMethods, method)
// 按优先级排序
sort.Slice(e.exploitMethods, func(i, j int) bool {
return e.exploitMethods[i].Priority > e.exploitMethods[j].Priority
})
}
// GetExploitMethods 获取支持的利用方法
func (e *BaseExploiter) GetExploitMethods() []ExploitMethod {
return e.exploitMethods
}
// IsExploitSupported 检查是否支持指定的利用方法
func (e *BaseExploiter) IsExploitSupported(exploitType ExploitType) bool {
for _, method := range e.exploitMethods {
if method.Type == exploitType {
return true
}
}
return false
}
// Exploit 执行利用操作
func (e *BaseExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *Credential) (*ExploitResult, error) {
// 按优先级尝试不同的利用方法
for _, method := range e.exploitMethods {
// 检查前置条件
if !e.checkConditions(method.Conditions, info, creds) {
common.LogDebug(i18n.GetText("exploit_method_condition_not_met", method.Name))
continue
}
common.LogDebug(i18n.GetText("exploit_method_trying", i18n.GetExploitMethodName(method.Name)))
// 执行利用
result, err := method.Handler(ctx, info, creds)
if err != nil {
common.LogError(i18n.GetText("exploit_method_failed", method.Name, err))
continue
}
if result != nil && result.Success {
common.LogSuccess(i18n.GetText("exploit_method_success", i18n.GetExploitMethodName(method.Name)))
result.Type = method.Type
result.Method = method.Name
return result, nil
}
}
return nil, fmt.Errorf(i18n.GetText("exploit_all_methods_failed"))
}
// checkConditions 检查前置条件
func (e *BaseExploiter) checkConditions(conditions []string, info *common.HostInfo, creds *Credential) bool {
for _, condition := range conditions {
if !e.evaluateCondition(condition, info, creds) {
return false
}
}
return true
}
// evaluateCondition 评估单个条件
func (e *BaseExploiter) evaluateCondition(condition string, info *common.HostInfo, creds *Credential) bool {
switch condition {
case "has_credentials":
return creds != nil && (creds.Username != "" || creds.Password != "")
case "has_username_password":
return creds != nil && creds.Username != "" && creds.Password != ""
case "has_password_only":
return creds != nil && creds.Password != "" && creds.Username == ""
case "unauthorized_access":
return creds == nil || (creds.Username == "" && creds.Password == "")
default:
// 默认条件满足
return true
}
}
// =============================================================================
// 常用利用方法实现
// =============================================================================
// ExploitMethodBuilder 利用方法构建器
type ExploitMethodBuilder struct {
method ExploitMethod
}
// NewExploitMethod 创建利用方法构建器
func NewExploitMethod(exploitType ExploitType, name string) *ExploitMethodBuilder {
return &ExploitMethodBuilder{
method: ExploitMethod{
Type: exploitType,
Name: name,
Priority: 5, // 默认优先级
Conditions: make([]string, 0),
},
}
}
// WithDescription 设置描述
func (b *ExploitMethodBuilder) WithDescription(desc string) *ExploitMethodBuilder {
b.method.Description = desc
return b
}
// WithPriority 设置优先级
func (b *ExploitMethodBuilder) WithPriority(priority int) *ExploitMethodBuilder {
b.method.Priority = priority
return b
}
// WithConditions 设置前置条件
func (b *ExploitMethodBuilder) WithConditions(conditions ...string) *ExploitMethodBuilder {
b.method.Conditions = conditions
return b
}
// WithHandler 设置处理函数
func (b *ExploitMethodBuilder) WithHandler(handler ExploitHandler) *ExploitMethodBuilder {
b.method.Handler = handler
return b
}
// Build 构建利用方法
func (b *ExploitMethodBuilder) Build() ExploitMethod {
return b.method
}
// =============================================================================
// 利用结果处理工具
// =============================================================================
// SaveExploitResult 保存利用结果
func SaveExploitResult(info *common.HostInfo, result *ExploitResult, pluginName string) {
if result == nil || !result.Success {
return
}
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
var message string
switch result.Type {
case ExploitWeakPassword:
message = i18n.GetText("exploit_weak_password_success", pluginName, target)
case ExploitUnauthorized:
message = i18n.GetText("exploit_unauthorized_success", pluginName, target)
case ExploitCommandExec:
message = i18n.GetText("exploit_command_exec_success", pluginName, target)
case ExploitFileWrite:
message = i18n.GetText("exploit_file_write_success", pluginName, target)
case ExploitSQLInjection:
message = i18n.GetText("exploit_sql_injection_success", pluginName, target)
case ExploitDataExtraction:
message = i18n.GetText("exploit_data_extraction_success", pluginName, target, i18n.GetExploitMethodName(result.Method))
default:
message = i18n.GetText("exploit_generic_success", pluginName, target, i18n.GetExploitMethodName(result.Method))
}
if result.Output != "" {
message += i18n.GetText("exploit_with_output", result.Output)
}
common.LogSuccess(message)
// 保存文件信息
if len(result.Files) > 0 {
common.LogSuccess(i18n.GetText("exploit_files_created", result.Files))
}
// 保存Shell信息
if result.Shell != nil {
common.LogSuccess(i18n.GetText("exploit_shell_obtained",
result.Shell.Type, result.Shell.Host, result.Shell.Port, result.Shell.User))
}
}
// =============================================================================
// 常用利用工具函数
// =============================================================================
// CreateSuccessExploitResult 创建成功的利用结果
func CreateSuccessExploitResult(exploitType ExploitType, method string) *ExploitResult {
return &ExploitResult{
Success: true,
Type: exploitType,
Method: method,
Extra: make(map[string]interface{}),
}
}
// CreateFailedExploitResult 创建失败的利用结果
func CreateFailedExploitResult(exploitType ExploitType, method string, err error) *ExploitResult {
return &ExploitResult{
Success: false,
Type: exploitType,
Method: method,
Error: err,
Extra: make(map[string]interface{}),
}
}
// AddOutputToResult 向结果添加输出
func AddOutputToResult(result *ExploitResult, output string) {
if result.Output == "" {
result.Output = output
} else {
result.Output += "\n" + output
}
}
// AddFileToResult 向结果添加文件
func AddFileToResult(result *ExploitResult, filename string) {
if result.Files == nil {
result.Files = make([]string, 0)
}
result.Files = append(result.Files, filename)
}

View File

@ -1,162 +0,0 @@
package base
import (
"context"
"github.com/shadow1ng/fscan/common"
)
// =============================================================================
// 核心接口定义
// =============================================================================
// Scanner 扫描器接口 - 负责发现和识别服务
type Scanner interface {
// Scan 执行扫描操作
Scan(ctx context.Context, info *common.HostInfo) (*ScanResult, error)
// GetName 获取扫描器名称
GetName() string
// GetCapabilities 获取扫描器支持的能力
GetCapabilities() []Capability
}
// Exploiter 利用器接口 - 负责各种攻击利用
type Exploiter interface {
// Exploit 执行利用操作
Exploit(ctx context.Context, info *common.HostInfo, creds *Credential) (*ExploitResult, error)
// GetExploitMethods 获取支持的利用方法
GetExploitMethods() []ExploitMethod
// IsExploitSupported 检查是否支持指定的利用方法
IsExploitSupported(method ExploitType) bool
}
// Plugin 完整插件接口 - 组合扫描和利用功能
type Plugin interface {
Scanner
Exploiter
// Initialize 初始化插件
Initialize() error
// GetMetadata 获取插件元数据
GetMetadata() *PluginMetadata
}
// =============================================================================
// 支持类型定义
// =============================================================================
// Capability 插件能力类型
type Capability string
const (
CapWeakPassword Capability = "weak_password" // 弱密码检测
CapUnauthorized Capability = "unauthorized" // 未授权访问
CapSQLInjection Capability = "sql_injection" // SQL注入
CapCommandExecution Capability = "command_execution" // 命令执行
CapFileUpload Capability = "file_upload" // 文件上传
CapFileWrite Capability = "file_write" // 文件写入
CapPrivilegeEsc Capability = "privilege_esc" // 提权
CapDataExtraction Capability = "data_extraction" // 数据提取
CapDenialOfService Capability = "denial_of_service" // 拒绝服务
CapInformationLeak Capability = "information_leak" // 信息泄露
)
// ExploitType 利用类型
type ExploitType string
const (
ExploitWeakPassword ExploitType = "weak_password"
ExploitUnauthorized ExploitType = "unauthorized"
ExploitSQLInjection ExploitType = "sql_injection"
ExploitCommandExec ExploitType = "command_exec"
ExploitFileWrite ExploitType = "file_write"
ExploitPrivilegeEsc ExploitType = "privilege_esc"
ExploitDataExtraction ExploitType = "data_extraction"
)
// ExploitMethod 利用方法定义
type ExploitMethod struct {
Type ExploitType // 利用类型
Name string // 方法名称
Description string // 描述
Priority int // 优先级1-1010最高
Conditions []string // 前置条件
Handler ExploitHandler // 处理函数
}
// ExploitHandler 利用处理函数类型
type ExploitHandler func(ctx context.Context, info *common.HostInfo, creds *Credential) (*ExploitResult, error)
// =============================================================================
// 数据结构定义
// =============================================================================
// PluginMetadata 插件元数据
type PluginMetadata struct {
Name string // 插件名称
Version string // 版本
Author string // 作者
Description string // 描述
Category string // 分类service/web/local
Ports []int // 默认端口
Protocols []string // 支持的协议
Tags []string // 标签
}
// Credential 通用凭据结构
type Credential struct {
Username string // 用户名
Password string // 密码
Domain string // 域名用于AD等
KeyData []byte // 密钥数据SSH私钥等
Token string // 令牌
Extra map[string]string // 扩展字段
}
// ScanResult 扫描结果
type ScanResult struct {
Success bool // 是否成功
Error error // 错误信息
Service string // 服务类型
Version string // 版本信息
Banner string // 服务横幅
Credentials []*Credential // 发现的凭据
Vulnerabilities []Vulnerability // 发现的漏洞
Extra map[string]interface{} // 扩展信息
}
// ExploitResult 利用结果
type ExploitResult struct {
Success bool // 是否成功
Error error // 错误信息
Type ExploitType // 利用类型
Method string // 利用方法
Output string // 命令输出或结果
Files []string // 创建/修改的文件
Shell *ShellInfo // 获得的Shell信息
Data map[string]interface{} // 提取的数据
Extra map[string]interface{} // 扩展信息
}
// Vulnerability 漏洞信息
type Vulnerability struct {
ID string // 漏洞ID (CVE等)
Name string // 漏洞名称
Severity string // 严重程度
Description string // 描述
References []string // 参考链接
}
// ShellInfo Shell信息
type ShellInfo struct {
Type string // Shell类型reverse/bind/webshell
Host string // 连接主机
Port int // 连接端口
User string // 运行用户
OS string // 操作系统
Privileges string // 权限级别
}

View File

@ -1,259 +0,0 @@
package base
import (
"context"
"fmt"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// =============================================================================
// 完整插件基础实现
// =============================================================================
// BasePlugin 基础插件实现,组合扫描和利用功能
type BasePlugin struct {
*BaseScanner
*BaseExploiter
metadata *PluginMetadata
initialized bool
}
// NewBasePlugin 创建基础插件
func NewBasePlugin(metadata *PluginMetadata) *BasePlugin {
return &BasePlugin{
BaseScanner: NewBaseScanner(metadata.Name, metadata),
BaseExploiter: NewBaseExploiter(metadata.Name),
metadata: metadata,
initialized: false,
}
}
// Initialize 初始化插件
func (p *BasePlugin) Initialize() error {
if p.initialized {
return nil
}
// 执行插件特定的初始化逻辑
common.LogDebug(i18n.GetText("plugin_init", p.metadata.Name))
p.initialized = true
return nil
}
// GetMetadata 获取插件元数据
func (p *BasePlugin) GetMetadata() *PluginMetadata {
return p.metadata
}
// =============================================================================
// 通用插件实现模板
// =============================================================================
// ServicePlugin 服务插件模板 - 提供常见的服务扫描模式
type ServicePlugin struct {
*BasePlugin
credentialScanner CredentialScanner
serviceConnector ServiceConnector
}
// ServiceConnector 服务连接器接口
type ServiceConnector interface {
// Connect 连接到服务
Connect(ctx context.Context, info *common.HostInfo) (interface{}, error)
// Authenticate 认证
Authenticate(ctx context.Context, conn interface{}, cred *Credential) error
// Close 关闭连接
Close(conn interface{}) error
}
// NewServicePlugin 创建服务插件
func NewServicePlugin(metadata *PluginMetadata, connector ServiceConnector) *ServicePlugin {
plugin := &ServicePlugin{
BasePlugin: NewBasePlugin(metadata),
serviceConnector: connector,
}
// 设置自己为凭据扫描器
plugin.credentialScanner = plugin
return plugin
}
// Scan 服务扫描实现
func (p *ServicePlugin) Scan(ctx context.Context, info *common.HostInfo) (*ScanResult, error) {
// 检查是否禁用暴力破解
if common.DisableBrute {
return &ScanResult{
Success: false,
Error: fmt.Errorf(i18n.GetText("plugin_brute_disabled")),
}, nil
}
// 生成凭据列表
credentials := p.generateCredentials()
if len(credentials) == 0 {
return &ScanResult{
Success: false,
Error: fmt.Errorf(i18n.GetText("plugin_no_credentials")),
}, nil
}
// 执行并发扫描
config := &ConcurrentScanConfig{
MaxConcurrent: common.ModuleThreadNum,
Timeout: time.Duration(common.Timeout) * time.Second,
MaxRetries: common.MaxRetries,
}
return ConcurrentCredentialScan(ctx, p.credentialScanner, info, credentials, config)
}
// ScanCredential 实现CredentialScanner接口
func (p *ServicePlugin) ScanCredential(ctx context.Context, info *common.HostInfo, cred *Credential) (*ScanResult, error) {
// 连接到服务
conn, err := p.serviceConnector.Connect(ctx, info)
if err != nil {
return &ScanResult{
Success: false,
Error: fmt.Errorf("连接失败: %v", err),
}, nil
}
defer p.serviceConnector.Close(conn)
// 尝试认证
err = p.serviceConnector.Authenticate(ctx, conn, cred)
if err != nil {
return &ScanResult{
Success: false,
Error: fmt.Errorf("认证失败: %v", err),
}, nil
}
// 认证成功
result := &ScanResult{
Success: true,
Service: p.metadata.Name,
Credentials: []*Credential{cred},
Extra: make(map[string]interface{}),
}
return result, nil
}
// generateCredentials 生成凭据列表(需要子类重写)
func (p *ServicePlugin) generateCredentials() []*Credential {
// 默认实现:从通用字典生成
serviceName := p.metadata.Name
usernames := common.Userdict[serviceName]
if len(usernames) == 0 {
usernames = []string{"admin", "root", serviceName}
}
return GenerateCredentials(usernames, common.Passwords)
}
// GetServiceConnector 获取服务连接器(提供给子插件访问)
func (p *ServicePlugin) GetServiceConnector() ServiceConnector {
return p.serviceConnector
}
// =============================================================================
// 插件工厂
// =============================================================================
// PluginFactory 插件工厂接口
type PluginFactory interface {
CreatePlugin() Plugin
GetMetadata() *PluginMetadata
}
// SimplePluginFactory 简单插件工厂
type SimplePluginFactory struct {
metadata *PluginMetadata
creator func() Plugin
}
// NewSimplePluginFactory 创建简单插件工厂
func NewSimplePluginFactory(metadata *PluginMetadata, creator func() Plugin) *SimplePluginFactory {
return &SimplePluginFactory{
metadata: metadata,
creator: creator,
}
}
// CreatePlugin 创建插件实例
func (f *SimplePluginFactory) CreatePlugin() Plugin {
return f.creator()
}
// GetMetadata 获取插件元数据
func (f *SimplePluginFactory) GetMetadata() *PluginMetadata {
return f.metadata
}
// =============================================================================
// 插件注册管理器
// =============================================================================
// PluginRegistry 插件注册表
type PluginRegistry struct {
factories map[string]PluginFactory
}
// NewPluginRegistry 创建插件注册表
func NewPluginRegistry() *PluginRegistry {
return &PluginRegistry{
factories: make(map[string]PluginFactory),
}
}
// Register 注册插件工厂
func (r *PluginRegistry) Register(name string, factory PluginFactory) {
r.factories[name] = factory
}
// Create 创建插件实例
func (r *PluginRegistry) Create(name string) (Plugin, error) {
factory, exists := r.factories[name]
if !exists {
return nil, fmt.Errorf("插件 %s 未注册", name)
}
plugin := factory.CreatePlugin()
if err := plugin.Initialize(); err != nil {
return nil, fmt.Errorf("插件初始化失败: %v", err)
}
return plugin, nil
}
// GetAll 获取所有注册的插件名称
func (r *PluginRegistry) GetAll() []string {
names := make([]string, 0, len(r.factories))
for name := range r.factories {
names = append(names, name)
}
return names
}
// GetMetadata 获取插件元数据
func (r *PluginRegistry) GetMetadata(name string) *PluginMetadata {
factory, exists := r.factories[name]
if !exists {
return nil
}
return factory.GetMetadata()
}
// GetFactory 获取插件工厂
func (r *PluginRegistry) GetFactory(name string) PluginFactory {
return r.factories[name]
}
// 全局插件注册表
var GlobalPluginRegistry = NewPluginRegistry()

View File

@ -1,267 +0,0 @@
package base
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"strings"
"sync"
"time"
)
// =============================================================================
// 通用扫描器基础实现
// =============================================================================
// BaseScanner 基础扫描器,提供通用的扫描逻辑
type BaseScanner struct {
Name string
metadata *PluginMetadata
capabilities []Capability
}
// NewBaseScanner 创建基础扫描器
func NewBaseScanner(name string, metadata *PluginMetadata) *BaseScanner {
return &BaseScanner{
Name: name,
metadata: metadata,
}
}
// GetName 获取扫描器名称
func (s *BaseScanner) GetName() string {
return s.Name
}
// GetCapabilities 获取扫描器支持的能力
func (s *BaseScanner) GetCapabilities() []Capability {
return s.capabilities
}
// SetCapabilities 设置扫描器能力
func (s *BaseScanner) SetCapabilities(caps []Capability) {
s.capabilities = caps
}
// GetMetadata 获取插件元数据
func (s *BaseScanner) GetMetadata() *PluginMetadata {
return s.metadata
}
// =============================================================================
// 通用并发扫描框架
// =============================================================================
// ConcurrentScanConfig 并发扫描配置
type ConcurrentScanConfig struct {
MaxConcurrent int // 最大并发数
Timeout time.Duration // 单次扫描超时
MaxRetries int // 最大重试次数
RetryDelay time.Duration // 重试延迟
}
// CredentialScanner 凭据扫描器接口
type CredentialScanner interface {
// ScanCredential 扫描单个凭据
ScanCredential(ctx context.Context, info *common.HostInfo, cred *Credential) (*ScanResult, error)
}
// ConcurrentCredentialScan 并发凭据扫描通用实现
func ConcurrentCredentialScan(
ctx context.Context,
scanner CredentialScanner,
info *common.HostInfo,
credentials []*Credential,
config *ConcurrentScanConfig,
) (*ScanResult, error) {
if len(credentials) == 0 {
return nil, fmt.Errorf("没有提供凭据")
}
// 设置默认配置
if config == nil {
config = &ConcurrentScanConfig{
MaxConcurrent: 10,
Timeout: time.Duration(common.Timeout) * time.Second,
MaxRetries: common.MaxRetries,
RetryDelay: 500 * time.Millisecond,
}
}
// 限制并发数
maxConcurrent := config.MaxConcurrent
if maxConcurrent <= 0 {
maxConcurrent = 10
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *ScanResult, 1)
workChan := make(chan *Credential, 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:
// 开始监控连接
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
monitor := common.GetConcurrencyMonitor()
monitor.StartConnection("credential", target)
result := scanCredentialWithRetry(scanCtx, scanner, info, credential, config)
// 完成连接监控
monitor.FinishConnection("credential", target)
if result != nil && result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
for _, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result, nil
}
return nil, fmt.Errorf(i18n.GetText("plugin_all_creds_failed"))
case <-ctx.Done():
scanCancel()
return nil, ctx.Err()
}
}
// scanCredentialWithRetry 带重试的单凭据扫描
func scanCredentialWithRetry(
ctx context.Context,
scanner CredentialScanner,
info *common.HostInfo,
cred *Credential,
config *ConcurrentScanConfig,
) *ScanResult {
for retry := 0; retry < config.MaxRetries; retry++ {
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Error: ctx.Err(),
}
default:
if retry > 0 {
time.Sleep(config.RetryDelay)
}
// 创建独立的超时上下文
connCtx, cancel := context.WithTimeout(ctx, config.Timeout)
result, err := scanner.ScanCredential(connCtx, info, cred)
cancel()
if result != nil && result.Success {
return result
}
// 检查是否需要重试
if err != nil && !shouldRetry(err) {
break
}
}
}
return &ScanResult{
Success: false,
Error: fmt.Errorf("重试次数耗尽"),
}
}
// shouldRetry 判断是否应该重试
func shouldRetry(err error) bool {
if err == nil {
return false
}
errStr := strings.ToLower(err.Error())
// 不需要重试的错误
noRetryErrors := []string{
"access denied",
"authentication failed",
"invalid credentials",
"permission denied",
"unauthorized",
}
for _, noRetry := range noRetryErrors {
if strings.Contains(errStr, noRetry) {
return false
}
}
return true
}
// =============================================================================
// 凭据生成工具
// =============================================================================
// GenerateCredentials 生成用户名密码组合的凭据列表
func GenerateCredentials(usernames []string, passwords []string) []*Credential {
var credentials []*Credential
for _, username := range usernames {
for _, password := range passwords {
// 支持 {user} 占位符替换
actualPassword := strings.ReplaceAll(password, "{user}", username)
credentials = append(credentials, &Credential{
Username: username,
Password: actualPassword,
Extra: make(map[string]string),
})
}
}
return credentials
}
// 已移除未使用的 GeneratePasswordOnlyCredentials 方法

320
plugins/cassandra.go Normal file
View File

@ -0,0 +1,320 @@
package plugins
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// CassandraPlugin Cassandra数据库扫描插件 - 基于gocql库
type CassandraPlugin struct {
name string
ports []int
}
// NewCassandraPlugin 创建Cassandra插件
func NewCassandraPlugin() *CassandraPlugin {
return &CassandraPlugin{
name: "cassandra",
ports: []int{9042, 9160, 7000, 7001}, // CQL端口 + Thrift端口 + 集群通信端口
}
}
// GetName 实现Plugin接口
func (p *CassandraPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *CassandraPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Cassandra扫描 - CQL协议弱密码检测
func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("cassandra")
if len(credentials) == 0 {
// Cassandra默认凭据
credentials = []Credential{
{Username: "cassandra", Password: "cassandra"},
{Username: "admin", Password: "admin"},
{Username: "admin", Password: ""},
{Username: "root", Password: "root"},
{Username: "user", Password: "user"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "cassandra",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// Cassandra认证成功
common.LogSuccess(i18n.GetText("cassandra_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "cassandra",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "cassandra",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据 - 使用gocql进行CQL认证
func (p *CassandraPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return false
}
// 创建Cassandra集群配置
cluster := gocql.NewCluster(info.Host)
cluster.Port = port
// 设置连接参数
timeout := time.Duration(common.Timeout) * time.Second
cluster.Timeout = timeout
cluster.ConnectTimeout = timeout
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
// 设置认证信息
if cred.Username != "" || cred.Password != "" {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: cred.Username,
Password: cred.Password,
}
}
// 支持代理连接
if common.Socks5Proxy != "" {
cluster.Dialer = &cassandraProxyDialer{timeout: timeout}
}
// 设置重试策略
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 2}
// 创建会话
session, err := p.createSessionWithTimeout(ctx, cluster)
if err != nil {
return false
}
defer session.Close()
// 执行简单查询验证连接
return p.validateConnection(ctx, session)
}
// createSessionWithTimeout 在超时控制下创建Cassandra会话
func (p *CassandraPlugin) createSessionWithTimeout(ctx context.Context, cluster *gocql.ClusterConfig) (*gocql.Session, error) {
// 创建会话通道以支持Context超时
sessionChan := make(chan struct {
session *gocql.Session
err error
}, 1)
// 在goroutine中创建会话
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}:
}
}()
// 等待会话创建或Context取消
select {
case result := <-sessionChan:
return result.session, result.err
case <-ctx.Done():
return nil, fmt.Errorf("创建会话超时: %v", ctx.Err())
}
}
// validateConnection 验证Cassandra连接 - 执行系统查询
func (p *CassandraPlugin) validateConnection(ctx context.Context, session *gocql.Session) bool {
// 使用通道支持Context超时
resultChan := make(chan bool, 1)
go func() {
// 尝试执行系统查询
// 先尝试查询节点信息,如果失败再尝试本地信息
var dummy interface{}
err := session.Query("SELECT peer FROM system.peers LIMIT 1").WithContext(ctx).Scan(&dummy)
if err != nil {
// 如果peer查询失败尝试查询本地节点信息
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&dummy)
}
select {
case <-ctx.Done():
case resultChan <- (err == nil):
}
}()
// 等待查询结果或Context取消
select {
case success := <-resultChan:
return success
case <-ctx.Done():
return false
}
}
// identifyService 服务识别 - 尝试连接Cassandra但不认证
func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 解析端口
port, err := strconv.Atoi(info.Ports)
if err != nil {
return &ScanResult{
Success: false,
Service: "cassandra",
Error: fmt.Errorf("无效的端口号: %s", info.Ports),
}
}
// 创建基本集群配置(无认证)
cluster := gocql.NewCluster(info.Host)
cluster.Port = port
timeout := time.Duration(common.Timeout) * time.Second
cluster.Timeout = timeout
cluster.ConnectTimeout = timeout
cluster.ProtoVersion = 4
// 支持代理
if common.Socks5Proxy != "" {
cluster.Dialer = &cassandraProxyDialer{timeout: timeout}
}
// 尝试创建会话
session, err := p.createSessionWithTimeout(ctx, cluster)
if err != nil {
// 检查错误类型,判断是否为认证问题
if p.isAuthenticationError(err) {
// 认证错误说明服务存在但需要密码
common.LogSuccess(i18n.GetText("cassandra_service_identified", target, "Cassandra (需要认证)"))
return &ScanResult{
Success: true,
Service: "cassandra",
Banner: "Cassandra (需要认证)",
}
}
return &ScanResult{
Success: false,
Service: "cassandra",
Error: err,
}
}
defer session.Close()
// 连接成功获取Cassandra版本信息
banner := p.getCassandraVersion(ctx, session)
common.LogSuccess(i18n.GetText("cassandra_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "cassandra",
Banner: banner,
}
}
// getCassandraVersion 获取Cassandra版本信息
func (p *CassandraPlugin) getCassandraVersion(ctx context.Context, session *gocql.Session) string {
// 尝试查询系统版本信息
var releaseVersion string
err := session.Query("SELECT release_version FROM system.local").WithContext(ctx).Scan(&releaseVersion)
if err == nil && releaseVersion != "" {
return fmt.Sprintf("Cassandra %s", releaseVersion)
}
// 如果无法获取版本,返回通用描述
return "Cassandra (版本未知)"
}
// isAuthenticationError 判断是否为认证错误
func (p *CassandraPlugin) isAuthenticationError(err error) bool {
if err == nil {
return false
}
errStr := err.Error()
// 检查常见的认证错误信息
authErrors := []string{
"authentication",
"password",
"credentials",
"unauthorized",
"login",
}
for _, authErr := range authErrors {
if strings.Contains(strings.ToLower(errStr), authErr) {
return true
}
}
return false
}
// cassandraProxyDialer 代理拨号器
type cassandraProxyDialer struct {
timeout time.Duration
}
// DialContext 实现gocql.Dialer接口
func (d *cassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return common.WrapperTcpWithContext(ctx, network, addr)
}
// init 自动注册插件
func init() {
RegisterPlugin("cassandra", func() Plugin {
return NewCassandraPlugin()
})
}

319
plugins/ftp.go Normal file
View File

@ -0,0 +1,319 @@
package plugins
import (
"context"
"fmt"
"strings"
"time"
ftplib "github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// FTPPlugin FTP服务扫描和利用插件 - 包含文件操作利用功能
type FTPPlugin struct {
name string
ports []int
}
// NewFTPPlugin 创建FTP插件
func NewFTPPlugin() *FTPPlugin {
return &FTPPlugin{
name: "ftp",
ports: []int{21, 2121, 990}, // FTP端口 + FTPS端口
}
}
// GetName 实现Plugin接口
func (p *FTPPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *FTPPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行FTP扫描 - 弱密码检测和匿名访问检测
func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(info)
}
// 首先检查匿名访问
if result := p.testAnonymousAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("ftp_anonymous_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("ftp")
if len(credentials) == 0 {
// FTP默认凭据
credentials = []Credential{
{Username: "ftp", Password: "ftp"},
{Username: "admin", Password: "admin"},
{Username: "admin", Password: ""},
{Username: "user", Password: "user"},
{Username: "test", Password: "test"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "ftp",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if conn := p.testCredential(ctx, info, cred); conn != nil {
conn.Quit() // 关闭测试连接
// FTP认证成功
common.LogSuccess(i18n.GetText("ftp_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "ftp",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "ftp",
Error: fmt.Errorf("未发现弱密码或匿名访问"),
}
}
// Exploit 执行FTP利用操作 - 实现文件操作功能
func (p *FTPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立FTP连接
conn := p.testCredential(ctx, info, creds)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("FTP连接失败"),
}
}
defer conn.Quit()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("FTP利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== FTP利用结果 - %s ===\n", target))
// 获取当前工作目录
if pwd, err := conn.CurrentDir(); err == nil {
output.WriteString(fmt.Sprintf("\n[当前目录] %s\n", pwd))
}
// 列出根目录文件
if entries, err := conn.List("/"); err == nil {
output.WriteString(fmt.Sprintf("\n[根目录文件列表] (共%d项)\n", len(entries)))
for i, entry := range entries {
if i >= 10 { // 限制显示前10项
output.WriteString("... (更多文件)\n")
break
}
output.WriteString(fmt.Sprintf(" %s %10d %s %s\n",
entry.Type, entry.Size, entry.Time.Format("2006-01-02 15:04"), entry.Name))
}
}
// 检查常见敏感目录
sensitiveDirectories := []string{"/etc", "/home", "/var", "/tmp", "/root", "/opt"}
for _, dir := range sensitiveDirectories {
select {
case <-ctx.Done():
return &ExploitResult{
Success: false,
Output: output.String(),
Error: ctx.Err(),
}
default:
}
if entries, err := conn.List(dir); err == nil {
output.WriteString(fmt.Sprintf("\n[敏感目录] %s (共%d项)\n", dir, len(entries)))
for i, entry := range entries {
if i >= 5 { // 每个目录只显示前5项
output.WriteString(" ... (更多文件)\n")
break
}
output.WriteString(fmt.Sprintf(" %s %s\n", entry.Type, entry.Name))
}
}
}
// 检查系统信息
if sysInfo := p.getSystemInfo(conn); sysInfo != "" {
output.WriteString(fmt.Sprintf("\n[系统信息]\n%s\n", sysInfo))
}
// 尝试创建测试文件(验证写权限)
testFileName := "fscan_test.txt"
testContent := "FScan Security Test File"
if err := p.testWritePermission(conn, testFileName, testContent); err == nil {
output.WriteString(fmt.Sprintf("\n[写权限测试] ✅ 成功创建文件: %s\n", testFileName))
// 清理测试文件
conn.Delete(testFileName)
} else {
output.WriteString(fmt.Sprintf("\n[写权限测试] ❌ 无写权限: %v\n", err))
}
common.LogSuccess(fmt.Sprintf("FTP利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testAnonymousAccess 测试匿名访问
func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
// 尝试匿名登录
anonCreds := []Credential{
{Username: "anonymous", Password: ""},
{Username: "anonymous", Password: "anonymous"},
{Username: "anonymous", Password: "guest@example.com"},
{Username: "ftp", Password: ""},
}
for _, cred := range anonCreds {
if conn := p.testCredential(ctx, info, cred); conn != nil {
conn.Quit()
return &ScanResult{
Success: true,
Service: "ftp",
Username: cred.Username,
Password: cred.Password,
Banner: "匿名访问",
}
}
}
return nil
}
// testCredential 测试单个凭据 - 返回FTP连接或nil
func (p *FTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *ftplib.ServerConn {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 使用Context控制超时的连接
type ftpResult struct {
conn *ftplib.ServerConn
err error
}
connChan := make(chan ftpResult, 1)
go func() {
// 建立FTP连接
conn, err := ftplib.DialTimeout(target, timeout)
if err != nil {
connChan <- ftpResult{nil, err}
return
}
// 尝试登录
err = conn.Login(cred.Username, cred.Password)
if err != nil {
conn.Quit()
connChan <- ftpResult{nil, err}
return
}
connChan <- ftpResult{conn, nil}
}()
// 等待连接结果或超时
select {
case result := <-connChan:
if result.err != nil {
return nil
}
return result.conn
case <-ctx.Done():
return nil
}
}
// testWritePermission 测试写权限
func (p *FTPPlugin) testWritePermission(conn *ftplib.ServerConn, filename, content string) error {
// 尝试创建文件并写入内容
return conn.Stor(filename, strings.NewReader(content))
}
// getSystemInfo 获取系统信息
func (p *FTPPlugin) getSystemInfo(conn *ftplib.ServerConn) string {
var info strings.Builder
// 尝试获取当前目录作为系统信息
if pwd, err := conn.CurrentDir(); err == nil {
info.WriteString(fmt.Sprintf("当前目录: %s\n", pwd))
}
// 尝试列出当前目录获取更多信息
if entries, err := conn.List("."); err == nil && len(entries) > 0 {
info.WriteString(fmt.Sprintf("目录项数量: %d\n", len(entries)))
}
return info.String()
}
// identifyService 服务识别 - 检测FTP服务
func (p *FTPPlugin) identifyService(info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试连接FTP服务
conn, err := ftplib.DialTimeout(target, timeout)
if err != nil {
return &ScanResult{
Success: false,
Service: "ftp",
Error: err,
}
}
defer conn.Quit()
// 获取FTP服务器信息
var banner string
if pwd, err := conn.CurrentDir(); err == nil {
banner = fmt.Sprintf("FTP服务 (根目录: %s)", pwd)
} else {
banner = "FTP服务"
}
common.LogSuccess(i18n.GetText("ftp_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "ftp",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("ftp", func() Plugin {
return NewFTPPlugin()
})
}

349
plugins/kafka.go Normal file
View File

@ -0,0 +1,349 @@
package plugins
import (
"context"
"fmt"
"strings"
"time"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// KafkaPlugin Kafka消息队列扫描和利用插件 - 包含信息收集利用功能
type KafkaPlugin struct {
name string
ports []int
}
// NewKafkaPlugin 创建Kafka插件
func NewKafkaPlugin() *KafkaPlugin {
return &KafkaPlugin{
name: "kafka",
ports: []int{9092, 9093, 9094}, // Kafka broker端口
}
}
// GetName 实现Plugin接口
func (p *KafkaPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *KafkaPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Kafka扫描 - 弱密码检测和未授权访问检测
func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 首先检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("kafka_unauthorized_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("kafka")
if len(credentials) == 0 {
// Kafka默认凭据
credentials = []Credential{
{Username: "admin", Password: "admin"},
{Username: "admin", Password: ""},
{Username: "kafka", Password: "kafka"},
{Username: "user", Password: "user"},
{Username: "test", Password: "test"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "kafka",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if client := p.testCredential(ctx, info, cred); client != nil {
client.Close() // 关闭测试连接
// Kafka认证成功
common.LogSuccess(i18n.GetText("kafka_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "kafka",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "kafka",
Error: fmt.Errorf("未发现弱密码或未授权访问"),
}
}
// Exploit 执行Kafka利用操作 - 实现信息收集功能
func (p *KafkaPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Kafka连接
client := p.testCredential(ctx, info, creds)
if client == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Kafka连接失败"),
}
}
defer client.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Kafka利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Kafka利用结果 - %s ===\n", target))
// 获取集群元数据
if err := client.RefreshMetadata(); err == nil {
// 获取刷新后的元数据
brokers := client.Brokers()
topics, _ := client.Topics()
output.WriteString(fmt.Sprintf("\n[集群信息]\n"))
output.WriteString(fmt.Sprintf(" Broker数量: %d\n", len(brokers)))
// 显示Broker信息
for i, broker := range brokers {
if i >= 5 { // 限制显示前5个broker
output.WriteString(" ... (更多broker)\n")
break
}
output.WriteString(fmt.Sprintf(" Broker %d: %s\n", broker.ID(), broker.Addr()))
}
// 显示Topic列表
output.WriteString(fmt.Sprintf("\n[Topic列表] (共%d个)\n", len(topics)))
for i, topic := range topics {
if i >= 10 { // 限制显示前10个topic
output.WriteString(" ... (更多topic)\n")
break
}
// 获取topic分区数
if partitions, err := client.Partitions(topic); err == nil {
output.WriteString(fmt.Sprintf(" %s (分区数: %d)\n", topic, len(partitions)))
} else {
output.WriteString(fmt.Sprintf(" %s\n", topic))
}
}
}
// 获取消费者组信息
if groups, err := p.getConsumerGroups(client); err == nil && len(groups) > 0 {
output.WriteString(fmt.Sprintf("\n[消费者组] (共%d个)\n", len(groups)))
for i, group := range groups {
if i >= 5 { // 限制显示前5个组
output.WriteString(" ... (更多消费者组)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", group))
}
}
// 尝试生产消息测试(如果有写权限)
if err := p.testProduceMessage(client, "fscan-test-topic"); err == nil {
output.WriteString(fmt.Sprintf("\n[权限测试] ✅ 成功发送测试消息\n"))
} else {
output.WriteString(fmt.Sprintf("\n[权限测试] ❌ 无生产者权限: %v\n", err))
}
common.LogSuccess(fmt.Sprintf("Kafka利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *KafkaPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
// 尝试无认证连接
emptyCred := Credential{Username: "", Password: ""}
if client := p.testCredential(ctx, info, emptyCred); client != nil {
client.Close()
return &ScanResult{
Success: true,
Service: "kafka",
Banner: "未授权访问",
}
}
return nil
}
// testCredential 测试单个凭据 - 返回Kafka客户端或nil
func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) sarama.Client {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 创建Kafka配置
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 cred.Username != "" || cred.Password != "" {
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.SASL.User = cred.Username
config.Net.SASL.Password = cred.Password
config.Net.SASL.Handshake = true
}
brokers := []string{target}
// 使用Context控制超时
type kafkaResult struct {
client sarama.Client
err error
}
clientChan := make(chan kafkaResult, 1)
go func() {
// 尝试创建客户端
client, err := sarama.NewClient(brokers, config)
select {
case <-ctx.Done():
if client != nil {
client.Close()
}
case clientChan <- kafkaResult{client, err}:
}
}()
// 等待客户端创建或超时
select {
case result := <-clientChan:
if result.err != nil {
return nil
}
return result.client
case <-ctx.Done():
return nil
}
}
// getConsumerGroups 获取消费者组列表
func (p *KafkaPlugin) getConsumerGroups(client sarama.Client) ([]string, error) {
// 创建协调器客户端获取消费者组信息
brokers := client.Brokers()
if len(brokers) == 0 {
return nil, fmt.Errorf("没有可用的broker")
}
broker := brokers[0] // 使用第一个broker
// 打开broker连接
if err := broker.Open(client.Config()); err != nil {
return nil, err
}
defer broker.Close()
// 发送ListGroups请求
request := &sarama.ListGroupsRequest{}
response, err := broker.ListGroups(request)
if err != nil {
return nil, err
}
groups := make([]string, 0, len(response.Groups))
for groupId := range response.Groups {
groups = append(groups, groupId)
}
return groups, nil
}
// testProduceMessage 测试发送消息
func (p *KafkaPlugin) testProduceMessage(client sarama.Client, topic string) error {
config := client.Config()
config.Producer.Return.Successes = true
config.Producer.Timeout = 5 * time.Second
producer, err := sarama.NewSyncProducerFromClient(client)
if err != nil {
return err
}
defer producer.Close()
// 发送测试消息
message := &sarama.ProducerMessage{
Topic: topic,
Value: sarama.StringEncoder("FScan security test message"),
}
_, _, err = producer.SendMessage(message)
return err
}
// identifyService 服务识别 - 检测Kafka服务
func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试无认证连接
emptyCred := Credential{Username: "", Password: ""}
client := p.testCredential(ctx, info, emptyCred)
if client == nil {
return &ScanResult{
Success: false,
Service: "kafka",
Error: fmt.Errorf("无法连接到Kafka服务"),
}
}
defer client.Close()
// 获取集群信息作为banner
var banner string
if err := client.RefreshMetadata(); err == nil {
brokers := client.Brokers()
topics, _ := client.Topics()
banner = fmt.Sprintf("Kafka集群 (Brokers: %d, Topics: %d)", len(brokers), len(topics))
} else {
banner = "Kafka服务"
}
common.LogSuccess(i18n.GetText("kafka_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "kafka",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("kafka", func() Plugin {
return NewKafkaPlugin()
})
}

472
plugins/ldap.go Normal file
View File

@ -0,0 +1,472 @@
package plugins
import (
"context"
"fmt"
"strings"
ldaplib "github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// LDAPPlugin LDAP轻量级目录访问协议扫描和利用插件 - 包含目录信息收集利用功能
type LDAPPlugin struct {
name string
ports []int
}
// NewLDAPPlugin 创建LDAP插件
func NewLDAPPlugin() *LDAPPlugin {
return &LDAPPlugin{
name: "ldap",
ports: []int{389, 636, 3268, 3269}, // 389: LDAP, 636: LDAPS, 3268/3269: Global Catalog
}
}
// GetName 实现Plugin接口
func (p *LDAPPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *LDAPPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行LDAP扫描 - 匿名绑定检测和弱密码检测
func (p *LDAPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 首先测试匿名绑定
if result := p.testAnonymousBind(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("ldap_anonymous_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("ldap")
if len(credentials) == 0 {
// LDAP默认凭据
credentials = []Credential{
{Username: "admin", Password: "admin"},
{Username: "administrator", Password: "administrator"},
{Username: "admin", Password: "password"},
{Username: "admin", Password: "123456"},
{Username: "ldap", Password: "ldap"},
{Username: "manager", Password: "manager"},
{Username: "root", Password: "root"},
{Username: "bind", Password: "bind"},
{Username: "guest", Password: "guest"},
{Username: "test", Password: "test"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "ldap",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// LDAP认证成功
common.LogSuccess(i18n.GetText("ldap_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "ldap",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "ldap",
Error: fmt.Errorf("未发现弱密码或匿名访问"),
}
}
// Exploit 执行LDAP利用操作 - 实现目录信息收集功能
func (p *LDAPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("LDAP利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== LDAP利用结果 - %s ===\n", target))
if creds.Username != "" {
output.WriteString(fmt.Sprintf("认证凭据: %s/%s\n", creds.Username, creds.Password))
} else {
output.WriteString("认证方式: 匿名绑定\n")
}
// 建立LDAP连接
conn, err := p.connectLDAP(ctx, info, creds)
if err != nil {
output.WriteString(fmt.Sprintf("\n[连接失败] %v\n", err))
return &ExploitResult{
Success: false,
Output: output.String(),
Error: err,
}
}
defer conn.Close()
output.WriteString("\n[连接状态] ✅ 成功建立LDAP连接\n")
// 获取根DSE信息
if rootDSE := p.getRootDSE(conn); rootDSE != "" {
output.WriteString(fmt.Sprintf("\n[根DSE信息]\n%s\n", rootDSE))
}
// 获取命名上下文
if namingContexts := p.getNamingContexts(conn); len(namingContexts) > 0 {
output.WriteString(fmt.Sprintf("\n[命名上下文] (共%d个)\n", len(namingContexts)))
for _, context := range namingContexts {
output.WriteString(fmt.Sprintf(" %s\n", context))
}
}
// 枚举用户
if users := p.enumerateUsers(conn); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户枚举] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 20 { // 限制显示前20个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 枚举组织单位
if ous := p.enumerateOUs(conn); len(ous) > 0 {
output.WriteString(fmt.Sprintf("\n[组织单位] (共%d个)\n", len(ous)))
for i, ou := range ous {
if i >= 15 { // 限制显示前15个OU
output.WriteString("... (更多组织单位)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", ou))
}
}
common.LogSuccess(fmt.Sprintf("LDAP利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testAnonymousBind 测试匿名绑定
func (p *LDAPPlugin) testAnonymousBind(ctx context.Context, info *common.HostInfo) *ScanResult {
conn, err := p.connectLDAP(ctx, info, Credential{})
if err != nil {
return nil
}
defer conn.Close()
// 尝试匿名绑定
if err := conn.UnauthenticatedBind(""); err != nil {
return nil
}
return &ScanResult{
Success: true,
Service: "ldap",
Banner: "匿名访问",
}
}
// testCredential 测试单个凭据
func (p *LDAPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
conn, err := p.connectLDAP(ctx, info, cred)
if err != nil {
return false
}
defer conn.Close()
// 尝试绑定
bindDNs := p.generateBindDNs(cred.Username)
for _, bindDN := range bindDNs {
if err := conn.Bind(bindDN, cred.Password); err == nil {
return true
}
}
return false
}
// connectLDAP 连接到LDAP服务
func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, creds Credential) (*ldaplib.Conn, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 根据端口选择连接方式
var conn *ldaplib.Conn
var err error
if info.Ports == "636" { // LDAPS
conn, err = ldaplib.DialTLS("tcp", target, nil)
} else {
conn, err = ldaplib.Dial("tcp", target)
}
if err != nil {
return nil, err
}
return conn, nil
}
// generateBindDNs 生成绑定DN列表
func (p *LDAPPlugin) generateBindDNs(username string) []string {
return []string{
fmt.Sprintf("cn=%s,dc=example,dc=com", username),
fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", username),
fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", username),
fmt.Sprintf("uid=%s,dc=example,dc=com", username),
fmt.Sprintf("cn=%s,cn=users,dc=example,dc=com", username),
fmt.Sprintf("sAMAccountName=%s", username), // Active Directory
username, // 直接使用用户名作为DN
}
}
// getRootDSE 获取根DSE信息
func (p *LDAPPlugin) getRootDSE(conn *ldaplib.Conn) string {
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"*", "+"}, // 请求所有属性
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil || len(sr.Entries) == 0 {
return "无法获取根DSE信息"
}
var info strings.Builder
entry := sr.Entries[0]
// 显示重要属性
importantAttrs := []string{
"vendorName", "vendorVersion", "serverName",
"supportedLDAPVersion", "namingContexts",
"defaultNamingContext", "schemaNamingContext",
"configurationNamingContext",
}
for _, attr := range importantAttrs {
if values := entry.GetAttributeValues(attr); len(values) > 0 {
info.WriteString(fmt.Sprintf("%s: %s\n", attr, strings.Join(values, ", ")))
}
}
return info.String()
}
// getNamingContexts 获取命名上下文
func (p *LDAPPlugin) getNamingContexts(conn *ldaplib.Conn) []string {
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"namingContexts"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil || len(sr.Entries) == 0 {
return nil
}
return sr.Entries[0].GetAttributeValues("namingContexts")
}
// enumerateUsers 枚举用户
func (p *LDAPPlugin) enumerateUsers(conn *ldaplib.Conn) []string {
// 尝试常见的用户搜索基DN
searchBases := []string{
"dc=example,dc=com",
"ou=users,dc=example,dc=com",
"cn=users,dc=example,dc=com",
"",
}
var users []string
for _, baseDN := range searchBases {
searchRequest := ldaplib.NewSearchRequest(
baseDN,
ldaplib.ScopeWholeSubtree,
ldaplib.NeverDerefAliases,
50, 0, false, // 限制返回50个结果
"(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))",
[]string{"cn", "uid", "sAMAccountName", "mail", "displayName"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
for _, entry := range sr.Entries {
var userInfo strings.Builder
userInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN))
if cn := entry.GetAttributeValue("cn"); cn != "" {
userInfo.WriteString(fmt.Sprintf(", CN: %s", cn))
}
if uid := entry.GetAttributeValue("uid"); uid != "" {
userInfo.WriteString(fmt.Sprintf(", UID: %s", uid))
}
if sam := entry.GetAttributeValue("sAMAccountName"); sam != "" {
userInfo.WriteString(fmt.Sprintf(", SAM: %s", sam))
}
if mail := entry.GetAttributeValue("mail"); mail != "" {
userInfo.WriteString(fmt.Sprintf(", Mail: %s", mail))
}
users = append(users, userInfo.String())
}
if len(users) > 0 {
break // 找到用户就停止搜索其他基DN
}
}
return users
}
// enumerateOUs 枚举组织单位
func (p *LDAPPlugin) enumerateOUs(conn *ldaplib.Conn) []string {
searchBases := []string{
"dc=example,dc=com",
"",
}
var ous []string
for _, baseDN := range searchBases {
searchRequest := ldaplib.NewSearchRequest(
baseDN,
ldaplib.ScopeWholeSubtree,
ldaplib.NeverDerefAliases,
30, 0, false, // 限制返回30个结果
"(objectClass=organizationalUnit)",
[]string{"ou", "description"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
for _, entry := range sr.Entries {
var ouInfo strings.Builder
ouInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN))
if ou := entry.GetAttributeValue("ou"); ou != "" {
ouInfo.WriteString(fmt.Sprintf(", OU: %s", ou))
}
if desc := entry.GetAttributeValue("description"); desc != "" {
ouInfo.WriteString(fmt.Sprintf(", Desc: %s", desc))
}
ous = append(ous, ouInfo.String())
}
if len(ous) > 0 {
break // 找到OU就停止搜索其他基DN
}
}
return ous
}
// identifyService 服务识别 - 检测LDAP服务
func (p *LDAPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := p.connectLDAP(ctx, info, Credential{})
if err != nil {
return &ScanResult{
Success: false,
Service: "ldap",
Error: err,
}
}
defer conn.Close()
// 尝试获取根DSE信息来确认这是LDAP服务
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"vendorName", "vendorVersion"},
nil,
)
sr, err := conn.Search(searchRequest)
var banner string
if err != nil {
banner = "LDAP目录服务"
} else if len(sr.Entries) > 0 {
entry := sr.Entries[0]
vendor := entry.GetAttributeValue("vendorName")
version := entry.GetAttributeValue("vendorVersion")
if vendor != "" && version != "" {
banner = fmt.Sprintf("LDAP目录服务 (%s %s)", vendor, version)
} else if vendor != "" {
banner = fmt.Sprintf("LDAP目录服务 (%s)", vendor)
} else {
banner = "LDAP目录服务"
}
} else {
banner = "LDAP目录服务"
}
common.LogSuccess(i18n.GetText("ldap_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "ldap",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("ldap", func() Plugin {
return NewLDAPPlugin()
})
}

418
plugins/memcached.go Normal file
View File

@ -0,0 +1,418 @@
package plugins
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MemcachedPlugin Memcached分布式缓存系统扫描和利用插件 - 包含缓存数据提取利用功能
type MemcachedPlugin struct {
name string
ports []int
}
// NewMemcachedPlugin 创建Memcached插件
func NewMemcachedPlugin() *MemcachedPlugin {
return &MemcachedPlugin{
name: "memcached",
ports: []int{11211, 11212, 11213}, // Memcached端口
}
}
// GetName 实现Plugin接口
func (p *MemcachedPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MemcachedPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Memcached扫描 - 未授权访问检测
func (p *MemcachedPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// Memcached主要检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("memcached_unauth_success", target))
return result
}
// 未授权访问失败
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("Memcached服务连接失败"),
}
}
// Exploit 执行Memcached利用操作 - 实现缓存数据提取功能
func (p *MemcachedPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Memcached连接
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Memcached连接失败"),
}
}
defer conn.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Memcached利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Memcached利用结果 - %s ===\n", target))
// 获取服务器统计信息
if stats := p.getStats(conn); stats != "" {
output.WriteString(fmt.Sprintf("\n[服务器统计]\n%s\n", stats))
}
// 获取版本信息
if version := p.getVersion(conn); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取所有键
if keys := p.getAllKeys(conn); len(keys) > 0 {
output.WriteString(fmt.Sprintf("\n[缓存键] (共%d个)\n", len(keys)))
for i, key := range keys {
if i >= 20 { // 限制显示前20个键
output.WriteString("... (更多键值)\n")
break
}
// 获取键的值
value := p.getValue(conn, key)
if len(value) > 100 {
value = value[:100] + "..."
}
output.WriteString(fmt.Sprintf(" %s: %s\n", key, value))
}
}
// 获取设置信息
if settings := p.getSettings(conn); settings != "" {
output.WriteString(fmt.Sprintf("\n[配置设置]\n%s\n", settings))
}
// 测试写权限
if writeTest := p.testWritePermission(conn); writeTest != "" {
output.WriteString(fmt.Sprintf("\n[写权限测试]\n%s\n", writeTest))
}
common.LogSuccess(fmt.Sprintf("Memcached利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return nil
}
defer conn.Close()
// 尝试执行stats命令测试
if p.testBasicCommand(conn) {
return &ScanResult{
Success: true,
Service: "memcached",
Banner: "未授权访问",
}
}
return nil
}
// connectToMemcached 连接到Memcached服务
func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo) net.Conn {
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 nil
}
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
return conn
}
// testBasicCommand 测试基本命令
func (p *MemcachedPlugin) testBasicCommand(conn net.Conn) bool {
timeout := time.Duration(common.Timeout) * time.Second
// 发送version命令
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte("version\r\n")); err != nil {
return false
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return false
}
responseStr := string(response[:n])
return strings.Contains(responseStr, "VERSION") || strings.Contains(responseStr, "memcached")
}
// sendCommand 发送Memcached命令
func (p *MemcachedPlugin) sendCommand(conn net.Conn, command string) string {
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(command + "\r\n")); err != nil {
return ""
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 4096)
n, err := conn.Read(response)
if err != nil {
return ""
}
return strings.TrimSpace(string(response[:n]))
}
// getStats 获取统计信息
func (p *MemcachedPlugin) getStats(conn net.Conn) string {
response := p.sendCommand(conn, "stats")
if response == "" {
return ""
}
lines := strings.Split(response, "\n")
var stats strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 3 {
key := parts[1]
value := parts[2]
// 只显示重要的统计信息
if key == "version" || key == "uptime" || key == "curr_items" ||
key == "total_items" || key == "bytes" || key == "curr_connections" ||
key == "cmd_get" || key == "cmd_set" || key == "get_hits" || key == "get_misses" {
stats.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
}
}
return stats.String()
}
// getVersion 获取版本信息
func (p *MemcachedPlugin) getVersion(conn net.Conn) string {
response := p.sendCommand(conn, "version")
if strings.HasPrefix(response, "VERSION") {
return strings.TrimPrefix(response, "VERSION ")
}
return ""
}
// getAllKeys 获取所有键通过stats cachedump
func (p *MemcachedPlugin) getAllKeys(conn net.Conn) []string {
var keys []string
// 首先获取slabs信息
slabsResponse := p.sendCommand(conn, "stats slabs")
if slabsResponse == "" {
return keys
}
// 解析slab ID
slabIDs := make(map[string]bool)
lines := strings.Split(slabsResponse, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 2 {
statName := parts[1]
// 格式如: "1:chunk_size"
if strings.Contains(statName, ":") {
slabID := strings.Split(statName, ":")[0]
slabIDs[slabID] = true
}
}
}
}
// 对每个slab ID执行cachedump
for slabID := range slabIDs {
if len(keys) >= 50 { // 限制键的数量
break
}
dumpCmd := fmt.Sprintf("stats cachedump %s 50", slabID)
dumpResponse := p.sendCommand(conn, dumpCmd)
dumpLines := strings.Split(dumpResponse, "\n")
for _, line := range dumpLines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "ITEM") {
parts := strings.Fields(line)
if len(parts) >= 2 {
key := parts[1]
keys = append(keys, key)
}
}
}
}
return keys
}
// getValue 获取键的值
func (p *MemcachedPlugin) getValue(conn net.Conn, key string) string {
getCmd := fmt.Sprintf("get %s", key)
response := p.sendCommand(conn, getCmd)
lines := strings.Split(response, "\n")
for i, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "VALUE") {
// VALUE行的下一行是实际值
if i+1 < len(lines) {
value := strings.TrimSpace(lines[i+1])
if value != "END" {
return value
}
}
}
}
return "[无法获取]"
}
// getSettings 获取设置信息
func (p *MemcachedPlugin) getSettings(conn net.Conn) string {
response := p.sendCommand(conn, "stats settings")
if response == "" {
return ""
}
lines := strings.Split(response, "\n")
var settings strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 3 {
key := parts[1]
value := parts[2]
// 只显示重要的设置
if key == "maxbytes" || key == "maxconns" || key == "growth_factor" ||
key == "chunk_size" || key == "num_threads" || key == "cas_enabled" {
settings.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
}
}
return settings.String()
}
// testWritePermission 测试写权限
func (p *MemcachedPlugin) testWritePermission(conn net.Conn) string {
// 尝试设置一个测试键
setCmd := "set fscan_test 0 60 11\r\nfscan_test\r\n"
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(setCmd)); err != nil {
return "❌ 无写权限: " + err.Error()
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil {
return "❌ 写入测试失败: " + err.Error()
}
responseStr := strings.TrimSpace(string(response[:n]))
if responseStr == "STORED" {
// 删除测试键
p.sendCommand(conn, "delete fscan_test")
return "✅ 具有读写权限"
}
return "❌ 写入失败: " + responseStr
}
// identifyService 服务识别 - 检测Memcached服务
func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("无法连接到Memcached服务"),
}
}
defer conn.Close()
// 尝试识别Memcached协议
version := p.getVersion(conn)
var banner string
if version != "" {
banner = fmt.Sprintf("Memcached服务 (版本: %s)", version)
} else if p.testBasicCommand(conn) {
banner = "Memcached分布式缓存服务"
} else {
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("无法识别为Memcached服务"),
}
}
common.LogSuccess(i18n.GetText("memcached_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "memcached",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("memcached", func() Plugin {
return NewMemcachedPlugin()
})
}

536
plugins/mongodb.go Normal file
View File

@ -0,0 +1,536 @@
package plugins
import (
"context"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MongoDBPlugin MongoDB数据库扫描和利用插件 - 包含数据查询利用功能
type MongoDBPlugin struct {
name string
ports []int
}
// NewMongoDBPlugin 创建MongoDB插件
func NewMongoDBPlugin() *MongoDBPlugin {
return &MongoDBPlugin{
name: "mongodb",
ports: []int{27017, 27018, 27019}, // MongoDB端口
}
}
// GetName 实现Plugin接口
func (p *MongoDBPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MongoDBPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行MongoDB扫描 - 未授权访问检测
func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// MongoDB主要检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("mongodb_unauth_success", target))
return result
}
// 未授权访问失败
return &ScanResult{
Success: false,
Service: "mongodb",
Error: fmt.Errorf("MongoDB需要认证或连接失败"),
}
}
// Exploit 执行MongoDB利用操作 - 实现数据查询功能
func (p *MongoDBPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立MongoDB连接
conn := p.connectToMongoDB(ctx, info)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("MongoDB连接失败"),
}
}
defer conn.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("MongoDB利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== MongoDB利用结果 - %s ===\n", target))
// 获取服务器状态
if serverStatus := p.getServerStatus(conn); serverStatus != "" {
output.WriteString(fmt.Sprintf("\n[服务器状态]\n%s\n", serverStatus))
}
// 获取数据库列表
if databases := p.getDatabases(conn); len(databases) > 0 {
output.WriteString(fmt.Sprintf("\n[数据库列表] (共%d个)\n", len(databases)))
for i, dbName := range databases {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多数据库)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", dbName))
}
}
// 获取集合信息admin数据库
if collections := p.getCollections(conn, "admin"); len(collections) > 0 {
output.WriteString(fmt.Sprintf("\n[admin数据库集合] (共%d个)\n", len(collections)))
for i, collection := range collections {
if i >= 5 { // 限制显示前5个集合
output.WriteString("... (更多集合)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", collection))
}
}
// 获取用户信息
if users := p.getUsers(conn); users != "" {
output.WriteString(fmt.Sprintf("\n[用户信息]\n%s\n", users))
}
// 获取版本信息
if version := p.getBuildInfo(conn); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
common.LogSuccess(fmt.Sprintf("MongoDB利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *MongoDBPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
conn := p.connectToMongoDB(ctx, info)
if conn == nil {
return nil
}
defer conn.Close()
// 尝试执行基本查询测试
if p.testBasicQuery(conn) {
return &ScanResult{
Success: true,
Service: "mongodb",
Banner: "未授权访问",
}
}
return nil
}
// connectToMongoDB 连接到MongoDB服务
func (p *MongoDBPlugin) connectToMongoDB(ctx context.Context, info *common.HostInfo) net.Conn {
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 nil
}
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
return conn
}
// testBasicQuery 测试基本查询
func (p *MongoDBPlugin) testBasicQuery(conn net.Conn) bool {
// 创建MongoDB查询消息
queryMsg := p.createListDatabasesQuery()
if _, err := conn.Write(queryMsg); err != nil {
return false
}
// 读取响应
response := make([]byte, 4096)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return false
}
// 检查响应是否有效
return n > 36 && p.isValidMongoResponse(response[:n])
}
// isValidMongoResponse 检查是否为有效的MongoDB响应
func (p *MongoDBPlugin) isValidMongoResponse(data []byte) bool {
if len(data) < 36 {
return false
}
// 检查MongoDB协议标志
responseStr := string(data)
return strings.Contains(responseStr, "databases") ||
strings.Contains(responseStr, "totalSize") ||
strings.Contains(responseStr, "name") ||
strings.Contains(responseStr, "sizeOnDisk")
}
// getServerStatus 获取服务器状态
func (p *MongoDBPlugin) getServerStatus(conn net.Conn) string {
// 创建serverStatus命令
statusMsg := p.createServerStatusQuery()
if _, err := conn.Write(statusMsg); err != nil {
return ""
}
response := make([]byte, 2048)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return ""
}
responseStr := string(response[:n])
var status strings.Builder
if strings.Contains(responseStr, "version") {
status.WriteString("MongoDB服务运行中\n")
}
if strings.Contains(responseStr, "uptime") {
status.WriteString("服务器运行正常\n")
}
return status.String()
}
// getDatabases 获取数据库列表
func (p *MongoDBPlugin) getDatabases(conn net.Conn) []string {
// 创建listDatabases查询
queryMsg := p.createListDatabasesQuery()
if _, err := conn.Write(queryMsg); err != nil {
return nil
}
response := make([]byte, 4096)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return nil
}
responseStr := string(response[:n])
// 解析数据库名称
var databases []string
if strings.Contains(responseStr, "admin") {
databases = append(databases, "admin")
}
if strings.Contains(responseStr, "local") {
databases = append(databases, "local")
}
if strings.Contains(responseStr, "config") {
databases = append(databases, "config")
}
return databases
}
// getCollections 获取指定数据库的集合列表
func (p *MongoDBPlugin) getCollections(conn net.Conn, database string) []string {
// 创建listCollections查询
queryMsg := p.createListCollectionsQuery(database)
if _, err := conn.Write(queryMsg); err != nil {
return nil
}
response := make([]byte, 2048)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return nil
}
responseStr := string(response[:n])
// 解析集合名称
var collections []string
if strings.Contains(responseStr, "system") {
collections = append(collections, "system.users")
collections = append(collections, "system.roles")
}
return collections
}
// getUsers 获取用户信息
func (p *MongoDBPlugin) getUsers(conn net.Conn) string {
// 尝试查询admin.system.users
userQuery := p.createFindUsersQuery()
if _, err := conn.Write(userQuery); err != nil {
return ""
}
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return ""
}
responseStr := string(response[:n])
if strings.Contains(responseStr, "user") || strings.Contains(responseStr, "role") {
return "发现用户数据"
}
return "无用户信息或权限不足"
}
// getBuildInfo 获取版本信息
func (p *MongoDBPlugin) getBuildInfo(conn net.Conn) string {
buildInfoMsg := p.createBuildInfoQuery()
if _, err := conn.Write(buildInfoMsg); err != nil {
return ""
}
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return ""
}
responseStr := string(response[:n])
if strings.Contains(responseStr, "version") {
return "MongoDB服务器信息可用"
}
return ""
}
// createListDatabasesQuery 创建listDatabases查询
func (p *MongoDBPlugin) createListDatabasesQuery() []byte {
// 简化的MongoDB Wire Protocol消息
// OP_QUERY消息结构
query := make([]byte, 100)
// 消息头 (16字节)
query[0] = 0x64 // 消息长度
query[4] = 0x01 // 请求ID
query[12] = 0x04 // OP_QUERY操作码
query[13] = 0x20
query[14] = 0x00
query[15] = 0x00
// 查询标志
query[16] = 0x00
query[17] = 0x00
query[18] = 0x00
query[19] = 0x00
// 集合名称 "admin.$cmd"
copy(query[20:], "admin.$cmd\x00")
// BSON查询文档 {listDatabases: 1}
bsonQuery := []byte{
0x1A, 0x00, 0x00, 0x00, // 文档长度
0x10, // int32类型
0x6C, 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x00, // "listDatabases"
0x01, 0x00, 0x00, 0x00, // 值为1
0x00, // 文档结束
}
copy(query[32:], bsonQuery)
return query[:58]
}
// createServerStatusQuery 创建serverStatus查询
func (p *MongoDBPlugin) createServerStatusQuery() []byte {
query := make([]byte, 100)
// 消息头
query[0] = 0x60
query[4] = 0x02
query[12] = 0x04
query[13] = 0x20
query[14] = 0x00
query[15] = 0x00
// 查询标志
query[16] = 0x00
query[17] = 0x00
query[18] = 0x00
query[19] = 0x00
// 集合名称
copy(query[20:], "admin.$cmd\x00")
// BSON查询文档 {serverStatus: 1}
bsonQuery := []byte{
0x18, 0x00, 0x00, 0x00,
0x10,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x00,
0x01, 0x00, 0x00, 0x00,
0x00,
}
copy(query[32:], bsonQuery)
return query[:56]
}
// createListCollectionsQuery 创建listCollections查询
func (p *MongoDBPlugin) createListCollectionsQuery(database string) []byte {
query := make([]byte, 100)
// 消息头
query[0] = 0x70
query[4] = 0x03
query[12] = 0x04
query[13] = 0x20
query[14] = 0x00
query[15] = 0x00
// 查询标志
query[16] = 0x00
query[17] = 0x00
query[18] = 0x00
query[19] = 0x00
// 集合名称
collectionName := database + ".$cmd\x00"
copy(query[20:], collectionName)
// BSON查询文档 {listCollections: 1}
bsonQuery := []byte{
0x1C, 0x00, 0x00, 0x00,
0x10,
0x6C, 0x69, 0x73, 0x74, 0x43, 0x6F, 0x6C, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x00,
0x01, 0x00, 0x00, 0x00,
0x00,
}
copy(query[20+len(collectionName):], bsonQuery)
return query[:46+len(collectionName)]
}
// createFindUsersQuery 创建查找用户查询
func (p *MongoDBPlugin) createFindUsersQuery() []byte {
query := make([]byte, 120)
// 消息头
query[0] = 0x78
query[4] = 0x04
query[12] = 0x04
query[13] = 0x20
query[14] = 0x00
query[15] = 0x00
// 查询标志
query[16] = 0x00
query[17] = 0x00
query[18] = 0x00
query[19] = 0x00
// 集合名称 "admin.system.users"
copy(query[20:], "admin.system.users\x00")
// 空的查询文档 {}
bsonQuery := []byte{0x05, 0x00, 0x00, 0x00, 0x00}
copy(query[39:], bsonQuery)
return query[:44]
}
// createBuildInfoQuery 创建buildInfo查询
func (p *MongoDBPlugin) createBuildInfoQuery() []byte {
query := make([]byte, 100)
// 消息头
query[0] = 0x5C
query[4] = 0x05
query[12] = 0x04
query[13] = 0x20
query[14] = 0x00
query[15] = 0x00
// 查询标志
query[16] = 0x00
query[17] = 0x00
query[18] = 0x00
query[19] = 0x00
// 集合名称
copy(query[20:], "admin.$cmd\x00")
// BSON查询文档 {buildInfo: 1}
bsonQuery := []byte{
0x15, 0x00, 0x00, 0x00,
0x10,
0x62, 0x75, 0x69, 0x6C, 0x64, 0x49, 0x6E, 0x66, 0x6F, 0x00,
0x01, 0x00, 0x00, 0x00,
0x00,
}
copy(query[32:], bsonQuery)
return query[:53]
}
// identifyService 服务识别 - 检测MongoDB服务
func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn := p.connectToMongoDB(ctx, info)
if conn == nil {
return &ScanResult{
Success: false,
Service: "mongodb",
Error: fmt.Errorf("无法连接到MongoDB服务"),
}
}
defer conn.Close()
// 尝试识别MongoDB协议
if p.testBasicQuery(conn) {
banner := "MongoDB数据库服务"
common.LogSuccess(i18n.GetText("mongodb_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "mongodb",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "mongodb",
Error: fmt.Errorf("无法识别为MongoDB服务"),
}
}
// init 自动注册插件
func init() {
RegisterPlugin("mongodb", func() Plugin {
return NewMongoDBPlugin()
})
}

397
plugins/mssql.go Normal file
View File

@ -0,0 +1,397 @@
package plugins
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MSSQLPlugin Microsoft SQL Server数据库扫描和利用插件 - 包含数据库查询利用功能
type MSSQLPlugin struct {
name string
ports []int
}
// NewMSSQLPlugin 创建MSSQL插件
func NewMSSQLPlugin() *MSSQLPlugin {
return &MSSQLPlugin{
name: "mssql",
ports: []int{1433, 1434}, // MSSQL端口
}
}
// GetName 实现Plugin接口
func (p *MSSQLPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MSSQLPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行MSSQL扫描 - 弱密码检测
func (p *MSSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("mssql")
if len(credentials) == 0 {
// MSSQL默认凭据
credentials = []Credential{
{Username: "sa", Password: ""},
{Username: "sa", Password: "sa"},
{Username: "sa", Password: "password"},
{Username: "sa", Password: "admin"},
{Username: "sa", Password: "123456"},
{Username: "administrator", Password: "administrator"},
{Username: "admin", Password: "admin"},
{Username: "mssql", Password: "mssql"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "mssql",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if db := p.testCredential(ctx, info, cred); db != nil {
db.Close() // 关闭测试连接
// MSSQL认证成功
common.LogSuccess(i18n.GetText("mssql_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "mssql",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "mssql",
Error: fmt.Errorf("未发现弱密码"),
}
}
// Exploit 执行MSSQL利用操作 - 实现数据库查询功能
func (p *MSSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立MSSQL连接
db := p.testCredential(ctx, info, creds)
if db == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("MSSQL连接失败"),
}
}
defer db.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("MSSQL利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== MSSQL利用结果 - %s ===\n", target))
// 获取版本信息
if version := p.getVersion(db); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取数据库列表
if databases := p.getDatabases(db); len(databases) > 0 {
output.WriteString(fmt.Sprintf("\n[数据库列表] (共%d个)\n", len(databases)))
for i, dbName := range databases {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多数据库)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", dbName))
}
}
// 获取表列表master数据库
if tables := p.getTables(db, "master"); len(tables) > 0 {
output.WriteString(fmt.Sprintf("\n[master数据库表] (共%d个)\n", len(tables)))
for i, table := range tables {
if i >= 5 { // 限制显示前5个表
output.WriteString("... (更多表)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", table))
}
}
// 获取用户列表
if users := p.getUsers(db); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户列表] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 10 { // 限制显示前10个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 获取权限信息
if privileges := p.getPrivileges(db, creds.Username); privileges != "" {
output.WriteString(fmt.Sprintf("\n[用户权限]\n%s\n", privileges))
}
// 检查xp_cmdshell状态
if cmdshellStatus := p.checkXpCmdshell(db); cmdshellStatus != "" {
output.WriteString(fmt.Sprintf("\n[系统命令执行]\n%s\n", cmdshellStatus))
}
common.LogSuccess(fmt.Sprintf("MSSQL利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testCredential 测试单个凭据 - 返回数据库连接或nil
func (p *MSSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB {
// 构建连接字符串
connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=master;connection timeout=%d",
info.Host, cred.Username, cred.Password, info.Ports, common.Timeout)
// 打开数据库连接
db, err := sql.Open("mssql", connStr)
if err != nil {
return nil
}
// 设置连接参数
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 创建超时上下文
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 测试连接
err = db.PingContext(pingCtx)
if err != nil {
db.Close()
return nil
}
return db
}
// getVersion 获取MSSQL版本信息
func (p *MSSQLPlugin) getVersion(db *sql.DB) string {
var version string
err := db.QueryRow("SELECT @@VERSION").Scan(&version)
if err != nil {
return ""
}
// 只返回第一行版本信息
lines := strings.Split(version, "\n")
if len(lines) > 0 {
return strings.TrimSpace(lines[0])
}
return version
}
// getDatabases 获取数据库列表
func (p *MSSQLPlugin) getDatabases(db *sql.DB) []string {
query := "SELECT name FROM sys.databases WHERE database_id > 4" // 排除系统数据库
rows, err := db.Query(query)
if err != nil {
// 如果查询失败,尝试查询所有数据库
rows, err = db.Query("SELECT name FROM sys.databases")
if err != nil {
return nil
}
}
defer rows.Close()
var databases []string
for rows.Next() {
var dbName string
if err := rows.Scan(&dbName); err == nil {
databases = append(databases, dbName)
}
}
return databases
}
// getTables 获取指定数据库的表列表
func (p *MSSQLPlugin) getTables(db *sql.DB, database string) []string {
query := fmt.Sprintf("SELECT TABLE_NAME FROM %s.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", database)
rows, err := db.Query(query)
if err != nil {
return nil
}
defer rows.Close()
var tables []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err == nil {
tables = append(tables, tableName)
}
}
return tables
}
// getUsers 获取用户列表
func (p *MSSQLPlugin) getUsers(db *sql.DB) []string {
query := "SELECT name FROM sys.server_principals WHERE type IN ('S', 'U') AND is_disabled = 0"
rows, err := db.Query(query)
if err != nil {
// 尝试备用查询
rows, err = db.Query("SELECT loginname FROM master.dbo.syslogins")
if err != nil {
return nil
}
}
defer rows.Close()
var users []string
for rows.Next() {
var userName string
if err := rows.Scan(&userName); err == nil {
users = append(users, userName)
}
}
return users
}
// getPrivileges 获取用户权限信息
func (p *MSSQLPlugin) getPrivileges(db *sql.DB, username string) string {
var privileges strings.Builder
// 检查是否为sysadmin
var isSysadmin int
err := db.QueryRow("SELECT IS_SRVROLEMEMBER('sysadmin', ?)", username).Scan(&isSysadmin)
if err == nil {
if isSysadmin == 1 {
privileges.WriteString("sysadmin权限: YES\n")
} else {
privileges.WriteString("sysadmin权限: NO\n")
}
}
// 检查其他服务器角色
serverRoles := []string{"securityadmin", "serveradmin", "setupadmin", "processadmin", "diskadmin", "dbcreator", "bulkadmin"}
for _, role := range serverRoles {
var hasRole int
err := db.QueryRow(fmt.Sprintf("SELECT IS_SRVROLEMEMBER('%s', ?)", role), username).Scan(&hasRole)
if err == nil && hasRole == 1 {
privileges.WriteString(fmt.Sprintf("%s权限: YES\n", role))
}
}
return privileges.String()
}
// checkXpCmdshell 检查xp_cmdshell状态
func (p *MSSQLPlugin) checkXpCmdshell(db *sql.DB) string {
// 检查xp_cmdshell是否启用
var configValue int
err := db.QueryRow("SELECT CAST(value as int) FROM sys.configurations WHERE name = 'xp_cmdshell'").Scan(&configValue)
if err != nil {
return "无法检查xp_cmdshell状态"
}
if configValue == 1 {
// 尝试执行一个简单的命令测试
var result sql.NullString
err = db.QueryRow("EXEC xp_cmdshell 'echo test'").Scan(&result)
if err == nil && result.Valid {
return "✅ xp_cmdshell已启用支持系统命令执行"
}
return "⚠️ xp_cmdshell已启用但无法执行命令"
}
return "❌ xp_cmdshell未启用"
}
// identifyService 服务识别 - 检测MSSQL服务
func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接MSSQL服务
connStr := fmt.Sprintf("server=%s;user id=invalid;password=invalid;port=%s;database=master;connection timeout=%d",
info.Host, info.Ports, common.Timeout)
db, err := sql.Open("mssql", connStr)
if err != nil {
return &ScanResult{
Success: false,
Service: "mssql",
Error: err,
}
}
defer db.Close()
// 尝试连接(即使认证失败,也能识别服务)
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
err = db.PingContext(pingCtx)
var banner string
if err != nil && (strings.Contains(strings.ToLower(err.Error()), "login failed") ||
strings.Contains(strings.ToLower(err.Error()), "mssql") ||
strings.Contains(strings.ToLower(err.Error()), "sql server")) {
banner = "Microsoft SQL Server数据库服务"
} else if err == nil {
banner = "Microsoft SQL Server (连接成功)"
} else {
return &ScanResult{
Success: false,
Service: "mssql",
Error: fmt.Errorf("无法识别为MSSQL服务"),
}
}
common.LogSuccess(i18n.GetText("mssql_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "mssql",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("mssql", func() Plugin {
return NewMSSQLPlugin()
})
}

212
plugins/mysql.go Normal file
View File

@ -0,0 +1,212 @@
package plugins
import (
"context"
"database/sql"
"fmt"
"net"
"regexp"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MySQLPlugin MySQL数据库弱密码扫描插件 - 单文件实现
type MySQLPlugin struct {
name string
ports []int
}
// NewMySQLPlugin 创建MySQL插件
func NewMySQLPlugin() *MySQLPlugin {
return &MySQLPlugin{
name: "mysql",
ports: []int{3306, 3307, 33060},
}
}
// GetName 实现Plugin接口
func (p *MySQLPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MySQLPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行MySQL扫描 - 核心功能实现
func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(info)
}
// 生成测试凭据
credentials := GenerateCredentials("mysql")
if len(credentials) == 0 {
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("没有可用的测试凭据"),
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "mysql",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// 弱密码发现成功
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "mysql",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据 - 核心认证逻辑
func (p *MySQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 构建连接字符串
connStr := p.buildConnectionString(info.Host, info.Ports, cred.Username, cred.Password)
// 创建数据库连接
db, err := sql.Open("mysql", connStr)
if err != nil {
return false
}
defer db.Close()
// 设置连接超时
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 测试连接 - 使用Context超时控制
err = db.PingContext(ctx)
return err == nil
}
// buildConnectionString 构建MySQL连接字符串
func (p *MySQLPlugin) buildConnectionString(host, port, username, password string) string {
// 支持SOCKS代理
if common.Socks5Proxy != "" {
// 如果使用代理,需要注册自定义拨号器
p.registerProxyDialer()
return fmt.Sprintf("%s:%s@tcp-proxy(%s:%s)/mysql?charset=utf8&timeout=%ds",
username, password, host, port, common.Timeout)
}
// 标准TCP连接
return fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds",
username, password, host, port, common.Timeout)
}
// identifyService 服务识别 - 不进行暴力破解时的功能
func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接获取握手包
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &ScanResult{
Success: false,
Service: "mysql",
Error: err,
}
}
defer conn.Close()
// 读取MySQL握手包
if banner := p.readMySQLBanner(conn); banner != "" {
common.LogSuccess(i18n.GetText("mysql_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "mysql",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("无法识别为MySQL服务"),
}
}
// readMySQLBanner 读取MySQL服务器握手包
func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string {
// 设置读取超时
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// 读取握手包
handshake := make([]byte, 512)
n, err := conn.Read(handshake)
if err != nil || n < 10 {
return ""
}
// 检查MySQL协议版本通常是10
if handshake[4] != 10 {
return ""
}
// 提取版本字符串
versionStart := 5
versionEnd := versionStart
for versionEnd < n && handshake[versionEnd] != 0 {
versionEnd++
}
if versionEnd <= versionStart {
return ""
}
versionStr := string(handshake[versionStart:versionEnd])
// 验证版本字符串格式
if regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr) {
return fmt.Sprintf("MySQL %s", versionStr)
}
return ""
}
// registerProxyDialer 注册SOCKS代理拨号器
func (p *MySQLPlugin) registerProxyDialer() {
// TODO: 实现代理拨号器注册
// 这里简化处理实际需要注册到MySQL驱动
}
// init 自动注册插件
func init() {
RegisterPlugin("mysql", func() Plugin {
return NewMySQLPlugin()
})
}

519
plugins/neo4j.go Normal file
View File

@ -0,0 +1,519 @@
package plugins
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// Neo4jPlugin Neo4j图数据库扫描和利用插件 - 包含图数据查询利用功能
type Neo4jPlugin struct {
name string
ports []int
}
// NewNeo4jPlugin 创建Neo4j插件
func NewNeo4jPlugin() *Neo4jPlugin {
return &Neo4jPlugin{
name: "neo4j",
ports: []int{7474, 7687, 7473}, // Neo4j HTTP、Bolt、HTTPS端口
}
}
// GetName 实现Plugin接口
func (p *Neo4jPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *Neo4jPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Neo4j扫描 - 未授权访问和弱密码检测
func (p *Neo4jPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 首先检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("neo4j_unauth_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("neo4j")
if len(credentials) == 0 {
// Neo4j默认凭据
credentials = []Credential{
{Username: "neo4j", Password: "neo4j"},
{Username: "neo4j", Password: "admin"},
{Username: "neo4j", Password: "password"},
{Username: "neo4j", Password: "123456"},
{Username: "admin", Password: "admin"},
{Username: "admin", Password: "neo4j"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "neo4j",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// Neo4j认证成功
common.LogSuccess(i18n.GetText("neo4j_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "neo4j",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "neo4j",
Error: fmt.Errorf("未发现弱密码或未授权访问"),
}
}
// Exploit 执行Neo4j利用操作 - 实现图数据查询功能
func (p *Neo4jPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Neo4j利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Neo4j利用结果 - %s ===\n", target))
// 获取服务器信息
if serverInfo := p.getServerInfo(ctx, info, creds); serverInfo != "" {
output.WriteString(fmt.Sprintf("\n[服务器信息]\n%s\n", serverInfo))
}
// 获取数据库统计信息
if dbStats := p.getDatabaseStats(ctx, info, creds); dbStats != "" {
output.WriteString(fmt.Sprintf("\n[数据库统计]\n%s\n", dbStats))
}
// 获取节点标签
if labels := p.getNodeLabels(ctx, info, creds); len(labels) > 0 {
output.WriteString(fmt.Sprintf("\n[节点标签] (共%d个)\n", len(labels)))
for i, label := range labels {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多标签)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", label))
}
}
// 获取关系类型
if relationships := p.getRelationshipTypes(ctx, info, creds); len(relationships) > 0 {
output.WriteString(fmt.Sprintf("\n[关系类型] (共%d个)\n", len(relationships)))
for i, rel := range relationships {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多关系)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", rel))
}
}
// 获取存储过程
if procedures := p.getProcedures(ctx, info, creds); len(procedures) > 0 {
output.WriteString(fmt.Sprintf("\n[存储过程] (共%d个)\n", len(procedures)))
for i, proc := range procedures {
if i >= 5 { // 限制显示前5个
output.WriteString("... (更多存储过程)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", proc))
}
}
common.LogSuccess(fmt.Sprintf("Neo4j利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
// 尝试无认证访问
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 检查是否可以直接访问数据库
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
if err != nil {
return nil
}
resp, err := client.Do(req)
if err != nil {
return nil
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
return &ScanResult{
Success: true,
Service: "neo4j",
Banner: "未授权访问",
}
}
return nil
}
// testCredential 测试单个凭据
func (p *Neo4jPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 尝试认证
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil)
if err != nil {
return false
}
req.SetBasicAuth(cred.Username, cred.Password)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == 200
}
// executeQuery 执行Cypher查询
func (p *Neo4jPlugin) executeQuery(ctx context.Context, info *common.HostInfo, creds Credential, query string) (map[string]interface{}, error) {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 构建查询请求
queryData := map[string]interface{}{
"statements": []map[string]interface{}{
{
"statement": query,
},
},
}
jsonData, err := json.Marshal(queryData)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/db/data/transaction/commit", strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
req.SetBasicAuth(creds.Username, creds.Password)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("查询失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, err
}
return result, nil
}
// getServerInfo 获取服务器信息
func (p *Neo4jPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, creds Credential) string {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
if err != nil {
return ""
}
req.SetBasicAuth(creds.Username, creds.Password)
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return ""
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ""
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
var serverInfo map[string]interface{}
err = json.Unmarshal(body, &serverInfo)
if err != nil {
return ""
}
var info_str strings.Builder
if version, ok := serverInfo["neo4j_version"]; ok {
info_str.WriteString(fmt.Sprintf("Neo4j版本: %v\n", version))
}
if extensions, ok := serverInfo["extensions"]; ok {
info_str.WriteString(fmt.Sprintf("扩展信息: %v\n", extensions))
}
return info_str.String()
}
// getDatabaseStats 获取数据库统计信息
func (p *Neo4jPlugin) getDatabaseStats(ctx context.Context, info *common.HostInfo, creds Credential) string {
// 执行统计查询
result, err := p.executeQuery(ctx, info, creds, "MATCH (n) RETURN count(n) as node_count")
if err != nil {
return ""
}
var stats strings.Builder
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 {
if row, ok := data[0].(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
stats.WriteString(fmt.Sprintf("节点总数: %v\n", rowData[0]))
}
}
}
}
}
// 获取关系统计
relResult, err := p.executeQuery(ctx, info, creds, "MATCH ()-[r]->() RETURN count(r) as rel_count")
if err == nil {
if results, ok := relResult["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 {
if row, ok := data[0].(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
stats.WriteString(fmt.Sprintf("关系总数: %v\n", rowData[0]))
}
}
}
}
}
}
return stats.String()
}
// getNodeLabels 获取节点标签列表
func (p *Neo4jPlugin) getNodeLabels(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL db.labels()")
if err != nil {
return nil
}
var labels []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if label, ok := rowData[0].(string); ok {
labels = append(labels, label)
}
}
}
}
}
}
}
return labels
}
// getRelationshipTypes 获取关系类型列表
func (p *Neo4jPlugin) getRelationshipTypes(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL db.relationshipTypes()")
if err != nil {
return nil
}
var relationships []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if rel, ok := rowData[0].(string); ok {
relationships = append(relationships, rel)
}
}
}
}
}
}
}
return relationships
}
// getProcedures 获取存储过程列表
func (p *Neo4jPlugin) getProcedures(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL dbms.procedures() YIELD name RETURN name LIMIT 10")
if err != nil {
return nil
}
var procedures []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if proc, ok := rowData[0].(string); ok {
procedures = append(procedures, proc)
}
}
}
}
}
}
}
return procedures
}
// identifyService 服务识别 - 检测Neo4j服务
func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: err,
}
}
resp, err := client.Do(req)
if err != nil {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: err,
}
}
defer resp.Body.Close()
// 检查响应头或内容是否包含Neo4j特征
var banner string
if resp.Header.Get("Server") != "" && strings.Contains(strings.ToLower(resp.Header.Get("Server")), "neo4j") {
banner = "Neo4j图数据库 (HTTP接口)"
} else if resp.StatusCode == 200 || resp.StatusCode == 401 {
// 尝试检查根路径响应
body, _ := io.ReadAll(resp.Body)
if strings.Contains(strings.ToLower(string(body)), "neo4j") {
banner = "Neo4j图数据库服务"
} else {
banner = "Neo4j服务 (协议识别)"
}
} else {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: fmt.Errorf("无法识别为Neo4j服务"),
}
}
common.LogSuccess(i18n.GetText("neo4j_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "neo4j",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("neo4j", func() Plugin {
return NewNeo4jPlugin()
})
}

363
plugins/oracle.go Normal file
View File

@ -0,0 +1,363 @@
package plugins
import (
"context"
"database/sql"
"fmt"
"strings"
// _ "github.com/mattn/go-oci8" // Oracle驱动需要特殊安装暂时注释
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// OraclePlugin Oracle数据库扫描和利用插件 - 包含数据库查询利用功能
type OraclePlugin struct {
name string
ports []int
}
// NewOraclePlugin 创建Oracle插件
func NewOraclePlugin() *OraclePlugin {
return &OraclePlugin{
name: "oracle",
ports: []int{1521, 1522, 1525}, // Oracle端口
}
}
// GetName 实现Plugin接口
func (p *OraclePlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *OraclePlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Oracle扫描 - 弱密码检测
func (p *OraclePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("oracle")
if len(credentials) == 0 {
// Oracle默认凭据
credentials = []Credential{
{Username: "sys", Password: "sys"},
{Username: "sys", Password: "system"},
{Username: "sys", Password: "oracle"},
{Username: "system", Password: "system"},
{Username: "system", Password: "oracle"},
{Username: "system", Password: "manager"},
{Username: "scott", Password: "tiger"},
{Username: "oracle", Password: "oracle"},
{Username: "admin", Password: "admin"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "oracle",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if db := p.testCredential(ctx, info, cred); db != nil {
db.Close() // 关闭测试连接
// Oracle认证成功
common.LogSuccess(i18n.GetText("oracle_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "oracle",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "oracle",
Error: fmt.Errorf("未发现弱密码"),
}
}
// Exploit 执行Oracle利用操作 - 实现数据库查询功能
func (p *OraclePlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Oracle连接
db := p.testCredential(ctx, info, creds)
if db == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Oracle连接失败"),
}
}
defer db.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Oracle利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Oracle利用结果 - %s ===\n", target))
// 获取版本信息
if version := p.getVersion(db); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取数据库信息
if dbInfo := p.getDatabaseInfo(db); dbInfo != "" {
output.WriteString(fmt.Sprintf("\n[数据库信息]\n%s\n", dbInfo))
}
// 获取表空间信息
if tablespaces := p.getTablespaces(db); len(tablespaces) > 0 {
output.WriteString(fmt.Sprintf("\n[表空间] (共%d个)\n", len(tablespaces)))
for i, ts := range tablespaces {
if i >= 5 { // 限制显示前5个
output.WriteString("... (更多表空间)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", ts))
}
}
// 获取用户列表
if users := p.getUsers(db); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户列表] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 10 { // 限制显示前10个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 获取表列表(当前用户)
if tables := p.getTables(db, creds.Username); len(tables) > 0 {
output.WriteString(fmt.Sprintf("\n[用户表] (共%d个)\n", len(tables)))
for i, table := range tables {
if i >= 5 { // 限制显示前5个表
output.WriteString("... (更多表)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", table))
}
}
// 获取权限信息
if privileges := p.getPrivileges(db, creds.Username); privileges != "" {
output.WriteString(fmt.Sprintf("\n[用户权限]\n%s\n", privileges))
}
common.LogSuccess(fmt.Sprintf("Oracle利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testCredential 测试单个凭据 - 返回数据库连接或nil
func (p *OraclePlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB {
// Oracle驱动需要特殊安装这里简化实现
// 在实际环境中需要安装Oracle客户端和go-oci8驱动
return nil
}
// getVersion 获取Oracle版本信息
func (p *OraclePlugin) getVersion(db *sql.DB) string {
var version string
err := db.QueryRow("SELECT banner FROM v$version WHERE rownum = 1").Scan(&version)
if err != nil {
// 尝试备用查询
err = db.QueryRow("SELECT version FROM product_component_version WHERE rownum = 1").Scan(&version)
if err != nil {
return ""
}
}
return version
}
// getDatabaseInfo 获取数据库基本信息
func (p *OraclePlugin) getDatabaseInfo(db *sql.DB) string {
var info strings.Builder
// 获取数据库名
var dbName string
err := db.QueryRow("SELECT name FROM v$database").Scan(&dbName)
if err == nil {
info.WriteString(fmt.Sprintf("数据库名: %s\n", dbName))
}
// 获取实例名
var instanceName string
err = db.QueryRow("SELECT instance_name FROM v$instance").Scan(&instanceName)
if err == nil {
info.WriteString(fmt.Sprintf("实例名: %s\n", instanceName))
}
// 获取字符集
var charset string
err = db.QueryRow("SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET'").Scan(&charset)
if err == nil {
info.WriteString(fmt.Sprintf("字符集: %s\n", charset))
}
return info.String()
}
// getTablespaces 获取表空间列表
func (p *OraclePlugin) getTablespaces(db *sql.DB) []string {
query := "SELECT tablespace_name FROM dba_tablespaces"
rows, err := db.Query(query)
if err != nil {
// 尝试用户表空间查询
query = "SELECT tablespace_name FROM user_tablespaces"
rows, err = db.Query(query)
if err != nil {
return nil
}
}
defer rows.Close()
var tablespaces []string
for rows.Next() {
var tsName string
if err := rows.Scan(&tsName); err == nil {
tablespaces = append(tablespaces, tsName)
}
}
return tablespaces
}
// getUsers 获取用户列表
func (p *OraclePlugin) getUsers(db *sql.DB) []string {
query := "SELECT username FROM dba_users ORDER BY username"
rows, err := db.Query(query)
if err != nil {
// 尝试all_users
query = "SELECT username FROM all_users ORDER BY username"
rows, err = db.Query(query)
if err != nil {
return nil
}
}
defer rows.Close()
var users []string
for rows.Next() {
var userName string
if err := rows.Scan(&userName); err == nil {
users = append(users, userName)
}
}
return users
}
// getTables 获取指定用户的表列表
func (p *OraclePlugin) getTables(db *sql.DB, owner string) []string {
query := "SELECT table_name FROM dba_tables WHERE owner = :1 ORDER BY table_name"
rows, err := db.Query(query, strings.ToUpper(owner))
if err != nil {
// 尝试用户表查询
query = "SELECT table_name FROM user_tables ORDER BY table_name"
rows, err = db.Query(query)
if err != nil {
return nil
}
}
defer rows.Close()
var tables []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err == nil {
tables = append(tables, tableName)
}
}
return tables
}
// getPrivileges 获取用户权限信息
func (p *OraclePlugin) getPrivileges(db *sql.DB, username string) string {
var privileges strings.Builder
// 检查DBA权限
var dbaRole int
err := db.QueryRow("SELECT COUNT(*) FROM dba_role_privs WHERE grantee = :1 AND granted_role = 'DBA'",
strings.ToUpper(username)).Scan(&dbaRole)
if err == nil && dbaRole > 0 {
privileges.WriteString("DBA权限: YES\n")
} else {
privileges.WriteString("DBA权限: NO\n")
}
// 检查SYSDBA权限
var sysdbaCount int
err = db.QueryRow("SELECT COUNT(*) FROM v$pwfile_users WHERE username = :1 AND sysdba = 'TRUE'",
strings.ToUpper(username)).Scan(&sysdbaCount)
if err == nil && sysdbaCount > 0 {
privileges.WriteString("SYSDBA权限: YES\n")
}
// 获取角色列表
query := "SELECT granted_role FROM dba_role_privs WHERE grantee = :1 AND rownum <= 5"
rows, err := db.Query(query, strings.ToUpper(username))
if err == nil {
defer rows.Close()
privileges.WriteString("已授予角色: ")
var roles []string
for rows.Next() {
var role string
if err := rows.Scan(&role); err == nil {
roles = append(roles, role)
}
}
if len(roles) > 0 {
privileges.WriteString(strings.Join(roles, ", ") + "\n")
} else {
privileges.WriteString("无\n")
}
}
return privileges.String()
}
// identifyService 服务识别 - 检测Oracle服务
func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
// Oracle驱动需要特殊安装这里简化实现
// 在实际环境中需要安装Oracle客户端和go-oci8驱动
return &ScanResult{
Success: false,
Service: "oracle",
Error: fmt.Errorf("Oracle驱动未安装"),
}
}
// init 自动注册插件
func init() {
RegisterPlugin("oracle", func() Plugin {
return NewOraclePlugin()
})
}

354
plugins/postgresql.go Normal file
View File

@ -0,0 +1,354 @@
package plugins
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/lib/pq"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// PostgreSQLPlugin PostgreSQL数据库扫描和利用插件 - 包含数据库查询利用功能
type PostgreSQLPlugin struct {
name string
ports []int
}
// NewPostgreSQLPlugin 创建PostgreSQL插件
func NewPostgreSQLPlugin() *PostgreSQLPlugin {
return &PostgreSQLPlugin{
name: "postgresql",
ports: []int{5432, 5433, 5434}, // PostgreSQL端口
}
}
// GetName 实现Plugin接口
func (p *PostgreSQLPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *PostgreSQLPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行PostgreSQL扫描 - 弱密码检测
func (p *PostgreSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("postgresql")
if len(credentials) == 0 {
// PostgreSQL默认凭据
credentials = []Credential{
{Username: "postgres", Password: ""},
{Username: "postgres", Password: "postgres"},
{Username: "postgres", Password: "password"},
{Username: "postgres", Password: "admin"},
{Username: "postgres", Password: "123456"},
{Username: "admin", Password: "admin"},
{Username: "root", Password: "root"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "postgresql",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if db := p.testCredential(ctx, info, cred); db != nil {
db.Close() // 关闭测试连接
// PostgreSQL认证成功
common.LogSuccess(i18n.GetText("postgresql_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "postgresql",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "postgresql",
Error: fmt.Errorf("未发现弱密码"),
}
}
// Exploit 执行PostgreSQL利用操作 - 实现数据库查询功能
func (p *PostgreSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立PostgreSQL连接
db := p.testCredential(ctx, info, creds)
if db == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("PostgreSQL连接失败"),
}
}
defer db.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("PostgreSQL利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== PostgreSQL利用结果 - %s ===\n", target))
// 获取版本信息
if version := p.getVersion(db); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取数据库列表
if databases := p.getDatabases(db); len(databases) > 0 {
output.WriteString(fmt.Sprintf("\n[数据库列表] (共%d个)\n", len(databases)))
for i, dbName := range databases {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多数据库)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", dbName))
}
}
// 获取表列表(当前数据库)
if tables := p.getTables(db); len(tables) > 0 {
output.WriteString(fmt.Sprintf("\n[表列表] (共%d个)\n", len(tables)))
for i, table := range tables {
if i >= 10 { // 限制显示前10个表
output.WriteString("... (更多表)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", table))
}
}
// 获取用户列表
if users := p.getUsers(db); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户列表] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 10 { // 限制显示前10个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 获取权限信息
if privileges := p.getPrivileges(db, creds.Username); privileges != "" {
output.WriteString(fmt.Sprintf("\n[用户权限]\n%s\n", privileges))
}
common.LogSuccess(fmt.Sprintf("PostgreSQL利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testCredential 测试单个凭据 - 返回数据库连接或nil
func (p *PostgreSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB {
// 构建连接字符串
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/postgres?sslmode=disable&connect_timeout=%d",
cred.Username, cred.Password, info.Host, info.Ports, common.Timeout)
// 打开数据库连接
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil
}
// 设置连接参数
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 创建超时上下文
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 测试连接
err = db.PingContext(pingCtx)
if err != nil {
db.Close()
return nil
}
return db
}
// getVersion 获取PostgreSQL版本信息
func (p *PostgreSQLPlugin) getVersion(db *sql.DB) string {
var version string
err := db.QueryRow("SELECT version()").Scan(&version)
if err != nil {
return ""
}
return version
}
// getDatabases 获取数据库列表
func (p *PostgreSQLPlugin) getDatabases(db *sql.DB) []string {
query := "SELECT datname FROM pg_database WHERE datistemplate = false"
rows, err := db.Query(query)
if err != nil {
return nil
}
defer rows.Close()
var databases []string
for rows.Next() {
var dbName string
if err := rows.Scan(&dbName); err == nil {
databases = append(databases, dbName)
}
}
return databases
}
// getTables 获取表列表
func (p *PostgreSQLPlugin) getTables(db *sql.DB) []string {
query := `SELECT tablename FROM pg_tables WHERE schemaname = 'public'
UNION SELECT viewname FROM pg_views WHERE schemaname = 'public'
ORDER BY 1 LIMIT 20`
rows, err := db.Query(query)
if err != nil {
return nil
}
defer rows.Close()
var tables []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err == nil {
tables = append(tables, tableName)
}
}
return tables
}
// getUsers 获取用户列表
func (p *PostgreSQLPlugin) getUsers(db *sql.DB) []string {
query := "SELECT usename FROM pg_user ORDER BY usename"
rows, err := db.Query(query)
if err != nil {
return nil
}
defer rows.Close()
var users []string
for rows.Next() {
var userName string
if err := rows.Scan(&userName); err == nil {
users = append(users, userName)
}
}
return users
}
// getPrivileges 获取用户权限信息
func (p *PostgreSQLPlugin) getPrivileges(db *sql.DB, username string) string {
var privileges strings.Builder
// 检查是否为超级用户
var isSuperUser bool
err := db.QueryRow("SELECT usesuper FROM pg_user WHERE usename = $1", username).Scan(&isSuperUser)
if err == nil {
if isSuperUser {
privileges.WriteString("超级用户权限: YES\n")
} else {
privileges.WriteString("超级用户权限: NO\n")
}
}
// 获取用户属性
var createDB, createRole bool
err = db.QueryRow("SELECT usecreatedb, usecreaterole FROM pg_user WHERE usename = $1",
username).Scan(&createDB, &createRole)
if err == nil {
privileges.WriteString(fmt.Sprintf("创建数据库权限: %v\n", createDB))
privileges.WriteString(fmt.Sprintf("创建角色权限: %v\n", createRole))
}
return privileges.String()
}
// identifyService 服务识别 - 检测PostgreSQL服务
func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接PostgreSQL服务
connStr := fmt.Sprintf("postgres://invalid:invalid@%s:%s/postgres?sslmode=disable&connect_timeout=%d",
info.Host, info.Ports, common.Timeout)
db, err := sql.Open("postgres", connStr)
if err != nil {
return &ScanResult{
Success: false,
Service: "postgresql",
Error: err,
}
}
defer db.Close()
// 尝试连接(即使认证失败,也能识别服务)
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
err = db.PingContext(pingCtx)
var banner string
if err != nil && strings.Contains(strings.ToLower(err.Error()), "postgres") {
banner = "PostgreSQL数据库服务"
} else if err == nil {
banner = "PostgreSQL数据库 (连接成功)"
} else {
return &ScanResult{
Success: false,
Service: "postgresql",
Error: fmt.Errorf("无法识别为PostgreSQL服务"),
}
}
common.LogSuccess(i18n.GetText("postgresql_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "postgresql",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("postgresql", func() Plugin {
return NewPostgreSQLPlugin()
})
}

508
plugins/rabbitmq.go Normal file
View File

@ -0,0 +1,508 @@
package plugins
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// RabbitMQPlugin RabbitMQ消息队列扫描和利用插件 - 包含队列信息提取利用功能
type RabbitMQPlugin struct {
name string
ports []int
}
// NewRabbitMQPlugin 创建RabbitMQ插件
func NewRabbitMQPlugin() *RabbitMQPlugin {
return &RabbitMQPlugin{
name: "rabbitmq",
ports: []int{5672, 15672, 5671}, // AMQP、管理界面、AMQPS端口
}
}
// GetName 实现Plugin接口
func (p *RabbitMQPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *RabbitMQPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行RabbitMQ扫描 - 弱密码检测
func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// RabbitMQ管理端口通常是15672AMQP端口是5672
if info.Ports == "5672" || info.Ports == "5671" {
// AMQP端口进行协议检测
return p.testAMQPProtocol(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("rabbitmq")
if len(credentials) == 0 {
// RabbitMQ默认凭据
credentials = []Credential{
{Username: "guest", Password: "guest"},
{Username: "admin", Password: "admin"},
{Username: "admin", Password: "password"},
{Username: "admin", Password: "123456"},
{Username: "rabbitmq", Password: "rabbitmq"},
{Username: "user", Password: "user"},
}
}
// 逐个测试凭据(针对管理接口)
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "rabbitmq",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// RabbitMQ认证成功
common.LogSuccess(i18n.GetText("rabbitmq_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "rabbitmq",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("未发现弱密码"),
}
}
// Exploit 执行RabbitMQ利用操作 - 实现队列信息提取功能
func (p *RabbitMQPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("RabbitMQ利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== RabbitMQ利用结果 - %s ===\n", target))
// 获取集群信息
if clusterInfo := p.getClusterInfo(ctx, info, creds); clusterInfo != "" {
output.WriteString(fmt.Sprintf("\n[集群信息]\n%s\n", clusterInfo))
}
// 获取节点信息
if nodes := p.getNodes(ctx, info, creds); len(nodes) > 0 {
output.WriteString(fmt.Sprintf("\n[节点列表] (共%d个)\n", len(nodes)))
for i, node := range nodes {
if i >= 5 { // 限制显示前5个节点
output.WriteString("... (更多节点)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", node))
}
}
// 获取虚拟主机
if vhosts := p.getVHosts(ctx, info, creds); len(vhosts) > 0 {
output.WriteString(fmt.Sprintf("\n[虚拟主机] (共%d个)\n", len(vhosts)))
for _, vhost := range vhosts {
output.WriteString(fmt.Sprintf(" %s\n", vhost))
}
}
// 获取用户列表
if users := p.getUsers(ctx, info, creds); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户列表] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 10 { // 限制显示前10个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 获取队列列表
if queues := p.getQueues(ctx, info, creds); len(queues) > 0 {
output.WriteString(fmt.Sprintf("\n[队列列表] (共%d个)\n", len(queues)))
for i, queue := range queues {
if i >= 10 { // 限制显示前10个队列
output.WriteString("... (更多队列)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", queue))
}
}
// 获取交换器列表
if exchanges := p.getExchanges(ctx, info, creds); len(exchanges) > 0 {
output.WriteString(fmt.Sprintf("\n[交换器] (共%d个)\n", len(exchanges)))
for i, exchange := range exchanges {
if i >= 5 { // 限制显示前5个交换器
output.WriteString("... (更多交换器)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", exchange))
}
}
common.LogSuccess(fmt.Sprintf("RabbitMQ利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testAMQPProtocol 测试AMQP协议
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *ScanResult {
// 对于AMQP端口我们只做协议识别
// AMQP协议检测比较复杂这里简化处理
return &ScanResult{
Success: true,
Service: "rabbitmq",
Banner: "RabbitMQ AMQP协议端口",
}
}
// testCredential 测试单个凭据通过管理API
func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 构建管理API URL
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 尝试访问API overview
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
if err != nil {
return false
}
req.SetBasicAuth(cred.Username, cred.Password)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == 200
}
// makeAPIRequest 执行API请求
func (p *RabbitMQPlugin) makeAPIRequest(ctx context.Context, info *common.HostInfo, creds Credential, endpoint string) (map[string]interface{}, error) {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+endpoint, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(creds.Username, creds.Password)
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("API请求失败状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
// 尝试解析为数组
var arrayResult []map[string]interface{}
if err2 := json.Unmarshal(body, &arrayResult); err2 == nil {
// 如果是数组,返回包装的结果
return map[string]interface{}{"data": arrayResult}, nil
}
return nil, err
}
return result, nil
}
// getClusterInfo 获取集群信息
func (p *RabbitMQPlugin) getClusterInfo(ctx context.Context, info *common.HostInfo, creds Credential) string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/overview")
if err != nil {
return ""
}
var info_str strings.Builder
if managementVersion, ok := result["management_version"]; ok {
info_str.WriteString(fmt.Sprintf("管理版本: %v\n", managementVersion))
}
if rabbitMQVersion, ok := result["rabbitmq_version"]; ok {
info_str.WriteString(fmt.Sprintf("RabbitMQ版本: %v\n", rabbitMQVersion))
}
if clusterName, ok := result["cluster_name"]; ok {
info_str.WriteString(fmt.Sprintf("集群名称: %v\n", clusterName))
}
return info_str.String()
}
// getNodes 获取节点列表
func (p *RabbitMQPlugin) getNodes(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/nodes")
if err != nil {
return nil
}
var nodes []string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if node, ok := item.(map[string]interface{}); ok {
if name, ok := node["name"]; ok {
if nameStr, ok := name.(string); ok {
nodes = append(nodes, nameStr)
}
}
}
}
}
return nodes
}
// getVHosts 获取虚拟主机列表
func (p *RabbitMQPlugin) getVHosts(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/vhosts")
if err != nil {
return nil
}
var vhosts []string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if vhost, ok := item.(map[string]interface{}); ok {
if name, ok := vhost["name"]; ok {
if nameStr, ok := name.(string); ok {
vhosts = append(vhosts, nameStr)
}
}
}
}
}
return vhosts
}
// getUsers 获取用户列表
func (p *RabbitMQPlugin) getUsers(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/users")
if err != nil {
return nil
}
var users []string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if user, ok := item.(map[string]interface{}); ok {
if name, ok := user["name"]; ok {
if nameStr, ok := name.(string); ok {
// 获取用户标签信息
var tags string
if tagsInterface, ok := user["tags"]; ok {
if tagsStr, ok := tagsInterface.(string); ok {
tags = fmt.Sprintf(" [%s]", tagsStr)
}
}
users = append(users, nameStr+tags)
}
}
}
}
}
return users
}
// getQueues 获取队列列表
func (p *RabbitMQPlugin) getQueues(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/queues")
if err != nil {
return nil
}
var queues []string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if queue, ok := item.(map[string]interface{}); ok {
if name, ok := queue["name"]; ok {
if nameStr, ok := name.(string); ok {
// 获取队列消息数
var messages string
if messagesInterface, ok := queue["messages"]; ok {
messages = fmt.Sprintf(" (消息数: %v)", messagesInterface)
}
queues = append(queues, nameStr+messages)
}
}
}
}
}
return queues
}
// getExchanges 获取交换器列表
func (p *RabbitMQPlugin) getExchanges(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.makeAPIRequest(ctx, info, creds, "/api/exchanges")
if err != nil {
return nil
}
var exchanges []string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if exchange, ok := item.(map[string]interface{}); ok {
if name, ok := exchange["name"]; ok {
if nameStr, ok := name.(string); ok {
// 过滤掉空名称的默认交换器
if nameStr == "" {
nameStr = "(default)"
}
// 获取交换器类型
var exchType string
if typeInterface, ok := exchange["type"]; ok {
if typeStr, ok := typeInterface.(string); ok {
exchType = fmt.Sprintf(" [%s]", typeStr)
}
}
exchanges = append(exchanges, nameStr+exchType)
}
}
}
}
}
return exchanges
}
// identifyService 服务识别 - 检测RabbitMQ服务
func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 根据端口类型进行不同的识别
if info.Ports == "5672" || info.Ports == "5671" {
// AMQP端口
return &ScanResult{
Success: true,
Service: "rabbitmq",
Banner: "RabbitMQ AMQP协议服务",
}
}
// 管理端口尝试HTTP请求
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
return &ScanResult{
Success: false,
Service: "rabbitmq",
Error: err,
}
}
resp, err := client.Do(req)
if err != nil {
return &ScanResult{
Success: false,
Service: "rabbitmq",
Error: err,
}
}
defer resp.Body.Close()
// 检查响应是否包含RabbitMQ特征
var banner string
if resp.StatusCode == 200 || resp.StatusCode == 401 {
body, _ := io.ReadAll(resp.Body)
bodyStr := strings.ToLower(string(body))
if strings.Contains(bodyStr, "rabbitmq") {
banner = "RabbitMQ管理界面"
} else if strings.Contains(bodyStr, "management") {
banner = "RabbitMQ服务 (管理端口)"
} else {
banner = "RabbitMQ消息队列服务"
}
} else {
return &ScanResult{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
}
}
common.LogSuccess(i18n.GetText("rabbitmq_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "rabbitmq",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("rabbitmq", func() Plugin {
return NewRabbitMQPlugin()
})
}

475
plugins/redis.go Normal file
View File

@ -0,0 +1,475 @@
package plugins
import (
"context"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// RedisPlugin Redis数据库扫描和利用插件 - 包含文件写入利用功能
type RedisPlugin struct {
name string
ports []int
}
// NewRedisPlugin 创建Redis插件
func NewRedisPlugin() *RedisPlugin {
return &RedisPlugin{
name: "redis",
ports: []int{6379, 6380, 6381, 16379, 26379}, // Redis端口
}
}
// GetName 实现Plugin接口
func (p *RedisPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *RedisPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Redis扫描 - 未授权访问检测和弱密码检测
func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 首先检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("redis_unauth_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("redis")
if len(credentials) == 0 {
// Redis默认凭据
credentials = []Credential{
{Username: "", Password: ""},
{Username: "", Password: "redis"},
{Username: "", Password: "password"},
{Username: "", Password: "123456"},
{Username: "", Password: "admin"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "redis",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if conn := p.testCredential(ctx, info, cred); conn != nil {
conn.Close() // 关闭测试连接
// Redis认证成功
common.LogSuccess(i18n.GetText("redis_scan_success", target, cred.Password))
return &ScanResult{
Success: true,
Service: "redis",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "redis",
Error: fmt.Errorf("未发现弱密码或未授权访问"),
}
}
// Exploit 执行Redis利用操作 - 实现文件写入功能
func (p *RedisPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Redis连接
conn := p.testCredential(ctx, info, creds)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Redis连接失败"),
}
}
defer conn.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Redis利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Redis利用结果 - %s ===\n", target))
// 获取Redis基本信息
if info := p.getRedisInfo(conn); info != "" {
output.WriteString(fmt.Sprintf("\n[Redis信息]\n%s\n", info))
}
// 获取键值信息
if keys := p.getRedisKeys(conn); len(keys) > 0 {
output.WriteString(fmt.Sprintf("\n[数据库键] (共%d个)\n", len(keys)))
for i, key := range keys {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多键值)\n")
break
}
value := p.getKeyValue(conn, key)
output.WriteString(fmt.Sprintf(" %s: %s\n", key, value))
}
}
// 获取配置信息
if config := p.getRedisConfig(conn); config != "" {
output.WriteString(fmt.Sprintf("\n[配置信息]\n%s\n", config))
}
// 尝试文件写入测试(如果有写权限)
if testResult := p.testFileWrite(conn); testResult != "" {
output.WriteString(fmt.Sprintf("\n[文件写入测试]\n%s\n", testResult))
}
common.LogSuccess(fmt.Sprintf("Redis利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
// 尝试无密码连接
emptyCred := Credential{Username: "", Password: ""}
if conn := p.testCredential(ctx, info, emptyCred); conn != nil {
conn.Close()
return &ScanResult{
Success: true,
Service: "redis",
Banner: "未授权访问",
}
}
return nil
}
// testCredential 测试单个凭据 - 返回Redis连接或nil
func (p *RedisPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) net.Conn {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 使用Context控制超时的连接
type connResult struct {
conn net.Conn
err error
}
connChan := make(chan connResult, 1)
go func() {
// 建立TCP连接
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
connChan <- connResult{nil, err}
return
}
// 如果有密码,进行认证
if cred.Password != "" {
authCmd := fmt.Sprintf("AUTH %s\r\n", cred.Password)
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(authCmd)); err != nil {
conn.Close()
connChan <- connResult{nil, err}
return
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil || !strings.Contains(string(response[:n]), "+OK") {
conn.Close()
connChan <- connResult{nil, fmt.Errorf("认证失败")}
return
}
}
// 发送PING命令测试连接
pingCmd := "PING\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(pingCmd)); err != nil {
conn.Close()
connChan <- connResult{nil, err}
return
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil || !strings.Contains(string(response[:n]), "PONG") {
conn.Close()
connChan <- connResult{nil, fmt.Errorf("PING测试失败")}
return
}
connChan <- connResult{conn, nil}
}()
// 等待连接结果或超时
select {
case result := <-connChan:
if result.err != nil {
return nil
}
return result.conn
case <-ctx.Done():
return nil
}
}
// getRedisInfo 获取Redis服务器信息
func (p *RedisPlugin) getRedisInfo(conn net.Conn) string {
timeout := time.Duration(common.Timeout) * time.Second
// 发送INFO命令
infoCmd := "INFO server\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(infoCmd)); err != nil {
return ""
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 2048)
n, err := conn.Read(response)
if err != nil {
return ""
}
responseStr := string(response[:n])
lines := strings.Split(responseStr, "\r\n")
var info strings.Builder
for _, line := range lines {
if strings.HasPrefix(line, "redis_version:") ||
strings.HasPrefix(line, "redis_mode:") ||
strings.HasPrefix(line, "os:") ||
strings.HasPrefix(line, "arch_bits:") {
info.WriteString(line + "\n")
}
}
return info.String()
}
// getRedisKeys 获取Redis键列表
func (p *RedisPlugin) getRedisKeys(conn net.Conn) []string {
timeout := time.Duration(common.Timeout) * time.Second
// 发送KEYS命令获取所有键
keysCmd := "KEYS *\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(keysCmd)); err != nil {
return nil
}
conn.SetReadDeadline(time.Now().Add(timeout))
response, err := io.ReadAll(conn)
if err != nil {
return nil
}
responseStr := string(response)
lines := strings.Split(responseStr, "\r\n")
var keys []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "*") && !strings.HasPrefix(line, "$") && line != "+OK" {
keys = append(keys, line)
}
}
return keys
}
// getKeyValue 获取键的值
func (p *RedisPlugin) getKeyValue(conn net.Conn, key string) string {
timeout := time.Duration(common.Timeout) * time.Second
// 发送GET命令
getCmd := fmt.Sprintf("GET %s\r\n", key)
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(getCmd)); err != nil {
return "[error]"
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil {
return "[error]"
}
responseStr := string(response[:n])
lines := strings.Split(responseStr, "\r\n")
if len(lines) > 1 && lines[1] != "" {
if len(lines[1]) > 50 {
return lines[1][:50] + "..."
}
return lines[1]
}
return "[empty]"
}
// getRedisConfig 获取Redis配置信息
func (p *RedisPlugin) getRedisConfig(conn net.Conn) string {
timeout := time.Duration(common.Timeout) * time.Second
// 获取关键配置
configs := []string{"dir", "dbfilename", "save", "requirepass"}
var result strings.Builder
for _, config := range configs {
configCmd := fmt.Sprintf("CONFIG GET %s\r\n", config)
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(configCmd)); err != nil {
continue
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
continue
}
responseStr := string(response[:n])
lines := strings.Split(responseStr, "\r\n")
if len(lines) > 3 && lines[3] != "" {
result.WriteString(fmt.Sprintf("%s: %s\n", config, lines[3]))
}
}
return result.String()
}
// testFileWrite 测试文件写入功能
func (p *RedisPlugin) testFileWrite(conn net.Conn) string {
timeout := time.Duration(common.Timeout) * time.Second
// 尝试设置一个测试键
setCmd := "SET fscan_test \"FScan Security Test\"\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(setCmd)); err != nil {
return "❌ 无写权限: " + err.Error()
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil || !strings.Contains(string(response[:n]), "OK") {
return "❌ 设置键值失败"
}
// 删除测试键
delCmd := "DEL fscan_test\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
conn.Write([]byte(delCmd))
return "✅ 具有读写权限,可进行文件写入利用"
}
// identifyService 服务识别 - 检测Redis服务
func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 尝试连接Redis服务
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return &ScanResult{
Success: false,
Service: "redis",
Error: err,
}
}
defer conn.Close()
// 发送PING命令识别
pingCmd := "PING\r\n"
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(pingCmd)); err != nil {
return &ScanResult{
Success: false,
Service: "redis",
Error: err,
}
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil {
return &ScanResult{
Success: false,
Service: "redis",
Error: err,
}
}
responseStr := string(response[:n])
var banner string
if strings.Contains(responseStr, "PONG") {
banner = "Redis服务 (PONG响应)"
} else if strings.Contains(responseStr, "-NOAUTH") {
banner = "Redis服务 (需要认证)"
} else if strings.Contains(responseStr, "-ERR") {
banner = "Redis服务 (协议响应)"
} else {
banner = "Redis服务"
}
common.LogSuccess(i18n.GetText("redis_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "redis",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("redis", func() Plugin {
return NewRedisPlugin()
})
}

344
plugins/rsync.go Normal file
View File

@ -0,0 +1,344 @@
package plugins
import (
"bufio"
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// RsyncPlugin Rsync文件同步服务扫描和利用插件 - 包含文件列表利用功能
type RsyncPlugin struct {
name string
ports []int
}
// NewRsyncPlugin 创建Rsync插件
func NewRsyncPlugin() *RsyncPlugin {
return &RsyncPlugin{
name: "rsync",
ports: []int{873}, // Rsync端口
}
}
// GetName 实现Plugin接口
func (p *RsyncPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *RsyncPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Rsync扫描 - 未授权访问检测
func (p *RsyncPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// Rsync主要检查未授权访问和弱密码
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("rsync_unauth_success", target))
return result
}
// 如果未授权访问失败,尝试弱密码
if result := p.testWeakPasswords(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("rsync_weak_pwd_success", target))
return result
}
// 所有尝试都失败
return &ScanResult{
Success: false,
Service: "rsync",
Error: fmt.Errorf("Rsync服务连接失败或需要认证"),
}
}
// Exploit 执行Rsync利用操作 - 实现文件列表功能
func (p *RsyncPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Rsync连接
conn := p.connectToRsync(ctx, info)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Rsync连接失败"),
}
}
defer conn.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Rsync利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Rsync利用结果 - %s ===\n", target))
// 获取模块列表
modules := p.getModules(conn)
if len(modules) > 0 {
output.WriteString(fmt.Sprintf("\n[可用模块] (共%d个)\n", len(modules)))
for _, module := range modules {
output.WriteString(fmt.Sprintf(" %s\n", module))
}
// 尝试列出每个模块的内容
for i, module := range modules {
if i >= 3 { // 限制最多列出3个模块内容
break
}
moduleName := strings.Fields(module)[0] // 获取模块名
if files := p.getModuleFiles(ctx, info, moduleName); len(files) > 0 {
output.WriteString(fmt.Sprintf("\n[模块 %s 文件列表] (共%d个)\n", moduleName, len(files)))
for j, file := range files {
if j >= 20 { // 每个模块最多显示20个文件
output.WriteString("... (更多文件)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", file))
}
}
}
} else {
output.WriteString("\n[模块列表] 无可访问模块或服务受限\n")
}
// 测试写权限
if writeTest := p.testWritePermission(ctx, info); writeTest != "" {
output.WriteString(fmt.Sprintf("\n[写权限测试]\n%s\n", writeTest))
}
common.LogSuccess(fmt.Sprintf("Rsync利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
conn := p.connectToRsync(ctx, info)
if conn == nil {
return nil
}
defer conn.Close()
// 尝试列出模块
modules := p.getModules(conn)
if len(modules) > 0 {
return &ScanResult{
Success: true,
Service: "rsync",
Banner: "未授权访问",
}
}
return nil
}
// testWeakPasswords 测试弱密码
func (p *RsyncPlugin) testWeakPasswords(ctx context.Context, info *common.HostInfo) *ScanResult {
// Rsync密码通常在模块级别设置这里简化处理
// 实际场景中需要针对具体模块进行认证
return nil
}
// connectToRsync 连接到Rsync服务
func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo) net.Conn {
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 nil
}
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
return conn
}
// getModules 获取Rsync模块列表
func (p *RsyncPlugin) getModules(conn net.Conn) []string {
// 发送RSYNCD协议的模块列表请求
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte("\n")); err != nil {
return nil
}
conn.SetReadDeadline(time.Now().Add(timeout))
scanner := bufio.NewScanner(conn)
var modules []string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
// Rsync协议结束标记
if strings.HasPrefix(line, "@RSYNCD: EXIT") {
break
}
// 跳过协议头
if strings.HasPrefix(line, "@RSYNCD:") {
continue
}
modules = append(modules, line)
}
return modules
}
// getModuleFiles 获取指定模块的文件列表
func (p *RsyncPlugin) getModuleFiles(ctx context.Context, info *common.HostInfo, module string) []string {
conn := p.connectToRsync(ctx, info)
if conn == nil {
return nil
}
defer conn.Close()
timeout := time.Duration(common.Timeout) * time.Second
// 发送模块名和协议版本
request := fmt.Sprintf("%s\n", module)
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(request)); err != nil {
return nil
}
conn.SetReadDeadline(time.Now().Add(timeout))
scanner := bufio.NewScanner(conn)
var files []string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
// 检查错误响应
if strings.Contains(line, "@ERROR") {
break
}
// Rsync协议结束标记
if strings.HasPrefix(line, "@RSYNCD: EXIT") {
break
}
// 跳过协议头
if strings.HasPrefix(line, "@RSYNCD:") {
continue
}
files = append(files, line)
// 限制文件数量避免过多输出
if len(files) >= 50 {
break
}
}
return files
}
// testWritePermission 测试写权限
func (p *RsyncPlugin) testWritePermission(ctx context.Context, info *common.HostInfo) string {
// Rsync写权限测试比较复杂需要实际的rsync客户端
// 这里简化为检测是否支持上传
return "❌ 写权限检测需要完整的rsync客户端支持"
}
// identifyService 服务识别 - 检测Rsync服务
func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn := p.connectToRsync(ctx, info)
if conn == nil {
return &ScanResult{
Success: false,
Service: "rsync",
Error: fmt.Errorf("无法连接到Rsync服务"),
}
}
defer conn.Close()
// 尝试Rsync协议握手
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte("\n")); err != nil {
return &ScanResult{
Success: false,
Service: "rsync",
Error: err,
}
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return &ScanResult{
Success: false,
Service: "rsync",
Error: err,
}
}
responseStr := string(response[:n])
var banner string
if strings.Contains(responseStr, "@RSYNCD") {
// 解析版本信息
lines := strings.Split(responseStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "@RSYNCD:") {
banner = fmt.Sprintf("Rsync服务 (%s)", strings.TrimSpace(line))
break
}
}
if banner == "" {
banner = "Rsync文件同步服务"
}
} else {
return &ScanResult{
Success: false,
Service: "rsync",
Error: fmt.Errorf("无法识别为Rsync服务"),
}
}
common.LogSuccess(i18n.GetText("rsync_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "rsync",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("rsync", func() Plugin {
return NewRsyncPlugin()
})
}

View File

@ -1,193 +0,0 @@
package activemq
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// ActiveMQConnector 实现ActiveMQ消息队列服务连接器
// 基于STOMP协议提供标准化的ActiveMQ连接和认证功能
// 遵循 base.ServiceConnector 接口规范,支持弱密码检测和自动利用
type ActiveMQConnector struct {
host string // 目标主机地址
port int // 目标端口号
timeout time.Duration // 连接超时时间
}
// NewActiveMQConnector 创建新的ActiveMQ连接器实例
func NewActiveMQConnector() *ActiveMQConnector {
return &ActiveMQConnector{
timeout: time.Duration(common.Timeout) * time.Second,
}
}
// Connect 建立到ActiveMQ服务的基础连接
// 实现 base.ServiceConnector 接口的 Connect 方法
// 返回原始TCP连接供后续认证阶段使用
func (c *ActiveMQConnector) 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
target := fmt.Sprintf("%s:%d", info.Host, port)
// 创建带超时的TCP连接
conn, err := c.connectWithTimeout(ctx, target)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
return conn, nil
}
// Authenticate 使用STOMP协议对ActiveMQ服务进行身份认证
func (c *ActiveMQConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
// 从连接接口中获取TCP连接
tcpConn, ok := conn.(net.Conn)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 使用STOMP协议进行认证
err := c.authenticateSTOMP(ctx, tcpConn, cred.Username, cred.Password)
if err == nil {
common.LogDebug(i18n.GetText("activemq_stomp_auth_success", cred.Username, c.host, c.port))
} else {
common.LogDebug(i18n.GetText("activemq_stomp_auth_failed", err))
}
return err
}
// Close 关闭ActiveMQ连接
// 实现 base.ServiceConnector 接口的 Close 方法
// 发送STOMP DISCONNECT帧进行优雅断开
func (c *ActiveMQConnector) Close(conn interface{}) error {
if conn == nil {
return nil
}
tcpConn, ok := conn.(net.Conn)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 发送DISCONNECT帧
disconnectFrame := "DISCONNECT\n\n\x00"
tcpConn.Write([]byte(disconnectFrame))
// 关闭连接
return tcpConn.Close()
}
// connectWithTimeout 创建带超时的TCP连接
func (c *ActiveMQConnector) connectWithTimeout(ctx context.Context, target string) (net.Conn, error) {
// 使用现有的TCP包装器以保持兼容性
return common.WrapperTcpWithTimeout("tcp", target, c.timeout)
}
// authenticateSTOMP 使用STOMP协议进行身份验证
func (c *ActiveMQConnector) authenticateSTOMP(ctx context.Context, conn net.Conn, username, password string) error {
// 构造STOMP CONNECT命令
// STOMP是一种简单的文本协议用于与消息代理通信
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00",
username, password)
// 设置写超时并发送认证请求
if err := conn.SetWriteDeadline(time.Now().Add(c.timeout)); err != nil {
return fmt.Errorf("设置写超时失败: %v", err)
}
if _, err := conn.Write([]byte(stompConnect)); err != nil {
return fmt.Errorf("发送认证请求失败: %v", err)
}
// 设置读超时并读取响应
if err := conn.SetReadDeadline(time.Now().Add(c.timeout)); err != nil {
return fmt.Errorf("设置读超时失败: %v", err)
}
// 读取服务器响应
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
// 解析STOMP响应
success, parseErr := c.parseSTOMPResponse(string(response[:n]))
if !success {
return parseErr
}
return nil
}
// parseSTOMPResponse 解析STOMP协议响应
func (c *ActiveMQConnector) parseSTOMPResponse(response string) (bool, error) {
// 检查成功的连接响应
if strings.Contains(response, "CONNECTED") {
return true, nil
}
// 检查认证失败响应
if strings.Contains(response, "ERROR") {
// 提取错误信息
lines := strings.Split(response, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "message:") {
errorMsg := strings.TrimPrefix(line, "message:")
return false, fmt.Errorf("认证失败: %s", errorMsg)
}
}
return false, fmt.Errorf("认证失败: 服务器返回ERROR")
}
// 检查其他可能的认证失败指示
if strings.Contains(response, "Authentication failed") ||
strings.Contains(response, "Access denied") ||
strings.Contains(response, "Invalid credentials") {
return false, fmt.Errorf("认证失败: 无效凭据")
}
// 未知响应类型
return false, fmt.Errorf("未知响应格式: %s", response)
}
// 已移除未使用的 getProtocolByPort 方法
// GetDefaultCredentials 获取ActiveMQ默认凭据
func (c *ActiveMQConnector) GetDefaultCredentials() []*base.Credential {
return []*base.Credential{
{Username: "admin", Password: "admin"},
{Username: "admin", Password: "Aa123456789"}, // 测试环境凭据
{Username: "test", Password: "test123"}, // 测试环境凭据
{Username: "root", Password: "root123"}, // 测试环境凭据
{Username: "system", Password: "admin123"}, // 测试环境凭据
{Username: "admin", Password: "password"},
{Username: "admin", Password: "123456"},
{Username: "user", Password: "user"},
{Username: "guest", Password: "guest"},
{Username: "activemq", Password: "activemq"},
{Username: "mqadmin", Password: "mqadmin"},
{Username: "broker", Password: "broker"},
}
}

View File

@ -1,37 +0,0 @@
package activemq
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// ActiveMQExploiter ActiveMQ利用器实现 - 最小化版本,不提供利用功能
type ActiveMQExploiter struct {
*base.BaseExploiter
}
// NewActiveMQExploiter 创建ActiveMQ利用器
func NewActiveMQExploiter() *ActiveMQExploiter {
exploiter := &ActiveMQExploiter{
BaseExploiter: base.NewBaseExploiter("activemq"),
}
// ActiveMQ插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *ActiveMQExploiter) setupExploitMethods() {
// ActiveMQ插件暂时不提供利用功能因为当前实现的都是信息收集类功能
// 没有实际的GetShell或文件写入等攻击价值
}
// Exploit 利用接口实现 - 空实现
func (e *ActiveMQExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// ActiveMQ插件不提供利用功能
return nil, nil
}

View File

@ -1,281 +0,0 @@
package activemq
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// ActiveMQ插件基于新一代插件架构的完整实现
// 支持STOMP协议的弱密码检测、信息收集、队列管理等功能
// 展示了消息队列服务插件的标准实现模式
// ActiveMQPlugin ActiveMQ消息队列扫描和利用插件
// 集成了弱密码检测、自动利用、信息收集等完整功能
type ActiveMQPlugin struct {
*base.ServicePlugin // 继承基础服务插件功能
exploiter *ActiveMQExploiter // ActiveMQ专用利用模块
}
// NewActiveMQPlugin 创建新的ActiveMQ插件实例
// 这是标准的插件工厂函数,展示了新架构的完整初始化流程
func NewActiveMQPlugin() *ActiveMQPlugin {
// 定义插件元数据 - 这些信息用于插件注册和管理
metadata := &base.PluginMetadata{
Name: "activemq", // 插件唯一标识符
Version: "2.0.0", // 插件版本(新架构版本)
Author: "fscan-team", // 开发团队
Description: "ActiveMQ消息队列扫描和利用插件", // 功能描述
Category: "service", // 插件类别
Ports: []int{61613, 61614}, // ActiveMQ STOMP端口标准端口, SSL端口
Protocols: []string{"tcp", "stomp"}, // 支持的协议
Tags: []string{"message-queue", "activemq", "stomp", "bruteforce", "exploit"}, // 功能标签
}
// 创建ActiveMQ专用连接器
connector := NewActiveMQConnector()
// 基于连接器创建基础服务插件
servicePlugin := base.NewServicePlugin(metadata, connector)
// 组装完整的ActiveMQ插件
plugin := &ActiveMQPlugin{
ServicePlugin: servicePlugin,
exploiter: NewActiveMQExploiter(), // 集成利用模块
}
// 声明插件具备的安全测试能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword, // 弱密码检测
base.CapDataExtraction, // 数据提取
base.CapInformationLeak, // 信息泄露
base.CapPrivilegeEsc, // 权限提升
})
return plugin
}
// Scan 执行ActiveMQ服务的完整安全扫描
// 重写基础扫描方法,集成弱密码检测和自动利用功能
func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,则进行基础服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 调用基础服务插件进行弱密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err // 扫描失败,直接返回
}
// 记录成功的弱密码发现使用i18n根据端口显示不同协议
cred := result.Credentials[0]
// 专注于STOMP协议的成功消息
common.LogSuccess(i18n.GetText("activemq_stomp_scan_success", target, cred.Username, cred.Password))
// ActiveMQ插件不提供利用功能仅进行弱密码扫描
return result, nil
}
// autoExploit方法已移除 - ActiveMQ插件不提供利用功能
// getExploitMethodName 获取利用方法的中文名称
func (p *ActiveMQPlugin) getExploitMethodName(method base.ExploitType) string {
switch method {
case base.ExploitDataExtraction:
return i18n.GetText("exploit_method_name_data_extraction")
case base.ExploitPrivilegeEsc:
return i18n.GetText("exploit_method_name_activemq_queue_mgmt")
default:
return "未知利用"
}
}
// Exploit 手动利用接口
func (p *ActiveMQPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *ActiveMQPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *ActiveMQPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// 已移除未使用的 generateCredentials 方法
// performServiceIdentification 执行服务识别(-nobr模式
func (p *ActiveMQPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接到ActiveMQ服务进行基础识别
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer conn.Close()
// 尝试STOMP协议识别
stompInfo, isActiveMQ := p.identifySTOMPService(conn)
if isActiveMQ {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("activemq_service_identified", target, "STOMP", stompInfo))
return &base.ScanResult{
Success: true,
Service: "ActiveMQ",
Banner: stompInfo,
Extra: map[string]interface{}{
"service": "ActiveMQ",
"protocol": "STOMP",
"port": info.Ports,
"info": stompInfo,
},
}, nil
}
// 如果无法识别为ActiveMQ返回一般服务信息
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为ActiveMQ服务"),
}, nil
}
// identifySTOMPService 识别STOMP协议服务
func (p *ActiveMQPlugin) identifySTOMPService(conn net.Conn) (string, bool) {
// 发送STOMP CONNECT帧进行协议识别不提供凭据
connectFrame := "CONNECT\naccept-version:1.0,1.1,1.2\nhost:localhost\n\n\x00"
conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if _, err := conn.Write([]byte(connectFrame)); err != nil {
return "", false
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return "", false
}
responseStr := string(response[:n])
// 检查是否为STOMP协议响应
if strings.Contains(responseStr, "CONNECTED") {
// 提取版本信息
version := "unknown"
if strings.Contains(responseStr, "version:") {
lines := strings.Split(responseStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "version:") {
version = strings.TrimPrefix(line, "version:")
break
}
}
}
return fmt.Sprintf("STOMP协议版本: %s", version), true
} else if strings.Contains(responseStr, "ERROR") {
// 即使返回错误但能识别STOMP协议格式
return "STOMP协议需要认证", true
}
return "", false
}
// GetServiceName 获取服务名称
func (p *ActiveMQPlugin) GetServiceName() string {
return "ActiveMQ"
}
// GetServiceDescription 获取服务描述
func (p *ActiveMQPlugin) GetServiceDescription() string {
return "Apache ActiveMQ消息队列中间件"
}
// GetDefaultPorts 获取默认端口
func (p *ActiveMQPlugin) GetDefaultPorts() []int {
return []int{61613, 61614}
}
// SupportsBruteforce 支持暴力破解
func (p *ActiveMQPlugin) SupportsBruteforce() bool {
return true
}
// SupportsExploit 支持利用
func (p *ActiveMQPlugin) SupportsExploit() bool {
return true
}
// GetProtocols 获取支持的协议
func (p *ActiveMQPlugin) GetProtocols() []string {
return []string{"tcp", "stomp"}
}
// ValidateTarget 验证目标是否适用
func (p *ActiveMQPlugin) ValidateTarget(info *common.HostInfo) error {
// 基本验证
if info.Host == "" {
return fmt.Errorf("主机地址不能为空")
}
if info.Ports == "" {
return fmt.Errorf("端口不能为空")
}
return nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterActiveMQPlugin 注册ActiveMQ插件
func RegisterActiveMQPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "activemq",
Version: "2.0.0",
Author: "fscan-team",
Description: "ActiveMQ消息队列扫描和利用插件",
Category: "service",
Ports: []int{61613, 61614},
Protocols: []string{"tcp", "stomp"},
Tags: []string{"message-queue", "activemq", "stomp", "bruteforce", "exploit"},
},
func() base.Plugin {
return NewActiveMQPlugin()
},
)
// 注册到全局插件注册表
base.GlobalPluginRegistry.Register("activemq", factory)
// 记录注册信息
common.LogDebug("ActiveMQ插件已注册")
}
// 自动注册
func init() {
RegisterActiveMQPlugin()
}

View File

@ -1,169 +0,0 @@
package cassandra
import (
"context"
"fmt"
"net"
"strconv"
"time"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// CassandraConnector Cassandra连接器实现
type CassandraConnector struct {
host string
port string
}
// CassandraProxyDialer 实现gocql.Dialer接口支持代理连接
type CassandraProxyDialer struct {
timeout time.Duration
}
// DialContext 实现代理拨号
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))
}
// NewCassandraConnector 创建Cassandra连接器
func NewCassandraConnector() *CassandraConnector {
return &CassandraConnector{}
}
// Connect 连接到Cassandra服务
func (c *CassandraConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
c.host = info.Host
c.port = info.Ports
// 创建Cassandra集群配置
cluster := gocql.NewCluster(c.host)
// 解析端口
port, err := strconv.Atoi(c.port)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", c.port)
}
cluster.Port = port
// 设置连接参数
timeout := time.Duration(common.Timeout) * time.Second
cluster.Timeout = timeout
cluster.ConnectTimeout = timeout
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
// 如果配置了代理设置自定义Dialer
if common.Socks5Proxy != "" {
cluster.Dialer = &CassandraProxyDialer{
timeout: timeout,
}
}
// 设置重试策略
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
return cluster, nil
}
// Authenticate 认证
func (c *CassandraConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
cluster, ok := conn.(*gocql.ClusterConfig)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 创建集群配置副本
authCluster := *cluster
// 设置认证信息
if cred.Username != "" || cred.Password != "" {
authCluster.Authenticator = gocql.PasswordAuthenticator{
Username: cred.Username,
Password: cred.Password,
}
}
// 创建会话通道以支持Context超时
sessionChan := make(chan struct {
session *gocql.Session
err error
}, 1)
// 在goroutine中创建会话以便可以通过Context取消
go func() {
session, err := authCluster.CreateSession()
select {
case <-ctx.Done():
if session != nil {
session.Close()
}
case sessionChan <- struct {
session *gocql.Session
err error
}{session, err}:
}
}()
// 等待会话创建或Context取消
var session *gocql.Session
var err error
select {
case result := <-sessionChan:
session, err = result.session, result.err
if err != nil {
return fmt.Errorf("Cassandra认证失败: %v", err)
}
case <-ctx.Done():
return fmt.Errorf("Cassandra连接超时: %v", ctx.Err())
}
defer session.Close()
// 尝试执行查询验证连接
resultChan := make(chan struct {
success bool
err error
}, 1)
go func() {
var err error
// 尝试两种查询,确保至少一种成功
err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(nil)
if err != nil {
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(nil)
}
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{err == nil, err}:
}
}()
// 等待查询结果或Context取消
select {
case result := <-resultChan:
if !result.success && result.err != nil {
return fmt.Errorf("Cassandra查询验证失败: %v", result.err)
}
return nil
case <-ctx.Done():
return fmt.Errorf("Cassandra查询超时: %v", ctx.Err())
}
}
// Close 关闭连接
func (c *CassandraConnector) Close(conn interface{}) error {
// Cassandra集群配置无需显式关闭
return nil
}

View File

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

View File

@ -1,238 +0,0 @@
package cassandra
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// CassandraPlugin Cassandra插件实现
type CassandraPlugin struct {
*base.ServicePlugin
exploiter *CassandraExploiter
}
// NewCassandraPlugin 创建Cassandra插件
func NewCassandraPlugin() *CassandraPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "cassandra",
Version: "2.0.0",
Author: "fscan-team",
Description: "Apache Cassandra服务扫描和利用插件",
Category: "service",
Ports: []int{9042}, // Cassandra Native Protocol
Protocols: []string{"tcp"},
Tags: []string{"cassandra", "nosql", "database", "bruteforce"},
}
// 创建连接器和服务插件
connector := NewCassandraConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Cassandra插件
plugin := &CassandraPlugin{
ServicePlugin: servicePlugin,
exploiter: NewCassandraExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
base.CapInformationLeak,
})
return plugin
}
// Scan 重写扫描方法以支持自动利用
func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 执行基础的密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
// 记录成功的弱密码/未授权访问发现
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
cred := result.Credentials[0]
if cred.Username == "" && cred.Password == "" {
// 未授权访问
common.LogSuccess(i18n.GetText("plugin_unauthorized_access", "Cassandra", target))
} else {
// 弱密码
common.LogSuccess(i18n.GetText("plugin_login_success", "Cassandra", target, cred.Username, cred.Password))
}
// Cassandra插件不提供利用功能仅进行弱密码扫描
return result, nil
}
// 已移除未使用的 generateCredentials 方法
// autoExploit方法已移除 - Cassandra插件不提供利用功能
// Exploit 使用exploiter执行利用
func (p *CassandraPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *CassandraPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *CassandraPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Cassandra服务识别-nobr模式
func (p *CassandraPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接到Cassandra服务进行识别
cassandraInfo, isCassandra := p.identifyCassandraService(ctx, info)
if isCassandra {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("cassandra_service_identified", target, cassandraInfo))
return &base.ScanResult{
Success: true,
Service: "Cassandra",
Banner: cassandraInfo,
Extra: map[string]interface{}{
"service": "Cassandra",
"port": info.Ports,
"info": cassandraInfo,
},
}, nil
}
// 如果无法识别为Cassandra返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Cassandra服务"),
}, nil
}
// identifyCassandraService 通过连接识别Cassandra服务
func (p *CassandraPlugin) identifyCassandraService(ctx context.Context, info *common.HostInfo) (string, bool) {
// 尝试建立简单的TCP连接
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return "", false
}
defer conn.Close()
// 对于Cassandra native protocol (9042)尝试发送OPTIONS frame
if info.Ports == "9042" {
return p.identifyNativeProtocol(conn)
}
// 通用端口检测(其他端口)
return p.identifyGenericCassandra(conn)
}
// identifyNativeProtocol 识别Cassandra native protocol
func (p *CassandraPlugin) identifyNativeProtocol(conn net.Conn) (string, bool) {
// 设置读写超时
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// Cassandra native protocol OPTIONS frame
// Frame format: version(1) + flags(1) + stream(2) + opcode(1) + length(4) + body
optionsFrame := []byte{
0x04, // Version 4
0x00, // Flags
0x00, 0x00, // Stream ID
0x05, // Opcode: OPTIONS
0x00, 0x00, 0x00, 0x00, // Body length: 0
}
// 发送OPTIONS请求
_, err := conn.Write(optionsFrame)
if err != nil {
return "", false
}
// 读取响应
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil || n < 8 {
return "", false
}
// 检查响应是否为有效的Cassandra协议响应
if n >= 8 && response[0] == 0x84 { // Response version
// 简单解析响应以获取支持的版本信息
return "Cassandra Native Protocol v4", true
}
return "", false
}
// identifyGenericCassandra 通用Cassandra识别
func (p *CassandraPlugin) identifyGenericCassandra(conn net.Conn) (string, bool) {
// 设置超时
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// 尝试读取任何初始数据
response := make([]byte, 512)
n, err := conn.Read(response)
if err == nil && n > 0 {
responseStr := string(response[:n])
// 检查响应中是否包含Cassandra相关信息
if strings.Contains(strings.ToLower(responseStr), "cassandra") {
return fmt.Sprintf("Cassandra服务: %s", strings.TrimSpace(responseStr)), true
}
}
// 如果端口开放但没有明确标识仍然认为可能是Cassandra
return "Cassandra服务", true
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterCassandraPlugin 注册Cassandra插件
func RegisterCassandraPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "cassandra",
Version: "2.0.0",
Author: "fscan-team",
Description: "Apache Cassandra服务扫描和利用插件",
Category: "service",
Ports: []int{9042}, // Cassandra Native Protocol
Protocols: []string{"tcp"},
Tags: []string{"cassandra", "nosql", "database", "bruteforce"},
},
func() base.Plugin {
return NewCassandraPlugin()
},
)
base.GlobalPluginRegistry.Register("cassandra", factory)
}
// 自动注册
func init() {
RegisterCassandraPlugin()
}

View File

@ -1,116 +0,0 @@
package ftp
import (
"context"
"fmt"
"time"
ftplib "github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// FTPConnector FTP连接器实现
type FTPConnector struct {
host string
port string
}
// NewFTPConnector 创建FTP连接器
func NewFTPConnector() *FTPConnector {
return &FTPConnector{}
}
// Connect 连接到FTP服务
func (c *FTPConnector) 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)
// 创建FTP连接配置
config := &FTPConfig{
Target: target,
Timeout: time.Duration(common.Timeout) * time.Second,
}
return config, nil
}
// Authenticate 认证
func (c *FTPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
config, ok := conn.(*FTPConfig)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 在goroutine中建立FTP连接支持Context取消
resultChan := make(chan struct {
ftpConn *ftplib.ServerConn
err error
}, 1)
go func() {
ftpConn, err := ftplib.DialTimeout(config.Target, config.Timeout)
select {
case <-ctx.Done():
if ftpConn != nil {
ftpConn.Quit()
}
case resultChan <- struct {
ftpConn *ftplib.ServerConn
err error
}{ftpConn, err}:
}
}()
// 等待连接结果或Context取消
var ftpConn *ftplib.ServerConn
var err error
select {
case result := <-resultChan:
ftpConn, err = result.ftpConn, result.err
if err != nil {
return fmt.Errorf("FTP连接失败: %v", err)
}
case <-ctx.Done():
return fmt.Errorf("FTP连接超时: %v", ctx.Err())
}
defer ftpConn.Quit()
// 在goroutine中进行登录认证
loginChan := make(chan error, 1)
go func() {
err := ftpConn.Login(cred.Username, cred.Password)
select {
case <-ctx.Done():
case loginChan <- err:
}
}()
// 等待登录结果或Context取消
select {
case err := <-loginChan:
if err != nil {
return fmt.Errorf("FTP认证失败: %v", err)
}
return nil
case <-ctx.Done():
return fmt.Errorf("FTP认证超时: %v", ctx.Err())
}
}
// Close 关闭连接
func (c *FTPConnector) Close(conn interface{}) error {
// FTP配置无需显式关闭
return nil
}
// FTPConfig FTP连接配置
type FTPConfig struct {
Target string
Timeout time.Duration
}

View File

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

View File

@ -1,233 +0,0 @@
package ftp
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// FTPPlugin FTP插件实现
type FTPPlugin struct {
*base.ServicePlugin
exploiter *FTPExploiter
}
// NewFTPPlugin 创建FTP插件
func NewFTPPlugin() *FTPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "ftp",
Version: "2.0.0",
Author: "fscan-team",
Description: "FTP文件传输协议扫描和利用插件",
Category: "service",
Ports: []int{21, 2121}, // 21: 标准FTP端口, 2121: 常见替代端口
Protocols: []string{"tcp"},
Tags: []string{"ftp", "file_transfer", "bruteforce", "anonymous"},
}
// 创建连接器和服务插件
connector := NewFTPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建FTP插件
plugin := &FTPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewFTPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
base.CapDataExtraction,
base.CapFileUpload,
base.CapFileWrite,
})
return plugin
}
// Scan 重写扫描方法以支持匿名登录检测和自动利用
func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 首先尝试匿名登录
anonymousCred := &base.Credential{
Username: "anonymous",
Password: "",
}
result, err := p.ScanCredential(ctx, info, anonymousCred)
if err == nil && result.Success {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(i18n.GetText("ftp_anonymous_success", target))
// FTP插件不提供利用功能仅记录匿名访问
return result, nil
}
// 执行基础的密码扫描
result, err = p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
// 记录成功的弱密码发现
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
cred := result.Credentials[0]
common.LogSuccess(i18n.GetText("ftp_weak_pwd_success", target, cred.Username, cred.Password))
// FTP插件不提供利用功能仅进行弱密码扫描
return result, nil
}
// 已移除未使用的 generateCredentials 方法
// autoExploit方法已移除 - FTP插件不提供利用功能
// Exploit 使用exploiter执行利用
func (p *FTPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *FTPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *FTPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行FTP服务识别-nobr模式
func (p *FTPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接到FTP服务获取Banner
ftpInfo, isFTP := p.identifyFTPService(ctx, info)
if isFTP {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("ftp_service_identified", target, ftpInfo))
return &base.ScanResult{
Success: true,
Service: "FTP",
Banner: ftpInfo,
Extra: map[string]interface{}{
"service": "FTP",
"port": info.Ports,
"info": ftpInfo,
},
}, nil
}
// 如果无法识别为FTP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为FTP服务"),
}, nil
}
// identifyFTPService 通过Banner识别FTP服务
func (p *FTPPlugin) identifyFTPService(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()
// 设置读取超时
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// FTP服务器在连接后会发送Welcome Banner
banner := make([]byte, 1024)
n, err := conn.Read(banner)
if err != nil || n < 3 {
return "", false
}
bannerStr := strings.TrimSpace(string(banner[:n]))
// 检查FTP协议标识
if strings.HasPrefix(bannerStr, "220") {
// FTP服务器通常以220开头发送welcome消息
// 提取FTP服务器信息
lines := strings.Split(bannerStr, "\n")
if len(lines) > 0 {
firstLine := strings.TrimSpace(lines[0])
// 移除状态码
if len(firstLine) > 4 && firstLine[:3] == "220" {
serverInfo := strings.TrimSpace(firstLine[3:])
// 移除可能的连字符
if len(serverInfo) > 0 && serverInfo[0] == '-' {
serverInfo = strings.TrimSpace(serverInfo[1:])
}
if serverInfo != "" {
return fmt.Sprintf("FTP服务: %s", serverInfo), true
}
}
}
return "FTP服务", true
}
// 检查其他可能的FTP响应
lowerBanner := strings.ToLower(bannerStr)
if strings.Contains(lowerBanner, "ftp") ||
strings.Contains(lowerBanner, "file transfer") ||
strings.Contains(lowerBanner, "vsftpd") ||
strings.Contains(lowerBanner, "proftpd") ||
strings.Contains(lowerBanner, "pure-ftpd") {
return fmt.Sprintf("FTP服务: %s", bannerStr), true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterFTPPlugin 注册FTP插件
func RegisterFTPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "ftp",
Version: "2.0.0",
Author: "fscan-team",
Description: "FTP文件传输协议扫描和利用插件",
Category: "service",
Ports: []int{21, 2121}, // 21: 标准FTP端口, 2121: 常见替代端口
Protocols: []string{"tcp"},
Tags: []string{"ftp", "file_transfer", "bruteforce", "anonymous"},
},
func() base.Plugin {
return NewFTPPlugin()
},
)
base.GlobalPluginRegistry.Register("ftp", factory)
}
// 自动注册
func init() {
RegisterFTPPlugin()
}

View File

@ -1,133 +0,0 @@
package imap
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// IMAPConnection IMAP连接包装器
type IMAPConnection struct {
conn net.Conn
reader *bufio.Reader
target string
}
// IMAPConnector IMAP连接器实现
type IMAPConnector struct {
host string
port string
}
// NewIMAPConnector 创建IMAP连接器
func NewIMAPConnector() *IMAPConnector {
return &IMAPConnector{}
}
// Connect 连接到IMAP服务
func (c *IMAPConnector) 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)
// 根据端口选择连接类型
var conn net.Conn
var err error
if c.port == "993" {
// IMAPS端口使用TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
conn, err = common.WrapperTlsWithContext(ctx, "tcp", target, tlsConfig)
} else {
// IMAP端口使用普通连接
conn, err = common.WrapperTcpWithContext(ctx, "tcp", target)
}
if err != nil {
return nil, fmt.Errorf("IMAP连接失败: %v", err)
}
reader := bufio.NewReader(conn)
// 设置IMAP特殊超时默认超时时间 + 5秒
imapTimeout := time.Duration(common.Timeout+5) * time.Second
conn.SetReadDeadline(time.Now().Add(imapTimeout))
// 读取IMAP欢迎消息
if _, readErr := reader.ReadString('\n'); readErr != nil {
conn.Close()
return nil, fmt.Errorf("IMAP欢迎消息读取失败: %v", readErr)
}
return &IMAPConnection{
conn: conn,
reader: reader,
target: target,
}, nil
}
// Authenticate 认证
func (c *IMAPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
imapConn, ok := conn.(*IMAPConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 设置IMAP特殊超时默认超时时间 + 5秒
imapTimeout := time.Duration(common.Timeout+5) * time.Second
imapConn.conn.SetDeadline(time.Now().Add(imapTimeout))
// 发送LOGIN命令
loginCmd := fmt.Sprintf("a001 LOGIN \"%s\" \"%s\"\r\n", cred.Username, cred.Password)
_, err := imapConn.conn.Write([]byte(loginCmd))
if err != nil {
return fmt.Errorf("发送登录命令失败: %v", err)
}
// 读取认证响应
for {
select {
case <-ctx.Done():
return fmt.Errorf("IMAP认证超时: %v", ctx.Err())
default:
// 设置读取超时避免无限等待使用IMAP特殊超时
imapTimeout := time.Duration(common.Timeout+5) * time.Second
imapConn.conn.SetReadDeadline(time.Now().Add(imapTimeout))
response, err := imapConn.reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return fmt.Errorf("IMAP认证失败")
}
return fmt.Errorf("读取响应失败: %v", err)
}
if strings.Contains(response, "a001 OK") {
return nil // 认证成功
}
if strings.Contains(response, "a001 NO") || strings.Contains(response, "a001 BAD") {
return fmt.Errorf("IMAP认证失败")
}
}
}
}
// Close 关闭连接
func (c *IMAPConnector) Close(conn interface{}) error {
if imapConn, ok := conn.(*IMAPConnection); ok && imapConn.conn != nil {
return imapConn.conn.Close()
}
return nil
}

View File

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

View File

@ -1,214 +0,0 @@
package imap
import (
"context"
"crypto/tls"
"fmt"
"net"
"regexp"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// IMAPPlugin IMAP插件实现
type IMAPPlugin struct {
*base.ServicePlugin
exploiter *IMAPExploiter
}
// NewIMAPPlugin 创建IMAP插件
func NewIMAPPlugin() *IMAPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "imap",
Version: "2.0.0",
Author: "fscan-team",
Description: "IMAP邮件服务扫描和利用插件",
Category: "service",
Ports: []int{143, 993}, // IMAP和IMAPS端口
Protocols: []string{"tcp"},
Tags: []string{"imap", "mail", "bruteforce"},
}
// 创建连接器和服务插件
connector := NewIMAPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建IMAP插件
plugin := &IMAPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewIMAPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法以支持服务识别
func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用了暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 执行基础的密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
// 记录成功的弱密码发现
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
cred := result.Credentials[0]
common.LogSuccess(i18n.GetText("imap_weak_pwd_success", target, cred.Username, cred.Password))
return result, nil
}
// 已移除未使用的 generateCredentials 方法
// Exploit 使用exploiter执行利用
func (p *IMAPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *IMAPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *IMAPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行IMAP服务识别-nobr模式
func (p *IMAPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 根据端口选择连接类型
var conn net.Conn
var err error
if info.Ports == "993" {
// IMAPS端口使用TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
conn, err = common.WrapperTlsWithContext(ctx, "tcp", target, tlsConfig)
} else {
// IMAP端口使用普通连接
conn, err = common.WrapperTcpWithContext(ctx, "tcp", target)
}
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer conn.Close()
// 读取IMAP Banner
imapInfo, isIMAP := p.identifyIMAPService(conn)
if isIMAP {
// 记录服务识别成功
service := "IMAP"
if info.Ports == "993" {
service = "IMAPS"
}
common.LogSuccess(i18n.GetText("imap_service_identified", target, imapInfo))
return &base.ScanResult{
Success: true,
Service: service,
Banner: imapInfo,
Extra: map[string]interface{}{
"service": service,
"port": info.Ports,
"info": imapInfo,
},
}, nil
}
// 如果无法识别为IMAP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为IMAP服务"),
}, nil
}
// identifyIMAPService 通过Banner识别IMAP服务
func (p *IMAPPlugin) identifyIMAPService(conn net.Conn) (string, bool) {
// 设置读取超时
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// IMAP服务器在连接后会发送欢迎消息
banner := make([]byte, 512)
n, err := conn.Read(banner)
if err != nil || n < 4 {
return "", false
}
bannerStr := strings.TrimSpace(string(banner[:n]))
// 检查IMAP协议标识
if strings.Contains(bannerStr, "* OK") && (strings.Contains(strings.ToLower(bannerStr), "imap") ||
strings.Contains(strings.ToLower(bannerStr), "dovecot") ||
strings.Contains(strings.ToLower(bannerStr), "courier") ||
strings.Contains(strings.ToLower(bannerStr), "cyrus")) {
// 提取服务器信息
if matched := regexp.MustCompile(`\* OK (.+?) ready`).FindStringSubmatch(bannerStr); len(matched) >= 2 {
return fmt.Sprintf("IMAP服务: %s", matched[1]), true
}
if matched := regexp.MustCompile(`\* OK (.+?)$`).FindStringSubmatch(bannerStr); len(matched) >= 2 {
return fmt.Sprintf("IMAP服务: %s", matched[1]), true
}
return fmt.Sprintf("IMAP服务: %s", bannerStr), true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterIMAPPlugin 注册IMAP插件
func RegisterIMAPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "imap",
Version: "2.0.0",
Author: "fscan-team",
Description: "IMAP邮件服务扫描和利用插件",
Category: "service",
Ports: []int{143, 993}, // IMAP和IMAPS端口
Protocols: []string{"tcp"},
Tags: []string{"imap", "mail", "bruteforce"},
},
func() base.Plugin {
return NewIMAPPlugin()
},
)
base.GlobalPluginRegistry.Register("imap", factory)
}
// 自动注册
func init() {
RegisterIMAPPlugin()
}

View File

@ -1,114 +0,0 @@
package kafka
import (
"context"
"fmt"
"strings"
"time"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// KafkaConnection Kafka连接包装器
type KafkaConnection struct {
client sarama.Client
target string
}
// KafkaConnector Kafka连接器实现
type KafkaConnector struct {
host string
port string
}
// NewKafkaConnector 创建Kafka连接器
func NewKafkaConnector() *KafkaConnector {
return &KafkaConnector{}
}
// Connect 连接到Kafka服务
func (c *KafkaConnector) 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)
// 返回连接信息实际连接在Authenticate时建立
return &KafkaConnection{
client: nil, // 延迟连接
target: target,
}, nil
}
// Authenticate 认证
func (c *KafkaConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
kafkaConn, ok := conn.(*KafkaConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 关闭之前的连接(如果有)
if kafkaConn.client != nil {
kafkaConn.client.Close()
}
// 创建新的认证配置
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 cred.Username != "" || cred.Password != "" {
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.SASL.User = cred.Username
config.Net.SASL.Password = cred.Password
config.Net.SASL.Handshake = true
}
brokers := []string{kafkaConn.target}
// 尝试作为消费者连接测试
consumer, err := sarama.NewConsumer(brokers, config)
if err == nil {
consumer.Close()
// 创建认证后的客户端
client, clientErr := sarama.NewClient(brokers, config)
if clientErr != nil {
return fmt.Errorf("创建认证客户端失败: %v", clientErr)
}
kafkaConn.client = client
return nil
}
// 如果消费者连接失败,尝试作为客户端连接
client, clientErr := sarama.NewClient(brokers, config)
if clientErr == nil {
kafkaConn.client = client
return nil
}
// 检查认证相关错误
if strings.Contains(err.Error(), "SASL") ||
strings.Contains(err.Error(), "authentication") ||
strings.Contains(err.Error(), "credentials") {
return fmt.Errorf("Kafka认证失败")
}
return fmt.Errorf("Kafka连接失败: %v", err)
}
// Close 关闭连接
func (c *KafkaConnector) Close(conn interface{}) error {
if kafkaConn, ok := conn.(*KafkaConnection); ok && kafkaConn.client != nil {
return kafkaConn.client.Close()
}
return nil
}

View File

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

View File

@ -1,206 +0,0 @@
package kafka
import (
"context"
"fmt"
"strings"
"time"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// KafkaPlugin Kafka插件实现
type KafkaPlugin struct {
*base.ServicePlugin
exploiter *KafkaExploiter
}
// NewKafkaPlugin 创建Kafka插件
func NewKafkaPlugin() *KafkaPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "kafka",
Version: "2.0.0",
Author: "fscan-team",
Description: "Apache Kafka消息队列扫描和利用插件",
Category: "service",
Ports: []int{9092, 9093, 9094}, // Kafka常用端口
Protocols: []string{"tcp"},
Tags: []string{"kafka", "message-queue", "bruteforce", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewKafkaConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Kafka插件
plugin := &KafkaPlugin{
ServicePlugin: servicePlugin,
exploiter: NewKafkaExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapDataExtraction,
})
return plugin
}
// Scan 重写扫描方法,先检测无认证访问
func (p *KafkaPlugin) 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: ""}
unauthResult, err := p.ScanCredential(ctx, info, unauthCred)
if err == nil && unauthResult.Success {
// 无认证访问成功
common.LogSuccess(i18n.GetText("kafka_unauth_access", target))
return &base.ScanResult{
Success: true,
Service: "Kafka",
Credentials: []*base.Credential{unauthCred},
Extra: map[string]interface{}{
"service": "Kafka",
"port": info.Ports,
"unauthorized": true,
"access_type": "no_authentication",
},
}, nil
}
// 执行基础的密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
// 记录成功的弱密码发现
cred := result.Credentials[0]
common.LogSuccess(i18n.GetText("kafka_weak_pwd_success", target, cred.Username, cred.Password))
return result, nil
}
// 已移除未使用的 generateCredentials 方法
// Exploit 使用exploiter执行利用
func (p *KafkaPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *KafkaPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *KafkaPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行Kafka服务识别-nobr模式
func (p *KafkaPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接Kafka获取版本信息
kafkaInfo, isKafka := p.identifyKafkaService(ctx, info)
if isKafka {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("kafka_service_identified", target, kafkaInfo))
return &base.ScanResult{
Success: true,
Service: "Kafka",
Banner: kafkaInfo,
Extra: map[string]interface{}{
"service": "Kafka",
"port": info.Ports,
"info": kafkaInfo,
},
}, nil
}
// 如果无法识别为Kafka返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Kafka服务"),
}, nil
}
// identifyKafkaService 通过连接识别Kafka服务
func (p *KafkaPlugin) identifyKafkaService(ctx context.Context, info *common.HostInfo) (string, bool) {
target := fmt.Sprintf("%s:%s", 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
brokers := []string{target}
// 尝试创建客户端连接
client, err := sarama.NewClient(brokers, config)
if err != nil {
// 检查错误是否表明这是Kafka服务但认证失败
if strings.Contains(strings.ToLower(err.Error()), "kafka") ||
strings.Contains(strings.ToLower(err.Error()), "sasl") ||
strings.Contains(strings.ToLower(err.Error()), "authentication") {
return fmt.Sprintf("Kafka服务 (需要认证): %v", err), true
}
return "", false
}
defer client.Close()
// 获取集群信息
brokerList := client.Brokers()
if len(brokerList) > 0 {
return fmt.Sprintf("Kafka集群 (Brokers: %d)", len(brokerList)), true
}
return "Kafka服务", true
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterKafkaPlugin 注册Kafka插件
func RegisterKafkaPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "kafka",
Version: "2.0.0",
Author: "fscan-team",
Description: "Apache Kafka消息队列扫描和利用插件",
Category: "service",
Ports: []int{9092, 9093, 9094}, // Kafka常用端口
Protocols: []string{"tcp"},
Tags: []string{"kafka", "message-queue", "bruteforce", "unauthorized"},
},
func() base.Plugin {
return NewKafkaPlugin()
},
)
base.GlobalPluginRegistry.Register("kafka", factory)
}
// 自动注册
func init() {
RegisterKafkaPlugin()
}

View File

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

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

@ -1,212 +0,0 @@
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 方法
// 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

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

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

@ -1,223 +0,0 @@
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 方法
// 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

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

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

@ -1,291 +0,0 @@
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 方法
// 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

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

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

@ -1,221 +0,0 @@
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 方法
// 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

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

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

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

@ -1,168 +0,0 @@
package mysql
import (
"context"
"database/sql"
"fmt"
"net"
"strconv"
"time"
"github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// MySQLConnector 实现MySQL数据库服务连接器
// 遵循 base.ServiceConnector 接口规范提供标准化的MySQL连接和认证功能
// MySQLConnector MySQL数据库连接器
type MySQLConnector struct {
host string // 目标主机地址
port int // 目标端口号
}
// NewMySQLConnector 创建新的MySQL连接器实例
// 自动注册SOCKS代理支持统一使用Context超时控制
func NewMySQLConnector() *MySQLConnector {
connector := &MySQLConnector{}
// 注册SOCKS代理支持的dialer如果配置了代理
connector.registerProxyDialer()
return connector
}
// Connect 建立到MySQL服务的基础连接
// 实现 base.ServiceConnector 接口的 Connect 方法
func (c *MySQLConnector) 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
// 构建基础连接字符串(无认证信息)
connStr := c.buildConnectionString(info.Host, port, "", "")
// 创建数据库连接实例
db, err := sql.Open("mysql", connStr)
if err != nil {
return nil, fmt.Errorf("创建连接失败: %v", err)
}
// 配置连接池参数
timeout := time.Duration(common.Timeout) * time.Second
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
return db, nil
}
// Authenticate 使用凭据对MySQL服务进行身份认证
// 实现 base.ServiceConnector 接口的 Authenticate 方法
// 关键优化使用独立的Context避免上游超时问题并优化内存使用
func (c *MySQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
// 直接使用传入的Context它已经包含了正确的超时设置
// 内存优化:预构建连接字符串,避免重复分配
connStr := c.buildConnectionString(c.host, c.port, cred.Username, cred.Password)
common.LogDebug(fmt.Sprintf("MySQL尝试认证: %s@%s:%d", cred.Username, c.host, c.port))
// 内存优化:直接建立连接而不创建连接池
// 避免为单次认证创建不必要的连接池开销
rawConn, err := c.connectDirect(ctx, connStr)
if err != nil {
common.LogDebug(fmt.Sprintf("MySQL直连失败: %s@%s:%d - %v", cred.Username, c.host, c.port, err))
return fmt.Errorf("连接失败: %v", err)
}
defer rawConn.Close()
// 执行简单的认证验证
err = c.validateConnection(ctx, rawConn)
if err != nil {
common.LogDebug(fmt.Sprintf("MySQL认证失败: %s@%s:%d - %v", cred.Username, c.host, c.port, err))
return fmt.Errorf("认证失败: %v", err)
}
common.LogDebug(fmt.Sprintf("MySQL认证成功: %s@%s:%d", cred.Username, c.host, c.port))
return nil
}
// Close 关闭MySQL连接
// 实现 base.ServiceConnector 接口的 Close 方法
func (c *MySQLConnector) Close(conn interface{}) error {
if db, ok := conn.(*sql.DB); ok {
return db.Close()
}
return nil
}
// 已移除未使用的 connectWithCredentials 方法
// buildConnectionString 构建MySQL连接字符串
// 根据是否配置SOCKS代理选择合适的连接方式
// 移除timeout参数统一使用Context控制超时
func (c *MySQLConnector) buildConnectionString(host string, port int, username, password string) string {
// 根据代理配置选择网络类型
if common.Socks5Proxy != "" {
// SOCKS代理连接模式移除timeout参数由Context控制
return fmt.Sprintf("%v:%v@tcp-proxy(%v:%v)/mysql?charset=utf8",
username, password, host, port)
} else {
// 标准TCP直连模式移除timeout参数由Context控制
return fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8",
username, password, host, port)
}
}
// 已移除未使用的 buildConnectionStringWithCredentials 方法
// connectDirect 内存优化直接建立MySQL连接避免连接池开销
// 用于单次认证场景,减少内存分配和资源浪费
func (c *MySQLConnector) connectDirect(ctx context.Context, connStr string) (*sql.Conn, error) {
// 创建最小化配置的临时数据库实例
db, err := sql.Open("mysql", connStr)
if err != nil {
return nil, fmt.Errorf("创建连接实例失败: %v", err)
}
defer db.Close() // 确保临时db实例被清理
// 禁用连接池以减少内存开销
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
db.SetConnMaxLifetime(0)
// 获取原始连接
conn, err := db.Conn(ctx)
if err != nil {
return nil, fmt.Errorf("获取连接失败: %v", err)
}
return conn, nil
}
// validateConnection 内存优化:轻量级连接验证
// 使用最小开销的方式验证MySQL连接有效性
func (c *MySQLConnector) validateConnection(ctx context.Context, conn *sql.Conn) error {
// 使用传入的Context进行验证统一超时控制
return conn.PingContext(ctx)
}
// registerProxyDialer 注册SOCKS代理支持的网络拨号器
// 仅在配置了SOCKS代理时才注册避免不必要的开销
func (c *MySQLConnector) registerProxyDialer() {
if common.Socks5Proxy == "" {
return // 未配置代理,跳过注册
}
// 向MySQL驱动注册自定义的代理拨号器
mysql.RegisterDialContext("tcp-proxy", func(ctx context.Context, addr string) (net.Conn, error) {
return common.WrapperTcpWithContext(ctx, "tcp", addr)
})
}

View File

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

View File

@ -1,215 +0,0 @@
package mysql
import (
"context"
"fmt"
"net"
"regexp"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// MySQL插件新一代插件架构的完整实现示例
// 展示了如何正确实现服务扫描、凭据爆破、自动利用等功能
// 本插件可作为其他数据库插件迁移的标准参考模板
// MySQLPlugin MySQL数据库扫描和利用插件
// 集成了弱密码检测、自动利用、信息收集等完整功能
type MySQLPlugin struct {
*base.ServicePlugin // 继承基础服务插件功能
exploiter *MySQLExploiter // MySQL专用利用模块
}
// NewMySQLPlugin 创建新的MySQL插件实例
// 这是标准的插件工厂函数,展示了新架构的完整初始化流程
func NewMySQLPlugin() *MySQLPlugin {
// 定义插件元数据 - 这些信息用于插件注册和管理
metadata := &base.PluginMetadata{
Name: "mysql", // 插件唯一标识符
Version: "2.0.0", // 插件版本(新架构版本)
Author: "fscan-team", // 开发团队
Description: "MySQL数据库扫描和利用插件", // 功能描述
Category: "service", // 插件类别
Ports: []int{3306, 3307, 33060, 33061, 33062}, // MySQL常用端口包括默认端口和备用端口
Protocols: []string{"tcp"}, // 支持的协议
Tags: []string{"database", "mysql", "bruteforce", "exploit"}, // 功能标签
}
// 创建MySQL专用连接器
connector := NewMySQLConnector()
// 基于连接器创建基础服务插件
servicePlugin := base.NewServicePlugin(metadata, connector)
// 组装完整的MySQL插件
plugin := &MySQLPlugin{
ServicePlugin: servicePlugin,
exploiter: NewMySQLExploiter(), // 集成利用模块
}
// 声明插件具备的安全测试能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword, // 弱密码检测
base.CapDataExtraction, // 数据提取
base.CapFileWrite, // 文件写入
base.CapSQLInjection, // SQL注入
base.CapInformationLeak, // 信息泄露
})
return plugin
}
// Scan 执行MySQL服务的完整安全扫描
// 重写基础扫描方法,集成弱密码检测和自动利用功能
func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,则进行基础服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 调用基础服务插件进行弱密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err // 扫描失败,直接返回
}
// 记录成功的弱密码发现使用i18n
cred := result.Credentials[0]
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))
// MySQL插件不提供利用功能仅进行弱密码扫描
return result, nil
}
// autoExploit方法已移除 - MySQL插件不提供利用功能
// Exploit 手动利用接口
func (p *MySQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *MySQLPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *MySQLPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// 已移除未使用的 generateCredentials 方法
// performServiceIdentification 执行MySQL服务识别-nobr模式
func (p *MySQLPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接到MySQL服务获取握手包
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer conn.Close()
// 读取MySQL握手包
mysqlInfo, isMySQL := p.identifyMySQLService(conn)
if isMySQL {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("mysql_service_identified", target, mysqlInfo))
return &base.ScanResult{
Success: true,
Service: "MySQL",
Banner: mysqlInfo,
Extra: map[string]interface{}{
"service": "MySQL",
"port": info.Ports,
"info": mysqlInfo,
},
}, nil
}
// 如果无法识别为MySQL返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为MySQL服务"),
}, nil
}
// identifyMySQLService 通过握手包识别MySQL服务
func (p *MySQLPlugin) identifyMySQLService(conn net.Conn) (string, bool) {
// 设置读取超时
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// MySQL服务器在连接后会主动发送握手包
handshake := make([]byte, 1024)
n, err := conn.Read(handshake)
if err != nil || n < 10 {
return "", false
}
// 检查MySQL握手包格式
// MySQL握手包开始: 包长度(3字节) + 序号(1字节) + 协议版本(1字节)
if handshake[4] != 10 { // MySQL 协议版本通常是10
return "", false
}
// 提取版本字符串从第5字节开始到第一个0结束
versionStart := 5
versionEnd := versionStart
for versionEnd < n && handshake[versionEnd] != 0 {
versionEnd++
}
if versionEnd <= versionStart {
return "", false
}
versionStr := string(handshake[versionStart:versionEnd])
// 验证版本字符串是否包含MySQL标识
if len(versionStr) > 0 && (regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr)) {
return fmt.Sprintf("MySQL版本: %s", versionStr), true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterMySQLPlugin 注册MySQL插件
func RegisterMySQLPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "mysql",
Version: "2.0.0",
Author: "fscan-team",
Description: "MySQL数据库扫描和利用插件",
Category: "service",
Ports: []int{3306, 3307, 33060, 33061, 33062},
Protocols: []string{"tcp"},
Tags: []string{"database", "mysql", "bruteforce", "exploit"},
},
func() base.Plugin {
return NewMySQLPlugin()
},
)
base.GlobalPluginRegistry.Register("mysql", factory)
}
// 自动注册
func init() {
RegisterMySQLPlugin()
}

View File

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

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

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

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

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

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

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