Redis/Valkey - TCP/6379
An in-memory data store often used to store small data like cache, sessions or queues
Description
The Redis Documentation is a great way to learn about its features. It is a TCP server commonly bound to port 6379, using an ASCII protocol, with no binary data (see Protocol). By sending commands, you can authenticate, write, and read data at specific keys.
Many more advanced features exist, see Commands for all of them. Most are for managing big clusters of servers and maintenance tasks, but some are useful for dealing with all the different data types. The following are a few examples where you could often see Redis:
Caching content for URLs or identifiers temporarily to reduce the load on a server
Storing sessions of logged-in users with their associated data
Holding queues of processing requests that a server will take from
SSRF
In a lucky scenario, you might have direct access to the Redis port and can interact with it. But often this application is only accessible locally (127.0.0.1), or on a different backend instance. In these cases you need to persuade a target that can reach it, to send requests on your behalf. With Server-Side Request Forgery vulnerabilities, you may be able to send arbitrary HTTP requests, or even raw TCP data to a location you control. A common technique is targetting this at a Redis server at port :6379 and seeing if you can exploit it in any way.
To find a Redis server you can try to reach out to localhost (127.0.0.1 or variations), as well as internal hosts like the docker (172.17.0.0/16, note that it may be changed), and hostnames like "redis". You can send some simple command to view if a clean response comes back (testing by connecting to your own system helps), but often you will be doing this blind. See Detection: Callbacks for ideas on detecting this remotely.
HTTP
HTTP is a common target for SSRF vulnerabilities as servers often make such requests. Redis is a plaintext protocol meaning it would be easy to write some POST data with command that it will execute. Therefore, the developers have thought about this. Sending any command starting with POST
or Host:
(case-insensitive) will quit the connection, possibly before you had your chance to write any commands. We need to be more creative.
As every request should have a Host:
header, we have 3 places where we could possibly inject commands before it terminates:
The request method itself, the first thing it sees. If we simply make this a GET request (a default redis command), it won't quit and by inserting
\r\n
newline characters we may even be able to insert more commandsThe location (path) of the request, right after the method. If we are able to insert newline characters here you can write more commands. Note that this is impossible for POST requests as they quit early
Extra headers before the
Host:
header, which may have newline injection possibilities or, because the name is the start of a line, the name can be a command by itself
Detection: Callbacks
For Blind SSRF scenarios where you are unsure about the backend, and if it is vulnerable, you can try to generate a simple callback to yourself to confirm that you are executing Redis commands. These expect to interact with another Redis server that you can set up, but will also simply connect and send data to a simple nc
listener that you make. Here are some ways:
Exploitation
See Enumeration for examples where you directly receive a response, as well as some RCE techniques further down the page. With an active system the MONITOR
command can also give good information about incoming queries and writes.
Without a response, there are still some powerful things you can leak. Above we saw some ways of detecting a callback to your server, but the MIGRATE
command is very powerful for this. It sends the value of a key to your server, which can be after you perform some complex commands and saving a result in such a key! This way we can exfiltrate data and responses as if we were interacting directly.
For the cleanest output, simply set up a simple Redis server, for example with docker:
Then with redis-cli
locally you can view all data, and using MONITOR
even in real-time!
We will trigger some commands on the server that will try to send a key and value to our new server. Try it with the key1
payload shown above, and the callback with content should appear:
While it is a good confirmation of the vulnerability, we can try to exploit it to gain more impact. Instead of leaking our own key, we can leak some keys that it stores, but how do we find the names of the keys? This is where the powerful Lua scripting (EVAL
) can come into play. We will list and write all the keys to a single value, which we can leak through the MIGRATE
method:
This will run the KEYS *
command (or any command you like), and concat the results with a ,
comma, and finally save this list to a key named output
which we can leak:
Remember that you can now always leak a key by name using this method, and find all the secrets even with random names. Also, remember that more commands can be executed like this to enumerate the instance as needed.
Then when you finally understand how the system is used, you can try to exploit it by (over)writing data in the store. This can cause all kinds of unexpected vulnerabilities as developers often trust data coming from their own Redis store, causing injection, deserialization, or other access vulnerabilities on the main application. This can simply be done with the SET
commands blindly.
ACLs
As always, the Redis Documentation explains ACLs in great detail by itself. The gist is that developers can add Access Control Lists with security rules for specific users to restrict what commands they have access to. There is one special user called "default" that does not require a password.
This section contains some common rules that can be bypassed in various ways.
Discovering keys
When the -@dangerous
permission is set, all commands in this ACL group are disabled, such as KEYS
to discover keys that may have a randomly generated name that you cannot guess. While a developer might see this as a protection, there are various non-@dangerous
ways and alternative ways to leak these key names with different commands:
An almost drop-in replacement for the KEYS
command is SCAN
which incrementally returns a subset of all keys. Using the COUNT
argument you can get as many as you want to discover all keys:
A simple but effective way to bypass a forbidden KEYS
command is to get a RANDOMKEY
, which does exactly what you think it does. By sending it many times you can get as many keys as you like until you get the one you need. This is an easy mistake to make because KEYS
is part of the @dangerous
ACL group, while RANDOMKEY
is not!
Another more advanced method is using the TRACKING feature of Redis, where you can listen for keys that change globally. By creating a client, registering the invalidation for BCAST (all keys), and then subscribing to the channel. You instantly get a message whenever a key is added or written to.
Reading data
When you have found an interesting key you'll often want to read its contents. There are a few different types of keys all with their own 'get' commands. Here are most of them:
Another useful general command that works on all data types is DUMP
which returns the binary representation of the data, often with readable strings and metadata included.
Last updated