CVE-2017-16618 (published 2017-11-08T03:29Z)

An exploitable vulnerability exists in the YAML loading functionality of util.py in OwlMixin before 2.0.0a12. A "Load YAML" string or file (aka load_yaml or load_yamlf) can execute arbitrary Python commands resulting in command execution because load is used where safe_load should have been used. An attacker can insert Python into loaded YAML to trigger this vulnerability.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/tadashi-aikawa/owlmixin
commit_details: repo_url: https://github.com/tadashi-aikawa/owlmixin
author: tadashi-aikawa
author_date: 2017-11-07 22:49:31+09:00
msg: :scream: Fix vulnerability of `load_yaml` and `load_yamlf` (#12)
repository: repo_url: https://github.com/tadashi-aikawa/owlmixin
repo_name: tadashi-aikawa/owlmixin
description: Parsing mixin which converts data class instance, dict object, json string and yaml string each other.
repo_language: Python
owner: tadashi-aikawa
file_changes: filename: util.py
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@ import codecs
 import json
 import yaml
 from urllib.request import urlopen
-from yaml import Loader, SafeLoader
+from yaml import SafeLoader
 
 import csv
 from csv import register_dialect, Dialect, QUOTE_MINIMAL
@@ -44,7 +44,6 @@ class MyDumper(yaml.SafeDumper):
 def construct_yaml_str(self, node):
     return self.construct_scalar(node)
 
-Loader.add_constructor(u'tag:yaml.org,2002:str', construct_yaml_str)
 SafeLoader.add_constructor(u'tag:yaml.org,2002:str', construct_yaml_str)
 
 
@@ -93,7 +92,7 @@ def load_yaml(yaml_str):
     :param unicode yaml_str:
     :rtype: dict | list
     """
-    return yaml.load(yaml_str)
+    return yaml.safe_load(yaml_str)
 
 
 def load_yamlf(fpath, encoding):
@@ -103,7 +102,7 @@ def load_yamlf(fpath, encoding):
     :rtype: dict | list
     """
     with codecs.open(fpath, encoding=encoding) as f:
-        return yaml.load(f)
+        return yaml.safe_load(f)
 
 
 def load_csvf(fpath, fieldnames, encoding):

programming_language: Python
filename: test_util.py
change_type: ModificationType.MODIFY
diff: @@ -2,8 +2,11 @@
 
 from __future__ import division, absolute_import, unicode_literals
 
+from yaml.constructor import ConstructorError
 from owlmixin import util
 
+import pytest
+
 
 class TestReplaceKeys:
     def test_need_not_snake(self):
@@ -82,3 +85,38 @@ class TestToSnake:
         assert util.to_snake("<file_list>") == "file_list"
         assert util.to_snake("-o") == "o"
         assert util.to_snake("--detail-option") == "detail_option"
+
+
+class TestLoadYaml:
+    def test(self):
+        actual = util.load_yaml('''
+id: 1
+names:
+  - tadashi
+  - aikawa
+        ''')
+        assert actual == {
+            "id": 1,
+            "names": ['tadashi', 'aikawa']
+        }
+
+    def test_yaml_load_vulnerability(self):
+        with pytest.raises(ConstructorError):
+            util.load_yaml('!!python/object/apply:os.system ["calc.exe"]')
+
+
+class TestLoadYamlf:
+    def test(self):
+        assert util.load_yamlf('tests/yaml/spots_utf8.yaml', 'utf-8') == [
+            {
+                "address": {"name": "address1"},
+                "names": ["spot1"]
+            },
+            {
+                "names": ["スポット21", "スポット22"]
+            }
+        ]
+
+    def test_yaml_load_vulnerability(self):
+        with pytest.raises(ConstructorError):
+            util.load_yamlf('tests/yaml/vulnerability.yaml', 'utf-8')

programming_language: Python
filename: vulnerability.yaml
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+!!python/object/apply:os.system ["calc.exe"]

programming_language: Batchfile