Dateien in .NET mit Open Source auf Viren scannen
Scannen Sie eine Datei im Zwischenverzeichnis Ihrer Anwendung mit ClamAV, bevor Sie sie akzeptieren. Dieses .NET-10-Konsolenprogramm sendet die Datei an einen lokalen Daemon und liefert getrennte Befunde für unauffällige Dateien, infizierte Dateien und Scannerfehler. Es bereinigt, verändert oder löscht die Eingabe niemals.
Einführung
Nur ein abgeschlossener Scan mit unauffälligem Befund erlaubt den nächsten Verarbeitungsschritt. Bei einer fehlenden Datei, einer Größenbegrenzung, einem nicht verfügbaren Daemon, einer fehlerhaften Antwort, einer Zeitüberschreitung oder einem Abbruch darf die Datei nicht akzeptiert werden. Ein unauffälliger Befund beschreibt den Scan der Engine mit ihrer aktuellen Konfiguration und ihren aktuellen Signaturen; er garantiert nicht, dass eine Datei harmlos ist.
Open-Source-Antivirus-Tools für .NET
ClamAV stellt den Scanner bereit. Der integrierte TCP-Client von .NET kann dessen dokumentiertes INSTREAM-Protokoll verwenden. Dieses Beispiel benötigt daher keinen NuGet-Wrapper für den Scanner. Betreiben Sie den Daemon auf demselben Host: Seine TCP-Schnittstelle bietet keine Authentifizierung.
ClamAV einrichten
Die folgenden Befehle verwenden Bash unter Linux. Installieren Sie das aktuelle .NET 10 SDK, Version 10.0.111 oder neuer innerhalb der .NET-10-Reihe, sowie ClamAV 1.5.4 gemäß der plattformbezogenen Anleitung. Das Programm wurde mit SDK 10.0.111 und ClamAV 1.5.4 getestet. Prüfen Sie die ClamAV-Support-Richtlinie, bevor Sie diese Version einsetzen.
Konfigurieren Sie clamd und freshclam so, dass sie dasselbe Datenbankverzeichnis verwenden,
und warten Sie, bis der erste Signaturdownload abgeschlossen ist. Wenn Ihre Distribution einen
FreshClam-Dienst ausführt, lassen Sie ihn die Updates verwalten, statt einen zweiten Updater zu
starten. Behalten Sie die Datenbank-, Benutzer- und Protokollierungseinstellungen des Pakets in
clamd.conf bei.
Ersetzen Sie vorhandene Werte dieser Direktiven durch diesen Ausschnitt; hängen Sie keine
widersprüchlichen Kopien an:
TCPAddr 127.0.0.1
TCPSocket 3310
StreamMaxLength 25M
MaxFileSize 26M
MaxScanSize 26M
AlertExceedsMax yes
Das Stream-Limit entspricht der Client-Obergrenze von 25 MiB. Die Engine-Limits begrenzen auch
entpackte Inhalte. AlertExceedsMax sorgt dafür, dass die entsprechenden Limits Warnungen auslösen, die dieses Programm
als Scannerfehler behandelt. Prüfen Sie weitere Archivlimits und die Richtlinie für verschlüsselte
Dateien für Ihren Anwendungsfall. Siehe die
versionierte Konfigurationsreferenz.
Starten Sie clamd mit der bearbeiteten Konfiguration über die Dienstverwaltung Ihrer Plattform oder starten Sie den Dienst neu.
Erstellen Sie die Anwendung in einem beschreibbaren Verzeichnis außerhalb einer vorhandenen
.NET-Build-Verzeichnisstruktur. Fügen Sie diesen Bash-Block als Ganzes ein. Er verweigert die
Ausführung bei SDK-/Build-Konfigurationen in übergeordneten Verzeichnissen oder einem vorhandenen
Verzeichnis FileScan und wechselt erst nach erfolgreicher Erstellung und Wiederherstellung in das Projekt:
(
cd -P . || exit 1
scanner_parent=$PWD
while :; do
for scanner_config in global.json Directory.Build.props Directory.Build.targets; do
if [ -e "$scanner_parent/$scanner_config" ] || [ -L "$scanner_parent/$scanner_config" ]; then
printf 'Choose a directory outside an existing .NET build tree.\n' >&2
exit 1
fi
done
[ "$scanner_parent" = / ] && break
scanner_parent=${scanner_parent%/*}
scanner_parent=${scanner_parent:-/}
done
command -v dotnet >/dev/null &&
mkdir FileScan &&
dotnet new console --framework net10.0 --name FileScan --output FileScan --no-restore &&
dotnet restore FileScan/FileScan.csproj
) && cd FileScan
Eine fehlgeschlagene Erstellung oder Wiederherstellung kann ein unvollständiges Verzeichnis
FileScan hinterlassen, während Ihre Shell im ursprünglichen Verzeichnis bleibt.
Untersuchen Sie den Fehler und bewahren Sie vorhandene Dateien auf. Wenn dieser Block das Projekt
erfolgreich erstellt hat, die Wiederherstellung aber fehlgeschlagen ist, beheben Sie das Problem mit
dem SDK oder der Paketquelle. Versuchen Sie es dann erneut aus dem ursprünglichen Verzeichnis:
dotnet restore FileScan/FileScan.csproj && cd FileScan
Wenn bereits die Projekterstellung fehlgeschlagen ist, wählen Sie einen neuen, leeren Speicherort,
bevor Sie die Einrichtung wiederholen. Verwenden Sie --force nicht, um ein vorhandenes Projekt zu überschreiben.
Dateiscans in C# implementieren
Ersetzen Sie Program.cs durch dieses vollständige Programm. Es streamt 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 Strg+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 aus dem Projektverzeichnis eine synthetische Textdatei und scannen Sie sie.
Dabei wird sample.txt ersetzt; verwenden Sie diesen Namen nur für entbehrliche Testdaten.
Ein fehlgeschlagener Schreibvorgang verhindert die Ausführung des Scans:
printf 'ordinary test text\n' > sample.txt &&
dotnet run -- sample.txt
Bei funktionierendem Daemon wird Clean ausgegeben.
Die Exit-Codes sind 0 für unauffällige Dateien, 1 für infizierte Dateien und 2 für Scannerfehler.
Prüfen Sie den Exit-Code, bevor Sie eine Datei akzeptieren. Eine verweigerte Verbindung muss
ScannerError ergeben, niemals Clean.
Dateien beim Scannen unverändert lassen
Das Programm öffnet die Eingabe ausschließlich zum Lesen. Bewahren Sie zwischengespeicherte 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.
Unauffällige, infizierte und fehlgeschlagene Scans testen
Laden Sie die offizielle EICAR-Testdatei als
eicar.com.txt in das Projektverzeichnis herunter und führen Sie dann Folgendes aus:
dotnet run -- eicar.com.txt
Erwarten Sie Infected und den Exit-Code 1.
EICAR ist eine harmlose Antivirus-Testdatei; andere Antivirus-Software auf Ihrem Rechner kann sie
unabhängig von diesem Programm in Quarantäne verschieben.
Stoppen Sie Ihren lokalen Daemon und wiederholen Sie dotnet run -- sample.txt: Erwarten Sie
ScannerError und den Exit-Code 2.
Testen Sie außerdem eine fehlende Datei, eine Eingabe größer als 25 MiB und ein Archiv, dessen
entpackter Inhalt das konfigurierte Engine-Limit überschreitet. Diese Fälle müssen
ScannerError zurückgeben, nicht Clean. Eine leere Datei und eine gewöhnliche Datei mit exakt 25 MiB liegen
innerhalb des Client-Limits. Vergleichen Sie die Hashwerte der Eingaben vor und nach jedem Scan.
Ein privater Daemon mit einer Signaturdatenbank nur für Tests überprüft die Integration,
belegt aber nicht die Abdeckung durch die Malware-Datenbank für den Produktivbetrieb.
Bewährte Verfahren
Halten Sie die Signaturen für den Produktivbetrieb aktuell, prüfen Sie die Engine-Limits und bewahren Sie zwischengespeicherte Dateien gemäß der Aufbewahrungsrichtlinie Ihrer Anwendung auf. Bei Scanfehlern müssen Sie entscheiden, ob Sie den Scan erneut versuchen oder die Datei ablehnen; sie erlauben keine Veröffentlichung. Geben Sie keine Zugangsdaten oder Scannerdiagnosen in Antworten aus, die für Benutzer sichtbar sind.
Fazit
Für Scans innerhalb einer Dateiverarbeitungspipeline bietet Transloadit den Virus Scan Robot.
