From 072a4257ed225a05d2a6f0f4450421cd04f60978 Mon Sep 17 00:00:00 2001 From: OfficeCom Codex Date: Sat, 1 Aug 2026 01:41:57 +0200 Subject: [PATCH] Report ransomware sensor coverage in beta --- docs/beta-deployment.md | 6 ++ docs/roadmap.md | 2 + src/OCSentinelCli/Models.cs | 15 +++++ src/OCSentinelCli/OCSentinelCli.csproj | 4 +- src/OCSentinelCli/RansomwareBetaDetector.cs | 56 +++++++++++++++---- .../RansomwareBetaTests.cs | 25 +++++++++ 6 files changed, 96 insertions(+), 12 deletions(-) diff --git a/docs/beta-deployment.md b/docs/beta-deployment.md index 46a1f32..8eee286 100644 --- a/docs/beta-deployment.md +++ b/docs/beta-deployment.md @@ -27,6 +27,12 @@ Ausnahme kann `ransomwareBetaEnabled` in `C:\Program Files\OCSentinel\config\ocsentinel-settings.json` auf `false` gesetzt werden. +Jeder Beta-Report enthaelt zudem eine datensparsame Sensorabdeckung fuer +Security-Ereignis 4688, PowerShell 4104, Sysmon 1 und das optionale +Datei-Auditing 4663. Damit bedeutet ein Status `ok` nicht mehr stillschweigend, +dass alle Datenquellen vorhanden waren. Es werden nur Verfuegbarkeit, +technischer Zustand und gezaehlte Ereignisse uebertragen. + Der optionale Datei-Churn-Sensor wird nur mit `ransomwareFileChurnEnabled: true` aktiviert. Er wertet ausschliesslich bereits vorhandene Security-Ereignisse 4663 aus, setzt keine Audit-Richtlinie und diff --git a/docs/roadmap.md b/docs/roadmap.md index d67d438..d74128f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -126,6 +126,8 @@ Ein Pilot wird pausiert und zurueckgesetzt, wenn eines dieser Kriterien eintritt - Snapshot der SMB-Sitzungen und des Incident-Kontexts erst bei einer Auffaelligkeit. - Verdichtetes Ransomware-Incident-Protokoll fuer n8n und die Uebersicht. +- Strukturierte Sensorabdeckung im Report, damit fehlende Audit- oder Sysmon- + Quellen von einem echten unauffaelligen Zeitraum unterscheidbar sind. **Bewertung** diff --git a/src/OCSentinelCli/Models.cs b/src/OCSentinelCli/Models.cs index 1d8f47e..425b086 100644 --- a/src/OCSentinelCli/Models.cs +++ b/src/OCSentinelCli/Models.cs @@ -129,11 +129,26 @@ internal sealed record RansomwareBetaSummary public List Signals { get; init; } = []; + public List Sensors { get; init; } = []; + public List SmbSessions { get; init; } = []; public RansomwareFileChurnSummary FileChurn { get; init; } = new(); } +internal sealed record RansomwareSensorStatus +{ + public string Name { get; init; } = string.Empty; + + public bool Enabled { get; init; } + + public bool Available { get; init; } + + public string State { get; init; } = "unknown"; + + public int EventCount { get; init; } +} + internal sealed record RansomwareFileChurnSummary { public bool Enabled { get; init; } diff --git a/src/OCSentinelCli/OCSentinelCli.csproj b/src/OCSentinelCli/OCSentinelCli.csproj index 98134af..7e7e2da 100644 --- a/src/OCSentinelCli/OCSentinelCli.csproj +++ b/src/OCSentinelCli/OCSentinelCli.csproj @@ -9,10 +9,10 @@ OCSentinelCli OfficeCom Sentinel OfficeCom - 1.5.0-beta.4 + 1.5.0-beta.5 1.5.0.0 1.5.0.0 - 1.5.0-beta.4 + 1.5.0-beta.5 diff --git a/src/OCSentinelCli/RansomwareBetaDetector.cs b/src/OCSentinelCli/RansomwareBetaDetector.cs index ccc77ba..120f894 100644 --- a/src/OCSentinelCli/RansomwareBetaDetector.cs +++ b/src/OCSentinelCli/RansomwareBetaDetector.cs @@ -18,11 +18,22 @@ internal static class RansomwareBetaDetector int lookbackMinutes = Math.Clamp(configuration.RansomwareLookbackMinutes, 1, 60); DateTimeOffset since = DateTimeOffset.UtcNow.AddMinutes(-lookbackMinutes); var signals = new List(); + var sensors = new List + { + ScanSecurityProcesses(signals, errors, since, configuration), + ScanPowerShellScriptBlocks(signals, errors, since, configuration), + ScanSysmonProcesses(signals, errors, since, configuration) + }; - ScanSecurityProcesses(signals, errors, since, configuration); - ScanPowerShellScriptBlocks(signals, errors, since, configuration); - ScanSysmonProcesses(signals, errors, since, configuration); RansomwareFileChurnSummary fileChurn = RansomwareFileChurnDetector.Scan(configuration, errors); + sensors.Add(new RansomwareSensorStatus + { + Name = "security-file-audit-4663", + Enabled = fileChurn.Enabled, + Available = fileChurn.DataAvailable, + State = !fileChurn.Enabled ? "disabled" : fileChurn.IsTruncated ? "truncated" : fileChurn.DataAvailable ? "available" : "unavailable", + EventCount = fileChurn.FileOperationCount + }); RansomwareSignal? fileChurnSignal = RansomwareFileChurnDetector.CreateSignal(fileChurn, configuration); if (fileChurnSignal is not null) { @@ -61,24 +72,25 @@ internal static class RansomwareBetaDetector Reason = reason, LookbackMinutes = lookbackMinutes, Signals = distinctSignals, + Sensors = sensors, SmbSessions = smbSessions, FileChurn = fileChurn }; } - private static void ScanSecurityProcesses(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) + private static RansomwareSensorStatus ScanSecurityProcesses(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) { - TryScan("Security", 4688, since, errors, record => AddSignal(signals, record, ReadProperty(record, 5), ReadProperty(record, 8), "Security", configuration)); + return TryScan("security-process-4688", "Security", 4688, since, errors, record => AddSignal(signals, record, ReadProperty(record, 5), ReadProperty(record, 8), "Security", configuration)); } - private static void ScanPowerShellScriptBlocks(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) + private static RansomwareSensorStatus ScanPowerShellScriptBlocks(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) { - TryScan("Microsoft-Windows-PowerShell/Operational", 4104, since, errors, record => AddSignal(signals, record, "powershell", FormatDescription(record), "PowerShell", configuration)); + return TryScan("powershell-script-block-4104", "Microsoft-Windows-PowerShell/Operational", 4104, since, errors, record => AddSignal(signals, record, "powershell", FormatDescription(record), "PowerShell", configuration)); } - private static void ScanSysmonProcesses(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) + private static RansomwareSensorStatus ScanSysmonProcesses(List signals, List errors, DateTimeOffset since, ScannerConfiguration configuration) { - TryScan("Microsoft-Windows-Sysmon/Operational", 1, since, errors, record => AddSignal(signals, record, "sysmon-process", FormatDescription(record), "Sysmon", configuration)); + return TryScan("sysmon-process-1", "Microsoft-Windows-Sysmon/Operational", 1, since, errors, record => AddSignal(signals, record, "sysmon-process", FormatDescription(record), "Sysmon", configuration)); } private static void AddSignal(List signals, EventRecord record, string process, string commandLine, string source, ScannerConfiguration configuration) @@ -146,8 +158,9 @@ internal static class RansomwareBetaDetector private static bool ContainsAll(string value, params string[] needles) => needles.All(needle => value.Contains(needle, StringComparison.Ordinal)); - private static void TryScan(string logName, int eventId, DateTimeOffset since, List errors, Action processRecord) + private static RansomwareSensorStatus TryScan(string sensorName, string logName, int eventId, DateTimeOffset since, List errors, Action processRecord) { + int eventCount = 0; try { long milliseconds = Math.Max(1, (long)(DateTimeOffset.UtcNow - since).TotalMilliseconds); @@ -157,16 +170,39 @@ internal static class RansomwareBetaDetector { using (record) { + eventCount++; processRecord(record); } } + + return new RansomwareSensorStatus + { + Name = sensorName, + Enabled = true, + Available = true, + State = "available", + EventCount = eventCount + }; } catch (EventLogNotFoundException) { + return new RansomwareSensorStatus + { + Name = sensorName, + Enabled = true, + State = "not-installed" + }; } catch (Exception exception) { errors.Add($"Ransomware beta query failed for {logName}: {exception.Message}"); + return new RansomwareSensorStatus + { + Name = sensorName, + Enabled = true, + State = "query-failed", + EventCount = eventCount + }; } } diff --git a/tests/OCSentinelCli.Tests/RansomwareBetaTests.cs b/tests/OCSentinelCli.Tests/RansomwareBetaTests.cs index 3b16112..ac3d3d5 100644 --- a/tests/OCSentinelCli.Tests/RansomwareBetaTests.cs +++ b/tests/OCSentinelCli.Tests/RansomwareBetaTests.cs @@ -1,4 +1,5 @@ using System.Runtime.Versioning; +using System.Text.Json; using Xunit; namespace OCSentinelCli.Tests; @@ -13,6 +14,30 @@ public sealed class RansomwareBetaTests Assert.False(new ScannerConfiguration().RansomwareBetaAlertingEnabled); } + [Fact] + public void DisabledRansomwareBetaDoesNotReportSensorCoverage() + { + var summary = RansomwareBetaDetector.Scan(new ScannerConfiguration { RansomwareBetaEnabled = false }, []); + + Assert.False(summary.Enabled); + Assert.Empty(summary.Sensors); + } + + [Fact] + public void SensorCoverageIsIncludedInTheJsonReport() + { + var summary = new RansomwareBetaSummary + { + Enabled = true, + Sensors = [new RansomwareSensorStatus { Name = "security-process-4688", Enabled = true, Available = true, State = "available", EventCount = 4 }] + }; + + string json = JsonSerializer.Serialize(summary, JsonOptions.Default); + + Assert.Contains("\"Sensors\"", json); + Assert.Contains("security-process-4688", json); + } + [Fact] public void FileChurnBelowBothThresholdsDoesNotCreateSignal() {