forked from dnnsoftware/Dnn.Platform
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hardened handling of DnnImageHandler url validation
Hardened handling of DnnImageHandler url validation
- Loading branch information
Showing
5 changed files
with
123 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
DNN Platform/Library/Services/GeneratedImage/UriValidator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information | ||
|
||
namespace DotNetNuke.Services.GeneratedImage | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
using DotNetNuke.Abstractions.Portals; | ||
using DotNetNuke.Entities.Portals; | ||
|
||
/// <summary> | ||
/// Validates urls that could be used in the Image Handler. | ||
/// </summary> | ||
internal class UriValidator | ||
{ | ||
private readonly IPortalAliasController portalAliasController; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="UriValidator"/> class. | ||
/// </summary> | ||
/// <param name="portalAliasController">Provides services related to portal aliases.</param> | ||
public UriValidator(IPortalAliasController portalAliasController) | ||
{ | ||
this.portalAliasController = portalAliasController; | ||
} | ||
|
||
/// <summary> | ||
/// Checks if a URI belongs to hosted sites. | ||
/// </summary> | ||
/// <param name="uri">The URI to validate.</param> | ||
/// <returns>A value indicating whether the provided Uri belongs to the a valid site.</returns> | ||
internal bool UriBelongsToSite(Uri uri) | ||
{ | ||
IEnumerable<string> hostAliases = | ||
this.portalAliasController | ||
.GetPortalAliases().Values.Cast<IPortalAliasInfo>() | ||
.Select(alias => alias.HttpAlias.ToLowerInvariant()); | ||
|
||
// Extract the host and normalize the path from the incoming URI | ||
string uriHost = uri.DnsSafeHost.ToLowerInvariant(); // Just the host (e.g., "mysite.com") | ||
string uriPath = uri.LocalPath.TrimEnd('/').ToLowerInvariant(); // Path (e.g., "/siteB") | ||
|
||
// Split the alias into host and optional path (e.g., "mysite.com/siteB") | ||
foreach (var alias in hostAliases) | ||
{ | ||
var aliasParts = alias.Split(new[] { '/' }, 2, StringSplitOptions.None); // Split on the first '/' to separate host and path | ||
string aliasHost = aliasParts[0]; // Host part of the alias (e.g., "mysite.com") | ||
string aliasPath = aliasParts.Length > 1 ? "/" + aliasParts[1].TrimEnd('/') : string.Empty; // Path part, if any | ||
|
||
// Ensure exact host match and validate the path | ||
if (string.Equals(uriHost, aliasHost, StringComparison.OrdinalIgnoreCase) && | ||
uriPath.StartsWith(aliasPath, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
// No matching alias found | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
DNN Platform/Tests/DotNetNuke.Tests.Core/Services/GeneratedImage/UriValidatorTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information | ||
|
||
namespace DotNetNuke.Tests.Core.Services.GeneratedImage | ||
{ | ||
using System; | ||
using DotNetNuke.Entities.Portals; | ||
using DotNetNuke.Services.GeneratedImage; | ||
using Moq; | ||
using NUnit.Framework; | ||
|
||
[TestFixture] | ||
public class UriValidatorTests | ||
{ | ||
[TestCase("https://mysite.com/page", true)] | ||
[TestCase("http://mysite.com/page", true)] | ||
[TestCase("https://mysite.com", true)] | ||
[TestCase("http://mysite.com", true)] | ||
[TestCase("https://mysite.com/siteB", true)] | ||
[TestCase("http://mysite.com/siteB", true)] | ||
[TestCase("https://badactor.com", false)] | ||
[TestCase("http://badactor.com", false)] | ||
[TestCase("https://badactor.com/siteB", false)] | ||
[TestCase("http://badactor.com/siteB", false)] | ||
[TestCase("https://mysite.com.badactor.com", false)] | ||
[TestCase("http://mysite.com.badactor.com", false)] | ||
[TestCase("https://mysite.com.badactor.com/siteB", false)] | ||
[TestCase("http://mysite.com.badactor.com/siteB", false)] | ||
[TestCase("https://mysite.com.badactor.com/siteB/page", false)] | ||
[TestCase("http://mysite.com.badactor.com/siteB/page", false)] | ||
public void UriBelongsToSite_MultipleScenarios(string uriString, bool expected) | ||
{ | ||
// Arrange | ||
var mockPortalAliasController = new Mock<IPortalAliasController>(); | ||
var portalAliases = new PortalAliasCollection(); | ||
portalAliases.Add("mysite", new PortalAliasInfo { HTTPAlias = "mysite.com" }); | ||
portalAliases.Add("siteB", new PortalAliasInfo { HTTPAlias = "mysite.com/siteB" }); | ||
mockPortalAliasController | ||
.Setup(controller => controller.GetPortalAliases()) | ||
.Returns(portalAliases); | ||
|
||
var validator = new UriValidator(mockPortalAliasController.Object); | ||
|
||
var testUri = new Uri(uriString); | ||
|
||
// Act | ||
var result = validator.UriBelongsToSite(testUri); | ||
|
||
Assert.That(result, Is.EqualTo(expected)); | ||
} | ||
} | ||
} |