1+ using System ;
2+ using System . Collections . Concurrent ;
3+ using System . Collections . Generic ;
4+ using System . DirectoryServices . Protocols ;
5+ using System . IO ;
6+ using System . Linq ;
7+ using System . Text . RegularExpressions ;
8+ using System . Threading . Tasks ;
9+ using System . Xml . XPath ;
10+ using Microsoft . Extensions . Logging ;
11+ using SharpHoundCommonLib . Enums ;
12+ using SharpHoundCommonLib . LDAPQueries ;
13+ using SharpHoundCommonLib . OutputTypes ;
14+
15+ namespace SharpHoundCommonLib . Processors
16+ {
17+ public class GPOLmCompatibilityLevelProcessor
18+ {
19+ private static readonly Regex NTLMv1Regex = new Regex ( @"\\LmCompatibilityLevel *= *\d+ *, *(\d)" , RegexOptions . Compiled | RegexOptions . Singleline | RegexOptions . IgnoreCase ) ;
20+
21+ private readonly ILogger _log ;
22+
23+ private readonly ILDAPUtils _utils ;
24+
25+ public GPOLmCompatibilityLevelProcessor ( ILDAPUtils utils , ILogger log = null )
26+ {
27+ _utils = utils ;
28+ _log = log ?? Logging . LogProvider . CreateLogger ( "GPOLmCompatProc" ) ;
29+ }
30+
31+ public Task < Boolean > ReadGPOLmCompatibilityLevel ( ISearchResultEntry entry )
32+ {
33+ var dn = entry . DistinguishedName ;
34+ return ReadGPOLmCompatibilityLevel ( dn ) ;
35+ }
36+
37+ public async Task < Boolean > ReadGPOLmCompatibilityLevel ( string gpDn )
38+ {
39+ var opts = new LDAPQueryOptions
40+ {
41+ Filter = new LDAPFilter ( ) . AddAllObjects ( ) . GetFilter ( ) ,
42+ Scope = SearchScope . Base ,
43+ Properties = CommonProperties . GPCFileSysPath ,
44+ AdsPath = gpDn
45+ } ;
46+ var filePath = _utils . QueryLDAP ( opts ) . FirstOrDefault ( ) ?
47+ . GetProperty ( LDAPProperties . GPCFileSYSPath ) ;
48+ if ( filePath == null )
49+ {
50+ _log . LogWarning ( "Unable to process {} for NTLMv1 flag" , gpDn ) ;
51+ return false ;
52+ }
53+
54+
55+ //Add the actions for each file. The GPO template file actions will override the XML file actions
56+ return await ProcessGPOTemplateFile ( filePath ) ;
57+ }
58+
59+ /// <summary>
60+ /// Parses a GPO GptTmpl.inf file and pulls group membership changes out
61+ /// </summary>
62+ /// <param name="basePath"></param>
63+ /// <param name="gpoDomain"></param>
64+ /// <returns></returns>
65+ internal async Task < Boolean > ProcessGPOTemplateFile ( string basePath )
66+ {
67+ var templatePath = Path . Combine ( basePath , "MACHINE" , "Microsoft" , "Windows NT" , "SecEdit" , "GptTmpl.inf" ) ;
68+
69+ if ( ! File . Exists ( templatePath ) )
70+ return false ;
71+
72+ FileStream fs ;
73+ try
74+ {
75+ fs = new FileStream ( templatePath , FileMode . Open , FileAccess . Read ) ;
76+ }
77+ catch
78+ {
79+ return false ;
80+ }
81+
82+ using var reader = new StreamReader ( fs ) ;
83+ var content = await reader . ReadToEndAsync ( ) ;
84+ var ntlmv1Match = NTLMv1Regex . Match ( content ) ;
85+
86+ if ( ! ntlmv1Match . Success )
87+ return false ;
88+
89+ //We've got a match! Lets figure out whats going on
90+ var ntlmv1Text = int . Parse ( ntlmv1Match . Groups [ 1 ] . Value ) ;
91+ return ntlmv1Text < 3 ;
92+ }
93+ }
94+ }
0 commit comments