mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
feat: 实现ActiveMQ消息队列服务专业扫描插件
新增功能: - 基于STOMP协议的ActiveMQ弱密码检测 - 完整的多语言i18n支持(中英文) - 自动信息收集和权限识别 - 队列枚举和管理权限检测 - 优化的Docker测试环境配置 技术特性: - 支持端口61613(STOMP)和61614(STOMP+SSL) - 智能用户权限分析 - 异步利用执行机制 - 统一的插件架构设计 - 完善的错误处理和日志记录 测试环境: - 简化的ActiveMQ Docker配置 - 预配置多种测试凭据 - 专注STOMP协议,提升性能
This commit is contained in:
parent
d91ed05d0e
commit
ecc79aa9b8
@ -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",
|
||||
},
|
||||
}
|
@ -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"
|
||||
|
201
Plugins/services/activemq/connector.go
Normal file
201
Plugins/services/activemq/connector.go
Normal 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"},
|
||||
}
|
||||
}
|
383
Plugins/services/activemq/exploiter.go
Normal file
383
Plugins/services/activemq/exploiter.go
Normal 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
|
||||
}
|
252
Plugins/services/activemq/plugin.go
Normal file
252
Plugins/services/activemq/plugin.go
Normal 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()
|
||||
}
|
@ -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"]
|
@ -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&wireFormat.maxFrameSize=104857600"/>
|
||||
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
|
||||
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=500&wireFormat.maxFrameSize=104857600"/>
|
||||
<transportConnector name="stomp+ssl" uri="stomp+ssl://0.0.0.0:61614?maximumConnections=500&wireFormat.maxFrameSize=104857600"/>
|
||||
</transportConnectors>
|
||||
|
||||
<!-- 禁用JMX和Web控制台以简化配置 -->
|
||||
<managementContext>
|
||||
<managementContext createConnector="false"/>
|
||||
</managementContext>
|
||||
|
||||
<!-- 简化的持久化配置 -->
|
||||
<persistenceAdapter>
|
||||
<memoryPersistenceAdapter/>
|
||||
</persistenceAdapter>
|
||||
</broker>
|
||||
</beans>
|
15
TestDocker/ActiveMQ/docker-compose.yml
Normal file
15
TestDocker/ActiveMQ/docker-compose.yml
Normal 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
|
12
TestDocker/ActiveMQ/jetty-realm.properties
Normal file
12
TestDocker/ActiveMQ/jetty-realm.properties
Normal 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
|
112
TestDocker/ActiveMQ/jetty.xml
Normal file
112
TestDocker/ActiveMQ/jetty.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user