Skip to content

Commit 218b6d7

Browse files
committed
Use case insensitive check
1 parent 73e35d8 commit 218b6d7

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

src/Core/Platform/Mailer/HandlebarMailRenderer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ private async Task<string> ReadSourceAsync(Assembly assembly, string template)
9898
var baseDirectory = Path.GetFullPath(_globalSettings.MailTemplateDirectory);
9999

100100
// Ensure the resolved path is within the configured directory
101-
if (!diskPath.StartsWith(baseDirectory + Path.DirectorySeparatorChar) &&
102-
diskPath != baseDirectory)
101+
if (!diskPath.StartsWith(baseDirectory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) &&
102+
!diskPath.Equals(baseDirectory, StringComparison.OrdinalIgnoreCase))
103103
{
104104
_logger.LogWarning("Template path traversal attempt detected: {Template}", template);
105105
return null;

test/Core.Test/Platform/Mailer/HandlebarMailRendererTests.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,111 @@ public async Task RenderAsync_LoadsFromDisk_WhenSelfHostedAndFileExists()
6262
}
6363
}
6464
}
65+
66+
[Theory]
67+
[InlineData("../../../etc/passwd")]
68+
[InlineData("../../../../malicious.txt")]
69+
[InlineData("../../malicious.txt")]
70+
[InlineData("../malicious.txt")]
71+
public async Task ReadSourceFromDiskAsync_PrevenetsPathTraversal_WhenMaliciousPathProvided(string maliciousPath)
72+
{
73+
var logger = Substitute.For<ILogger<HandlebarMailRenderer>>();
74+
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
75+
Directory.CreateDirectory(tempDir);
76+
77+
try
78+
{
79+
var globalSettings = new GlobalSettings
80+
{
81+
SelfHosted = true,
82+
MailTemplateDirectory = tempDir
83+
};
84+
85+
// Create a malicious file outside the template directory
86+
var maliciousFile = Path.Combine(Path.GetTempPath(), "malicious.txt");
87+
await File.WriteAllTextAsync(maliciousFile, "Malicious Content");
88+
89+
var renderer = new HandlebarMailRenderer(logger, globalSettings);
90+
91+
// Use reflection to call the private ReadSourceFromDiskAsync method
92+
var method = typeof(HandlebarMailRenderer).GetMethod("ReadSourceFromDiskAsync",
93+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
94+
var task = (Task<string?>)method!.Invoke(renderer, new object[] { maliciousPath })!;
95+
var result = await task;
96+
97+
// Should return null and not load the malicious file
98+
Assert.Null(result);
99+
100+
// Verify that a warning was logged for the path traversal attempt
101+
logger.Received(1).Log(
102+
LogLevel.Warning,
103+
Arg.Any<EventId>(),
104+
Arg.Any<object>(),
105+
Arg.Any<Exception>(),
106+
Arg.Any<Func<object, Exception, string>>());
107+
108+
// Cleanup malicious file
109+
if (File.Exists(maliciousFile))
110+
{
111+
File.Delete(maliciousFile);
112+
}
113+
}
114+
finally
115+
{
116+
// Cleanup
117+
if (Directory.Exists(tempDir))
118+
{
119+
Directory.Delete(tempDir, true);
120+
}
121+
}
122+
}
123+
124+
[Fact]
125+
public async Task ReadSourceFromDiskAsync_AllowsValidFileWithDifferentCase_WhenCaseInsensitiveFileSystem()
126+
{
127+
var logger = Substitute.For<ILogger<HandlebarMailRenderer>>();
128+
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
129+
Directory.CreateDirectory(tempDir);
130+
131+
try
132+
{
133+
var globalSettings = new GlobalSettings
134+
{
135+
SelfHosted = true,
136+
MailTemplateDirectory = tempDir
137+
};
138+
139+
// Create a test template file
140+
var templateFileName = "TestTemplate.hbs";
141+
var templatePath = Path.Combine(tempDir, templateFileName);
142+
await File.WriteAllTextAsync(templatePath, "Test Content");
143+
144+
var renderer = new HandlebarMailRenderer(logger, globalSettings);
145+
146+
// Try to read with different case (should work on case-insensitive file systems like Windows/macOS)
147+
var method = typeof(HandlebarMailRenderer).GetMethod("ReadSourceFromDiskAsync",
148+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
149+
var task = (Task<string?>)method!.Invoke(renderer, new object[] { templateFileName })!;
150+
var result = await task;
151+
152+
// Should successfully read the file
153+
Assert.Equal("Test Content", result);
154+
155+
// Verify no warning was logged
156+
logger.DidNotReceive().Log(
157+
LogLevel.Warning,
158+
Arg.Any<EventId>(),
159+
Arg.Any<object>(),
160+
Arg.Any<Exception>(),
161+
Arg.Any<Func<object, Exception, string>>());
162+
}
163+
finally
164+
{
165+
// Cleanup
166+
if (Directory.Exists(tempDir))
167+
{
168+
Directory.Delete(tempDir, true);
169+
}
170+
}
171+
}
65172
}

0 commit comments

Comments
 (0)