Protect Sitecore Media Library with ClamAV antivirus
Posted 9 Sep 2024 by Marek Musielak
Have you ever wondered what kind of files are uploaded to Sitecore media library by content authors? Do they run any antivirus checks before uploading them? Do they check them in any way or do they only push what they receive without any thinking if the file is not a malicious one. Or maybe there is an option to upload files to the Sitecore media library from a form on your website? This blog post provides a guide how you can run an antivirus check of any file before it is stored in media library.
I wrote this blog post while working for Blastic, a company that delivers great Sitecore solutions and much more.
First decision you need to make is which antivirus software you want to use. For the purpose of this blog post I decided to use ClamAV which is an open-source antivirus engine with GNU license. Let's go through the steps that will allow to run ClamAV as a windows service and ensure automatic updates of virus databases.
Installing ClamAV as windows service
- Visit the ClamAV download page.
- Expand Windows section and choose 64 msi file, e.g.
clamav-1.4.1.win.x64.msi
. - Run the installer and follow the steps to install the application. If you use custom installation folder, use that path in the next steps.
- Explore
C:\Program Files\ClamAV\conf_examples
folder and remove.sample
from both file names. - Edit
clamd.conf
file and:- Remove the
Example
line. - Uncomment the following lines by removing
#
character:
LogFile "C:\Program Files\ClamAV\clamd.log"
LogFileMaxSize 2M
LogTime yes
LogClean yes
LogVerbose yes
DatabaseDirectory "C:\Program Files\ClamAV\database"
- If you want only to scan files sent to ClamAV service instead of scanning files on the system, use the following lines for all the drives:
ExcludePath "C:"
ExcludePath "D:"
ExcludePath "E:"
- Remove the
- Edit
freshclam.conf
file and:- Remove the
Example
line. - Uncomment the following lines by removing
#
character:
DatabaseDirectory "C:\Program Files\ClamAV\database"
UpdateLogFile "C:\Program Files\ClamAV\freshclam.log"
LogFileMaxSize 10M
LogTime yes
LogVerbose yes
Checks 24
- Remove the
- Move both
clamd.conf
andfreshclam.conf
out ofconf_examples
toC:\Program Files\ClamAV
folder. - Run
freshclam.exe --install-service
as Administrator to install service that will update ClamAV virus databases. - Run
clamd.exe --install-service
as Administrator to install ClamAV service. - Start Windows Services by running
services.msc
command. - Find
ClamAV FreshClam
service and change its startup type to Automatic. Start the service. - Check
C:\Program Files\ClamAV\database
folder - wait until you seemain.cvd
file there. - For troubleshooting, check
C:\Program Files\ClamAV\freshclam.log
. - Find
ClamAV ClamD
service and change its startup type to Automatic. Start the service. - Check
C:\Program Files\ClamAV\clamd.log
file for any errors. - More information on ClamAV documentation website
- ClamAV antivirus should be now running as a service on your Windows system.
Configure Sitecore to check files attached and uploaded to media library
First, add Nuget reference to nClam
package, create an interface and implementation of antivirus service:
namespace MyAssembly.MyNamespace.Services { public interface IAntivirusService { bool IsFileSafe(Stream stream, string fileName); } } using System; using System.IO; using System.Linq; using nClam; using Sitecore.Diagnostics; namespace MyAssembly.MyNamespace.Services { public class ClamAvAntivirusService : IAntivirusService { private static readonly string ClamAvServerUrl = Sitecore.Configuration.Settings.GetSetting("ClamAVServer.Url"); private static readonly int ClamAvServerPort = int.Parse(Sitecore.Configuration.Settings.GetSetting("ClamAVServer.Port")); public bool IsFileSafe(Stream stream, string fileName) { Log.Info($"ClamAV scan started for file {fileName}", this); var result = true; try { if (stream != null && stream.Length > 0) { using (var ms = new MemoryStream()) { if (stream.Position != 0) stream.Position = 0; stream.CopyTo(ms); if (stream.Position != 0) stream.Position = 0; var fileBytes = ms.ToArray(); var clam = new ClamClient(ClamAvServerUrl, ClamAvServerPort); var scanResult = clam.SendAndScanFileAsync(fileBytes).Result; switch (scanResult.Result) { case ClamScanResults.Clean: Log.Info($"The file '{fileName}' is clean! ScanResult: {scanResult.RawResult}", this); break; case ClamScanResults.VirusDetected: Log.Error($"Virus Found in file '{fileName}'! Virus name: {scanResult.InfectedFiles?.FirstOrDefault()?.VirusName}", this); result = false; break; case ClamScanResults.Error: Log.Error($"An error occurred while scanning the file '{fileName}'! ScanResult: {scanResult.RawResult}", this); result = false; break; case ClamScanResults.Unknown: Log.Error($"Unknown scan result while scanning the file '{fileName}'! ScanResult: {scanResult.RawResult}", this); break; default: throw new NotImplementedException(); } } } } catch (Exception ex) { Log.Error($"ClamAV Scan Exception while scanning the file '{fileName}'", ex, this); } Log.Info($"ClamAV scan completed for file {fileName}", this); return result; } } }
Next, let's create processors that will utilize the service. We want scan to happen on both uploading new files to media library and while attaching new content to an existing media library item:
using MyAssembly.MyNamespace.Services; using Sitecore; using Sitecore.Exceptions; using Sitecore.Pipelines.Attach; namespace MyAssembly.MyNamespace.Processors.Attach { public class ScanForVirusesOnAttach { private readonly IAntivirusService _antivirusService; public ScanForVirusesOnAttach(IAntivirusService antivirusService) { _antivirusService = antivirusService; } public void Process(AttachArgs args) { if (args?.FileWrapper?.InputStream != null && !_antivirusService.IsFileSafe(args.FileWrapper.InputStream, args.FileWrapper.FileName)) { throw new ClientAlertException(string.Format(Texts.TheFile0HasMaliciousContent, args.FileWrapper.FileName)); } } } } using MyAssembly.MyNamespace.Services; using Sitecore; using Sitecore.Pipelines.Upload; namespace MyAssembly.MyNamespace.Processors.Upload { public class ScanForVirusesOnUpload { private readonly IAntivirusService _antivirusService; public ScanForVirusesOnUpload(IAntivirusService antivirusService) { _antivirusService = antivirusService; } public void Process(UploadArgs args) { if (args?.Files != null) { foreach (string key in args.Files) { var file = args.Files[key]; if (file?.InputStream != null) { if (!_antivirusService.IsFileSafe(file.InputStream, file.FileName)) { args.UiResponseHandlerEx.MaliciousFile(StringUtil.EscapeJavascriptString(file.FileName)); args.ErrorText = string.Format(Texts.TheFile0HasMaliciousContent, file.FileName); args.AbortPipeline(); } } } } } } }
Finally, we have to add configuration file:
<?xml version="1.0"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:env="http://www.sitecore.net/xmlconfig/env/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" > <sitecore role:require="Standalone or ContentManagement"> <settings> <setting name="ClamAVServer.Url" value="localhost" /> <setting name="ClamAVServer.Port" value="3310" /> </settings> <processors> <attachFile> <processor mode="on" patch:after="processor[@type='Sitecore.Pipelines.Attach.CheckSize,Sitecore.Kernel']" type="MyAssembly.MyNamespace.Processors.Attach.ScanForVirusesOnAttach, MyAssembly" resolve="true"> </processor> </attachFile> <uiUpload> <processor mode="on" patch:after="processor[@type='Sitecore.Pipelines.Upload.CheckSize, Sitecore.Kernel']" type="MyAssembly.MyNamespace.Processors.Upload.ScanForVirusesOnUpload, MyAssembly" resolve="true"> </processor> </uiUpload> </processors> <services> <register serviceType="MyAssembly.MyNamespace.Services.IAntivirusService, MyAssembly" implementationType="MyAssembly.MyNamespace.Services.ClamAvAntivirusService, MyAssembly" lifetime="Singleton" /> </services> <!-- only needed if reflection filtering is enabled --> <reflection> <allowedMethods> <descriptor type="MyAssembly.MyNamespace.Dialogs.UploadMedia.UploadMediaForm" methodName="ShowMaliciousFileWarning" assemblyName="MyAssembly" hint="ShowMaliciousFileWarning" /> </allowedMethods> </reflection> </sitecore> </configuration>
Additionally, if you're using a version of Sitecore older than 10.4, you will need to extend UploadMediaForm
with the ShowMaliciousFileWarning
method.
Update /sitecore/shell/Applications/Media/Upload Media/UploadMedia.xml
and change CodeBeside
to:
<CodeBeside Type="MyAssembly.MyNamespace.Dialogs.UploadMedia.UploadMediaForm,MyAssembly"/>
and create that class with code:
using Sitecore; using Sitecore.Diagnostics; using Sitecore.Web.UI.Sheer; namespace MyAssembly.MyNamespace.Dialogs.UploadMedia { public class UploadMediaForm : Sitecore.Shell.Applications.Media.UploadMedia.UploadMediaForm { protected void ShowMaliciousFileWarning(string fileName) { Assert.ArgumentNotNullOrEmpty(fileName, nameof(fileName)); SheerResponse.Alert(Texts.TheFile0HasMaliciousContent, fileName); OK.Disabled = true; Cancel.Disabled = true; OK.Disabled = false; Cancel.Disabled = false; } } }
Having learned how to configure ClamAV for Sitecore, I strongly recommend you apply these measures to secure your media library. Ensuring the protection of your digital assets from malicious threats is vital in the current cybersecurity landscape. Following this guide will help you fortify the safety of your Sitecore system and provide a more secure experience for your users.