Rails serializes store data as a regular hash instead of using HWIA
source link: https://blog.saeloun.com/2022/11/30/ar-store-regular-hash
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rails serializes store data as a regular hash instead of using HWIA
Nov 30, 2022 , by Swaathi Kakarla
2 minute read
ActiveRecord’s store provides a simple way to store hashes in a single column, regardless of database support. This functionality is enabled by serialization. A hash is converted either into to a string or a binary format before being stored. This action is known as “dumping”. When the data is read back from the database, it is loaded and converted to a hash. Serialization is a core Ruby module provided by the Marshal library.
Let’s create a simple Rails model which uses store.
➜ rails g model user name:string profile:text
invoke active_record
create db/migrate/20221111115257_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
The profile column, though a text column, is serialized as a hash. Let’s add a store accessor to the model. There are many more options available on the Rails guides.
class User
store :profile, accessors: [ :dnd, :theme ]
end
Before
We can now create a User object and set the profile attributes.
irb(main):001:0> user = User.create!(name: "Emily", dnd: true, theme: "dark")
(1.8ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
JsonObj Create (1.5ms) INSERT INTO "users" ("profile", "created_at", "updated_at", "name") VALUES (?, ?, ?, ?) [["profile", "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\dnd: true\n\theme: dark\n"], ["created_at", "2022-11-11 08:46:32.271095"], ["updated_at", "2022-11-11 08:46:32.271095"], ["name", "Emily"]]
TRANSACTION (0.3ms) commit transaction
(Object doesn't support #inspect)
=>
irb(main):003:0> user.dnd
/Users/swaathi/.rbenv/versions/3.1.2/lib/ruby/3.1.0/psych/class_loader.rb:99:in `find': Tried to load unspecified class: ActiveSupport::HashWithIndifferentAccess (Psych::DisallowedClass)
There are two errors here,
- The profile column is serialized as a HashWithIndifferentAccess object which does not support the #inspect method.
- We are unable to access the dnd attribute.
This is due to a potential RCE in YAML serialized columns in the database.
When serialized columns that use YAML (the default) are deserialized, Rails uses YAML.unsafe_load to convert the YAML data in to Ruby objects. If an attacker can manipulate data in the database (via means like SQL injection), then it may be possible for the attacker to escalate to an RCE.
To safe guard against this, Rails changed the default YAML deserializer to use YAML.safe_load, which prevents deserialization of possibly dangerous objects. Unfortunately, this meant store which uses YAML serialization via HashWithIndifferentAccess was also affected.
After
The solution was for Rails to deserialize YAML data into a regular hash and then cast it back to HashWithIndifferentAccess before accessing. Thanks to this PR ActiveRecord store no longer uses HashWithIndifferentAccess and instead serializes data to a regular hash.
irb(main):001:0> user = User.create!(name: "Emily", dnd: true, theme: "dark")
TRANSACTION (0.1ms) begin transaction
User Create (1.5ms) INSERT INTO "users" ("profile", "created_at", "updated_at", "name") VALUES (?, ?, ?, ?) [["profile", "---\ndnd: true\ntheme: dark"], ["created_at", "2022-11-11 08:45:13.289388"], ["updated_at", "2022-11-11 08:45:13.289388"], ["name", "Emily"]]
TRANSACTION (0.4ms) commit transaction
=> #<User:0x000000010fb86b08
...
irb(main):003:0> user.dnd
=> true
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK