Closed smtalk closed 1 year ago
Yes, I know, but I cannot make "admin" user serve static files from /home/admin/public_html
and user "vbart" serve files from /home/vbart/public_html
, can I? Without ability to do this, both security issues are still there on shared environments.
@smtalk You just need to add user of Unit daemon to primary groups of both vbart
and admin
.
For example, let's vbart
have vbart
group, admin
have admin
group, and Unit daemon are run as unit
.
Then you have to add unit
user to vbart
and admin
groups. If group permissions are properly set to those directories, then unit
will have access to both directories, while users will have access only to their own.
@VBart this does not solve the problem :) For example, I have unit added to vbart and admin groups, and it's still able to read root owned /etc/passwd or any other world-readable file on the system that file privileges allow to read. What's the point of full app isolation if simple symlink can read files from other users (if permissions allow do do it?).
I think full isolation of routes would require major recoding of that part. So, maybe at least a protection could be added to the next release not to follow the symlinks if the owner differs? If symlink is owned by user:user
- don't follow the symlink to a file owned by anotheruser:anotheruser
. This simple check would be enough to cover the basic security needs.
Thank you!
@smtalk There's no way to change permissions per request without significant performance degradation, that's why it doesn't make much sense to implement.
Note, that Apache option SymlinkIfOwnerMatch
won't help you too in this case. I'll quote the Apache documentation here:
This option should not be considered a security restriction, since symlink testing is subject to race conditions that make it circumventable.
It has serious race condition in its implementation, that effectively allows attacker to ignore any restrictions.
Currently, the best you can do with Unit is either putting it inside a container/chrooted environment or disable symlinks via mount options for users directories.
We can implement something similar to nginx directive disable_symlinks
with if_not_owner
option, but I should warn you that enabling it also leads to performance degradation.
@VBart yes, you've got the point, I had something like the following in my mind in latest posts:
disable_symlinks if_not_owner from=$document_root;
Providing a way to define static content serving rules directly in the app, even if it's way slower would be the most awesome though, that way @i4ki 's linux-namespace implementation would be in effect, including chroot. App is always running anyway, so, no overhead of the namespace creation. The ones who prefer safety vs. speed would have an alternative, in case they have an app which is 'associated' with the static files. That way whole linux namespace feature (with chrooting) could be used to serve the static content. Unit would be able to read files that are user-owned, and they wouldn't need to be readable by nobody user anymore (a bonus?). And everyone who wants just to serve static content without any app could use the current method.
Anyway. nginx-like disable_symlinks
is way better than nothing on shared hosting envs if there is no time for any other solutions. And it's pretty obvious that checking file type, ownership (and ownership of the target file) with every request slows the things down, but it's really acceptable on shared envs.
Thank you, I'm still surprised how you find time for so detailed and fast responses 👍
@smtalk That's not about checking only files ownership and type. This way Apache works and this way it has race condition. For proper check you have to follow the whole path to the file, opening and checking each directory.
Currently, you can serve static files from the app as well. In order to do that don't need anything specific from the Unit side. You can write an app that will work only as a static file server in any language supported by Unit.
@VBart I get it. The thing is we adapt Unit for masses in a web hosting control panel, it's not to have it for a specific app or own server. If it was for our own needs - the request wouldn't be here. We simply cannot ask end-customers to add a static file server to their wordpress installations and so on. We just want a feature to safely serve static content for them from unit, without offloading static content to Nginx (or Apache). Without this feature the only real solution would be to serve static content on another webserver, but this doesn't fit well with standalone-Unit case.
If you see no point to have an option to serve static content safely for other users of Unit, and if you don't see linux namespaces (+chrooting) codebase in Unit underutilized (because it might fit static content case perfectly, and be a little unique, because I know no other webserver doing this?) - we'll just try 'workarounding' this by not letting customers run Unit as a standalone webserver and serving static content directly in another webserver. I just wanted to know your opinion and plan the final things of nginx unit implementation in control panel.
@smtalk The problem here and I guess that also explains why you don't see similar features in other servers as well: loosing popularity of shared hosting and strong trend on clouds. That's what we see for the last few years. The disable_symlinks
directive in nginx was introduced in 2011 and now I can't remember when I see it used by someone last time. Nowadays it feels like forgotten and rarely used directive. Almost everybody just use cloud-hosting, or rent a cheap VM, and never having any shared-hosting related problems.
We simply cannot ask end-customers to add a static file server to their wordpress installations and so on. We just want a feature to safely serve static content for them from unit, without offloading static content to Nginx (or Apache).
But if you don't let them having their own Unit instance that they can fully control and that you can isolate, then you don't need to ask to add anything to their WordPress installation. You can automatically run such static server app with the same permissions as their WP instance.
Basically, if we would implement something like that, then the implementation will be similar: we will have to write an app that acts like a static server. Of course, it probably will be embed in any app module, but it just an implementation detail. Separate app here won't be much worse.
@VBart I won't argue on this, even if our product is growing a lot (past 17 years), it wouldn't mean your statements aren't true.
I did not mention solutions like mod_ruid2 or MPM-ITK, because they aren't so nice and pose other security risks. I specifically had linux namespaces in my mind. Apache/Nginx have no native support for linux namespaces/sandboxing.
Forget the hosting and my other statements. What I want to say is that it sounds really weird to me that I can run a PHP app in a very secure environment, seeing nothing else, just the app and it's files, then I just execute:
<?php
symlink('/etc/passwd','read.txt');
?>
Access read.txt over static file router and that namespace/chrooting loses its potential, as I see something app couldn't see natively.
"app like a static server" sounds great, but.. it's a 2nd namespace and 2nd process, a bit of waste of resources I think, but.. way better than nothing.
@VBart I also forgot to add something to the discussion of the "uniquenesses". Even application server Phusion Passenger has no support of chroot or linux namespaces, see their discussion to someone here: https://github.com/phusion/passenger/issues/307.
Quote: It made some sense in the past, but now that we have containerization technologies such as LXC and Docker, chroot is effectively obsolete.
However, I'm very glad Nginx Unit did not take that approach, and this uniqueness is pretty beneficial in many other environments, not just hosting.
Another request like https://github.com/nginx/unit/issues/494 would add another uniqueness there, and it would attract even more end-customers with different use-cases.
See https://twitter.com/solomonstre/status/1111004913222324225, in short (yes, maybe a bit overhyped): If WASM+WASI existed in 2008, we wouldn't have needed to created Docker.
Just wanted to say that "almost everybody just use cloud-hosting with containerized apps in docker" isn't the answer to everything, and I'm glad to see nginx-unit current feature-set not following the same route as passenger, as this differentiates the products and strengthens Unit. In another git issue you've told me that you don't see many Go/NodeJS users in Unit user-base (and like "chicken-or-the-egg" problem), it doesn't mean Go/NodeJS aren't popular or that their days are over :) And I'm pretty sure that whenever we finish stable/final integration with Unit, you'll see a spike in number of "hosting environments". The day Go/NodeJS issue with update-dependencies get solved - you'll see more "Go/NodeJS" environements. And whenever you have X-Forwarded-For implemented - you'll see more usage in the mentioned clouds and proxied environments. It's all about the possibilities (and yes, user-base that could use them).
Current Unit strengths to me are the uniquenesses it has, and that they were the things that attracted me the most! So, just a kind "thank you" for getting them here :) And I'm really not pushing you on anything, as anyone can be wrong with their predictions, that's why the discussion is here, which I really appreciate!
@smtalk
What I want to say is that it sounds really weird to me that I can run a PHP app in a very secure environment, seeing nothing else, just the app and it's files, then I just execute
It's easily fixed by not putting your static files to the application working directory.
There's no reason to mess app code with static media. Actually most web frameworks and apps don't have that mess and use a separate directory for public. However, there're bunch of apps and frameworks (mostly PHP) that mix .php
with other files under the same directory, which is a very bad practice actually.
This bad practice leads to many security flaws, not only that one you mentioned. For example, you can accidentally serve the source code of your app as a static file. Even if you configured your server to avoid serving .php
, but you're using editor that leaves some tmp/backup files like .php~
and these files can be served as static, exposing your code or configuration, or any other sensitive data. There a lot of ways to fail if you put your code in public document root directory for static serving.
Moreover, such mess complicates configuration a lot.
Even if the app is so bad written and mix static files with the code files, there're ways how to fix that:
Having static media separated also allows you to serve it from different machine or easily offload to CDN, which nowadays pretty common.
@VBart I fully agree with you and your statements, the problem - the frameworks/CMS you've mentioned.
Even some of the ones having static files served in another directory - I haven't checked every app mentioned on https://unit.nginx.org/howto/#applications, is there any app which couldn't reach its own static files dir? If it could, that dir would be owned by the user, and it'd still suffer from the same issue. I've opened 3-4 of them to check, but.. all had static content "mixed". In most cases the app would be able to write to the static content dir I think, because if the app allows uploads locally (not talking about the cases where it uploads to external file server/storage) - write privilege is there, and any code injection would let create the symlink and access it over static content router.
Anyway, we're discussing good/bad practices here. I wish everyone used only good practises and isolate everything as much as they can on the app level, however, the reality is different, that's why things like linux namespaces, ACLs and other things are used. Maybe even 2FA authentication or spam filters wouldn't be needed nowadays if everyone would follow good practices and do the things right :)
@smtalk I agree with you and yes, we'd probably add similar to disable_symlinks
directive.
But serving static from the app processes is a nightmare from implementation point of view. In order to support Go/Node.js (and other possible stand-alone apps), we had to write a separate lightweight library (libunit
), that is loaded into apps and implements all the IPC protocol for passing requests and web-sockets. Thus, we can't use any arbitrary functions from Unit in runtime, especially after execv()
call: we don't have access to its config (to MIME types, for example) and very limited in what we can do. So it literally means writing and supporting another separate static file server almost from scratch.
That's why I suggest you just to do the same for your needs. Or just wait for disable_symlinks
.
@VBart disable_symlinks
is enough for the basic (and our) needs, if this can be added into the next releases - this would be awesome!
Static file-server as an app sounds great for additional protection (static file serving case), and for cases where customers might want user-owned process to proxy to some secure UDS, as currently the socket needs to be readable by the user unit is running as. Use case: I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others. Simple user ownership and 600 chmod would be sufficient to serve it over unit and control in Unit who and how can access it over http/https. So, adding it to some distant-future plans would be great, unless you see no point for it.
Thanks again.
I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others
This is solvable by the permissions scheme I mentioned in the comment https://github.com/nginx/unit/issues/505#issuecomment-734505896 above.
Your NodeJS will have its own personal group and user, then you can give access to the group without letting others to have access to it. Only Unit user will be in that group.
I want my NodeJS app to listen on a UDS, and I just want unit to proxy to it. I don't want that socket to be readable by others
This is solvable by the permissions scheme I mentioned in the comment #505 (comment) above.
Your NodeJS will have its own personal group and user, then you can give access to the group without letting others to have access to it. Only Unit user will be in that group.
Yes, but it's similar to solving "static content load" problem by using containers. I mean it's one of 'you can make things work another way' things.
Anyway, as I said - if you see no point there, just skip it :) It'd just open more possibilities for us and others, but workarounds are always possible.
Thanks, I guess case can be closed.
@smtalk chroot
, follow_symlinks
, and traverse_mounts
options were added in https://github.com/nginx/unit/commit/53279af5d44dce2b679399d6a36eb46292928175 (will be released as 1.24.0 by the end of the month). I assume that's one of the solutions you asked for.
@smtalk
chroot
,follow_symlinks
, andtraverse_mounts
options were added in 53279af (will be released as 1.24.0 by the end of the month). I assume that's one of the solutions you asked for.
Released with Unit 1.24.0. See the docs: https://unit.nginx.org/configuration/#path-restrictions
Closed because solved with a release.
Yes, I know, it sounds like a weird request. However, we'd like to be able to specify user/group for routes for security purposes.
Let's say we have a route:
If I have user-owned symlink:
lrwxrwxrwx 1 admin admin 11 Nov 26 21:14 index.html -> /etc/passwd
Or:
lrwxrwxrwx 1 admin admin 11 Nov 26 21:14 index.html -> /home/anoteruser/public_html/wp-config.php
It just returns me the content of the file in aaba.com/test. There is no such thing as "SymlinkIfOwnerMatch" (Apache thing) in unit at this time.
And it's only one of the problems. Another case can be:
"action": { "proxy": "http://unix://home/admin/nodejs.sock" }
It'd simply fail with "Permission denied", unless I make the socket readable by user "nobody". What if I'm user admin, and I don't want any other users to read my socket? (or at least I don't want "nobody" to have ability to read it)
Alternatively - let "static" content or proxy be an "app" (and route would just pass to it), that way it'd run under user/group + even be in chrooted namespace if configured.
I'll be waiting for your input. Thank you!