mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
docs: 完善参数配置文档并修复插件兼容性
- 添加完整的参数配置表格和使用示例到README.md - 修复Kafka插件的协议错误识别逻辑 - 修复RabbitMQ插件的AMQP协议检测 - 完成所有核心参数的功能验证测试
This commit is contained in:
parent
b7b805874f
commit
af2c92a591
139
README.md
139
README.md
@ -33,25 +33,132 @@
|
|||||||
- 扫描结果存储:将所有检测结果保存至文件,便于后续分析
|
- 扫描结果存储:将所有检测结果保存至文件,便于后续分析
|
||||||
|
|
||||||
# 0x03 使用说明
|
# 0x03 使用说明
|
||||||
完整功能介绍、使用说明及最新更新请访问我们的官方网站。
|
|
||||||
|
|
||||||
## 官方网站
|
## 参数配置详解
|
||||||
|
|
||||||
**https://fscan.club/**
|
### 目标配置参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-h` | 无 | 目标主机: IP, IP段, IP段文件, 域名 |
|
||||||
|
| `-eh` | 无 | 排除主机 |
|
||||||
|
| `-p` | 1000个常用端口 | 端口: 默认1000个常用端口 |
|
||||||
|
| `-ep` | 无 | 排除端口 |
|
||||||
|
| `-hf` | 无 | 主机文件 |
|
||||||
|
| `-pf` | 无 | 端口文件 |
|
||||||
|
|
||||||
访问官网获取:
|
### 扫描控制参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-m` | all | 扫描模式: all(全部), icmp(存活探测), 或指定插件名称 |
|
||||||
|
| `-t` | 600 | 端口扫描线程数 |
|
||||||
|
| `-time` | 3 | 端口扫描超时时间(秒) |
|
||||||
|
| `-mt` | 50 | 模块线程数 |
|
||||||
|
| `-gt` | 180 | 全局超时时间(秒) |
|
||||||
|
| `-np` | false | 禁用ping探测 |
|
||||||
|
| `-fp` | false | 启用指纹识别 |
|
||||||
|
| `-local` | 无 | 指定本地插件名称 (如: cleaner, avdetect, keylogger 等) |
|
||||||
|
| `-ao` | false | 仅进行存活探测 |
|
||||||
|
|
||||||
- 详细功能文档
|
### 认证与凭据参数
|
||||||
- 使用教程
|
| 参数 | 默认值 | 说明 |
|
||||||
- 最新版本下载
|
|------|--------|------|
|
||||||
- 常见问题解答
|
| `-user` | 无 | 用户名 |
|
||||||
- 技术支持
|
| `-pwd` | 无 | 密码 |
|
||||||
|
| `-usera` | 无 | 额外用户名 |
|
||||||
|
| `-pwda` | 无 | 额外密码 |
|
||||||
|
| `-userf` | 无 | 用户名字典文件 |
|
||||||
|
| `-pwdf` | 无 | 密码字典文件 |
|
||||||
|
| `-hashf` | 无 | 哈希文件 |
|
||||||
|
| `-hash` | 无 | 哈希值 |
|
||||||
|
| `-domain` | 无 | 域名 (SMB扫描用) |
|
||||||
|
| `-sshkey` | 无 | SSH私钥文件 |
|
||||||
|
|
||||||
|
### Web扫描参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-u` | 无 | 目标URL |
|
||||||
|
| `-uf` | 无 | URL文件 |
|
||||||
|
| `-cookie` | 无 | HTTP Cookie |
|
||||||
|
| `-wt` | 5 | Web超时时间(秒) |
|
||||||
|
| `-proxy` | 无 | HTTP代理 |
|
||||||
|
| `-socks5` | 无 | SOCKS5代理 (如: 127.0.0.1:1080) |
|
||||||
|
|
||||||
|
### POC测试参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-pocpath` | 无 | POC脚本路径 |
|
||||||
|
| `-pocname` | 无 | POC名称 |
|
||||||
|
| `-full` | false | 全量POC扫描 |
|
||||||
|
| `-dns` | false | DNS日志记录 |
|
||||||
|
| `-num` | 20 | POC并发数 |
|
||||||
|
| `-nopoc` | false | 禁用POC扫描 |
|
||||||
|
|
||||||
|
### Redis利用参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-rf` | 无 | Redis文件 |
|
||||||
|
| `-rs` | 无 | Redis Shell |
|
||||||
|
| `-rwp` | 无 | Redis写入路径 |
|
||||||
|
| `-rwc` | 无 | Redis写入内容 |
|
||||||
|
| `-rwf` | 无 | Redis写入文件 |
|
||||||
|
|
||||||
|
### 输出与显示控制参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-o` | result.txt | 输出文件 |
|
||||||
|
| `-f` | txt | 输出格式: txt, json, csv |
|
||||||
|
| `-no` | false | 禁用结果保存 |
|
||||||
|
| `-silent` | false | 静默模式 |
|
||||||
|
| `-nocolor` | false | 禁用颜色输出 |
|
||||||
|
| `-log` | info+success | 日志级别 |
|
||||||
|
| `-nopg` | false | 禁用进度条 |
|
||||||
|
|
||||||
|
### 高级功能参数
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `-nobr` | false | 禁用暴力破解 |
|
||||||
|
| `-retry` | 3 | 最大重试次数 |
|
||||||
|
| `-sc` | 无 | Shellcode |
|
||||||
|
| `-rsh` | 无 | 反弹Shell目标地址:端口 (如: 192.168.1.100:4444) |
|
||||||
|
| `-start-socks5` | 0 | 启动SOCKS5代理服务器端口 (如: 1080) |
|
||||||
|
| `-fsh-port` | 4444 | 启动正向Shell服务器端口 |
|
||||||
|
| `-persistence-file` | 无 | Linux持久化目标文件路径 (支持.elf/.sh文件) |
|
||||||
|
| `-win-pe` | 无 | Windows持久化目标PE文件路径 (支持.exe/.dll文件) |
|
||||||
|
| `-keylog-output` | keylog.txt | 键盘记录输出文件路径 |
|
||||||
|
| `-download-url` | 无 | 要下载的文件URL |
|
||||||
|
| `-download-path` | 无 | 下载文件保存路径 |
|
||||||
|
| `-lang` | zh | 语言: zh, en |
|
||||||
|
|
||||||
|
## 常用示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本主机扫描
|
||||||
|
./fscan -h 192.168.1.1/24
|
||||||
|
|
||||||
|
# 指定端口扫描
|
||||||
|
./fscan -h 192.168.1.1 -p 80,443,22,3389
|
||||||
|
|
||||||
|
# 仅存活探测
|
||||||
|
./fscan -h 192.168.1.1/24 -ao
|
||||||
|
|
||||||
|
# 禁用暴力破解,仅服务识别
|
||||||
|
./fscan -h 192.168.1.1/24 -nobr
|
||||||
|
|
||||||
|
# 指定用户名密码
|
||||||
|
./fscan -h 192.168.1.1 -user admin -pwd admin123
|
||||||
|
|
||||||
|
# Web扫描
|
||||||
|
./fscan -u http://192.168.1.1 -proxy http://127.0.0.1:8080
|
||||||
|
|
||||||
|
# 本地插件使用
|
||||||
|
./fscan -local cleaner
|
||||||
|
```
|
||||||
|
|
||||||
## 编译说明
|
## 编译说明
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 基础编译
|
# 基础编译
|
||||||
go build -ldflags="-s -w" -trimpath main.go
|
go build -ldflags="-s -w" -trimpath -o fscan main.go
|
||||||
|
|
||||||
# UPX压缩(可选)
|
# UPX压缩(可选)
|
||||||
upx -9 fscan
|
upx -9 fscan
|
||||||
@ -65,6 +172,18 @@ yay -S fscan-git
|
|||||||
paru -S fscan-git
|
paru -S fscan-git
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 官方网站
|
||||||
|
|
||||||
|
**https://fscan.club/**
|
||||||
|
|
||||||
|
访问官网获取:
|
||||||
|
|
||||||
|
- 详细功能文档
|
||||||
|
- 使用教程
|
||||||
|
- 最新版本下载
|
||||||
|
- 常见问题解答
|
||||||
|
- 技术支持
|
||||||
|
|
||||||
# 0x04 运行截图
|
# 0x04 运行截图
|
||||||
|
|
||||||
`fscan.exe -h 192.168.x.x (全功能、ms17010、读取网卡信息)`
|
`fscan.exe -h 192.168.x.x (全功能、ms17010、读取网卡信息)`
|
||||||
|
@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/IBM/sarama"
|
"github.com/IBM/sarama"
|
||||||
@ -22,16 +23,16 @@ func NewKafkaPlugin() *KafkaPlugin {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
if common.DisableBrute {
|
if common.DisableBrute {
|
||||||
return p.identifyService(ctx, info)
|
return p.identifyService(ctx, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials := GenerateCredentials("kafka")
|
credentials := plugins.GenerateCredentials("kafka")
|
||||||
if len(credentials) == 0 {
|
if len(credentials) == 0 {
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "kafka",
|
Service: "kafka",
|
||||||
Error: fmt.Errorf("没有可用的测试凭据"),
|
Error: fmt.Errorf("没有可用的测试凭据"),
|
||||||
@ -39,10 +40,21 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cred := range credentials {
|
for _, cred := range credentials {
|
||||||
|
// 检查上下文是否已取消
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return &plugins.Result{
|
||||||
|
Success: false,
|
||||||
|
Service: "kafka",
|
||||||
|
Error: ctx.Err(),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
if client := p.testCredential(ctx, info, cred); client != nil {
|
if client := p.testCredential(ctx, info, cred); client != nil {
|
||||||
client.Close()
|
client.Close()
|
||||||
common.LogSuccess(fmt.Sprintf("Kafka %s %s:%s", target, cred.Username, cred.Password))
|
common.LogSuccess(fmt.Sprintf("Kafka %s %s:%s", target, cred.Username, cred.Password))
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: true,
|
Success: true,
|
||||||
Service: "kafka",
|
Service: "kafka",
|
||||||
Username: cred.Username,
|
Username: cred.Username,
|
||||||
@ -51,7 +63,7 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "kafka",
|
Service: "kafka",
|
||||||
Error: fmt.Errorf("未发现弱密码"),
|
Error: fmt.Errorf("未发现弱密码"),
|
||||||
@ -59,7 +71,7 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) sarama.Client {
|
func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred plugins.Credential) sarama.Client {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
timeout := time.Duration(common.Timeout) * time.Second
|
timeout := time.Duration(common.Timeout) * time.Second
|
||||||
|
|
||||||
@ -87,32 +99,71 @@ func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
|
func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
emptyCred := Credential{Username: "", Password: ""}
|
// 尝试无认证连接
|
||||||
|
emptyCred := plugins.Credential{Username: "", Password: ""}
|
||||||
client := p.testCredential(ctx, info, emptyCred)
|
client := p.testCredential(ctx, info, emptyCred)
|
||||||
if client == nil {
|
if client != nil {
|
||||||
return &ScanResult{
|
defer client.Close()
|
||||||
|
banner := "Kafka (无认证)"
|
||||||
|
common.LogSuccess(fmt.Sprintf("Kafka %s %s", target, banner))
|
||||||
|
return &plugins.Result{
|
||||||
|
Success: true,
|
||||||
|
Service: "kafka",
|
||||||
|
Banner: banner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试简单认证检测
|
||||||
|
config := sarama.NewConfig()
|
||||||
|
config.Net.DialTimeout = time.Duration(common.Timeout) * time.Second
|
||||||
|
config.Version = sarama.V2_0_0_0
|
||||||
|
|
||||||
|
brokers := []string{target}
|
||||||
|
client, err := sarama.NewClient(brokers, config)
|
||||||
|
if err != nil {
|
||||||
|
// 如果连接失败,尝试检查是否是Kafka协议错误
|
||||||
|
if p.isKafkaProtocolError(err) {
|
||||||
|
banner := "Kafka (需要认证)"
|
||||||
|
common.LogSuccess(fmt.Sprintf("Kafka %s %s", target, banner))
|
||||||
|
return &plugins.Result{
|
||||||
|
Success: true,
|
||||||
|
Service: "kafka",
|
||||||
|
Banner: banner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "kafka",
|
Service: "kafka",
|
||||||
Error: fmt.Errorf("无法连接到Kafka服务"),
|
Error: fmt.Errorf("无法识别为Kafka服务: %v", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
banner := "Kafka"
|
banner := "Kafka"
|
||||||
common.LogSuccess(fmt.Sprintf("Kafka %s %s", target, banner))
|
common.LogSuccess(fmt.Sprintf("Kafka %s %s", target, banner))
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: true,
|
Success: true,
|
||||||
Service: "kafka",
|
Service: "kafka",
|
||||||
Banner: banner,
|
Banner: banner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isKafkaProtocolError 检查错误是否表示Kafka协议响应
|
||||||
|
func (p *KafkaPlugin) isKafkaProtocolError(err error) bool {
|
||||||
|
errStr := strings.ToLower(err.Error())
|
||||||
|
// Kafka常见的协议错误模式
|
||||||
|
return strings.Contains(errStr, "sasl") ||
|
||||||
|
strings.Contains(errStr, "authentication") ||
|
||||||
|
strings.Contains(errStr, "kafka") ||
|
||||||
|
strings.Contains(errStr, "protocol") ||
|
||||||
|
strings.Contains(errStr, "broker")
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 使用高效注册方式:直接传递端口信息,避免实例创建
|
plugins.RegisterWithPorts("kafka", func() plugins.Plugin {
|
||||||
RegisterPluginWithPorts("kafka", func() Plugin {
|
|
||||||
return NewKafkaPlugin()
|
return NewKafkaPlugin()
|
||||||
}, []int{9092, 9093, 9094})
|
}, []int{9092, 9093, 9094})
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -24,20 +25,30 @@ func NewRabbitMQPlugin() *RabbitMQPlugin {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
if common.DisableBrute {
|
if common.DisableBrute {
|
||||||
return p.identifyService(ctx, info)
|
return p.identifyService(ctx, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于AMQP端口,首先识别服务
|
||||||
if info.Ports == "5672" || info.Ports == "5671" {
|
if info.Ports == "5672" || info.Ports == "5671" {
|
||||||
return p.testAMQPProtocol(ctx, info)
|
if result := p.testAMQPProtocol(ctx, info); result.Success {
|
||||||
|
// AMQP协议识别成功,尝试HTTP管理接口的密码爆破
|
||||||
|
managementResult := p.testManagementInterface(ctx, info)
|
||||||
|
if managementResult.Success {
|
||||||
|
return managementResult
|
||||||
|
}
|
||||||
|
// 返回服务识别结果
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials := GenerateCredentials("rabbitmq")
|
// HTTP端口的密码爆破
|
||||||
|
credentials := plugins.GenerateCredentials("rabbitmq")
|
||||||
if len(credentials) == 0 {
|
if len(credentials) == 0 {
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Error: fmt.Errorf("没有可用的测试凭据"),
|
Error: fmt.Errorf("没有可用的测试凭据"),
|
||||||
@ -45,10 +56,20 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cred := range credentials {
|
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) {
|
if p.testCredential(ctx, info, cred) {
|
||||||
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s:%s", target, cred.Username, cred.Password))
|
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s:%s", target, cred.Username, cred.Password))
|
||||||
|
return &plugins.Result{
|
||||||
return &ScanResult{
|
|
||||||
Success: true,
|
Success: true,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Username: cred.Username,
|
Username: cred.Username,
|
||||||
@ -57,7 +78,7 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Error: fmt.Errorf("未发现弱密码"),
|
Error: fmt.Errorf("未发现弱密码"),
|
||||||
@ -65,15 +86,94 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *ScanResult {
|
// testAMQPProtocol 检测AMQP协议
|
||||||
return &ScanResult{
|
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 连接到AMQP端口
|
||||||
|
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return &plugins.Result{
|
||||||
|
Success: false,
|
||||||
|
Service: "rabbitmq",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
Success: true,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Banner: "RabbitMQ AMQP",
|
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 (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
|
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 {
|
||||||
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@ -104,17 +204,19 @@ func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostIn
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
|
func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
// 对于AMQP端口,检测AMQP协议
|
||||||
|
|
||||||
if info.Ports == "5672" || info.Ports == "5671" {
|
if info.Ports == "5672" || info.Ports == "5671" {
|
||||||
return &ScanResult{
|
return p.testAMQPProtocol(ctx, info)
|
||||||
Success: true,
|
|
||||||
Service: "rabbitmq",
|
|
||||||
Banner: "RabbitMQ AMQP",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于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)
|
||||||
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@ -123,7 +225,7 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI
|
|||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Error: err,
|
Error: err,
|
||||||
@ -132,7 +234,7 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Error: err,
|
Error: err,
|
||||||
@ -146,33 +248,29 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI
|
|||||||
bodyStr := strings.ToLower(string(body))
|
bodyStr := strings.ToLower(string(body))
|
||||||
|
|
||||||
if strings.Contains(bodyStr, "rabbitmq") {
|
if strings.Contains(bodyStr, "rabbitmq") {
|
||||||
banner = "RabbitMQ"
|
banner = "RabbitMQ Management"
|
||||||
} else if strings.Contains(bodyStr, "management") {
|
} else if strings.Contains(bodyStr, "management") {
|
||||||
banner = "RabbitMQ"
|
banner = "RabbitMQ Management"
|
||||||
} else {
|
} else {
|
||||||
banner = "RabbitMQ"
|
banner = "RabbitMQ Management"
|
||||||
|
}
|
||||||
|
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
|
||||||
|
return &plugins.Result{
|
||||||
|
Success: true,
|
||||||
|
Service: "rabbitmq",
|
||||||
|
Banner: banner,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return &ScanResult{
|
return &plugins.Result{
|
||||||
Success: false,
|
Success: false,
|
||||||
Service: "rabbitmq",
|
Service: "rabbitmq",
|
||||||
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
|
Error: fmt.Errorf("无法识别为RabbitMQ服务"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner))
|
|
||||||
|
|
||||||
return &ScanResult{
|
|
||||||
Success: true,
|
|
||||||
Service: "rabbitmq",
|
|
||||||
Banner: banner,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init 自动注册插件
|
|
||||||
func init() {
|
func init() {
|
||||||
// 使用高效注册方式:直接传递端口信息,避免实例创建
|
plugins.RegisterWithPorts("rabbitmq", func() plugins.Plugin {
|
||||||
RegisterPluginWithPorts("rabbitmq", func() Plugin {
|
|
||||||
return NewRabbitMQPlugin()
|
return NewRabbitMQPlugin()
|
||||||
}, []int{5672, 15672, 5671})
|
}, []int{5672, 15672, 5671})
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user