[GHSA-qwqm-3jx5-rr8m] Checkout state skip via unvalidated state parameter

3 views
Skip to first unread message

Jared Norman

unread,
Oct 7, 2026, 11:38:45 AM (5 days ago) Oct 7
to solidus-security
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-rr8m

If you have any questions about how to secure your site please stop by our
Slack: http://slack.solidus.io/

Reply all
Reply to author
Forward
0 new messages