CVE-2020-7763: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: This affects the package phantom-html-to-pdf before 0.6.1.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 7.5
Published: 2020-11-05T14:15Z
Repository: https://github.com/pofider/phantom-html-to-pdf
Language: JavaScript
Files changed: lib/scripts/conversionScriptPart.js, test/test.js
Commit date: 2020-11-02 15:51:26 +0100
Commit message: fix access to files without protocol

Diff: diff --git a/lib/scripts/conversionScriptPart.js b/lib/scripts/conversionScriptPart.js
index 20c8f42..044ab07 100644
--- a/lib/scripts/conversionScriptPart.js
+++ b/lib/scripts/conversionScriptPart.js
@@ -17,23 +17,23 @@ if(body.cookies.length > 0) {
 
 page.onResourceRequested = function (request, networkRequest) {
     console.log('Request ' + request.url);
     if (request.url.lastIndexOf(body.url, 0) === 0) {
         return;
-    }
-
-    //potentially dangerous request
-    if (request.url.lastIndexOf("file:///", 0) === 0 && !body.allowLocalFilesAccess) {
-        networkRequest.abort();
-        return;
-    }
+    }   
 
     //to support cdn like format //cdn.jquery...
     if (request.url.lastIndexOf("file://", 0) === 0 && request.url.lastIndexOf("file:///", 0) !== 0) {
         networkRequest.changeUrl(request.url.replace("file://", "http://"));
     }
 
+     //potentially dangerous request
+     if (request.url.lastIndexOf("http://", 0) !== 0 && request.url.lastIndexOf("https://", 0) && !body.allowLocalFilesAccess) {
+        networkRequest.abort();
+        return;
+    }
+
     if (body.waitForJS && request.url.lastIndexOf("http://intruct-javascript-ending", 0) === 0) {
         pageJSisDone = true;
     }
 };
 
diff --git a/test/test.js b/test/test.js
index 2d74f19..3bc6a0d 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,8 +1,8 @@
-var should = require("should"),
+var should = require("should"),   
     path = require("path"),
-    fs = require("fs"),
+    fs = require("fs"),    
     phantomjs = require("phantomjs"),
     phantomjs2 = require("phantomjs-prebuilt")
     tmpDir = path.join(__dirname, "temp"),
     conversion = require("../lib/conversion.js")({
         timeout: 10000,
@@ -93,25 +93,11 @@ describe("phantom html to pdf", function () {
                     res.stream.should.have.property("readable");
 
                     done();
                 });
             });
-        });
-
-        it('should create a pdf file ignoring ssl errors', function(done) {
-            conversion({
-                url: 'https://sygris.com'
-            }, function(err, res) {
-                if (err) {
-                    return done(err);
-                }
-
-                res.numberOfPages.should.be.eql(1);
-                res.stream.should.have.property("readable");
-                done();
-            });
-        });
+        });       
 
         it('should wait for page js execution', function(done) {
             conversion({
                 html: '<h1>aa</h1><script>window.PHANTOM_HTML_TO_PDF_READY = true;</script>',
                 waitForJS: true
@@ -199,11 +185,11 @@ describe("phantom html to pdf", function () {
             }, function(err, res) {
                 if (err) {
                     return done(err);
                 }
 
-                JSON.stringify(res.logs).should.containEql('foo');
+                ;
                 done();
             });
         });
 
         it('should trim logs for long base64 encoded images', function(done) {
@@ -259,10 +245,39 @@ describe("phantom html to pdf", function () {
                     return done(err);
                 JSON.stringify(res.logs).should.containEql('test-cookie1=test-value1; test-cookie2=test-value2');
                 done();
             })
         });
+
+        it('should reject local files', function(done) {
+            conversion({
+                html: `<script>
+                document.write(window.location='${__filename.replace(/\\/g, '/')}')                
+              </script>`
+            }, function(err, res) {
+                if (err) {
+                    return done(err);
+                }
+                JSON.stringify(res.logs).should.containEql('Unable to load resource')
+                done()
+            });
+        });
+
+        it('should allow local files when allowLocalFilesAccess', function(done) {
+            conversion({
+                allowLocalFilesAccess: true,
+                html: `<script>
+                document.write(window.location='${__filename.replace(/\\/g, '/')}')                
+              </script>`
+            }, function(err, res) {
+                if (err) {
+                    return done(err);
+                }
+                JSON.stringify(res.logs).should.not.containEql('Unable to load resource')
+                done()
+            });
+        });
     }
 
     rmDir = function (dirPath) {
         if (!fs.existsSync(dirPath))
             fs.mkdirSync(dirPath);

Vulnerable code:     }

    //potentially dangerous request
    if (request.url.lastIndexOf("file:///", 0) === 0 && !body.allowLocalFilesAccess) {
        networkRequest.abort();
        return;
    }
var should = require("should"),
    fs = require("fs"),
        });

        it('should create a pdf file ignoring ssl errors', function(done) {
            conversion({
                url: 'https://sygris.com'
            }, function(err, res) {
                if (err) {
                    return done(err);
                }

                res.numberOfPages.should.be.eql(1);
                res.stream.should.have.property("readable");
                done();
            });
        });
                JSON.stringify(res.logs).should.containEql('foo');
Fixed code:     }   
     //potentially dangerous request
     if (request.url.lastIndexOf("http://", 0) !== 0 && request.url.lastIndexOf("https://", 0) && !body.allowLocalFilesAccess) {
        networkRequest.abort();
        return;
    }

var should = require("should"),   
    fs = require("fs"),    
        });       
                ;

        it('should reject local files', function(done) {
            conversion({
                html: `<script>
                document.write(window.location='${__filename.replace(/\\/g, '/')}')                
              </script>`
            }, function(err, res) {
                if (err) {
                    return done(err);
                }
                JSON.stringify(res.logs).should.containEql('Unable to load resource')
                done()
            });
        });

        it('should allow local files when allowLocalFilesAccess', function(done) {
            conversion({
                allowLocalFilesAccess: true,
                html: `<script>
                document.write(window.location='${__filename.replace(/\\/g, '/')}')                
              </script>`
            }, function(err, res) {
                if (err) {
                    return done(err);
                }
                JSON.stringify(res.logs).should.not.containEql('Unable to load resource')
                done()
            });
        });