CVE-2023-25805: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: versionn, software for changing version information across multiple files, has a command injection vulnerability in all versions prior to version 1.1.0. This issue is patched in version 1.1.0.
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-02-20T16:15Z
Repository: https://github.com/commenthol/versionn
Language: JavaScript
Files changed: lib/gitfn.js, test/test.js
Commit date: 2023-02-19 11:00:55 +0100
Commit message: fix: Command Injection Vuln

Diff: diff --git a/lib/gitfn.js b/lib/gitfn.js
index 5bc669d..7145c59 100644
--- a/lib/gitfn.js
+++ b/lib/gitfn.js
@@ -1,32 +1,44 @@
 'use strict'
 
-var child = require('child_process')
-
-function GitFn (version, options) {
-  this._version = version
-  this._options = {
-    cwd: options.dir,
-    env: process.env,
-    setsid: false,
-    stdio: [0, 1, 2]
+const child = require('child_process')
+const semver = require('semver')
+
+const assertVersionValid = version => {
+  if (!semver.valid(version)) {
+    throw new Error('version is invalid')
   }
 }
-module.exports = GitFn
 
-GitFn.prototype = {
-  tag: function (cb) {
-    var cmd = ['git', 'tag', 'v' + this._version].join(' ')
-    this._exec(cmd, cb)
-  },
-  untag: function (cb) {
-    var cmd = ['git', 'tag', '-d', 'v' + this._version].join(' ')
-    this._exec(cmd, cb)
-  },
-  commit: function (cb) {
-    var cmd = ['git', 'commit', '-am', '"' + this._version + '"'].join(' ')
-    this._exec(cmd, cb)
-  },
-  _exec: function (cmd, cb) {
-    child.exec(cmd, this._options, cb)
+const exec = (cmd, options, cb) => child.exec(cmd, options, cb)
+
+class GitFn {
+  constructor (version, options) {
+    this._version = version
+    this._options = {
+      cwd: options.dir,
+      env: process.env,
+      setsid: false,
+      stdio: [0, 1, 2]
+    }
+  }
+
+  tag (cb) {
+    assertVersionValid(this._version)
+    const cmd = ['git', 'tag', 'v' + this._version].join(' ')
+    exec(cmd, this._options, cb)
+  }
+
+  untag (cb) {
+    assertVersionValid(this._version)
+    const cmd = ['git', 'tag', '-d', 'v' + this._version].join(' ')
+    exec(cmd, this._options, cb)
+  }
+
+  commit (cb) {
+    assertVersionValid(this._version)
+    const cmd = ['git', 'commit', '-am', '"' + this._version + '"'].join(' ')
+    exec(cmd, this._options, cb)
   }
 }
+
+module.exports = GitFn
diff --git a/test/test.js b/test/test.js
index 393a5d7..b18d1d6 100644
--- a/test/test.js
+++ b/test/test.js
@@ -307,6 +307,16 @@ describe('change multiple files', function () {
         assert.ok(e.message, 'could not increment')
       }
       done()
     })
   })
+
+  it('shall throw if not a valid version', function (done) {
+    const gitFn = new Version._.GitFn('& touch newFile', { dir: './' })
+    try {
+      gitFn.tag(done)
+    } catch (e) {
+      assert.strictEqual(e.message, 'version is invalid')
+      done()
+    }
+  })
 })

Vulnerable code: var child = require('child_process')

function GitFn (version, options) {
  this._version = version
  this._options = {
    cwd: options.dir,
    env: process.env,
    setsid: false,
    stdio: [0, 1, 2]
module.exports = GitFn
GitFn.prototype = {
  tag: function (cb) {
    var cmd = ['git', 'tag', 'v' + this._version].join(' ')
    this._exec(cmd, cb)
  },
  untag: function (cb) {
    var cmd = ['git', 'tag', '-d', 'v' + this._version].join(' ')
    this._exec(cmd, cb)
  },
  commit: function (cb) {
    var cmd = ['git', 'commit', '-am', '"' + this._version + '"'].join(' ')
    this._exec(cmd, cb)
  },
  _exec: function (cmd, cb) {
    child.exec(cmd, this._options, cb)
Fixed code: const child = require('child_process')
const semver = require('semver')

const assertVersionValid = version => {
  if (!semver.valid(version)) {
    throw new Error('version is invalid')
const exec = (cmd, options, cb) => child.exec(cmd, options, cb)

class GitFn {
  constructor (version, options) {
    this._version = version
    this._options = {
      cwd: options.dir,
      env: process.env,
      setsid: false,
      stdio: [0, 1, 2]
    }
  }

  tag (cb) {
    assertVersionValid(this._version)
    const cmd = ['git', 'tag', 'v' + this._version].join(' ')
    exec(cmd, this._options, cb)
  }

  untag (cb) {
    assertVersionValid(this._version)
    const cmd = ['git', 'tag', '-d', 'v' + this._version].join(' ')
    exec(cmd, this._options, cb)
  }

  commit (cb) {
    assertVersionValid(this._version)
    const cmd = ['git', 'commit', '-am', '"' + this._version + '"'].join(' ')
    exec(cmd, this._options, cb)

module.exports = GitFn

  it('shall throw if not a valid version', function (done) {
    const gitFn = new Version._.GitFn('& touch newFile', { dir: './' })
    try {
      gitFn.tag(done)
    } catch (e) {
      assert.strictEqual(e.message, 'version is invalid')
      done()
    }
  })