Queries Crafted to be Used as Report Findings
Unsupported Operating Systems in Use (“Unsupported Software” finding)
⚡ There are two queries that will help you with this finding. Don’t miss the second one. ⚡ Also see the boilerplate finding text at the end!
- Generate the list of computers with unsupported operating systems.
- Lists domain-joined computers that:
- Are enabled in AD
- Have had the machine password changed within the last 45 days.
- This normally happens automatically every 30 days for domain-joined machines.
- If the machine is in the list, it has been online within the last 45 days.
- Lists domain-joined computers that:
MATCH (c:Computer {enabled: True}) WITH c, duration.inDays(datetime({epochSeconds: toInteger(c.pwdlastset)}), datetime()).days AS lastPWActivityDaysAgo WHERE c.operatingsystem =~ '(?i).*\\b(2000|2003|2008|xp|vista| 7|me|2012)\\b.*' AND lastPWActivityDaysAgo < 45 RETURN c.name AS ComputerName, c.operatingsystem AS OS, DATETIME({epochSeconds: toInteger(c.pwdlastset)}) AS PwdLastSet, lastPWActivityDaysAgo ORDER BY lastPWActivityDaysAgo ASC
If you want to confirm that hosts listed above respond to ping and if port 445 is reachable, this PowerShell one-liner will take CSV exported from Neo4j from the query above as input, and it will output a new CSV with the ping and port scan results.
get-content '.\bloodhound-unsupported_OS.csv' | convertfrom-csv | select ComputerName | foreach { Test-NetConnection -ComputerName $_.ComputerName -Port 445 } | convertto-csv | add-content ping_and_portscan_results.csv -PassThru- List operating systems ONLY (for reporting):
MATCH (c:Computer {enabled: True}) WITH c, duration.inDays(datetime({epochSeconds: toInteger(c.pwdlastset)}), datetime()).days AS lastPWActivityDaysAgo WHERE c.operatingsystem =~ '(?i).*\\b(2000|2003|2008|xp|vista| 7|me|2012)\\b.*' AND lastPWActivityDaysAgo < 45 RETURN DISTINCT c.operatingsystem AS UnsupportedOS ORDER BY UnsupportedOS ASC
- Check for out-of-date Domain Controller OSes (additional severity)
- Note that this query DOES NOT limit results to passwords changed in the last 45 days.
MATCH p=(c:Computer {enabled: true})-[:MemberOf*1]->(g:Group) WITH c, g, duration.inDays(datetime({epochSeconds: toInteger(c.pwdlastset)}), datetime()).days AS lastPWActivityDaysAgo WHERE c.operatingsystem =~ '(?i).*\\b(2000|2003|2008|xp|vista| 7|me|2012)\\b.*' AND g.name =~ '(?i).*(Domain.Controllers).*' RETURN g.name AS Domain_Controllers_Group,c.name AS Computer, c.operatingsystem AS Operating_System, c.enabled AS Active, lastPWActivityDaysAgo AS Days_Since_Last_Password_Change ORDER BY Domain_Controllers_Group ASC, Days_Since_Last_Password_Change DESC- Some boilerplate report text for your finding:
Versions of the Windows operating system were in use within the internal network that no longer receive support from Microsoft.
Specific outdated operating systems observed in the environment included:
<BULLETED LIST OF OPERATING SYSTEMS FROM SECOND CYPHER QUERY>
As shown in the table below, mainstream and extended support has ended these products. And while the subscription-based, Extended Security Update program is available for Windows Server 2012, BHIS was unable to determine whether the Server 2012 hosts observed were receiving Extended Security Updates.
<TABLE FROM BELOW. REMOVE ROWS IF DESIRED/AS APPROPRIATE>
The complete list of hosts affected by this finding is included in the supporting data archive that accompanies this report.
<INCLUDE THE CSV EXPORTED FROM BLOODHOUND FROM THE FIRST QUERY>
<SCREENSHOT OF NEO4J OUTPUT SHOWING A PORTION OF THE AFFECTED HOSTS>
Here is a table that might help too.
- Reference: https://learn.microsoft.com/en-us/lifecycle/
| Operating System | End of Mainstream Support | End of Extended Support | End of Extended Security Updates (Paid subscription) |
|---|---|---|---|
| Windows 7 | January 13, 2015 | January 14, 2020 | January 10, 2023 |
| Windows Server 2008 | January 13, 2015 | January 14, 2020 | January 10, 2023 |
| Windows Server 2008 R2 | January 13, 2015 | January 14, 2020 | January 10, 2023 |
| Windows Server 2012 | October 9, 2018 | October 10, 2023 | October 13, 2026 |
| Windows Server 2012 R2 | October 9, 2018 | October 10, 2023 | October 13, 2026 |
Password Expiration Exceptions
List all active (enabled) users with password never expires - THIS IS A FINDING
- Also lists Number of days since last password change
- This will help determine if a third-party/non-AD service is automatically changing the passwords.
- If it’s been multiple years since the last password change, probably they are not getting changed. :P
match (u:User {enabled:true, pwdneverexpires:true} ) WITH u, duration.inDays(datetime({epochSeconds: toInteger(u.pwdlastset)}), datetime()).days AS Days_Since_Password_Change RETURN u.name AS User,u.enabled AS Account_Enabled,u.pwdneverexpires AS Password_Never_Expires,Days_Since_Password_Change ORDER BY Days_Since_Password_Change DESC
Widespread Local Admin? / LAPS not in use
- Not necessarily a finding on its own, but it can add additional information.
Count all of the computers that do not have LAPS enabled.
MATCH (c:Computer {enabled: True, haslaps: false }) WITH c, duration.inDays(datetime({epochSeconds: toInteger(c.pwdlastset)}), datetime()).days AS lastPWActivityDaysAgo WHERE lastPWActivityDaysAgo < 45 RETURN c.name AS Computer_Name, c.haslaps AS Has_LAPS
Count all of the computers in the domain for comparison.
MATCH (c:Computer) RETURN count(DISTINCT(c.name)) AS COMPUTER
Excessive Domain Account SPNs
List all Kerberoastable users that are members of High Value Target groups:
- DA’s in here? That’s a findin.’
MATCH p=shortestPath((n:User {hasspn:true, enabled:true})-[:MemberOf]->(g:Group {highvalue:true})) RETURN n.name AS Kerberoastable_User,g.name AS Privileged_Group
Query to ALSO include AS-REP roastable users:
MATCH p=shortestPath((n:User enabled:true)-[:MemberOf]->(g:Group)) WHERE g.highvalue=true AND ( n.hasspn=true OR n.dontreqpreauth=true ) RETURN n.name AS Roastable_User,g.name AS Privileged_Group