From 3e71e7e4c9b824c38e0f2edac2bf6cd725098363 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 9 Aug 2025 13:30:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20RabbitMQ=E6=B6=88=E6=81=AF=E9=98=9F?= =?UTF-8?q?=E5=88=97=E6=9C=8D=E5=8A=A1=E6=8F=92=E4=BB=B6=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=E6=96=B0=E6=9E=B6=E6=9E=84=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现AMQP协议连接器,支持RabbitMQ消息队列服务扫描 - 支持弱密码检测和服务识别功能 - 优先检测默认凭据admin/123456和guest/guest - 服务识别使用TCP连接检测,避免AMQP协议复杂度 - 简化实现,不提供利用功能,专注于安全扫描 - 集成国际化消息系统 - 测试通过:服务识别和弱密码检测功能 --- Plugins/services/rabbitmq/connector.go | 195 +++++++++++++++++++++ Plugins/services/rabbitmq/exploiter.go | 36 ++++ Plugins/services/rabbitmq/plugin.go | 230 +++++++++++++++++++++++++ 3 files changed, 461 insertions(+) create mode 100644 Plugins/services/rabbitmq/connector.go create mode 100644 Plugins/services/rabbitmq/exploiter.go create mode 100644 Plugins/services/rabbitmq/plugin.go diff --git a/Plugins/services/rabbitmq/connector.go b/Plugins/services/rabbitmq/connector.go new file mode 100644 index 0000000..dc55c1b --- /dev/null +++ b/Plugins/services/rabbitmq/connector.go @@ -0,0 +1,195 @@ +package rabbitmq + +import ( + "context" + "fmt" + "net" + "strconv" + "time" + + amqp "github.com/rabbitmq/amqp091-go" + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// RabbitMQConnector RabbitMQ连接器实现 +type RabbitMQConnector struct { + host string + port int +} + +// RabbitMQConnection RabbitMQ连接结构 +type RabbitMQConnection struct { + conn *amqp.Connection + amqpURL string + username string + password string + info string +} + +// NewRabbitMQConnector 创建RabbitMQ连接器 +func NewRabbitMQConnector() *RabbitMQConnector { + return &RabbitMQConnector{} +} + +// Connect 建立RabbitMQ连接 +func (c *RabbitMQConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + // 解析端口 + port, err := strconv.Atoi(info.Ports) + if err != nil { + return nil, fmt.Errorf("无效的端口号: %s", info.Ports) + } + + c.host = info.Host + c.port = port + + // 对于服务识别,只需要检查端口连通性,不需要AMQP认证 + timeout := time.Duration(common.Timeout) * time.Second + address := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 结果通道 + type connResult struct { + conn *RabbitMQConnection + err error + banner string + } + resultChan := make(chan connResult, 1) + + // 在协程中尝试连接 + go func() { + // 首先检查TCP连通性 + tcpConn, err := net.DialTimeout("tcp", address, timeout) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- connResult{nil, err, ""}: + } + return + } + tcpConn.Close() + + // TCP连接成功,创建一个基础的连接对象用于服务识别 + rabbitConn := &RabbitMQConnection{ + conn: nil, // 服务识别阶段不需要真正的AMQP连接 + amqpURL: fmt.Sprintf("amqp://guest:guest@%s:%s/", info.Host, info.Ports), + info: "RabbitMQ Service (Detected)", + } + + select { + case <-ctx.Done(): + case resultChan <- connResult{rabbitConn, nil, "RabbitMQ Service (Detected)"}: + } + }() + + // 等待连接结果 + select { + case result := <-resultChan: + if result.err != nil { + return nil, result.err + } + return result.conn, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// Authenticate 进行RabbitMQ认证 +func (c *RabbitMQConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + timeout := time.Duration(common.Timeout) * time.Second + amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%d/", cred.Username, cred.Password, c.host, c.port) + + // 结果通道 + type authResult struct { + conn *amqp.Connection + err error + } + resultChan := make(chan authResult, 1) + + // 在协程中尝试认证连接 + go func() { + // 配置连接 + config := amqp.Config{ + Dial: func(network, addr string) (net.Conn, error) { + dialer := &net.Dialer{Timeout: timeout} + return dialer.DialContext(ctx, network, addr) + }, + } + + // 尝试连接 + authConn, err := amqp.DialConfig(amqpURL, config) + select { + case <-ctx.Done(): + if authConn != nil { + authConn.Close() + } + case resultChan <- authResult{authConn, err}: + } + }() + + // 等待认证结果 + select { + case result := <-resultChan: + if result.err != nil { + return fmt.Errorf(i18n.GetText("rabbitmq_auth_failed"), result.err) + } + + // 更新连接信息 + if rabbitConn, ok := conn.(*RabbitMQConnection); ok { + // 关闭旧连接 + if rabbitConn.conn != nil { + rabbitConn.conn.Close() + } + // 更新为认证后的连接 + rabbitConn.conn = result.conn + rabbitConn.username = cred.Username + rabbitConn.password = cred.Password + rabbitConn.amqpURL = amqpURL + } + + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Close 关闭RabbitMQ连接 +func (c *RabbitMQConnector) Close(conn interface{}) error { + if rabbitConn, ok := conn.(*RabbitMQConnection); ok { + if rabbitConn.conn != nil { + rabbitConn.conn.Close() + } + return nil + } + return fmt.Errorf("无效的RabbitMQ连接类型") +} + +// GetConnectionInfo 获取连接信息 +func (conn *RabbitMQConnection) GetConnectionInfo() map[string]interface{} { + info := map[string]interface{}{ + "protocol": "AMQP", + "service": "RabbitMQ", + "info": conn.info, + } + + if conn.username != "" { + info["username"] = conn.username + info["authenticated"] = true + } + + return info +} + +// IsAlive 检查连接是否仍然有效 +func (conn *RabbitMQConnection) IsAlive() bool { + if conn.conn == nil { + return false + } + + return !conn.conn.IsClosed() +} + +// GetServerInfo 获取服务器信息 +func (conn *RabbitMQConnection) GetServerInfo() string { + return conn.info +} \ No newline at end of file diff --git a/Plugins/services/rabbitmq/exploiter.go b/Plugins/services/rabbitmq/exploiter.go new file mode 100644 index 0000000..2894608 --- /dev/null +++ b/Plugins/services/rabbitmq/exploiter.go @@ -0,0 +1,36 @@ +package rabbitmq + +import ( + "context" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// RabbitMQExploiter RabbitMQ利用器实现 - 最小化版本,不提供利用功能 +type RabbitMQExploiter struct { + *base.BaseExploiter +} + +// NewRabbitMQExploiter 创建RabbitMQ利用器 +func NewRabbitMQExploiter() *RabbitMQExploiter { + exploiter := &RabbitMQExploiter{ + BaseExploiter: base.NewBaseExploiter("rabbitmq"), + } + + // RabbitMQ插件不提供利用功能 + exploiter.setupExploitMethods() + + return exploiter +} + +// setupExploitMethods 设置利用方法 +func (e *RabbitMQExploiter) setupExploitMethods() { + // RabbitMQ插件不提供利用功能,仅进行弱密码扫描和服务识别 +} + +// Exploit 利用接口实现 - 空实现 +func (e *RabbitMQExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // RabbitMQ插件不提供利用功能 + return nil, nil +} \ No newline at end of file diff --git a/Plugins/services/rabbitmq/plugin.go b/Plugins/services/rabbitmq/plugin.go new file mode 100644 index 0000000..7bf88d9 --- /dev/null +++ b/Plugins/services/rabbitmq/plugin.go @@ -0,0 +1,230 @@ +package rabbitmq + +import ( + "context" + "fmt" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// RabbitMQPlugin RabbitMQ插件实现 +type RabbitMQPlugin struct { + *base.ServicePlugin + exploiter *RabbitMQExploiter +} + +// NewRabbitMQPlugin 创建RabbitMQ插件 +func NewRabbitMQPlugin() *RabbitMQPlugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "rabbitmq", + Version: "2.0.0", + Author: "fscan-team", + Description: "RabbitMQ消息队列服务扫描插件", + Category: "service", + Ports: []int{5672, 5671, 15672}, // AMQP端口、AMQPS端口、管理端口 + Protocols: []string{"tcp"}, + Tags: []string{"rabbitmq", "amqp", "message-queue", "weak-password"}, + } + + // 创建连接器和服务插件 + connector := NewRabbitMQConnector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建RabbitMQ插件 + plugin := &RabbitMQPlugin{ + ServicePlugin: servicePlugin, + exploiter: NewRabbitMQExploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapWeakPassword, + }) + + return plugin +} + +// Scan 重写扫描方法,进行RabbitMQ服务扫描 +func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 优先尝试默认凭据 + defaultCredentials := []*base.Credential{ + {Username: "admin", Password: "123456"}, + {Username: "guest", Password: "guest"}, + {Username: "admin", Password: "admin"}, + } + + // 先测试默认凭据 + for _, cred := range defaultCredentials { + result, err := p.ScanCredential(ctx, info, cred) + if err == nil && result.Success { + // 认证成功 + common.LogSuccess(i18n.GetText("rabbitmq_weak_pwd_success", target, cred.Username, cred.Password)) + + return &base.ScanResult{ + Success: true, + Service: "RabbitMQ", + Credentials: []*base.Credential{cred}, + Banner: result.Banner, + Extra: map[string]interface{}{ + "service": "RabbitMQ", + "port": info.Ports, + "username": cred.Username, + "password": cred.Password, + "type": "default-credentials", + }, + }, nil + } + } + + // 生成其他凭据进行暴力破解 + credentials := p.generateCredentials() + + // 遍历凭据进行测试 + for _, cred := range credentials { + result, err := p.ScanCredential(ctx, info, cred) + if err == nil && result.Success { + // 认证成功 + common.LogSuccess(i18n.GetText("rabbitmq_weak_pwd_success", target, cred.Username, cred.Password)) + + return &base.ScanResult{ + Success: true, + Service: "RabbitMQ", + Credentials: []*base.Credential{cred}, + Banner: result.Banner, + Extra: map[string]interface{}{ + "service": "RabbitMQ", + "port": info.Ports, + "username": cred.Username, + "password": cred.Password, + "type": "weak-password", + }, + }, nil + } + } + + // 所有凭据都失败,但可能识别到了RabbitMQ服务 + return p.performServiceIdentification(ctx, info) +} + +// generateCredentials 生成RabbitMQ凭据 +func (p *RabbitMQPlugin) generateCredentials() []*base.Credential { + var credentials []*base.Credential + + // 获取RabbitMQ用户名字典 + usernames := common.Userdict["rabbitmq"] + if len(usernames) == 0 { + usernames = []string{"admin", "root", "rabbitmq", "user", "test"} + } + + // 获取密码字典 + passwords := common.Passwords + if len(passwords) == 0 { + passwords = []string{"", "admin", "password", "123456", "rabbitmq", "root123", "admin123"} + } + + // 生成用户名密码组合 + for _, username := range usernames { + for _, password := range passwords { + // 替换密码中的用户名占位符 + actualPassword := strings.Replace(password, "{user}", username, -1) + + credentials = append(credentials, &base.Credential{ + Username: username, + Password: actualPassword, + }) + } + } + + return credentials +} + +// Exploit 使用exploiter执行利用 +func (p *RabbitMQPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *RabbitMQPlugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *RabbitMQPlugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行RabbitMQ服务识别(-nobr模式) +func (p *RabbitMQPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试识别RabbitMQ服务 + connector := NewRabbitMQConnector() + conn, err := connector.Connect(ctx, info) + + if err == nil && conn != nil { + if rabbitConn, ok := conn.(*RabbitMQConnection); ok { + // 记录服务识别成功 + common.LogSuccess(i18n.GetText("rabbitmq_service_identified", target, rabbitConn.info)) + + connector.Close(conn) + return &base.ScanResult{ + Success: true, + Service: "RabbitMQ", + Banner: rabbitConn.info, + Extra: map[string]interface{}{ + "service": "RabbitMQ", + "port": info.Ports, + "info": rabbitConn.info, + "protocol": "AMQP", + }, + }, nil + } + } + + // 如果无法识别为RabbitMQ,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为RabbitMQ服务"), + }, nil +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterRabbitMQPlugin 注册RabbitMQ插件 +func RegisterRabbitMQPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "rabbitmq", + Version: "2.0.0", + Author: "fscan-team", + Description: "RabbitMQ消息队列服务扫描插件", + Category: "service", + Ports: []int{5672, 5671, 15672}, // AMQP端口、AMQPS端口、管理端口 + Protocols: []string{"tcp"}, + Tags: []string{"rabbitmq", "amqp", "message-queue", "weak-password"}, + }, + func() base.Plugin { + return NewRabbitMQPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("rabbitmq", factory) +} + +// 自动注册 +func init() { + RegisterRabbitMQPlugin() +} \ No newline at end of file