Skip to content

Commit

Permalink
PingCastle 3.2.0.0 beta 2
Browse files Browse the repository at this point in the history
  • Loading branch information
vletoux committed Feb 4, 2024
1 parent 85f8db6 commit f5163c3
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 119 deletions.
31 changes: 29 additions & 2 deletions ADWS/ADItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ static ADItem()
public DateTime WhenCreated { get; set; }
[ADAttributeAttribute("whenChanged", ADAttributeValueKind.DateValue2)]
public DateTime WhenChanged { get; set; }

public List<string> GetApplicableGPO()
{
var output = new List<string>();
Expand Down Expand Up @@ -424,7 +424,7 @@ private static SecurityIdentifier ExtractSIDValue(XmlNode child)
}

// see https://msdn.microsoft.com/en-us/library/cc223786.aspx
private static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[] data)
internal static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[] data)
{
List<HealthCheckTrustDomainInfoData> output = new List<HealthCheckTrustDomainInfoData>();
Trace.WriteLine("Beginning to analyze a forestinfo data " + Convert.ToBase64String(data));
Expand Down Expand Up @@ -473,6 +473,33 @@ private static List<HealthCheckTrustDomainInfoData> ConvertByteToTrustInfo(byte[
domaininfoc.Sid = sid.Value;
output.Add(domaininfoc);
}
else if (recordType == 4)
{
/*Trace.WriteLine("RecordType 4");
int tempPointer = pointer + recordSize;
int binaryDataLen = BitConverter.ToInt32(data, tempPointer);
tempPointer += 4;
int subRecordType = BitConverter.ToInt32(data, tempPointer);
tempPointer += 4;
int sidLen = data[ tempPointer];
tempPointer += 1;
if (sidLen > 0)
{
SecurityIdentifier sid = new SecurityIdentifier(data, tempPointer);
tempPointer += sidLen;
}
int DnsNameLen = BitConverter.ToInt32(data, tempPointer);
tempPointer += 4;
string DnsName = UnicodeEncoding.UTF8.GetString(data, tempPointer, DnsNameLen);
tempPointer += DnsNameLen;
int NetbiosNameLen = BitConverter.ToInt32(data, tempPointer);
tempPointer += 4;
string NetbiosName = UnicodeEncoding.UTF8.GetString(data, tempPointer, NetbiosNameLen);
tempPointer += NetbiosNameLen;*/
}
else
{
}
pointer += 4 + recordLen;
}
return output;
Expand Down
15 changes: 13 additions & 2 deletions Exports/ExportComputers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ public override void Export(string filename)
header.AddRange(hcprop);
header.Add("OperatingSystem");
header.Add("OperatingSystemVersion");
header.Add("IsCluster");
header.Add("PC OS 1");
header.Add("PC OS 2");
header.Add("IsCluster");
header.Add("LAPS last update (legacy LAPS)");
header.Add("LAPS last update (Ms LAPS)");

Expand All @@ -61,6 +61,15 @@ public override void Export(string filename)
{
var d = new AddData();
HealthcheckAnalyzer.ProcessAccountData(d, x, false, default(DateTime));
if (lapsAnalyzer.LegacyLAPSIntId != 0 && x.ReplPropertyMetaData != null && x.ReplPropertyMetaData.ContainsKey(lapsAnalyzer.LegacyLAPSIntId))
{
d.AddWithoutDetail("LAPS");
}
if (lapsAnalyzer.MsLAPSIntId != 0 && x.ReplPropertyMetaData != null && x.ReplPropertyMetaData.ContainsKey(lapsAnalyzer.MsLAPSIntId))
{
d.AddWithoutDetail("LAPSNew");
}
if ((++export % 500) == 0)
{
DisplayAdvancement("Exported: " + export);
Expand Down Expand Up @@ -143,7 +152,9 @@ public override void Export(string filename)
};

DisplayAdvancement("Starting");
adws.Enumerate(domainInfo.DefaultNamingContext, HealthcheckAnalyzer.computerfilter, HealthcheckAnalyzer.computerProperties, callback, "SubTree");
var attributes = new List<string> (HealthcheckAnalyzer.computerProperties);
attributes.Add("replPropertyMetaData");
adws.Enumerate(domainInfo.DefaultNamingContext, HealthcheckAnalyzer.computerfilter, attributes.ToArray(), callback, "SubTree");
DisplayAdvancement("Done");
}
}
Expand Down
1 change: 1 addition & 0 deletions Healthcheck/HealthcheckAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5296,6 +5296,7 @@ void TestFirewallRPCDC(HealthcheckDomainController DC, int threadId)
guid = Guid.Parse("12345678-1234-ABCD-EF00-0123456789AB"),
pipe = "spoolss",
major = 1,
minor = 0,
functions = new Dictionary<string, int> { { "RpcRemoteFindFirstPrinterChangeNotification", 62 },{ "RpcRemoteFindFirstPrinterChangeNotificationEx", 65 }}
},
};
Expand Down
2 changes: 1 addition & 1 deletion Healthcheck/Rules/HeatlcheckRulePrivilegedAdminLogin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class HeatlcheckRulePrivilegedAdminLogin : RuleBase<HealthcheckData>
Trace.WriteLine("P-AdminLogin: computation value: " + minDays);
return minDays;
}
return 0;
return 3 * threshold;
}

Trace.WriteLine("P-AdminLogin: fallback to default computation model");
Expand Down
83 changes: 19 additions & 64 deletions Healthcheck/Rules/HeatlcheckRuleStaleADRegistrationEnabled.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,81 +23,36 @@ public class HeatlcheckRuleStaleADRegistrationEnabled : RuleBase<HealthcheckData
{
return 0;
}
var gpo = new Dictionary<GPOInfo, bool>();
var gpo = new Dictionary<IGPOReference, string>();
foreach (GPPRightAssignment right in healthcheckData.GPPRightAssignment)
{
if (string.IsNullOrEmpty(right.GPOId))
{
continue;
}
if (healthcheckData.GPOInfoDic == null || !healthcheckData.GPOInfoDic.ContainsKey(right.GPOId))
{
continue;
}
var refGPO = healthcheckData.GPOInfoDic[right.GPOId];
if (refGPO.IsDisabled)
{
continue;
}
if (refGPO.AppliedTo == null || refGPO.AppliedTo.Count == 0)
{
continue;
}
if (right.Privilege == "SeMachineAccountPrivilege")
{
if (right.User == GraphObjectReference.Everyone
|| right.User == GraphObjectReference.AuthenticatedUsers
|| right.User == GraphObjectReference.Users
|| right.User == GraphObjectReference.Anonymous
)
{
Trace.WriteLine("SeMachineAccountPrivilege found in GPO 1 " + right.GPOName);
gpo[refGPO] = true;
}
else
{
Trace.WriteLine("SeMachineAccountPrivilege found in GPO 2 " + right.GPOName);
gpo[refGPO] = false;
}
}
}
// 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.
if (gpo.Count == 0)
return healthcheckData.MachineAccountQuota;
var applied = new Dictionary<string, Dictionary<int, bool>>();
foreach (var v in gpo.Keys)
{
for (int i = 0; i < v.AppliedTo.Count; i++)
{
var a = v.AppliedTo[i];
int order = 0;
if (v.AppliedOrder != null && v.AppliedOrder.Count > i)
{
order = v.AppliedOrder[i];
}
if (!applied.ContainsKey(a))
applied[a] = new Dictionary<int, bool>();
applied[a][order] = gpo[v];
gpo.Add(right, right.User);
}
}
var applied2 = new Dictionary<string, bool>();
foreach (var a in applied.Keys)
var o = ApplyGPOPrority2(healthcheckData, gpo);

bool found = false;
foreach (var v in o)
{
var min = int.MaxValue;
var w = false;
foreach (var v in applied[a])
found = true;
if (v.Value == GraphObjectReference.Everyone
|| v.Value == GraphObjectReference.AuthenticatedUsers
|| v.Value == GraphObjectReference.Users
|| v.Value == GraphObjectReference.Anonymous
)
{
if (v.Key < min)
{
w = v.Value;
}
Trace.WriteLine("Found on " + v.Key.GPOName + " with " + v.Value);
return healthcheckData.MachineAccountQuota;
}
applied2[a] = w;
}
foreach (var v in applied2)

// 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.
if (!found)
{
if (v.Value == true)
return healthcheckData.MachineAccountQuota;
Trace.WriteLine("Defined in no GPO so default AD settings");
return healthcheckData.MachineAccountQuota;
}
return 0;
}
Expand Down
7 changes: 7 additions & 0 deletions Healthcheck/Rules/HeatlcheckRuleStaledObsoleteW10.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ public class HeatlcheckRuleStaledObsoleteW10 : RuleBase<HealthcheckData>
// and https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
switch (release)
{
case 22631:
if (healthcheckData.GenerationDate > new DateTime(2026, 11, 10))
{
AddRawDetail("Windows 11 23H2", osVersion.data.Number, osVersion.data.NumberActive);
totalActive += osVersion.data.NumberActive;
}
break;
case 22621:
if (healthcheckData.GenerationDate > new DateTime(2025, 10, 14))
{
Expand Down
36 changes: 17 additions & 19 deletions Healthcheck/Rules/HeatlcheckRuleStaledOldNtlm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Licensed under the Non-Profit OSL. See LICENSE file in the project root for full license information.
//
using PingCastle.Rules;
using System.Collections.Generic;

namespace PingCastle.Healthcheck.Rules
{
Expand All @@ -18,40 +19,37 @@ public class HeatlcheckRuleStaledOldNtlm : RuleBase<HealthcheckData>
{
protected override int? AnalyzeDataNew(HealthcheckData healthcheckData)
{
bool found = false;
var gpo = new Dictionary<IGPOReference, int>();

if (healthcheckData.GPOLsaPolicy != null)
{
foreach (GPPSecurityPolicy policy in healthcheckData.GPOLsaPolicy)
{
if (healthcheckData.GPOInfoDic == null || !healthcheckData.GPOInfoDic.ContainsKey(policy.GPOId))
{
continue;
}
var refGPO = healthcheckData.GPOInfoDic[policy.GPOId];
if (refGPO.IsDisabled)
{
continue;
}
if (refGPO.AppliedTo == null || refGPO.AppliedTo.Count == 0)
{
continue;
}
// The default level value for LmCompatibilityLevel for each version of Windows is as follows:
// Windows XP: 0 Windows 2003: 2 Vista/2008 3 Win7/2008 R2 3
// DC is not 5
foreach (GPPSecurityPolicyProperty property in policy.Properties)
{
if (property.Property == "LmCompatibilityLevel")
{
found = true;
if (property.Value < 5)
{
AddRawDetail(policy.GPOName, property.Value);
}
gpo.Add(policy, property.Value);
}
}
}
}

var o = ApplyGPOPrority2(healthcheckData, gpo);

bool found = false;
foreach (var v in o)
{
found = true;
if (v.Value < 5)
{
AddRawDetail(v.Key.GPOName, v.Value);
}
}

if (!found)
{
AddRawDetail("Windows default without an active GPO", "3");
Expand Down
3 changes: 2 additions & 1 deletion Healthcheck/Rules/RuleDescription.resx
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,8 @@ Do not apply /quarantine on a forest trust: you will break the transitivity of t
<data name="S_PrimaryGroup_Solution" xml:space="preserve">
<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".
You can use the following script to list Users with a primary group id different from domain users:
&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>
&lt;i&gt;$DomainUsersSid = New-Object System.Security.Principal.SecurityIdentifier ([System.Security.Principal.WellKnownSidType]::AccountDomainUsersSid,(Get-ADDomain).DomainSID)&lt;br&gt;
Get-ADUser -Filter * -Properties PrimaryGroup | Where-Object { $_.PrimaryGroup -ne (Get-ADGroup -Filter {SID -eq $DomainUsersSid} ).DistinguishedName } | Select-Object UserPrincipalName,PrimaryGroup&lt;/i&gt;</value>
</data>
<data name="S_PrimaryGroup_Rationale" xml:space="preserve">
<value>Presence of wrong primary group for users: {count}</value>
Expand Down
24 changes: 0 additions & 24 deletions RPC/rpcfirewallchecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,30 +97,6 @@ public static int CheckRPCOpnum(Guid interfaceId, string pipe, ushort majorVersi
return checker.CheckOpNum(opnum, server);
}

public static bool CheckElfrOpenBELW(string server)
{
var expectedError = CheckRPCOpnum(Guid.Parse("82273fdc-e32a-18c3-3f78-827929dc23ea"), "eventlog", 0, 0, 17, server);
if (expectedError == 1783)
return true;
return false;
}

public static bool CheckEfsRpcAddUsersToFile(string server)
{
var expectedError = CheckRPCOpnum(Guid.Parse("df1941c5-fe89-4e79-bf10-463657acf44d"), "netlogon", 1, 0, 9, server);
if (expectedError == 1783)
return true;
return false;
}

public static bool CheckRpcRemoteFindFirstPrinterChangeNotification(string server)
{
var expectedError = CheckRPCOpnum(Guid.Parse("12345678-1234-abcd-ef00-0123456789ab"), "spoolss", 1, 0, 9, server);
if (expectedError == 1783)
return true;
return false;
}

public static List<string> TestFunctions(string server, Guid interfaceId, string pipe, ushort majorVersion, ushort minorVersion, Dictionary<string, int> functionsToTest)
{
List<string> output = new List<string>();
Expand Down
Loading

0 comments on commit f5163c3

Please sign in to comment.