Module: Msf::Exploit::Remote::LDAP

Defined in:
lib/msf/core/exploit/remote/ldap/server.rb,
lib/msf/core/exploit/remote/ldap.rb

Overview

This module exposes methods for querying a remote LDAP service

Defined Under Namespace

Modules: Server

Instance Method Summary collapse

Instance Method Details

#discover_base_dn(ldap) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/msf/core/exploit/remote/ldap.rb', line 124

def discover_base_dn(ldap)
  naming_contexts = get_naming_contexts(ldap)

  unless naming_contexts
    print_error("#{peer} Base DN cannot be determined")
    return
  end

  # NOTE: We assume the first namingContexts value is the base DN
  base_dn = naming_contexts.first

  print_good("#{peer} Discovered base DN: #{base_dn}")
  base_dn
end

#get_connect_optsObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/msf/core/exploit/remote/ldap.rb', line 39

def get_connect_opts
  connect_opts = {
    host: rhost,
    port: rport,
    connect_timeout: datastore['LDAP::ConnectTimeout']
  }

  if datastore['SSL']
    connect_opts[:encryption] = {
      method: :simple_tls,
      tls_options: {
        verify_mode: OpenSSL::SSL::VERIFY_NONE
      }
    }
  end

  if datastore['BIND_DN']
    connect_opts[:auth] = {
      method: :simple,
      username: datastore['BIND_DN']
    }
    if datastore['BIND_PW']
      connect_opts[:auth][:password] = datastore['BIND_PW']
    end
  end
  connect_opts
end

#get_naming_contexts(ldap) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/msf/core/exploit/remote/ldap.rb', line 103

def get_naming_contexts(ldap)
  vprint_status("#{peer} Getting root DSE")

  unless (root_dse = ldap.search_root_dse)
    print_error("#{peer} Could not retrieve root DSE")
    return
  end

  vprint_line(root_dse.to_ldif)

  naming_contexts = root_dse[:namingcontexts]

  # NOTE: Net::LDAP converts attribute names to lowercase
  if naming_contexts.empty?
    print_error("#{peer} Empty namingContexts attribute")
    return
  end

  naming_contexts
end

#initialize(info = {}) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/msf/core/exploit/remote/ldap.rb', line 11

def initialize(info = {})
  super

  register_options([
    Opt::RHOST,
    Opt::RPORT(389),
    OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
    OptString.new('BIND_DN', [false, 'The username to authenticate to LDAP server'], fallbacks: ['USERNAME']),
    OptString.new('BIND_PW', [false, 'Password for the BIND_DN'], fallbacks: ['PASSWORD'])
  ])

  register_advanced_options([
    OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0])
  ])
end

#ldap_connect(opts = {}, &block) ⇒ Object



67
68
69
# File 'lib/msf/core/exploit/remote/ldap.rb', line 67

def ldap_connect(opts = {}, &block)
  Net::LDAP.open(get_connect_opts.merge(opts), &block)
end

#ldap_new(opts = {}) {|ldap| ... } ⇒ Object

Yields:

  • (ldap)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/msf/core/exploit/remote/ldap.rb', line 71

def ldap_new(opts = {})
  ldap = Net::LDAP.new(get_connect_opts.merge(opts))

  # NASTY, but required
  # monkey patch ldap object in order to ignore bind errors
  # Some servers (e.g. OpenLDAP) return result even after a bind
  # has failed, e.g. with LDAP_INAPPROPRIATE_AUTH - anonymous bind disallowed.
  # See: https://www.openldap.org/doc/admin23/security.html#Authentication%20Methods
  # "Note that disabling the anonymous bind mechanism does not prevent anonymous
  # access to the directory."
  #
  # Bug created for Net:LDAP https://github.com/ruby-ldap/ruby-net-ldap/issues/375
  #
  def ldap.use_connection(args)
    if @open_connection
      yield @open_connection
    else
      begin
        conn = new_connection
        conn.bind(args[:auth] || @auth)
        # Commented out vs. original
        # result = conn.bind(args[:auth] || @auth)
        # return result unless result.result_code == Net::LDAP::ResultCodeSuccess
        yield conn
      ensure
        conn.close if conn
      end
    end
  end
  yield ldap
end

#peerObject



35
36
37
# File 'lib/msf/core/exploit/remote/ldap.rb', line 35

def peer
  "#{rhost}:#{rport}"
end

#rhostObject



27
28
29
# File 'lib/msf/core/exploit/remote/ldap.rb', line 27

def rhost
  datastore['RHOST']
end

#rportObject



31
32
33
# File 'lib/msf/core/exploit/remote/ldap.rb', line 31

def rport
  datastore['RPORT']
end

#validate_bind_success!(ldap) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/msf/core/exploit/remote/ldap.rb', line 139

def validate_bind_success!(ldap)
  bind_result = ldap.as_json['result']['ldap_result']

  # Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
  case bind_result['resultCode']
  when 0
    vprint_good('Successfully bound to the LDAP server!')
  when 1
    fail_with(Msf::Exploit::Remote::Failure::NoAccess, "An operational error occurred, perhaps due to lack of authorization. The error was: #{bind_result['errorMessage'].strip}")
  when 7
    fail_with(Msf::Exploit::Remote::Failure::NoTarget, 'Target does not support the simple authentication mechanism!')
  when 8
    fail_with(Msf::Exploit::Remote::Failure::NoTarget, "Server requires a stronger form of authentication than we can provide! The error was: #{bind_result['errorMessage'].strip}")
  when 14
    fail_with(Msf::Exploit::Remote::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result['errorMessage'].strip}")
  when 48
    fail_with(Msf::Exploit::Remote::Failure::NoAccess, "Target doesn't support the requested authentication type we sent. Try binding to the same user without a password, or providing credentials if you were doing anonymous authentication.")
  when 49
    fail_with(Msf::Exploit::Remote::Failure::NoAccess, 'Invalid credentials provided!')
  else
    fail_with(Msf::Exploit::Remote::Failure::Unknown, "Unknown error occurred whilst binding: #{bind_result['errorMessage'].strip}")
  end
end

#validate_query_result!(query_result, filter) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/msf/core/exploit/remote/ldap.rb', line 163

def validate_query_result!(query_result, filter)
  if query_result.class != Hash
    raise ArgumentError.new('Parameter to "validate_query_result!" function was not a Hash!')
  end

  # Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
  case query_result['resultCode']
  when 0
    vprint_status("Successfully queried #{filter}.")
  when 1
    # This is unknown as whilst we could fail on lack of authorization, this is not guaranteed with this error code.
    # The user will need to inspect the error message to determine the root cause of the issue.
    fail_with(Failure::Unknown, "An LDAP operational error occurred on #{filter}. It is likely the client requires authorization! The error was: #{query_result['errorMessage'].strip}")
  when 2
    fail_with(Failure::BadConfig, "The LDAP protocol being used by Metasploit isn't supported. The error was #{query_result['errorMessage'].strip}")
  when 3
    fail_with(Failure::TimeoutExpired, "The LDAP server returned a timeout response to the query #{filter}.")
  when 4
    fail_with(Failure::UnexpectedReply, "The LDAP query #{filter} was determined to result in too many entries for the LDAP server to return.")
  when 11
    fail_with(Failure::UnexpectedReply, "The LDAP server indicated that #{filter} hit some administrative limit within the server whilst the request was being processed.")
  when 16
    fail_with(Failure::NotFound, "The LDAP operation failed for #{filter} because the referenced attribute does not exist.")
  when 18
    fail_with(Failure::BadConfig, "The LDAP search with #{filter} failed because some matching is not supported for the target attribute type!")
  when 32
    fail_with(Failure::UnexpectedReply, "The LDAP search with #{filter} failed cause the operation targeted an entity within the base DN that does not exist.")
  when 33
    fail_with(Failure::BadConfig, "An attempt was made to deference an alias that didn't resolve properly with #{filter}.")
  when 34
    fail_with(Failure::BadConfig, 'The request included an invalid base DN entry.')
  when 50
    fail_with(Failure::NoAccess, 'The LDAP operation failed due to insufficient access rights.')
  when 51
    fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is too busy to perform the request.')
  when 52
    fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is not currently available to process the request.')
  when 53
    fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is unwilling to perform the request.')
  when 64
    fail_with(Failure::Unknown, "The LDAP operation failed due to a naming violation when using #{filter}.")
  when 65
    fail_with(Failure::Unknown, "The LDAP operation failed due to an object class violation when using #{filter}.")
  else
    if query_result['errorMessage'].blank?
      fail_with(Failure::Unknown, "Query #{filter} failed but no error message was returned!")
    else
      fail_with(Failure::Unknown, "Query #{filter} failed with error: #{query_result['errorMessage'].strip}")
    end
  end
end