fscan/Plugins/services/redis/plugin.go
ZacharyZcR 51735c4e25 feat: 实现-nobr/-ne参数和服务识别功能
新增功能:
- 添加-ne参数禁用利用攻击,实现弱密码检测和利用攻击的分离控制
- 实现-nobr模式下所有插件的服务识别功能,单数据包获取最大信息
- 修复端口插件匹配逻辑,只调用适配端口的插件提升扫描效率

插件改造:
- MySQL: 通过握手包识别获取版本信息
- Redis: INFO命令识别版本,优先检测未授权访问
- SSH: Banner识别获取协议和服务器版本
- ActiveMQ: STOMP协议识别获取版本和认证状态

技术改进:
- 新增端口匹配算法确保精准插件调用
- 完善i18n国际化支持所有新功能
- 统一服务识别接口设计便于扩展
2025-08-08 03:32:00 +08:00

282 lines
8.4 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 redis
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// Redis插件展示如何实现未授权访问检测和弱密码爆破
// 作为NoSQL数据库插件的标准参考实现
// 重点展示了自定义扫描逻辑和未授权访问检测模式
// RedisPlugin Redis插件实现
type RedisPlugin struct {
*base.ServicePlugin
exploiter *RedisExploiter
}
// NewRedisPlugin 创建Redis插件
func NewRedisPlugin() *RedisPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "redis",
Version: "2.0.0",
Author: "fscan-team",
Description: "Redis数据库扫描和利用插件",
Category: "service",
Ports: []int{6379, 6380, 6381, 16379, 26379}, // Redis常用端口包括默认端口、集群端口和备用端口
Protocols: []string{"tcp"},
Tags: []string{"database", "redis", "bruteforce", "exploit", "unauthorized"},
}
// 创建连接器和服务插件
connector := NewRedisConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建Redis插件
plugin := &RedisPlugin{
ServicePlugin: servicePlugin,
exploiter: NewRedisExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapUnauthorized,
base.CapFileWrite,
base.CapCommandExecution,
base.CapDataExtraction,
base.CapInformationLeak,
})
return plugin
}
// Scan 重写扫描方法以支持未授权访问检测和后续利用
func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogDebug(i18n.GetText("redis_scan_start", target))
// 先检查未授权访问
unauthorizedResult := p.checkUnauthorizedAccess(ctx, info)
if unauthorizedResult != nil && unauthorizedResult.Success {
common.LogSuccess(i18n.GetText("redis_unauth_success", target))
// 如果启用了利用功能,执行自动利用
if !common.DisableExploit { // 使用DisableExploit控制利用功能
go p.autoExploit(context.Background(), info, nil) // 未授权访问不需要凭据
}
return unauthorizedResult, nil
}
// 如果未授权访问失败,在-nobr模式下进行基础服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
// 执行基础的暴力破解扫描
result, err := p.ServicePlugin.Scan(ctx, info)
if err != nil || !result.Success {
return result, err
}
common.LogSuccess(i18n.GetText("redis_weak_pwd_success",
target, result.Credentials[0].Password))
// 如果扫描成功并且启用了利用功能,执行自动利用
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
go p.autoExploit(context.Background(), info, result.Credentials[0])
}
return result, nil
}
// checkUnauthorizedAccess 检查未授权访问
func (p *RedisPlugin) checkUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *base.ScanResult {
conn, err := p.ServicePlugin.GetServiceConnector().Connect(ctx, info)
if err != nil {
return nil
}
defer p.ServicePlugin.GetServiceConnector().Close(conn)
// 尝试无密码认证
err = p.ServicePlugin.GetServiceConnector().Authenticate(ctx, conn, nil)
if err != nil {
return nil
}
// 未授权访问成功
return &base.ScanResult{
Success: true,
Service: "redis",
Credentials: []*base.Credential{}, // 未授权访问无凭据
Vulnerabilities: []base.Vulnerability{
{
ID: "REDIS-UNAUTH",
Name: "Redis未授权访问",
Severity: "High",
Description: "Redis服务允许未授权访问攻击者可以读取、修改数据或执行命令",
References: []string{"https://redis.io/topics/security"},
},
},
Extra: make(map[string]interface{}),
}
}
// autoExploit 自动利用
func (p *RedisPlugin) 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", "Redis", target))
// 执行利用
result, err := p.exploiter.Exploit(ctx, info, creds)
if err != nil {
common.LogError(i18n.GetText("plugin_exploit_failed", "Redis", err))
return
}
if result != nil && result.Success {
common.LogSuccess(i18n.GetText("plugin_exploit_success", "Redis", result.Method))
base.SaveExploitResult(info, result, "Redis")
}
}
// Exploit 手动利用接口
func (p *RedisPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *RedisPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *RedisPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// generateCredentials 重写凭据生成方法Redis只需要密码
func (p *RedisPlugin) generateCredentials() []*base.Credential {
// Redis通常只需要密码不需要用户名
return base.GeneratePasswordOnlyCredentials(common.Passwords)
}
// performServiceIdentification 执行Redis服务识别-nobr模式
func (p *RedisPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接到Redis服务
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer conn.Close()
// 发送INFO命令获取Redis服务器信息
redisInfo, isRedis := p.identifyRedisService(conn)
if isRedis {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("redis_service_identified", target, redisInfo))
return &base.ScanResult{
Success: true,
Service: "Redis",
Banner: redisInfo,
Extra: map[string]interface{}{
"service": "Redis",
"port": info.Ports,
"info": redisInfo,
},
}, nil
}
// 如果无法识别为Redis返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为Redis服务"),
}, nil
}
// identifyRedisService 通过INFO命令识别Redis服务
func (p *RedisPlugin) identifyRedisService(conn net.Conn) (string, bool) {
// 发送INFO命令
infoCmd := "INFO server\r\n"
conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if _, err := conn.Write([]byte(infoCmd)); err != nil {
return "", false
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
response := make([]byte, 2048)
n, err := conn.Read(response)
if err != nil || n < 10 {
return "", false
}
responseStr := string(response[:n])
// 检查是否为Redis响应
if strings.Contains(responseStr, "redis_version:") {
// 提取Redis版本信息
lines := strings.Split(responseStr, "\r\n")
for _, line := range lines {
if strings.HasPrefix(line, "redis_version:") {
version := strings.TrimPrefix(line, "redis_version:")
return fmt.Sprintf("Redis版本: %s", version), true
}
}
return "Redis服务版本未知", true
} else if strings.Contains(responseStr, "-NOAUTH") {
// 需要认证的Redis
return "Redis服务需要认证", true
} else if strings.Contains(responseStr, "+PONG") || strings.Contains(responseStr, "$") {
// 通过RESP协议特征识别
return "Redis服务", true
}
return "", false
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterRedisPlugin 注册Redis插件
func RegisterRedisPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "redis",
Version: "2.0.0",
Author: "fscan-team",
Description: "Redis数据库扫描和利用插件",
Category: "service",
Ports: []int{6379, 6380, 6381, 16379, 26379},
Protocols: []string{"tcp"},
Tags: []string{"database", "redis", "bruteforce", "exploit", "unauthorized"},
},
func() base.Plugin {
return NewRedisPlugin()
},
)
base.GlobalPluginRegistry.Register("redis", factory)
}
// 自动注册
func init() {
RegisterRedisPlugin()
}