Skip to content

Instantly share code, notes, and snippets.

@dadevel
Last active July 15, 2023 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadevel/36e5c97a058e910d7456632208d6db9e to your computer and use it in GitHub Desktop.
Save dadevel/36e5c97a058e910d7456632208d6db9e to your computer and use it in GitHub Desktop.
Impacket Ticket Helper
#!/usr/bin/env python3
from argparse import ArgumentParser, Namespace
import base64
import hashlib
import itertools
import os
import shlex
import sys
from impacket.krb5.ccache import CCache
class CustomParser(ArgumentParser):
def print_help(self) -> None:
print(self.format_help(), file=sys.stderr)
def error(self, message: str) -> None:
print(message, file=sys.stderr)
def main() -> None:
entrypoint = CustomParser()
parsers = entrypoint.add_subparsers(dest='command', required=True)
parser = parsers.add_parser('import')
parser.add_argument('input', nargs='?', default='', metavar='-|base64:KIRBIBLOB|KIRBIFILE')
parser.add_argument('output', nargs='?', default='', metavar='CCACHEFILE')
parser = parsers.add_parser('export')
parser.add_argument('input', nargs='?', default='', metavar='CCACHEFILE')
parser = parsers.add_parser('set')
parser.add_argument('input', nargs=1, default='', metavar='CCACHEFILE')
parser = parsers.add_parser('unset')
opts = entrypoint.parse_args()
try:
globals()[f'ccache_{opts.command}'](opts)
except Exception as e:
print(f"{e.__class__.__name__}: {e}", file=sys.stderr)
exit(1)
def ccache_import(opts: Namespace) -> None:
if not opts.input or opts.input == '-':
ccache = CCache()
ccache.fromKRBCRED(base64.b64decode(sys.stdin.buffer.read()))
elif opts.input.startswith('base64:') and not os.path.exists(opts.input):
ccache = CCache()
ccache.fromKRBCRED(base64.b64decode(opts.input.removeprefix('base64:')))
elif opts.input.endswith('.kirbi') and os.path.exists(opts.input):
ccache = CCache.loadKirbiFile(opts.input)
else:
raise ValueError('invalid input')
if not ccache:
raise ValueError('invalid kirbi ticket')
realm, user = _extract_user_info(ccache)
if opts.output:
filename = opts.output
else:
filename = f'{user}.ccache'
if os.path.exists(filename):
newhash = hashlib.md5(ccache.getData()).hexdigest()
with open(filename, 'rb') as file:
oldhash = hashlib.md5(file.read()).hexdigest()
if newhash != oldhash:
for i in itertools.count():
filename = f'{user}{i}.ccache'
if not os.path.exists(filename):
break
ccache.saveFile(filename)
print(_generate_expots(filename, realm, user))
def ccache_export(opts: Namespace) -> None:
ccache = CCache.loadFile(opts.input or os.environ['KRB5CCNAME'])
if not ccache:
raise ValueError('invalid ccache ticket')
print(f'echo {base64.b64encode(ccache.toKRBCRED()).decode()}')
def ccache_set(opts: Namespace) -> None:
ccache = CCache.loadFile(opts.input[0])
if not ccache:
raise ValueError('invalid ccache ticket')
realm, user = _extract_user_info(ccache)
print(_generate_expots(opts.input[0], realm, user))
def ccache_unset(_):
print('unset KRB5CCNAME KRB5CCNAME_REALM KRB5CCNAME_USER')
def _extract_user_info(ccache: CCache) -> tuple[str, str]:
credential = ccache.credentials[0]
upn = credential['client'].prettyPrint().decode().lower()
user, realm = upn.split('@', maxsplit=1)
assert '..' not in user and '/' not in user
return realm, user
def _generate_expots(path: str, realm: str, user: str) -> str:
return f'export KRB5CCNAME={shlex.quote(os.path.realpath(path))} KRB5CCNAME_REALM={shlex.quote(realm)} KRB5CCNAME_USER={shlex.quote(user)}'
if __name__ == '__main__':
main()
ccache() {
declare result
python3 ~/.lib/impacket-ccache-helper.py "$@" | read -r result && eval "${result}"
}
compdef "_arguments '1:first arg:(import export set unset)' '::optional arg:_files'" ccache
prompt_ccache() {
if [[ -e "$KRB5CCNAME" && -n "$KRB5CCNAME_REALM" && -n "$KRB5CCNAME_USER" ]]; then
p10k segment +e -t "$KRB5CCNAME_REALM/$KRB5CCNAME_USER"
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment