Ticket #13679 (reopened PLIP)

Opened 3 years ago

Last modified 2 years ago

Automatic CSRF protection

Reported by: vangheem Owned by:
Priority: minor Milestone: 5.0
Component: General Version:
Keywords: Cc: keul

Description (last modified by vangheem) (diff)

Proposer: Nathan Van Gheem
Seconder: The Security Team

Motivation

  • currently, csrf is not required or automatically provided in some of our form libraries
  • will automatically make add-on products more secure
  • will fix ZMI forms

Proposal & Implementation

  • in plone.protect, provide a mechanism to always check for CSRF protection when an object is going to be written to the database. If the CSRF protection is invalid or not provided, abort the transaction and provide a warning page to the user describing what happened, what the risk was that we mitigated and then allow the user to re-complete the action again by confirming with a valid CSRF token.
  • accomplish CSRF checking by using an IPubBeforeCommit event handler to check for CSRF protection
  • automatically include a CSRF token on all internal forms if the current user is logged in via plone.transformchain and using lxml to transform the output document
  • specific requests can opt-out of the auto CSRF check by applying an interface to the current request. This will need to be done for things like image scale generation
  • automatically rotate the keyring used for csrf protection
  • support CSRF tokens being provided via a header in the request
  • provide a way to opt-out of automatic CSRF protection via an environment variable
  • for methods right now that we write to the database with GET requests, include the CSRF token as part of the GET request
  • provide a standard way to include the CSRF token for use with javascript
  • randomize key usage for responses to mitigate BREACH attack

Deliverables

  • new version of plone.protect that will provide the automatic CSRF protection described above
  • new version of plone.keyring to that will automatically rotate the key used for CSRF protection
  • remove all old manual CSRF protection from forms
  • documentation on Plone's CSRF protection

Risks

  • in plone, we write to the database for things like image scales on demand. We'll have to make sure we don't miss any other cases that will not need to have the auto CSRF protection
  • if we're not careful, we could leak CSRF tokens(if applied to a form that submits outside of plone). Auto-rotating keys for the tokens should help mitigate that.

Change History

comment:1 Changed 3 years ago by vangheem

  • Type changed from Bug to PLIP
  • Milestone changed from 4.x to 5.0

comment:2 Changed 3 years ago by vangheem

  • Component changed from Unknown to General

comment:3 Changed 3 years ago by vangheem

  • Description modified (diff)

comment:4 Changed 3 years ago by davisagli

I would anticipate this causing some annoyances till we work out the kinks, but it would be a real win for security. And the major version bump to 5.0 is the right time to do this.

Questions:

  • How will we deal with parts of the UI that currently try to change the database via GET requests (i.e. most of the edit bar menu items)?
  • Will we provide anything to help javascript code get and use the token? (i.e. automatically add the header to ajax requests using a $.ajaxSend hook)
  • Should we skip including and checking the authenticator for anonymous forms?
  • Could you get someone from the Django security team to look over your approach? I know they tried several different solutions for Django before reaching their current one, so they may recognize things that would be problematic.

Additional deliverables should include:

  • Remove manual CSRF protection from things that don't need it once this PLIP is implemented.
  • Provide documentation for add-on authors of things they need to be aware of w/r/t CSRF protection.

comment:5 Changed 3 years ago by vangheem

  • Description modified (diff)

Here are some answers:

  • Updated ticket to include information on what we do for GET requests that write to the database
  • Rather than automatically provide CSRF protection for ajax post requests, I'd rather just provide a way to easily grab the token for use in custom ajax form submission cases.
  • I've contacted the django security list to see if they have any comments on their approach
  • yes, only authenticated forms will include the authenticator

comment:6 Changed 3 years ago by vangheem

  • Description modified (diff)

comment:7 Changed 3 years ago by vangheem

I received a nice reply from one of the Django developers. Here are some of the points:

  • they use a csrf cookie token because they don't have a unique session value for every user. People can actually disable their user session management and still work with csrf
  • they used to do automatically csrf protection but found it was difficult to diagnose when things went wrong. They don't have a xml transformation framework like we do and it almost sounded like they didn't use an html parser for transforming the final document. Additionally, they don't have much control in what the consumers of their framework respond with in their templates--not as much as we do.

My impression: I am not concerned with the proposed approach. I think Plone is in an excellent position right now to make this happen.

comment:8 Changed 2 years ago by thet

Your proposal sounds good! +1

Anyways, I have some remarks:

  • Automatic CSRF protection is then only available, if plone.transformchain is used? And thats the case when we use Diazo. IMO, think we can assume, Diazo is used in Plone 5, so no prob on this.
  • CSRF disabling for outgoing forms might be possible by checking the action URL within the transformchain chain. Maybe a list of trusted domains can also be set within a control panel.
  • Why should CSRF be disabled for image scaling?

OWASP has a tool for checking CSRF protection:  https://www.owasp.org/index.php/Csrftester and more useful information:  https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29

Last edited 2 years ago by thet (previous) (diff)

comment:9 Changed 2 years ago by vangheem

  • plone.transformchain is active even when diazo is disabled. plone.app.theming just uses transformchain to apply the diazo theme
  • A trusted domain list could be a fine idea. It really shouldn't be necessary if done correctly though
  • CSRF is disabled on image scaling because there is no way to check for protection and image scales are created on GET/read requests.

comment:10 Changed 2 years ago by keul

  • Cc keul added

comment:11 Changed 2 years ago by vangheem

  • Status changed from new to closed
  • Resolution set to fixed

comment:12 Changed 2 years ago by davisagli

  • Status changed from closed to reopened
  • Resolution fixed deleted

Let's not close this until the merge is done. We still need to finish fixing tests and remove the older CSRF protection.

comment:13 Changed 2 years ago by simahawk

Guys, I have a strange behaviour when trying to access portal_quickinstaller form.

The check at this line  https://github.com/plone/plone.protect/blob/master/plone/protect/auto.py#L134

throws a "Forbidden, Form authenticator is invalid." so that you don't get to the form until you click on the "confirm action" button.

This happen on a brand new plone site with buildout.coredev master.

I tried to access all the tools in the portal and looks like quickinstaller is the only one affected by this.

Any pointer on how this can be fixed?

comment:14 Changed 2 years ago by davisagli

This is what happens now if a request tries to write to the database but the request doesn't have a valid CSRF '_authenticator' token. The token is now inserted automatically into all POST forms, so this likely means that the quickinstaller tool is writing to the database on a GET request, which is probably a bug. To find out what object it's trying to write, try putting a pdb breakpoint in Connection._register in the ZODB.

(Note: I think Nathan rewrote the Add-ons control panel specifically to avoid this problem, so you may be able to use that instead of portal_quickinstaller in the ZMI, as a workaround.)

comment:15 Changed 2 years ago by simahawk

Ok, I put it there and it's <QuickInstallerTool at portal_quickinstaller>.

Going up in the chain it looks like it's 'self.errors' on listing products methods. But Nathan already fix this in plip and it's already merged in master using _v_errors:

 https://github.com/plone/Products.CMFQuickInstallerTool/blob/master/Products/CMFQuickInstallerTool/QuickInstallerTool.py

Maybe we should pull also QuickInstallerTool from master into coredev buildout since we are already using plone.protect from master?

Last edited 2 years ago by simahawk (previous) (diff)

comment:16 Changed 2 years ago by vangheem

Yes please. If Products.CMFQuickInstallerTool isn't a checkout in the coredev buildout, it's something that I forgot to do.

comment:17 Changed 2 years ago by simahawk

Note: See TracTickets for help on using tickets.