From ce955a301ff372e8e9fb3a5b516620c60e7a082a Mon Sep 17 00:00:00 2001 From: Iskren Chernev Date: Wed, 6 Jul 2022 21:57:15 +0300 Subject: [PATCH] Bugfix: Fix command injection vulnerability in grunt tzdata pipeline The grunt data pipeline was constructed using full bash strings with input coming from command line, which can lead to arbitrary code execution. Switch from exec to execFile, which lists all command arguments in an array, so no injection is possible. Advisory: https://github.com/moment/moment-timezone/security/advisories/GHSA-56x4-j7p9-fcf9 --- tasks/data-download.js | 23 ++++++++++++----------- tasks/data-zdump.js | 8 +++----- tasks/data-zic.js | 7 +++++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tasks/data-download.js b/tasks/data-download.js index f606cb23..d232939f 100644 --- a/tasks/data-download.js +++ b/tasks/data-download.js @@ -1,32 +1,33 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-download', '1. Download data from iana.org/time-zones.', function (version) { version = version || 'latest'; var done = this.async(), - src = 'ftp://ftp.iana.org/tz/tzdata-latest.tar.gz', + src = (version === 'latest' ? + 'ftp://ftp.iana.org/tz/tzdata-latest.tar.gz' : + 'https://data.iana.org/time-zones/releases/tzdata' + version + '.tar.gz'), curl = path.resolve('temp/curl', version, 'data.tar.gz'), dest = path.resolve('temp/download', version); - if (version !== 'latest') { - src = 'https://data.iana.org/time-zones/releases/tzdata' + version + '.tar.gz'; - } - grunt.file.mkdir(path.dirname(curl)); grunt.file.mkdir(dest); grunt.log.ok('Downloading ' + src); - exec('curl ' + src + ' -o ' + curl + ' && cd ' + dest + ' && gzip -dc ' + curl + ' | tar -xf -', function (err) { + execFile('curl', [src, '-o', curl], function (err) { if (err) { throw err; } + grunt.log.ok('Downloaded ' + curl + ', extracting . . .'); + execFile('tar', ['-xzf', curl], { cwd: dest }, function (err) { + if (err) { throw err; } - grunt.log.ok('Downloaded ' + src); - - done(); + grunt.log.ok('Extracted ' + dest); + done(); + }); }); }); -}; \ No newline at end of file +}; diff --git a/tasks/data-zdump.js b/tasks/data-zdump.js index c1771950..65314b54 100644 --- a/tasks/data-zdump.js +++ b/tasks/data-zdump.js @@ -1,14 +1,12 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-zdump', '3. Dump data with zdump(8).', function (version) { version = version || 'latest'; - console.log(path.resolve('zdump')); - var done = this.async(), zicBase = path.resolve('temp/zic', version), zdumpBase = path.resolve('temp/zdump', version), @@ -34,7 +32,7 @@ module.exports = function (grunt) { src = path.join(zicBase, file), dest = path.join(zdumpBase, file); - exec('zdump -v ' + src, { maxBuffer: 20*1024*1024 }, function (err, stdout) { + execFile('zdump', ['-v', src], { maxBuffer: 20*1024*1024 }, function (err, stdout) { if (err) { throw err; } grunt.file.mkdir(path.dirname(dest)); @@ -42,7 +40,7 @@ module.exports = function (grunt) { if (stdout.length === 0) { // on some systems, when there are no transitions then we have // to get creative to learn the offset and abbreviation - exec('zdump UTC ' + src, { maxBuffer: 20*1024*1024 }, function (_err, _stdout) { + execFile('zdump', ['UTC', src], { maxBuffer: 20*1024*1024 }, function (_err, _stdout) { if (_err) { throw _err; } grunt.file.write(dest + '.zdump', normalizePaths(_stdout)); diff --git a/tasks/data-zic.js b/tasks/data-zic.js index bd81f7e3..7913f16c 100644 --- a/tasks/data-zic.js +++ b/tasks/data-zic.js @@ -1,7 +1,7 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-zic', '2. Compile data sources with zic(8).', function (version) { @@ -22,7 +22,10 @@ module.exports = function (grunt) { var file = files.shift(), src = path.resolve('temp/download', version, file); - exec('zic -d ' + dest + ' ' + src, function (err) { + if (!grunt.file.exists(src)) { + throw new Error("Can't process " + src + " with zic. File doesn't exist"); + } + execFile('zic', ['-d', dest, src], function (err) { if (err) { throw err; } grunt.verbose.ok('Compiled zic ' + version + ':' + file);