mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 删除整个legacy插件系统(7794行代码) - 完成所有插件向单文件架构迁移 - 移除19个插件的虚假Exploit功能,只保留真实利用: * Redis: 文件写入、SSH密钥注入、计划任务 * SSH: 命令执行 * MS17010: EternalBlue漏洞利用 - 统一插件接口,简化架构复杂度 - 清理临时文件和备份文件 重构效果: - 代码行数: -7794行 - 插件文件数: 从3文件架构→单文件架构 - 真实利用插件: 从22个→3个 - 架构复杂度: 大幅简化
432 lines
11 KiB
Go
432 lines
11 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
)
|
||
|
||
// RabbitMQPlugin RabbitMQ消息队列扫描和利用插件 - 包含队列信息提取利用功能
|
||
type RabbitMQPlugin struct {
|
||
name string
|
||
ports []int
|
||
}
|
||
|
||
// NewRabbitMQPlugin 创建RabbitMQ插件
|
||
func NewRabbitMQPlugin() *RabbitMQPlugin {
|
||
return &RabbitMQPlugin{
|
||
name: "rabbitmq",
|
||
ports: []int{5672, 15672, 5671}, // AMQP、管理界面、AMQPS端口
|
||
}
|
||
}
|
||
|
||
// GetName 实现Plugin接口
|
||
func (p *RabbitMQPlugin) GetName() string {
|
||
return p.name
|
||
}
|
||
|
||
// GetPorts 实现Plugin接口
|
||
func (p *RabbitMQPlugin) GetPorts() []int {
|
||
return p.ports
|
||
}
|
||
|
||
// Scan 执行RabbitMQ扫描 - 弱密码检测
|
||
func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 如果禁用暴力破解,只做服务识别
|
||
if common.DisableBrute {
|
||
return p.identifyService(ctx, info)
|
||
}
|
||
|
||
// RabbitMQ管理端口通常是15672,AMQP端口是5672
|
||
if info.Ports == "5672" || info.Ports == "5671" {
|
||
// AMQP端口,进行协议检测
|
||
return p.testAMQPProtocol(ctx, info)
|
||
}
|
||
|
||
// 生成测试凭据
|
||
credentials := GenerateCredentials("rabbitmq")
|
||
if len(credentials) == 0 {
|
||
// RabbitMQ默认凭据
|
||
credentials = []Credential{
|
||
{Username: "guest", Password: "guest"},
|
||
{Username: "admin", Password: "admin"},
|
||
{Username: "admin", Password: "password"},
|
||
{Username: "admin", Password: "123456"},
|
||
{Username: "rabbitmq", Password: "rabbitmq"},
|
||
{Username: "user", Password: "user"},
|
||
}
|
||
}
|
||
|
||
// 逐个测试凭据(针对管理接口)
|
||
for _, cred := range credentials {
|
||
// 检查Context是否被取消
|
||
select {
|
||
case <-ctx.Done():
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "rabbitmq",
|
||
Error: ctx.Err(),
|
||
}
|
||
default:
|
||
}
|
||
|
||
// 测试凭据
|
||
if p.testCredential(ctx, info, cred) {
|
||
// RabbitMQ认证成功
|
||
common.LogSuccess(i18n.GetText("rabbitmq_scan_success", target, cred.Username, cred.Password))
|
||
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "rabbitmq",
|
||
Username: cred.Username,
|
||
Password: cred.Password,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 所有凭据都失败
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "rabbitmq",
|
||
Error: fmt.Errorf("未发现弱密码"),
|
||
}
|
||
}
|
||
|
||
|
||
// testAMQPProtocol 测试AMQP协议
|
||
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
// 对于AMQP端口,我们只做协议识别
|
||
// AMQP协议检测比较复杂,这里简化处理
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "rabbitmq",
|
||
Banner: "RabbitMQ AMQP协议端口",
|
||
}
|
||
}
|
||
|
||
// testCredential 测试单个凭据(通过管理API)
|
||
func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
|
||
// 构建管理API URL
|
||
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
||
|
||
client := &http.Client{
|
||
Timeout: time.Duration(common.Timeout) * time.Second,
|
||
}
|
||
|
||
// 尝试访问API overview
|
||
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 {
|
||
return false
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
return resp.StatusCode == 200
|
||
}
|
||
|
||
// makeAPIRequest 执行API请求
|
||
func (p *RabbitMQPlugin) makeAPIRequest(ctx context.Context, info *common.HostInfo, creds Credential, endpoint string) (map[string]interface{}, error) {
|
||
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+endpoint, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.SetBasicAuth(creds.Username, creds.Password)
|
||
req.Header.Set("Accept", "application/json")
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return nil, fmt.Errorf("API请求失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var result map[string]interface{}
|
||
err = json.Unmarshal(body, &result)
|
||
if err != nil {
|
||
// 尝试解析为数组
|
||
var arrayResult []map[string]interface{}
|
||
if err2 := json.Unmarshal(body, &arrayResult); err2 == nil {
|
||
// 如果是数组,返回包装的结果
|
||
return map[string]interface{}{"data": arrayResult}, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// getClusterInfo 获取集群信息
|
||
func (p *RabbitMQPlugin) getClusterInfo(ctx context.Context, info *common.HostInfo, creds Credential) string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/overview")
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
|
||
var info_str strings.Builder
|
||
|
||
if managementVersion, ok := result["management_version"]; ok {
|
||
info_str.WriteString(fmt.Sprintf("管理版本: %v\n", managementVersion))
|
||
}
|
||
|
||
if rabbitMQVersion, ok := result["rabbitmq_version"]; ok {
|
||
info_str.WriteString(fmt.Sprintf("RabbitMQ版本: %v\n", rabbitMQVersion))
|
||
}
|
||
|
||
if clusterName, ok := result["cluster_name"]; ok {
|
||
info_str.WriteString(fmt.Sprintf("集群名称: %v\n", clusterName))
|
||
}
|
||
|
||
return info_str.String()
|
||
}
|
||
|
||
// getNodes 获取节点列表
|
||
func (p *RabbitMQPlugin) getNodes(ctx context.Context, info *common.HostInfo, creds Credential) []string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/nodes")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var nodes []string
|
||
|
||
if data, ok := result["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if node, ok := item.(map[string]interface{}); ok {
|
||
if name, ok := node["name"]; ok {
|
||
if nameStr, ok := name.(string); ok {
|
||
nodes = append(nodes, nameStr)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nodes
|
||
}
|
||
|
||
// getVHosts 获取虚拟主机列表
|
||
func (p *RabbitMQPlugin) getVHosts(ctx context.Context, info *common.HostInfo, creds Credential) []string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/vhosts")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var vhosts []string
|
||
|
||
if data, ok := result["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if vhost, ok := item.(map[string]interface{}); ok {
|
||
if name, ok := vhost["name"]; ok {
|
||
if nameStr, ok := name.(string); ok {
|
||
vhosts = append(vhosts, nameStr)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return vhosts
|
||
}
|
||
|
||
// getUsers 获取用户列表
|
||
func (p *RabbitMQPlugin) getUsers(ctx context.Context, info *common.HostInfo, creds Credential) []string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/users")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var users []string
|
||
|
||
if data, ok := result["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if user, ok := item.(map[string]interface{}); ok {
|
||
if name, ok := user["name"]; ok {
|
||
if nameStr, ok := name.(string); ok {
|
||
// 获取用户标签信息
|
||
var tags string
|
||
if tagsInterface, ok := user["tags"]; ok {
|
||
if tagsStr, ok := tagsInterface.(string); ok {
|
||
tags = fmt.Sprintf(" [%s]", tagsStr)
|
||
}
|
||
}
|
||
users = append(users, nameStr+tags)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return users
|
||
}
|
||
|
||
// getQueues 获取队列列表
|
||
func (p *RabbitMQPlugin) getQueues(ctx context.Context, info *common.HostInfo, creds Credential) []string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/queues")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var queues []string
|
||
|
||
if data, ok := result["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if queue, ok := item.(map[string]interface{}); ok {
|
||
if name, ok := queue["name"]; ok {
|
||
if nameStr, ok := name.(string); ok {
|
||
// 获取队列消息数
|
||
var messages string
|
||
if messagesInterface, ok := queue["messages"]; ok {
|
||
messages = fmt.Sprintf(" (消息数: %v)", messagesInterface)
|
||
}
|
||
queues = append(queues, nameStr+messages)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return queues
|
||
}
|
||
|
||
// getExchanges 获取交换器列表
|
||
func (p *RabbitMQPlugin) getExchanges(ctx context.Context, info *common.HostInfo, creds Credential) []string {
|
||
result, err := p.makeAPIRequest(ctx, info, creds, "/api/exchanges")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var exchanges []string
|
||
|
||
if data, ok := result["data"].([]interface{}); ok {
|
||
for _, item := range data {
|
||
if exchange, ok := item.(map[string]interface{}); ok {
|
||
if name, ok := exchange["name"]; ok {
|
||
if nameStr, ok := name.(string); ok {
|
||
// 过滤掉空名称的默认交换器
|
||
if nameStr == "" {
|
||
nameStr = "(default)"
|
||
}
|
||
|
||
// 获取交换器类型
|
||
var exchType string
|
||
if typeInterface, ok := exchange["type"]; ok {
|
||
if typeStr, ok := typeInterface.(string); ok {
|
||
exchType = fmt.Sprintf(" [%s]", typeStr)
|
||
}
|
||
}
|
||
exchanges = append(exchanges, nameStr+exchType)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return exchanges
|
||
}
|
||
|
||
// identifyService 服务识别 - 检测RabbitMQ服务
|
||
func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 根据端口类型进行不同的识别
|
||
if info.Ports == "5672" || info.Ports == "5671" {
|
||
// AMQP端口
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "rabbitmq",
|
||
Banner: "RabbitMQ AMQP协议服务",
|
||
}
|
||
}
|
||
|
||
// 管理端口,尝试HTTP请求
|
||
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 &ScanResult{
|
||
Success: false,
|
||
Service: "rabbitmq",
|
||
Error: err,
|
||
}
|
||
}
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "rabbitmq",
|
||
Error: err,
|
||
}
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 检查响应是否包含RabbitMQ特征
|
||
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管理界面"
|
||
} else if strings.Contains(bodyStr, "management") {
|
||
banner = "RabbitMQ服务 (管理端口)"
|
||
} else {
|
||
banner = "RabbitMQ消息队列服务"
|
||
}
|
||
} else {
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "rabbitmq",
|
||
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
|
||
}
|
||
}
|
||
|
||
common.LogSuccess(i18n.GetText("rabbitmq_service_identified", target, banner))
|
||
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "rabbitmq",
|
||
Banner: banner,
|
||
}
|
||
}
|
||
|
||
// init 自动注册插件
|
||
func init() {
|
||
RegisterPlugin("rabbitmq", func() Plugin {
|
||
return NewRabbitMQPlugin()
|
||
})
|
||
} |