Loading YAML safely in code

YAML · Advanced · 12 min

YAML loaders can instantiate objects, so loading untrusted YAML unsafely is a remote-code-execution bug.

YAML can carry tags that tell a loader to build language-specific objects, for example !!python/object/apply:os.system. A loader that honours them will run code chosen by whoever wrote the file. So:

  • Python: use yaml.safe_load (or yaml.load(s, Loader=yaml.SafeLoader)), never yaml.load with Loader=yaml.Loader or UnsafeLoader on untrusted input. Since PyYAML 5.1, calling yaml.load without a Loader warns, and since 6.0 it is an error.
  • Ruby: YAML.safe_load, the default for YAML.load since Psych 4.
  • JavaScript: js-yaml 4 and the yaml package load only standard types by default.
  • Java: SnakeYAML 2 uses a safe constructor by default; older code with new Yaml() was vulnerable.

Even safe loaders can be abused by alias bombs ("billion laughs"): a few lines with nested aliases that expand to billions of elements when the data is walked or serialized. Limit input size, and use parsers' alias limits where available (Go's yaml.v3 and the yaml npm package have them).

Some applications need to edit YAML while keeping comments and formatting. Normal loaders discard both. Use a round-trip library: ruamel.yaml in Python, the yaml package's Document API in JavaScript, yaml.v3 Node in Go.

For speed in Python, yaml.CSafeLoader uses libyaml and is many times faster.

Example

import yaml

doc = '''
name: demo
ports: [80, 443]
'''
print(yaml.safe_load(doc))          # {'name': 'demo', 'ports': [80, 443]}

evil = "!!python/object/apply:os.system ['echo pwned']"
try:
    yaml.safe_load(evil)
except yaml.constructor.ConstructorError as e:
    print("refused:", e.problem)    # refused: could not determine a constructor for the tag ...

from ruamel.yaml import YAML        # round-trip: comments survive
import sys
rt = YAML()
data = rt.load("replicas: 2  # keep this comment\n")
data["replicas"] = 3
rt.dump(data, sys.stdout)           # replicas: 3  # keep this comment

Remember

  • Use safe_load (or the language's safe API) for any untrusted YAML
  • Alias bombs can blow up memory even with safe loaders: cap input size
  • Use round-trip libraries like ruamel.yaml to edit files and keep comments

Watch out for

  • Loading user-supplied YAML with yaml.load(..., Loader=yaml.Loader) or UnsafeLoader.

Check yourself

  1. Why is yaml.load(data, Loader=yaml.UnsafeLoader) dangerous on untrusted input?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pyyaml.org/wiki/PyYAMLDocumentation