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.
This commit is contained in:
Arun
2026-06-14 22:14:43 +05:30
committed by GitHub
parent dd830dca80
commit 8f9b4b8a90
2 changed files with 30 additions and 1 deletions
@@ -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();
}
@@ -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=<script>alert(1)</script>&x=1";
String expandedLocation = "/context" + maliciousLocation;
String expectedBody = "/context/bar/foo.jsp?next=&lt;script&gt;alert(1)&lt;/script&gt;&amp;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 <script> tag",
writer.toString().contains("<script>"));
}
public void testAbsoluteRedirectAnchor() {
view.setLocation("/bar/foo.jsp");
view.setAnchor("fragment");