From edb128d4e0f5afd7b291f0ede6e01930c9028593 Mon Sep 17 00:00:00 2001 From: mgeeky Date: Thu, 5 Mar 2020 13:21:59 +0100 Subject: [PATCH] added markOwnedNodesInNeo4j.py --- red-teaming/README.md | 18 ++++ red-teaming/markOwnedNodesInNeo4j.py | 125 +++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 red-teaming/markOwnedNodesInNeo4j.py diff --git a/red-teaming/README.md b/red-teaming/README.md index 39a58d0..f6d2928 100644 --- a/red-teaming/README.md +++ b/red-teaming/README.md @@ -259,6 +259,24 @@ PS E:\PowerSploit\Recon> Get-DomainOU | Get-DomainOUTree - **`malleable_redirector`** - A [proxy2](https://github.com/mgeeky/proxy2) plugin for resilient, evasive C2 infrastructures covering your redirectors from AV/EDR/Sandbox/IR lurking eyes based on the CobaltStrike's Malleable C2 Profile specified. Combines advantages of classic evasion techniques such as Apache2 Mod_Rewrite/`.htaccess` and deep c2-profile-drive HTTP/HTTPS request inspection +- **`markOwnedNodesInNeo4j.py`** - This script takes an input file containing Node names to be marked in Neo4j database as owned = True. The strategy for working with neo4j and Bloodhound becomes fruitful during complex Active Directory Security Review assessments or Red Teams. Imagine you've kerberoasted a number of accounts, access set of workstations or even cracked userPassword hashes. Using this script you can quickly instruct Neo4j to mark that principals as owned, which will enrich your future use of BloodHound. + +``` +$ ./markOwnedNodesInNeo4j.py kerberoasted.txt +[.] Connected to neo4j instance. +[.] Marking nodes (0..10) ... +[+] Marked 10 nodes in 4.617 seconds. Finish ETA: in 16.622 seconds. +[.] Marking nodes (10..20) ... +[+] Marked 10 nodes in 4.663 seconds. Finish ETA: in 12.064 seconds. +[.] Marking nodes (20..30) ... +[+] Marked 10 nodes in 4.157 seconds. Finish ETA: in 7.167 seconds. +[.] Marking nodes (30..40) ... +[+] Marked 10 nodes in 4.365 seconds. Finish ETA: in 2.670 seconds. +[.] Marking nodes (40..46) ... +[+] Marked 6 nodes in 2.324 seconds. Finish ETA: in 0 seconds. +[+] Nodes marked as owned successfully in 20.246 seconds. +``` + - **`msbuild-powershell-msgbox.xml`** - Example of Powershell execution via MSBuild inline task XML file. On a simple Message-Box script. ([gist](https://gist.github.com/mgeeky/617c54a23f0c4e99e6f475e6af070810)) diff --git a/red-teaming/markOwnedNodesInNeo4j.py b/red-teaming/markOwnedNodesInNeo4j.py new file mode 100644 index 0000000..4c8eb3f --- /dev/null +++ b/red-teaming/markOwnedNodesInNeo4j.py @@ -0,0 +1,125 @@ +#!/usr/bin/python3 +# +# This script takes an input file containing Node names to be marked in Neo4j database as +# owned = True. The strategy for working with neo4j and Bloodhound becomes fruitful during +# complex Active Directory Security Review assessments or Red Teams. Imagine you've kerberoasted +# a number of accounts, access set of workstations or even cracked userPassword hashes. Using this +# script you can quickly instruct Neo4j to mark that principals as owned, which will enrich your +# future use of BloodHound. +# +# Mariusz B. / mgeeky +# + +import sys +import os +import time +try: + from neo4j import GraphDatabase +except ImportError: + print('[!] "neo4j >= 1.7.0" required. Install it with: python3 -m pip install neo4j') + +# +# =========================================== +# + +# Specify neo4j connection details +NEO4J_CONNECTION_DETAILS = \ +{ + 'Host': '127.0.0.1', # neo4j listening address. + 'Port': 7687, # Bolt port + 'User': 'neo4j', + 'Pass': 'neo4j1' +} + +# +# =========================================== +# + +# +# Construct a MATCH ... SET owned=TRUE query of not more than this number of nodes. +# This number impacts single query execution time. If it is more than 1000, neo4j may complain +# about running out of heap memory space (java...). +# +numberOfNodesToAddPerStep = 500 + +def markNodes(tx, nodes): + query = '' + + for i in range(len(nodes)): + n = nodes[i] + query += 'MATCH (n {name: "' + n + '"}) SET n.owned = TRUE ' + if i < len(nodes) - 1: query += 'UNION ' + + tx.run(query) + +def main(argv): + if len(argv) != 2: + print(''' + Takes a file containing node names on input and marks them as Owned in specified neo4j database. + +Usage: ./markOwnedNodesInNeo4j.py +''') + return False + + nodesFile = argv[1] + programStart = time.time() + + if not os.path.isfile(nodesFile): + print(f'[!] Input file containing nodes does not exist: "{nodesFile}"!') + return False + + nodes = [] + with open(nodesFile) as f: nodes = [x.strip() for x in f.readlines()] + + try: + driver = GraphDatabase.driver( + f"bolt://{NEO4J_CONNECTION_DETAILS['Host']}:{NEO4J_CONNECTION_DETAILS['Port']}", + auth = (NEO4J_CONNECTION_DETAILS['User'], NEO4J_CONNECTION_DETAILS['Pass']), + encrypted = False, + connection_timeout = 10, + max_retry_time = 5, + keep_alive = True + ) + except Exception as e: + print(f'[-] Could not connect to the neo4j database. Reason: {str(e)}') + return False + + print('[.] Connected to neo4j instance.') + + if len(nodes) >= 200: + print('[*] Warning: Working with a large number of nodes may be time-consuming in large databases.') + print('\te.g. setting 1000 nodes as owned can take up to 10 minutes easily.') + print() + + finishEta = 0.0 + totalTime = 0.0 + runs = 0 + + try: + with driver.session() as session: + for a in range(0, len(nodes), numberOfNodesToAddPerStep): + b = a + min(numberOfNodesToAddPerStep, len(nodes) - a) + print(f'[.] Marking nodes ({a}..{b}) ...') + start = time.time() + session.write_transaction(markNodes, nodes[a:b]) + stop = time.time() + + totalTime += (stop - start) + runs += 1 + + finishEta = ((len(nodes) / numberOfNodesToAddPerStep) - runs) * (totalTime / float(runs)) + if finishEta < 0: finishEta = 0 + + print(f'[+] Marked {b-a} nodes in {stop - start:.3f} seconds. Finish ETA: in {finishEta:.3f} seconds.') + + except KeyboardInterrupt: + print('[.] User interruption.') + driver.close() + return False + + driver.close() + programStop = time.time() + print(f'\n[+] Nodes marked as owned successfully in {programStop - programStart:.3f} seconds.') + +if __name__ == '__main__': + main(sys.argv)