CVE-2019-1010200 (JavaScript, published 2019-07-23T18:15Z)
Description: value: Voice Builder Prior to commit c145d4604df67e6fc625992412eef0bf9a85e26b and f6660e6d8f0d1d931359d591dbdec580fef36d36 is affected by: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'). The impact is: Remote code execution with the same privileges as the servers. The component is: Two web servers in the projects expose three vulnerable endpoints that can be accessed remotely. The endpoints are defined at: - /tts: https://github.com/google/voice-builder/blob/3a449a3e8d5100ff323161c89b897f6d5ccdb6f9/merlin_model_server/api.js#L34 - /alignment: https://github.com/google/voice-builder/blob/3a449a3e8d5100ff323161c89b897f6d5ccdb6f9/festival_model_server/api.js#L28 - /tts: https://github.com/google/voice-builder/blob/3a449a3e8d5100ff323161c89b897f6d5ccdb6f9/festival_model_server/api.js#L65. The attack vector is: Attacker sends a GET request to the vulnerable endpoint with a specially formatted query parameter. The fixed version is: After commit f6660e6d8f0d1d931359d591dbdec580fef36d36.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-78 \u2014 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
Repository: https://github.com/google/voice-builder
Files changed:
festival_model_server/api.js
festival_model_server/settings.js
festival_model_server/tests/UtilsTests.js
festival_model_server/utils.js
Commit message: Added function to replace characters with spaces and use it to sanitize text

Change-Id: I45ebf2da031f5526c202642cd8946b7202a44e53


Vulnerable code:
  const escapedText = JSON.stringify(text).slice(1, -1);
  // -b "(utt.relation.print (SayText \" hello \") 'Segment)"
  const cmd = `${FESTIVAL_BIN} -b ${festvoxFile}  -b "(${voiceName})" \
  -b "(utt.relation.print (SayText \\" ${escapedText} \\") 'Segment)"`;
  exec(cmd, options, (err, stdout, stderr) => {
  const escapedText = JSON.stringify(text).slice(1, -1);
  console.log(`Synthesize "${text}"`);
  // echo "Hello world" | festival/bin/text2wave \
  const cmd = `echo "${escapedText}" \
              | ${TEXT_TO_WAV_BIN} -eval ${festvoxFile} -eval "(${voiceName})"`;
  exec(cmd, options, (err, stdout, stderr) => {

Fixed code:
  const sanitizedText = utils.replaceCharactersWithSpaces(text);
  // read txt; TEXT=${txt};
  // -b "(utt.relation.print (SayText \" ${TEXT} \") 'Segment)";
  const cmd = `read txt; TEXT=\$\{txt\}; ${FESTIVAL_BIN} -b ${festvoxFile}  -b "(${voiceName})" \
  -b "(utt.relation.print (SayText \\" \$\{TEXT\} \\") 'Segment)";`;
  const child = exec(cmd, options, (err, stdout, stderr) => {
  child.stdin.write(sanitizedText);
  child.stdin.end();
  const sanitizedText = utils.replaceCharactersWithSpaces(text);
  console.log(`Synthesizing ${sanitizedText}`);
  // festival/bin/text2wave \
  const cmd = `${TEXT_TO_WAV_BIN} -eval ${festvoxFile} -eval "(${voiceName})"`;

  const child = exec(cmd, options, (err, stdout, stderr) => {
  child.stdin.write(sanitizedText);
  child.stdin.end();
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Module containing settings for merlin server.
 */
const settings = {
  // Festival synthesis pipeline might not handle these characters as expected
  // and also they are typically not needed during tts synthesis.
  // Therefore we replace them with spaces.
  CHARACTERS_TO_REPLACE_WITH_SPACES: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~',
};
module.exports = settings;
describe('Testing utils replaceCharactersWithSpaces method', () => {
  it('The function should return characters replaced with spaces and trim', () => {
    const testData = [
      ['`<text>`', 'text'],
      ['`text`', 'text'],
      ['$(text)', 'text'],
      ['"\$(text\)', 'text'],
      ['\"\$(text\)', 'text'],
      ['${{{text}', 'text'],
      ['\$\{text\}', 'text'],
      ['\${text"}', 'text'],
      ['"${{text"}', 'text'],
      ['"${text"}', 'text'],
      ["\"ශ්රී ලංකාව'\"", 'ශ්රී ලංකාව'],
      ['"$text"', 'text'],
      ['$text', 'text'],
      [",./;'[]*\-=", ''],
      ['echo `<text>`', 'echo   text'],
      ['echo $("text")', 'echo    text'],
      ['echo \$\{text\}', 'echo   text'],
    ];

    testData.forEach(function(entry) {
      expect(utils.replaceCharactersWithSpaces(entry[0])).toEqual(entry[1]);
    });
  });
});

const settings = require('./settings');

/**
 * Returns the original string with characters
 * specified in settings replaced with spaces.
 * @text {String} text to be replaced.
 */
const replaceCharactersWithSpaces = (text) => {
  const re = new RegExp(`[${settings.CHARACTERS_TO_REPLACE_WITH_SPACES}]`, 'g');
  const newText = text.replace(re, ' ').trim();
  return newText;
};

  replaceCharactersWithSpaces,