Blogs @ Jsmon.sh
  • Request a Demo
  • Jsmon in Action!
Sign in Subscribe
YAML Deserialization

What is Ruby YAML Deserialization? Ways to Exploit, Examples and Impact

Master Ruby YAML deserialization security. Explore technical exploit examples, gadget chains, and actionable mitigation strategies to protect your apps.

Nadeem Ahmad

09 Mar 2026 — 6 min read
What is Ruby YAML Deserialization? Ways to Exploit, Examples and Impact

Ruby is a language celebrated for its developer-friendly syntax and powerful metaprogramming capabilities. However, these same features can introduce significant security risks when developers handle external data incorrectly. One of the most critical vulnerabilities in the Ruby ecosystem is YAML deserialization, a flaw that has historically led to high-profile Remote Code Execution (RCE) exploits in major frameworks like Ruby on Rails. Understanding how this vulnerability works, how it is exploited, and how to defend against it is essential for any modern cybersecurity professional or Ruby developer.

Understanding YAML and Ruby's Psych Engine

YAML (YAML Ain't Markup Language) is a human-readable data serialization standard that is commonly used for configuration files, data exchange, and object persistence. In the Ruby world, YAML is the de facto standard for storing complex data structures. To handle this data, Ruby uses a library called Psych, which is a wrapper around the libyaml C library.

When you serialize a Ruby object into YAML, you are converting a living, breathing object in memory into a string format that can be stored in a database or sent over a network. Deserialization is the reverse process: taking that string and reconstructing the Ruby object. The core of the security issue lies in the fact that YAML is not just a format for simple key-value pairs; it is expressive enough to represent complex Ruby objects, including their classes and internal states.

In older versions of Ruby, the default parser was Syck, but it has since been replaced by Psych. Regardless of the engine, the fundamental capability to instantiate arbitrary classes via YAML tags remains the primary source of risk when dealing with untrusted input.

The Fundamentals of Deserialization

To understand the vulnerability, we must first understand what deserialization is intended to do. In a typical application, you might have a User object. If you want to save this user's session to a cookie or a database, you might serialize it.

require 'yaml'

class User
  attr_accessor :name, :role
  def initialize(name, role)
    @name = name
    @role = role
  end
end

user = User.new("Alice", "admin")
yaml_data = YAML.dump(user)
# Returns: "--- !ruby/object:User\nname: Alice\nrole: admin\n"

The tag !ruby/object:User tells the YAML parser exactly which class to instantiate when the data is loaded back. When the application calls YAML.load(yaml_data), Psych sees that tag, creates a new instance of the User class, and populates its instance variables (@name and @role) with the provided values. This is incredibly powerful, but if an attacker can provide the yaml_data, they can tell the application to instantiate any class available in the Ruby environment, not just the one you intended.

The Security Risk: Why YAML.load is Dangerous

The danger of YAML.load (and its alias unsafe_load in newer versions) is that it allows for the instantiation of arbitrary Ruby objects. By itself, creating an object might seem harmless. However, in Ruby, objects often have "magic methods" or hooks that execute automatically during certain lifecycle events. These include initialize, marshal_load, or methods triggered when an object is treated like a string (like to_s) or accessed like a hash.

An attacker doesn't just want to create a User object; they want to find a "Gadget." A gadget is a class already present in the application's codebase or its dependencies (gems) that performs a dangerous action when it is initialized or when one of its methods is called. By chaining these gadgets together, an attacker can move from simple object creation to full Remote Code Execution.

Evolution of the Vulnerability: Syck vs. Psych

Historically, Ruby used the Syck engine, which was replaced by Psych in Ruby 1.9.3. For a long time, YAML.load was the standard method used by developers. It was only after several massive vulnerabilities—most notably the 2013 Rails YAML exploit (CVE-2013-0156)—that the community realized the inherent danger of unrestricted deserialization.

In response, the Psych library introduced YAML.safe_load. This method restricts the types of objects that can be deserialized to basic scalars like strings, integers, and arrays. If you try to load a custom Ruby object using safe_load, the parser will throw an error unless you explicitly whitelist that class.

In Ruby 3.1, the language maintainers took a massive step forward by making YAML.load behave like YAML.safe_load by default. While this significantly improves security for new applications, many legacy systems and older gems still use older versions of Ruby or call YAML.unsafe_load, leaving them vulnerable.

Exploiting Ruby YAML Deserialization: The Gadget Chain

Exploiting a YAML deserialization vulnerability requires finding a gadget chain. A gadget chain is a sequence of function calls that starts with a method triggered during deserialization and ends with a "sink"—a dangerous function like eval, system, or exec.

Example 1: Basic Object Instantiation

If an application takes a YAML string from a user-controlled cookie, an attacker can test for the vulnerability by trying to instantiate a class that doesn't exist.

--- !ruby/object:ThisClassDoesNotExist

If the application returns a 500 error with a message like ArgumentError (undefined class/module ThisClassDoesNotExist), the attacker knows the application is using YAML.load and is attempting to instantiate the class. This confirms the presence of the vulnerability.

Example 2: Achieving Remote Code Execution (RCE)

To achieve RCE, attackers often look for gadgets in common libraries like ActiveSupport or the Ruby standard library itself. One classic example involves the Gem::StubSpecification class found in the RubyGems library. By carefully crafting the internal state of this object, an attacker can trigger a call to eval when the object is interacted with.

Here is a conceptual look at what a malicious YAML payload might look like (simplified for educational purposes):

--- !ruby/object:Gem::StubSpecification
spec: !ruby/object:Gem::Source::SpecificFile
  spec: &1 !ruby/object:Gem::Specification
    name: "exploit"
    version: !ruby/object:Gem::Version
      version: "1.0"
    summary: "|/usr/bin/id"
  source: *1

In this scenario, the attacker leverages how RubyGems handles specifications. When the application eventually calls a method on this deserialized object (like to_s or validate), the internal logic of the Gem classes might execute the string provided in the summary field as a system command.

Another common technique involves the ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy gadget. This gadget is designed to warn developers when they access a deprecated variable. However, it can be manipulated to call a method on an arbitrary object. By pointing it at an instance of ERB, which is used for templating, an attacker can execute arbitrary Ruby code.

Real-World Impact and Case Studies

The impact of a successful YAML deserialization attack is almost always catastrophic. Since the attacker can execute arbitrary code, they operate with the same permissions as the application user. This leads to:

  1. Full Server Compromise: Attackers can install backdoors, move laterally through the network, or use the server for cryptomining.
  2. Data Exfiltration: Access to database credentials, environment variables, and sensitive customer data.
  3. Denial of Service (DoS): By instantiating massive objects or triggering infinite loops, an attacker can crash the application.

One of the most famous instances was the 2013 Rails vulnerability (CVE-2013-0156). This vulnerability allowed attackers to send a YAML payload via a simple HTTP request (by changing the Content-Type header). Because Rails automatically parsed YAML parameters, millions of applications were vulnerable to RCE without requiring any authentication. This event fundamentally changed how the Ruby community approached security.

How to Prevent Ruby YAML Deserialization Attacks

Prevention is straightforward but requires diligence. The goal is to never pass untrusted input to a function that can instantiate arbitrary objects.

Use YAML.safe_load

Always use YAML.safe_load instead of YAML.load. If you are using an older version of Ruby where YAML.load is still unsafe, make it a priority to update or explicitly use the safe method.

# Safe
YAML.safe_load(user_input)

# Unsafe
YAML.load(user_input)

Whitelisting Classes

If your application absolutely must deserialize custom objects, use the permitted_classes argument in safe_load. This ensures that only the classes you expect are allowed to be created.

YAML.safe_load(user_input, permitted_classes: [User, Symbol])

By restricting the parser to a small set of safe classes, you eliminate the possibility of an attacker using a dangerous gadget chain found in a third-party library.

Use Alternative Data Formats

For many use cases, YAML is overkill. If you only need to exchange simple data, JSON is a much safer alternative. The standard Ruby JSON.parse method does not support object instantiation by default, making it inherently resistant to this class of vulnerability. If your API or internal messaging doesn't require complex Ruby objects, switch to JSON.

Conclusion

Ruby YAML deserialization remains a classic example of how powerful language features can become liabilities when security isn't baked into the design of data handling. While modern Ruby versions have made significant strides in securing the Psych engine by default, the prevalence of legacy code and the complexity of modern dependency trees mean that gadget chains are still a very real threat. As a developer or security researcher, the rule of thumb is simple: treat every YAML string from an external source as potentially malicious. By prioritizing safe_load, whitelisting necessary classes, and moving toward safer formats like JSON, you can protect your infrastructure from one of the most potent attack vectors in the Ruby ecosystem.

To proactively monitor your organization's external attack surface and catch exposures like insecure deserialization before attackers do, try Jsmon.

Read more

What is Broken Access Control? Ways to Exploit, Examples and Impact

What is Broken Access Control? Ways to Exploit, Examples and Impact

Explore Broken Access Control, IDOR, and privilege escalation. Learn how to exploit and prevent the #1 OWASP vulnerability with technical code examples.

By Gaurav Singh Bisht 09 Mar 2026
What is Race Condition? Ways to Exploit, Examples and Impact

What is Race Condition? Ways to Exploit, Examples and Impact

Discover how race conditions work, see real-world exploit examples like TOCTOU, and learn how to secure your code against concurrency vulnerabilities.

By Gaurav Singh Bisht 09 Mar 2026
What is Webhook Vulnerability? Ways to Exploit, Examples and Impact

What is Webhook Vulnerability? Ways to Exploit, Examples and Impact

Explore webhook vulnerabilities like SSRF and replay attacks. Learn technical exploitation methods and best practices to secure your callback endpoints.

By Gaurav Singh Bisht 09 Mar 2026
What is GraphQL N+1 Problem? Ways to Exploit, Examples and Impact

What is GraphQL N+1 Problem? Ways to Exploit, Examples and Impact

Understand the GraphQL N+1 problem, its security impact, and how to prevent Denial of Service attacks with DataLoaders and query complexity analysis.

By Gaurav Singh Bisht 09 Mar 2026
Blogs @ Jsmon.sh
  • Request a Demo
  • Jsmon in Action!
  • Jsmon Extensions
  • Jsmon Pricing
Powered by Ghost

Blogs @ Jsmon.sh

In-depth research, technical articles, and security analysis from Jsmon.