CVE-2024-25609
Description
HtmlUtil.escapeRedirect in Liferay Portal 7.2.0 through 7.4.3.12, and older unsupported versions, and Liferay DXP 7.4 before update 9, 7.3 service pack 3, 7.2 fix pack 15 through 18, and older unsupported versions can be circumvented by using two forward slashes, which allows remote attackers to redirect users to arbitrary external URLs via the (1) 'redirect parameter (2) FORWARD_URL` parameter, and (3) others parameters that rely on HtmlUtil.escapeRedirect. This vulnerability is the result of an incomplete fix in CVE-2022-28977.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.liferay.portal:release.portal.bomMaven | >= 7.2.0, < 7.4.3.13-ga13 | 7.4.3.13-ga13 |
com.liferay.portal:release.dxp.bomMaven | >= 7.2.10.fp15, <= 7.2.10.fp18 | — |
com.liferay.portal:release.dxp.bomMaven | >= 7.4.0, < 7.4.13.u9 | 7.4.13.u9 |
Affected products
2- Liferay/DXPv5Range: 7.4.13
Patches
7f015ad20bd9eLPS-144696 Fix test
1 file changed · +4 −3
portal-impl/test/unit/com/liferay/portal/util/PortalImplEscapeRedirectTest.java+4 −3 modified@@ -209,9 +209,10 @@ public void testEscapeRedirectWithRelativeURL() throws Exception { "user/test/~/control_panel/manage/-/select/image,url/", _portalImpl.escapeRedirect( "user/test/~/control_panel/manage/-/select/image,url/")); - Assert.assertEquals("?param1=abc", "?param1=abc"); - Assert.assertEquals("#abc", "#abc"); - Assert.assertEquals("", ""); + Assert.assertEquals( + "?param1=abc", _portalImpl.escapeRedirect("?param1=abc")); + Assert.assertEquals("#abc", _portalImpl.escapeRedirect("#abc")); + Assert.assertEquals("", _portalImpl.escapeRedirect("")); // Relative path with protocol
702a1e358966LPS-144696 More test
1 file changed · +5 −0
portal-impl/test/unit/com/liferay/portal/util/PortalImplEscapeRedirectTest.java+5 −0 modified@@ -110,6 +110,8 @@ public void testEscapeRedirectWithDomains() throws Exception { Assert.assertNull(_portalImpl.escapeRedirect("//www.google.com")); Assert.assertNull(_portalImpl.escapeRedirect("//www.google.com/")); + Assert.assertNull( + _portalImpl.escapeRedirect("//www.google.com//www.google.com")); Assert.assertNull(_portalImpl.escapeRedirect("https:google.com")); Assert.assertNull(_portalImpl.escapeRedirect(":@liferay.com")); Assert.assertNull(_portalImpl.escapeRedirect("http:/web")); @@ -207,6 +209,9 @@ public void testEscapeRedirectWithRelativeURL() throws Exception { "user/test/~/control_panel/manage/-/select/image,url/", _portalImpl.escapeRedirect( "user/test/~/control_panel/manage/-/select/image,url/")); + Assert.assertEquals("?param1=abc", "?param1=abc"); + Assert.assertEquals("#abc", "#abc"); + Assert.assertEquals("", ""); // Relative path with protocol
5c9655c941b1LPS-144696 Centralize relative path tests
1 file changed · +29 −32
portal-impl/test/unit/com/liferay/portal/util/PortalImplEscapeRedirectTest.java+29 −32 modified@@ -75,27 +75,6 @@ public void testEscapeRedirectWithDomains() throws Exception { }; _redirectURLSettingsImpl.securityMode = "domain"; - // Relative path - - Assert.assertEquals("/", _portalImpl.escapeRedirect("/")); - Assert.assertEquals( - "/web/guest", _portalImpl.escapeRedirect("/web/guest")); - Assert.assertEquals( - "/a/b;c=d?e=f&g=h#x=y", - _portalImpl.escapeRedirect("/a/b;c=d?e=f&g=h#x=y")); - Assert.assertEquals( - "/web/http:", _portalImpl.escapeRedirect("/web/http:")); - Assert.assertEquals( - "web/http:", _portalImpl.escapeRedirect("web/http:")); - Assert.assertEquals( - "test@google.com", _portalImpl.escapeRedirect("test@google.com")); - Assert.assertNull(_portalImpl.escapeRedirect("///liferay.com")); - - // Relative path with protocol - - Assert.assertNull(_portalImpl.escapeRedirect("https:/path")); - Assert.assertNull(_portalImpl.escapeRedirect("test:/google.com")); - // Allowed domains Assert.assertEquals( @@ -151,17 +130,6 @@ public void testEscapeRedirectWithIPs() throws Exception { try { - // Relative path - - Assert.assertEquals("/", _portalImpl.escapeRedirect("/")); - Assert.assertEquals( - "/web/guest", _portalImpl.escapeRedirect("/web/guest")); - Assert.assertEquals( - "/a/b;c=d?e=f&g=h#x=y", - _portalImpl.escapeRedirect("/a/b;c=d?e=f&g=h#x=y")); - Assert.assertEquals( - "liferay.com", _portalImpl.escapeRedirect("liferay.com")); - // Absolute URL Assert.assertEquals( @@ -207,6 +175,30 @@ public void testEscapeRedirectWithIPs() throws Exception { @Test public void testEscapeRedirectWithRelativeURL() throws Exception { + + // Relative path + + Assert.assertEquals("/", _portalImpl.escapeRedirect("/")); + Assert.assertEquals( + "/web/guest", _portalImpl.escapeRedirect("/web/guest")); + Assert.assertEquals( + "/a/b;c=d?e=f&g=h#x=y", + _portalImpl.escapeRedirect("/a/b;c=d?e=f&g=h#x=y")); + Assert.assertEquals( + "liferay.com", _portalImpl.escapeRedirect("liferay.com")); + Assert.assertEquals("/", _portalImpl.escapeRedirect("/")); + Assert.assertEquals( + "/web/guest", _portalImpl.escapeRedirect("/web/guest")); + Assert.assertEquals( + "/a/b;c=d?e=f&g=h#x=y", + _portalImpl.escapeRedirect("/a/b;c=d?e=f&g=h#x=y")); + Assert.assertEquals( + "/web/http:", _portalImpl.escapeRedirect("/web/http:")); + Assert.assertEquals( + "web/http:", _portalImpl.escapeRedirect("web/http:")); + Assert.assertEquals( + "test@google.com", _portalImpl.escapeRedirect("test@google.com")); + Assert.assertNull(_portalImpl.escapeRedirect("///liferay.com")); Assert.assertEquals( "user/test/~/control_panel/manage/-/select/image%2Clurl/", _portalImpl.escapeRedirect( @@ -215,6 +207,11 @@ public void testEscapeRedirectWithRelativeURL() throws Exception { "user/test/~/control_panel/manage/-/select/image,url/", _portalImpl.escapeRedirect( "user/test/~/control_panel/manage/-/select/image,url/")); + + // Relative path with protocol + + Assert.assertNull(_portalImpl.escapeRedirect("https:/path")); + Assert.assertNull(_portalImpl.escapeRedirect("test:/google.com")); } @Test
3c5ee2054b44LPS-144696 Exclude the 1st type of rel-ref, because we don't want it
1 file changed · +5 −8
portal-impl/src/com/liferay/portal/util/PortalImpl.java+5 −8 modified@@ -950,19 +950,16 @@ public String escapeRedirect(String url) { if (!uri.isAbsolute()) { - // !uri.isAbsolute() == // https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 - return url; - } + if (url.startsWith(StringPool.DOUBLE_SLASH)) { - String protocol = uri.getScheme(); + // "//" authority path-abempty - if (protocol == null) { - - // Protocol is required + return null; + } - return null; + return url; } String domain = uri.getHost();
dca931af71a3LPS-144696 Use Library method
1 file changed · +5 −9
portal-impl/src/com/liferay/portal/util/PortalImpl.java+5 −9 modified@@ -948,16 +948,10 @@ public String escapeRedirect(String url) { return null; } - String decodedURL = HttpUtil.decodeURL(url); + if (!uri.isAbsolute()) { - String domain = uri.getHost(); - String path = uri.getPath(); - - if ((domain == null) && (path != null) && - !path.equals(StringPool.BLANK) && - decodedURL.startsWith(HttpUtil.decodeURL(path))) { - - // Relative URL + // !uri.isAbsolute() == + // https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 return url; } @@ -971,6 +965,8 @@ public String escapeRedirect(String url) { return null; } + String domain = uri.getHost(); + if (Validator.isNull(domain)) { // Absolute URL must have a domain
66f3ae610c24LPS-144696 Update test
1 file changed · +1 −0
portal-impl/test/unit/com/liferay/portal/util/PortalImplEscapeRedirectTest.java+1 −0 modified@@ -130,6 +130,7 @@ public void testEscapeRedirectWithDomains() throws Exception { // Invalid URLs Assert.assertNull(_portalImpl.escapeRedirect("//www.google.com")); + Assert.assertNull(_portalImpl.escapeRedirect("//www.google.com/")); Assert.assertNull(_portalImpl.escapeRedirect("https:google.com")); Assert.assertNull(_portalImpl.escapeRedirect(":@liferay.com")); Assert.assertNull(_portalImpl.escapeRedirect("http:/web"));
7aca15e7195aLPS-144696 Fix relative URL check
1 file changed · +6 −4
portal-impl/src/com/liferay/portal/util/PortalImpl.java+6 −4 modified@@ -950,8 +950,12 @@ public String escapeRedirect(String url) { String decodedURL = HttpUtil.decodeURL(url); - if (Validator.isNotNull(uri.getPath()) && - decodedURL.startsWith(HttpUtil.decodeURL(uri.getPath()))) { + String domain = uri.getHost(); + String path = uri.getPath(); + + if ((domain == null) && (path != null) && + !path.equals(StringPool.BLANK) && + decodedURL.startsWith(HttpUtil.decodeURL(path))) { // Relative URL @@ -967,8 +971,6 @@ public String escapeRedirect(String url) { return null; } - String domain = uri.getHost(); - if (Validator.isNull(domain)) { // Absolute URL must have a domain
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
10- github.com/advisories/GHSA-3qq5-wcrx-4h8rghsaADVISORY
- liferay.dev/portal/security/known-vulnerabilities/-/asset_publisher/jekt/content/cve-2024-25609ghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2024-25609ghsaADVISORY
- github.com/liferay/liferay-portal/commit/3c5ee2054b44e4354cd2e53782914157ef2b5362ghsaWEB
- github.com/liferay/liferay-portal/commit/5c9655c941b18d8948a0c38b2bc84f4a1f83543aghsaWEB
- github.com/liferay/liferay-portal/commit/66f3ae610c24f10a6950e75e0ca4c981935244edghsaWEB
- github.com/liferay/liferay-portal/commit/702a1e35896681f04ec3c7c8075aa87d5e16a18dghsaWEB
- github.com/liferay/liferay-portal/commit/7aca15e7195a03243d5461fcf09cde0fa7de81f0ghsaWEB
- github.com/liferay/liferay-portal/commit/dca931af71a3d9fbd896a25b92396df8458d2886ghsaWEB
- github.com/liferay/liferay-portal/commit/f015ad20bd9ee1661ccff5fb48e03dd3a1ebf003ghsaWEB
News mentions
0No linked articles in our index yet.