Skip to content

Commit

Permalink
made the tree objects sandbox-aware, which ripples down to file and d…
Browse files Browse the repository at this point in the history
…irectory objects
  • Loading branch information
mikedeboer committed Sep 12, 2012
1 parent 6eb1821 commit 1877802
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 4 deletions.
14 changes: 13 additions & 1 deletion lib/DAV/server.js
Expand Up @@ -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('.'));
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -168,6 +172,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
*
Expand Down
63 changes: 62 additions & 1 deletion lib/DAV/tree.js
Expand Up @@ -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
Expand All @@ -20,6 +22,57 @@ exports.jsDAV_Tree = jsDAV_Tree;
(function() {
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
Expand All @@ -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);
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -141,7 +202,7 @@ exports.jsDAV_Tree = jsDAV_Tree;
afterCopy(destination);
});
});

}

function afterCopy(destination) {
Expand Down
15 changes: 14 additions & 1 deletion lib/DAV/tree/filesystem.js
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand All @@ -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());
2 changes: 1 addition & 1 deletion test/test_mount.js
Expand Up @@ -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
});
57 changes: 57 additions & 0 deletions test/test_sandbox.js
@@ -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.