Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2022-32224 PoC — Rails 代码问题漏洞

Source
Associated Vulnerability
Title:Rails 代码问题漏洞 (CVE-2022-32224)
Description:Rails是美国Rails团队的一套基于Ruby语言的开源Web应用框架。 Rails 存在安全漏洞,该漏洞源于当使用YAML(默认)的序列化列被反序列化时,Rails使用YAML.unsafe_load将YAML数据转化为Ruby对象。如果攻击者可以操纵数据库中的数据(通过SQL注入等手段),那么攻击者就有可能升级到RCE。
Readme
CVE-2022-3222 ActiveRecord シリアライズ 動作確認
===

- https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017
- https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html
- https://github.com/rails/rails/pull/45584


### 準備

```
bundle install

bin/rails db:migrate
```


### モデル

```
bin/rails generate model User values:string
```

```ruby
class User < ApplicationRecord
  serialize :values, Array
end
```


### 動作確認

#### 配列をシリアライズして保存

```ruby
❯ 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"]
```

`class_name_or_coder` として `Array` が指定されているため、文字列をそのままは渡せない

```ruby
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)
```


#### 更新

```ruby
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
```


#### `"`や`'`、改行を含む文字列のシリアライズ・デシリアライズ

```ruby
irb(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"}]}]
```


### 任意のクラスの復元

7.0.3では任意のクラスがシリアライズでの保存、読み込み時のデシリアライズができる

```ruby
❯ 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]
```   

7.0.3.1では保存、読み込みともにエラーになる

```ruby
❯ 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)
```

7.0.3であっても`ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy`のインスタンスを保存しようとするとエラーとなってうまく行かない

```ruby
.../psych/visitors/emitter.rb:32:in `scalar': wrong argument type ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy (expected String) (TypeError)
```


### シンボルの保存

7.0.3では保存できる

```ruby
❯ bundle exec rails c
Loading development environment (Rails 7.0.3)
irb(main):001:0> User.create(values: [{a: 2}])
   (1.8ms)  SELECT sqlite_version(*)
  TRANSACTION (0.0ms)  begin transaction
  User Create (0.5ms)  INSERT INTO "users" ("values", "created_at", "updated_at") VALUES (?, ?, ?)  [["values", "---\n- :a: 2\n"], ["created_at", "2022-07-17 08:03:26.435339"], ["updated_at", "2022-07-17 08:03:26.435339"]]
  TRANSACTION (0.3ms)  commit transaction
=>
#<User:0x00000001138835d8
 id: 5,
 values: [{:a=>2}],
 created_at: Sun, 17 Jul 2022 08:03:26.435339000 UTC +00:00,
 updated_at: Sun, 17 Jul 2022 08:03:26.435339000 UTC +00:00>
```

7.0.3.1では保存できない

```ruby
irb(main):002:0> User.create(values: [{a: 2}])
  TRANSACTION (0.1ms)  begin transaction
  TRANSACTION (0.1ms)  rollback transaction
.../psych/class_loader.rb:99:in `find': Tried to load unspecified class: Symbol (Psych::DisallowedClass)
.../psych/class_loader.rb:99:in `find': Tried to load unspecified class: Symbol (Psych::DisallowedClass)
```

---

### delayed_job

- https://github.com/collectiveidea/delayed_job_active_record

```
bundle install

rails g delayed_job:active_record
rake db:migrate
```

```ruby
class TestJob < ApplicationJob
  queue_as :default

  def perform(*arg)
    puts arg
  end
end
```

jobの引数はYAMLとして保存され、delayed_jobにより追加された`YAML.load_dj`でYAMLが読み込まれる
https://github.com/collectiveidea/delayed_job/blob/v4.1.10/lib/delayed/psych_ext.rb#L15

```ruby
irb(main):001:0> TestJob.perform_later({a: 2})
   (0.6ms)  SELECT sqlite_version(*)
  TRANSACTION (0.0ms)  begin transaction
  Delayed::Backend::ActiveRecord::Job Create (0.8ms)  INSERT INTO "delayed_jobs" ("priority", "attempts", "handler", "last_error", "run_at", "locked_at", "failed_at", "locked_by", "queue", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["priority", 0], ["attempts", 0], ["handler", "--- !ruby/object:ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper\njob_data:\n  job_class: TestJob\n  job_id: bb5c0a70-0ee8-4435-8e1c-2fc934f90c38\n  provider_job_id:\n  queue_name: default\n  priority:\n  arguments:\n  - a: 2\n    _aj_symbol_keys:\n    - a\n  executions: 0\n  exception_executions: {}\n  locale: en\n  timezone: UTC\n  enqueued_at: '2022-08-06T05:24:25Z'\n"], ["last_error", nil], ["run_at", "2022-08-06 05:24:25.560338"], ["locked_at", nil], ["failed_at", nil], ["locked_by", nil], ["queue", "default"], ["created_at", "2022-08-06 05:24:25.560377"], ["updated_at", "2022-08-06 05:24:25.560377"]]
  TRANSACTION (0.4ms)  commit transaction
```

delayed_jobのシリアライズはActiveRecordのものではないため、Railsのバージョンの影響を受けず、クラスが制限されない

```ruby
❯ bundle exec rails c
Loading development environment (Rails 7.0.3.1)
irb(main):001:0> TestJob.perform_now("test")
Performing TestJob (Job ID: 350a9420-0609-4b82-b860-d01be744dfaa) from DelayedJob(default) enqueued at  with arguments: "test"
test
Performed TestJob (Job ID: 350a9420-0609-4b82-b860-d01be744dfaa) from DelayedJob(default) in 1.66ms
=> nil

irb(main):002:0> gemspec = Gem::Specification.new("test")

irb(main):003:0> TestJob.perform_now(gemspec)
Performing TestJob (Job ID: 35ca24aa-18e4-4477-a92f-2247621c211e) from DelayedJob(default) enqueued at  with arguments: #<Gem::Specification:...
#<Gem::Specification name=test version=>
Performed TestJob (Job ID: 35ca24aa-18e4-4477-a92f-2247621c211e) from DelayedJob(default) in 0.73ms
=> nil
irb(main):004:0>
```
File Snapshot

[4.0K] /data/pocs/430abe888ef2d9ac143f90a4fd983e1d823012d4 ├── [4.0K] app │   ├── [4.0K] assets │   │   ├── [4.0K] config │   │   │   └── [ 63] manifest.js │   │   ├── [4.0K] images │   │   └── [4.0K] stylesheets │   │   └── [ 721] application.css │   ├── [4.0K] controllers │   │   ├── [ 57] application_controller.rb │   │   └── [4.0K] concerns │   ├── [4.0K] helpers │   │   └── [ 29] application_helper.rb │   ├── [4.0K] jobs │   │   ├── [ 269] application_job.rb │   │   └── [ 94] test_job.rb │   ├── [4.0K] models │   │   ├── [ 74] application_record.rb │   │   ├── [4.0K] concerns │   │   └── [ 62] user.rb │   └── [4.0K] views │   └── [4.0K] layouts │   └── [ 292] application.html.erb ├── [4.0K] bin │   ├── [ 175] delayed_job │   ├── [ 141] rails │   ├── [ 90] rake │   └── [1010] setup ├── [4.0K] config │   ├── [1.2K] application.rb │   ├── [ 207] boot.rb │   ├── [ 464] credentials.yml.enc │   ├── [ 620] database.yml │   ├── [ 128] environment.rb │   ├── [4.0K] environments │   │   ├── [2.2K] development.rb │   │   ├── [3.3K] production.rb │   │   └── [2.1K] test.rb │   ├── [4.0K] initializers │   │   ├── [ 504] assets.rb │   │   ├── [1.0K] content_security_policy.rb │   │   ├── [ 396] filter_parameter_logging.rb │   │   ├── [ 649] inflections.rb │   │   └── [ 384] permissions_policy.rb │   ├── [4.0K] locales │   │   └── [ 849] en.yml │   ├── [1.8K] puma.rb │   ├── [ 196] routes.rb │   └── [1.1K] storage.yml ├── [ 160] config.ru ├── [4.0K] db │   ├── [4.0K] migrate │   │   ├── [ 154] 20220717043019_create_users.rb │   │   └── [1.3K] 20220806051757_create_delayed_jobs.rb │   ├── [1.4K] schema.rb │   └── [ 374] seeds.rb ├── [ 859] Gemfile ├── [4.2K] Gemfile.lock ├── [4.0K] lib │   ├── [4.0K] assets │   └── [4.0K] tasks ├── [4.0K] log ├── [4.0K] public │   ├── [1.7K] 404.html │   ├── [1.7K] 422.html │   ├── [1.6K] 500.html │   ├── [ 0] apple-touch-icon.png │   ├── [ 0] apple-touch-icon-precomposed.png │   ├── [ 0] favicon.ico │   └── [ 99] robots.txt ├── [ 227] Rakefile ├── [ 11K] README.md ├── [4.0K] storage └── [4.0K] tmp 27 directories, 46 files
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. If the original source is unavailable, please email f.jinxu#gmail.com for a local snapshot (replace # with @).
    3. Shenlong has snapshotted the POC code for you. To support long-term maintenance, please consider donating. Thank you for your support.