package services import ( "context" "fmt" "io" "net" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" ) // RedisPlugin Redis数据库扫描和利用插件 - 包含文件写入利用功能 type RedisPlugin struct { plugins.BasePlugin } // NewRedisPlugin 创建Redis插件 func NewRedisPlugin() *RedisPlugin { return &RedisPlugin{ BasePlugin: plugins.NewBasePlugin("redis"), } } // Scan 执行Redis扫描 - 未授权访问检测和弱密码检测 func (p *RedisPlugin) 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) } // 首先检查未授权访问 if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { common.LogSuccess(i18n.GetText("redis_unauth_success", target)) return result } // 生成测试凭据 credentials := GenerateCredentials("redis") if len(credentials) == 0 { // Redis默认凭据 credentials = []Credential{ {Username: "", Password: ""}, {Username: "", Password: "redis"}, {Username: "", Password: "password"}, {Username: "", Password: "123456"}, {Username: "", Password: "admin"}, } } // 逐个测试凭据 for _, cred := range credentials { // 检查Context是否被取消 select { case <-ctx.Done(): return &ScanResult{ Success: false, Service: "redis", Error: ctx.Err(), } default: } // 测试凭据 if conn := p.testCredential(ctx, info, cred); conn != nil { conn.Close() // 关闭测试连接 // Redis认证成功 common.LogSuccess(i18n.GetText("redis_scan_success", target, cred.Password)) return &ScanResult{ Success: true, Service: "redis", Username: cred.Username, Password: cred.Password, } } } // 所有凭据都失败 return &ScanResult{ Success: false, Service: "redis", Error: fmt.Errorf("未发现弱密码或未授权访问"), } } // testUnauthorizedAccess 测试未授权访问 func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { // 尝试无密码连接 emptyCred := Credential{Username: "", Password: ""} if conn := p.testCredential(ctx, info, emptyCred); conn != nil { conn.Close() return &ScanResult{ Success: true, Service: "redis", Banner: "未授权访问", } } return nil } // testCredential 测试单个凭据 - 返回Redis连接或nil func (p *RedisPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) net.Conn { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second // 使用Context控制超时的连接 type connResult struct { conn net.Conn err error } connChan := make(chan connResult, 1) go func() { // 建立TCP连接 conn, err := net.DialTimeout("tcp", target, timeout) if err != nil { connChan <- connResult{nil, err} return } // 如果有密码,进行认证 if cred.Password != "" { authCmd := fmt.Sprintf("AUTH %s\r\n", cred.Password) conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(authCmd)); err != nil { conn.Close() connChan <- connResult{nil, err} return } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) if err != nil || !strings.Contains(string(response[:n]), "+OK") { conn.Close() connChan <- connResult{nil, fmt.Errorf("认证失败")} return } } // 发送PING命令测试连接 pingCmd := "PING\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(pingCmd)); err != nil { conn.Close() connChan <- connResult{nil, err} return } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) if err != nil || !strings.Contains(string(response[:n]), "PONG") { conn.Close() connChan <- connResult{nil, fmt.Errorf("PING测试失败")} return } connChan <- connResult{conn, nil} }() // 等待连接结果或超时 select { case result := <-connChan: if result.err != nil { return nil } return result.conn case <-ctx.Done(): return nil } } // getRedisInfo 获取Redis服务器信息 func (p *RedisPlugin) getRedisInfo(conn net.Conn) string { timeout := time.Duration(common.Timeout) * time.Second // 发送INFO命令 infoCmd := "INFO server\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(infoCmd)); err != nil { return "" } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 2048) n, err := conn.Read(response) if err != nil { return "" } responseStr := string(response[:n]) lines := strings.Split(responseStr, "\r\n") var info strings.Builder for _, line := range lines { if strings.HasPrefix(line, "redis_version:") || strings.HasPrefix(line, "redis_mode:") || strings.HasPrefix(line, "os:") || strings.HasPrefix(line, "arch_bits:") { info.WriteString(line + "\n") } } return info.String() } // getRedisKeys 获取Redis键列表 func (p *RedisPlugin) getRedisKeys(conn net.Conn) []string { timeout := time.Duration(common.Timeout) * time.Second // 发送KEYS命令获取所有键 keysCmd := "KEYS *\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(keysCmd)); err != nil { return nil } conn.SetReadDeadline(time.Now().Add(timeout)) response, err := io.ReadAll(conn) if err != nil { return nil } responseStr := string(response) lines := strings.Split(responseStr, "\r\n") var keys []string for _, line := range lines { line = strings.TrimSpace(line) if line != "" && !strings.HasPrefix(line, "*") && !strings.HasPrefix(line, "$") && line != "+OK" { keys = append(keys, line) } } return keys } // getKeyValue 获取键的值 func (p *RedisPlugin) getKeyValue(conn net.Conn, key string) string { timeout := time.Duration(common.Timeout) * time.Second // 发送GET命令 getCmd := fmt.Sprintf("GET %s\r\n", key) conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(getCmd)); err != nil { return "[error]" } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) if err != nil { return "[error]" } responseStr := string(response[:n]) lines := strings.Split(responseStr, "\r\n") if len(lines) > 1 && lines[1] != "" { if len(lines[1]) > 50 { return lines[1][:50] + "..." } return lines[1] } return "[empty]" } // getRedisConfig 获取Redis配置信息 func (p *RedisPlugin) getRedisConfig(conn net.Conn) string { timeout := time.Duration(common.Timeout) * time.Second // 获取关键配置 configs := []string{"dir", "dbfilename", "save", "requirepass"} var result strings.Builder for _, config := range configs { configCmd := fmt.Sprintf("CONFIG GET %s\r\n", config) conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(configCmd)); err != nil { continue } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 1024) n, err := conn.Read(response) if err != nil { continue } responseStr := string(response[:n]) lines := strings.Split(responseStr, "\r\n") if len(lines) > 3 && lines[3] != "" { result.WriteString(fmt.Sprintf("%s: %s\n", config, lines[3])) } } return result.String() } // testFileWrite 测试文件写入功能 func (p *RedisPlugin) testFileWrite(conn net.Conn) string { timeout := time.Duration(common.Timeout) * time.Second // 尝试设置一个测试键 setCmd := "SET fscan_test \"FScan Security Test\"\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(setCmd)); err != nil { return "❌ 无写权限: " + err.Error() } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) if err != nil || !strings.Contains(string(response[:n]), "OK") { return "❌ 设置键值失败" } // 删除测试键 delCmd := "DEL fscan_test\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) conn.Write([]byte(delCmd)) return "✅ 具有读写权限,可进行文件写入利用" } // identifyService 服务识别 - 检测Redis服务 func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second // 尝试连接Redis服务 conn, err := net.DialTimeout("tcp", target, timeout) if err != nil { return &ScanResult{ Success: false, Service: "redis", Error: err, } } defer conn.Close() // 发送PING命令识别 pingCmd := "PING\r\n" conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(pingCmd)); err != nil { return &ScanResult{ Success: false, Service: "redis", Error: err, } } conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) if err != nil { return &ScanResult{ Success: false, Service: "redis", Error: err, } } responseStr := string(response[:n]) var banner string if strings.Contains(responseStr, "PONG") { banner = "Redis服务 (PONG响应)" } else if strings.Contains(responseStr, "-NOAUTH") { banner = "Redis服务 (需要认证)" } else if strings.Contains(responseStr, "-ERR") { banner = "Redis服务 (协议响应)" } else { banner = "Redis服务" } common.LogSuccess(i18n.GetText("redis_service_identified", target, banner)) return &ScanResult{ Success: true, Service: "redis", Banner: banner, } } // init 自动注册插件 func init() { // 使用高效注册方式:直接传递端口信息,避免实例创建 RegisterPluginWithPorts("redis", func() Plugin { return NewRedisPlugin() }, []int{6379, 6380, 6381, 16379, 26379}) }