Moderate severityNVD Advisory· Published May 21, 2024· Updated Aug 2, 2024
Open Redirect Bypass Protection
CVE-2024-34071
Description
Umbraco is an ASP.NET CMS used by more than 730.000 websites. Umbraco has an endpoint that is vulnerable to open redirects. The endpoint is protected so it requires the user to be signed into backoffice before the vulnerable is exposed. This vulnerability has been patched in version(s) 8.18.14, 10.8.6, 12.3.10 and 13.3.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
UmbracoCms.CoreNuGet | >= 8.18.5, < 8.18.14 | 8.18.14 |
UmbracoCms.CoreNuGet | >= 10.5.0, < 10.8.6 | 10.8.6 |
UmbracoCms.CoreNuGet | >= 12.0.0, < 12.3.10 | 12.3.10 |
UmbracoCms.CoreNuGet | >= 13.0.0, < 13.3.1 | 13.3.1 |
Umbraco.Cms.Web.BackOfficeNuGet | >= 8.18.5, < 8.18.14 | 8.18.14 |
Umbraco.Cms.Web.BackOfficeNuGet | >= 10.5.0, < 10.8.6 | 10.8.6 |
Umbraco.Cms.Web.BackOfficeNuGet | >= 12.0.0, < 12.3.10 | 12.3.10 |
Umbraco.Cms.Web.BackOfficeNuGet | >= 13.0.0, < 13.3.1 | 13.3.1 |
Affected products
1- Range: >= 8.18.5, < 8.18.14
Patches
45f24de308584Merge pull request from GHSA-j74q-mv2c-rxmp
4 files changed · +111 −3
src/Umbraco.Core/Routing/WebPath.cs+24 −0 modified@@ -50,4 +50,28 @@ public static string Combine(params string[]? paths) return sb.ToString(); } + + + /// <summary> + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// </summary> + /// <param name="webPath">The web path to check. This can be null.</param> + /// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param> + /// <returns> + /// true if <paramref name="webPath"/> is well-formed; otherwise, false. + /// </returns> + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } }
src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs+2 −1 modified@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -122,7 +123,7 @@ public IActionResult GetResized(string imagePath, int width) private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; }
src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs+2 −2 modified@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public ActionResult End(string? redir = null) // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString());
tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs+83 −0 modified@@ -30,4 +30,87 @@ public void Combine_must_handle_empty_array() => [Test] public void Combine_must_handle_null() => Assert.Throws<ArgumentNullException>(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + }
c17d4e1a6000Merge pull request from GHSA-j74q-mv2c-rxmp
4 files changed · +111 −3
src/Umbraco.Core/Routing/WebPath.cs+24 −0 modified@@ -50,4 +50,28 @@ public static string Combine(params string[]? paths) return sb.ToString(); } + + + /// <summary> + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// </summary> + /// <param name="webPath">The web path to check. This can be null.</param> + /// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param> + /// <returns> + /// true if <paramref name="webPath"/> is well-formed; otherwise, false. + /// </returns> + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } }
src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs+2 −1 modified@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -123,7 +124,7 @@ public IActionResult GetResized(string imagePath, int width) private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; }
src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs+2 −2 modified@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public ActionResult End(string? redir = null) // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString());
tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs+83 −0 modified@@ -31,4 +31,87 @@ public void Combine_must_handle_empty_array() => [Test] public void Combine_must_handle_null() => Assert.Throws<ArgumentNullException>(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + }
c8f71af64617Merge pull request from GHSA-j74q-mv2c-rxmp
5 files changed · +135 −1
src/Umbraco.Tests/Routing/WebPathTests.cs+97 −0 added@@ -0,0 +1,97 @@ +using NUnit.Framework; +using System; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.Routing +{ + + [TestFixture] + public class WebPathTests + { + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + return WebPath.IsWellFormedWebPath(webPath, uriKind); + } + + } +}
src/Umbraco.Tests/Umbraco.Tests.csproj+1 −0 modified@@ -170,6 +170,7 @@ <Compile Include="PublishedContent\NuCacheTests.cs" /> <Compile Include="Routing\MediaUrlProviderTests.cs" /> <Compile Include="Routing\RoutableDocumentFilterTests.cs" /> + <Compile Include="Routing\WebPathTests.cs" /> <Compile Include="Runtimes\StandaloneTests.cs" /> <Compile Include="Routing\GetContentUrlsTests.cs" /> <Compile Include="Scheduling\ContentVersionCleanup_Tests_UnitTests.cs" />
src/Umbraco.Web/Editors/ImagesController.cs+2 −1 modified@@ -8,6 +8,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Web.Mvc; +using Umbraco.Web.Routing; using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors @@ -102,7 +103,7 @@ public HttpResponseMessage GetResized(string imagePath, int width) private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; }
src/Umbraco.Web/Routing/WebPath.cs+34 −0 added@@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Routing +{ + public class WebPath + { + /// <summary> + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// </summary> + /// <param name="webPath">The web path to check. This can be null.</param> + /// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param> + /// <returns> + /// true if <paramref name="webPath"/> is well-formed; otherwise, false. + /// </returns> + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } + } +}
src/Umbraco.Web/Umbraco.Web.csproj+1 −0 modified@@ -325,6 +325,7 @@ <Compile Include="Routing\IPublishedRouter.cs" /> <Compile Include="Routing\MediaUrlProviderCollection.cs" /> <Compile Include="Routing\MediaUrlProviderCollectionBuilder.cs" /> + <Compile Include="Routing\WebPath.cs" /> <Compile Include="Scheduling\ContentVersionCleanup.cs" /> <Compile Include="Scheduling\SimpleTask.cs" /> <Compile Include="Scheduling\TempFileCleanup.cs" />
d8df405db4eaMerge pull request from GHSA-j74q-mv2c-rxmp
4 files changed · +111 −3
src/Umbraco.Core/Routing/WebPath.cs+24 −0 modified@@ -50,4 +50,28 @@ public static string Combine(params string[]? paths) return sb.ToString(); } + + + /// <summary> + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// </summary> + /// <param name="webPath">The web path to check. This can be null.</param> + /// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param> + /// <returns> + /// true if <paramref name="webPath"/> is well-formed; otherwise, false. + /// </returns> + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } }
src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs+2 −1 modified@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -122,7 +123,7 @@ public IActionResult GetResized(string imagePath, int width) private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; }
src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs+2 −2 modified@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public ActionResult End(string? redir = null) // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString());
tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs+83 −0 modified@@ -30,4 +30,87 @@ public void Combine_must_handle_empty_array() => [Test] public void Combine_must_handle_null() => Assert.Throws<ArgumentNullException>(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-j74q-mv2c-rxmpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-34071ghsaADVISORY
- github.com/umbraco/Umbraco-CMS/commit/5f24de308584b9771240a6db1a34630a5114c450ghsax_refsource_MISCWEB
- github.com/umbraco/Umbraco-CMS/commit/c17d4e1a600098ec524e4126f4395255476bc33fghsax_refsource_MISCWEB
- github.com/umbraco/Umbraco-CMS/commit/c8f71af646171074c13e5c34f74312def4512031ghsax_refsource_MISCWEB
- github.com/umbraco/Umbraco-CMS/commit/d8df405db4ea884bb4b96f088d10d9a2070cf024ghsax_refsource_MISCWEB
- github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-j74q-mv2c-rxmpghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.