forked from apache/struts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for CSP in Struts 2: - Implements a CSP Interceptor that adds a nonce-based, strict-dynamic policy to HTTP responses. - Implements custom JSP and FTL <script> tags that add nonces to script blocks automatically. This makes these tags compatible with CSP with minimal refactoring. - Implements an extensible action that can be used to collect CSP reports out of the box. This behaviour is extensible, so developers can customise the processing of CSP reports. Co-authored-by: Ecenaz Jen Ozmen <[email protected]> Co-authored-by: Giannis Chatziveroglou <[email protected]> Co-authored-by: Sal <[email protected]>
- Loading branch information
1 parent
8902a9a
commit 4d8bec5
Showing
27 changed files
with
2,602 additions
and
0 deletions.
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
core/src/main/java/org/apache/struts2/action/CspReportAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
package org.apache.struts2.action; | ||
|
||
import com.opensymphony.xwork2.ActionSupport; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
|
||
import static org.apache.struts2.interceptor.csp.CspSettings.CSP_REPORT_TYPE; | ||
|
||
/** | ||
* An abstract Action that can be extended to process the incoming CSP violation reports. Performs | ||
* necessary checks to extract the JSON string of the CSP report and make sure it's a valid report. | ||
* Always returns a 204 response. | ||
* | ||
* Override the <code>processReport(String jsonCspReport)</code> method to customize how the action processes | ||
* the CSP report. See {@link DefaultCspReportAction} for the default implementation. | ||
* | ||
* Add the action to the endpoint that is the <code>reportUri</code> in the {@link org.apache.struts2.interceptor.csp.CspInterceptor} | ||
* to collect the reports. | ||
* | ||
* <pre> | ||
* <package name="csp-reports" namespace="/" extends="struts-default"> | ||
* <action name="csp-reports" class="org.apache.struts2.action.DefaultCspReportAction"> | ||
* <result type="httpheader"> | ||
* <param name="statusCode">200</param> | ||
* </result> | ||
* </action> | ||
* </package> | ||
* </pre> | ||
* | ||
* @see DefaultCspReportAction | ||
*/ | ||
public abstract class CspReportAction extends ActionSupport implements ServletRequestAware, ServletResponseAware { | ||
private HttpServletRequest request; | ||
|
||
public String execute() throws IOException { | ||
return "success"; | ||
} | ||
|
||
@Override | ||
public void withServletRequest(HttpServletRequest request) { | ||
if (!isCspReportRequest(request)) { | ||
return; | ||
} | ||
|
||
try { | ||
BufferedReader reader = request.getReader(); | ||
String cspReport = reader.readLine(); | ||
processReport(cspReport); | ||
} catch (IOException ignored) { | ||
} | ||
} | ||
|
||
private boolean isCspReportRequest(HttpServletRequest request) { | ||
if (!"POST".equals(request.getMethod()) || request.getContentLength() <= 0){ | ||
return false; | ||
} | ||
|
||
String contentType = request.getContentType(); | ||
return CSP_REPORT_TYPE.equals(contentType); | ||
} | ||
|
||
@Override | ||
public void withServletResponse(HttpServletResponse response) { | ||
response.setStatus(204); | ||
} | ||
|
||
abstract void processReport(String jsonCspReport); | ||
|
||
public void setServletRequest(HttpServletRequest request) { | ||
this.request = request; | ||
} | ||
|
||
public HttpServletRequest getServletRequest() { | ||
return request; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
core/src/main/java/org/apache/struts2/action/DefaultCspReportAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
package org.apache.struts2.action; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
|
||
/** | ||
* The default implementation of {@link CspReportAction} that simply logs the JSON object | ||
* that contains the details of the CSP violation. | ||
* | ||
* @see CspReportAction | ||
*/ | ||
public class DefaultCspReportAction extends CspReportAction { | ||
|
||
protected static final Logger LOG = LogManager.getLogger(DefaultCspReportAction.class); | ||
|
||
@Override | ||
void processReport(String jsonCspReport) { | ||
LOG.error(jsonCspReport); | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
core/src/main/java/org/apache/struts2/components/Link.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.apache.struts2.components; | ||
|
||
import com.opensymphony.xwork2.util.ValueStack; | ||
import org.apache.struts2.views.annotations.StrutsTag; | ||
import org.apache.struts2.views.annotations.StrutsTagAttribute; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
/** | ||
* <p> | ||
* Add nonce propagation feature to implement CSP in link tags | ||
* </p> | ||
* | ||
* <p> | ||
* The link tag allows the user to load external resources, most usually style sheets. External resources | ||
* can inject malicious code and perform XSS and data injection attacks. The s:link tag includes a nonce | ||
* attribute that is being randomly generated with each request and only allows links with the valid | ||
* nonce value to be executed. | ||
* </p> | ||
* | ||
* <p><b>Examples</b></p> | ||
* | ||
* <pre> | ||
* | ||
* <s:link ... /> | ||
* | ||
* </pre> | ||
* | ||
*/ | ||
@StrutsTag(name="link", | ||
tldTagClass="org.apache.struts2.views.jsp.ui.LinkTag", | ||
description="Link tag automatically adds nonces to link elements - should be used in combination with Struts' CSP Interceptor.", | ||
allowDynamicAttributes=true) | ||
public class Link extends UIBean{ | ||
|
||
private static final String TEMPLATE="link"; | ||
|
||
protected String href; | ||
protected String hreflang; | ||
protected String rel; | ||
protected String media; | ||
protected String referrerpolicy; | ||
protected String sizes; | ||
protected String crossorigin; | ||
protected String type; | ||
protected String as; | ||
|
||
public Link(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { | ||
super(stack, request, response); | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link href attribute") | ||
public void setHref(String href) { | ||
this.href = href; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link hreflang attribute") | ||
public void setHreflang(String hreflang) { | ||
this.hreflang = hreflang; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link rel attribute") | ||
public void setRel(String rel) { | ||
this.rel = rel; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link sizes attribute") | ||
public void setSizes(String sizes) { | ||
this.sizes = sizes; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link crossorigin attribute") | ||
public void setCrossorigin(String crossorigin) { | ||
this.crossorigin = crossorigin; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link type attribute") | ||
public void setType(String type) { | ||
this.type = type; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link as attribute") | ||
public void setAs(String as) { | ||
this.as = as; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link media attribute") | ||
public void setMedia(String media) { | ||
this.media = media; | ||
} | ||
|
||
@StrutsTagAttribute(description="HTML link referrerpolicy attribute") | ||
public void setReferrerpolicy(String referrerpolicy) { | ||
this.referrerpolicy = referrerpolicy; | ||
} | ||
|
||
@Override | ||
protected String getDefaultTemplate() { | ||
return TEMPLATE; | ||
} | ||
|
||
@Override | ||
protected void evaluateExtraParams() { | ||
super.evaluateExtraParams(); | ||
|
||
if (href != null) { | ||
addParameter("href", findString(href)); | ||
} | ||
|
||
if (hreflang != null) { | ||
addParameter("hreflang", findString(hreflang)); | ||
} | ||
|
||
if (rel != null) { | ||
addParameter("rel", findString(rel)); | ||
} | ||
|
||
if (media != null) { | ||
addParameter("media", findString(media)); | ||
} | ||
|
||
if (referrerpolicy != null) { | ||
addParameter("referrerpolicy", findString(referrerpolicy)); | ||
} | ||
|
||
if (sizes != null) { | ||
addParameter("sizes", findString(sizes)); | ||
} | ||
|
||
if (crossorigin != null) { | ||
addParameter("crossorigin", findString(crossorigin)); | ||
} | ||
|
||
if (type != null) { | ||
addParameter("type", findString(type)); | ||
} | ||
|
||
if (as != null) { | ||
addParameter("as", findString(as)); | ||
} | ||
|
||
if (disabled != null) { | ||
addParameter("disabled", findString(disabled)); | ||
} | ||
|
||
if (title != null) { | ||
addParameter("title", findString(title)); | ||
} | ||
|
||
if (stack.getActionContext().getSession().containsKey("nonce")) { | ||
String nonceValue = stack.getActionContext().getSession().get("nonce").toString(); | ||
addParameter("nonce", nonceValue); | ||
} | ||
} | ||
} |
Oops, something went wrong.