CVE-2020-11020 (JavaScript, published 2020-04-29T18:15Z)
Description: value: Faye (NPM, RubyGem) versions greater than 0.5.0 and before 1.0.4, 1.1.3 and 1.2.5, has the potential for authentication bypass in the extension system. The vulnerability allows any client to bypass checks put in place by server-side extensions, by appending extra segments to the message channel. It is patched in versions 1.0.4, 1.1.3 and 1.2.5.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/faye/faye
Files changed:
lib/faye/protocol/server.rb
spec/javascript/server/extensions_spec.js
spec/ruby/server/extensions_spec.rb
src/protocol/server.js
Commit message: Strict meta channel recognition in server

This addresses a security vulnerability affecting user-added extensions
that implement access control for channels. These extensions typically
work by checking incoming messages whose channel is `/meta/subscribe`
and then performing some authentication routine before allowing the
message through.

However, the Server parses channels in a way that means any channel
namespaced under `/meta/subscribe` will also work as a subscription
request. For example if the client sends a message to the channel
`/meta/subscribe/x`, that will bypass most authentication extensions but
will still be interpreted by the server as a subscription request, and
the client will be subscribed to the requested channel. The client has
thus bypassed the user's access control policy.

Here we prevent this by using a strict equality check; only messages
whose channel is exactly `/meta/subscribe` will be interpreted as
subscription requests. The same pattern is applied to all other meta
channels.


Vulnerable code:
    META_METHODS = %w[handshake connect disconnect subscribe unsubscribe]

      method = Channel.parse(message['channel'])[1]
      unless META_METHODS.include?(method)
  META_METHODS: ['handshake', 'connect', 'disconnect', 'subscribe', 'unsubscribe'],

    var method = Channel.parse(message.channel)[1],
    if (array.indexOf(this.META_METHODS, method) < 0) {

Fixed code:
      method = method_for(message)
      unless method
    def method_for(message)
      case message['channel']
      when Channel::HANDSHAKE   then :handshake
      when Channel::CONNECT     then :connect
      when Channel::SUBSCRIBE   then :subscribe
      when Channel::UNSUBSCRIBE then :unsubscribe
      when Channel::DISCONNECT  then :disconnect
      end
    end

  describe("with subscription auth installed", function() { with(this) {
    before(function() { with(this) {
      var extension = {
        incoming: function(message, callback) {
          if (message.channel === "/meta/subscribe" && !message.auth) {
            message.error = "Invalid auth"
          }
          callback(message)
        }
      }
      server.addExtension(extension)
    }})

    it("does not subscribe using the intended channel", function() { with(this) {
      var message = {
        channel: "/meta/subscribe",
        clientId: "fakeclientid",
        subscription: "/foo"
      }
      stub(engine, "clientExists").yields([true])
      expect(engine, "subscribe").exactly(0)
      server.process(message, false, function() {})
    }})

    it("does not subscribe using an extended channel", function() { with(this) {
      var message = {
        channel: "/meta/subscribe/x",
        clientId: "fakeclientid",
        subscription: "/foo"
      }
      stub(engine, "clientExists").yields([true])
      expect(engine, "subscribe").exactly(0)
      server.process(message, false, function() {})
    }})
  }})

  describe "with subscription auth installed" do
    before do
      extension = Class.new do
        def incoming(message, callback)
          if message["channel"] == "/meta/subscribe" and !message["auth"]
            message["error"] = "Invalid auth"
          end
          callback.call(message)
        end
      end
      server.add_extension(extension.new)
    end

    it "does not subscribe using the intended channel" do
      message = {
        "channel" => "/meta/subscribe",
        "clientId" => "fakeclientid",
        "subscription" => "/foo"
      }
      engine.stub(:client_exists).and_yield(true)
      engine.should_not_receive(:subscribe)
      server.process(message, false) {}
    end

    it "does not subscribe using an extended channel" do
      message = {
        "channel" => "/meta/subscribe/x",
        "clientId" => "fakeclientid",
        "subscription" => "/foo"
      }
      engine.stub(:client_exists).and_yield(true)
      engine.should_not_receive(:subscribe)
      server.process(message, false) {}
    end
  end

    var method = this._methodFor(message),
    if (method === null) {
  _methodFor: function(message) {
    var channel = message.channel;

    if (channel === Channel.HANDSHAKE)   return 'handshake';
    if (channel === Channel.CONNECT)     return 'connect';
    if (channel === Channel.SUBSCRIBE)   return 'subscribe';
    if (channel === Channel.UNSUBSCRIBE) return 'unsubscribe';
    if (channel === Channel.DISCONNECT)  return 'disconnect';

    return null;
  },