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.
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.
Before
We can now create a User object and set the profile attributes.
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.