CVE-2022-4064 (Ruby, published 2022-11-19T19:15Z)
Description: value: A vulnerability was found in Dalli. It has been classified as problematic. Affected is the function self.meta_set of the file lib/dalli/protocol/meta/request_formatter.rb of the component Meta Protocol Handler. The manipulation leads to injection. The exploit has been disclosed to the public and may be used. The name of the patch is 48d594dae55934476fec61789e7a7c3700e0f50d. It is recommended to apply a patch to fix this issue. VDB-214026 is the identifier assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 3.7)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/petergoldstein/dalli
Files changed:
CHANGELOG.md
lib/dalli/protocol/meta.rb
lib/dalli/protocol/meta/request_formatter.rb
test/integration/test_network.rb
test/protocol/meta/test_request_formatter.rb
test/test_rack_session.rb
Commit message: Fixes #932 (#933)

Do some input sanitization for the meta protocol. Since this protocol uses raw test, it is possible to execute an injection attack against unsanitized inputs.

This commit explicitly sanitizes the CAS arguments and the ttl passed to flush.

I also added some missing tests for meta_arithmetic and fixed a few lints.

Vulnerable code:
          cmd << " C#{cas}" if cas && !cas.zero?
          cmd << " C#{cas}" if cas && !cas.zero?
          cmd << " C#{cas}" if cas && !cas.zero?
          cmd << " N#{ttl}" if ttl
          cmd << " #{delay}" if delay
            dc = Dalli::Client.new 'localhost:19333'
                dc = Dalli::Client.new('localhost:19123')
                dc = Dalli::Client.new(socket_path)
                dc = Dalli::Client.new('localhost:19123')
                dc = Dalli::Client.new('localhost:19123')
                dc = Dalli::Client.new('localhost:19123')
    it 'returns incorporates CAS when passed cas' do
      rsd = Rack::Session::Dalli.new(incrementor, memcache_server: 'nosuchserver')
      rsd = Rack::Session::Dalli.new(incrementor, namespace: 'test:rack:session')

Fixed code:
- Sanitize CAS inputs to ensure additional commands are not passed to memcached (xhzeem / petergoldstein)
- Sanitize input to flush command to ensure additional commands are not passed to memcached (xhzeem / petergoldstein)
        ttl = TtlSanitizer.sanitize(ttl)
          cmd << cas_string(cas)
          cmd << cas_string(cas)
          # Always set a TTL if an initial value is specified
          cmd << " N#{ttl || 0}" if ttl || initial
          cmd << cas_string(cas)
          cmd << " #{parse_to_64_bit_int(delay, 0)}" if delay

        def self.cas_string(cas)
          cas = parse_to_64_bit_int(cas, nil)
          cas.nil? || cas.zero? ? '' : " C#{cas}"
        end

        def self.parse_to_64_bit_int(val, default)
          val.nil? ? nil : Integer(val)
        rescue ArgumentError
          # Sanitize to default if it isn't parsable as an integer
          default
        end
          dc = Dalli::Client.new 'localhost:19333'
              dc = Dalli::Client.new('localhost:19123')
              dc = Dalli::Client.new(socket_path)
              dc = Dalli::Client.new('localhost:19123')
              dc = Dalli::Client.new('localhost:19123')
              dc = Dalli::Client.new('localhost:19123')
    it 'excludes CAS if set to 0' do
      assert_equal "ms #{key} #{val.bytesize} c F#{bitflags} MS\r\n#{val}\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, cas: 0)
    end

    it 'excludes non-numeric CAS values' do
      assert_equal "ms #{key} #{val.bytesize} c F#{bitflags} MS\r\n#{val}\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags,
                                                                    cas: "\nset importantkey 1 1000 8\ninjected")
    end


    it 'sets the base64 mode if configured' do
      assert_equal "ms #{key} #{val.bytesize} c b F#{bitflags} MS\r\n#{val}\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags,
                                                                    base64: true)
    end
    it 'incorporates CAS when passed cas' do

    it 'excludes CAS when set to 0' do
      assert_equal "md #{key}\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, cas: 0)
    end

    it 'excludes non-numeric CAS values' do
      assert_equal "md #{key}\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key,
                                                                       cas: "\nset importantkey 1 1000 8\ninjected")
    end

    it 'sets the base64 mode if configured' do
      assert_equal "md #{key} b\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, base64: true)
    end
  end

  describe 'meta_arithmetic' do
    let(:key) { SecureRandom.hex(4) }
    let(:delta) { rand(500..999) }
    let(:initial) { rand(500..999) }
    let(:cas) { rand(500..999) }
    let(:ttl) { rand(500..999) }

    it 'returns the expected string with the default N flag when passed non-nil key, delta, and initial' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N0 MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial)
    end

    it 'excludes the J and N flags when initial is nil and ttl is not set' do
      assert_equal "ma #{key} v D#{delta} MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: nil)
    end

    it 'omits the D flag is delta is nil' do
      assert_equal "ma #{key} v J#{initial} N0 MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: nil, initial: initial)
    end

    it 'uses ttl for the N flag when ttl passed explicitly along with an initial value' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N#{ttl} MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           ttl: ttl)
    end

    it 'incorporates CAS when passed cas' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N0 C#{cas} MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           cas: cas)
    end

    it 'excludes CAS when CAS is set to 0' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N0 MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           cas: 0)
    end

    it 'includes the N flag when ttl passed explicitly with a nil initial value' do
      assert_equal "ma #{key} v D#{delta} N#{ttl} MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: nil,
                                                                           ttl: ttl)
    end

    it 'swaps from MI to MD when the incr value is explicitly false' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N0 MD\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           incr: false)
    end

    it 'includes the quiet flag when specified' do
      assert_equal "ma #{key} v D#{delta} J#{initial} N0 q MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           quiet: true)
    end

    it 'sets the base64 mode if configured' do
      assert_equal "ma #{key} v b D#{delta} J#{initial} N0 MI\r\n",
                   Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial,
                                                                           base64: true)
    end
    it 'santizes the delay argument' do
      delay = "\nset importantkey 1 1000 8\ninjected"
      assert_equal "flush_all 0\r\n", Dalli::Protocol::Meta::RequestFormatter.flush(delay: delay)
    end

    rsd = Rack::Session::Dalli.new(incrementor, memcache_server: 'nosuchserver')
    rsd = Rack::Session::Dalli.new(incrementor, namespace: 'test:rack:session')