fscan/Plugins/services/redis/connector.go
ZacharyZcR de286026e8 refactor: 统一插件超时机制实现Context-First架构
- 重构SSH/MySQL/Redis插件超时控制,移除第三方库超时依赖
- 统一使用Go Context超时机制,提升超时控制可靠性和精确度
- 扩展MySQL/Redis/SSH插件默认端口支持,提升扫描覆盖率
- 修复插件系统中ConcurrentScanConfig超时配置缺失问题
- 优化插件检测逻辑,正确识别新架构插件并显示准确状态信息
- 解决插件在错误端口上长时间等待问题,显著提升扫描效率
2025-08-07 14:01:50 +08:00

302 lines
7.1 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"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"io"
"net"
"strings"
"time"
)
// RedisConnection Redis连接包装
type RedisConnection struct {
conn net.Conn
authenticated bool
config *RedisConfig
}
// RedisConfig Redis配置信息
type RedisConfig struct {
DBFilename string
Dir string
}
// RedisConnector Redis连接器实现
type RedisConnector struct {
}
// NewRedisConnector 创建Redis连接器
func NewRedisConnector() *RedisConnector {
return &RedisConnector{}
}
// Connect 连接到Redis服务
func (c *RedisConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 使用Context控制超时的TCP连接
timeout := time.Duration(common.Timeout) * time.Second
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
// 创建Redis连接包装
redisConn := &RedisConnection{
conn: conn,
authenticated: false,
config: &RedisConfig{},
}
return redisConn, nil
}
// Authenticate 认证
func (c *RedisConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
redisConn, ok := conn.(*RedisConnection)
if !ok {
return fmt.Errorf("无效的连接类型")
}
// 如果没有密码,先检查未授权访问
if cred == nil || cred.Password == "" {
return c.checkUnauthorizedAccess(redisConn)
}
// 有密码的情况下进行认证
return c.authenticateWithPassword(redisConn, cred.Password)
}
// Close 关闭连接
func (c *RedisConnector) Close(conn interface{}) error {
if redisConn, ok := conn.(*RedisConnection); ok {
if redisConn.conn != nil {
return redisConn.conn.Close()
}
}
return nil
}
// checkUnauthorizedAccess 检查未授权访问
func (c *RedisConnector) checkUnauthorizedAccess(conn *RedisConnection) error {
// 发送INFO命令测试
if err := c.sendCommand(conn, "INFO"); err != nil {
return fmt.Errorf("发送INFO命令失败: %v", err)
}
// 读取响应
response, err := c.readResponse(conn)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
// 检查是否包含Redis版本信息
if !strings.Contains(response, "redis_version") {
return fmt.Errorf("未发现Redis未授权访问")
}
// 获取配置信息
if err := c.getConfig(conn); err != nil {
common.LogDebug(fmt.Sprintf("获取Redis配置失败: %v", err))
}
conn.authenticated = true
return nil
}
// authenticateWithPassword 使用密码认证
func (c *RedisConnector) authenticateWithPassword(conn *RedisConnection, password string) error {
// 发送AUTH命令
authCmd := fmt.Sprintf("AUTH %s", password)
if err := c.sendCommand(conn, authCmd); err != nil {
return fmt.Errorf("发送AUTH命令失败: %v", err)
}
// 读取响应
response, err := c.readResponse(conn)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
// 检查认证结果
if !strings.Contains(response, "+OK") {
return fmt.Errorf("认证失败")
}
// 获取配置信息
if err := c.getConfig(conn); err != nil {
common.LogDebug(fmt.Sprintf("获取Redis配置失败: %v", err))
}
conn.authenticated = true
return nil
}
// sendCommand 发送Redis命令
func (c *RedisConnector) sendCommand(conn *RedisConnection, command string) error {
// 使用统一的超时设置
timeout := time.Duration(common.Timeout) * time.Second
conn.conn.SetWriteDeadline(time.Now().Add(timeout))
// 发送命令添加CRLF
_, err := conn.conn.Write([]byte(command + "\r\n"))
return err
}
// readResponse 读取Redis响应
func (c *RedisConnector) readResponse(conn *RedisConnection) (string, error) {
// 使用统一的超时设置
timeout := time.Duration(common.Timeout) * time.Second
conn.conn.SetReadDeadline(time.Now().Add(timeout))
// 读取所有数据
data, err := io.ReadAll(conn.conn)
if len(data) > 0 {
// 如果读到数据忽略EOF错误
err = nil
}
return string(data), err
}
// getConfig 获取Redis配置
func (c *RedisConnector) getConfig(conn *RedisConnection) error {
// 获取数据库文件名
if err := c.sendCommand(conn, "CONFIG GET dbfilename"); err != nil {
return err
}
response, err := c.readResponse(conn)
if err != nil {
return err
}
// 解析响应
lines := strings.Split(response, "\r\n")
if len(lines) > 2 {
conn.config.DBFilename = lines[len(lines)-2]
}
// 获取数据库目录
if err := c.sendCommand(conn, "CONFIG GET dir"); err != nil {
return err
}
response, err = c.readResponse(conn)
if err != nil {
return err
}
// 解析响应
lines = strings.Split(response, "\r\n")
if len(lines) > 2 {
conn.config.Dir = lines[len(lines)-2]
}
return nil
}
// =============================================================================
// Redis操作辅助函数
// =============================================================================
// ExecuteCommand 执行Redis命令
func (c *RedisConnector) ExecuteCommand(conn *RedisConnection, command string) (string, error) {
if !conn.authenticated {
return "", fmt.Errorf("连接未认证")
}
if err := c.sendCommand(conn, command); err != nil {
return "", err
}
return c.readResponse(conn)
}
// SetConfig 设置Redis配置
func (c *RedisConnector) SetConfig(conn *RedisConnection, key, value string) error {
if !conn.authenticated {
return fmt.Errorf("连接未认证")
}
command := fmt.Sprintf("CONFIG SET %s %s", key, value)
if err := c.sendCommand(conn, command); err != nil {
return err
}
response, err := c.readResponse(conn)
if err != nil {
return err
}
if !strings.Contains(response, "OK") {
return fmt.Errorf("设置配置失败: %s", response)
}
return nil
}
// SetKey 设置Redis键值
func (c *RedisConnector) SetKey(conn *RedisConnection, key, value string) error {
if !conn.authenticated {
return fmt.Errorf("连接未认证")
}
command := fmt.Sprintf("SET %s \"%s\"", key, value)
if err := c.sendCommand(conn, command); err != nil {
return err
}
response, err := c.readResponse(conn)
if err != nil {
return err
}
if !strings.Contains(response, "OK") {
return fmt.Errorf("设置键值失败: %s", response)
}
return nil
}
// Save 保存Redis数据
func (c *RedisConnector) Save(conn *RedisConnection) error {
if !conn.authenticated {
return fmt.Errorf("连接未认证")
}
if err := c.sendCommand(conn, "SAVE"); err != nil {
return err
}
response, err := c.readResponse(conn)
if err != nil {
return err
}
if !strings.Contains(response, "OK") {
return fmt.Errorf("保存数据失败: %s", response)
}
return nil
}
// RestoreConfig 恢复Redis配置
func (c *RedisConnector) RestoreConfig(conn *RedisConnection, originalConfig *RedisConfig) error {
if originalConfig.DBFilename != "" {
if err := c.SetConfig(conn, "dbfilename", originalConfig.DBFilename); err != nil {
return fmt.Errorf("恢复dbfilename失败: %v", err)
}
}
if originalConfig.Dir != "" {
if err := c.SetConfig(conn, "dir", originalConfig.Dir); err != nil {
return fmt.Errorf("恢复dir失败: %v", err)
}
}
return nil
}