Someone left a loaded gun in the Redis client-unblocking code, and a full PoC just picked it up
CVE-2026-23479 is a use-after-free in Redis's unblockClientOnKey() function in src/blocked.c. When a blocked client (e.g., one waiting on XREAD BLOCK) is evicted due to maxmemory-clients pressure during command re-execution, processCommandAndResetClient() frees the client structure — but the caller never checks the return value indicating the client was freed, and proceeds to access the dangling pointer. An authenticated attacker with @admin + @scripting + @stream + @read/@write ACL permissions can groom the heap, reclaim the freed allocation with a crafted fake client structure, and ultimately achieve arbitrary command execution as the redis-server process. All Redis versions from 7.2.0 through 8.6.2 are affected across the 7.2.x, 7.4.x, 8.2.x, 8.4.x, and 8.6.x branches. Fixed versions shipped May 5, 2026: 7.2.14, 7.4.9, 8.2.6, 8.4.3, 8.6.3, plus a backport to 6.2.22. Redis Software (enterprise) is fixed in 8.0.10-64, 7.22.2-79, 7.8.6-253, 7.4.6-279, 7.2.4-153.
The NVD assigned CVSS 3.1 8.8 with AC:L, implying this is trivially exploitable by any low-privileged user. That overstates reality. Red Hat independently rescored to 7.5 with AC:H, noting *'a highly specific sequence of events and time-dependent conditions that are not directly in control of an attacker must occur.'* Redis's own GHSA uses CVSS 4.0 and arrived at 7.7. Both of those are closer to the truth. The chain is real — a weaponized Python PoC from v12-security achieves full RCE even under full RELRO — but it requires authenticated access with admin-class permissions, heap grooming across multiple stages, and precise timing of the eviction race. In default configurations without ACLs, the default user has every permission needed, which keeps the floor firmly at HIGH. But calling this AC:L misrepresents the exploitation engineering involved.
6 steps from start to impact.
Authenticated session with privileged ACLs
@admin, @scripting, @stream, @read, and @write — all permissions required for the chain. In ACL-hardened deployments, the attacker needs a credential mapped to these categories.- Network reachability to Redis TCP port
- Valid credential with @admin, @scripting, @stream, @read, @write permissions
- Enterprise deployments typically bind Redis to localhost or internal VLANs — fewer than 5% of enterprise Redis instances are internet-facing
- ACL-hardened deployments restrict CONFIG SET and EVAL to ops accounts, not application service accounts
product:Redis on port 6379; internal asset inventory should flag any internet-exposed Redis instancesInformation leak via Lua evaluation
EVAL "return tostring(redis.call)" 0 to extract a heap pointer from the Lua function's string representation. This provides an address anchor for subsequent heap arithmetic, defeating ASLR. The @scripting permission is required.- Authenticated session from step 1
- EVAL command permitted
- Requires @scripting ACL — commonly restricted in production ACL policies
- Some Redis deployments disable Lua scripting entirely via
rename-command EVAL
EVAL commands containing tostring(redis.call) — this pattern has no legitimate useHeap grooming and memory-limit manipulation
CONFIG SET maxmemory-clients to first inflate and then sharply reduce the client memory limit. This creates conditions for a target blocked client to be evicted during command re-execution. Multiple SET and GET commands groom the heap to place attacker-controlled data adjacent to the client allocation.- CONFIG SET access (@admin ACL)
- Ability to issue rapid sequences of SET/GET commands
- CONFIG SET maxmemory-clients is a runtime configuration change that may trigger alerts in config-drift monitoring
- Heap layout must be deterministic — containerized or multi-tenant Redis may have unpredictable allocation patterns
Trigger use-after-free via blocked client eviction
XREAD BLOCK 0, then simultaneously drops maxmemory-clients to 1 and wakes the stream via XADD. During unblockClientOnKey(), processCommandAndResetClient() frees the blocked client as a side-effect of eviction, but the function continues accessing the dangling pointer. A pipelined SET command reclaims the freed allocation with attacker-controlled data forming a fake client structure.- Successful heap grooming from step 3
- Precise timing of XADD and CONFIG SET to trigger eviction during re-execution
- This is a race condition — Red Hat explicitly notes 'time-dependent conditions that are not directly in control of an attacker'
- The v12-security PoC handles this reliably on single-threaded Redis, but multi-instance or Sentinel setups may interfere
handleClientsBlockedOnKey or unblockClientOnKey; unexpected redis-server restartsArbitrary write via updateClientMemoryUsage
last_memory_type (array index) and last_memory_usage (decrement value). When updateClientMemoryUsage() runs server.stat_clients_type_memory[c->last_memory_type] -= c->last_memory_usage, it produces an arbitrary 8-byte decrement relative to the server global in the .data section. Under partial RELRO (default in official Docker images), the GOT is writable, allowing corruption of function pointers.- Successful use-after-free reclaim from step 4
- Partial RELRO on the redis-server binary (default for official Docker/package builds)
- Full RELRO binaries (some hardened distros) block GOT overwrites — BUT the v12-security PoC claims to work 'with full RELRO' via an alternative code path through the command lookup dictionary
- Stack canaries and other mitigations may require additional bypass steps
Remote code execution as redis-server process
strcasecmp() GOT entry (or the command lookup dictionary function pointer under full RELRO) to point at system(). Subsequent Redis command parsing triggers arbitrary OS command execution in the context of the redis-server process. From here, the attacker can exfiltrate all cached data, pivot laterally, or establish persistence.- Successful arbitrary write from step 5
- Redis typically runs as a dedicated
redisuser, not root — limits immediate host-level impact - SELinux/AppArmor profiles on RHEL/Ubuntu may confine post-exploitation actions
- Container-based Redis (Docker, K8s) limits blast radius to the container unless escape is chained
CONFIG SET (@admin) and EVAL (@scripting). Strip these from all application service accounts and grant them only to dedicated ops identities authenticated via strong credentials. This breaks the chain at step 1 for most attacker scenarios. Deploy within the noisgate mitigation SLA of 30 days for HIGH severity.127.0.0.1 or a private VLAN interface via bind directive. Layer network ACLs (security groups, iptables, firewall policies) to restrict port 6379 access to known application hosts only. This eliminates the internet-facing attack surface entirely and limits internal exposure to compromised application-tier hosts. Deploy within 30 days.protected-mode yes and configure requirepass or ACL-based authentication. This prevents unauthenticated exploitation — relevant because ~11% of internet-facing Redis instances accept unauthenticated connections. Combined with ACL restrictions, this reduces the attacker population to credential-holding insiders. Deploy within 30 days.rename-command EVAL "" and rename-command CONFIG "" in redis.conf to disable the two commands essential to the exploitation chain. This is a blunt but effective measure for instances that do not use Lua scripting or runtime config changes. Verify application compatibility before deploying. Deploy within 30 days.- TLS encryption on the Redis port — TLS protects data in transit but does not prevent an authenticated attacker from executing the exploit chain over an encrypted connection. The vulnerability is post-authentication.
- Redis Sentinel or Cluster mode — HA configurations do not mitigate the underlying use-after-free. Each node in the cluster/sentinel group runs the vulnerable code independently. Failover may mask a crash but does not prevent exploitation.
- Container isolation alone — Running Redis in Docker/K8s limits post-exploitation lateral movement but does not prevent the RCE itself. The attacker still achieves code execution within the container and can access all data Redis holds. Network policies within the orchestrator are needed as a complementary control.
The supporting signals.
| In-the-Wild Exploitation | No evidence. Redis vendor states *'as of this publication we have no evidence of exploitation of these vulnerabilities at Redis or in customer environments.'* Not listed on CISA KEV. No GreyNoise tags observed for this CVE. |
|---|---|
| Proof-of-Concept | Full weaponized PoC available. v12-security/pocs on GitHub — Python 3, achieves RCE as redis-server process, works with full RELRO, requires only TCP command interface. No Metasploit module or nuclei template found. PoC was not indexed on pocindex.io at time of review. |
| EPSS | 0.01506 (~1.5th percentile) — low predicted exploitation probability, consistent with the authenticated + high-complexity attack chain |
| KEV Status | Not listed. No CISA KEV entry as of 2026-09-27. |
| CVSS Vectors | NVD: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H → 8.8. Red Hat: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H → 7.5. GHSA (CVSS 4.0): 7.7. The AC:L vs AC:H disagreement is the key divergence — Red Hat's AC:H is more defensible given the multi-stage heap manipulation required. |
| Affected Versions | Introduced in Redis 7.2.0. Affects 7.2.0–7.2.13, 7.4.0–7.4.8, 8.2.0–8.2.5, 8.4.0–8.4.2, 8.6.0–8.6.2. Redis < 7.2.0 (including 6.x, 7.0.x) is *not* vulnerable — the code path was introduced in 7.2. |
| Fixed Versions | OSS/CE: 6.2.22, 7.2.14, 7.4.9, 8.2.6, 8.4.3, 8.6.3. Redis Software: 8.0.10-64, 7.22.2-79, 7.8.6-253, 7.4.6-279, 7.2.4-153. Debian bookworm: 7.0.15-1~deb12u10 (not affected — ships 7.0.x). Debian sid: 8.0.6-3. Redis Cloud: auto-patched, no customer action. |
| Internet Exposure | Censys reports ~350,675 internet-accessible Redis services across 260,534 hosts; ~11% (39,405) require no authentication. However, enterprise Redis is overwhelmingly internal — Shodan/Censys exposure is dominated by dev instances and cloud misconfigurations. |
| Disclosure & Credit | Disclosed 2026-05-05 by Redis. Discovered by Team Xint Code (Tim Becker @tjbecker, Jacob Newman, Juno IM) through the Wiz ZeroDay.Cloud program. |
| Researcher PoC Author | Weaponized PoC by v12-security (published on GitHub). Separate from the original discovery team. |
Sources.
Why this verdict
- AC:H is correct, not AC:L. The chain requires four distinct stages — info leak, heap grooming, race-condition trigger, and GOT/dict corruption — each with specific timing and memory-layout dependencies. Red Hat's AC:H assessment at 7.5 better reflects the engineering effort, even though a PoC automates it.
- Authentication is a real gate but a weak one in defaults. The chain requires
@admin+@scripting+@streampermissions, which the default user has in unconfigured Redis. In ACL-hardened deployments, application service accounts rarely hold@admin. This narrows the reachable population to default-config and weakly-configured instances — a substantial fraction but not universal. - No in-the-wild exploitation, no KEV, low EPSS (1.5%). Five months post-disclosure with a public PoC and still no observed exploitation suggests the complexity barrier is real. The PoC exists but hasn't been operationalized in campaigns.
- Network exposure is mostly internal. Enterprise Redis is overwhelmingly bound to internal interfaces. Censys's 350K internet-facing count is dominated by dev/cloud misconfigs, not enterprise production. The attacker position is typically post-initial-access for enterprise targets.
- Role multiplier: Production data tier. Redis is canonically deployed as a production cache, session store, and message broker — fitting the *'production database engines on data tiers'* high-value role category. In this role, RCE yields host-level compromise + access to all cached sessions/tokens/application data + lateral pivot to the application tier. This blast radius at the host-to-tenant level floors the verdict at HIGH regardless of friction.
- Role multiplier: CI/CD and container infrastructure. Redis is commonly deployed in Kubernetes clusters and CI pipelines. Compromising Redis in these roles can enable lateral movement within the orchestration layer. However, container isolation limits the blast radius to the pod/container unless a container escape is chained, which is an additional prerequisite not part of this CVE. This role supports the HIGH floor but does not elevate to CRITICAL.
Why not higher?
The chain does not reach CRITICAL because Redis is not *canonically* an identity-tier, hypervisor, or supply-chain component — it is a data-tier cache/broker. RCE on a Redis host yields host-to-tenant blast radius (session data, cached tokens), not domain/fleet/supply-chain scale. The authentication requirement with admin-class permissions, combined with high attack complexity and the predominantly internal network posture, prevents this from being a 'patch tonight' emergency. No in-the-wild exploitation after five months with a public PoC reinforces that the theoretical severity exceeds the practical risk.
Why not lower?
A weaponized PoC achieving full RCE exists and is publicly available. Default Redis configurations grant all required permissions to the default user — meaning a significant fraction of real deployments are exploitable as-is by any authenticated user. Redis holds production data (sessions, auth tokens, rate-limit state) in most enterprise deployments, making the post-exploitation impact material. The deployment-role floor analysis places Redis in the 'production database engine on data tiers' category, which floors the verdict at HIGH. Dropping to MEDIUM would ignore the demonstrated RCE capability and the data-tier blast radius.
Crowdsourced verification payload.
Run this script on each host running Redis, or from an auditor workstation with redis-cli installed and network access to the target. Invoke as: bash check_cve_2026_23479.sh <redis-host> <redis-port> [redis-password]. No elevated privileges required — uses redis-cli INFO server to read the version.
#!/usr/bin/env bash
# check_cve_2026_23479.sh — Detect CVE-2026-23479 (Redis UAF in unblockClientOnKey)
# Usage: bash check_cve_2026_23479.sh <host> <port> [password]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
HOST="${1:-127.0.0.1}"
PORT="${2:-6379}"
PASS="${3:-}"
AUTH_ARGS=()
if [[ -n "$PASS" ]]; then
AUTH_ARGS=(-a "$PASS" --no-auth-warning)
fi
# Fetch redis version
VERSION=$(redis-cli -h "$HOST" -p "$PORT" "${AUTH_ARGS[@]}" INFO server 2>/dev/null \
| grep -E '^redis_version:' | cut -d: -f2 | tr -d '\r')
if [[ -z "$VERSION" ]]; then
echo "UNKNOWN — could not connect to Redis at $HOST:$PORT or parse version."
exit 2
fi
echo "Detected Redis version: $VERSION"
# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
# Versions < 7.2.0 are NOT affected (vulnerable code introduced in 7.2.0)
if (( MAJOR < 7 )) || { (( MAJOR == 7 )) && (( MINOR < 2 )); }; then
echo "PATCHED — Redis $VERSION predates the vulnerable code (introduced in 7.2.0)."
exit 0
fi
# Check each affected branch against its fixed version
VULN=0
if (( MAJOR == 7 && MINOR == 2 )); then
(( PATCH < 14 )) && VULN=1
elif (( MAJOR == 7 && MINOR == 4 )); then
(( PATCH < 9 )) && VULN=1
elif (( MAJOR == 8 && MINOR == 2 )); then
(( PATCH < 6 )) && VULN=1
elif (( MAJOR == 8 && MINOR == 4 )); then
(( PATCH < 3 )) && VULN=1
elif (( MAJOR == 8 && MINOR == 6 )); then
(( PATCH < 3 )) && VULN=1
elif (( MAJOR >= 9 )); then
# Future major version — assume patched
VULN=0
else
# Other 7.x or 8.x minor branches (e.g., 7.3, 8.0, 8.1) —
# likely affected if >= 7.2 but not in a known-fixed branch.
echo "UNKNOWN — Redis $VERSION is on an unrecognized branch. Check vendor advisory."
exit 2
fi
if (( VULN == 1 )); then
echo "VULNERABLE — Redis $VERSION is affected by CVE-2026-23479."
echo "Upgrade to the fixed version for your branch (7.2.14 / 7.4.9 / 8.2.6 / 8.4.3 / 8.6.3)."
exit 1
else
echo "PATCHED — Redis $VERSION includes the fix for CVE-2026-23479."
exit 0
fi