fix(security): patch SQL injection, path traversal, command injection

This commit is contained in:
admin 2025-12-17 19:30:35 -06:00
parent 2a523af1db
commit b8c130c73d
5 changed files with 38 additions and 11 deletions

View file

@ -69,6 +69,8 @@ private void ExtractZip(string compressedFile, string destination)
throw new IOException(string.Format("File {0} failed archive validation.", compressedFile)); throw new IOException(string.Format("File {0} failed archive validation.", compressedFile));
} }
var destinationFullPath = Path.GetFullPath(destination);
foreach (ZipEntry zipEntry in zipFile) foreach (ZipEntry zipEntry in zipFile)
{ {
if (!zipEntry.IsFile) if (!zipEntry.IsFile)
@ -85,7 +87,16 @@ private void ExtractZip(string compressedFile, string destination)
var zipStream = zipFile.GetInputStream(zipEntry); var zipStream = zipFile.GetInputStream(zipEntry);
// Manipulate the output filename here as desired. // Manipulate the output filename here as desired.
var fullZipToPath = Path.Combine(destination, entryFileName); var fullZipToPath = Path.GetFullPath(Path.Combine(destination, entryFileName));
// Prevent path traversal attacks - ensure extracted path is within destination
if (!fullZipToPath.StartsWith(destinationFullPath + Path.DirectorySeparatorChar) &&
!fullZipToPath.Equals(destinationFullPath, StringComparison.Ordinal))
{
_logger.Warn("Skipping zip entry with path traversal attempt: {0}", entryFileName);
continue;
}
var directoryName = Path.GetDirectoryName(fullZipToPath); var directoryName = Path.GetDirectoryName(fullZipToPath);
if (directoryName.Length > 0) if (directoryName.Length > 0)
{ {

View file

@ -356,17 +356,17 @@ private List<Process> GetProcessesByName(string name)
{ {
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase)) if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
{ {
return ("cmd.exe", $"/c {path} {args}"); return ("cmd.exe", $"/c \"{path}\" {args}");
} }
if (OsInfo.IsWindows && path.EndsWith(".ps1", StringComparison.InvariantCultureIgnoreCase)) if (OsInfo.IsWindows && path.EndsWith(".ps1", StringComparison.InvariantCultureIgnoreCase))
{ {
return ("powershell.exe", $"-ExecutionPolicy Bypass -NoProfile -File {path} {args}"); return ("powershell.exe", $"-ExecutionPolicy Bypass -NoProfile -File \"{path}\" {args}");
} }
if (OsInfo.IsWindows && path.EndsWith(".py", StringComparison.InvariantCultureIgnoreCase)) if (OsInfo.IsWindows && path.EndsWith(".py", StringComparison.InvariantCultureIgnoreCase))
{ {
return ("python.exe", $"{path} {args}"); return ("python.exe", $"\"{path}\" {args}");
} }
return (path, args); return (path, args);

View file

@ -31,19 +31,17 @@ public void Clean()
.SelectMany(v => GetUsedTags(v, mapper)) .SelectMany(v => GetUsedTags(v, mapper))
.Concat(GetAutoTaggingTagSpecificationTags(mapper)) .Concat(GetAutoTaggingTagSpecificationTags(mapper))
.Distinct() .Distinct()
.ToList(); .ToArray();
if (usedTags.Any()) if (usedTags.Any())
{ {
var usedTagsList = usedTags.Select(d => d.ToString()).Join(",");
if (_database.DatabaseType == DatabaseType.PostgreSQL) if (_database.DatabaseType == DatabaseType.PostgreSQL)
{ {
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" = ANY (\'{{{usedTagsList}}}\'::int[])"); mapper.Execute("DELETE FROM \"Tags\" WHERE NOT \"Id\" = ANY (@UsedTags)", new { UsedTags = usedTags });
} }
else else
{ {
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})"); mapper.Execute("DELETE FROM \"Tags\" WHERE \"Id\" NOT IN @UsedTags", new { UsedTags = usedTags });
} }
} }
else else

View file

@ -27,7 +27,15 @@ public override string Map(string resourceUrl)
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);
var resourcePath = Path.Combine(_appFolderInfo.GetAppDataPath(), path); var basePath = Path.GetFullPath(_appFolderInfo.GetAppDataPath());
var resourcePath = Path.GetFullPath(Path.Combine(basePath, path));
// Prevent path traversal attacks - ensure path stays within AppData folder
if (!resourcePath.StartsWith(basePath + Path.DirectorySeparatorChar) &&
!resourcePath.Equals(basePath, StringComparison.Ordinal))
{
return null;
}
if (!_diskProvider.FileExists(resourcePath) || _diskProvider.GetFileSize(resourcePath) == 0) if (!_diskProvider.FileExists(resourcePath) || _diskProvider.GetFileSize(resourcePath) == 0)
{ {

View file

@ -23,7 +23,17 @@ public override string Map(string resourceUrl)
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path); var basePath = Path.GetFullPath(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder));
var fullPath = Path.GetFullPath(Path.Combine(basePath, path));
// Prevent path traversal attacks - ensure path stays within UI folder
if (!fullPath.StartsWith(basePath + Path.DirectorySeparatorChar) &&
!fullPath.Equals(basePath, StringComparison.Ordinal))
{
return null;
}
return fullPath;
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)