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