From: Daniel Owens via Fulldisclosure <fulldisclosure () seclists org>
Date: Thu, 29 Oct 2026 13:34:55 +0000
Description
Apache Struts2 Core version 7.4.0 and below's `FetchMetadataInterceptor`, `CoepInterceptor`, and `CoopInterceptor` each
compare their `exemptedPaths` configuration -- a per-interceptor-reference, administrator-supplied set of paths
intended to exempt specific actions from the corresponding cross-origin protection -- against
`HttpServletRequest.getContextPath()`. `getContextPath()` is fixed for the entire deployed web application and is
identical on every request regardless of which action or servlet path is being invoked, so it cannot distinguish one
action from another.
Impact
An administrator who configures `exemptedPaths` expecting it to exempt a specific action or endpoint, instead is
configuring a value that can never match the real, single, deployment-wide context path returned on genuine traffic, so
the per-action exemption never takes effect and the intended protection remains enforced everywhere, including on the
endpoint the administrator believed was exempted. More importantly, if an administrator's configured exemption string
happens to coincide with the application's actual context path -- such as if an administrator assumes that the
parameter takes a path-like value and supplies the deployment's own root or an equally-named segment --
`exemptedPaths.contains(contextPath)` evaluates to `true` on every single request to the application and Fetch Metadata
request filtering, the Cross-Origin Embedder Policy (COEP) header, and the Cross-Origin Opener Policy (COOP) header are
all silently disabled for the entire deployment (well beyond the endpoint that the administrator intended to exempt),
disabling the protections that such code is intended to provide across the entire application.
Examples
Sample Code
All three interceptors, each in `struts2-core`, apply the identical pattern -- a per-action `exemptedPaths` set
compared against the deployment-wide `getContextPath()` rather than against anything that actually varies per action.
```java
private final Set<String> exemptedPaths = new HashSet<>();
private final ResourceIsolationPolicy resourceIsolationPolicy = new StrutsResourceIsolationPolicy();
@Inject(required = false)
public void setExemptedPaths(String paths) {
this.exemptedPaths.addAll(TextParseUtil.commaDelimitedStringToSet(paths));
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext context = invocation.getInvocationContext();
HttpServletRequest request = context.getServletRequest();
addVaryHeaders(invocation);
String contextPath = request.getContextPath();
// Apply exemptions: paths/endpoints meant to be served cross-origin
if (exemptedPaths.contains(contextPath)) {
return invocation.invoke();
}
```
Code snippet from `struts2-core`'s core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java,
lines 55-73
```java
@Override
public void beforeResult(ActionInvocation invocation, String resultCode) {
HttpServletRequest req = invocation.getInvocationContext().getServletRequest();
final String path = req.getContextPath();
if (exemptedPaths.contains(path)) {
```
Snippet from `struts2-core`'s core/src/main/java/org/apache/struts2/interceptor/CoepInterceptor.java, lines 59-63
```java
@Override
public void beforeResult(ActionInvocation invocation, String resultCode) {
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
String path = request.getContextPath();
if (isExempted(path)) {
```
Code from `struts2-core`'s core/src/main/java/org/apache/struts2/interceptor/CoopInterceptor.java, lines 61-65
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/
Current thread:
- Apache Struts2 Core Cross-Origin Protection Exemption Miskeyed to a Deployment-Wide Path Daniel Owens via Fulldisclosure (Nov 01)