CVE-2018-3757
Description
CVE-2018-3757 is a command injection in pdf-image v2.0.0 due to unescaped shell command construction.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2018-3757 is a command injection in pdf-image v2.0.0 due to unescaped shell command construction.
Vulnerability
CVE-2018-3757 is a command injection vulnerability in pdf-image version 2.0.0. The library constructs shell commands by concatenating user-controlled input (the pdfFilePath argument) without proper escaping. Specifically, the constructConvertCommandForPage method uses util.format to build a string that is passed directly to child_process.exec, allowing an attacker who controls the PDF file path to inject arbitrary shell commands [1][3].
Exploitation
An attacker with the ability to supply a malicious PDF file path (e.g., through an application that passes user-supplied filenames to pdf-image) can inject shell metacharacters into the command string. For example, a path containing backticks or semicolons would be executed by the shell. No authentication or special privileges are required beyond the ability to provide the file path to the library [1][3].
Impact
Successful exploitation allows the attacker to execute arbitrary shell commands in the context of the application process. This leads to full compromise of the affected system, including potential data exfiltration, file manipulation, or lateral movement. The vulnerability has a CVSS v3 severity of 9.8 (Critical) [2].
Mitigation
The vulnerability is fixed in commit 54679496a89738443917608c2bbe2f6e5dd20e83, which replaces child_process.exec with child-process-promise.spawn, avoiding shell interpretation. Users should update to a version of pdf-image that includes this fix (post-2.0.0). No other workarounds are available [1][2].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pdf-imagenpm | < 2.0.0 | 2.0.0 |
Affected products
1Patches
154679496a897resolve #340208 - Command injection in 'pdf-image', Severity:Medium (6.1) - fix #38 - solution for v2
3 files changed · +91 −65
index.js+59 −57 modified@@ -5,7 +5,7 @@ var Promise = require("es6-promise").Promise; var path = require("path"); var fs = require("fs"); var util = require("util"); -var exec = require("child_process").exec; +var spawn = require("child-process-promise").spawn; function PDFImage(pdfFilePath, options) { if (!options) options = {}; @@ -23,10 +23,10 @@ function PDFImage(pdfFilePath, options) { PDFImage.prototype = { constructGetInfoCommand: function () { - return util.format( - "pdfinfo \"%s\"", - this.pdfFilePath - ); + return { + cmd: "pdfinfo", + args: [this.pdfFilePath] + }; }, parseGetInfoCommandOutput: function (output) { var info = {}; @@ -40,20 +40,12 @@ PDFImage.prototype = { getInfo: function () { var self = this; var getInfoCommand = this.constructGetInfoCommand(); - var promise = new Promise(function (resolve, reject) { - exec(getInfoCommand, function (err, stdout, stderr) { - if (err) { - return reject({ - message: "Failed to get PDF'S information", - error: err, - stdout: stdout, - stderr: stderr - }); - } - return resolve(self.parseGetInfoCommandOutput(stdout)); - }); + return new Promise(function (resolve, reject) { + spawn(getInfoCommand.cmd, getInfoCommand.args, { capture: [ 'stdout', 'stderr' ]}) + .then(function (cmdResult) { + resolve(self.parseGetInfoCommandOutput(cmdResult.stdout.toString())); + }).catch(reject); }); - return promise; }, numberOfPages: function () { return this.getInfo().then(function (info) { @@ -84,46 +76,53 @@ PDFImage.prototype = { constructConvertCommandForPage: function (pageNumber) { var pdfFilePath = this.pdfFilePath; var outputImagePath = this.getOutputImagePathForPage(pageNumber); - var convertOptionsString = this.constructConvertOptions(); - return util.format( - "%s %s\"%s[%d]\" \"%s\"", - this.useGM ? "gm convert" : "convert", - convertOptionsString ? convertOptionsString + " " : "", - pdfFilePath, pageNumber, outputImagePath - ); + var convertOptions = this.constructConvertOptions(); + var args = []; + if (convertOptions) args = convertOptions.slice(); + args.push(pdfFilePath+"["+pageNumber+"]"); + args.push(outputImagePath); + + return { + cmd: this.useGM ? "gm convert" : "convert", + args: args + }; }, constructCombineCommandForFile: function (imagePaths) { - return util.format( - "%s -append %s \"%s\"", - this.useGM ? "gm convert" : "convert", - imagePaths.join(' '), - this.getOutputImagePathForFile() - ); + var args = imagePaths.slice(); + args.push(this.getOutputImagePathForFile()); + args.unshift("-append"); + return { + cmd: this.useGM ? "gm convert" : "convert", + args: args + }; }, constructConvertOptions: function () { - return Object.keys(this.convertOptions).sort().map(function (optionName) { + var convertOptions = []; + Object.keys(this.convertOptions).sort().map(function (optionName) { if (this.convertOptions[optionName] !== null) { - return optionName + " " + this.convertOptions[optionName]; + convertOptions.push(optionName); + convertOptions.push(this.convertOptions[optionName]); } else { - return optionName; + convertOptions.push(optionName); } - }, this).join(" "); + }, this); + return convertOptions; }, combineImages: function(imagePaths) { var pdfImage = this; var combineCommand = pdfImage.constructCombineCommandForFile(imagePaths); return new Promise(function (resolve, reject) { - exec(combineCommand, function (err, stdout, stderr) { - if (err) { - return reject({ + spawn(combineCommand.cmd, combineCommand.args, { capture: [ 'stdout', 'stderr' ]}) + .then(function () { + spawn("rm", imagePaths); //cleanUp + resolve(pdfImage.getOutputImagePathForFile()); + }).catch(function(error){ + reject({ message: "Failed to combine images", - error: err, - stdout: stdout, - stderr: stderr + error: error.message, + stdout: error.stdout, + stderr: error.stderr }); - } - exec("rm "+imagePaths.join(' ')); //cleanUp - return resolve(pdfImage.getOutputImagePathForFile()); }); }); }, @@ -167,16 +166,18 @@ PDFImage.prototype = { var promise = new Promise(function (resolve, reject) { function convertPageToImage() { - exec(convertCommand, function (err, stdout, stderr) { - if (err) { - return reject({ + return new Promise(function (resolve, reject) { + spawn(convertCommand.cmd, convertCommand.args, { capture: [ 'stdout', 'stderr' ]}) + .then(function () { + resolve(outputImagePath); + }).catch(function(error){ + reject({ message: "Failed to convert page to image", - error: err, - stdout: stdout, - stderr: stderr + error: error.message, + stdout: error.stdout, + stderr: error.stderr }); - } - return resolve(outputImagePath); + }); }); } @@ -194,7 +195,9 @@ PDFImage.prototype = { if (imageNotExists) { // (1) - convertPageToImage(); + convertPageToImage().then(function(result){ + resolve(result); + }).catch(reject); return; } @@ -209,11 +212,10 @@ PDFImage.prototype = { if (imageFileStat.mtime < pdfFileStat.mtime) { // (2) - convertPageToImage(); - return; + convertPageToImage().then(function(result){ + resolve(result); + }).catch(reject); } - - return resolve(outputImagePath); }); }); });
package.json+2 −1 modified@@ -11,7 +11,8 @@ }, "homepage": "https://github.com/mooz/node-pdf-image#readme", "dependencies": { - "es6-promise": "~4.2.4" + "es6-promise": "~4.2.4", + "child-process-promise": "^2.2.1" }, "devDependencies": { "chai": "~4.1.2",
tests/test-main.js+30 −7 modified@@ -45,19 +45,22 @@ describe("PDFImage", function () { }); it("should return correct convert command", function () { - expect(pdfImage.constructConvertCommandForPage(1)) - .equal('convert "/tmp/test.pdf[1]" "/tmp/test-1.png"'); + var convertCommand = pdfImage.constructConvertCommandForPage(1); + expect(convertCommand.cmd).equal("convert"); + expect(convertCommand.args.length).equal(2); }); it("should return correct convert command to combine images", function () { - expect(pdfImage.constructCombineCommandForFile(['/tmp/test-0.png', '/tmp/test-1.png'])) - .equal('convert -append /tmp/test-0.png /tmp/test-1.png "/tmp/test.png"'); + var cmdConfig = pdfImage.constructCombineCommandForFile(['/tmp/test-0.png', '/tmp/test-1.png']); + expect(cmdConfig.cmd).equal('convert'); + expect(cmdConfig.args.length).equal(4); }); it("should use gm when you ask it to", function () { pdfImage = new PDFImage(pdfPath, {graphicsMagick: true}); - expect(pdfImage.constructConvertCommandForPage(1)) - .equal('gm convert "/tmp/test.pdf[1]" "/tmp/test-1.png"'); + var cmdConfig = pdfImage.constructConvertCommandForPage(1); + expect(cmdConfig.cmd).equal('gm convert'); + expect(cmdConfig.args.length).equal(2); }); // TODO: Do page updating test @@ -148,7 +151,27 @@ describe("PDFImage", function () { "-density": 300, "-trim": null }); - expect(pdfImage.constructConvertOptions()).equal("-density 300 -trim"); + expect(pdfImage.constructConvertOptions()[0]).equal("-density 300"); + expect(pdfImage.constructConvertOptions()[1]).equal("-trim"); + }); + + it("should convert all PDF's pages with convertOptions", function () { + return new Promise(function(resolve, reject){ + pdfImage.setConvertOptions({ + "-quality": 100, + "-trim": null + }); + + pdfImage.convertFile().then(function (images) { + images.forEach(function(image){ + expect(fs.existsSync(image)).to.be.true; + }); + generatedFiles = images; + resolve(); + }).catch(function (error) { + reject(error.message + " " + error.stderr); + }); + }) }); afterEach(function(done){
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-5gwh-g79j-vh4qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-3757ghsaADVISORY
- github.com/roest01/node-pdf-image/commit/54679496a89738443917608c2bbe2f6e5dd20e83ghsax_refsource_CONFIRMWEB
- hackerone.com/reports/340208ghsax_refsource_MISCWEB
- www.npmjs.com/advisories/670ghsaWEB
News mentions
0No linked articles in our index yet.