CVE-2020-7729: Initialization of a Resource with an Insecure Default (CWE-1188)
value: The package grunt before 1.3.0 are vulnerable to Arbitrary Code Execution due to the default usage of the function load() instead of its secure replacement safeLoad() of the package js-yaml inside grunt.file.readYAML.
CWE description: The product initializes or sets a resource with a default that is intended to be changed by the administrator, but the default is not secure.
CVSS v3 base score: 7.1
Published: 2020-09-03T09:15Z
Repository: https://github.com/gruntjs/grunt
Language: JavaScript
Files changed: lib/grunt/file.js, test/grunt/file_test.js
Commit date: 2020-08-17 11:31:50 -0400
Commit message: Switch to use `safeLoad` for loading YML files via `file.readYAML`.

For previous behaviour please use the following:

```
readYAML('test/fixtures/utf8.yaml', null, {unsafeLoad: true});
```

Diff: diff --git a/lib/grunt/file.js b/lib/grunt/file.js
index eefeddb..7e0e2fb 100644
--- a/lib/grunt/file.js
+++ b/lib/grunt/file.js
@@ -239,16 +239,25 @@ file.readJSON = function(filepath, options) {
     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
   }
 };
 
 // Read a YAML file, parse its contents, return an object.
-file.readYAML = function(filepath, options) {
+file.readYAML = function(filepath, options, yamlOptions) {
+  if (!options) { options = {}; }
+  if (!yamlOptions) { yamlOptions = {}; }
+
   var src = file.read(filepath, options);
   var result;
   grunt.verbose.write('Parsing ' + filepath + '...');
   try {
-    result = YAML.load(src);
+    // use the recommended way of reading YAML files
+    // https://github.com/nodeca/js-yaml#safeload-string---options-
+    if (yamlOptions.unsafeLoad) {
+      result = YAML.load(src);
+    } else {
+      result = YAML.safeLoad(src);
+    }
     grunt.verbose.ok();
     return result;
   } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
diff --git a/test/grunt/file_test.js b/test/grunt/file_test.js
index e833fb2..b192cad 100644
--- a/test/grunt/file_test.js
+++ b/test/grunt/file_test.js
@@ -450,14 +450,17 @@ exports.file = {
     obj = grunt.file.readJSON('test/fixtures/iso-8859-1.json');
     test.deepEqual(obj, this.object, 'changing the default encoding should work.');
     test.done();
   },
   'readYAML': function(test) {
-    test.expect(4);
+    test.expect(5);
     var obj;
     obj = grunt.file.readYAML('test/fixtures/utf8.yaml');
-    test.deepEqual(obj, this.object, 'file should be read as utf8 by default and parsed correctly.');
+    test.deepEqual(obj, this.object, 'file should be safely read as utf8 by default and parsed correctly.');
+
+    obj = grunt.file.readYAML('test/fixtures/utf8.yaml', null, {unsafeLoad: true});
+    test.deepEqual(obj, this.object, 'file should be unsafely read as utf8 by default and parsed correctly.');
 
     obj = grunt.file.readYAML('test/fixtures/iso-8859-1.yaml', {encoding: 'iso-8859-1'});
     test.deepEqual(obj, this.object, 'file should be read using the specified encoding.');
 
     test.throws(function() {

Vulnerable code: file.readYAML = function(filepath, options) {
    result = YAML.load(src);
    test.expect(4);
    test.deepEqual(obj, this.object, 'file should be read as utf8 by default and parsed correctly.');
Fixed code: file.readYAML = function(filepath, options, yamlOptions) {
  if (!options) { options = {}; }
  if (!yamlOptions) { yamlOptions = {}; }

    // use the recommended way of reading YAML files
    // https://github.com/nodeca/js-yaml#safeload-string---options-
    if (yamlOptions.unsafeLoad) {
      result = YAML.load(src);
    } else {
      result = YAML.safeLoad(src);
    }
    test.expect(5);
    test.deepEqual(obj, this.object, 'file should be safely read as utf8 by default and parsed correctly.');

    obj = grunt.file.readYAML('test/fixtures/utf8.yaml', null, {unsafeLoad: true});
    test.deepEqual(obj, this.object, 'file should be unsafely read as utf8 by default and parsed correctly.');