CVE-2024-25619 (Ruby, published 2024-02-14T21:15Z)
Description: value: Mastodon is a free, open-source social network server based on ActivityPub. When an OAuth Application is destroyed, the streaming server wasn't being informed that the Access Tokens had also been destroyed, this could have posed security risks to users by allowing an application to continue listening to streaming after the application had been destroyed. Essentially this comes down to the fact that when Doorkeeper sets up the relationship between Applications and Access Tokens, it uses a `dependent: delete_all` configuration, which means the `after_commit` callback setup on `AccessTokenExtension` didn't actually fire, since `delete_all` doesn't trigger ActiveRecord callbacks. To mitigate, we need to add a `before_destroy` callback to `ApplicationExtension` which announces to streaming that all the Application's Access Tokens are being "killed". Impact should be negligible given the affected application had to be owned by the user. None the less this issue has been addressed in versions 4.2.6, 4.1.14, 4.0.14, and 3.5.18. Users are advised to upgrade. There are no known workaround for this vulnerability.
Severity: nan (CVSS v3 base score: )
Weakness: CWE-672 \u2014 Operation on a Resource after Expiration or Release
The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked.
Repository: https://github.com/mastodon/mastodon
Files changed:
app/lib/application_extension.rb
app/models/user.rb
spec/models/user_spec.rb
Commit message: Merge pull request from GHSA-7w3c-p9j8-mq3x

* Ensure destruction of OAuth Applications notifies streaming

Due to doorkeeper using a dependent: delete_all relationship, the destroy of an OAuth Application bypassed the existing AccessTokenExtension callbacks for announcing destructing of access tokens.

* Ensure password resets revoke access to Streaming API

* Improve performance of deleting OAuth tokens

---------

Co-authored-by: Claire <claire.github-309c@sitedethib.com>

Vulnerable code:

Fixed code:
    include Redisable


    # The relationship used between Applications and AccessTokens is using
    # dependent: delete_all, which means the ActiveRecord callback in
    # AccessTokenExtension is not run, so instead we manually announce to
    # streaming that these tokens are being deleted.
    before_destroy :push_to_streaming_api, prepend: true

  def push_to_streaming_api
    # TODO: #28793 Combine into a single topic
    payload = Oj.dump(event: :kill)
    access_tokens.in_batches do |tokens|
      redis.pipelined do |pipeline|
        tokens.ids.each do |id|
          pipeline.publish("timeline:access_token:#{id}", payload)
        end
      end
    end
  end

      # Revoke each access token for the Streaming API, since `update_all``
      # doesn't trigger ActiveRecord Callbacks:
      # TODO: #28793 Combine into a single topic
      payload = Oj.dump(event: :kill)
      redis.pipelined do |pipeline|
        batch.ids.each do |id|
          pipeline.publish("timeline:access_token:#{id}", payload)
        end
      end
    let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) }

      allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub)
    it 'revokes streaming access for all access tokens' do
      expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", Oj.dump(event: :kill)).once
    end