Skip to content

Commit 572292b

Browse files
authored
Merge pull request #32 from skelsec/main
Main
2 parents 1d4450b + 13c305b commit 572292b

File tree

9 files changed

+117
-43
lines changed

9 files changed

+117
-43
lines changed

minikerberos/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
__version__ = "0.4.1"
2+
__version__ = "0.4.2"
33
__banner__ = \
44
"""
55
# minikerberos %s

minikerberos/aioclient.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def tgs_from_ccache(self, spn_user:KerberosSPN):
355355
except Exception as e:
356356
return None, None, None, e
357357

358-
async def get_TGS(self, spn_user:KerberosSPN, override_etype = None, is_linux = False):
358+
async def get_TGS(self, spn_user:KerberosSPN, override_etype = None, is_linux = False, flags = ['forwardable','renewable','renewable_ok', 'canonicalize']):
359359
"""
360360
Requests a TGS ticket for the specified user.
361361
Retruns the TGS ticket, end the decrpyted encTGSRepPart.
@@ -380,7 +380,7 @@ async def get_TGS(self, spn_user:KerberosSPN, override_etype = None, is_linux =
380380
logger.debug('Constructing TGS request for user %s' % spn_user.get_formatted_pname())
381381
now = datetime.datetime.now(datetime.timezone.utc)
382382
kdc_req_body = {}
383-
kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','renewable_ok', 'canonicalize']))
383+
kdc_req_body['kdc-options'] = KDCOptions(set(flags))
384384
kdc_req_body['realm'] = spn_user.domain.upper()
385385
kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': spn_user.get_principalname()})
386386
kdc_req_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
@@ -778,4 +778,32 @@ def truncate_key(value, keysize):
778778
encasrep = EncASRepPart.load(dec_data).native
779779
cipher = _enctype_table[ int(encasrep['key']['keytype'])]
780780
session_key = Key(cipher.enctype, encasrep['key']['keyvalue'])
781-
return encasrep, session_key, cipher
781+
return encasrep, session_key, cipher
782+
783+
async def get_referral_ticket(self, target_domain, target_ip = None):
784+
"""Cross domain TGT referral"""
785+
"""If target_ip is not set, the target domain will be used as the hostname for the newly created connection"""
786+
from minikerberos.common.factory import KerberosClientFactory
787+
from minikerberos.common.kirbi import Kirbi
788+
789+
crossrealm_spn = KerberosSPN()
790+
crossrealm_spn.username = target_domain
791+
crossrealm_spn.service = 'krbtgt'
792+
crossrealm_spn.domain = self.credential.domain.upper()
793+
794+
await self.get_TGT()
795+
logger.debug('Getting TGS for otherdomain krbtgt')
796+
tgs, encpart, key = await self.get_TGS(crossrealm_spn)
797+
logger.debug('Got referral ticket!')
798+
799+
800+
kirbi = Kirbi.from_ticketdata(tgs, encpart)
801+
802+
target_addr = target_domain
803+
if target_ip is not None:
804+
target_addr = target_ip
805+
newt = self.target.get_newtarget(target_addr)
806+
newc = KerberosCredential.from_kirbi(kirbi, encoding='kirbi')
807+
new_factory = KerberosClientFactory(newt, newc, newt.proxies)
808+
809+
return tgs, encpart, key, new_factory

minikerberos/common/creds.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import platform
66
import copy
77
from typing import List
8+
import os
89

910

1011
from unicrypto import hashlib
@@ -210,6 +211,11 @@ def from_keytab(keytab_file_path: str, principal: str, realm: str, encoding = 'f
210211
@staticmethod
211212
def from_ccache(data, principal: str = None, realm: str = None, encoding = 'file') -> KerberosCredential:
212213
"""Returns a kerberos credential object with CCACHE database"""
214+
if data is None:
215+
ccache_path = os.environ.get('KRB5CCNAME')
216+
if ccache_path is None:
217+
raise Exception('No CCACHE data or path provided!')
218+
data = ccache_path
213219
data = get_encoded_data(data, encoding=encoding)
214220
k = KerberosCredential()
215221
k.username = principal

minikerberos/common/spn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def from_spn(s, override_realm:str = None):
3838
kt.service, kt.username = t.split('/')
3939
else:
4040
if s.find('@') != -1:
41-
kt.username, kt.domain = s.split('@')
41+
kt.username, kt.domain = s.rsplit('@', 1)
4242
else:
4343
kt.username = s
4444
if override_realm is None or override_realm == '':

minikerberos/common/target.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11

22
from asysocks.unicomm.common.target import UniTarget, UniProto
3+
import copy
34

45
class KerberosTarget(UniTarget):
56
def __init__(self, ip:str = None, proxies = None, protocol = UniProto.CLIENT_TCP, timeout = 10, port = 88):
67
UniTarget.__init__(self, ip, port , protocol, timeout=timeout, proxies = proxies, dc_ip = ip)
78

9+
def get_newtarget(self, ip, port=88, hostname = None):
10+
return KerberosTarget(
11+
ip,
12+
port = port,
13+
protocol = self.protocol,
14+
timeout = self.timeout,
15+
proxies=copy.deepcopy(self.proxies)
16+
)
17+
818
def __str__(self):
919
t = '===KerberosTarget===\r\n'
1020
for k in self.__dict__:

minikerberos/examples/crosstest.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import logging
3+
import asyncio
4+
import copy
5+
from minikerberos.common.factory import KerberosClientFactory, kerberos_url_help_epilog
6+
from minikerberos.common.target import KerberosTarget
7+
from asysocks.unicomm.common.target import UniProto
8+
from minikerberos.common.spn import KerberosSPN
9+
from minikerberos.aioclient import AIOKerberosClient
10+
from minikerberos.common.kirbi import Kirbi
11+
from minikerberos.common.creds import KerberosCredential
12+
from minikerberos import logger
13+
14+
async def getTGS(kerberos_url, kirbifile = None):
15+
if isinstance(spn, str):
16+
spn = KerberosSPN.from_spn(spn)
17+
18+
cu = KerberosClientFactory.from_url(kerberos_url)
19+
client = cu.get_client()
20+
logging.debug('Getting TGT')
21+
await client.get_TGT()
22+
logging.debug('Getting TGS for otherdomain krbtgt')
23+
ref_tgs, ref_encpart, ref_key, new_factory = await client.get_referral_ticket(spn.domain)
24+
kirbi = Kirbi.from_ticketdata(ref_tgs, ref_encpart)
25+
print(str(kirbi))
26+
if kirbifile is not None:
27+
kirbi.to_file(kirbifile)
28+
29+
logging.info('Done!')
30+
31+
def main():
32+
import argparse
33+
34+
parser = argparse.ArgumentParser(description='Polls the kerberos service for a TGS for the sepcified user and specified service', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
35+
parser.add_argument('-v', '--verbose', action='count', default=0)
36+
parser.add_argument('--kirbi', help='kirbi file to store the TGT ticket in, otherwise kirbi will be printed to stdout')
37+
parser.add_argument('kerberos_url', help='the kerberos target string. ')
38+
39+
logger.setLevel(logging.DEBUG)
40+
args = parser.parse_args()
41+
if args.verbose == 0:
42+
logging.basicConfig(level=logging.INFO)
43+
elif args.verbose == 1:
44+
logging.basicConfig(level=logging.DEBUG)
45+
else:
46+
logging.basicConfig(level=1)
47+
48+
asyncio.run(getTGS(args.kerberos_url, args.kirbi))
49+
50+
if __name__ == '__main__':
51+
main()

minikerberos/examples/getTGS.py

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
from minikerberos.aioclient import AIOKerberosClient
77
from minikerberos.common.kirbi import Kirbi
88

9-
async def getTGS(kerberos_url, spn, kirbifile = None, ccachefile = None):
9+
async def getTGS(kerberos_url, spn, kirbifile = None, ccachefile = None, cross_domain = False):
1010
if isinstance(spn, str):
1111
spn = KerberosSPN.from_spn(spn)
1212

1313
cu = KerberosClientFactory.from_url(kerberos_url)
1414
client = cu.get_client()
1515
logging.debug('Getting TGT')
1616
await client.get_TGT()
17+
if cross_domain is True:
18+
logging.debug('Getting TGS for otherdomain krbtgt')
19+
_, _, _, new_factory = await client.get_referral_ticket(spn.domain)
20+
client = new_factory.get_client()
1721
logging.debug('Getting TGS')
1822
tgs, encpart, key = await client.get_TGS(spn)
1923
if ccachefile is not None:
@@ -27,44 +31,14 @@ async def getTGS(kerberos_url, spn, kirbifile = None, ccachefile = None):
2731

2832
logging.info('Done!')
2933

30-
async def amain(args):
31-
32-
if args.spn.find('@') == -1:
33-
raise Exception('SPN must contain @')
34-
t, domain = args.spn.split('@')
35-
if t.find('/') != -1:
36-
service, hostname = t.split('/')
37-
else:
38-
hostname = t
39-
service = None
40-
41-
spn = KerberosSPN()
42-
spn.username = hostname
43-
spn.service = service
44-
spn.domain = domain
45-
46-
cu = KerberosClientFactory.from_url(args.kerberos_connection_url)
47-
ccred = cu.get_creds()
48-
target = cu.get_target()
49-
50-
logging.debug('Getting TGT')
51-
52-
client = AIOKerberosClient(ccred, target)
53-
logging.debug('Getting TGT')
54-
await client.get_TGT()
55-
logging.debug('Getting TGS')
56-
await client.get_TGS(spn)
57-
58-
client.ccache.to_file(args.ccache)
59-
logging.info('Done!')
60-
6134
def main():
6235
import argparse
6336

6437
parser = argparse.ArgumentParser(description='Polls the kerberos service for a TGS for the sepcified user and specified service', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
6538
parser.add_argument('-v', '--verbose', action='count', default=0)
6639
parser.add_argument('--ccache', help='CCACHE file to store the TGT ticket in, otherwise kirbi will be printed to stdout')
6740
parser.add_argument('--kirbi', help='kirbi file to store the TGT ticket in, otherwise kirbi will be printed to stdout')
41+
parser.add_argument('--cross-domain', action='store_true', help='SPN is in another domain.')
6842
parser.add_argument('kerberos_url', help='the kerberos target string. ')
6943
parser.add_argument('spn', help='the SPN to request the TGS for. Must be in the format of service/host@domain')
7044

@@ -77,7 +51,7 @@ def main():
7751
else:
7852
logging.basicConfig(level=1)
7953

80-
asyncio.run(amain(args))
54+
asyncio.run(getTGS(args.kerberos_url, args.spn, args.kirbi, args.ccache, args.cross_domain))
8155

8256
if __name__ == '__main__':
8357
main()

minikerberos/protocol/encryption.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,11 @@ def __init__(self, enctype:Enctype, contents:bytes):
696696
self.enctype = enctype
697697
self.contents = contents
698698

699+
def __str__(self):
700+
temp = '<EMPTY>'
701+
if self.contents is not None:
702+
temp = self.contents.hex()
703+
return 'Key(%d, %s)' % (self.enctype, temp)
699704

700705
def random_to_key(enctype, seed):
701706
e = _get_enctype_profile(enctype)
@@ -717,7 +722,7 @@ def encrypt(key, keyusage, plaintext, confounder=None):
717722

718723

719724
def decrypt(key, keyusage, ciphertext):
720-
# Throw InvalidChecksum on checksum failure. Throw ValueError on
725+
# Throw InvalidChecksum on checksum failure. Throw ValueError on
721726
# invalid key enctype or malformed ciphertext.
722727
e = _get_enctype_profile(key.enctype)
723728
return e.decrypt(key, keyusage, ciphertext)

setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@
4444
"Operating System :: OS Independent",
4545
],
4646
install_requires=[
47-
'asn1crypto>=1.3.0',
48-
'oscrypto>=1.2.1',
49-
'asysocks==0.2.7',
50-
'unicrypto==0.0.10',
47+
'asn1crypto>=1.5.1',
48+
'oscrypto>=1.3.0',
49+
'asysocks>=0.2.8',
50+
'unicrypto>=0.0.10',
5151
'tqdm',
5252
'six',
5353
],

0 commit comments

Comments
 (0)