Skip to content

Commit f5163c3

Browse files
committed
PingCastle 3.2.0.0 beta 2
1 parent 85f8db6 commit f5163c3

14 files changed

+224
-119
lines changed

ADWS/ADItem.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ static ADItem()
316316
public DateTime WhenCreated { get; set; }
317317
[ADAttributeAttribute("whenChanged", ADAttributeValueKind.DateValue2)]
318318
public DateTime WhenChanged { get; set; }
319-
319+
320320
public List<string> GetApplicableGPO()
321321
{
322322
var output = new List<string>();
@@ -424,7 +424,7 @@ private static SecurityIdentifier ExtractSIDValue(XmlNode child)
424424
}
425425

426426
// see https://msdn.microsoft.com/en-us/library/cc223786.aspx
427-
private static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[] data)
427+
internal static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[] data)
428428
{
429429
List<HealthCheckTrustDomainInfoData> output = new List<HealthCheckTrustDomainInfoData>();
430430
Trace.WriteLine("Beginning to analyze a forestinfo data " + Convert.ToBase64String(data));
@@ -473,6 +473,33 @@ private static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[
473473
domaininfoc.Sid = sid.Value;
474474
output.Add(domaininfoc);
475475
}
476+
else if (recordType == 4)
477+
{
478+
/*Trace.WriteLine("RecordType 4");
479+
int tempPointer = pointer + recordSize;
480+
int binaryDataLen = BitConverter.ToInt32(data, tempPointer);
481+
tempPointer += 4;
482+
int subRecordType = BitConverter.ToInt32(data, tempPointer);
483+
tempPointer += 4;
484+
int sidLen = data[ tempPointer];
485+
tempPointer += 1;
486+
if (sidLen > 0)
487+
{
488+
SecurityIdentifier sid = new SecurityIdentifier(data, tempPointer);
489+
tempPointer += sidLen;
490+
}
491+
int DnsNameLen = BitConverter.ToInt32(data, tempPointer);
492+
tempPointer += 4;
493+
string DnsName = UnicodeEncoding.UTF8.GetString(data, tempPointer, DnsNameLen);
494+
tempPointer += DnsNameLen;
495+
int NetbiosNameLen = BitConverter.ToInt32(data, tempPointer);
496+
tempPointer += 4;
497+
string NetbiosName = UnicodeEncoding.UTF8.GetString(data, tempPointer, NetbiosNameLen);
498+
tempPointer += NetbiosNameLen;*/
499+
}
500+
else
501+
{
502+
}
476503
pointer += 4 + recordLen;
477504
}
478505
return output;

Exports/ExportComputers.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public override void Export(string filename)
4747
header.AddRange(hcprop);
4848
header.Add("OperatingSystem");
4949
header.Add("OperatingSystemVersion");
50-
header.Add("IsCluster");
5150
header.Add("PC OS 1");
5251
header.Add("PC OS 2");
52+
header.Add("IsCluster");
5353
header.Add("LAPS last update (legacy LAPS)");
5454
header.Add("LAPS last update (Ms LAPS)");
5555

@@ -61,6 +61,15 @@ public override void Export(string filename)
6161
{
6262
var d = new AddData();
6363
HealthcheckAnalyzer.ProcessAccountData(d, x, false, default(DateTime));
64+
if (lapsAnalyzer.LegacyLAPSIntId != 0 && x.ReplPropertyMetaData != null && x.ReplPropertyMetaData.ContainsKey(lapsAnalyzer.LegacyLAPSIntId))
65+
{
66+
d.AddWithoutDetail("LAPS");
67+
}
68+
if (lapsAnalyzer.MsLAPSIntId != 0 && x.ReplPropertyMetaData != null && x.ReplPropertyMetaData.ContainsKey(lapsAnalyzer.MsLAPSIntId))
69+
{
70+
d.AddWithoutDetail("LAPSNew");
71+
}
72+
6473
if ((++export % 500) == 0)
6574
{
6675
DisplayAdvancement("Exported: " + export);
@@ -143,7 +152,9 @@ public override void Export(string filename)
143152
};
144153

145154
DisplayAdvancement("Starting");
146-
adws.Enumerate(domainInfo.DefaultNamingContext, HealthcheckAnalyzer.computerfilter, HealthcheckAnalyzer.computerProperties, callback, "SubTree");
155+
var attributes = new List<string> (HealthcheckAnalyzer.computerProperties);
156+
attributes.Add("replPropertyMetaData");
157+
adws.Enumerate(domainInfo.DefaultNamingContext, HealthcheckAnalyzer.computerfilter, attributes.ToArray(), callback, "SubTree");
147158
DisplayAdvancement("Done");
148159
}
149160
}

Healthcheck/HealthcheckAnalyzer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5296,6 +5296,7 @@ void TestFirewallRPCDC(HealthcheckDomainController DC, int threadId)
52965296
guid = Guid.Parse("12345678-1234-ABCD-EF00-0123456789AB"),
52975297
pipe = "spoolss",
52985298
major = 1,
5299+
minor = 0,
52995300
functions = new Dictionary<string, int> { { "RpcRemoteFindFirstPrinterChangeNotification", 62 },{ "RpcRemoteFindFirstPrinterChangeNotificationEx", 65 }}
53005301
},
53015302
};

Healthcheck/Rules/HeatlcheckRulePrivilegedAdminLogin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public class HeatlcheckRulePrivilegedAdminLogin : RuleBase<HealthcheckData>
7171
Trace.WriteLine("P-AdminLogin: computation value: " + minDays);
7272
return minDays;
7373
}
74-
return 0;
74+
return 3 * threshold;
7575
}
7676

7777
Trace.WriteLine("P-AdminLogin: fallback to default computation model");

Healthcheck/Rules/HeatlcheckRuleStaleADRegistrationEnabled.cs

Lines changed: 19 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,81 +23,36 @@ public class HeatlcheckRuleStaleADRegistrationEnabled : RuleBase<HealthcheckData
2323
{
2424
return 0;
2525
}
26-
var gpo = new Dictionary<GPOInfo, bool>();
26+
var gpo = new Dictionary<IGPOReference, string>();
2727
foreach (GPPRightAssignment right in healthcheckData.GPPRightAssignment)
2828
{
29-
if (string.IsNullOrEmpty(right.GPOId))
30-
{
31-
continue;
32-
}
33-
if (healthcheckData.GPOInfoDic == null || !healthcheckData.GPOInfoDic.ContainsKey(right.GPOId))
34-
{
35-
continue;
36-
}
37-
var refGPO = healthcheckData.GPOInfoDic[right.GPOId];
38-
if (refGPO.IsDisabled)
39-
{
40-
continue;
41-
}
42-
if (refGPO.AppliedTo == null || refGPO.AppliedTo.Count == 0)
43-
{
44-
continue;
45-
}
4629
if (right.Privilege == "SeMachineAccountPrivilege")
4730
{
48-
if (right.User == GraphObjectReference.Everyone
49-
|| right.User == GraphObjectReference.AuthenticatedUsers
50-
|| right.User == GraphObjectReference.Users
51-
|| right.User == GraphObjectReference.Anonymous
52-
)
53-
{
54-
Trace.WriteLine("SeMachineAccountPrivilege found in GPO 1 " + right.GPOName);
55-
gpo[refGPO] = true;
56-
}
57-
else
58-
{
59-
Trace.WriteLine("SeMachineAccountPrivilege found in GPO 2 " + right.GPOName);
60-
gpo[refGPO] = false;
61-
}
62-
}
63-
}
64-
// note: according to https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/add-workstations-to-domain if not GPO sets SeMachineAccountPrivilege it is assigned by DC to authenticated users.
65-
if (gpo.Count == 0)
66-
return healthcheckData.MachineAccountQuota;
67-
var applied = new Dictionary<string, Dictionary<int, bool>>();
68-
foreach (var v in gpo.Keys)
69-
{
70-
for (int i = 0; i < v.AppliedTo.Count; i++)
71-
{
72-
var a = v.AppliedTo[i];
73-
int order = 0;
74-
if (v.AppliedOrder != null && v.AppliedOrder.Count > i)
75-
{
76-
order = v.AppliedOrder[i];
77-
}
78-
if (!applied.ContainsKey(a))
79-
applied[a] = new Dictionary<int, bool>();
80-
applied[a][order] = gpo[v];
31+
gpo.Add(right, right.User);
8132
}
8233
}
83-
var applied2 = new Dictionary<string, bool>();
84-
foreach (var a in applied.Keys)
34+
var o = ApplyGPOPrority2(healthcheckData, gpo);
35+
36+
bool found = false;
37+
foreach (var v in o)
8538
{
86-
var min = int.MaxValue;
87-
var w = false;
88-
foreach (var v in applied[a])
39+
found = true;
40+
if (v.Value == GraphObjectReference.Everyone
41+
|| v.Value == GraphObjectReference.AuthenticatedUsers
42+
|| v.Value == GraphObjectReference.Users
43+
|| v.Value == GraphObjectReference.Anonymous
44+
)
8945
{
90-
if (v.Key < min)
91-
{
92-
w = v.Value;
93-
}
46+
Trace.WriteLine("Found on " + v.Key.GPOName + " with " + v.Value);
47+
return healthcheckData.MachineAccountQuota;
9448
}
95-
applied2[a] = w;
9649
}
97-
foreach (var v in applied2)
50+
51+
// note: according to https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/add-workstations-to-domain if not GPO sets SeMachineAccountPrivilege it is assigned by DC to authenticated users.
52+
if (!found)
9853
{
99-
if (v.Value == true)
100-
return healthcheckData.MachineAccountQuota;
54+
Trace.WriteLine("Defined in no GPO so default AD settings");
55+
return healthcheckData.MachineAccountQuota;
10156
}
10257
return 0;
10358
}

Healthcheck/Rules/HeatlcheckRuleStaledObsoleteW10.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ public class HeatlcheckRuleStaledObsoleteW10 : RuleBase<HealthcheckData>
4343
// and https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
4444
switch (release)
4545
{
46+
case 22631:
47+
if (healthcheckData.GenerationDate > new DateTime(2026, 11, 10))
48+
{
49+
AddRawDetail("Windows 11 23H2", osVersion.data.Number, osVersion.data.NumberActive);
50+
totalActive += osVersion.data.NumberActive;
51+
}
52+
break;
4653
case 22621:
4754
if (healthcheckData.GenerationDate > new DateTime(2025, 10, 14))
4855
{

Healthcheck/Rules/HeatlcheckRuleStaledOldNtlm.cs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Licensed under the Non-Profit OSL. See LICENSE file in the project root for full license information.
66
//
77
using PingCastle.Rules;
8+
using System.Collections.Generic;
89

910
namespace PingCastle.Healthcheck.Rules
1011
{
@@ -18,40 +19,37 @@ public class HeatlcheckRuleStaledOldNtlm : RuleBase<HealthcheckData>
1819
{
1920
protected override int? AnalyzeDataNew(HealthcheckData healthcheckData)
2021
{
21-
bool found = false;
22+
var gpo = new Dictionary<IGPOReference, int>();
23+
2224
if (healthcheckData.GPOLsaPolicy != null)
2325
{
2426
foreach (GPPSecurityPolicy policy in healthcheckData.GPOLsaPolicy)
2527
{
26-
if (healthcheckData.GPOInfoDic == null || !healthcheckData.GPOInfoDic.ContainsKey(policy.GPOId))
27-
{
28-
continue;
29-
}
30-
var refGPO = healthcheckData.GPOInfoDic[policy.GPOId];
31-
if (refGPO.IsDisabled)
32-
{
33-
continue;
34-
}
35-
if (refGPO.AppliedTo == null || refGPO.AppliedTo.Count == 0)
36-
{
37-
continue;
38-
}
3928
// The default level value for LmCompatibilityLevel for each version of Windows is as follows:
4029
// Windows XP: 0 Windows 2003: 2 Vista/2008 3 Win7/2008 R2 3
4130
// DC is not 5
4231
foreach (GPPSecurityPolicyProperty property in policy.Properties)
4332
{
4433
if (property.Property == "LmCompatibilityLevel")
4534
{
46-
found = true;
47-
if (property.Value < 5)
48-
{
49-
AddRawDetail(policy.GPOName, property.Value);
50-
}
35+
gpo.Add(policy, property.Value);
5136
}
5237
}
5338
}
5439
}
40+
41+
var o = ApplyGPOPrority2(healthcheckData, gpo);
42+
43+
bool found = false;
44+
foreach (var v in o)
45+
{
46+
found = true;
47+
if (v.Value < 5)
48+
{
49+
AddRawDetail(v.Key.GPOName, v.Value);
50+
}
51+
}
52+
5553
if (!found)
5654
{
5755
AddRawDetail("Windows default without an active GPO", "3");

Healthcheck/Rules/RuleDescription.resx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,8 @@ Do not apply /quarantine on a forest trust: you will break the transitivity of t
887887
<data name="S_PrimaryGroup_Solution" xml:space="preserve">
888888
<value>Unless strongly justified, change the primary group id to its default: 513 or 514 for users, 516 or 521 for domain controllers, 514 or 515 for computers. The primary group can be edited in a friendly manner by editing the account with the "Active Directory Users and Computers" and after selecting the "Member Of" tab, "set primary group".
889889
You can use the following script to list Users with a primary group id different from domain users:
890-
&lt;i&gt;Get-ADUser -Filter * -Properties PrimaryGroup | Where-Object { $_.PrimaryGroup -ne (Get-ADGroup -Identity "Domain Users").DistinguishedName } | Select-Object UserPrincipalName,PrimaryGroup&lt;/i&gt;</value>
890+
&lt;i&gt;$DomainUsersSid = New-Object System.Security.Principal.SecurityIdentifier ([System.Security.Principal.WellKnownSidType]::AccountDomainUsersSid,(Get-ADDomain).DomainSID)&lt;br&gt;
891+
Get-ADUser -Filter * -Properties PrimaryGroup | Where-Object { $_.PrimaryGroup -ne (Get-ADGroup -Filter {SID -eq $DomainUsersSid} ).DistinguishedName } | Select-Object UserPrincipalName,PrimaryGroup&lt;/i&gt;</value>
891892
</data>
892893
<data name="S_PrimaryGroup_Rationale" xml:space="preserve">
893894
<value>Presence of wrong primary group for users: {count}</value>

RPC/rpcfirewallchecker.cs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,30 +97,6 @@ public static int CheckRPCOpnum(Guid interfaceId, string pipe, ushort majorVersi
9797
return checker.CheckOpNum(opnum, server);
9898
}
9999

100-
public static bool CheckElfrOpenBELW(string server)
101-
{
102-
var expectedError = CheckRPCOpnum(Guid.Parse("82273fdc-e32a-18c3-3f78-827929dc23ea"), "eventlog", 0, 0, 17, server);
103-
if (expectedError == 1783)
104-
return true;
105-
return false;
106-
}
107-
108-
public static bool CheckEfsRpcAddUsersToFile(string server)
109-
{
110-
var expectedError = CheckRPCOpnum(Guid.Parse("df1941c5-fe89-4e79-bf10-463657acf44d"), "netlogon", 1, 0, 9, server);
111-
if (expectedError == 1783)
112-
return true;
113-
return false;
114-
}
115-
116-
public static bool CheckRpcRemoteFindFirstPrinterChangeNotification(string server)
117-
{
118-
var expectedError = CheckRPCOpnum(Guid.Parse("12345678-1234-abcd-ef00-0123456789ab"), "spoolss", 1, 0, 9, server);
119-
if (expectedError == 1783)
120-
return true;
121-
return false;
122-
}
123-
124100
public static List<string> TestFunctions(string server, Guid interfaceId, string pipe, ushort majorVersion, ushort minorVersion, Dictionary<string, int> functionsToTest)
125101
{
126102
List<string> output = new List<string>();

0 commit comments

Comments
 (0)