fscan/plugins/services/rabbitmq.go
ZacharyZcR 36f0e5076d refactor: 重构Memcached和RabbitMQ插件使用统一发包控制
- 修改Memcached插件,在TCP连接和服务识别中添加发包控制
- 修改RabbitMQ插件,在AMQP连接、HTTP连接和管理接口中添加发包控制
- 统一包计数逻辑,确保TCP连接成功和失败都正确计数
- 保持现有缓存服务和消息队列检测功能
2025-09-02 11:52:59 +00:00

309 lines
7.5 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 services
import (
"context"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins"
)
type RabbitMQPlugin struct {
plugins.BasePlugin
}
func NewRabbitMQPlugin() *RabbitMQPlugin {
return &RabbitMQPlugin{
BasePlugin: plugins.NewBasePlugin("rabbitmq"),
}
}
func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *plugins.Result {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 对于AMQP端口首先识别服务
if info.Ports == "5672" || info.Ports == "5671" {
if result := p.testAMQPProtocol(ctx, info); result.Success {
// AMQP协议识别成功尝试HTTP管理接口的密码爆破
managementResult := p.testManagementInterface(ctx, info)
if managementResult.Success {
return managementResult
}
// 返回服务识别结果
return result
}
}
// HTTP端口的密码爆破
credentials := plugins.GenerateCredentials("rabbitmq")
if len(credentials) == 0 {
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("没有可用的测试凭据"),
}
}
for _, cred := range credentials {
// 检查上下文是否已取消
select {
case <-ctx.Done():
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: ctx.Err(),
}
default:
}
if p.testCredential(ctx, info, cred) {
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s:%s", target, cred.Username, cred.Password))
return &plugins.Result{
Success: true,
Service: "rabbitmq",
Username: cred.Username,
Password: cred.Password,
}
}
}
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testAMQPProtocol 检测AMQP协议
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *plugins.Result {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("RabbitMQ AMQP连接 %s 受限: %s", target, reason))
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("发包受限: %s", reason),
}
}
// 连接到AMQP端口
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
common.IncrementTCPFailedPacketCount()
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: err,
}
}
common.IncrementTCPSuccessPacketCount()
defer conn.Close()
// 设置超时
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// 发送AMQP协议头请求 (AMQP 0-9-1)
amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01}
_, err = conn.Write(amqpHeader)
if err != nil {
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("发送AMQP握手失败: %v", err),
}
}
// 读取服务器响应
buffer := make([]byte, 32)
n, err := conn.Read(buffer)
if err != nil {
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("读取AMQP响应失败: %v", err),
}
}
// 调试信息(可选)
common.LogDebug(fmt.Sprintf("RabbitMQ AMQP响应长度: %d, 前8字节: %v", n, buffer[:min(n, 8)]))
// 检查AMQP协议头或连接开始帧
if n >= 4 && string(buffer[:4]) == "AMQP" {
// 服务器返回AMQP协议头
banner := fmt.Sprintf("RabbitMQ AMQP %d.%d.%d", buffer[5], buffer[6], buffer[7])
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
return &plugins.Result{
Success: true,
Service: "rabbitmq",
Banner: banner,
}
} else if n >= 8 && buffer[0] == 0x01 && buffer[7] == 0xCE {
// Connection.Start方法帧 (AMQP 0-9-1)
banner := "RabbitMQ AMQP 0-9-1"
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
return &plugins.Result{
Success: true,
Service: "rabbitmq",
Banner: banner,
}
} else if n >= 8 && buffer[0] == 0x01 {
// 可能是AMQP帧但格式不同
banner := "RabbitMQ AMQP"
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
return &plugins.Result{
Success: true,
Service: "rabbitmq",
Banner: banner,
}
}
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("非AMQP协议响应"),
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred plugins.Credential) bool {
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("RabbitMQ HTTP连接 %s:%s 受限: %s", info.Host, info.Ports, reason))
return false
}
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
if err != nil {
return false
}
req.SetBasicAuth(cred.Username, cred.Password)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
common.IncrementTCPFailedPacketCount()
return false
}
common.IncrementTCPSuccessPacketCount()
defer resp.Body.Close()
return resp.StatusCode == 200
}
func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *plugins.Result {
// 对于AMQP端口检测AMQP协议
if info.Ports == "5672" || info.Ports == "5671" {
return p.testAMQPProtocol(ctx, info)
}
// 对于HTTP端口检测管理界面
return p.testManagementInterface(ctx, info)
}
// testManagementInterface 检测RabbitMQ管理界面
func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *common.HostInfo) *plugins.Result {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("RabbitMQ管理接口 %s 受限: %s", target, reason))
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("发包受限: %s", reason),
}
}
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: err,
}
}
resp, err := client.Do(req)
if err != nil {
common.IncrementTCPFailedPacketCount()
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: err,
}
}
common.IncrementTCPSuccessPacketCount()
defer resp.Body.Close()
var banner string
if resp.StatusCode == 200 || resp.StatusCode == 401 {
body, _ := io.ReadAll(resp.Body)
bodyStr := strings.ToLower(string(body))
if strings.Contains(bodyStr, "rabbitmq") {
banner = "RabbitMQ Management"
} else if strings.Contains(bodyStr, "management") {
banner = "RabbitMQ Management"
} else {
banner = "RabbitMQ Management"
}
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
return &plugins.Result{
Success: true,
Service: "rabbitmq",
Banner: banner,
}
} else {
return &plugins.Result{
Success: false,
Service: "rabbitmq",
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
}
}
}
func init() {
plugins.RegisterWithPorts("rabbitmq", func() plugins.Plugin {
return NewRabbitMQPlugin()
}, []int{5672, 15672, 5671})
}