Skip to content

Cross Site Request Forgery (CSRF)

General

What is CSRF?

Cross-site request forgery (CSRF) is a type of browser-based attack involving a user who visits two sites:

  • A web-based user logs into one site (eg https://crm.example.org). They have an active browser session/cookie on this site.
  • Later, the same user casually browses to another site (eg https://evil.example.com).
  • The evil.example.com site emits some kind of hyperlink (<A HREF>, <FORM ACTION>, <IFRAME>, <IMG>, <SCRIPT>, etc) which tells the browser to send a request to crm.example.org, eg
    <img src="https://crm.example.org/civicrm/ajax/rest?entity=Contact&action=delete&..."/>
    
  • The browser requests this hyperlink. Because the user has an active session and suitable permission, crm.example.org performs the requested action (eg deleting random contacts).
  • Of course, the user didn't actually want to delete anything. This was a surreptitious request sent on their behalf.

How does CiviCRM protect against CSRF?

There are different techniques - some techniques are used for REST services (eg APIv3/APIv4); other techniques are used for web forms (such as HTML_QuickForm).

When should CSRF protections be required?

Requests which actively manipulate data must have CSRF protection. For example, civicrm/ajax/rest must have CSRF protection.

Requests which passively display linkable screens must not have CSRF protection. For example, civicrm/event/info must not have CSRF protection.

Many workflows involve a hybrid. For example, it is useful to have external hyperlinks to civicrm/event/register. The first page-load presents a form to a user; this page does not require CSRF protection. As the user interacts with the form (eg using rich widgets that require APIs; eg submitting the form to finish regstration), the page sends additional requests - and these do require CSRF protection.

APIv3/APIv4 REST

All requests for APIv3 REST and APIv4 REST are assessed for CSRF risk - they must have some attribute to indicate that CSRF is not a concern. Either:

  • Send the header X-Requested-With: XMLHttpRequest.
  • Authenticate with a mechanism that is not susceptible to CSRF.

The rationales for each are examined below.

When does APIv3/APIv4 require X-Requested-With?

It varies by version, authentication mechanism, and/or end-point. For APIv3/APIv4:

  • After v5.47+, CSRF protection depends on the authentication mechanism:
    • X-Requested-With: is required if you authenticate with standard HTTP headers (Cookie: or Authorization:).
    • X-Requested-With: is not required for bespoke authentication mechanisms (X-Civi-Auth:, ?_authx=, ?api_key=).
  • Before v5.47, CSRF protection depends on the end-point URL:
    • X-Requested-With: is required by civicrm/ajax/* -- regardless of whether you use standard or bespoke authentication.
    • X-Requested-With: is not required by extern/rest.php. This is not required because extern/rest.php only supports bespoke authentication (?api_key).

How does X-Requested-With mitigate CSRF?

When the browser follows a regular HTML hyperlink, it only sends standard headers. X-Requested-With: is a custom-header. evil.example.com can generate HTML hyperlinks in many ways (<A HREF>, <FORM ACTION>, etc), but none of them can specify the custom-header X-Requested-With:.

Browsers will send custom-headers under some other circumstances; notably, Javascript logic can send a custom-header. However, this is subject to the standard Same Origin Policy. Javascript on https://crm.example.org can send custom-headers to crm.example.org; but Javascript on https://evil.example.com cannot.

What authentication mechanisms are susceptible to CSRF?

CSRF attacks exploit standard HTTP headers such as Cookie: and/or Authorization:. For example, when a user logs into crm.example.org, the browser makes a note to continue sending Cookie: and/or Authorization: with every subsequent request (regardless of how the request is initiated). This automatic behavior creates the opportunity for CSRF.

For contrast, consider the legacy end-point extern/rest.php. Instead of standard headers, it uses the bespoke parameter ?api_key=. The browser does not send ?api_key= automatically -- so it's not a vector for CSRF.

Hypothetically, evil.example.com could construct a hyperlink to extern/rest.php?api_key=MY_API_KEY. But they need to specify MY_API_KEY. If they don't specify MY_API_KEY, then the request is anonymous - and conveys no extra privileges. If they do specify MY_API_KEY, then you have a prior security compromise. CSRF countermeasures like X-Requested-With: won't protect you from an attacker who already has an API key.

In short, CSRF is an important consideration for standard browser-based authentication flows (Cookie:/Authorization:) but not for bespoke authentication flows (?api_key=, X-Civi-Auth:).

QuickForm

TODO: Discuss qfid mechanism