Last year I did a research on the embedded Lua interpreter of redis-server
(+wrote a pwnable). During this research, I managed to spot a hidden, 2-year old privilege escalation that was left un-noticed for quite some time (:
Redis’ embedded Lua interpreter is widely used by web-apps backend, especially if the app wants to perform complex, atomic database transactions with some logic in it, which makes the Lua feature very attractive/useful. Hence, it makes sense that all the security mechanisms should be in-place not only in the redis server, but also in the embedded lua interpreter.
Bug Description
During my research on the Redis Lua interpreter, I noticed that all the redis clients share the same lua global scope(_G
).
This might be dangerous, since one client with low-privileges can execute a lua script that might affect other scripts, which are executed by higher-privileged users. In a scenario like this, the low-privileged user could hijack the higher-privileged user’s session & execute redis commands on his behalf.
Layers of defense
Achieving the ACL bypass exploit wasn’t as trivial as it sounds. The Redis maintainers made sure to implement the same security controls they normally use in the rest of the server when they added the ACL functionality to the Lua interpreter. However, once you peek into the internals of the Lua component in the server, you realize there’s a logic bug you can leverage to escalate your privs and bypass the built-in security controls. Below are the obstacles & how they can be bypassed.
Obstacle #1
As expected, redis-server has a mechanism to protect the Lua’s _G
table. This mechanism is defined in scriptingEnableGlobalsProtection
:
If you’re not familiar with setmetatable()
, you can think of it as a similar mechanism to define getter/setters. They are calling setmetatable()
on the global table(_G
) in order to prevent modifications of it by any redis client.
Obstacle #2
On top of scriptingEnableGlobalsProtection
, ACL rules are also enforced if the client tries to perform redis-related operations via the Lua interpreter that are not in his ACL rules settings:
They are using ACLCheckAllPerm
to check if the current user is authorized to perform the requested redis operation.
Bypass :D
It was found that even though we have those two obstacles/mechanisms(described above), a low-privileged user can still elevate his privileges. This can be done by poisoning Lua’s _G
table in order to hijack another client’s session(with higher privileges) using Lua’s built-in rawset
method, which is not blacklisted.
By doing that, it is possible to bypass scriptingEnableGlobalsProtection
due to the insufficient blacklisting, and also overcome the check of ACLCheckAllPerm
because we’re doing a redis operation on behalf of another user that already has those permissions.
Below is a PoC to demonstrate the exploitation.
PoC
For the proof of concept, we’ll use the following ACL configurations:
user lowpriv on >lowpriv-pwd ~* +eval
user root on >root ~* &* +@all
The lowpriv
user cannot execute any redis operations(GET/SET/etc) except Lua transactions.
The root user has all permissions enabled.
The following steps demonstrates the attack:
- The
lowpriv
user executes the following Lua script:
We switched the redis.call
function implementation and added our own sneaky ‘backdoor’ to it.(pocfunc
)
2. Then, the root user logs in the server & perform a Lua script that executes a GET operation:
127.0.0.1:6379> KEYS *
1) "foo"
127.0.0.1:6379> EVAL "return redis.call('get', 'foo')" 0
"bar"
127.0.0.1:6379> KEYS *
1) "hijacked"
2) "foo"
127.0.0.1:6379> GET hijacked
"poc-test-123123"
127.0.0.1:6379> ACL LIST
1) "user lowpriv on #aa3a017020b8bb26ffc9efb7641689203f7fb4ad83907f96b362906b9bff9f5c ~* &* -@all +eval"
2) "user root on #4813494d137e1631bba301d5acab6e7bb7aa74ce1185d456565ef51d737677b2 ~* &* +@all"
As can be seen above, even though the root user performed a legitimate GET operation, a new redis key was created(named ‘hijacked’).
Note: The same attack scenario(of poisoning the global scope) can be done with
setmetatable
, which is also available in the embedded Lua interpreter. Using this method, a malicious client can effectively disable all the hard-coded protections defined inscriptingEnableGlobalsProtection
.
Reporting to Redis (Dup? or maybe not??)
When I sent this to the Redis security team, they said it’s a known attack vector(poisoning the _G
global scope to hijack other users).
However, it was initially reported on 2014, and it had a much lower severity due to the design/security maturity of redis, which led it to be dropped(back then, there were no security controls like ACLs or passwords).
But since that time redis stepped-up the security awareness and added security features(protected mode in 2016, ACLs in 2020 which was even a bigger leap). Since the introduction of ACLs in 2020 there was no warning/note saying that ACLCheckAllPerm can be subverted as a side-effect, leaving a lot of redis servers exposed for this new attack surface without their knowledge.
As a result, they agreed to give a partial credit. In the advisory description, it says that i’m responsible for reporting, but not for the discovery(usually it says ‘This issue was discovered and reported by XXXXX’). Which is quite fair imho, the original report bypassed scriptingEnableGlobalsProtection
, and I was responsible for demonstrating the higher impact by pointing out that this finding was left-out since 2014, and can be leveraged to defeat modern security controls(such as ACLCheckAllPerm
), which resulted in an immediate fix. If you have Redis 6.0 and above, you are affected. This issue was fixed in 7.0.0 and 6.2.7.
Thanks for reading, I hope it was informative.
References / Further Reading
- Some related work: BSidesTLV CTF 2021 - ‘Rainy Redis’ writeup (pwn)
- Lua metatables: https://www.lua.org/pil/13.html
- CVE(s) assigned:
- Release notes for Reids 7.0: https://groups.google.com/g/redis-db/c/7iWUlwtoDqU
- Advisory for CVE-2022-24735: GHSA-647m-2wmq-qmvq
- Advisory for CVE-2022-24736(Another variant, which is levereged to trigger a segfault/dos): GHSA-3qpw-7686-5984