From 8f9b4b8a90fec8df59b037599e57a3f65680c9ef Mon Sep 17 00:00:00 2001 From: Arun Date: Sun, 14 Jun 2026 22:14:43 +0530 Subject: [PATCH] WW-5636 Harden redirect URL escaping in non-302 response body (#1737) * Implement test for status code 200 with HTML escaping * Escape HTML in ServletRedirectResult response Escape HTML in the final location before writing to the response. --- .../struts2/result/ServletRedirectResult.java | 3 +- .../result/ServletRedirectResultTest.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/struts2/result/ServletRedirectResult.java b/core/src/main/java/org/apache/struts2/result/ServletRedirectResult.java index 7bc8ab616..18fb93577 100644 --- a/core/src/main/java/org/apache/struts2/result/ServletRedirectResult.java +++ b/core/src/main/java/org/apache/struts2/result/ServletRedirectResult.java @@ -26,6 +26,7 @@ import org.apache.struts2.util.reflection.ReflectionException; import org.apache.struts2.util.reflection.ReflectionExceptionHandler; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.dispatcher.Dispatcher; @@ -248,7 +249,7 @@ public class ServletRedirectResult extends StrutsResultSupport implements Reflec response.setStatus(statusCode); response.setHeader("Location", finalLocation); try { - response.getWriter().write(finalLocation); + response.getWriter().write(StringEscapeUtils.escapeHtml4(finalLocation)); } finally { response.getWriter().close(); } diff --git a/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java b/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java index ced68e3d9..fb276d7e1 100644 --- a/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java +++ b/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java @@ -47,6 +47,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static jakarta.servlet.http.HttpServletResponse.SC_SEE_OTHER; import static org.easymock.EasyMock.createControl; import static org.easymock.EasyMock.createMock; @@ -143,6 +144,33 @@ public class ServletRedirectResultTest extends StrutsInternalTestCase implements assertEquals("/context/bar/foo.jsp", writer.toString()); } + public void testStatusCode200LocationIsHtmlEscapedInBody() { + String maliciousLocation = "/bar/foo.jsp?next=&x=1"; + String expandedLocation = "/context" + maliciousLocation; + String expectedBody = "/context/bar/foo.jsp?next=<script>alert(1)</script>&x=1"; + + view.setLocation(maliciousLocation); + view.setStatusCode(SC_OK); + responseMock.expectAndReturn("encodeRedirectURL", expandedLocation, expandedLocation); + responseMock.expect("setStatus", C.args(C.eq(SC_OK))); + responseMock.expect("setHeader", C.args(C.eq("Location"), C.eq(expandedLocation))); + StringWriter writer = new StringWriter(); + responseMock.matchAndReturn("getWriter", new PrintWriter(writer)); + + try { + view.execute(ai); + requestMock.verify(); + responseMock.verify(); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + assertEquals(expectedBody, writer.toString()); + assertFalse("response body must not contain a raw