CVE-2023-29566: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: huedawn-tesseract 0.3.3 and dawnsparks-node-tesseract 0.4.0 to 0.4.1 was discovered to contain a remote code execution (RCE) vulnerability via the child_process function.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 9.8
Published: 2023-04-24T18:15Z
Repository: https://github.com/rona-dinihari/dawnsparks-node-tesseract
Language: JSON
Files changed: lib/tesseract.js, package-lock.json, package.json, test/tesseract.js
Commit date: 2023-03-07 00:43:56 +0700
Commit message: exec() to execFile()

Diff: diff --git a/lib/tesseract.js b/lib/tesseract.js
index 54846f8..563deb4 100644
--- a/lib/tesseract.js
+++ b/lib/tesseract.js
@@ -2,16 +2,16 @@
 
 /**
  * Module dependencies.
  */
 var utils = require('./utils');
-var exec = require('child_process').exec;
+var execFile = require('child_process').execFile;
 var fs = require('fs');
 var tmpdir = require('os').tmpdir(); // let the os take care of removing zombie tmp files
 var uuid = require('uuid');
 var path = require('path');
-var { glob, } = require("glob");
+var { globSync, } = require("glob");
 
 var Tesseract = {
 
   tmpFiles: [],
 
@@ -49,11 +49,11 @@ var Tesseract = {
     const shellMetaChar = /\$\(.+\)/;
     if (shellMetaChar.test(image)) {
       callback(Error('Invalid image path'));
       return;
     }
-    
+
     var defaultOptions = utils.merge({}, Tesseract.options);
     options = utils.merge(defaultOptions, options);
 
     // generate output file name
     var output = path.resolve(tmpdir, 'node-tesseract-' + uuid.v4());
@@ -63,27 +63,27 @@ var Tesseract = {
 
     // assemble tesseract command
     var command = [options.binary, image, output];
 
     if (options.l !== null) {
-      command.push('-l ' + options.l);
+      command.push('-l')
+      command.push(options.l);
     }
 
     if (options.psm !== null) {
-      command.push('--psm ' + options.psm);
+      command.push('--psm')
+      command.push(options.psm);
     }
 
     if (options.config !== null) {
       command.push(options.config);
     }
 
-    command = command.join(' ');
-
     var opts = options.env || {};
 
     // Run the tesseract command
-    exec(command, opts, function(err, stdout, stderr) {
+    execFile(command[0], command.slice(1), opts, function(err, stdout, stderr) {
       if (err) {
         // Something went wrong executing the assembled command
         callback(err, null);
         return;
       }
@@ -96,15 +96,13 @@ var Tesseract = {
         let orientation = parseInt(match[1]);
         extra.orientation = orientation;
       }
 
       // Find one of the three possible extension
-      glob(output + '.+(html|hocr|txt)', function(err, files){
-        if (err) {
-          callback(err, null);
-          return;
-        }
+      try {
+        const files = globSync(output + '.{html,hocr,txt}');
+        const readFiles = function(files) {
         fs.readFile(files[0], Tesseract.outputEncoding, function(err, data) {
           if (err) {
             callback(err, null);
             return;
           }
@@ -114,11 +112,15 @@ var Tesseract = {
 
           fs.unlinkSync(files[0]);
 
           callback(null, data, extra);
         });
-      })
+        };
+        readFiles(files);
+      } catch (err) {
+        callback(err, null);
+      }
     }); // end exec
 
   }
 
 };
diff --git a/package-lock.json b/package-lock.json
index 15672e7..e34a10f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,14 +1,14 @@
 {
-  "name": "dawnsparks-tesseract",
-  "version": "0.4.0",
+  "name": "dawnsparks-node-tesseract",
+  "version": "0.4.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "dawnsparks-tesseract",
-      "version": "0.4.0",
+      "name": "dawnsparks-node-tesseract",
+      "version": "0.4.1",
       "license": "MIT",
       "dependencies": {
         "glob": "^9.0.1",
         "uuid": "^9.0.0"
       },
diff --git a/package.json b/package.json
index 9a3f6ca..a5248f2 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,14 @@
 {
   "name": "dawnsparks-node-tesseract",
-  "version": "0.4.0",
+  "version": "0.4.1",
   "author": "Rona Dini Hari <hari.ronadini@gmail.com>",
   "description": "A fork of a simple wrapper for the Tesseract OCR package",
   "main": "index.js",
   "repository": {
     "type": "git",
-    "url": "https://rona_dinihari@bitbucket.org/dawnsparks/dawnsparks-node-tesseract.git"
+    "url": "https://github.com/rona-dinihari/dawnsparks-node-tesseract.git"
   },
   "keywords": [
     "tesseract",
     "ocr",
     "text recognition"
diff --git a/test/tesseract.js b/test/tesseract.js
index ab11592..6ed0ea4 100644
--- a/test/tesseract.js
+++ b/test/tesseract.js
@@ -7,32 +7,36 @@ const fs = require('fs');
 
 
 describe('process', function(){
   it('should return the string "node-tesseract"', function(done){
 
-    var testImage = "\"" + __dirname + '/test.png' + "\"";
+    var testImage = path.join(__dirname, 'test with spaces.png');
 
     tesseract.process(testImage, function(err, text) {
-      text.trim().should.equal('node-tesseract');
-      done();
+      if (err) {
+        done(err);
+      } else {
+        text.trim().should.equal('node-tesseract');
+        done();
+      }
     });
-
-    done();
   })
 })
 
 describe('orientation', function(){
   it('returns orientation equal to 0 using psm 1', function(done){
 
-    var testImage = "\"" + __dirname + '/test.png' + "\"";
+    var testImage = path.join(__dirname, 'test with spaces.png');
 
     tesseract.process(testImage, { psm: 1 }, function(err, text, extra) {
-      extra.orientation.should.equal(0);
-      done();
+      if (err) {
+        done(err);
+      } else {
+        extra.orientation.should.equal(0);
+        done();
+      }
     });
-
-    done();
   })
 })
 
 describe('poc0', function() {
 
diff --git a/test/test.png b/test/test with spaces.png
similarity index 100%
rename from test/test.png
rename to test/test with spaces.png

Vulnerable code: var exec = require('child_process').exec;
var { glob, } = require("glob");
    
      command.push('-l ' + options.l);
      command.push('--psm ' + options.psm);
    command = command.join(' ');

    exec(command, opts, function(err, stdout, stderr) {
      glob(output + '.+(html|hocr|txt)', function(err, files){
        if (err) {
          callback(err, null);
          return;
        }
      })
  "name": "dawnsparks-tesseract",
  "version": "0.4.0",
      "name": "dawnsparks-tesseract",
      "version": "0.4.0",
  "version": "0.4.0",
    "url": "https://rona_dinihari@bitbucket.org/dawnsparks/dawnsparks-node-tesseract.git"
    var testImage = "\"" + __dirname + '/test.png' + "\"";
      text.trim().should.equal('node-tesseract');
      done();

    done();
    var testImage = "\"" + __dirname + '/test.png' + "\"";
      extra.orientation.should.equal(0);
      done();

    done();
Fixed code: var execFile = require('child_process').execFile;
var { globSync, } = require("glob");

      command.push('-l')
      command.push(options.l);
      command.push('--psm')
      command.push(options.psm);
    execFile(command[0], command.slice(1), opts, function(err, stdout, stderr) {
      try {
        const files = globSync(output + '.{html,hocr,txt}');
        const readFiles = function(files) {
        };
        readFiles(files);
      } catch (err) {
        callback(err, null);
      }
  "name": "dawnsparks-node-tesseract",
  "version": "0.4.1",
      "name": "dawnsparks-node-tesseract",
      "version": "0.4.1",
  "version": "0.4.1",
    "url": "https://github.com/rona-dinihari/dawnsparks-node-tesseract.git"
    var testImage = path.join(__dirname, 'test with spaces.png');
      if (err) {
        done(err);
      } else {
        text.trim().should.equal('node-tesseract');
        done();
      }
    var testImage = path.join(__dirname, 'test with spaces.png');
      if (err) {
        done(err);
      } else {
        extra.orientation.should.equal(0);
        done();
      }