fscan/Plugins/services/activemq/exploiter.go
ZacharyZcR ecc79aa9b8 feat: 实现ActiveMQ消息队列服务专业扫描插件
新增功能:
- 基于STOMP协议的ActiveMQ弱密码检测
- 完整的多语言i18n支持(中英文)
- 自动信息收集和权限识别
- 队列枚举和管理权限检测
- 优化的Docker测试环境配置

技术特性:
- 支持端口61613(STOMP)和61614(STOMP+SSL)
- 智能用户权限分析
- 异步利用执行机制
- 统一的插件架构设计
- 完善的错误处理和日志记录

测试环境:
- 简化的ActiveMQ Docker配置
- 预配置多种测试凭据
- 专注STOMP协议,提升性能
2025-08-08 02:53:14 +08:00

383 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package activemq
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// ActiveMQExploiter ActiveMQ利用模块
// 实现ActiveMQ相关的安全测试和利用功能
type ActiveMQExploiter struct {
connector *ActiveMQConnector
timeout time.Duration
}
// NewActiveMQExploiter 创建新的ActiveMQ利用器
func NewActiveMQExploiter() *ActiveMQExploiter {
return &ActiveMQExploiter{
connector: NewActiveMQConnector(),
timeout: time.Duration(common.Timeout) * time.Second,
}
}
// Exploit 执行ActiveMQ利用攻击
func (e *ActiveMQExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogDebug(fmt.Sprintf("开始ActiveMQ利用攻击: %s", target))
// 按优先级尝试各种利用方法
exploitMethods := []func(context.Context, *common.HostInfo, *base.Credential) (*base.ExploitResult, error){
e.exploitInformationGathering, // 信息收集
e.exploitMessageEnumeration, // 消息枚举
e.exploitQueueManagement, // 队列管理
e.exploitConfigurationDump, // 配置转储
}
var lastErr error
for _, method := range exploitMethods {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
result, err := method(ctx, info, creds)
if err != nil {
lastErr = err
common.LogDebug(fmt.Sprintf("利用方法失败: %v", err))
continue
}
if result != nil && result.Success {
return result, nil
}
}
}
return nil, fmt.Errorf("所有利用方法都失败了: %v", lastErr)
}
// exploitInformationGathering 信息收集利用
func (e *ActiveMQExploiter) exploitInformationGathering(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 连接到ActiveMQ服务
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 收集基本信息
info_data, err := e.gatherBasicInfo(conn, creds)
if err != nil {
return nil, fmt.Errorf("信息收集失败: %v", err)
}
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "信息收集",
Output: info_data,
Data: map[string]interface{}{
"service": "ActiveMQ",
"info_type": "system_information",
"data": info_data,
},
}, nil
}
// exploitMessageEnumeration 消息枚举利用
func (e *ActiveMQExploiter) exploitMessageEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 尝试枚举队列和主题
queues, err := e.enumerateQueues(conn)
if err != nil {
return nil, fmt.Errorf("队列枚举失败: %v", err)
}
if len(queues) > 0 {
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "消息枚举",
Output: fmt.Sprintf("发现队列: %s", strings.Join(queues, ", ")),
Data: map[string]interface{}{
"service": "ActiveMQ",
"queues": queues,
"count": len(queues),
},
}, nil
}
return nil, fmt.Errorf("未发现可访问的队列")
}
// exploitQueueManagement 队列管理利用
func (e *ActiveMQExploiter) exploitQueueManagement(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 尝试创建测试队列
testQueue := "test.fscan.queue"
success := e.testQueueCreation(conn, testQueue)
if success {
return &base.ExploitResult{
Success: true,
Type: base.ExploitPrivilegeEsc,
Method: "队列管理",
Output: fmt.Sprintf("成功创建测试队列: %s", testQueue),
Data: map[string]interface{}{
"service": "ActiveMQ",
"privilege": "queue_management",
"test_queue": testQueue,
},
}, nil
}
return nil, fmt.Errorf("队列管理测试失败")
}
// exploitConfigurationDump 配置转储利用
func (e *ActiveMQExploiter) exploitConfigurationDump(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 这里可以实现配置信息获取逻辑
// 由于STOMP协议限制主要通过管理API获取
configInfo := "无法通过STOMP协议获取详细配置信息建议使用Web管理界面"
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "配置转储",
Output: configInfo,
Data: map[string]interface{}{
"service": "ActiveMQ",
"note": "需要Web管理界面访问权限",
},
}, nil
}
// createConnection 创建ActiveMQ连接
func (e *ActiveMQExploiter) createConnection(ctx context.Context, target string, creds *base.Credential) (net.Conn, error) {
conn, err := common.WrapperTcpWithTimeout("tcp", target, e.timeout)
if err != nil {
return nil, err
}
// 进行STOMP认证
authErr := e.connector.authenticateSTOMP(ctx, conn, creds.Username, creds.Password)
if authErr != nil {
conn.Close()
return nil, fmt.Errorf("认证失败: %v", authErr)
}
return conn, nil
}
// gatherBasicInfo 收集基本信息
func (e *ActiveMQExploiter) gatherBasicInfo(conn net.Conn, creds *base.Credential) (string, error) {
var info []string
// 添加认证信息
info = append(info, fmt.Sprintf("认证用户: %s", creds.Username))
info = append(info, fmt.Sprintf("协议: STOMP"))
// 尝试获取服务器信息
serverInfo := e.getServerInfo(conn)
if serverInfo != "" {
info = append(info, fmt.Sprintf("服务器: %s", serverInfo))
}
// 尝试枚举队列
queues, err := e.enumerateQueues(conn)
if err == nil && len(queues) > 0 {
info = append(info, fmt.Sprintf("发现队列: %s", strings.Join(queues, ", ")))
}
// 测试队列创建权限
if e.testQueueCreation(conn, "fscan-test-queue") {
info = append(info, "具备队列创建权限")
}
// 用户权限信息
permissions := e.checkUserPermissions(creds.Username)
if permissions != "" {
info = append(info, fmt.Sprintf("用户权限: %s", permissions))
}
return strings.Join(info, " | "), nil
}
// getServerInfo 获取服务器信息
func (e *ActiveMQExploiter) getServerInfo(conn net.Conn) string {
// 发送INFO帧尝试获取服务器信息
infoFrame := "INFO\n\n\x00"
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(infoFrame)); err != nil {
return ""
}
conn.SetReadDeadline(time.Now().Add(time.Second * 2)) // 短超时
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return ""
}
responseStr := string(response[:n])
if strings.Contains(responseStr, "server:") {
lines := strings.Split(responseStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "server:") {
return strings.TrimPrefix(line, "server:")
}
}
}
return ""
}
// enumerateQueues 枚举队列
func (e *ActiveMQExploiter) enumerateQueues(conn net.Conn) ([]string, error) {
// 常见的ActiveMQ队列名称
commonQueues := []string{
"ActiveMQ.Advisory.>",
"ActiveMQ.Statistics.>",
"test",
"queue.test",
"default",
"example",
}
var discoveredQueues []string
for _, queueName := range commonQueues {
if e.testQueueAccess(conn, queueName) {
discoveredQueues = append(discoveredQueues, queueName)
}
}
return discoveredQueues, nil
}
// testQueueAccess 测试队列访问
func (e *ActiveMQExploiter) testQueueAccess(conn net.Conn, queueName string) bool {
// 发送SUBSCRIBE帧尝试订阅队列
subscribeFrame := fmt.Sprintf("SUBSCRIBE\ndestination:/queue/%s\nack:auto\nid:test-sub-%s\n\n\x00",
queueName, queueName)
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(subscribeFrame)); err != nil {
return false
}
// 读取响应,检查是否订阅成功
conn.SetReadDeadline(time.Now().Add(time.Second))
response := make([]byte, 512)
_, err := conn.Read(response)
// 如果没有ERROR响应认为订阅可能成功
return err == nil && !strings.Contains(string(response), "ERROR")
}
// testQueueCreation 测试队列创建
func (e *ActiveMQExploiter) testQueueCreation(conn net.Conn, queueName string) bool {
// 发送SEND帧到新队列这会隐式创建队列
sendFrame := fmt.Sprintf("SEND\ndestination:/queue/%s\n\nTest message from fscan\x00", queueName)
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(sendFrame)); err != nil {
return false
}
// 读取响应检查是否成功
conn.SetReadDeadline(time.Now().Add(time.Second))
response := make([]byte, 512)
_, err := conn.Read(response)
return err == nil && !strings.Contains(string(response), "ERROR")
}
// GetExploitMethods 获取支持的利用方法
func (e *ActiveMQExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Type: base.ExploitDataExtraction,
Name: "信息收集",
Description: "收集ActiveMQ服务基本信息",
Priority: 8,
Handler: nil,
},
{
Type: base.ExploitDataExtraction,
Name: "消息枚举",
Description: "枚举可访问的队列和主题",
Priority: 7,
Handler: nil,
},
{
Type: base.ExploitPrivilegeEsc,
Name: "队列管理",
Description: "测试队列创建和管理权限",
Priority: 6,
Handler: nil,
},
{
Type: base.ExploitDataExtraction,
Name: "配置转储",
Description: "获取配置信息提示",
Priority: 5,
Handler: nil,
},
}
}
// checkUserPermissions 检查用户权限类型
func (e *ActiveMQExploiter) checkUserPermissions(username string) string {
// 根据用户名推断权限级别
switch strings.ToLower(username) {
case "admin", "root", "system":
return "管理员权限"
case "publisher", "producer":
return "发布者权限"
case "consumer", "subscriber":
return "消费者权限"
case "guest":
return "访客权限"
default:
return "用户权限"
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *ActiveMQExploiter) IsExploitSupported(exploitType base.ExploitType) bool {
supportedTypes := []base.ExploitType{
base.ExploitDataExtraction,
base.ExploitPrivilegeEsc,
}
for _, t := range supportedTypes {
if t == exploitType {
return true
}
}
return false
}