Versions Affected: All versions of Solidus (`solidus_api`)
Not affected: NONE
Fixed Versions: 4.7.2, 4.6.4
Impact
------
The PUT `/api/checkouts/:id/next`, `/api/checkouts/:id/advance`,
`/api/checkouts/:id/complete`, and `/api/checkouts/:id` endpoints all allow
setting the state of an order to an arbitrary value. Invalid states will fail
due a Ruby-level validation that the state exists on the state machine.
However, it is possible to use the parameter to skip over states by passing a
valid future state. This could allow a user to skip over any callbacks
associated to the state transition. For example, given an order in the
`"address"` state, we could pass the following parameters to the update
endpoint to move the order to the `"confirm"` state without ever providing a
valid shipping address or selecting a shipping method:
{
"state": "payment",
"order": {
"payments_attributes": [{
"payment_method_id": 1
}]
}
}
The order can then be completed, producing a completed order with no shipping
address or shipment. Payment cannot be skipped this way: completing an order
still requires a valid payment.
This vulnerability could result in data integrity issues due to the state
transitions being skipped when they shouldn't be.
Patches
-------
Versions 4.6.4 and 4.7.2 have been patched to fix this issue. The patch adds a
check to ensure the passed in state parameter is a valid checkout step and
that it is not a future step. This will allow users to return to previous
steps if necessary, and ensures that the state is only advanced by calls to
`order.next!` which happen in the `/api/checkouts/:id/next`,
`/api/checkouts/:id/advance`, and `/api/checkouts/:id` endpoints.
If for some reason your app depends on the old behaviour, you can choose to
opt out of the new behaviour by setting:
Spree::Api::Config.configure do |config|
config.allow_arbitrary_state_in_checkout = true
end
Workarounds
-----------
If you're unable to update, you can fix this in your app by adding the
following initializer:
# config/initializers/checkout_state_skip_fix.rb
Rails.application.config.to_prepare do
Spree::Api::CheckoutsController.class_eval do
private
def update_order_state
return unless params[:state]
unless @order.passed_checkout_step?(params[:state]) || @order.state == params[:state]
return head :unprocessable_entity
end
@order.state = params[:state]
state_callback(:before)
end
end
end
References
----------
*
https://github.com/solidusio/solidus/security/advisories/GHSA-qwqm-3jx5-rr8mIf you have any questions about how to secure your site please stop by our
Slack:
http://slack.solidus.io/