Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directory traversal security vulnerability in serveStatic #1910

Open
3 tasks done
slashnick opened this issue May 19, 2022 · 4 comments
Open
3 tasks done

Directory traversal security vulnerability in serveStatic #1910

slashnick opened this issue May 19, 2022 · 4 comments

Comments

@slashnick
Copy link

  • Used appropriate template for the issue type
  • Searched both open and closed issues for duplicates of this issue
  • Title adequately and concisely reflects the feature or the bug

Restify Version: 8.6.1
Node.js Version: 17.6.0

Expected behaviour

Users can only request files from src/static/.

Actual behaviour

Users can request files in src/ outside of static/ by requesting /static/../<path>.

Repro case

Imagine this directory structure:

  • package.json
  • src/
    • app.js
    • static/
      • foo.txt

If you want to serve files out of static/, you might write some code like this:

const restify = require('restify');
const server = restify.createServer();

server.get('/static/*', restify.plugins.serveStatic({
  directory: './src',
}));

server.listen(8080);

With the above code, an attacker can read app.js by requesting /static/../app.js.

Note: this is hard to reproduce with curl or a browser, because they typically normalize the request path client-side. I reproduced this locally by running:

$ (echo -n 'GET /static/../app.js HTTP/1.1\r\n\r\n' && sleep .5) | nc localhost 8080

Cause

Path normalization occurs in serveStatic, after the request routing has occurred. A request for /static/../app.js matches the route for /static/*.

Possible fixes

  • Normalize the request path before doing any routing
  • Deprecate or remove appendRequestPath: true
  • Deprecate or remove serveStatic in favor of serveStaticFiles

Are you willing and able to fix this?

No

@kolbma
Copy link

kolbma commented May 23, 2022

file = path.join(opts.directory, decodeURIComponent(opts.file));
} else if (opts.appendRequestPath) {
file = path.join(opts.directory, decodeURIComponent(req.path()));
} else {
var dirBasename = path.basename(opts.directory);
var reqpathBasename = path.basename(req.path());
if (
path.extname(req.path()) === '' &&
dirBasename === reqpathBasename
) {
file = opts.directory;
} else {
file = path.join(
opts.directory,
decodeURIComponent(path.basename(req.path()))

Because of the decodeURIComponent() the traversal might also be possible with appendRequestPath: false: %2F..%2Fapp.js

There should be a check if file is below opts.directory.

@kolbma
Copy link

kolbma commented May 23, 2022

The check is there:

var p = path.normalize(opts.directory).replace(/\\/g, '/');
var re = new RegExp('^' + escapeRE(p) + '/?.*');

if (!re.test(file.replace(/\\/g, '/'))) {
next(new NotAuthorizedError('%s', req.path()));
return;
}

But the normalization of file got lost by these changes of @ap0 9f84a85

@kolbma
Copy link

kolbma commented May 23, 2022

Seems to be also a problem with the check...
#1692

And the correct error for this case would be not NotAuthorizedError but ForbiddenError.

@kolbma
Copy link

kolbma commented May 23, 2022

And the check could be done faster without RE-matching -> string startsWith().

kolbma added a commit to kolbma/node-restify that referenced this issue May 26, 2022
Fixes restify#1910
Added tests to check for traversals.
Reworked existing tests for making more sense.
kolbma added a commit to kolbma/node-restify that referenced this issue May 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants