Security Watchouts for Redis Clients
When the scalability and performance become the main goal in our business, in-memory caching systems are one of the items we look for to provide fast data access. Redis is one of them. In this post, we’ll try to give a list of security best practices that can be followed and applied when using a Redis client.
The actions can as well be aligned to other in-memory caching systems or client libraries, however, whenever appropriate we’ll give examples using the popular StackExchange.Redis C# library.
The first set of actions are related to a popular type of software security weakness; Injections, and in CWE words Data Neutralization Issues, which is perhaps better suited because it tells more about the root cause.
Connection String Injection
The cache client library needs a connection string to connect to the target server. So, the first injection problem might arouse when an untrusted input insecurely becomes part of the Redis connection string.
var connStr = "192.168.3.4:6379,password=...,name=" + clientName;
var redis = ConnectionMultiplexer.Connect(connStr);
If there is a possibility that variable clientName
above can be manipulated, then it’s possible to inject all sorts of configuration items, including a new bogus target server by using a single comma and the new IP address with port. The StackExchange.Redis client will happily accept the last target in the connection string overriding the old ones if exist.
Make sure a good whitelist validation is applied on the clientName
. Moreover, try to narrow down the possibility of bad content reaching to that variable; use admin only modifiable configuration files instead of accepting the data directly from admin/normal users, including a web interface.
Two obvious/bonus best practices
Never access a Redis server, or any other data-serving critical server for that matter, without using a authentication/authorization scheme. In this case, using a password is the best practice.
Most of the time, there’s a closed network between the cache clients (not the end users) and the Redis server. If that’s not the case or security maturity level is aimed to be the highest, then TLS should be used on communication channels, including the ones between the masters, replicas, nodes, etc.
Code Injection
The second best practice is related to a weakness type which is about injecting code that can run on the target Redis server. While the Redis security team finds it to be a strange use-case, once we are in the developers realm, anything is possible.
… While it would be a strange use case, the application should avoid composing the body of the Lua script from strings obtained from untrusted sources.
The library has a couple of methods that can be used to execute LUA scripts on the server;
StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync
StackExchange.Redis.IDatabaseAsync.ExecuteAsync
While providing a great flexibility, it is obvious that these are dangerous methods if used carelessly.
var result = (string)await redisDb.ScriptEvaluateAsync(script);
Make sure, you don’t accept full or part of a LUA script from users or any place except a trusted environment. Also, make sure the script repository is secure and monitored against unauthorized access.
Moreover, make sure our copy-paste habits don’t create a security mess when we are in need of example LUA scripts to start with or fixing bugs. ;)
While it’s somewhat hard to introduce code injection in Redis clients, it’s much more easier to have query injection problems when RediSearch is the case.
Insecure Deserialization or Cross Site Scripting
This one is a little bit of an indirect best practice. In general, there are two use cases of Redis; as a primary storage engine or as a cache.
No matter what the purpose is, it is important to acknowledge that the data residing on Redis may be tainted. So, when data-fetching methods of the client library are used, all sorts of data neutralization, whitelisting and API hardening efforts should be taken before this data goes to an interpreting engine.
Here’s a simple example showing the possibility of a deserialization bug;
value = await redisDb.StringGetAsync(key);
...
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
};
var obj = JsonConvert.DeserializeObject<T>(value, settings);
StackExchange.Redis is full of data-fetching methods;
StackExchange.Redis.IDatabaseAsync.StringGetAsync
StackExchange.Redis.IDatabaseAsync.SetPopAsync
StackExchange.Redis.IDatabaseAsync.SortedSetPopAsync
StackExchange.Redis.IDatabaseAsync.SetScanAsync
StackExchange.Redis.IDatabaseAsync.SetMembersAsync
StackExchange.Redis.IDatabaseAsync.ListRangeAsync
StackExchange.Redis.IDatabaseAsync.ListRemoveAsync
StackExchange.Redis.IDatabaseAsync.HashGetAsync
StackExchange.Redis.IDatabaseAsync.HashGetAllAsync
...
Again, this is an indirect best practice, however, it shows that we have to take the data we fetched using Redis with a bit of salt.
Insecure Library Versions
Missing patches for the third party components we use is a major security issue. StackExchange.Redis is not exempt from this.
While there are no reported vulnerabilities on this library per se, it’s still a good idea to have a maintenance schedule for it. Here’s an example published vulnerability for another client;
https://www.cve.org/CVERecord?id=CVE-2021-29469
Bad Design Choices
Up to now, we discussed a few possible but obvious security weaknesses of using a Redis client library. Here are a few others that are a little bit “not that obvious”.
Denial of Service (DoS) is another type of critical weakness that may exist on any software solution. Redis as a cache is generally used for solving the requirement of fast accessing data. So, the possibility of DoS is a direct blow to business having this requirement.
Avoid using slow commands
Running in-memory, Redis is a pretty fast cache. However, there are commands which may result in pretty slow executions, such as KEYS, HGETALL, LRANGE, etc.
All of these commands have O(n) complexity and if your cache data model design is not sane enough, all of these commands may trigger timeouts for good request loads.
So, as a best practice a good design is required before pushing all of your data to the cache server. Another precaution would be using size checks before using these potentially slow commands or using their sound counterparts, such as using SCAN instead of KEYS.
One another useful command is SORT, however, with just over 1M integers, the Redis execution (not latency) goes over seconds. And letting end-users choosing the input elements, the time needed to compute the sorted listed may get unexpectedly higher.
Conclusion
As the complexity of an engine increases, the possibility of finding weaknesses also increase. The LUA scripting apart, the expressiveness of Redis commands is substantially lower than the ones of a RDBMS.
This phenomenon also reflects itself on the number of published vulnerabilities on Redis and the related clients. However, it doesn’t mean that we can’t diminish the security of a cache based flow. It’s a matter of lack of security awareness and time. Therefore, albeit the Redis has simpler and seemingly secure mechanisms, it’s still important to follow security best practices and be prepared for the worst.