diff --git a/src/Backend/FluentCMS.Services/FileService.cs b/src/Backend/FluentCMS.Services/FileService.cs index e65112475..9d4efe8aa 100644 --- a/src/Backend/FluentCMS.Services/FileService.cs +++ b/src/Backend/FluentCMS.Services/FileService.cs @@ -1,5 +1,7 @@ using FluentCMS.Providers.FileStorageProviders; using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; namespace FluentCMS.Services; @@ -28,6 +30,13 @@ public async Task Create(File file, System.IO.Stream fileContent, Cancella file.NormalizedName = GetNormalizedFileName(file.Name); + // Sanitize SVG content before persisting to remove potential XSS payloads + if (IsSvgFile(file)) + { + fileContent = SanitizeSvg(fileContent); + file.Size = fileContent.Length; + } + // check if file with the same name already exists var existingFile = await fileRepository.GetByName(folder.SiteId, folder.Id, file.NormalizedName, cancellationToken); if (existingFile != null) @@ -155,4 +164,89 @@ private static string GetNormalizedFileName(string fileName) var normalized = fileName.Trim().ToLower(); return normalized; } + + private static bool IsSvgFile(File file) + { + return string.Equals(file.Extension, ".svg", StringComparison.OrdinalIgnoreCase) || + string.Equals(file.ContentType, "image/svg+xml", StringComparison.OrdinalIgnoreCase); + } + + // Dangerous SVG element local names + private static readonly HashSet _dangerousElements = new(StringComparer.OrdinalIgnoreCase) + { + "script", + "foreignObject", + }; + + // URL-bearing attribute local names whose values must not use dangerous schemes + private static readonly HashSet _urlAttributeLocalNames = new(StringComparer.OrdinalIgnoreCase) + { + "href", + "action", + "src", + }; + + // Dangerous URI schemes (allowlist approach would be better but this covers the known vectors) + private static readonly string[] _dangerousSchemes = ["javascript:", "vbscript:", "data:"]; + + private static readonly XNamespace _xlinkNs = "http://www.w3.org/1999/xlink"; + + private static System.IO.MemoryStream SanitizeSvg(System.IO.Stream svgStream) + { + XDocument doc; + try + { + // Disable DTD processing to prevent XXE attacks + var readerSettings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, + XmlResolver = null, + }; + using var reader = XmlReader.Create(svgStream, readerSettings); + doc = XDocument.Load(reader); + } + catch (XmlException) + { + // If the SVG cannot be parsed as XML return an empty SVG + var empty = System.Text.Encoding.UTF8.GetBytes(""); + return new System.IO.MemoryStream(empty); + } + + // Remove dangerous elements (e.g.