krbconverter 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- krbconverter-0.1.0/LICENSE +21 -0
- krbconverter-0.1.0/PKG-INFO +96 -0
- krbconverter-0.1.0/README.md +75 -0
- krbconverter-0.1.0/pyproject.toml +37 -0
- krbconverter-0.1.0/setup.cfg +4 -0
- krbconverter-0.1.0/src/krb/__init__.py +0 -0
- krbconverter-0.1.0/src/krb/convertor.py +115 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/PKG-INFO +96 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/SOURCES.txt +14 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/dependency_links.txt +1 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/entry_points.txt +2 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/requires.txt +10 -0
- krbconverter-0.1.0/src/krbconverter.egg-info/top_level.txt +2 -0
- krbconverter-0.1.0/src/main.py +22 -0
- krbconverter-0.1.0/tests/test_convertor.py +16 -0
- krbconverter-0.1.0/tests/test_genCachePath.py +56 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pengfei liu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: krbconverter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This tool can convert kerberos ticket of format .kirbi to standard MIT CCACHE format
|
|
5
|
+
Author-email: Pengfei Liu <pengfei.liu@casd.eu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: kerberos,ccache
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: pyasn1
|
|
12
|
+
Requires-Dist: minikerberos
|
|
13
|
+
Requires-Dist: asn1crypto
|
|
14
|
+
Requires-Dist: typer
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
18
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
19
|
+
Requires-Dist: isort>=5.12; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# krbTicketConvertor
|
|
23
|
+
|
|
24
|
+
The objective of this project is
|
|
25
|
+
1. to get a kerberos ticket(delegable TGT) with tool such as [rubeus](https://github.com/ghostpack/rubeus) or homemade.
|
|
26
|
+
2. to convert a kerberos ticket from format `.kirbi` to MIT Kerberos cache file.
|
|
27
|
+
|
|
28
|
+
## Get a delegated TGT
|
|
29
|
+
|
|
30
|
+
The first step is to get a TGT. We use a tool called `rubeus`, https://github.com/ghostpack/rubeus
|
|
31
|
+
|
|
32
|
+
The main code of how rubeus get the TGT is in class [LSA.cs](https://github.com/GhostPack/Rubeus/blob/master/Rubeus/lib/LSA.cs)
|
|
33
|
+
|
|
34
|
+
The function is called `RequestFakeDelegTicket`
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## tgtdeleg of LSA
|
|
38
|
+
|
|
39
|
+
The `tgtdeleg` using @gentilkiwi's Kekeo trick (tgt::deleg) that abuses the `Kerberos GSS-API` to retrieve a
|
|
40
|
+
usable TGT for the current user without needing elevation on the host. `AcquireCredentialsHandle()` is used to get
|
|
41
|
+
a handle to the current user's Kerberos security credentials, and `InitializeSecurityContext()` with the `ISC_REQ_DELEGATE`
|
|
42
|
+
flag and a target `SPN of HOST/DC.domain.com` to prepare a fake delegate context to send to the DC.
|
|
43
|
+
This results in an AP-REQ in the GSS-API output that contains a KRB_CRED in the authenticator checksum.
|
|
44
|
+
|
|
45
|
+
The service ticket session key is extracted from the local Kerberos cache and is used to decrypt the `KRB_CRED` in the
|
|
46
|
+
authenticator, resulting in a usable TGT `.kirbi`.
|
|
47
|
+
|
|
48
|
+
If automatic `target/domain` extraction is failing, a known SPN of a service configured with unconstrained delegation can be specified with /target:SPN.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## .kirbi vs CCACHE
|
|
52
|
+
|
|
53
|
+
As you can notice, the output of the `Kerberos ticket` is in format `.kirbi` which is not the standard MIT cache format.
|
|
54
|
+
And many tools only support MIT cache format.
|
|
55
|
+
|
|
56
|
+
| Feature | KIRBI (Windows/Internal) | CCACHE (MIT/Linux) |
|
|
57
|
+
|------------|------------------------------------|------------------------------|
|
|
58
|
+
| Encoding | ASN.1 DER (usually) | Specialized Binary Format |
|
|
59
|
+
| Origin | Microsoft KRB-CRED | MIT Kerberos V5 |
|
|
60
|
+
| Usage | Pass-the-Ticket (Rubeus/Mimikatz) | Standard Linux Auth (kinit) |
|
|
61
|
+
| Structure | Nested ASN.1 Sequences | Header + Credential List |
|
|
62
|
+
|
|
63
|
+
## The python implementation
|
|
64
|
+
|
|
65
|
+
This folder shows an implementation in python.
|
|
66
|
+
|
|
67
|
+
We use the `ccache.py` of the project [minikerberos](https://github.com/skelsec/minikerberos/blob/main/minikerberos/common/ccache.py)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## The TGT deleg of ssh
|
|
71
|
+
|
|
72
|
+
Here is the step-by-step breakdown of how that `TGT` travels from your `Windows machine` to the Linux server.
|
|
73
|
+
|
|
74
|
+
### The TGT Generation (On your Windows Machine)
|
|
75
|
+
|
|
76
|
+
When you log in to your Windows server, a TGT is generated and stored in your local cache (i.e. LSASS).
|
|
77
|
+
When you run `ssh -k user@srv1.example.com`:
|
|
78
|
+
1. `Requesting a Service Ticket`: Your SSH client asks your local Kerberos implementation for a Service Ticket for the remote server (host/srv1.example.com).
|
|
79
|
+
2. The "Forwardable" Flag: Because you specified delegation, your client asks the Key Distribution Center (KDC) for
|
|
80
|
+
a `new, forwardable TGT`.
|
|
81
|
+
3. The Package: The KDC sends back a TGT encrypted in a way that only you can open. Your `Windows machine` then wraps
|
|
82
|
+
this TGT inside the `GSSAPI authentication layer of the SSH protocol.2`.
|
|
83
|
+
|
|
84
|
+
### The TGT Forwarding
|
|
85
|
+
|
|
86
|
+
The TGT is sent over the encrypted SSH tunnel. It is never sent in the clear.It is bundled inside the SSH
|
|
87
|
+
"User Authentication" packet. It travels from your `Windows machine` to the `sshd daemon on the Linux server`.
|
|
88
|
+
|
|
89
|
+
### The CCache file creation
|
|
90
|
+
|
|
91
|
+
(On the Linux Server)Once the Linux server receives your delegated credentials, the `SSH Daemon (sshd)` manage the rest.
|
|
92
|
+
1. The sshd process receives the GSSAPI data containing the TGT
|
|
93
|
+
2. The sshd (which usually runs as root initially) creates a new, temporary file. By default, this is usually located in /tmp/krb5cc_UID_XXXXXX
|
|
94
|
+
3. The sshd changes the ownership of this file to your user account and sets permissions so only you can read it (600).
|
|
95
|
+
4. The sshd sets the `KRB5CCNAME environment variable` in your shell session. This tells other tools (e.g. klist, nfs, or ldapsearch)
|
|
96
|
+
exactly where to find that ticket.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# krbTicketConvertor
|
|
2
|
+
|
|
3
|
+
The objective of this project is
|
|
4
|
+
1. to get a kerberos ticket(delegable TGT) with tool such as [rubeus](https://github.com/ghostpack/rubeus) or homemade.
|
|
5
|
+
2. to convert a kerberos ticket from format `.kirbi` to MIT Kerberos cache file.
|
|
6
|
+
|
|
7
|
+
## Get a delegated TGT
|
|
8
|
+
|
|
9
|
+
The first step is to get a TGT. We use a tool called `rubeus`, https://github.com/ghostpack/rubeus
|
|
10
|
+
|
|
11
|
+
The main code of how rubeus get the TGT is in class [LSA.cs](https://github.com/GhostPack/Rubeus/blob/master/Rubeus/lib/LSA.cs)
|
|
12
|
+
|
|
13
|
+
The function is called `RequestFakeDelegTicket`
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## tgtdeleg of LSA
|
|
17
|
+
|
|
18
|
+
The `tgtdeleg` using @gentilkiwi's Kekeo trick (tgt::deleg) that abuses the `Kerberos GSS-API` to retrieve a
|
|
19
|
+
usable TGT for the current user without needing elevation on the host. `AcquireCredentialsHandle()` is used to get
|
|
20
|
+
a handle to the current user's Kerberos security credentials, and `InitializeSecurityContext()` with the `ISC_REQ_DELEGATE`
|
|
21
|
+
flag and a target `SPN of HOST/DC.domain.com` to prepare a fake delegate context to send to the DC.
|
|
22
|
+
This results in an AP-REQ in the GSS-API output that contains a KRB_CRED in the authenticator checksum.
|
|
23
|
+
|
|
24
|
+
The service ticket session key is extracted from the local Kerberos cache and is used to decrypt the `KRB_CRED` in the
|
|
25
|
+
authenticator, resulting in a usable TGT `.kirbi`.
|
|
26
|
+
|
|
27
|
+
If automatic `target/domain` extraction is failing, a known SPN of a service configured with unconstrained delegation can be specified with /target:SPN.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## .kirbi vs CCACHE
|
|
31
|
+
|
|
32
|
+
As you can notice, the output of the `Kerberos ticket` is in format `.kirbi` which is not the standard MIT cache format.
|
|
33
|
+
And many tools only support MIT cache format.
|
|
34
|
+
|
|
35
|
+
| Feature | KIRBI (Windows/Internal) | CCACHE (MIT/Linux) |
|
|
36
|
+
|------------|------------------------------------|------------------------------|
|
|
37
|
+
| Encoding | ASN.1 DER (usually) | Specialized Binary Format |
|
|
38
|
+
| Origin | Microsoft KRB-CRED | MIT Kerberos V5 |
|
|
39
|
+
| Usage | Pass-the-Ticket (Rubeus/Mimikatz) | Standard Linux Auth (kinit) |
|
|
40
|
+
| Structure | Nested ASN.1 Sequences | Header + Credential List |
|
|
41
|
+
|
|
42
|
+
## The python implementation
|
|
43
|
+
|
|
44
|
+
This folder shows an implementation in python.
|
|
45
|
+
|
|
46
|
+
We use the `ccache.py` of the project [minikerberos](https://github.com/skelsec/minikerberos/blob/main/minikerberos/common/ccache.py)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## The TGT deleg of ssh
|
|
50
|
+
|
|
51
|
+
Here is the step-by-step breakdown of how that `TGT` travels from your `Windows machine` to the Linux server.
|
|
52
|
+
|
|
53
|
+
### The TGT Generation (On your Windows Machine)
|
|
54
|
+
|
|
55
|
+
When you log in to your Windows server, a TGT is generated and stored in your local cache (i.e. LSASS).
|
|
56
|
+
When you run `ssh -k user@srv1.example.com`:
|
|
57
|
+
1. `Requesting a Service Ticket`: Your SSH client asks your local Kerberos implementation for a Service Ticket for the remote server (host/srv1.example.com).
|
|
58
|
+
2. The "Forwardable" Flag: Because you specified delegation, your client asks the Key Distribution Center (KDC) for
|
|
59
|
+
a `new, forwardable TGT`.
|
|
60
|
+
3. The Package: The KDC sends back a TGT encrypted in a way that only you can open. Your `Windows machine` then wraps
|
|
61
|
+
this TGT inside the `GSSAPI authentication layer of the SSH protocol.2`.
|
|
62
|
+
|
|
63
|
+
### The TGT Forwarding
|
|
64
|
+
|
|
65
|
+
The TGT is sent over the encrypted SSH tunnel. It is never sent in the clear.It is bundled inside the SSH
|
|
66
|
+
"User Authentication" packet. It travels from your `Windows machine` to the `sshd daemon on the Linux server`.
|
|
67
|
+
|
|
68
|
+
### The CCache file creation
|
|
69
|
+
|
|
70
|
+
(On the Linux Server)Once the Linux server receives your delegated credentials, the `SSH Daemon (sshd)` manage the rest.
|
|
71
|
+
1. The sshd process receives the GSSAPI data containing the TGT
|
|
72
|
+
2. The sshd (which usually runs as root initially) creates a new, temporary file. By default, this is usually located in /tmp/krb5cc_UID_XXXXXX
|
|
73
|
+
3. The sshd changes the ownership of this file to your user account and sets permissions so only you can read it (600).
|
|
74
|
+
4. The sshd sets the `KRB5CCNAME environment variable` in your shell session. This tells other tools (e.g. klist, nfs, or ldapsearch)
|
|
75
|
+
exactly where to find that ticket.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "krbconverter"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "This tool can convert kerberos ticket of format .kirbi to standard MIT CCACHE format"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Pengfei Liu", email = "pengfei.liu@casd.eu" }
|
|
10
|
+
]
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
keywords = ["kerberos", "ccache", ]
|
|
14
|
+
|
|
15
|
+
dependencies = ["pyasn1", "minikerberos","asn1crypto", "typer"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
# Development dependencies (install with: pip install -e ".[dev]")
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=7.0",
|
|
22
|
+
"pytest-cov>=4.0",
|
|
23
|
+
"black>=23.0", # Code formatter
|
|
24
|
+
"isort>=5.12", # Import sorter
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
# This allows the user to run 'convert-tgt' directly in the terminal
|
|
29
|
+
convert-tgt = "krb.main:main"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
33
|
+
build-backend = "setuptools.build_meta"
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
python_files = "test_*.py"
|
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import getpass
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from asn1crypto import core
|
|
9
|
+
from minikerberos.common.ccache import CCACHE, Credential, CCACHEPrincipal, Times, Keyblock, CCACHEOctetString
|
|
10
|
+
from minikerberos.common.kirbi import Kirbi
|
|
11
|
+
from minikerberos.protocol.asn1_structs import EncKrbCredPart, TicketFlags, Ticket
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def from_kirbi(kirbi: Kirbi):
|
|
15
|
+
krbcred = kirbi.kirbiobj.native
|
|
16
|
+
c = Credential()
|
|
17
|
+
enc_credinfo = EncKrbCredPart.load(krbcred['enc-part']['cipher']).native
|
|
18
|
+
ticket_info = enc_credinfo['ticket-info'][0]
|
|
19
|
+
|
|
20
|
+
c.client = CCACHEPrincipal.from_asn1(ticket_info['pname'], ticket_info['prealm'])
|
|
21
|
+
# yaaaaay 4 additional weirdness!!!!
|
|
22
|
+
# if sname name-string contains a realm as well htne impacket will crash miserably :(
|
|
23
|
+
if len(ticket_info['sname']['name-string']) > 2 and ticket_info['sname']['name-string'][-1].upper() == ticket_info[
|
|
24
|
+
'srealm'].upper():
|
|
25
|
+
print('SNAME contains the realm as well, trimming it')
|
|
26
|
+
t = ticket_info['sname']
|
|
27
|
+
t['name-string'] = t['name-string'][:-1]
|
|
28
|
+
c.server = CCACHEPrincipal.from_asn1(t, ticket_info['srealm'])
|
|
29
|
+
else:
|
|
30
|
+
c.server = CCACHEPrincipal.from_asn1(ticket_info['sname'], ticket_info['srealm'])
|
|
31
|
+
|
|
32
|
+
c.time = Times.from_asn1(ticket_info)
|
|
33
|
+
c.key = Keyblock.from_asn1(ticket_info['key'])
|
|
34
|
+
c.is_skey = 0 # not sure!
|
|
35
|
+
|
|
36
|
+
c.tktflags = TicketFlags(ticket_info['flags']).cast(core.IntegerBitString).native
|
|
37
|
+
c.num_address = 0
|
|
38
|
+
c.num_authdata = 0
|
|
39
|
+
c.ticket = CCACHEOctetString.from_asn1(
|
|
40
|
+
Ticket(krbcred['tickets'][0]).dump()) # kirbi only stores one ticket per file
|
|
41
|
+
c.second_ticket = CCACHEOctetString.empty()
|
|
42
|
+
|
|
43
|
+
return c
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def gen_cache_path(user: Optional[str] = None) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Generate MIT Kerberos ccache file path following OS-specific standards.
|
|
49
|
+
|
|
50
|
+
Refined to prioritize XDG specs on Linux and robust path handling on Windows.
|
|
51
|
+
"""
|
|
52
|
+
if sys.platform == "darwin":
|
|
53
|
+
# macOS typically uses API-based credential caches (KCM), not flat files.
|
|
54
|
+
raise NotImplementedError("macOS uses CCAPI; file-based paths are non-standard.")
|
|
55
|
+
|
|
56
|
+
if os.name == "nt":
|
|
57
|
+
# Windows best practice: Use USERPROFILE or LOCALAPPDATA for caches
|
|
58
|
+
user = user or getpass.getuser()
|
|
59
|
+
base = Path(os.environ.get("USERPROFILE", f"C:/Users/{user}"))
|
|
60
|
+
return (base / f"krb5cc_{user}").as_posix()
|
|
61
|
+
|
|
62
|
+
# Linux / Unix / POSIX
|
|
63
|
+
# 1. Check for XDG_RUNTIME_DIR (Modern Linux standard, e.g., /run/user/1000)
|
|
64
|
+
# 2. Fallback to /tmp with UID
|
|
65
|
+
uid = os.getuid()
|
|
66
|
+
runtime_dir = os.environ.get("XDG_RUNTIME_DIR")
|
|
67
|
+
|
|
68
|
+
if runtime_dir:
|
|
69
|
+
base_path = Path(runtime_dir)
|
|
70
|
+
else:
|
|
71
|
+
base_path = Path("/tmp")
|
|
72
|
+
|
|
73
|
+
return (base_path / f"krb5cc_{uid}").as_posix()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def convert_kirbi(src:str, dest:str=None)->None:
|
|
77
|
+
"""
|
|
78
|
+
This function convert the kirbi format to MIT ccache format.
|
|
79
|
+
:param src: The path of source .kirbi file which contains the tgt binary in base64 format
|
|
80
|
+
:param dest: The path of converted MIT ccache file
|
|
81
|
+
:return:
|
|
82
|
+
"""
|
|
83
|
+
# 1. Standardize path handling using Pathlib
|
|
84
|
+
# This handles ../, ./, absolute paths, and OS-specific separators ( \ vs / )
|
|
85
|
+
src_path = Path(src).expanduser().resolve()
|
|
86
|
+
|
|
87
|
+
if not src_path.exists():
|
|
88
|
+
raise FileNotFoundError(f"Source kirbi file not found: {src_path}")
|
|
89
|
+
|
|
90
|
+
# 2. Use context managers for safe File I/O
|
|
91
|
+
# This ensures the file handle is closed even if decoding fails
|
|
92
|
+
try:
|
|
93
|
+
with src_path.open("rb") as f:
|
|
94
|
+
kirbi_b64 = f.read()
|
|
95
|
+
|
|
96
|
+
# 3. Decode Base64 to raw bytes
|
|
97
|
+
kirbi_bytes = base64.b64decode(kirbi_b64)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise ValueError(f"Failed to read or decode kirbi file: {e}")
|
|
100
|
+
|
|
101
|
+
# 4. Process the credential cache
|
|
102
|
+
cc = CCACHE.from_bytes(kirbi_bytes)
|
|
103
|
+
|
|
104
|
+
# 5. Determine destination
|
|
105
|
+
if dest:
|
|
106
|
+
dest_path = Path(dest).expanduser().resolve()
|
|
107
|
+
else:
|
|
108
|
+
# Fallback to our logic from the previous gen_cache_path function
|
|
109
|
+
dest_path = Path(gen_cache_path())
|
|
110
|
+
|
|
111
|
+
# Ensure the parent directory for the destination exists
|
|
112
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
|
|
114
|
+
# 6. Write ticket to the file path
|
|
115
|
+
cc.to_file(str(dest_path))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: krbconverter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This tool can convert kerberos ticket of format .kirbi to standard MIT CCACHE format
|
|
5
|
+
Author-email: Pengfei Liu <pengfei.liu@casd.eu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: kerberos,ccache
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: pyasn1
|
|
12
|
+
Requires-Dist: minikerberos
|
|
13
|
+
Requires-Dist: asn1crypto
|
|
14
|
+
Requires-Dist: typer
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
18
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
19
|
+
Requires-Dist: isort>=5.12; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# krbTicketConvertor
|
|
23
|
+
|
|
24
|
+
The objective of this project is
|
|
25
|
+
1. to get a kerberos ticket(delegable TGT) with tool such as [rubeus](https://github.com/ghostpack/rubeus) or homemade.
|
|
26
|
+
2. to convert a kerberos ticket from format `.kirbi` to MIT Kerberos cache file.
|
|
27
|
+
|
|
28
|
+
## Get a delegated TGT
|
|
29
|
+
|
|
30
|
+
The first step is to get a TGT. We use a tool called `rubeus`, https://github.com/ghostpack/rubeus
|
|
31
|
+
|
|
32
|
+
The main code of how rubeus get the TGT is in class [LSA.cs](https://github.com/GhostPack/Rubeus/blob/master/Rubeus/lib/LSA.cs)
|
|
33
|
+
|
|
34
|
+
The function is called `RequestFakeDelegTicket`
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## tgtdeleg of LSA
|
|
38
|
+
|
|
39
|
+
The `tgtdeleg` using @gentilkiwi's Kekeo trick (tgt::deleg) that abuses the `Kerberos GSS-API` to retrieve a
|
|
40
|
+
usable TGT for the current user without needing elevation on the host. `AcquireCredentialsHandle()` is used to get
|
|
41
|
+
a handle to the current user's Kerberos security credentials, and `InitializeSecurityContext()` with the `ISC_REQ_DELEGATE`
|
|
42
|
+
flag and a target `SPN of HOST/DC.domain.com` to prepare a fake delegate context to send to the DC.
|
|
43
|
+
This results in an AP-REQ in the GSS-API output that contains a KRB_CRED in the authenticator checksum.
|
|
44
|
+
|
|
45
|
+
The service ticket session key is extracted from the local Kerberos cache and is used to decrypt the `KRB_CRED` in the
|
|
46
|
+
authenticator, resulting in a usable TGT `.kirbi`.
|
|
47
|
+
|
|
48
|
+
If automatic `target/domain` extraction is failing, a known SPN of a service configured with unconstrained delegation can be specified with /target:SPN.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## .kirbi vs CCACHE
|
|
52
|
+
|
|
53
|
+
As you can notice, the output of the `Kerberos ticket` is in format `.kirbi` which is not the standard MIT cache format.
|
|
54
|
+
And many tools only support MIT cache format.
|
|
55
|
+
|
|
56
|
+
| Feature | KIRBI (Windows/Internal) | CCACHE (MIT/Linux) |
|
|
57
|
+
|------------|------------------------------------|------------------------------|
|
|
58
|
+
| Encoding | ASN.1 DER (usually) | Specialized Binary Format |
|
|
59
|
+
| Origin | Microsoft KRB-CRED | MIT Kerberos V5 |
|
|
60
|
+
| Usage | Pass-the-Ticket (Rubeus/Mimikatz) | Standard Linux Auth (kinit) |
|
|
61
|
+
| Structure | Nested ASN.1 Sequences | Header + Credential List |
|
|
62
|
+
|
|
63
|
+
## The python implementation
|
|
64
|
+
|
|
65
|
+
This folder shows an implementation in python.
|
|
66
|
+
|
|
67
|
+
We use the `ccache.py` of the project [minikerberos](https://github.com/skelsec/minikerberos/blob/main/minikerberos/common/ccache.py)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## The TGT deleg of ssh
|
|
71
|
+
|
|
72
|
+
Here is the step-by-step breakdown of how that `TGT` travels from your `Windows machine` to the Linux server.
|
|
73
|
+
|
|
74
|
+
### The TGT Generation (On your Windows Machine)
|
|
75
|
+
|
|
76
|
+
When you log in to your Windows server, a TGT is generated and stored in your local cache (i.e. LSASS).
|
|
77
|
+
When you run `ssh -k user@srv1.example.com`:
|
|
78
|
+
1. `Requesting a Service Ticket`: Your SSH client asks your local Kerberos implementation for a Service Ticket for the remote server (host/srv1.example.com).
|
|
79
|
+
2. The "Forwardable" Flag: Because you specified delegation, your client asks the Key Distribution Center (KDC) for
|
|
80
|
+
a `new, forwardable TGT`.
|
|
81
|
+
3. The Package: The KDC sends back a TGT encrypted in a way that only you can open. Your `Windows machine` then wraps
|
|
82
|
+
this TGT inside the `GSSAPI authentication layer of the SSH protocol.2`.
|
|
83
|
+
|
|
84
|
+
### The TGT Forwarding
|
|
85
|
+
|
|
86
|
+
The TGT is sent over the encrypted SSH tunnel. It is never sent in the clear.It is bundled inside the SSH
|
|
87
|
+
"User Authentication" packet. It travels from your `Windows machine` to the `sshd daemon on the Linux server`.
|
|
88
|
+
|
|
89
|
+
### The CCache file creation
|
|
90
|
+
|
|
91
|
+
(On the Linux Server)Once the Linux server receives your delegated credentials, the `SSH Daemon (sshd)` manage the rest.
|
|
92
|
+
1. The sshd process receives the GSSAPI data containing the TGT
|
|
93
|
+
2. The sshd (which usually runs as root initially) creates a new, temporary file. By default, this is usually located in /tmp/krb5cc_UID_XXXXXX
|
|
94
|
+
3. The sshd changes the ownership of this file to your user account and sets permissions so only you can read it (600).
|
|
95
|
+
4. The sshd sets the `KRB5CCNAME environment variable` in your shell session. This tells other tools (e.g. klist, nfs, or ldapsearch)
|
|
96
|
+
exactly where to find that ticket.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/main.py
|
|
5
|
+
src/krb/__init__.py
|
|
6
|
+
src/krb/convertor.py
|
|
7
|
+
src/krbconverter.egg-info/PKG-INFO
|
|
8
|
+
src/krbconverter.egg-info/SOURCES.txt
|
|
9
|
+
src/krbconverter.egg-info/dependency_links.txt
|
|
10
|
+
src/krbconverter.egg-info/entry_points.txt
|
|
11
|
+
src/krbconverter.egg-info/requires.txt
|
|
12
|
+
src/krbconverter.egg-info/top_level.txt
|
|
13
|
+
tests/test_convertor.py
|
|
14
|
+
tests/test_genCachePath.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from krb.convertor import convert_kirbi
|
|
4
|
+
|
|
5
|
+
app = typer.Typer()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.command()
|
|
9
|
+
def main(
|
|
10
|
+
kirbi_path: str = typer.Argument(..., help="Path to the input .kirbi ticket file"),
|
|
11
|
+
ccache_path: str = typer.Argument(..., help="Path for the output .ccache file"),
|
|
12
|
+
):
|
|
13
|
+
# Convert a Kerberos .kirbi ticket to .ccache format.
|
|
14
|
+
try:
|
|
15
|
+
convert_kirbi(kirbi_path, ccache_path)
|
|
16
|
+
typer.echo(f"[+] Success: {ccache_path}")
|
|
17
|
+
except Exception as e:
|
|
18
|
+
typer.echo(f"[-] Error: {e}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
app()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from krb.convertor import convert_kirbi
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_convertor_with_given_path():
|
|
5
|
+
kirbi = "C:/Users/pliu/Documents/git/krbTicketConvertor/tests/tmp/tgt.kirbi"
|
|
6
|
+
ccache = "C:/Users/pliu/Documents/git/krbTicketConvertor/tests/tmp/tgt.ccache"
|
|
7
|
+
|
|
8
|
+
convert_kirbi(kirbi, ccache)
|
|
9
|
+
|
|
10
|
+
def test_convertor_with_default_path():
|
|
11
|
+
kirbi = "C:/Users/pliu/Documents/git/krbTicketConvertor/tests/tmp/tgt.kirbi"
|
|
12
|
+
convert_kirbi(kirbi)
|
|
13
|
+
|
|
14
|
+
def test_convertor_with_relative_path():
|
|
15
|
+
kirbi = "./tests/tmp/tgt.kirbi"
|
|
16
|
+
convert_kirbi(kirbi)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import krb
|
|
8
|
+
from krb import convertor
|
|
9
|
+
from krb.convertor import gen_cache_path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestGenCachePath:
|
|
13
|
+
|
|
14
|
+
@patch("sys.platform", "darwin")
|
|
15
|
+
def test_macos_raises_error(self):
|
|
16
|
+
"""Ensure macOS triggers the expected NotImplementedError."""
|
|
17
|
+
with pytest.raises(NotImplementedError, match="macOS uses CCAPI"):
|
|
18
|
+
gen_cache_path()
|
|
19
|
+
|
|
20
|
+
@patch("os.name", "nt")
|
|
21
|
+
@patch("getpass.getuser", return_value="alice")
|
|
22
|
+
@patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\alice"})
|
|
23
|
+
def test_windows_path_generation(self, mock_getuser):
|
|
24
|
+
"""Verify Windows path logic using environment variables."""
|
|
25
|
+
path = gen_cache_path()
|
|
26
|
+
# Pathlib handles the slash conversion, .as_posix() ensures forward slashes
|
|
27
|
+
assert path == "C:/Users/alice/krb5cc_alice"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@patch("os.name", "nt")
|
|
31
|
+
@patch.dict(os.environ, {"USERPROFILE": "C:\\Users\\bob"})
|
|
32
|
+
def test_explicit_user_override(self):
|
|
33
|
+
"""Ensure providing a 'user' argument overrides the system user."""
|
|
34
|
+
path = gen_cache_path(user="admin")
|
|
35
|
+
assert path == "C:/Users/bob/krb5cc_admin"
|
|
36
|
+
|
|
37
|
+
####### The linux os test will fail if you run under windows ###############
|
|
38
|
+
# the os module is dynamic; attributes like getuid or setuid simply do not exist when Python runs on Windows.
|
|
39
|
+
@patch("os.name", "posix")
|
|
40
|
+
@patch("sys.platform", "linux")
|
|
41
|
+
@patch("os.getuid", return_value=1000)
|
|
42
|
+
@patch.dict(os.environ, {}, clear=True)
|
|
43
|
+
def test_linux_fallback_path(self, mock_uid):
|
|
44
|
+
"""Verify fallback to /tmp when XDG_RUNTIME_DIR is missing.
|
|
45
|
+
"""
|
|
46
|
+
path = gen_cache_path()
|
|
47
|
+
assert path == "/tmp/krb5cc_1000"
|
|
48
|
+
|
|
49
|
+
@patch("os.name", "posix")
|
|
50
|
+
@patch("sys.platform", "linux")
|
|
51
|
+
@patch("os.getuid", return_value=1000)
|
|
52
|
+
@patch.dict(os.environ, {"XDG_RUNTIME_DIR": "/run/user/1000"})
|
|
53
|
+
def test_linux_xdg_path(self, mock_uid):
|
|
54
|
+
"""Verify preference for XDG_RUNTIME_DIR on modern Linux systems."""
|
|
55
|
+
path = gen_cache_path()
|
|
56
|
+
assert path == "/run/user/1000/krb5cc_1000"
|