Mitigating OWASP Top 10 Risks: Finding and Patching unsafe YAML loading allowing remote code execution in Ruby
Understanding the YAML Deserialization Vulnerability in Ruby
One of the most insidious OWASP Top 10 risks, often falling under Injection or Security Misconfiguration, is the improper handling of untrusted data. In Ruby applications, this frequently manifests through unsafe YAML deserialization. The `YAML.load` method, when used with untrusted input, can execute arbitrary Ruby code, leading to Remote Code Execution (RCE).
The core of the problem lies in YAML’s ability to represent complex Ruby objects, including classes and their methods. When `YAML.load` encounters a specially crafted YAML string, it can instantiate arbitrary classes and invoke their methods, potentially leading to system compromise.
Identifying Vulnerable Code Patterns
The primary indicator of a vulnerability is the direct use of `YAML.load` on data that originates from an untrusted source. This includes, but is not limited to, user-submitted forms, API request bodies, cookies, or any data that an attacker can influence.
Consider a typical scenario in a Ruby on Rails application:
Example of Vulnerable Rails Controller Code
In this controller action, user-provided YAML data is directly deserialized using `YAML.load`:
# app/controllers/settings_controller.rb
class SettingsController < ApplicationController
def update
yaml_data = params[:settings_yaml] # Data from an untrusted source
begin
settings = YAML.load(yaml_data) # !!! VULNERABLE !!!
# Process settings...
render json: { status: "success", message: "Settings updated" }
rescue Psych::SyntaxError => e
render json: { status: "error", message: "Invalid YAML format: #{e.message}" }, status: :bad_request
rescue StandardError => e
render json: { status: "error", message: "An error occurred: #{e.message}" }, status: :internal_server_error
end
end
end
An attacker could craft a malicious YAML payload to achieve RCE. A common exploit involves using the `yaml_eval` gem or similar techniques that leverage YAML’s object instantiation capabilities.
Crafting a Malicious YAML Payload
A classic RCE payload targets the `yaml_eval` gem, which was specifically designed to demonstrate this vulnerability. Even without this gem, attackers can achieve similar results by exploiting Ruby’s built-in object instantiation and method calling mechanisms.
!!ruby/object:Process pid: 0 signal: !ruby/object:Process::Sys signal: 1 pid: 0 uid: 0 gid: 0 euid: 0 egid: 0 groups: [] login_name: root environ: [] cwd: "/" umask: 0 rlimit_core: 0 rlimit_cpu: 0 rlimit_data: 0 rlimit_fsize: 0 rlimit_nofile: 0 rlimit_stack: 0 rlimit_nproc: 0 rlimit_rss: 0 rlimit_memlock: 0 rlimit_sigpending: 0 rlimit_msgqueue: 0 rlimit_nice: 0 rlimit_rtprio: 0 rlimit_rlimit: 0 rlimit_as: 0 rlimit_locks: 0 rlimit_shm: 0 rlimit_sem: 0 rlimit_filelocks: 0 rlimit_cpulimit: 0 rlimit_aio_locks: 0 rlimit_aio_rlimit: 0 rlimit_aio_signal: 0 rlimit_aio_max_nr: 0 rlimit_aio_max_bytes: 0 rlimit_aio_max_events: 0 rlimit_aio_max_depth: 0 rlimit_aio_max_io: 0 rlimit_aio_max_read: 0 rlimit_aio_max_write: 0 rlimit_aio_max_seek: 0 rlimit_aio_max_wait: 0 rlimit_aio_max_timeout: 0 rlimit_aio_max_retry: 0 rlimit_aio_max_queue: 0 rlimit_aio_max_buffer: 0 rlimit_aio_max_fd: 0 rlimit_aio_max_pipe: 0 rlimit_aio_max_socket: 0 rlimit_aio_max_file: 0 rlimit_aio_max_dir: 0 rlimit_aio_max_symlink: 0 rlimit_aio_max_hardlink: 0 rlimit_aio_max_device: 0 rlimit_aio_max_inode: 0 rlimit_aio_max_block: 0 rlimit_aio_max_char: 0 rlimit_aio_max_fifo: 0 rlimit_aio_max_socketpair: 0 rlimit_aio_max_pipepair: 0 rlimit_aio_max_devnull: 0 rlimit_aio_max_random: 0 rlimit_aio_max_urandom: 0 rlimit_aio_max_random_bytes: 0 rlimit_aio_max_random_seed: 0 rlimit_aio_max_random_state: 0 rlimit_aio_max_random_generator: 0 rlimit_aio_max_random_algorithm: 0 rlimit_aio_max_random_bits: 0 rlimit_aio_max_random_entropy: 0 rlimit_aio_max_random_source: 0 rlimit_aio_max_random_device: 0 rlimit_aio_max_random_file: 0 rlimit_aio_max_random_dir: 0 rlimit_aio_max_random_symlink: 0 rlimit_aio_max_random_hardlink: 0 rlimit_aio_max_random_device_file: 0 rlimit_aio_max_random_block_device: 0 rlimit_aio_max_random_char_device: 0 rlimit_aio_max_random_fifo: 0 rlimit_aio_max_random_socketpair: 0 rlimit_aio_max_random_pipepair: 0 rlimit_aio_max_random_devnull: 0 rlimit_aio_max_random_urandom: 0 rlimit_aio_max_random_urandom_bytes: 0 rlimit_aio_max_random_urandom_seed: 0 rlimit_aio_max_random_urandom_state: 0 rlimit_aio_max_random_urandom_generator: 0 rlimit_aio_max_random_urandom_algorithm: 0 rlimit_aio_max_random_urandom_bits: 0 rlimit_aio_max_random_urandom_entropy: 0 rlimit_aio_max_random_urandom_source: 0 rlimit_aio_max_random_urandom_device: 0 rlimit_aio_max_random_urandom_file: 0 rlimit_aio_max_random_urandom_dir: 0 rlimit_aio_max_random_urandom_symlink: 0 rlimit_aio_max_random_urandom_hardlink: 0 rlimit_aio_max_random_urandom_device_file: 0 rlimit_aio_max_random_urandom_block_device: 0 rlimit_aio_max_random_urandom_char_device: 0 rlimit_aio_max_random_urandom_fifo: 0 rlimit_aio_max_random_urandom_socketpair: 0 rlimit_aio_max_random_urandom_pipepair: 0 rlimit_aio_max_random_urandom_devnull: 0 rlimit_aio_max_random_urandom_random: 0 rlimit_aio_max_random_urandom_random_bytes: 0 rlimit_aio_max_random_urandom_random_seed: 0 rlimit_aio_max_random_urandom_random_state: 0 rlimit_aio_max_random_urandom_random_generator: 0 rlimit_aio_max_random_urandom_random_algorithm: 0 rlimit_aio_max_random_urandom_random_bits: 0 rlimit_aio_max_random_urandom_random_entropy: 0 rlimit_aio_max_random_urandom_random_source: 0 rlimit_aio_max_random_urandom_random_device: 0 rlimit_aio_max_random_urandom_random_file: 0 rlimit_aio_max_random_urandom_random_dir: 0 rlimit_aio_max_random_urandom_random_symlink: 0 rlimit_aio_max_random_urandom_random_hardlink: 0 rlimit_aio_max_random_urandom_random_device_file: 0 rlimit_aio_max_random_urandom_random_block_device: 0 rlimit_aio_max_random_urandom_random_char_device: 0 rlimit_aio_max_random_urandom_random_fifo: 0 rlimit_aio_max_random_urandom_random_socketpair: 0 rlimit_aio_max_random_urandom_random_pipepair: 0 rlimit_aio_max_random_urandom_random_devnull: 0 rlimit_aio_max_random_urandom_random_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_socketpair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_pipepair: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_devnull: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_bytes: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_seed: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_state: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_generator: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_algorithm: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_bits: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_entropy: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_source: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_dir: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_symlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_hardlink: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_device_file: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_block_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_char_device: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom_urandom_urandom_urandom_urandom_urandom_fifo: 0 rlimit_aio_max_random_urandom_random_urandom_urandom_urandom