Files
fscan/plugins/services/activemq.go
T
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
2026-01-11 20:16:23 +08:00

285 lines
7.3 KiB
Go

//go:build plugin_activemq || !plugin_selective
package services
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins"
)
// ActiveMQPlugin ActiveMQ扫描插件
type ActiveMQPlugin struct {
plugins.BasePlugin
}
func NewActiveMQPlugin() *ActiveMQPlugin {
return &ActiveMQPlugin{
BasePlugin: plugins.NewBasePlugin("activemq"),
}
}
func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
target := info.Target()
if config.DisableBrute {
return p.identifyService(ctx, info, config, state)
}
// 生成测试凭据
credentials := GenerateCredentials("activemq", config)
if len(credentials) == 0 {
// ActiveMQ默认凭据
credentials = []Credential{
{Username: "admin", Password: "admin"},
{Username: "admin", Password: ""},
{Username: "admin", Password: "password"},
{Username: "activemq", Password: "activemq"},
{Username: "activemq", Password: "admin"},
{Username: "user", Password: "user"},
{Username: "guest", Password: "guest"},
}
}
// 使用公共框架进行并发凭据测试
authFn := p.createAuthFunc(info, config, state)
testConfig := DefaultConcurrentTestConfig(config)
result := TestCredentialsConcurrently(ctx, credentials, authFn, "activemq", testConfig)
if result.Success {
common.LogSuccess(i18n.Tr("activemq_credential", target, result.Username, result.Password))
}
return result
}
// createAuthFunc 创建ActiveMQ认证函数
func (p *ActiveMQPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc {
return func(ctx context.Context, cred Credential) *AuthResult {
return p.doActiveMQAuth(ctx, info, cred, config, state)
}
}
// doActiveMQAuth 执行ActiveMQ认证
func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
target := info.Target()
timeout := config.Timeout
resultChan := make(chan *AuthResult, 1)
go func() {
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
state.IncrementTCPFailedPacketCount()
resultChan <- &AuthResult{
Success: false,
ErrorType: classifyActiveMQErrorType(err),
Error: err,
}
return
}
success, err := p.authenticateSTOMP(conn, cred.Username, cred.Password, config)
if success {
state.IncrementTCPSuccessPacketCount()
resultChan <- &AuthResult{
Success: true,
Conn: &activeMQConnWrapper{conn},
ErrorType: ErrorTypeUnknown,
Error: nil,
}
return
}
_ = conn.Close()
state.IncrementTCPFailedPacketCount()
resultChan <- &AuthResult{
Success: false,
ErrorType: classifyActiveMQErrorType(err),
Error: err,
}
}()
select {
case result := <-resultChan:
return result
case <-ctx.Done():
// context 被取消,启动清理协程等待并关闭可能创建的连接
go func() {
result := <-resultChan
if result != nil && result.Conn != nil {
_ = result.Conn.Close()
}
}()
return &AuthResult{
Success: false,
ErrorType: ErrorTypeNetwork,
Error: ctx.Err(),
}
}
}
// activeMQConnWrapper 包装ActiveMQ连接以实现io.Closer
type activeMQConnWrapper struct {
conn net.Conn
}
func (w *activeMQConnWrapper) Close() error {
return w.conn.Close()
}
// classifyActiveMQErrorType ActiveMQ错误分类
func classifyActiveMQErrorType(err error) ErrorType {
if err == nil {
return ErrorTypeUnknown
}
activeMQAuthErrors := []string{
"authentication failed",
"access denied",
"invalid credentials",
"login failed",
"unauthorized",
"403 forbidden",
"security exception",
"invalid user",
"invalid password",
"login incorrect",
}
return ClassifyError(err, activeMQAuthErrors, CommonNetworkErrors)
}
// authenticateSTOMP 使用STOMP协议认证ActiveMQ
func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) {
timeout := config.Timeout
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00",
username, password)
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
return false, fmt.Errorf("STOMP请求发送失败: %w", err)
}
_ = conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return false, fmt.Errorf("STOMP响应读取失败: %w", err)
}
if n == 0 {
return false, fmt.Errorf("STOMP无响应数据")
}
responseStr := string(response[:n])
if strings.Contains(responseStr, "CONNECTED") {
return true, nil
} else if strings.Contains(responseStr, "ERROR") {
errorMsg := "STOMP认证错误"
if strings.Contains(responseStr, "Authentication failed") {
errorMsg = "Authentication failed"
} else if strings.Contains(responseStr, "Access denied") {
errorMsg = "Access denied"
} else if strings.Contains(responseStr, "Invalid credentials") {
errorMsg = "Invalid credentials"
}
return false, fmt.Errorf("%s", errorMsg)
}
return false, fmt.Errorf("STOMP未知响应格式")
}
// identifyService ActiveMQ服务识别
func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
target := info.Target()
timeout := config.Timeout
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{
Success: false,
Service: "activemq",
Error: err,
}
}
defer func() { _ = conn.Close() }()
stompConnect := "CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\n\n\x00"
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
if _, writeErr := conn.Write([]byte(stompConnect)); writeErr != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法发送STOMP请求: %w", writeErr),
}
}
_ = conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法读取响应: %w", err),
}
}
if n == 0 {
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无响应数据"),
}
}
state.IncrementTCPSuccessPacketCount()
responseStr := string(response[:n])
if common.ContainsAny(responseStr, "CONNECTED", "ERROR") {
banner := "ActiveMQ STOMP"
if strings.Contains(responseStr, "server:") {
lines := strings.Split(responseStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "server:") {
banner = strings.TrimSpace(strings.TrimPrefix(line, "server:"))
break
}
}
}
common.LogSuccess(i18n.Tr("activemq_service", target, banner))
return &ScanResult{
Success: true,
Type: plugins.ResultTypeService,
Service: "activemq",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "activemq",
Error: fmt.Errorf("无法识别为ActiveMQ STOMP服务"),
}
}
func init() {
RegisterPluginWithPorts("activemq", func() Plugin {
return NewActiveMQPlugin()
}, []int{61613, 61614, 61616, 61617, 61618, 8161})
}