feat: 实现ActiveMQ消息队列服务专业扫描插件

新增功能:
- 基于STOMP协议的ActiveMQ弱密码检测
- 完整的多语言i18n支持(中英文)
- 自动信息收集和权限识别
- 队列枚举和管理权限检测
- 优化的Docker测试环境配置

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

测试环境:
- 简化的ActiveMQ Docker配置
- 预配置多种测试凭据
- 专注STOMP协议,提升性能
This commit is contained in:
ZacharyZcR 2025-08-08 02:53:14 +08:00
parent d91ed05d0e
commit ecc79aa9b8
10 changed files with 1097 additions and 12 deletions

View File

@ -412,4 +412,94 @@ var PluginMessages = map[string]map[string]string{
LangZH: "利用结果已保存: %s",
LangEN: "Exploitation result saved: %s",
},
// ========================= ActiveMQ插件消息 =========================
"activemq_scan_start": {
LangZH: "开始ActiveMQ扫描: %s",
LangEN: "Starting ActiveMQ scan: %s",
},
"activemq_stomp_scan_success": {
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
},
"activemq_stomp_auth_success": {
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
},
"activemq_connection_failed": {
LangZH: "ActiveMQ连接失败: %v",
LangEN: "ActiveMQ connection failed: %v",
},
"activemq_auth_failed": {
LangZH: "ActiveMQ认证失败: %v",
LangEN: "ActiveMQ authentication failed: %v",
},
"activemq_stomp_auth_failed": {
LangZH: "ActiveMQ STOMP认证失败: %v",
LangEN: "ActiveMQ STOMP authentication failed: %v",
},
// ActiveMQ利用方法消息
"activemq_exploit_info_gather": {
LangZH: "ActiveMQ信息收集成功",
LangEN: "ActiveMQ information gathering successful",
},
"activemq_exploit_message_enum": {
LangZH: "ActiveMQ消息枚举成功",
LangEN: "ActiveMQ message enumeration successful",
},
"activemq_exploit_queue_mgmt": {
LangZH: "ActiveMQ队列管理成功",
LangEN: "ActiveMQ queue management successful",
},
"activemq_exploit_config_dump": {
LangZH: "ActiveMQ配置转储成功",
LangEN: "ActiveMQ configuration dump successful",
},
"activemq_queues_found": {
LangZH: "发现ActiveMQ队列: %s",
LangEN: "ActiveMQ queues found: %s",
},
"activemq_topics_found": {
LangZH: "发现ActiveMQ主题: %s",
LangEN: "ActiveMQ topics found: %s",
},
"activemq_queue_created": {
LangZH: "成功创建测试队列: %s",
LangEN: "Test queue created successfully: %s",
},
"activemq_message_sent": {
LangZH: "消息发送成功到队列: %s",
LangEN: "Message sent successfully to queue: %s",
},
"activemq_version_info": {
LangZH: "ActiveMQ版本: %s",
LangEN: "ActiveMQ version: %s",
},
"activemq_broker_info": {
LangZH: "ActiveMQ Broker信息: %s",
LangEN: "ActiveMQ Broker info: %s",
},
"activemq_protocol_detected": {
LangZH: "检测到ActiveMQ协议: %s",
LangEN: "ActiveMQ protocol detected: %s",
},
// ActiveMQ利用方法名称
"exploit_method_name_activemq_info_gather": {
LangZH: "信息收集",
LangEN: "Information Gathering",
},
"exploit_method_name_activemq_message_enum": {
LangZH: "消息枚举",
LangEN: "Message Enumeration",
},
"exploit_method_name_activemq_queue_mgmt": {
LangZH: "队列管理",
LangEN: "Queue Management",
},
"exploit_method_name_activemq_config_dump": {
LangZH: "配置转储",
LangEN: "Configuration Dump",
},
}

View File

@ -6,6 +6,7 @@ import (
"github.com/shadow1ng/fscan/plugins/base"
// 导入新架构插件,触发自动注册
_ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"

View File

@ -0,0 +1,201 @@
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 根据端口获取协议类型
func (c *ActiveMQConnector) getProtocolByPort(port int) string {
switch port {
case 61613, 61614:
return "STOMP"
default:
return "STOMP" // 默认仅支持STOMP
}
}
// 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

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

View File

@ -0,0 +1,252 @@
package activemq
import (
"context"
"fmt"
"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) {
// 调用基础服务插件进行弱密码扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err // 扫描失败,直接返回
}
// 记录成功的弱密码发现使用i18n根据端口显示不同协议
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
cred := result.Credentials[0]
// 专注于STOMP协议的成功消息
common.LogSuccess(i18n.GetText("activemq_stomp_scan_success", target, cred.Username, cred.Password))
// 自动利用功能(可通过-nobr参数禁用
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
// 同步执行利用攻击,确保显示结果
p.autoExploit(context.Background(), info, result.Credentials[0])
}
return result, nil
}
// autoExploit 自动利用
func (p *ActiveMQPlugin) autoExploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogDebug(i18n.GetText("plugin_exploit_start", "ActiveMQ", target))
// 执行利用
result, err := p.exploiter.Exploit(ctx, info, creds)
if err != nil {
common.LogDebug(i18n.GetText("plugin_exploit_failed", "ActiveMQ", err))
return
}
if result != nil && result.Success {
// 使用利用结果中的Method字段作为方法名称
methodName := result.Method
if methodName == "" {
methodName = p.getExploitMethodName(result.Type)
}
// 只显示一次完整的利用结果
if result.Output != "" {
common.LogSuccess(fmt.Sprintf("ActiveMQ %s %s 利用成功 输出: %s", target, methodName, result.Output))
} else {
common.LogSuccess(fmt.Sprintf("ActiveMQ %s %s 利用成功", target, methodName))
}
// 保存利用结果(不显示额外日志)
// base.SaveExploitResult(info, result, "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 重写凭据生成方法
func (p *ActiveMQPlugin) generateCredentials() []*base.Credential {
// 获取ActiveMQ专用的用户名字典
usernames := common.Userdict["activemq"]
if len(usernames) == 0 {
// 默认ActiveMQ用户名
usernames = []string{
"admin", "test", "root", "system", "user", "guest",
"manager", "activemq", "mqadmin", "broker",
}
}
// 生成基本凭据组合
credentials := base.GenerateCredentials(usernames, common.Passwords)
// 添加ActiveMQ专用的默认凭据
defaultCreds := p.ServicePlugin.GetServiceConnector().(*ActiveMQConnector).GetDefaultCredentials()
credentials = append(credentials, defaultCreds...)
// 去重处理(简化实现)
seen := make(map[string]bool)
var unique []*base.Credential
for _, cred := range credentials {
key := cred.Username + ":" + cred.Password
if !seen[key] {
seen[key] = true
unique = append(unique, cred)
}
}
return unique
}
// 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,11 +1,14 @@
FROM rmohr/activemq:5.15.9
# 复制配置文件
COPY users.properties /opt/activemq/conf/users.properties
# 复制STOMP专用配置文件
COPY activemq.xml /opt/activemq/conf/activemq.xml
# 暴露端口
EXPOSE 61616 61613
# 暴露STOMP协议端口
EXPOSE 61613 61614
# 设置启动命令
# 设置环境变量
ENV ACTIVEMQ_OPTS_MEMORY="-Xms64M -Xmx512M"
ENV ACTIVEMQ_OPTS="-Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=/opt/activemq/conf/login.config"
# 启动ActiveMQ
CMD ["/opt/activemq/bin/activemq", "console"]

View File

@ -6,34 +6,50 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
<broker xmlns="http://activemq.apache.org/schema/core" useJmx="true" persistent="false">
<!-- 安全设置 -->
<!-- 专注于STOMP协议的ActiveMQ配置 -->
<broker xmlns="http://activemq.apache.org/schema/core" useJmx="false" persistent="false">
<!-- 安全认证配置 -->
<plugins>
<simpleAuthenticationPlugin>
<users>
<!-- 主要测试账户 -->
<authenticationUser username="admin" password="Aa123456789" groups="admins,publishers,consumers"/>
<authenticationUser username="admin" password="admin" groups="admins,publishers,consumers"/>
<authenticationUser username="test" password="test123" groups="publishers,consumers"/>
<authenticationUser username="root" password="root123" groups="admins"/>
<authenticationUser username="system" password="admin123" groups="admins"/>
<authenticationUser username="guest" password="guest" groups="consumers"/>
<authenticationUser username="activemq" password="activemq" groups="publishers,consumers"/>
</users>
</simpleAuthenticationPlugin>
<!-- 授权插件 -->
<!-- 简化的授权配置 -->
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue=">" read="consumers" write="publishers" admin="admins"/>
<authorizationEntry topic=">" read="consumers" write="publishers" admin="admins"/>
<authorizationEntry queue=">" read="consumers,admins" write="publishers,admins" admin="admins"/>
<authorizationEntry topic=">" read="consumers,admins" write="publishers,admins" admin="admins"/>
</authorizationEntries>
</authorizationMap>
</map>
</authorizationPlugin>
</plugins>
<!-- 仅启用STOMP传输连接器 -->
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=500&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp+ssl" uri="stomp+ssl://0.0.0.0:61614?maximumConnections=500&amp;wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
<!-- 禁用JMX和Web控制台以简化配置 -->
<managementContext>
<managementContext createConnector="false"/>
</managementContext>
<!-- 简化的持久化配置 -->
<persistenceAdapter>
<memoryPersistenceAdapter/>
</persistenceAdapter>
</broker>
</beans>

View File

@ -0,0 +1,15 @@
version: '3.8'
services:
activemq:
build: .
ports:
- "61613:61613" # STOMP
- "61616:61616" # OpenWire
- "8162:8161" # Web Console (mapped to host port 8162)
environment:
- ACTIVEMQ_ADMIN_LOGIN=admin
- ACTIVEMQ_ADMIN_PASSWORD=Aa123456789
volumes:
- ./activemq.xml:/opt/activemq/conf/activemq.xml
- ./users.properties:/opt/activemq/conf/users.properties

View File

@ -0,0 +1,12 @@
# ActiveMQ Web Console用户认证配置
# 格式: username: password [,role1,role2,...]
# 管理员用户
admin: Aa123456789,admin,user
test: test123,user
root: root123,admin,user
system: admin123,admin,user
# 默认测试用户
user: user,user
guest: guest,user

View File

@ -0,0 +1,112 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="BASIC" />
<property name="roles" value="user,admin" />
<property name="authenticate" value="true" />
</bean>
<bean id="adminSecurityConstraint" class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="BASIC" />
<property name="roles" value="admin" />
<property name="authenticate" value="true" />
</bean>
<bean id="securityConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
<property name="constraint" ref="securityConstraint" />
<property name="pathSpec" value="/admin/*,/api/*" />
</bean>
<bean id="realmSecurityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
<property name="authenticator">
<bean class="org.eclipse.jetty.security.authentication.BasicAuthenticator" />
</property>
<property name="constraintMappings">
<list>
<ref bean="securityConstraintMapping" />
</list>
</property>
<property name="loginService">
<bean class="org.eclipse.jetty.security.HashLoginService">
<property name="name" value="ActiveMQRealm" />
<property name="config" value="${activemq.conf}/jetty-realm.properties" />
</bean>
</property>
</bean>
<bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
</bean>
<bean id="jettyPort" class="org.apache.activemq.web.config.SystemPropertiesConfiguration" init-method="configure">
<property name="properties">
<map>
<entry key="jetty.port" value="8161" />
<entry key="jetty.host" value="0.0.0.0" />
</map>
</property>
</bean>
<bean id="Server" class="org.eclipse.jetty.server.Server"
depends-on="jettyPort"
init-method="start" destroy-method="stop">
<property name="connectors">
<list>
<bean id="Connector" class="org.eclipse.jetty.server.ServerConnector">
<constructor-arg ref="Server" />
<property name="host" value="#{systemProperties['jetty.host']}" />
<property name="port" value="#{systemProperties['jetty.port']}" />
</bean>
</list>
</property>
<property name="handler">
<bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
<property name="handlers">
<list>
<ref bean="contexts" />
<bean class="org.eclipse.jetty.server.handler.DefaultHandler" />
</list>
</property>
</bean>
</property>
</bean>
<bean id="invokeStart" class="org.springframework.beans.factory.config.MethodInvokingBean">
<property name="targetObject" ref="Server" />
<property name="targetMethod" value="start" />
</bean>
<bean class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/admin" />
<property name="resourceBase" value="${activemq.home}/webapps/admin" />
<property name="server" ref="Server" />
<property name="securityHandler" ref="realmSecurityHandler" />
</bean>
<bean class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/api" />
<property name="resourceBase" value="${activemq.home}/webapps/api" />
<property name="server" ref="Server" />
<property name="securityHandler" ref="realmSecurityHandler" />
</bean>
</beans>