Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Log4Shell playthings) Initial implementation of Rex LDAP Server #15961

Merged
merged 7 commits into from Dec 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/exploits/ldap/msf.ldif
@@ -0,0 +1,5 @@
dn: dc=metasploit,dc=com
objectClass: dcObject
objectClass: organization
o: Metasploit Framework
dc: metasploit
82 changes: 82 additions & 0 deletions documentation/modules/auxiliary/server/ldap.md
@@ -0,0 +1,82 @@
## Vulnerable Application
This module demonstrates setting up and running a basic LDAP server in Metasploit. The data it hosts is provided by the
`LDIF_FILE`.

## Verification Steps

1. Start msfconsole
1. Do: `use auxiliary/server/ldap`
1. Do: `set LDIF_FILE data/exploits/ldap/msf.ldif`
* This assumes the working directory is the top-level Metasploit Framework directory and configures the module to
use the included template.
1. Do: `run`
1. From a new shell, do: `ldapsearch -x -H ldap://192.168.159.128 -b "dc=metasploit,dc=com" "(objectClass=*)"`
* This runs a query using the `ldapsearch` utility to show the server is responsive.

## Options

### LDIF_FILE

Directory LDIF file path.

## Scenarios

### Metasploit Server Demonstration

```
msf6 > use auxiliary/server/ldap
msf6 auxiliary(server/ldap) > set LDIF_FILE data/exploits/ldap/msf.ldif
LDIF_FILE => data/exploits/ldap/msf.ldif
msf6 auxiliary(server/ldap) > show options

Module options (auxiliary/server/ldap):

Name Current Setting Required Description
---- --------------- -------- -----------
LDIF_FILE data/exploits/ldap/msf.ldif no Directory LDIF file path
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 389 yes The local port to listen on.


Auxiliary action:

Name Description
---- -----------
Service Run LDAP server


msf6 auxiliary(server/ldap) > run
[*] Auxiliary module running as background job 0.
msf6 auxiliary(server/ldap) >


```

From another shell:

```
$ ldapsearch -x -H ldap://192.168.159.128 -b "dc=metasploit,dc=com" "(objectClass=*)"
# extended LDIF
#
# LDAPv3
# base <dc=metasploit,dc=com> with scope subtree
# filter: (objectClass=*)
# requesting: ALL
#

# metasploit.com
dn: dc=metasploit,dc=com
objectClass: dcObject
objectClass: organization
o: Metasploit Framework
dc: metasploit

# search result
search: 2
result: 0 Success
text: Success

# numResponses: 2
# numEntries: 1

```
4 changes: 2 additions & 2 deletions lib/msf/core/exploit/remote/ldap.rb
Expand Up @@ -4,7 +4,7 @@
# This mixin is a wrapper around Net::LDAP
#

require 'net-ldap'
require 'rex/proto/ldap'
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved

module Msf
module Exploit::Remote::LDAP
Expand Down Expand Up @@ -36,7 +36,7 @@ def peer
"#{rhost}:#{rport}"
end

def get_connect_opts()
def get_connect_opts
connect_opts = {
host: rhost,
port: rport,
Expand Down
109 changes: 109 additions & 0 deletions lib/msf/core/exploit/remote/ldap/server.rb
@@ -0,0 +1,109 @@
# -*- coding: binary -*-

module Msf
###
#
# This module exposes methods for querying a remote LDAP service
#
###
module Exploit::Remote::LDAP
module Server
include Exploit::Remote::SocketServer

#
# Initializes an exploit module that serves LDAP requests
#
def initialize(info = {})
super

register_options(
[
OptPort.new('SRVPORT', [true, 'The local port to listen on.', 389]),
OptPath.new('LDIF_FILE', [ false, 'Directory LDIF file path']),
], Exploit::Remote::LDAP::Server
)

register_advanced_options(
[
OptBool.new('LdapServerUdp', [true, 'Serve UDP LDAP requests', true]),
OptBool.new('LdapServerTcp', [true, 'Serve TCP LDAP requests', true])
], Exploit::Remote::LDAP::Server
)
end

attr_accessor :service # :nodoc:

#
# Read LDIF file - from https://github.com/ruby-ldap/ruby-net-ldap/blob/master/testserver/ldapserver.rb#L162
#
# @ return [Hash] parsed ldif file
def read_ldif
return if datastore['LDIF_FILE'].blank? || !File.exist?(datastore['LDIF_FILE'])

ary = File.readlines(datastore['LDIF_FILE'])
ldif = {}
while (line = ary.shift) && line.chomp!
next unless line =~ /^dn:\s*/i

dn = Regexp.last_match.post_match
ldif[dn] = {}
while (attrib = ary.shift) && attrib.chomp! && attrib =~ /^(\w+)\s*:\s*/
ldif[dn][Regexp.last_match(1)] ||= []
ldif[dn][Regexp.last_match(1)] << Regexp.last_match.post_match
end
end
ldif
end

#
# Handle incoming requests
# Override this method in modules to take flow control
#
def on_dispatch_request(cli, data)
service.default_dispatch_request(cli, data)
end

#
# Handle incoming requests
# Override this method in modules to take flow control
#
def on_send_response(cli, data)
cli.write(data)
end

#
# Starts the server
#
def start_service
comm = _determine_server_comm(datastore['SRVHOST'])
self.service = Rex::ServiceManager.start(
Rex::Proto::LDAP::Server,
datastore['SRVHOST'],
datastore['SRVPORT'],
datastore['LdapServerUdp'],
datastore['LdapServerTcp'],
read_ldif,
comm,
{ 'Msf' => framework, 'MsfExploit' => self }
)

service.dispatch_request_proc = proc do |cli, data|
on_dispatch_request(cli, data)
end
service.send_response_proc = proc do |cli, data|
on_send_response(cli, data)
end
rescue ::Errno::EACCES => e
raise Rex::BindFailed, e.message
end

#
# Resets the LDAP server
#
def reset_service
cleanup
start_service
end
end
end
end
34 changes: 34 additions & 0 deletions lib/rex/proto/ldap.rb
@@ -0,0 +1,34 @@
require 'net/ldap'
require 'rex/socket'

# Monkeypatch upstream library, for now
# TODO: write a real LDAP client in Rex and migrate all consumers
class Net::LDAP::Connection # :nodoc:

def initialize(server)
begin
@conn = Rex::Socket::Tcp.create(
'PeerHost' => server[:host],
'PeerPort' => server[:port],
'Proxies' => server[:proxies]
)
rescue SocketError
raise Net::LDAP::LdapError, 'No such address or other socket error.'
rescue Errno::ECONNREFUSED
raise Net::LDAP::LdapError, "Server #{server[:host]} refused connection on port #{server[:port]}."
end

if server[:encryption]
setup_encryption server[:encryption]
end

yield self if block_given?
end
end

module Rex
module Proto
module LDAP
end
end
end