Versions Affected: All versions of Solidus (`solidus_api`)
Not affected: NONE
Fixed Versions: 4.7.1, 4.6.3
Impact
------
This is an authorization bypass affecting any Solidus store that mounts
`solidus_api` and allows guest checkout, the default configuration of a store
generated by `bin/rails g solidus:install`.
`Spree::Ability` substitutes an unsaved placeholder user for an anonymous
caller, so an unauthenticated request receives an ability whose `
user.id` is
`nil`. The default customer permission set then emits
`can [:read, :update], CreditCard, user_id:
user.id`, which for that anonymous
ability degenerates to `user_id: NULL`. Credit cards created during guest
checkout have a `NULL` `user_id`, so this rule matches every guest-checkout
card in the store. `Spree::Api::CreditCardsController#find_credit_card` looks
the record up unscoped by its sequential integer primary key and authorizes it
against exactly that ability, so the `authorize!` call is present and executes
but returns `true` incorrectly.
An unauthenticated attacker needs only an order token for a cart they own,
which anyone can obtain, to enumerate `Spree::CreditCard` primary keys and,
for every card left behind by a guest checkout, overwrite the cardholder name
and expiry date and read back the card's `last_digits` and full billing
address (name, street, city, postcode, phone). Registered users' cards are not
reachable this way (their `user_id` is non-`NULL`), the attacker cannot read a
full PAN or CVV, cannot change the billing address, and cannot reassign card
ownership.
Only payment sources persisted as `Spree::CreditCard` are exposed:
`Spree::CreditCard` records are created by the built-in
`Spree::PaymentMethod::CreditCard` gateway and the `Spree::Gateway::Bogus`
test gateway. solidus_stripe, solidus_paypal_commerce_platform, and
solidus_braintree persist their payment sources as their own classes backed by
their own tables and are not affected. A store that transacts exclusively
through those gateways accumulates no vulnerable `NULL`-owned
`Spree::CreditCard` rows.
Patches
-------
The fix guards every owner-scoped rule in
`Spree::PermissionSets::DefaultCustomer` with `if user.persisted?`, so an
ability with no persisted user grants no owner-scoped access. It is released
in Solidus 4.6.3 and 4.7.1. All prior versions are affected (the flaw has
existed since 1.0.0); the 4.6.x and 4.7.x series receive the fix, and stores
on earlier, end-of-life series should upgrade.
Workarounds
-----------
Stores that cannot upgrade immediately can apply the same guard from an
initializer by prepending a decorator to
`Spree::PermissionSets::DefaultCustomer` that re-emits the owner-scoped rules
only when `user.persisted?`, or, more surgically, disable guest checkout so
that no credit cards with a `NULL` `user_id` exist for the anonymous rule to
match. Restricting or unmounting the `PUT`/`PATCH /api/credit_cards/:id` route
also closes the write path, though it does not address the underlying
authorization defect.
References
----------
*
https://github.com/solidusio/solidus/security/advisories/GHSA-92gv-2pxv-7gjg*
https://github.com/solidusio/solidus/commit/c270eec2805abc818d086dba6799069bee891e0eIf you have any questions about how to secure your site please stop by our
Slack:
http://slack.solidus.io/