CVE-2017-16618: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2017-11-08T03:29Z
Repository: https://github.com/tadashi-aikawa/owlmixin
Language: Python
Files changed: owlmixin/util.py, tests/test_util.py, tests/yaml/vulnerability.yaml
Commit date: 2017-11-07 22:49:31 +0900
Commit message: :scream: Fix vulnerability of `load_yaml` and `load_yamlf` (#12)

Diff: diff --git a/owlmixin/util.py b/owlmixin/util.py
index 207b77f..15d8974 100644
--- a/owlmixin/util.py
+++ b/owlmixin/util.py
@@ -7,11 +7,11 @@ import io
 import re
 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
 from typing import List, Optional, Dict, Union, Sequence
 
@@ -42,11 +42,10 @@ 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)
 
 
 def replace_keys(d, keymap, force_snake_case):
     """
@@ -91,21 +90,21 @@ def load_jsonf(fpath, encoding):
 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):
     """
     :param unicode fpath:
     :param unicode 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):
     """
     :param unicode fpath:
diff --git a/tests/test_util.py b/tests/test_util.py
index 60fe899..e1dbb46 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,11 +1,14 @@
 # coding: utf-8
 
 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):
         keymap = {
             "self": "_self",
@@ -80,5 +83,40 @@ class TestToSnake:
 
     def test_docopt(self):
         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')
diff --git a/tests/yaml/vulnerability.yaml b/tests/yaml/vulnerability.yaml
new file mode 100644
index 0000000..66477b3
--- /dev/null
+++ b/tests/yaml/vulnerability.yaml
@@ -0,0 +1 @@
+!!python/object/apply:os.system ["calc.exe"]

Vulnerable code: from yaml import Loader, SafeLoader
Loader.add_constructor(u'tag:yaml.org,2002:str', construct_yaml_str)
    return yaml.load(yaml_str)
        return yaml.load(f)
Fixed code: from yaml import SafeLoader
    return yaml.safe_load(yaml_str)
        return yaml.safe_load(f)
from yaml.constructor import ConstructorError
import pytest



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')
!!python/object/apply:os.system ["calc.exe"]