├── BH Red2Blue.txt └── customqueries.json /BH Red2Blue.txt: -------------------------------------------------------------------------------- 1 | ***** DOMAIN USERS is Admin of Computer 2 | MATCH p=(m:Group)-[:AdminTo]->(n:Computer) WHERE m.name STARTS WITH 'DOMAIN USERS' RETURN p 3 | ***** 4 | 5 | ******* ShortestPath From Domain Users to Domain Admins 6 | MATCH p=ShortestPath((n {name:"DOMAIN USERS@TESTLAB.LOCAL"}) 7 | -[*1..]->(m {name: "DOMAIN ADMINS@TESTLAB.LOCAL"})) 8 | RETURN p 9 | ******* 10 | 11 | ******* Find Shortest Path from DOMAIN USERS to High Value Targets 12 | MATCH (g:Group),(n {highvalue:true}),p=shortestPath((g)-[*1..]->(n)) 13 | WHERE g.name STARTS WITH 'DOMAIN USERS' 14 | RETURN p 15 | ******** 16 | 17 | ******* Find ALL Path from DOMAIN USERS to High Value Targets 18 | MATCH (g:Group) WHERE g.name STARTS WITH 'DOMAIN USERS' MATCH (n {highvalue:true}),p=shortestPath((g)-[r*1..]->(n)) return p 19 | ***** 20 | 21 | ***** Find all other right Domain Users shouldn’t have 22 | MATCH p=(m:Group)-[r:Owns|:WriteDacl|:GenericAll|:WriteOwner|:ExecuteDCOM|:GenericWrite|:AllowedToDelegate|:ForceChangePassword]->(n:Computer) WHERE m.name STARTS WITH 'DOMAIN USERS' RETURN p 23 | ***** 24 | 25 | ***** Domain Controller object ownership. (BHE #1) 26 | MATCH (g:Group) 27 | WHERE g.objectid ENDS WITH '-516' 28 | MATCH p = (n:Base)-[:Owns]->(c:Computer)-[:MemberOf*1..]->(g) 29 | RETURN p 30 | ***** 31 | 32 | ***** Kerberoastable Accounts 33 | MATCH (n:User)WHERE n.hasspn=true RETURN n 34 | ***** 35 | 36 | ***** Kerberoastable Accounts member of High Value Group 37 | MATCH (n:User)-[r:MemberOf]->(g:Group) WHERE g.highvalue=true AND n.hasspn=true RETURN n, g, r 38 | ****** 39 | 40 | ****** Privileged kerberoastable users (BHE #2) 41 | MATCH p = shortestPath((u:User {hasspn:true})-[*1..]->(g:Group)) 42 | WHERE g.objectid ENDS WITH '-512' 43 | RETURN p 44 | ****** 45 | 46 | ***** Domain Users group and other large groups having control of other objects (BHE #3) 47 | MATCH p = (g:Group)-[{isacl:true}]->(m) 48 | WHERE g.objectid ENDS WITH '-513' 49 | RETURN p 50 | ***** 51 | 52 | ****** Find WORKSTATIONS where Domain Users can RDP To 53 | match p=(g:Group)-[:CanRDP]->(c:Computer) where g.name STARTS WITH 'DOMAIN USERS' AND NOT c.operatingsystem CONTAINS 'Server' return p 54 | ******* 55 | 56 | ******* Find SERVERS where Domain Users can RDP To 57 | match p=(g:Group)-[:CanRDP]->(c:Computer) 58 | where g.name STARTS WITH 'DOMAIN USERS' 59 | AND c.operatingsystem CONTAINS 'Server' 60 | return p 61 | ******* 62 | 63 | ******* Top 10 Computers with Most Admins 64 | MATCH (n:User),(m:Computer), (n)-[r:AdminTo]->(m) WHERE NOT n.name STARTS WITH 'ANONYMOUS LOGON' AND NOT n.name='' WITH m, count(r) as rel_count order by rel_count desc LIMIT 10 MATCH p=(m)<-[r:AdminTo]-(n) RETURN p 65 | ******* 66 | 67 | ******* How many High Value Target a Node can reach 68 | MATCH p = shortestPath((n)-[*1..]->(m {highvalue:true})) 69 | WHERE NOT n = m 70 | RETURN DISTINCT(m.name),LABELS(m)[0],COUNT(DISTINCT(n)) 71 | ORDER BY COUNT(DISTINCT(n)) DESC 72 | ******* 73 | 74 | ******* All DA Account Sessions 75 | MATCH (n:User)-[:MemberOf]->(g:Group {name:"DOMAIN ADMINS@TESTLAB.LOCAL"}) 76 | MATCH p = (c:Computer)-[:HasSession]->(n) 77 | return p 78 | ******** 79 | 80 | ******** DA Sessions to NON DC - NEW WORKING VERSION 81 | MATCH (dc:Computer)-[:MemberOf*1..]->(g:Group) 82 | WHERE g.name CONTAINS "DOMAIN CONTROLLERS" 83 | WITH COLLECT(dc.name) as dcs 84 | MATCH (c:Computer) WHERE NOT c.name in dcs 85 | MATCH p=(c)-[:HasSession]->(n:User)-[:MemberOf]->(g:Group) 86 | WHERE g.name STARTS WITH "DOMAIN ADMINS" 87 | RETURN n.name as Username 88 | ******** 89 | 90 | ******** Count on how many non DC machines DA have sessions - Original query by Waldo (Not working afaik) Probably missing a COLLECT after WITH 91 | MATCH (c:Computer)-[:MemberOf*1..]->(g:Group) 92 | WHERE g.objectsid ENDS WITH "-516" 93 | WITH c.name as DomainControllers 94 | MATCH p = (c2:Computer)-[:HasSession]->(u:User)-[:MemberOf*1..]->(g:Group) 95 | WHERE g.objectsid ENDS WITH "-512" AND NOT c2.name in DomainControllers 96 | RETURN DISTINCT(u.name),COUNT(DISTINCT(c2)) 97 | ORDER BY COUNT(DISTINCT(c2)) DESC 98 | ********* 99 | 100 | ********* EXCLUDE PATH (Edge) on the fly 101 | MATCH (n:User),(m:Group {name:"DOMAIN ADMINS@TESTLAB.LOCAL"}), 102 | p=shortestPath((n)-[r*1..]->(m)) 103 | WHERE ALL(x in relationships(p) WHERE type(x) <> "AdminTo") 104 | RETURN p 105 | ********** 106 | 107 | ********** Set SPN to a User 108 | MATCH (n:User {name:"JNOA00093@TESTLAB.LOCAL"}) SET n.hasspn=true 109 | ********** 110 | 111 | 112 | ********** Add DOMAIN USERS as Admin to COMPxxxx 113 | MERGE (n:Group {name:"DOMAIN USERS@TESTLAB.LOCAL"}) WITH n MERGE (m:Computer {name:"COMP00673.TESTLAB.LOCAL"}) WITH n,m MERGE (n)-[:AdminTo]->(m) 114 | ********** 115 | 116 | ********** Test our New Relationship 117 | MATCH p=(n:Group {name:"DOMAIN USERS@TESTLAB.LOCAL"})-[r:AdminTo]->(m:Computer {name:"COMP00673.TESTLAB.LOCAL"}) 118 | RETURN p 119 | ********** Try to replace AdminTo by 1** 120 | 121 | ********** Delete a Relationship 122 | MATCH p=(n:Group {name:"DOMAIN USERS@TESTLAB.LOCAL"})-[r:AdminTo]-> 123 | (m:Computer {name:"COMP00673.TESTLAB.LOCAL"}) 124 | DELETE r 125 | ********** 126 | 127 | ********** Shortest Path from Domain Users to Domain Admins 128 | MATCH (g:Group {name:"DOMAIN USERS@TESTLAB.LOCAL"}), 129 | (n {name:"DOMAIN ADMINS@TESTLAB.LOCAL"}), 130 | p=shortestPath((g)-[*1..]->(n)) 131 | return p 132 | ********** 133 | 134 | ********** Shortest Path from Domain Users to Domain Admins (No AdminTo) 135 | MATCH (g:Group {name:"DOMAIN USERS@TESTLAB.LOCAL"}), 136 | (n {name:"DOMAIN ADMINS@TESTLAB.LOCAL"}), 137 | p=shortestPath((g)-[r*1..]->(n)) 138 | WHERE ALL(x in relationships(p) WHERE type(x) <> "AdminTo") 139 | return p 140 | ********** 141 | 142 | ********** Find Admin Group not tagged highvalue 143 | MATCH (g:Group) 144 | WHERE g.name CONTAINS "ADMIN" 145 | AND g.highvalue IS null 146 | RETURN g.name 147 | ********** 148 | 149 | ********** Set Admin Group as highvalue 150 | MATCH (g:Group) 151 | WHERE g.name CONTAINS "ADMIN" 152 | AND g.highvalue IS null 153 | SET g.highvalue=true 154 | ********** 155 | 156 | ********** Remore Admin User highvalue 157 | MATCH p=(g:Group {highvalue: true})<-[:MemberOf]-(u:User) 158 | WHERE g.name CONTAINS "ADMIN" 159 | SET g.highvalue = NULL 160 | ********** 161 | 162 | If you want to enumerate all available properties : 163 | Match (n:Computer) return properties(n) 164 | 165 | ********** List all computers with unconstraineddelegation 166 | MATCH (c:Computer {unconstraineddelegation:true}) 167 | return c.name 168 | ********** 169 | 170 | ********** Computer where the most users can RDP to 171 | MATCH (c:Computer) 172 | OPTIONAL MATCH (u1:User)-[:CanRDP]->(c) 173 | OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:CanRDP]->(c) 174 | WITH COLLECT(u1) + COLLECT(u2) as tempVar,c 175 | UNWIND tempVar as users 176 | RETURN c.name,COUNT(DISTINCT(users)) 177 | ORDER BY COUNT(DISTINCT(users)) DESC 178 | ********** 179 | 180 | ********** Average Length of Path 181 | MATCH (g:Group {name:'DOMAIN ADMINS@CONTOSO.LOCAL'}) 182 | MATCH p = shortestPath((u:User)-[r:AdminTo|MemberOf|HasSession*1..]->(g)) 183 | WITH EXTRACT(n in NODES(p) | LABELS(n)[0]) as pathNodes 184 | WITH FILTER(x IN pathNodes WHERE x = "Computer") as filteredPathNodes 185 | RETURN AVG(LENGTH(filteredPathNodes)) 186 | ********** 187 | 188 | MATCH (u:User) 189 | MATCH (g:Group {highvalue: True}) 190 | MATCH p = shortestPath((u:User)-[*1..]->(g)) 191 | RETURN DISTINCT(u.name),u.enabled 192 | order by u.name 193 | 194 | ********** Count how many Users have a path to DA 195 | MATCH p=shortestPath((u:User)-[*1..]-> 196 | (m:Group {name: "DOMAIN ADMINS@TESTLAB.LOCAL"})) 197 | RETURN COUNT (DISTINCT(u)) 198 | ********** 199 | 200 | ********** % of how many Users have a path to DA 201 | OPTIONAL MATCH p=shortestPath((u:User)-[*1..]-> 202 | (m:Group {name: "DOMAIN ADMINS@TESTLAB.LOCAL"})) 203 | OPTIONAL MATCH (uT:User) 204 | WITH COUNT (DISTINCT(uT)) as uTotal, COUNT (DISTINCT(u)) as uHasPath 205 | RETURN uHasPath *100 / uTotal as Percent 206 | ********** 207 | 208 | ********** Find users WITHOUT a path to DA 209 | MATCH (n:User),(m:Group) WHERE NOT EXISTS ((n)-[*]->(m {name:"DOMAIN ADMINS@TESTLAB.LOCAL"})) RETURN n.name 210 | ********** WARNING - This is extremly long 211 | 212 | ********** Find High Value Users that are NOT in the Protected User group 213 | MATCH (prot:User)-[:MemberOf*1..]->(g:Group) 214 | WHERE g.name = "PROTECTED USERS@SYNTAX.NET" 215 | WITH COLLECT(prot) AS protected_user 216 | MATCH p = (np:User)-[:MemberOf*1..]->(m:Group) 217 | WHERE m.highvalue = True AND NOT np IN protected_user 218 | RETURN np 219 | ********** 220 | 221 | ********** Find "Easy To Exploit" Path to high value target from Owned 222 | MATCH p=shortestpath((s:User {owned: true})-[:MemberOf|AdminTo|Owns|ForceChangePassword|AddMember*1..]->(t {highvalue: true})) 223 | RETURN p 224 | 225 | ********** List all Account with the word Admin both on AD and AAD 226 | match (au:AZUser) 227 | where au.azname contains "ADMIN" 228 | match (u:User) 229 | where u.name contains "ADMIN" 230 | WITH COLLECT(u.name) + COLLECT(au.azname) as temp 231 | UNWIND temp as usernames 232 | return usernames 233 | 234 | ********** List local accounts that can control AAD Accounts 235 | match p=shortestpath((:User)-[*1..]->(:AZUser)) return p 236 | -------------------------------------------------------------------------------- /customqueries.json: -------------------------------------------------------------------------------- 1 | { 2 | "queries": [ 3 | { 4 | "name": "List Computers where DOMAIN USERS are Local Admin", 5 | "queryList": [ 6 | { 7 | "final": false, 8 | "title": "Select a Domain Users Group...", 9 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 10 | }, 11 | { 12 | "final": true, 13 | "query": 14 | "MATCH p=(m:Group {name:{result}})-[:AdminTo]->(n:Computer) RETURN p", 15 | "allowCollapse": true 16 | } 17 | ] 18 | }, 19 | { 20 | "name": "Shortest Path from DOMAIN USERS to High Value Targets", 21 | "queryList": [ 22 | { 23 | "final": false, 24 | "title": "Select a Domain Users Group...", 25 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 26 | }, 27 | { 28 | "final": true, 29 | "query": 30 | "MATCH p=shortestPath((g:Group {name:{result}})-[*1..]->(n {highvalue:true})) WHERE g.name STARTS WITH 'DOMAIN USERS' return p", 31 | "allowCollapse": true 32 | } 33 | ] 34 | }, 35 | { 36 | "name": "All Paths from DOMAIN USERS to High Value Targets", 37 | "queryList": [ 38 | { 39 | "final": false, 40 | "title": "Select a Domain Users Group...", 41 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 42 | }, 43 | { 44 | "final": true, 45 | "query": 46 | "MATCH p=shortestPath((g:Group {name:{result}})-[*1..]->(n {highvalue:true})) return p", 47 | "allowCollapse": true 48 | } 49 | ] 50 | }, 51 | { 52 | "name": "Find Workstations where DOMAIN USERS can RDP To", 53 | "queryList": [ 54 | { 55 | "final": false, 56 | "title": "Select a Domain Users Group...", 57 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 58 | }, 59 | { 60 | "final": true, 61 | "query": 62 | "match p=(g:Group {name:{result}})-[:CanRDP]->(c:Computer) where NOT c.operatingsystem CONTAINS 'Server' return p", 63 | "allowCollapse": true 64 | } 65 | ] 66 | }, 67 | { 68 | "name": "Find Servers where DOMAIN USERS can RDP To", 69 | "queryList": [ 70 | { 71 | "final": false, 72 | "title": "Select a Domain Users Group...", 73 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 74 | }, 75 | { 76 | "final": true, 77 | "query": 78 | "MATCH p=(g:Group {name:{result}})-[:CanRDP]->(c:Computer) WHERE c.operatingsystem CONTAINS 'Server' return p", 79 | "allowCollapse": false 80 | } 81 | ] 82 | }, 83 | { 84 | "name": "Find all other Rights DOMAIN USERS shouldn’t have", 85 | "queryList": [ 86 | { 87 | "final": false, 88 | "title": "Select a Domain Users Group...", 89 | "query": "MATCH (n:Group) WHERE n.name STARTS WITH 'DOMAIN USERS' RETURN n.name ORDER BY n.name DESC" 90 | }, 91 | { 92 | "final": true, 93 | "query": 94 | "MATCH p=(m:Group)-[r:Owns|:WriteDacl|:GenericAll|:WriteOwner|:ExecuteDCOM|:GenericWrite|:AllowedToDelegate|:ForceChangePassword]->(n:Computer) WHERE m.name STARTS WITH 'DOMAIN USERS' RETURN p", 95 | "allowCollapse": true 96 | } 97 | ] 98 | }, 99 | { 100 | "name": "Kerberoastable Accounts member of High Value Group", 101 | "queryList": [ 102 | { 103 | "final": true, 104 | "query": 105 | "MATCH p=shortestPath((n:User)-[r:MemberOf]->(g:Group)) WHERE g.highvalue=true AND n.hasspn=true RETURN p", 106 | "allowCollapse": true 107 | } 108 | ] 109 | }, 110 | { 111 | "name": "List all Kerberoastable Accounts", 112 | "queryList": [ 113 | { 114 | "final": true, 115 | "query": 116 | "MATCH (n:User)WHERE n.hasspn=true RETURN n", 117 | "allowCollapse": true 118 | } 119 | ] 120 | }, 121 | { 122 | "name": "Kerberoastable Users with most privileges", 123 | "queryList": [ 124 | { 125 | "final": true, 126 | "query": 127 | "MATCH (u:User {hasspn:true}) OPTIONAL MATCH (u)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (u)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH u,COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS comps RETURN u.name,COUNT(DISTINCT(comps)) ORDER BY COUNT(DISTINCT(comps)) DESC", 128 | "allowCollapse": true 129 | } 130 | ] 131 | }, 132 | { 133 | "name": "DA Account Sessions to NON DC", 134 | "queryList": [ 135 | { 136 | "final": true, 137 | "query": 138 | "MATCH (c:Computer)-[:MemberOf]->(t:Group) WHERE NOT t.name STARTS WITH 'DOMAIN CONTROLLERS' WITH c as NonDC MATCH p=(NonDC)-[:HasSession]->(n:User)-[:MemberOf]-> (g:Group) WHERE g.name STARTS WITH 'DOMAIN ADMINS' RETURN p", 139 | "allowCollapse": true 140 | } 141 | ] 142 | }, 143 | { 144 | "name": "Find unsupported OSs", 145 | "queryList": [ 146 | { 147 | "final": true, 148 | "query": 149 | "MATCH (n:Computer) WHERE n.operatingsystem =~ '(?i).(2000|2003|2008|xp|vista|7|me).' RETURN n", 150 | "allowCollapse": true 151 | } 152 | ] 153 | }, 154 | { 155 | "name": "Find AS-REP Roasting users (no kerberos pre-authentication)", 156 | "queryList": [ 157 | { 158 | "final": true, 159 | "query": 160 | "MATCH (u:User {dontreqpreauth: true}) RETURN u", 161 | "allowCollapse": true 162 | } 163 | ] 164 | } 165 | ] 166 | } 167 | --------------------------------------------------------------------------------