54 lines
2.4 KiB
C#
54 lines
2.4 KiB
C#
namespace OCSentinelCli;
|
|
|
|
internal static class ExchangeIisAccountCorrelator
|
|
{
|
|
private static readonly TimeSpan CorrelationWindow = TimeSpan.FromMinutes(2);
|
|
|
|
internal static List<AttackEvent> Enrich(IReadOnlyList<AttackEvent> attacks)
|
|
{
|
|
List<AttackEvent> securityFailures = attacks
|
|
.Where(IsNetworkSecurityFailure)
|
|
.Where(HasRecordedAccount)
|
|
.ToList();
|
|
|
|
return attacks.Select(attack => IsUnresolvedExchangeIisFailure(attack)
|
|
? EnrichFromSecurityFailure(attack, securityFailures)
|
|
: attack).ToList();
|
|
}
|
|
|
|
private static AttackEvent EnrichFromSecurityFailure(AttackEvent iisFailure, IReadOnlyList<AttackEvent> securityFailures)
|
|
{
|
|
List<string> accounts = securityFailures
|
|
.Where(failure => string.Equals(failure.SourceIp, iisFailure.SourceIp, StringComparison.OrdinalIgnoreCase))
|
|
.Where(failure => (failure.Timestamp - iisFailure.Timestamp).Duration() <= CorrelationWindow)
|
|
.Select(failure => failure.Username)
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
// A shared NAT address can produce concurrent failures; do not guess between accounts.
|
|
return accounts.Count == 1 ? iisFailure with { Username = accounts[0] } : iisFailure;
|
|
}
|
|
|
|
private static bool IsUnresolvedExchangeIisFailure(AttackEvent attack)
|
|
{
|
|
return string.Equals(attack.Source, "IIS W3C", StringComparison.OrdinalIgnoreCase)
|
|
&& (string.Equals(attack.Username, "[not logged]", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(attack.Username, "[unknown]", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static bool IsNetworkSecurityFailure(AttackEvent attack)
|
|
{
|
|
return attack.InstanceId == 4625
|
|
&& string.Equals(attack.Source, "Security", StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(attack.Service, "Network", StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(attack.Target, "Windows login", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static bool HasRecordedAccount(AttackEvent attack)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(attack.Username)
|
|
&& !string.Equals(attack.Username, "[unknown]", StringComparison.OrdinalIgnoreCase)
|
|
&& !string.Equals(attack.Username, "[not logged]", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|