mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 22:11:48 +08:00
Add path validation to prevent path traversal attacks
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNGTests.TestHelpers;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
|
||||
namespace mRemoteNGTests.Config.DataProviders;
|
||||
|
||||
@@ -46,4 +47,18 @@ public class FileBackupCreatorTests
|
||||
var backupFileExists = File.Exists(_testFilePathBackup);
|
||||
Assert.That(backupFileExists, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateBackupFile_WithPathTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"..\..\..\Windows\System32\config.xml";
|
||||
Assert.Throws<ArgumentException>(() => _fileBackupCreator.CreateBackupFile(maliciousPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateBackupFile_WithForwardSlashTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"../../../etc/passwd";
|
||||
Assert.Throws<ArgumentException>(() => _fileBackupCreator.CreateBackupFile(maliciousPath));
|
||||
}
|
||||
}
|
||||
52
mRemoteNGTests/Config/DataProviders/FileBackupPrunerTests.cs
Normal file
52
mRemoteNGTests/Config/DataProviders/FileBackupPrunerTests.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNGTests.TestHelpers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace mRemoteNGTests.Config.DataProviders;
|
||||
|
||||
public class FileBackupPrunerTests
|
||||
{
|
||||
private FileBackupPruner _fileBackupPruner;
|
||||
private string _testFilePath;
|
||||
private string _testFileDirectory;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_testFilePath = FileTestHelpers.NewTempFilePath();
|
||||
_testFileDirectory = Path.GetDirectoryName(_testFilePath);
|
||||
_fileBackupPruner = new FileBackupPruner();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
if (Directory.Exists(_testFileDirectory))
|
||||
Directory.Delete(_testFileDirectory, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PruneBackupFiles_WithPathTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"..\..\..\Windows\System32\config.xml";
|
||||
Assert.Throws<ArgumentException>(() => _fileBackupPruner.PruneBackupFiles(maliciousPath, 5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PruneBackupFiles_WithForwardSlashTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"../../../etc/passwd";
|
||||
Assert.Throws<ArgumentException>(() => _fileBackupPruner.PruneBackupFiles(maliciousPath, 5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PruneBackupFiles_WithValidPath_DoesNotThrow()
|
||||
{
|
||||
// Create the test file
|
||||
File.WriteAllText(_testFilePath, "test");
|
||||
|
||||
Assert.DoesNotThrow(() => _fileBackupPruner.PruneBackupFiles(_testFilePath, 5));
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,32 @@ public class FileDataProviderTests
|
||||
_dataProvider.Save("");
|
||||
Assert.That(File.Exists(fileThatShouldExist), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithPathTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\..\..\..\Windows\System32\config.xml";
|
||||
Assert.Throws<ArgumentException>(() => new FileDataProvider(maliciousPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FilePath_SetWithPathTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"..\..\..\Windows\System32\config.xml";
|
||||
Assert.Throws<ArgumentException>(() => _dataProvider.FilePath = maliciousPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveTo_WithPathTraversal_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"..\..\..\Windows\System32\config.xml";
|
||||
// The method catches the exception internally, so we need to verify it doesn't move the file
|
||||
_dataProvider.Save("test");
|
||||
_dataProvider.MoveTo(maliciousPath);
|
||||
|
||||
// Verify the file wasn't moved to the malicious path
|
||||
Assert.That(File.Exists(maliciousPath), Is.False);
|
||||
// Verify the original file still exists
|
||||
Assert.That(File.Exists(_testFilePath), Is.True);
|
||||
}
|
||||
}
|
||||
91
mRemoteNGTests/Tools/PathValidatorTests.cs
Normal file
91
mRemoteNGTests/Tools/PathValidatorTests.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using mRemoteNG.Tools;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace mRemoteNGTests.Tools;
|
||||
|
||||
public class PathValidatorTests
|
||||
{
|
||||
[Test]
|
||||
public void ValidPath_ReturnsTrue()
|
||||
{
|
||||
string validPath = @"C:\Users\TestUser\Documents\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(validPath), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PathWithForwardSlashTraversal_ReturnsFalse()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\..\..\..\Windows\System32\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(maliciousPath), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PathWithBackslashTraversal_ReturnsFalse()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\..\..\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(maliciousPath), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PathWithMixedTraversal_ReturnsFalse()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\.././..\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(maliciousPath), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PathWithEncodedTraversal_ReturnsFalse()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\%2e%2e\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(maliciousPath), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PathWithUppercaseEncodedTraversal_ReturnsFalse()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\%2E%2E\test.xml";
|
||||
Assert.That(PathValidator.IsValidPath(maliciousPath), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NullPath_ReturnsFalse()
|
||||
{
|
||||
Assert.That(PathValidator.IsValidPath(null), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptyPath_ReturnsFalse()
|
||||
{
|
||||
Assert.That(PathValidator.IsValidPath(""), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidatePathOrThrow_WithValidPath_DoesNotThrow()
|
||||
{
|
||||
string validPath = @"C:\Users\TestUser\Documents\test.xml";
|
||||
Assert.DoesNotThrow(() => PathValidator.ValidatePathOrThrow(validPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidatePathOrThrow_WithTraversalPath_ThrowsArgumentException()
|
||||
{
|
||||
string maliciousPath = @"C:\Users\TestUser\Documents\..\..\..\test.xml";
|
||||
var exception = Assert.Throws<ArgumentException>(() => PathValidator.ValidatePathOrThrow(maliciousPath));
|
||||
Assert.That(exception.Message, Does.Contain("path traversal"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidatePathOrThrow_WithNullPath_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => PathValidator.ValidatePathOrThrow(null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidatePathOrThrow_WithCustomParameterName_IncludesParameterName()
|
||||
{
|
||||
string maliciousPath = @"..\..\..\test.xml";
|
||||
var exception = Assert.Throws<ArgumentException>(() => PathValidator.ValidatePathOrThrow(maliciousPath, "customParam"));
|
||||
Assert.That(exception.ParamName, Is.EqualTo("customParam"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user