CVE-2025-29923 - go-redis Vulnerability Allows Out-of-Order Responses—How It Happens, Example Code, and How to Fix It

go-redis is the official Redis client for the Go language—widely used for building robust, scalable services. In early 2025, a vulnerability was discovered and fixed in go-redis affecting versions before 9.5.5, 9.6.3, and 9.7.3. This post is a practical, step-by-step explanation of the problem tracked as CVE-2025-29923, how to reproduce it, its impact, and how to secure your apps.

In Short: What Is CVE-2025-29923?

In some workloads, go-redis responds *out of order* if CLIENT SETINFO times out during connection establishment, possibly leaking one command's response to a different request. This crates confusing, buggy, and potentially dangerous behavior.

Attackers can leverage network issues, aggressive timeout settings, or make use of pipeline features to cause persistent response mismatch.

Technical Background: How Out-of-Order Responses Happen

When a client connects to Redis, go-redis (by default) sends its identity with CLIENT SETINFO. If the command *hangs* or times out, the library doesn’t properly clear the connection buffer. This leads to the following observed behaviors:
- With sticky connections, garbled responses persist: every command in the pipeline gets mismatched data.
- With the default ConnPool, after use, the next time the connection is checked, unread data marks the connection as “bad” and it is closed—but *one* bad out-of-order response can still occur before it's discarded.

Exploit Example: Triggering Out-of-Order Responses

Let’s see this in code. This Go snippet demonstrates the issue by simulating network latency causing CLIENT SETINFO to time out.

Vulnerable Example

*Assume you use go-redis < 9.5.5, and the network is flaky...*

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:         "localhost:6379",
        DialTimeout:  100 * time.Millisecond, // Aggressive timeout
        WriteTimeout: 100 * time.Millisecond,
        ReadTimeout:  100 * time.Millisecond,
    })

    ctx := context.Background()

    // Simulate connection problems
    for i := ; i < 3; i++ {
        go func(id int) {
            // Pipelined commands, each expecting a string response
            pipe := rdb.Pipeline()

            pipe.Get(ctx, "key1")
            pipe.Get(ctx, "key2")
            pipe.Get(ctx, "key3")

            res, err := pipe.Exec(ctx)
            if err != nil {
                fmt.Printf("Worker %d: error %v\n", id, err)
            } else {
                for j, cmd := range res {
                    fmt.Printf("Worker %d: Command %d result: %v\n", id, j, cmd)
                }
            }
        }(i)
    }

    time.Sleep(2 * time.Second) // Let goroutines finish
}

Expected: Each response matches its command.
With CVE-2025-29923: You might get garbled, mismatched responses—e.g., the value for key2 comes where key3 was expected, or vice versa.

If you're using sticky connections or persistent pipelines, all subsequent commands may return corrupt data until the connection is dropped.

Exploit Details

- Attack Vector: Force CLIENT SETINFO to time out by using slow or packet-dropping network or by setting low timeout values.
- Impact: Pipeline commands return the wrong results. Sticky connections keep returning bad responses for their whole lifetime. For pool connections, next user gets one mismatched response before go-redis detects the problem and closes the connection.
- Risk: While not a direct remote code execution or data breach, attackers could disrupt logic, causing apps to process the wrong data, such as leaking user data across sessions, corrupting state, or triggering false application errors.


## The Fix: Upgrade and/or Configure

1. Upgrade

The issue is fixed in go-redis 9.5.5, 9.6.3, and 9.7.3.

Upgrade immediately

go get github.com/redis/go-redis/v9@v9.7.3
go mod tidy

2. Mitigate (if you cannot upgrade)

If you cannot upgrade, you can set the DisableIdentity flag to true, so no identity is sent, and the vulnerable behavior does not occur:

client := redis.NewClient(&redis.Options{
    Addr:           "localhost:6379",
    DisableIdentity: true,
    // other options...
})

Note: As DisableIdentity is a new field, this only works on some older versions.

Official References

- CVE-2025-29923 at NVD (to be published)
- Github Advisory *(check for proper ID after publication)*
- go-redis Releases
- CLIENT SETINFO Command Documentation

Final Advice

CVE-2025-29923 can creep up in production, especially under heavy load or poor networks, and cause subtle but severe data consistency problems. Always stay up-to-date, monitor your Redis client connection logs for unexpected errors, and (if possible) set DisableIdentity if you don’t need client identification.

By understanding and addressing this vulnerability quickly, you protect your data integrity and keep your Go + Redis apps healthy and secure!

*If you found this post useful or have questions about applied security in Go, let me know!*

Timeline

Published on: 03/20/2025 18:15:19 UTC