Skip to content

Commit

Permalink
keep ldap connection open for use in a session
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelch-r7 committed Apr 5, 2024
1 parent 9e69bee commit a7ba39c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 27 deletions.
15 changes: 7 additions & 8 deletions lib/metasploit/framework/login_scanner/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class LDAP
include Metasploit::Framework::LDAP::Client
include Msf::Exploit::Remote::LDAP

attr_accessor :opts
attr_accessor :realm_key
attr_accessor :opts, :realm_key
# @!attribute use_client_as_proof
# @return [Boolean] If a login is successful and this attribute is true - an LDAP::Client instance is used as proof
attr_accessor :use_client_as_proof

def attempt_login(credential)
result_opts = {
credential: credential,
Expand All @@ -38,22 +38,22 @@ def do_login(credential)
}.merge(@opts)

connect_opts = ldap_connect_opts(host, port, connection_timeout, ssl: opts[:ssl], opts: opts)
ldap_open(connect_opts) do |ldap_client|
begin
ldap_client = Rex::Proto::LDAP::Client._open(connect_opts)
return status_code(ldap_client)
rescue StandardError => e
{ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e }
end
end

def status_code(ldap_client)
case ldap_client.get_operation_result.table[:code]
operation_result = ldap_client.get_operation_result.table[:code]
case operation_result
when 0
result = { status: Metasploit::Model::Login::Status::SUCCESSFUL }
if use_client_as_proof
result[:proof] = ldap_client
# client = nil
# self.sock = nil
# self.dispatcher = nil
result[:connection] = ldap_client.socket
end
result
else
Expand Down Expand Up @@ -93,7 +93,6 @@ def each_credential
credential.public = "#{credential.public}@#{opts[:domain]}"
yield credential
end

end
end
end
Expand Down
30 changes: 28 additions & 2 deletions lib/rex/proto/ldap/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
module Rex
module Proto
module LDAP

# This is a Rex Proto wrapper around the Net::LDAP client which is currently coming from the 'net-ldap' gem.
# The purpose of this wrapper is to provide 'peerhost' and 'peerport' methods to ensure the client interfaces
# are consistent between various session clients.
class Client < Net::LDAP
# @return [String] The remote IP address that LDAPr is running on

attr_reader :socket

# @return [String] The remote IP address that LDAP is running on
def peerhost
host
end
Expand All @@ -22,6 +24,30 @@ def peerport
def peerinfo
"#{peerhost}:#{peerport}"
end

# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
# We want to keep the ldap connection open to use later
# but there's no built in way within the `Net::LDAP` library to do that
# so we're
# @param connect_opts [Hash] Options for the LDAP connection.
def self._open(connect_opts)
client = new(connect_opts)
client._open
end

# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
def _open
raise Net::LDAP::AlreadyOpenedError, 'Open already in progress' if @open_connection

instrument 'open.net_ldap' do |payload|
@open_connection = new_connection
@socket = @open_connection.socket
payload[:connection] = @open_connection
payload[:bind] = @result = @open_connection.bind(@auth)
return self
end
end

end
end
end
Expand Down
38 changes: 22 additions & 16 deletions modules/auxiliary/scanner/ldap/ldap_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ def create_session?

def run
validate_connect_options!
super
# TODO: collect and log sessions/creds
results = super
logins = results.flat_map { |_k, v| v[:successful_logins] }
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
if datastore['CreateSession']
print_status("#{sessions.size} LDAP #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open an LDAP session with these credentials and %grnCreateSession%clr set to true')
end
results
end

def validate_connect_options!
Expand Down Expand Up @@ -118,6 +126,8 @@ def run_host(ip)
use_client_as_proof: create_session?
)

successful_logins = []
successful_sessions = []
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
Expand All @@ -127,44 +137,40 @@ def run_host(ip)
protocol: 'tcp'
)
if result.success?
successful_logins << result
if opts[:ldap_auth] == Msf::Exploit::Remote::AuthOption::SCHANNEL
# Schannel auth has no meaningful credential information to store in the DB
print_brute level: :good, ip: ip, msg: "Success: 'Cert File #{opts[:ldap_cert_file]}'"
else
create_credential_and_login(credential_data)
print_brute level: :good, ip: ip, msg: "Success: '#{result.credential}'"
end
create_session(result) if create_session?
successful_sessions << create_session(result) if create_session?
else
invalidate_login(credential_data)
vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
end
{ successful_logins: successful_logins, successful_sessions: successful_sessions }
end

private

def create_session(result)
session_setup(result, result.proof)
session_setup(result)
rescue StandardError => e
elog('Failed to setup the session', error: e)
print_brute level: :error, ip: 'fake_ip', msg: "Failed to setup the session - #{e.class} #{e.message}"
print_brute level: :error, ip: ip, msg: "Failed to setup the session - #{e.class} #{e.message}"
result.connection.close unless result.connection.nil?
end

# @param [Metasploit::Framework::LoginScanner::Result] result
# @param [Rex::Proto::LDAP::Client] client
# @return [Msf::Sessions::LDAP]
def session_setup(result, client)
return unless client
def session_setup(result)
return unless (result.connection && result.proof)

# Create a new session
# rstream = client.dispatcher.tcp_socket
sess = Msf::Sessions::LDAP.new(
nil, # TODO: make this nil, don't think we need it anymore for the new(er) session types
{
client: client
}
)
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof })

merge_me = {
'USERPASS_FILE' => nil,
Expand All @@ -174,6 +180,6 @@ def session_setup(result, client)
'PASSWORD' => result.credential.private
}

start_session(self, nil, merge_me, false, sess.rstream, sess)
start_session(self, nil, merge_me, false, my_session.rstream, my_session)
end
end
2 changes: 1 addition & 1 deletion spec/lib/rex/proto/ldap/client_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: binary -*-

require 'spec_helper'
require 'rex/proto/smb/simple_client'
require 'rex/proto/ldap/client'

RSpec.describe Rex::Proto::LDAP::Client do
let(:host) { '127.0.0.1' }
Expand Down

0 comments on commit a7ba39c

Please sign in to comment.