Skip to content

Commit 967146e

Browse files
authored
Prevent directory traversal (#73)
1 parent 74a462a commit 967146e

File tree

11 files changed

+136
-14
lines changed

11 files changed

+136
-14
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
sudo: false
22
language: node_js
33
node_js:
4-
- '8'
5-
- '6'
6-
- '4'
4+
- '13'
5+
- '12'
6+
- '10'

fixtures/edge_case_dots.tar.gz

318 Bytes
Binary file not shown.

fixtures/slip.zip

1.91 KB
Binary file not shown.

fixtures/slip2.zip

1.9 KB
Binary file not shown.

fixtures/slip3.zip

2.38 KB
Binary file not shown.

fixtures/slipping.tar.gz

188 Bytes
Binary file not shown.

fixtures/slipping_directory.tar.gz

161 Bytes
Binary file not shown.

fixtures/top_level_example.tar.gz

113 Bytes
Binary file not shown.

index.js

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,38 @@ const runPlugins = (input, opts) => {
1919
return Promise.all(opts.plugins.map(x => x(input, opts))).then(files => files.reduce((a, b) => a.concat(b)));
2020
};
2121

22+
const safeMakeDir = (dir, realOutputPath) => {
23+
return fsP.realpath(dir)
24+
.catch(_ => {
25+
const parent = path.dirname(dir);
26+
return safeMakeDir(parent, realOutputPath);
27+
})
28+
.then(realParentPath => {
29+
if (realParentPath.indexOf(realOutputPath) !== 0) {
30+
throw (new Error('Refusing to create a directory outside the output path.'));
31+
}
32+
33+
return makeDir(dir).then(fsP.realpath);
34+
});
35+
};
36+
37+
const preventWritingThroughSymlink = (destination, realOutputPath) => {
38+
return fsP.readlink(destination)
39+
.catch(_ => {
40+
// Either no file exists, or it's not a symlink. In either case, this is
41+
// not an escape we need to worry about in this phase.
42+
return null;
43+
})
44+
.then(symlinkPointsTo => {
45+
if (symlinkPointsTo) {
46+
throw new Error('Refusing to write into a symlink');
47+
}
48+
49+
// No symlink exists at `destination`, so we can continue
50+
return realOutputPath;
51+
});
52+
};
53+
2254
const extractFile = (input, output, opts) => runPlugins(input, opts).then(files => {
2355
if (opts.strip > 0) {
2456
files = files
@@ -47,12 +79,35 @@ const extractFile = (input, output, opts) => runPlugins(input, opts).then(files
4779
const now = new Date();
4880

4981
if (x.type === 'directory') {
50-
return makeDir(dest)
82+
return makeDir(output)
83+
.then(outputPath => fsP.realpath(outputPath))
84+
.then(realOutputPath => safeMakeDir(dest, realOutputPath))
5185
.then(() => fsP.utimes(dest, now, x.mtime))
5286
.then(() => x);
5387
}
5488

55-
return makeDir(path.dirname(dest))
89+
return makeDir(output)
90+
.then(outputPath => fsP.realpath(outputPath))
91+
.then(realOutputPath => {
92+
// Attempt to ensure parent directory exists (failing if it's outside the output dir)
93+
return safeMakeDir(path.dirname(dest), realOutputPath)
94+
.then(() => realOutputPath);
95+
})
96+
.then(realOutputPath => {
97+
if (x.type === 'file') {
98+
return preventWritingThroughSymlink(dest, realOutputPath);
99+
}
100+
101+
return realOutputPath;
102+
})
103+
.then(realOutputPath => {
104+
return fsP.realpath(path.dirname(dest))
105+
.then(realDestinationDir => {
106+
if (realDestinationDir.indexOf(realOutputPath) !== 0) {
107+
throw (new Error('Refusing to write outside output directory: ' + realDestinationDir));
108+
}
109+
});
110+
})
56111
.then(() => {
57112
if (x.type === 'link') {
58113
return fsP.link(x.linkname, dest);

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"url": "github.com/kevva"
1111
},
1212
"engines": {
13-
"node": ">=4"
13+
"node": ">=7.6.0"
1414
},
1515
"scripts": {
1616
"test": "xo && ava"
@@ -41,9 +41,21 @@
4141
},
4242
"devDependencies": {
4343
"ava": "*",
44+
"esm": "^3.2.25",
4445
"is-jpg": "^1.0.0",
4546
"path-exists": "^3.0.0",
4647
"pify": "^2.3.0",
48+
"rimraf": "^3.0.2",
4749
"xo": "*"
50+
},
51+
"ava": {
52+
"require": [
53+
"esm"
54+
]
55+
},
56+
"xo": {
57+
"rules": {
58+
"promise/prefer-await-to-then": "off"
59+
}
4860
}
4961
}

0 commit comments

Comments
 (0)