CVE-2014-6394: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: visionmedia send before 0.8.4 for Node.js uses a partial comparison for verifying whether a directory is within the document root, which allows remote attackers to access restricted directories, as demonstrated using "public-restricted" under a "public" directory.
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.
Published: 2014-10-08T17:55Z
Repository: https://github.com/visionmedia/send
Language: JavaScript
Files changed: History.md, lib/send.js, test/fixtures/name.d/name.txt, test/send.js
Commit date: 2014-09-04 14:27:58 -0400
Commit message: Fix a path traversal issue when using root

fixes #59
fixes #60

Diff: diff --git a/History.md b/History.md
index 19d5b98..29e7e27 100644
--- a/History.md
+++ b/History.md
@@ -1,5 +1,10 @@
+unreleased
+==========
+
+  * Fix a path traversal issue when using `root`
+
 0.8.3 / 2014-08-16
 ==================
 
   * deps: destroy@1.0.3
     - renamed from dethroy
diff --git a/lib/send.js b/lib/send.js
index 2ea9319..e172fe3 100644
--- a/lib/send.js
+++ b/lib/send.js
@@ -410,20 +410,20 @@ SendStream.prototype.pipe = function(res){
 
   var parts
   if (root !== null) {
     // join / normalize from optional root dir
     path = normalize(join(root, path))
-    root = normalize(root)
+    root = normalize(root + sep)
 
     // malicious path
     if (path.substr(0, root.length) !== root) {
       debug('malicious path "%s"', path)
       return this.error(403)
     }
 
     // explode path parts
-    parts = path.substr(root.length + 1).split(sep)
+    parts = path.substr(root.length).split(sep)
   } else {
     // ".." is malicious without "root"
     if (upPathRegexp.test(path)) {
       debug('malicious path "%s"', path)
       return this.error(403)
diff --git a/test/fixtures/name.d/name.txt b/test/fixtures/name.d/name.txt
new file mode 100644
index 0000000..789c47a
--- /dev/null
+++ b/test/fixtures/name.d/name.txt
@@ -0,0 +1 @@
+loki
\ No newline at end of file
diff --git a/test/send.js b/test/send.js
index 82d56de..f2db153 100644
--- a/test/send.js
+++ b/test/send.js
@@ -1095,10 +1095,21 @@ describe('send(file, options)', function(){
         request(app)
         .get('/pets/../name.txt')
         .expect(200, 'tobi', done)
       })
 
+      it('should with with trailing slash', function(done){
+        var app = http.createServer(function(req, res){
+          send(req, req.url, {root: __dirname + '/fixtures/'})
+          .pipe(res);
+        });
+
+        request(app)
+        .get('/name.txt')
+        .expect(200, 'tobi', done)
+      })
+
       it('should restrict paths to within root', function(done){
         var app = http.createServer(function(req, res){
           send(req, req.url, {root: __dirname + '/fixtures'})
           .pipe(res);
         });
@@ -1116,10 +1127,21 @@ describe('send(file, options)', function(){
 
         request(app)
         .get('/pets/../../send.js')
         .expect(403, done)
       })
+
+      it('should not allow root transversal', function(done){
+        var app = http.createServer(function(req, res){
+          send(req, req.url, {root: __dirname + '/fixtures/name.d'})
+          .pipe(res);
+        });
+
+        request(app)
+        .get('/../name.dir/name.txt')
+        .expect(403, done)
+      })
     })
 
     describe('when missing', function(){
       it('should consider .. malicious', function(done){
         var app = http.createServer(function(req, res){

Vulnerable code:     root = normalize(root)
    parts = path.substr(root.length + 1).split(sep)
Fixed code: unreleased
==========

  * Fix a path traversal issue when using `root`

    root = normalize(root + sep)
    parts = path.substr(root.length).split(sep)
loki
      it('should with with trailing slash', function(done){
        var app = http.createServer(function(req, res){
          send(req, req.url, {root: __dirname + '/fixtures/'})
          .pipe(res);
        });

        request(app)
        .get('/name.txt')
        .expect(200, 'tobi', done)
      })


      it('should not allow root transversal', function(done){
        var app = http.createServer(function(req, res){
          send(req, req.url, {root: __dirname + '/fixtures/name.d'})
          .pipe(res);
        });

        request(app)
        .get('/../name.dir/name.txt')
        .expect(403, done)
      })