CVE-2021-43846: Cross-Site Request Forgery (CSRF) (CWE-352)
value: `solidus_frontend` is the cart and storefront for the Solidus e-commerce project. Versions of `solidus_frontend` prior to 3.1.5, 3.0.5, and 2.11.14 contain a cross-site request forgery (CSRF) vulnerability that allows a malicious site to add an item to the user's cart without their knowledge. Versions 3.1.5, 3.0.5, and 2.11.14 contain a patch for this issue. The patch adds CSRF token verification to the "Add to cart" action. Adding forgery protection to a form that missed it can have some side effects. Other CSRF protection strategies as well as a workaround involving modifcation to config/application.rb` are available. More details on these mitigations are available in the GitHub Security Advisory.
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 4.3
Published: 2021-12-20T22:15Z
Repository: https://github.com/solidusio/solidus
Language: Ruby
Files changed: frontend/app/controllers/spree/orders_controller.rb
Commit date: 2021-12-14 11:59:45 +0100
Commit message: Protect `Spree::OrdersController#populate` against CSRF attacks

See
https://github.com/solidusio/solidus/security/advisories/GHSA-h3fg-h5v3-vf8m
for all the details.

Some time ago, all order actions were left out of CSRF protection (see
95ea57058ab1c5e722b327b10747cd41e68a4deb). The reason given was that the
authentication token got stale after the second rendering because the
product page is cached. That was limited to `#populate` in
cb797542c6948ef33d2cc9e6076c88f4cc927fb2 (see also
https://github.com/spree/spree/pull/5601).

However, those assumptions are not correct. Although the authenticity
token changes at every request, that doesn't mean that the old ones are
no longer valid. The variation comes from a one-time pad added to a
session-dependant token (and meant to avoid timing attacks). However,
before validation, that one-time pad is removed. That means the token
remains valid as long as the session has not been reset. Think about
submitting a form from one browser tab after opening another with the
same URL. Even if both tokens differ, the submission from the first tab
will still be valid. You can read
https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef
for an in-deep understanding.

The initial confusion could come because of
https://github.com/rails/rails/issues/21948. Due to browser-side cache,
a form can be re-rendered and sent without any attached request cookie.
That will cause an authentication error, as the sent token won't match
with the one in the session (none in this case). There's no perfect
solution for that, and all partial fixes should be seen at the
application level. From our side, we must provide a safe default. For an
excellent survey of all the available options, take a look at
https://github.com/betagouv/demarches-simplifiees.fr/blob/5b4f7f9ae9eaf0ac94008b62f7047e4714626cf9/doc/adr-csrf-forgery.md.
The information given in that link is third-party but it's very
relevant here. For that reason we've copied it in the security advisory
(see link above), but all the credit goes to @kemenaran.

Diff: diff --git a/frontend/app/controllers/spree/orders_controller.rb b/frontend/app/controllers/spree/orders_controller.rb
index bc23c3b46a..f21e4db653 100644
--- a/frontend/app/controllers/spree/orders_controller.rb
+++ b/frontend/app/controllers/spree/orders_controller.rb
@@ -8,11 +8,10 @@ module Spree
 
     before_action :store_guest_token
     before_action :assign_order, only: :update
     # note: do not lock the #edit action because that's where we redirect when we fail to acquire a lock
     around_action :lock_order, only: :update
-    skip_before_action :verify_authenticity_token, only: [:populate]
 
     def show
       @order = Spree::Order.find_by!(number: params[:id])
       authorize! :show, @order, cookies.signed[:guest_token]
     end

Vulnerable code:     skip_before_action :verify_authenticity_token, only: [:populate]