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!

  1. 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.
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
  1. 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
  1. 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
  1. 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.

Operating SystemEnd of Mainstream SupportEnd of Extended SupportEnd of Extended Security Updates (Paid subscription)
Windows 7January 13, 2015January 14, 2020January 10, 2023
Windows Server 2008January 13, 2015January 14, 2020January 10, 2023
Windows Server 2008 R2January 13, 2015January 14, 2020January 10, 2023
Windows Server 2012October 9, 2018October 10, 2023October 13, 2026
Windows Server 2012 R2October 9, 2018October 10, 2023October 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:

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

See Also