Permalink
Browse files

made the tree objects sandbox-aware, which ripples down to file and d…

…irectory objects
  • Loading branch information...
1 parent 6eb1821 commit 187780281fde4d8a59b3a5e6e1ed77bc5374cb69 @mikedeboer committed Sep 12, 2012
Showing with 147 additions and 4 deletions.
  1. +13 −1 lib/DAV/server.js
  2. +62 −1 lib/DAV/tree.js
  3. +14 −1 lib/DAV/tree/filesystem.js
  4. +1 −1 test/test_mount.js
  5. +57 −0 test/test_sandbox.js
View
@@ -40,7 +40,6 @@ exports.DEFAULT_TMPDIR = (function() {
* Auto-load bundled plugins.
*/
exports.DEFAULT_PLUGINS = {};
-var self = this;
Fs.readdirSync(__dirname + "/plugins").forEach(function(filename){
if (/\.js$/.test(filename)) {
var name = filename.substr(0, filename.lastIndexOf('.'));
@@ -119,6 +118,11 @@ function Server(options) {
if (options.server && options.mount) { //use an existing server object
var self = this;
+ this.sandboxed = typeof options.sandboxed != "undefined"
+ ? options.sandboxed
+ : true;
+ if (this.sandboxed)
+ this.tree.setSandbox(this.tree.basePath);
this.setBaseUri("/" + options.mount.replace(/^\/+/, ""));
if (options.standalone) {
@@ -169,6 +173,14 @@ function Server(options) {
this.plugins = {};
/**
+ * If this server instance is a DAV mount, this means that it operates from
+ * within a sandbox.
+ *
+ * @var Boolean
+ */
+ this.sandboxed = false;
+
+ /**
* Called when an http request comes in, pass it on to the Handler
*
* @param {ServerRequest} req
View
@@ -8,7 +8,9 @@
"use strict";
var jsDAV = require("./../jsdav");
+var Exc = require("./exceptions");
var Util = require("./util");
+var Path = require("path");
/**
* Abstract tree object
@@ -21,6 +23,57 @@ exports.jsDAV_Tree = jsDAV_Tree;
this.REGBASE = this.REGBASE | jsDAV.__TREE__;
/**
+ * Chunk of the path that is part of the sandbox
+ *
+ * @var string
+ */
+ this.sandbox = null;
+
+ /**
+ * Set the path that needs to be stripped from the real path when presented
+ * to the user/ client and to check if a path from a request is within this
+ * path to prevent operations to files and directories that are outside of
+ * this sandbox
+ *
+ * @param {string} path
+ * @return void
+ */
+ this.setSandbox = function(path) {
+ this.sandbox = path.replace(/[\/]+$/, "");
+ };
+
+ /**
+ * Strips the sandbox part from the path.
+ *
+ * @var {string} path
+ * @return string
+ */
+ this.stripSandbox = function(path) {
+ if (!this.sandbox)
+ return path;
+ var idx = path.indexOf(this.sandbox);
+ if (idx === -1)
+ return path;
+ // remove the sandbox path from full path, usually at the start of the string
+ return path.substr(idx + this.sandbox.length);
+ };
+
+ /**
+ * Check whether the provided path is located inside the sandbox
+ *
+ * @param {string} path
+ * @return boolean
+ */
+ this.insideSandbox = function(path) {
+ if (!this.sandbox)
+ return true;
+ // if the relative path FROM the sandbox directory TO the requested path
+ // is outside of the sandbox directory, the result of Path.relative() will
+ // start with '../', which we can trap and use:
+ return Path.relative(this.sandbox, path).indexOf("../") !== 0;
+ };
+
+ /**
* This function must return an iNode object for a path
* If a Path doesn't exist, thrown an jsDAV_Exception_FileNotFound
*
@@ -39,6 +92,10 @@ exports.jsDAV_Tree = jsDAV_Tree;
*/
this.copy = function(sourcePath, destinationPath, cbcopytree) {
var self = this;
+ if (!this.insideSandbox(destinationPath)) {
+ return cbcopytree(new Exc.jsDAV_Exception_Forbidden("You are not allowed to copy to " +
+ this.stripSandbox(destinationPath)));
+ }
this.getNodeForPath(sourcePath, function(err, sourceNode) {
if (err)
return cbcopytree(err);
@@ -67,6 +124,10 @@ exports.jsDAV_Tree = jsDAV_Tree;
var sourceDir = parts[0];
var sourceName = parts[1];
parts = Util.splitPath(destinationPath);
+ if (!this.insideSandbox(destinationPath)) {
+ return cbmovetree(new Exc.jsDAV_Exception_Forbidden("You are not allowed to move to " +
+ this.stripSandbox(destinationPath)));
+ }
var destinationDir = parts[0];
var destinationName = parts[1];
@@ -141,7 +202,7 @@ exports.jsDAV_Tree = jsDAV_Tree;
afterCopy(destination);
});
});
-
+
}
function afterCopy(destination) {
View
@@ -40,9 +40,13 @@ exports.jsDAV_Tree_Filesystem = jsDAV_Tree_Filesystem;
*/
this.getNodeForPath = function(path, cbfstree) {
var realPath = this.getRealPath(path);
+ var nicePath = this.stripSandbox(realPath);
+ if (!this.insideSandbox(realPath))
+ return cbfstree(new Exc.jsDAV_Exception_Forbidden("You are not allowed to access " + nicePath));
+
Fs.stat(realPath, function(err, stat) {
if (!Util.empty(err))
- return cbfstree(new Exc.jsDAV_Exception_FileNotFound("File at location " + realPath + " not found"));
+ return cbfstree(new Exc.jsDAV_Exception_FileNotFound("File at location " + nicePath + " not found"));
cbfstree(null, stat.isDirectory()
? new jsDAV_FS_Directory(realPath)
: new jsDAV_FS_File(realPath))
@@ -83,6 +87,11 @@ exports.jsDAV_Tree_Filesystem = jsDAV_Tree_Filesystem;
* @return void
*/
this.realCopy = function(source, destination, cbfsrcopy) {
+ if (!this.insideSandbox(destination)) {
+ return cbfsrcopy(new Exc.jsDAV_Exception_Forbidden("You are not allowed to copy to " +
+ this.stripSandbox(destination)));
+ }
+
Fs.stat(source, function(err, stat) {
if (!Util.empty(err))
return cbfsrcopy(err);
@@ -105,6 +114,10 @@ exports.jsDAV_Tree_Filesystem = jsDAV_Tree_Filesystem;
this.move = function(source, destination, cbfsmove) {
source = this.getRealPath(source);
destination = this.getRealPath(destination);
+ if (!this.insideSandbox(destination)) {
+ return cbfsmove(new Exc.jsDAV_Exception_Forbidden("You are not allowed to move to " +
+ this.stripSandbox(destination)));
+ }
Fs.rename(source, destination, cbfsmove);
};
}).call(jsDAV_Tree_Filesystem.prototype = new jsDAV_Tree());
View
@@ -19,7 +19,7 @@ var server = Http.createServer(function(req, resp) {
server.listen(8080, "127.0.0.1");
jsDAV.mount({
- path: __dirname + "/assets",
+ node: __dirname + "/assets",
mount: "test",
server: server
});
View
@@ -0,0 +1,57 @@
+/*
+ * @package jsDAV
+ * @subpackage DAV
+ * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax.org>
+ * @author Mike de Boer <mike AT ajax DOT org>
+ * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
+ */
+"use strict";
+
+var Http = require("http");
+var Assert = require("assert");
+var jsDAV = require("./../lib/jsdav");
+var Util = require("./../lib/DAV/util");
+
+jsDAV.debugMode = true;
+
+function done(err) {
+ if (err)
+ console.log("got error", err);
+ process.exit();
+}
+
+var server = Http.createServer(function(req, resp) {
+ console.log("Incoming request in other handler...");
+});
+
+var config = {
+ host: "127.0.0.1",
+ port: 8080
+};
+
+server.listen(config.port, config.host, function() {
+ // request a resource outside of the mount dir
+ Http.get(Util.extend({ path: "/test/blah" }, config), function(res) {
+ Assert.equal(res.statusCode, 404);
+
+ Http.get(Util.extend({ path: "/test/../../../../etc/passwd" }, config), function(res) {
+ Assert.equal(res.statusCode, 403);
+
+ Http.get(Util.extend({ path: "/test/1.txt" }, config), function(res) {
+ Assert.equal(res.statusCode, 200);
+
+ Http.get(Util.extend({ path: "/test/walk/dir1/1.txt" }, config), function(res) {
+ Assert.equal(res.statusCode, 200);
+
+ done();
+ }).on("error", done);
+ }).on("error", done);
+ }).on("error", done);
+ }).on("error", done);
+});
+
+jsDAV.mount({
+ node: __dirname + "/assets",
+ mount: "test",
+ server: server
+});

0 comments on commit 1877802

Please sign in to comment.