diff --git a/certipy/commands/auth.py b/certipy/commands/auth.py index 523e53f..015e096 100755 --- a/certipy/commands/auth.py +++ b/certipy/commands/auth.py @@ -303,6 +303,7 @@ def ldap_authentication( sasl_credentials=sasl_credentials, auto_bind=ldap3.AUTO_BIND_TLS_BEFORE_BIND, raise_exceptions=True, + receive_timeout=self.target.timeout * 10 ) except ldap3.core.exceptions.LDAPUnavailableResult as e: logging.error("LDAP not configured for SSL/TLS connections") diff --git a/certipy/commands/find.py b/certipy/commands/find.py index 271cb12..46258b2 100755 --- a/certipy/commands/find.py +++ b/certipy/commands/find.py @@ -778,7 +778,10 @@ def security_to_bloodhound_aces(self, security: ActiveDirectorySecurity) -> List is_inherited = rights["inherited"] principal = self.connection.lookup_sid(sid) - standard_rights = rights["rights"].to_list() + try: + standard_rights = list(rights["rights"]) + except: + standard_rights = rights["rights"].to_list() for right in standard_rights: aces.append( @@ -857,7 +860,6 @@ def get_template_permissions(self, template: LDAPEntry): if ( EXTENDED_RIGHTS_NAME_MAP["Enroll"] in rights["extended_rights"] - or EXTENDED_RIGHTS_NAME_MAP["AutoEnroll"] in rights["extended_rights"] ): enrollment_rights.append(self.connection.lookup_sid(sid).get("name")) if ( @@ -1029,7 +1031,6 @@ def can_user_enroll_in_template(self, template: LDAPEntry): EXTENDED_RIGHTS_NAME_MAP["All-Extended-Rights"] in rights["extended_rights"] or EXTENDED_RIGHTS_NAME_MAP["Enroll"] in rights["extended_rights"] - or EXTENDED_RIGHTS_NAME_MAP["AutoEnroll"] in rights["extended_rights"] or CERTIFICATE_RIGHTS.GENERIC_ALL in rights["rights"] ): enrollable_sids.append(sid) @@ -1076,7 +1077,10 @@ def get_ca_permissions(self, ca: LDAPEntry): for sid, rights in security.aces.items(): if self.hide_admins and is_admin_sid(sid): continue - ca_rights = rights["rights"].to_list() + try: + ca_rights = list(rights["rights"]) + except: + ca_rights = rights["rights"].to_list() for ca_right in ca_rights: if ca_right not in access_rights: access_rights[ca_right] = [ diff --git a/certipy/commands/forge.py b/certipy/commands/forge.py index 79db477..a6ef73b 100755 --- a/certipy/commands/forge.py +++ b/certipy/commands/forge.py @@ -4,7 +4,6 @@ from certipy.lib.certificate import ( PRINCIPAL_NAME, - NameOID, UTF8String, cert_id_to_parts, create_pfx, @@ -13,6 +12,9 @@ get_subject_from_str, load_pfx, x509, + asn1x509, + NTDS_CA_SECURITY_EXT, + szOID_NTDS_OBJECTSID ) from certipy.lib.logger import logging @@ -23,6 +25,7 @@ def __init__( ca_pfx: str = None, upn: str = None, dns: str = None, + sid: str = None, template: str = None, subject: str = None, issuer: str = None, @@ -35,6 +38,7 @@ def __init__( self.ca_pfx = ca_pfx self.alt_upn = upn self.alt_dns = dns + self.alt_sid = sid self.template = template self.subject = subject self.issuer = issuer @@ -200,6 +204,30 @@ def forge(self): False, ) + alt_sid = self.alt_sid + if alt_sid: + if type(alt_sid) == str: + alt_sid = alt_sid.encode() + + sid_extension = asn1x509.GeneralNames([asn1x509.GeneralName( + { + "other_name": asn1x509.AnotherName( + { + "type_id": szOID_NTDS_OBJECTSID, + "value": asn1x509.OctetString(alt_sid).retag( + {"explicit": 0} + ), + } + ) + } + )] + ) + + cert = cert.add_extension( + x509.UnrecognizedExtension(NTDS_CA_SECURITY_EXT, sid_extension.dump()), + False, + ) + cert = cert.sign(ca_key, signature_hash_algorithm()) pfx = create_pfx(key, cert) diff --git a/certipy/commands/parsers/forge.py b/certipy/commands/parsers/forge.py index 9f7fc07..e0cdeb7 100755 --- a/certipy/commands/parsers/forge.py +++ b/certipy/commands/parsers/forge.py @@ -22,6 +22,7 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable ) subparser.add_argument("-upn", action="store", metavar="alternative UPN") subparser.add_argument("-dns", action="store", metavar="alternative DNS") + subparser.add_argument("-sid", action="store", metavar="alternative Object SID") subparser.add_argument( "-template", action="store", diff --git a/certipy/commands/parsers/relay.py b/certipy/commands/parsers/relay.py index ecd3e44..789a440 100755 --- a/certipy/commands/parsers/relay.py +++ b/certipy/commands/parsers/relay.py @@ -32,6 +32,7 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable group.add_argument("-upn", action="store", metavar="alternative UPN") group.add_argument("-dns", action="store", metavar="alternative DNS") + group.add_argument("-sid", action="store", metavar="alternative Object SID") group.add_argument( "-retrieve", action="store", diff --git a/certipy/commands/parsers/req.py b/certipy/commands/parsers/req.py index 8e8f5a5..8a7cf3b 100755 --- a/certipy/commands/parsers/req.py +++ b/certipy/commands/parsers/req.py @@ -26,6 +26,7 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable ) group.add_argument("-upn", action="store", metavar="alternative UPN") group.add_argument("-dns", action="store", metavar="alternative DNS") + group.add_argument("-sid", action="store", metavar="alternative Object SID") group.add_argument( "-subject", action="store", diff --git a/certipy/commands/relay.py b/certipy/commands/relay.py index 1ded291..db89a46 100755 --- a/certipy/commands/relay.py +++ b/certipy/commands/relay.py @@ -191,6 +191,7 @@ def _run(self): self.username, alt_dns=self.adcs_relay.dns, alt_upn=self.adcs_relay.upn, + alt_sid=self.adcs_relay.sid, key_size=self.adcs_relay.key_size, ) @@ -374,6 +375,7 @@ def __init__( template=None, upn=None, dns=None, + sid=None, retrieve=None, key_size: int = 2048, out=None, @@ -389,6 +391,7 @@ def __init__( self.template = template self.upn = upn self.dns = dns + self.sid = sid self.request_id = int(retrieve) self.key_size = key_size self.out = out diff --git a/certipy/commands/req.py b/certipy/commands/req.py index 80610e0..7e06b96 100755 --- a/certipy/commands/req.py +++ b/certipy/commands/req.py @@ -524,6 +524,7 @@ def __init__( template: str = None, upn: str = None, dns: str = None, + sid: str = None, subject: str = None, retrieve: int = 0, on_behalf_of: str = None, @@ -545,6 +546,7 @@ def __init__( self.template = template self.alt_upn = upn self.alt_dns = dns + self.alt_sid = sid self.subject = subject self.request_id = int(retrieve) self.on_behalf_of = on_behalf_of @@ -669,6 +671,7 @@ def request(self) -> bool: username, alt_dns=self.alt_dns, alt_upn=self.alt_upn, + alt_sid=self.alt_sid, key=self.key, key_size=self.key_size, subject=self.subject, diff --git a/certipy/lib/certificate.py b/certipy/lib/certificate.py index c37a9e6..dcf3e9e 100755 --- a/certipy/lib/certificate.py +++ b/certipy/lib/certificate.py @@ -4,7 +4,7 @@ import os import struct import sys -from typing import Callable, List, Tuple +from typing import List, Tuple from asn1crypto import cms as asn1cms from asn1crypto import core as asn1core @@ -50,17 +50,29 @@ "DC": NameOID.DOMAIN_COMPONENT, } -PRINCIPAL_NAME = x509.ObjectIdentifier("1.3.6.1.4.1.311.20.2.3") +asn1x509.ExtensionId._map.update( + { + "1.3.6.1.4.1.311.25.2": "security_ext", + } +) -NTDS_CA_SECURITY_EXT = x509.ObjectIdentifier("1.3.6.1.4.1.311.25.2") +asn1x509.Extension._oid_specs.update( + { + "security_ext": asn1x509.GeneralNames, + } +) +PRINCIPAL_NAME = x509.ObjectIdentifier("1.3.6.1.4.1.311.20.2.3") +NTDS_CA_SECURITY_EXT = x509.ObjectIdentifier("1.3.6.1.4.1.311.25.2") +NTDS_OBJECTSID = x509.ObjectIdentifier("1.3.6.1.4.1.311.25.2.1") szOID_RENEWAL_CERTIFICATE = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.13.1") szOID_ENCRYPTED_KEY_HASH = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.21.21") szOID_PRINCIPAL_NAME = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.20.2.3") szOID_ENCRYPTED_KEY_HASH = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.21.21") szOID_CMC_ADD_ATTRIBUTES = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.10.10.1") - +szOID_NTDS_CA_SECURITY_EXT = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.25.2") +szOID_NTDS_OBJECTSID = asn1cms.ObjectIdentifier("1.3.6.1.4.1.311.25.2.1") class TaggedCertificationRequest(asn1core.Sequence): _fields = [ @@ -317,6 +329,7 @@ def create_csr( username: str, alt_dns: bytes = None, alt_upn: bytes = None, + alt_sid: bytes = None, key: rsa.RSAPrivateKey = None, key_size: int = 2048, subject: str = None, @@ -360,8 +373,6 @@ def create_csr( alt_dns = alt_dns.decode() general_names.append(asn1x509.GeneralName({"dns_name": alt_dns})) - # sans.append(x509.DNSName(alt_dns)) - if alt_upn: if type(alt_upn) == bytes: alt_upn = alt_upn.decode() @@ -393,6 +404,34 @@ def create_csr( cri_attributes.append(cri_attribute) + if alt_sid: + if type(alt_sid) == str: + alt_sid = alt_sid.encode() + + + san_extension = asn1x509.Extension( + {"extn_id": "security_ext", "extn_value": [asn1x509.GeneralName( + { + "other_name": asn1x509.AnotherName( + { + "type_id": szOID_NTDS_OBJECTSID, + "value": asn1x509.OctetString(alt_sid).retag( + {"explicit": 0} + ), + } + ) + } + )]} + ) + + set_of_extensions = asn1csr.SetOfExtensions([[san_extension]]) + + cri_attribute = asn1csr.CRIAttribute( + {"type": "extension_request", "values": set_of_extensions} + ) + + cri_attributes.append(cri_attribute) + if renewal_cert: cri_attributes.append( asn1csr.CRIAttribute( diff --git a/certipy/lib/constants.py b/certipy/lib/constants.py index fec880f..f4968a6 100755 --- a/certipy/lib/constants.py +++ b/certipy/lib/constants.py @@ -199,7 +199,7 @@ class MS_PKI_CERTIFICATE_AUTHORITY_FLAG(IntFlag): "1.3.6.1.4.1.311.10.3.2": "Microsoft Time Stamping", "1.3.6.1.4.1.311.76.8.1": "Microsoft Publishe", "1.3.6.1.5.5.7.3.2": "Client Authentication", - "1.3.6.1.5.2.3.4": "PKIINIT Client Authentication", + "1.3.6.1.5.2.3.4": "PKINIT Client Authentication", "1.3.6.1.4.1.311.10.3.13": "Lifetime Signing", "2.5.29.37.0": "Any Purpose", "1.3.6.1.4.1.311.64.1.1": "Server Trust", diff --git a/certipy/lib/ldap.py b/certipy/lib/ldap.py index 7146a83..7ec5020 100755 --- a/certipy/lib/ldap.py +++ b/certipy/lib/ldap.py @@ -301,7 +301,7 @@ def machine_account_quota(self): ], ) if len(results) != 1: - return None + return 0 result = results[0] machine_account_quota = result.get("ms-DS-MachineAccountQuota") @@ -354,13 +354,11 @@ def get_user_sids(self, username: str): if primary_group_id is not None: sids.add("%s-%d" % (self.domain_sid, primary_group_id)) - # Add Domain Computers group if Machine Account Quota > 0 - if self.machine_account_quota > 0: - logging.debug( - "Adding Domain Computers to list of current user's SIDs (Machine Account Quota: %d > 0)" - % self.machine_account_quota - ) - sids.add("%s-515" % self.domain_sid) + # Add Domain Computers group + logging.debug( + "Adding Domain Computers to list of current user's SIDs" + ) + sids.add("%s-515" % self.domain_sid) dns = [user.get("distinguishedName")] for sid in sids: diff --git a/certipy/lib/structs.py b/certipy/lib/structs.py index 51dc656..3bf8572 100755 --- a/certipy/lib/structs.py +++ b/certipy/lib/structs.py @@ -13,7 +13,10 @@ def to_list(self): return members def to_str_list(self): - return list(map(lambda x: str(x), self.to_list())) + try: + return list(map(lambda x: str(x), list(self))) + except: + return list(map(lambda x: str(x), self.to_list())) def __str__(self): cls = self.__class__ diff --git a/customqueries.json b/customqueries.json index 5a1907b..9eb7801 100644 --- a/customqueries.json +++ b/customqueries.json @@ -88,7 +88,7 @@ "queryList": [ { "final": true, - "query": "MATCH (n:GPO) WHERE n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage`) RETURN n" + "query": "MATCH (n:GPO) WHERE n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or n.`Any Purpose` = True) RETURN n" } ] }, @@ -98,7 +98,7 @@ "queryList": [ { "final": true, - "query": "MATCH p=allShortestPaths((g {owned:true})-[*1..]->(n:GPO)) WHERE g<>n and n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage`) return p" + "query": "MATCH p=allShortestPaths((g {owned:true})-[*1..]->(n:GPO)) WHERE g<>n and n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or n.`Any Purpose` = True) RETURN p" } ] }, @@ -108,7 +108,7 @@ "queryList": [ { "final": true, - "query": "MATCH (n:GPO) WHERE n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or 'Certificate Request Agent' IN n.`Extended Key Usage`) RETURN n" + "query": "MATCH (n:GPO) WHERE n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or 'Certificate Request Agent' IN n.`Extended Key Usage` or n.`Any Purpose` = True) RETURN n" } ] }, @@ -118,7 +118,7 @@ "queryList": [ { "final": true, - "query": "MATCH p=allShortestPaths((g {owned:true})-[*1..]->(n:GPO)) WHERE g<>n and n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or 'Certificate Request Agent' IN n.`Extended Key Usage`) return p" + "query": "MATCH p=allShortestPaths((g {owned:true})-[*1..]->(n:GPO)) WHERE g<>n and n.type = 'Certificate Template' and n.`Enabled` = true and (n.`Extended Key Usage` = [] or 'Any Purpose' IN n.`Extended Key Usage` or n.`Any Purpose` = True or 'Certificate Request Agent' IN n.`Extended Key Usage`) RETURN p" } ] }, @@ -213,4 +213,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/setup.py b/setup.py index 09bacd7..417d6b1 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="certipy-ad", - version="4.3.0", + version="4.5.1", license="MIT", author="ly4k", url="https://github.com/ly4k/Certipy", @@ -13,17 +13,19 @@ long_description_content_type="text/markdown", install_requires=[ "asn1crypto", - "cryptography>=37.0", + "cryptography>=39.0", "impacket", "ldap3", "pyasn1==0.4.8", "dnspython", "dsinternals", - "pyopenssl>=22.0.0", + "pyopenssl>=23.0.0", "requests", "requests_ntlm", 'winacl; platform_system=="Windows"', 'wmi; platform_system=="Windows"', + "pycryptodome", + "unicrypto" ], packages=[ "certipy",