mirror of
https://github.com/ShutdownRepo/dcshadow
synced 2026-06-06 16:44:32 +00:00
204 lines
8.6 KiB
Python
204 lines
8.6 KiB
Python
import ssl
|
|
from binascii import unhexlify
|
|
from typing import Any
|
|
|
|
import ldap3
|
|
from R2Log import logger
|
|
|
|
from impacket.smbconnection import SMBConnection
|
|
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
|
|
|
|
|
|
def get_machine_name(dc_ip: str, domain: str):
|
|
if dc_ip is not None:
|
|
s = SMBConnection(dc_ip, dc_ip)
|
|
else:
|
|
s = SMBConnection(domain, domain)
|
|
try:
|
|
s.login('', '')
|
|
except Exception:
|
|
if s.getServerName() == '':
|
|
raise Exception('Error while anonymous logging into %s' % domain)
|
|
else:
|
|
s.logoff()
|
|
return s.getServerName()
|
|
|
|
|
|
def init_ldap_connection(target: Any, tls_version: Any, kerberos: bool, domain: str, username: str, password: str, lm_hash: str, nt_hash: str, aes_key: str, dc_ip: str):
|
|
user = '%s\\%s' % (domain, username)
|
|
if tls_version is not None:
|
|
use_ssl = True
|
|
port = 636
|
|
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version)
|
|
else:
|
|
use_ssl = False
|
|
port = 389
|
|
tls = None
|
|
ldap_server = ldap3.Server(target, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls)
|
|
if kerberos:
|
|
ldap_session = ldap3.Connection(ldap_server)
|
|
ldap_session.bind()
|
|
ldap3_kerberos_login(connection=ldap_session, target=target, username=username, password=password, domain=domain, lm_hash=lm_hash, nt_hash=nt_hash, aes_key=aes_key, kdc_host=dc_ip)
|
|
elif not (lm_hash == '' and nt_hash == ''):
|
|
ldap_session = ldap3.Connection(ldap_server, user=user, password=lm_hash + ":" + nt_hash, authentication=ldap3.NTLM, auto_bind=True)
|
|
else:
|
|
ldap_session = ldap3.Connection(ldap_server, user=user, password=password, authentication=ldap3.NTLM, auto_bind=True)
|
|
|
|
return ldap_server, ldap_session
|
|
|
|
|
|
def ldap3_kerberos_login(connection: ldap3.Connection, target: str, username: str, password: str, domain='', lm_hash='', nt_hash='', aes_key='', kdc_host=None, tgt=None, st=None, use_cache=True):
|
|
from pyasn1.codec.ber import encoder, decoder
|
|
from pyasn1.type.univ import noValue
|
|
"""
|
|
logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
|
|
:param string user: username
|
|
:param string password: password for the user
|
|
:param string domain: domain where the account is valid for (required)
|
|
:param string lmhash: LMHASH used to authenticate using hashes (password is not used)
|
|
:param string nthash: NTHASH used to authenticate using hashes (password is not used)
|
|
:param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
|
|
:param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
|
|
:param struct TGT: If there's a TGT available, send the structure here and it will be used
|
|
:param struct TGS: same for TGS. See smb3.py for the format
|
|
:param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
|
|
:return: True, raises an Exception if error.
|
|
"""
|
|
|
|
if lm_hash != '' or nt_hash != '':
|
|
if len(lm_hash) % 2:
|
|
lm_hash = '0' + lm_hash
|
|
if len(nt_hash) % 2:
|
|
nt_hash = '0' + nt_hash
|
|
try: # just in case they were converted already
|
|
lm_hash = unhexlify(lm_hash)
|
|
nt_hash = unhexlify(nt_hash)
|
|
except TypeError:
|
|
pass
|
|
|
|
# Importing down here so pyasn1 is not required if kerberos is not used.
|
|
from impacket.krb5.ccache import CCache
|
|
from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
|
|
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
|
|
from impacket.krb5 import constants
|
|
from impacket.krb5.types import Principal, KerberosTime, Ticket
|
|
import datetime
|
|
|
|
if tgt is not None or st is not None:
|
|
use_cache = False
|
|
|
|
target = 'ldap/%s' % target
|
|
if use_cache:
|
|
domain, username, tgt, st = CCache.parseFile(domain, username, target)
|
|
|
|
# First of all, we need to get a TGT for the user
|
|
userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
|
if tgt is None:
|
|
if st is None:
|
|
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lm_hash, nt_hash, aes_key, kdc_host)
|
|
else:
|
|
tgt = tgt['KDC_REP']
|
|
cipher = tgt['cipher']
|
|
sessionKey = tgt['sessionKey']
|
|
|
|
if st is None:
|
|
serverName = Principal(target, type=constants.PrincipalNameType.NT_SRV_INST.value)
|
|
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdc_host, tgt, cipher, sessionKey)
|
|
else:
|
|
tgs = st['KDC_REP']
|
|
cipher = st['cipher']
|
|
sessionKey = st['sessionKey']
|
|
|
|
# Let's build a NegTokenInit with a Kerberos REQ_AP
|
|
|
|
blob = SPNEGO_NegTokenInit()
|
|
|
|
# Kerberos
|
|
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
|
|
|
|
# Let's extract the ticket from the TGS
|
|
tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
|
|
ticket = Ticket()
|
|
ticket.from_asn1(tgs['ticket'])
|
|
|
|
# Now let's build the AP_REQ
|
|
apReq = AP_REQ()
|
|
apReq['pvno'] = 5
|
|
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
|
|
|
|
opts = []
|
|
apReq['ap-options'] = constants.encodeFlags(opts)
|
|
seq_set(apReq, 'ticket', ticket.to_asn1)
|
|
|
|
authenticator = Authenticator()
|
|
authenticator['authenticator-vno'] = 5
|
|
authenticator['crealm'] = domain
|
|
seq_set(authenticator, 'cname', userName.components_to_asn1)
|
|
now = datetime.datetime.utcnow()
|
|
|
|
authenticator['cusec'] = now.microsecond
|
|
authenticator['ctime'] = KerberosTime.to_asn1(now)
|
|
|
|
encodedAuthenticator = encoder.encode(authenticator)
|
|
|
|
# Key Usage 11
|
|
# AP-REQ Authenticator (includes application authenticator
|
|
# subkey), encrypted with the application session key
|
|
# (Section 5.5.1)
|
|
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)
|
|
|
|
apReq['authenticator'] = noValue
|
|
apReq['authenticator']['etype'] = cipher.enctype
|
|
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
|
|
|
|
blob['MechToken'] = encoder.encode(apReq)
|
|
|
|
request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, username, None, 'GSS-SPNEGO', blob.getData())
|
|
|
|
# Done with the Kerberos saga, now let's get into LDAP
|
|
if connection.closed: # try to open connection if closed
|
|
connection.open(read_server_info=False)
|
|
|
|
connection.sasl_in_progress = True
|
|
response = connection.post_send_single_response(connection.send('bindRequest', request, None))
|
|
connection.sasl_in_progress = False
|
|
if response[0]['result'] != 0:
|
|
raise Exception(response)
|
|
|
|
connection.bound = True
|
|
|
|
return True
|
|
|
|
|
|
class LdapClient:
|
|
server = None
|
|
session = None
|
|
|
|
def __init__(self, domain: str, username: str, password: str, lm_hash: str, nt_hash: str, aes_key: str, kerberos: bool, dc_ip: str, use_ldaps: bool):
|
|
if kerberos:
|
|
target = get_machine_name(dc_ip=dc_ip, domain=domain)
|
|
else:
|
|
if dc_ip is not None:
|
|
target = dc_ip
|
|
else:
|
|
target = domain
|
|
|
|
if use_ldaps is True:
|
|
try:
|
|
self.server, self.session = init_ldap_connection(target=target, tls_version=ssl.PROTOCOL_TLSv1_2, domain=domain, username=username, password=password, lm_hash=lm_hash, nt_hash=nt_hash, aes_key=aes_key, dc_ip=dc_ip, kerberos=kerberos)
|
|
except ldap3.core.exceptions.LDAPSocketOpenError:
|
|
self.server, self.session = init_ldap_connection(target=target, tls_version=ssl.PROTOCOL_TLSv1, domain=domain, username=username, password=password, lm_hash=lm_hash, nt_hash=nt_hash, aes_key=aes_key, dc_ip=dc_ip, kerberos=kerberos)
|
|
else:
|
|
self.server, self.session = init_ldap_connection(target=target, tls_version=None, domain=domain, username=username, password=password, lm_hash=lm_hash, nt_hash=nt_hash, aes_key=aes_key, dc_ip=dc_ip, kerberos=kerberos)
|
|
|
|
def check_ldap_result(self):
|
|
if self.session.result['result'] == 0:
|
|
return True
|
|
else:
|
|
if self.session.result['result'] == 50:
|
|
logger.error('Could not modify object, the server reports insufficient rights: %s', self.session.result['message'])
|
|
elif self.session.result['result'] == 19:
|
|
logger.error('Could not modify object, the server reports a constrained violation: %s', self.session.result['message'])
|
|
else:
|
|
logger.error('The server returned an error: %s', self.session.result['message'])
|
|
return False |