Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Security Policy (CSP) Header not set #1816

Open
RogerHaase opened this issue Dec 3, 2024 · 3 comments
Open

Content Security Policy (CSP) Header not set #1816

RogerHaase opened this issue Dec 3, 2024 · 3 comments

Comments

@RogerHaase
Copy link
Member

RogerHaase commented Dec 3, 2024

One of the warning messages produced by a ZAP run against 127.0.0.1 is that the "Content Security Policy (CSP) Header not set"

See #318

One way to set the CSP headers is to insert something like the following into src/moin/apps/frontend/views.py:

@frontend.after_request
def add_security_headers(resp):
    resp.headers["Reporting-Endpoints"] = "csp-endpoint='http://127.0.0.1:5000/csp-report-url'"
    resp.headers["Content-Security-Policy-Report-Only"] = "default-src http:; report-uri csp-report-url; report-to csp-endpoint;"
    return resp

where the above needs work, pretty names, move headers to wikiconfig.py, do same/similar for admin views etc.

The first problem encountered from above is the browser tries to PUT a jason formatted report to csp-report-url resulting in a 404.

Adding a text item named csp-report-url eliminates the 404 and returns a 200, but the write fails silently in moin code with nothing updated. The silent failure is possible due to the contenttype of the browser post is application/csp-report.

See
https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html,

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only,

google other sources

@RogerHaase RogerHaase self-assigned this Dec 3, 2024
@RogerHaase
Copy link
Member Author

stuck!

When I add the deprecated report-uri format to apps/frontend/view.py:

@frontend.after_request
def add_security_headers(resp):
    resp.headers["Content-Security-Policy-Report-Only"] = "default-src 'self'; report-uri https//127.0.0.1/csp-report-uriXXX;"
    return resp

Then the Firefox browser creates 3 POST transactions (that fail on 403/404/500 with various tweaks). Chrome, Edge, and Opera do not create a POST tranaction.

When I add the report-to format to apps/frontend/view.py:

@frontend.after_request
def add_security_headers(resp):
    resp.headers["Reporting-Endpoints"] = "csp-endpoint='https://--snip--/csp-report-urlQQQ'"
    resp.headers["Content-Security-Policy-Report-Only"] = "default-src 'self'; report-to csp-endpoint;"
    return resp

Then Firefox, Chrome, Edge, and Opera do not create a POST xaction. Going into Firefox about:config and setting dom.reporting.enabled to True has no effect on Firefox.

I think the goal is to create a "Content-Security-Policy" and add support for an incoming POST with a contenttype of either "application/csp-report" or "application/reports+json". A well-written Content-Security-Policy will create no POST transactions. Wiki admins may view the CSP reports and delete files should there be many long reports. Wiki admins can turn off CSP reporting in wikiconfig.py. Since most of the work is performed in the client-side browser there will be no performance hit.

@RogerHaase RogerHaase removed their assignment Dec 16, 2024
@UlrichB22
Copy link
Collaborator

UlrichB22 commented Dec 17, 2024

I did a short test with Chromium version 131 on Ubuntu, adapted your template for report-uri above and added a route '/+cspreport/log'. This writes the report json to the server log.

As a result, there are 2 findings for the 'Home' item (the 'Welcome' page). The first log entry is:

INFO moin.apps.frontend.views 2024-12-17 22:13:28.247394 127.0.0.1 application/csp-report {'document-uri': 'http://localhost:5000/Home', 'referrer': 'http://localhost:5000/Home', 'violated-directive': 'style-src-attr', 'effective-directive': 'style-src-attr', 'original-policy': "default-src 'self'; report-uri +cspreport/log;", 'disposition': 'report', 'blocked-uri': 'inline', 'line-number': 392, 'source-file': 'http://localhost:5000/Home', 'status-code': 200, 'script-sample': ''}

Line 392 in the html source is
<div id="moin-options-for-javascript" style="display: none">

@UlrichB22
Copy link
Collaborator

UlrichB22 commented Dec 17, 2024

This is the changed code, partially copied from https://github.com/finalduty/csp-report-collector/blob/main/src/csp_report_collector.py:

diff --git a/src/moin/app.py b/src/moin/app.py
index 01d4c50b..de9e3dbe 100644
--- a/src/moin/app.py
+++ b/src/moin/app.py
@@ -294,7 +294,7 @@ def before_wiki():
     """
     Setup environment for wiki requests, start timers.
     """
-    if request and is_static_content(request.path):
+    if request and (is_static_content(request.path) or request.path == "+cspreport/log"):
         logging.debug(f"skipping before_wiki for {request.path}")
         return
 
diff --git a/src/moin/apps/frontend/views.py b/src/moin/apps/frontend/views.py
index 7baef9ac..63f7d93e 100644
--- a/src/moin/apps/frontend/views.py
+++ b/src/moin/apps/frontend/views.py
@@ -117,6 +117,14 @@ logging = log.getLogger(__name__)
 jfu_server_lock = threading.Lock()
 
 
[email protected]_request
+def add_security_headers(resp):
+    resp.headers["Content-Security-Policy-Report-Only"] = (
+        "default-src 'self'; report-uri +cspreport/log;"
+    )
+    return resp
+
+
 @frontend.route("/+dispatch", methods=["GET"])
 def dispatch():
     args = request.values.to_dict()
@@ -285,6 +293,21 @@ def lookup():
     return Response(html, status)
 
 
[email protected]("/+cspreport/log", methods=["POST"])
+def cspreport():
+    """
+    csp report receiver
+    """
+    logging.info("got CSP report.")
+    if request.content_type != "application/csp-report":
+        abort(400, f"Invalid content type. Expected 'application/csp-report', got '{request.content_type}'.")
+
+    csp_report = json.loads(request.data.decode("UTF-8"))["csp-report"]
+    logging.info(f"{datetime.now()} {request.remote_addr} {request.content_type} {csp_report}")
+    logging.info("got CSP report.")
+    return Response("", 204)
+
+
 def _compute_item_transclusions(item_name):
     """Compute which items are transcluded into item <item_name>.

The report-uri above will fail for any namespace other than 'default'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants