Safeguard
Vulnerability Analysis

Rails YAML deserialization RCE history and CVE-2013-0156 ...

A deep dive into CVE-2013-0156, the Rails YAML deserialization RCE that let attackers execute code via crafted requests, and how to remediate it.

Aman Khan
AppSec Engineer
8 min read

In January 2013, a single HTTP request could hand an attacker a shell on almost any Ruby on Rails application on the internet. That request didn't need a password, a session cookie, or a bug in application code — it just needed to exploit how Rails parsed incoming XML and YAML data. The result, CVE-2013-0156, is still the reference case study whenever security teams talk about Rails YAML deserialization RCE: an unauthenticated, remote code execution vulnerability baked into the framework's parameter-parsing layer, exploited at internet scale within days of disclosure. More than a decade later, it remains one of the clearest illustrations of why unsafe deserialization is such a dangerous class of bug, and why software supply chain visibility into framework versions matters as much as application-level code review.

What CVE-2013-0156 Actually Was

Rails' Action Pack component historically supported multiple request content types beyond form-encoded and JSON data, including XML. When Rails parsed an XML request body, it used a routine that could interpret embedded type annotations — including type="yaml" and type="symbol" — and pass the corresponding data straight into Ruby's YAML parser (or Marshal.load, depending on the code path affected by companion issues disclosed the same week). YAML in Ruby isn't a inert data format; it can be used to construct arbitrary Ruby objects, invoke constructors, and, when chained with the right gadget classes already loaded in a typical Rails process, execute operating system commands.

Because Action Pack would deserialize this XML/YAML payload automatically as part of normal parameter parsing — before any authentication, authorization, or application logic ran — an attacker only had to send a crafted POST request with the right Content-Type header. There was no login required, no CSRF token to bypass, and no application-specific bug to find. Every Rails app that accepted XML parameters, which was the framework default at the time, was reachable.

This is the essence of a Rails YAML deserialization RCE: the trust boundary that should exist between "untrusted network input" and "code execution" simply didn't exist at the framework layer.

Affected Versions and Components

CVE-2013-0156 affected Ruby on Rails' Action Pack parameter parsing across every actively used release line at the time:

  • Rails 3.2.x prior to 3.2.11
  • Rails 3.1.x prior to 3.1.10
  • Rails 3.0.x prior to 3.0.19
  • Rails 2.3.x prior to 2.3.15

The vulnerable component was Action Pack's XML parameter parser, which handled the conversion of XML request bodies into Ruby parameter hashes. It shipped as a default, enabled-by-default behavior in every supported Rails release line, which is a large part of why the exploitation footprint was so wide — this wasn't an opt-in feature or an edge-case configuration, it was how Rails behaved out of the box.

The Rails security team's advisory bundled the fix alongside related issues disclosed the same week, including CVE-2013-0155 (a parameter-hash type-confusion issue that could bypass authentication and other checks). Because the underlying pattern — untrusted input flowing into YAML or Symbol conversion — showed up in more than one code path, the initial round of patches was followed quickly by additional hardening, including CVE-2013-0333, after researchers found that the JSON parameter parser could be coerced into a similar object-instantiation problem. Anyone tracking "Rails remote code execution history" should treat this January 2013 window as a single, multi-CVE event rather than one isolated bug — the framework's whole approach to type-annotated parameter parsing needed to be redesigned, not just patched once.

CVSS, EPSS, and KEV Context

NVD's scoring for CVE-2013-0156 reflects the severity researchers and defenders assigned it at the time: a CVSS v2 base score in the high range (commonly cited as 7.5), driven by network attack vector, low attack complexity, no authentication required, and partial impact to confidentiality, integrity, and availability — though in practice, exploitation regularly achieved full remote code execution rather than merely "partial" impact, a gap that is common in CVSS v2 scores for object-instantiation bugs whose real-world severity outstrips the formula.

CVE-2013-0156 predates both the EPSS scoring system and CISA's Known Exploited Vulnerabilities (KEV) catalog, both of which launched years later. That said, the "known exploited" label would have applied immediately in spirit: security researchers and Rails maintainers observed mass scanning and automated exploitation attempts within days of public disclosure, well before most operators had a chance to patch. If this vulnerability were disclosed today, it is exactly the profile — unauthenticated, network-reachable, trivially weaponized — that would land on the KEV catalog and score at or near the top of the EPSS exploitation-probability range.

Timeline of the Rails YAML Deserialization RCE

  • Disclosure (January 2013): The Rails core team published a security advisory disclosing CVE-2013-0156 alongside CVE-2013-0155, describing the YAML/Symbol parameter parsing issue and releasing patched versions (3.2.11, 3.1.10, 3.0.19, 2.3.15) simultaneously with the announcement — a deliberate choice to minimize the window between disclosure and available fixes.
  • Days later: Proof-of-concept exploit code circulated publicly, and security researchers reported mass internet scanning for vulnerable Rails installations. Compromised servers were used for a range of follow-on abuse, from installing bots to pivoting into internal networks — a pattern typical of unauthenticated RCE bugs in widely deployed web frameworks.
  • Follow-up patches (same month): Researchers identified that the JSON parameter parser could be abused in a related way, leading to CVE-2013-0333 and additional point releases. Operators who had only applied the first patch still needed a second round of updates.
  • Long tail: For years afterward, security scanners and penetration testers continued to find unpatched, internet-facing Rails 2.3 and 3.0 applications vulnerable to CVE-2013-0156, particularly in legacy or unmaintained deployments — a reminder that patch availability and patch adoption are two very different things.

Remediation Steps

The direct fix for CVE-2013-0156 has existed since 2013, but the remediation playbook is worth restating because the same pattern recurs across Rails releases and other frameworks:

  1. Upgrade to a patched Rails version. For legacy applications, that means Rails 3.2.11, 3.1.10, 3.0.19, or 2.3.15 or later. For any application still running pre-2013 Rails today, the priority isn't just this one CVE — it's the accumulated years of subsequent Rails remote code execution history that also needs remediation, including later YAML-related issues like CVE-2022-32224 (Action Pack YAML deserialization via Psych) and cache-based deserialization bugs such as CVE-2020-8165.
  2. Disable or restrict XML parameter parsing if an immediate framework upgrade isn't possible. Rails later made this parser opt-in rather than default specifically because of this incident.
  3. Avoid YAML.load on untrusted input anywhere in application code, including outside the framework itself — use YAML.safe_load (or Psych's safe-loading APIs) which restrict deserialization to plain data types and refuse arbitrary object construction.
  4. Apply every Ruby on Rails security patch as a batch, not a checklist item. Because the January 2013 incident required multiple sequential patches to fully close the hole, treat framework security advisories as living documents until the vendor confirms no further follow-up is expected.
  5. Inventory your dependency tree, not just your top-level Rails version. Transitive gems, vendored copies of Action Pack, and containerized images built months before a deploy can all reintroduce a patched vulnerability without anyone noticing.

Rails Remote Code Execution History: A Recurring Pattern

CVE-2013-0156 wasn't a one-off. It's the most famous entry in a longer Rails remote code execution history that includes CVE-2019-5420 (remote code execution via an exposed development-mode secret and unsafe deserialization of session data), CVE-2020-8165 (deserialization of untrusted data in Rails' MemCacheStore and RedisCacheStore), and CVE-2022-32224 (a YAML deserialization bypass in Action Pack that resurrected the same underlying pattern nearly a decade later). The throughline across all of these is the same: Ruby's flexible object marshaling, applied to data an attacker controls, is a code execution primitive if the trust boundary isn't enforced. Frameworks can and do slip on this repeatedly, which is exactly why relying on "we patched it once" is not a durable security posture.

How Safeguard Helps

Vulnerabilities like CVE-2013-0156 are a software supply chain problem as much as an application security problem — the vulnerable code lived inside a framework dependency, not in code any individual engineering team wrote, and exploitation happened faster than most organizations could inventory which of their services were even running Rails, let alone which version.

Safeguard is built for exactly this scenario. Our platform continuously maps the real dependency graph across your services — including transitive gems, vendored framework code, and container base images — so that when a Rails YAML deserialization RCE or any other framework-level vulnerability surfaces, you get an immediate, accurate answer to "which of our services are actually affected," not a spreadsheet exercise that takes days. We correlate CVE disclosures against your live SBOM data, prioritize remediation using real exploitability signals rather than CVSS scores alone, and flag services still running end-of-life Rails releases before an advisory ever drops. For historical, high-severity issues like CVE-2013-0156, Safeguard also helps confirm that legacy or acquired codebases haven't quietly reintroduced a decade-old vulnerable version — closing the exact gap that let this bug persist in production systems for years after a fix was available.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.