Lucene search

K
hackeroneJeremyevansH1:706934
HistoryOct 03, 2019 - 5:19 a.m.

Ruby: Variant of CVE-2013-0269 (Denial of Service and Unsafe Object Creation Vulnerability in JSON)

2019-10-0305:19:48
jeremyevans
hackerone.com
$500
24

EPSS

0.019

Percentile

88.5%

During my recent keyword argument separation work on rb_scan_args in the master branch, I discovered what I now think is a vulnerability.

While the CVE-2013-0269 change fixed most usage of JSON.parse, it ended up not fixing Kernel#JSON. The reason behind this is that internally, in JSON::Parser#initialize (in cParser_initialize in ext/json/parser/parser.c), there is a separate branch taken depending on whether an option hash was provided. The fix for CVE-2013-026 only fixed one of these branches (when a option hash is provided). It did not fix the other branch (when no option hash is provided).

Kernel#JSON is able to easily hit the case where no option hash is provided, because it does:

  def JSON(object, *args)
    if object.respond_to? :to_str
      JSON.parse(object.to_str, args.first)

In the common case, no extra arguments are provided, and args.first is nil. Historically, Ruby has allowed the rb_scan_args : character to handle a nil option hash like no option hash was provided. This is deprecated in the master branch, and a warning is issued, but it is still supported.

I fixed this in the master branch in the rb_scan_args commit, as it was needed to avoid the warning:
https://github.com/ruby/ruby/commit/80b5a0ff2a7709367178f29d4ebe1c54122b1c27#diff-59fb0f5411be4c22009691e1a7f5a185 . It was only later, when I was going to report this issue upstream that I realized the security implications.

I believe all previously released versions of Ruby since 1.9 (when JSON was included in stdlib) are vulnerable to this. I think this fix should be backported to Ruby 2.4, 2.5, and 2.6, and another CVE issued.

In addition to Kernel#JSON, there are some other vulnerable calls, though they are likely to be less common.

Full example code:

  require 'json'

  class A < Struct.new(:a)
    def self.json_create(object)
      new(*object['args'])
    end

    def to_json(*args)
      {
        'json_class'  => self.class.name,
        'args'        => [ a ],
      }.to_json(*args)
    end
  end

  js = A.new(1).to_json
  p JSON.parse(js) #=> {"json_class"=>"A", "args"=>[1]}
  p JSON(js)       #=> #<struct A a=1>

  # Also vulnerable, resulting in #<struct A a=1>
  p JSON.parse(js, nil)
  p JSON[js, nil]
  p JSON::Parser.new(js).parse

Impact

This highly depends on the application using in question. In order to be vulnerable, Kernel#JSON or one of the other vulnerable calls must be called with user provided input.

I am not sure this results in denial of service since Ruby 2.2, due to the support of dynamic symbols. However, I have not analyzed the related JSON code to determine if it creates dynamic or static symbols when create_additions is used.

Assuming that Kernel#JSON is called with user-provided input, this allows creation of arbitrary objects where there is a named class that has a json_create singleton method… More precisely, this allows calling json_create methods on any named constant with arbitrary arguments (assuming the constant returns a true value for json_createable?). Many Ruby applications use libraries that have objects in constants that support method_missing and could possibly be vulnerable. However, I have not done any research into possible exploitability, which is why I listed severity as Medium.

If any json/add/* files have been required, this could possibly be very dangerous, as those can allow the creation of arbitrary core/stdlib objects. For example json/add/ostruct being required, when combined with this vulnerability, allows the creation of arbitrary objects that support attacker-defined methods with attacker-defined values of any type supported by JSON. json/add/regexp allows the creation of arbitrary Regexps which could easily lead to denial of service, and combined with a vulnerability in the regexp engine (Onigmo), could potentially lead to remote code execution.