Dateien in .NET mit Open Source auf Viren prüfen
Scannen Sie eine anwendungseigene Staging-Datei mit ClamAV, bevor Sie sie akzeptieren. Dieses Konsolenprogramm für .NET 10 sendet die Datei an einen lokalen Daemon und liefert getrennte Befunde für „sauber“, „infiziert“ und „Scannerfehler“. Die Eingabe wird niemals bereinigt, verändert oder gelöscht.
Einführung
Nur ein abgeschlossener Scan mit dem Befund „sauber“ erlaubt den nächsten Verarbeitungsschritt. Bei einer fehlenden Datei, einem überschrittenen Größenlimit, einem nicht verfügbaren Daemon, einer fehlerhaften Antwort, einer Zeitüberschreitung oder einem Abbruch darf die Datei nicht akzeptiert werden. Der Befund „sauber“ beschreibt den Scan der Engine mit ihrer aktuellen Konfiguration und ihren aktuellen Signaturen; er garantiert nicht, dass eine Datei harmlos ist.
Open-Source-Antivirentools für .NET
ClamAV stellt den Scanner bereit. Der integrierte TCP-Client von .NET unterstützt dessen dokumentiertes INSTREAM-Protokoll, sodass dieses Beispiel keinen NuGet-Wrapper für den Scanner benötigt. Betreiben Sie den Daemon auf demselben Host: Seine TCP-Schnittstelle bietet keine Authentifizierung.
ClamAV einrichten
Installieren Sie ClamAV anhand der Plattformanleitung,
konfigurieren Sie das Datenbankverzeichnis und aktualisieren Sie die Signaturen mit
freshclam. Binden Sie in clamd.conf
TCPAddr an 127.0.0.1, verwenden Sie
TCPSocket 3310 und setzen Sie StreamMaxLength auf mindestens 25 MiB.
Konfigurieren Sie AlertExceedsMax yes so, dass Scan-Limits eine Warnung auslösen,
anstatt Inhalte stillschweigend zu überspringen. Prüfen Sie MaxFileSize,
MaxScanSize, Archivlimits und die Richtlinie für verschlüsselte Dateien
im Hinblick auf Ihre Arbeitslast. Starten Sie clamd mit dieser
Konfiguration über die Dienstverwaltung Ihrer Plattform.
Installieren Sie das .NET 10 SDK und erstellen Sie anschließend die Anwendung:
dotnet new console --framework net10.0 --name FileScan
cd FileScan
Dateiscans in C# implementieren
Ersetzen Sie Program.cs durch dieses vollständige Programm. Es überträgt
höchstens 25 MiB in Blöcken von 64 KiB, begrenzt die Daemon-Antwort und bricht alle Netzwerk- und
Dateioperationen nach 30 Sekunden oder bei Ctrl+C ab.
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;
public enum Verdict { Clean, Infected, ScannerError }
public static class Program
{
public static async Task<int> Main(string[] args)
{
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(30));
Console.CancelKeyPress += (_, e) => { e.Cancel = true; deadline.Cancel(); };
if (args.Length != 1) { Console.Error.WriteLine("Pass one local file."); return 2; }
var verdict = await FileScanner.ScanAsync(args[0], 3310, deadline.Token);
Console.WriteLine(verdict);
return verdict switch { Verdict.Clean => 0, Verdict.Infected => 1, _ => 2 };
}
}
public static class FileScanner
{
private const int MaxBytes = 25 * 1024 * 1024;
public static async Task<Verdict> ScanAsync(string path, int port, CancellationToken ct)
{
try
{
await using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (file.Length > MaxBytes) return Verdict.ScannerError;
using var client = new TcpClient();
await client.ConnectAsync(IPAddress.Loopback, port, ct);
using var stream = client.GetStream();
await stream.WriteAsync(Encoding.ASCII.GetBytes("zINSTREAM\0"), ct);
var buffer = new byte[64 * 1024];
var header = new byte[4];
long sent = 0;
int count;
while ((count = await file.ReadAsync(buffer, ct)) != 0)
{
sent += count;
if (sent > MaxBytes) return Verdict.ScannerError;
BinaryPrimitives.WriteUInt32BigEndian(header, (uint)count);
await stream.WriteAsync(header, ct);
await stream.WriteAsync(buffer.AsMemory(0, count), ct);
}
BinaryPrimitives.WriteUInt32BigEndian(header, 0);
await stream.WriteAsync(header, ct);
var reply = new List<byte>();
var next = new byte[1];
while (reply.Count < 4096)
{
if (await stream.ReadAsync(next, ct) != 1) return Verdict.ScannerError;
if (next[0] == 0)
{
if (await stream.ReadAsync(next, ct) != 0) return Verdict.ScannerError;
string result = Encoding.UTF8.GetString(reply.ToArray());
if (result == "stream: OK") return Verdict.Clean;
if (result.StartsWith("stream: ", StringComparison.Ordinal) &&
result.EndsWith(" FOUND", StringComparison.Ordinal) && result.Length > 14 &&
!result.Contains("Heuristics.Limits.Exceeded", StringComparison.Ordinal))
return Verdict.Infected;
return Verdict.ScannerError;
}
reply.Add(next[0]);
}
return Verdict.ScannerError;
}
catch (Exception) { return Verdict.ScannerError; }
}
}
Erstellen Sie eine künstliche Textdatei und scannen Sie sie aus dem Projektverzeichnis:
printf 'ordinary test text\n' > sample.txt
dotnet run -- sample.txt
Die Exit-Codes sind 0 für „sauber“, 1
für „infiziert“ und 2 für Scannerfehler. Prüfen Sie den Exit-Code,
bevor Sie eine Datei akzeptieren. Eine abgelehnte Verbindung muss ScannerError
liefern, niemals Clean.
Dateien beim Scannen unverändert lassen
Das Programm öffnet die Eingabe ausschließlich zum Lesen. Bewahren Sie Staging-Dateien in einem anwendungseigenen Verzeichnis auf und verhindern Sie gleichzeitige Schreibzugriffe; verarbeiten Sie dieselben Bytes, die gescannt wurden. Eine Antivirus-API, die Scannen mit Bereinigung verbindet, ist ungeeignet, wenn die Anwendung Originaldateien erhalten muss.
Saubere, infizierte und fehlgeschlagene Scans testen
Verwenden Sie eine gewöhnliche künstliche Datei für den Testfall „sauber“ und die offizielle EICAR-Testdatei für den Erkennungsfall. Testen Sie außerdem einen nicht verfügbaren Daemon und eine Datei, die das Größenlimit überschreitet. Stellen Sie sicher, dass alle Eingaben bei jedem Ergebnis unverändert bleiben. Ein privater Daemon mit einer Signaturdatenbank ausschließlich für Tests prüft die Integration, belegt aber nicht die Abdeckung durch eine Malware-Datenbank für den Produktivbetrieb.
Bewährte Verfahren
Halten Sie die Signaturen im Produktivbetrieb aktuell, prüfen Sie die Engine-Limits und bewahren Sie Staging-Dateien gemäß der Aufbewahrungsrichtlinie Ihrer Anwendung auf. Bei Scan-Fehlern müssen Sie zwischen einem erneuten Versuch und einer Ablehnung entscheiden; sie erlauben keine Veröffentlichung. Halten Sie Zugangsdaten und Scanner-Diagnosen aus Antworten heraus, die für Nutzer sichtbar sind.
Fazit
Die Streaming-Schnittstelle von ClamAV ermöglicht eine schlanke Integration, die Dateien unverändert lässt und Fehler explizit behandelt. Transloadit bietet außerdem einen Virus Scan Robot für Dateien in einer Verarbeitungspipeline.
