-
-
Notifications
You must be signed in to change notification settings - Fork 231
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
Add 'followsymlinks' option which allows rejecting symlinks #127
base: master
Are you sure you want to change the base?
Conversation
…hat contain symlinks
I think this has a symlink race unless we manage to pass |
Hi Greg, I'm not sure what you mean. I see that it is theoretically possible to serve a file that we should disallow, if a request has determined that there is no symlink, and in the interim the filesystem is changed to include a symlink... but this would have to happen in the very small period of time between when node returns from line 109 and when it executes line 126 and would only result in a file served if the path existed as a valid path (without symlinks) in the first place, in which case, we would have been serving it prior anyway. Is that what you are referring to? If you can explain your concern I'll attempt to mitigate it. Jay |
Right -- classic TOCTOU. Further protecting us is that the check you're doing is synchronous, so you'd have to be running something like pm2 or kubernetes to be vulnerable. That said, followsymlinks is intended to protect against an attacker that has the ability to create symlinks, (otherwise there wouldn't be a vulnerable symlink in the first place) and our check (this PR) is running in response to a request that the attacker can generate, (the HTTP request for the static content) so the attacker is control of the timing of both the creation of the symlink and the validation of it. It looks like that Let me know if that works, or if you'd like me to test something out. Thanks for working on this PR! |
I've updated the patch to pass flags O_NOFOLLOW and O_RDONLY when followsymlinks is false. I also changed it so that if you choose 'followsymlinks=false' then it will resolve the root path to the real path at startup, which should still allow symlinks in the root path, at the cost of locking the root path at startup time. With followsymlinks=true (the default) the behavior is unchanged from prior to the patch. |
@@ -759,6 +764,89 @@ describe('serveStatic()', function () { | |||
.get('/todo/') | |||
.expect(404, done) | |||
}) | |||
}); | |||
|
|||
(skipSymlinks ? describe.skip : describe)('symlink tests', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(skipSymlinks ? describe.skip : describe)
Clever! Never seen this done before but makes sense :)
Is anyone able to merge this? @dougwilson? |
if (followsymlinks === false) { | ||
fullpath = realroot + path | ||
try { | ||
realpath = fs.realpathSync(realroot + path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is happening in the even loop of a request, we should probably use the async of this, not sync so it doesn't block unnecessarily
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can go ahead and give this try
Adds new option 'followssymlinks' which, when set to false, will reject any paths that contain symlinks.
Is smart enough to ignore symlinks below the root directory, and only rejects symlinks below the root.
Default behavior is completely unchanged. Tests included.
Resolves #120