enable safer CGI in shared hosting environments #32

Open
opened 2022-06-13 14:09:28 +00:00 by tjp · 6 comments

My goal is to make CGI work in shared hosting environments where it's essentially untenable right now because spawned CGI programs execute as the same user as the molly-brown server.

The docs mention that go doesn't support setuid and "there is no way to work around this", but maybe with the right workaround go doesn't need to setuid. In the daemontools/runit/s6/etc approach to daemon management, a typical worker (say for tcpserver) would look something like:

chroot /path/to/public/files setuidgid unprivileged_user:unprivileged_group /actual/cgi/program

They make heavy use of wrapper programs like chroot or setuidgid which simply reduce privileges of their environment in various ways, and then exec(3) some other program.

This is of course possible today, but every individual cgi program has to initialize itself using priv-dropping wrappers like the above command, requiring "good behavior" from the users in the shared hosting environment.

As a first step to make admin-enforced privilege dropping at least possible, I propose a config variable CGICommand which, whenever a suitable cgi program is found for a URL, will be run in place of the cgi program itself. Crucially, it will still get the CGI environment variables, so it should be expected at some point to exec $SCRIPT_PATH.

Would you be open to a patch implementing this?

My goal is to make CGI work in shared hosting environments where it's essentially untenable right now because spawned CGI programs execute as the same user as the molly-brown server. The docs mention that go doesn't support setuid and "there is no way to work around this", but maybe _with the right workaround go doesn't need to setuid_. In the daemontools/runit/s6/etc approach to daemon management, a typical worker (say for [tcpserver](https://cr.yp.to/ucspi-tcp/tcpserver.html)) would look something like: ``` chroot /path/to/public/files setuidgid unprivileged_user:unprivileged_group /actual/cgi/program ``` They make heavy use of wrapper programs like chroot or setuidgid which simply reduce privileges of their environment in various ways, and then `exec(3)` some other program. This is of course possible today, but every individual cgi program has to initialize itself using priv-dropping wrappers like the above command, requiring "good behavior" from the users in the shared hosting environment. As a first step to make admin-enforced privilege dropping _at least possible_, I propose a config variable `CGICommand` which, whenever a suitable cgi program is found for a URL, will be run in place of the cgi program itself. Crucially, it will still get the CGI environment variables, so it should be expected at some point to `exec $SCRIPT_PATH`. Would you be open to a patch implementing this?
tjp changed title from enable safe CGI in shared hosting environments to enable safer CGI in shared hosting environments 2022-06-13 14:09:46 +00:00

Go supports setuid as of a little while ago:

https://go.dev/doc/go1.16

It may be possible to allow safer CGI execution now without resorting to tricksy implementations. It'd be nice to see this implemented, as CGI scripts are off right now in the tilde I'm at due to what I assume are general security concerns.

Go supports setuid as of a little while ago: https://go.dev/doc/go1.16 It may be possible to allow safer CGI execution now without resorting to tricksy implementations. It'd be nice to see this implemented, as CGI scripts are off right now in the tilde I'm at due to what I assume are general security concerns.
Owner

Hmm...this is an interesting workaround, but in order for the CGICommand to be able to chroot() or setuid() before doing exec $SCRIPT_PATH, it would need to be running as root, right? Which would mean Molly Brown would need to be running as root. That would probably make shared hosting admins less happy, not more!

The exception to this is on Linux where setcap could be used to "bless" the CGICommand binary to let it do those things even while running as an unprivileged user. I guess it has some value in that setting.

But as per the discussion in issue #16, setuid() is possible on Linux with Go 1.16 and later (and, it turns out, was possible on other unixes all along, which I failed to realise). I am working on taking advantage of that, which I guess reduces the motivation for this kind of "tricksy" workaround.

If you can make a good case for why this might still be useful even with setuid() now on the table I will consider it, but I think I would prefer the more conventional approach.

Hmm...this is an interesting workaround, but in order for the `CGICommand` to be able to `chroot()` or `setuid()` before doing `exec $SCRIPT_PATH`, it would need to be running as root, right? Which would mean Molly Brown would need to be running as root. That would probably make shared hosting admins *less* happy, not more! The exception to this is on Linux where `setcap` could be used to "bless" the `CGICommand` binary to let it do those things even while running as an unprivileged user. I guess it has some value in that setting. But as per the discussion in issue #16, `setuid()` is possible on Linux with Go 1.16 and later (and, it turns out, was possible on other unixes all along, which I failed to realise). I am working on taking advantage of that, which I guess reduces the motivation for this kind of "tricksy" workaround. If you can make a good case for why this might still be useful even with `setuid()` now on the table I will consider it, but I think I would prefer the more conventional approach.
Contributor

it would need to be running as root

I'm pretty sure you can use setuid without running the command as root. This is how sudo works I believe. See https://news.ycombinator.com/item?id=34455094, or more directly: https://en.wikipedia.org/wiki/Setuid

> it would need to be running as root I'm pretty sure you can use `setuid` without running the command as root. This is how `sudo` works I believe. See https://news.ycombinator.com/item?id=34455094, or more directly: https://en.wikipedia.org/wiki/Setuid
Owner

Ah, of course, the helper could be a setuid variable. Not sure how I overlooked that (have literally tested that Molly works nicely in that exact configuration!), sorry. Okay, this is definitely very viable, but I still think that a direct solution is preferable.

Ah, of course, the helper could be a setuid variable. Not sure how I overlooked that (have literally tested that Molly works nicely in that exact configuration!), sorry. Okay, this is definitely very viable, but I still think that a direct solution is preferable.
Author

What something like a CGICommand uniquely enables is a resource barrier that's specific to CGIs, and separate from the molly-brown server itself.

So for instance molly-brown could be running as an unprivileged user, but CGIs could still be enforced to execute as the user in whose directory they are found. As an unprivileged user, molly-brown can't run setuid any longer itself, but a root-owned CGICommand with the setuid bit and which drops privileges could accomplish this.

There are all kinds of ways an administrator might want to limit CGIs specifically, but that is by no means the same set of limitations they'll want to place on the whole molly-brown server.

What something like a `CGICommand` uniquely enables is a resource barrier that's specific to CGIs, and separate from the molly-brown server itself. So for instance molly-brown could be running as an unprivileged user, but CGIs could still be enforced to execute as the user in whose directory they are found. As an unprivileged user, molly-brown can't run setuid any longer itself, but a root-owned `CGICommand` with the setuid bit and which drops privileges could accomplish this. There are [all kinds of ways](http://smarden.org/runit/chpst.8.html#sect3) an administrator might want to limit CGIs specifically, but that is by no means the same set of limitations they'll want to place on the whole molly-brown server.
Owner

Okay, I am convinced that this feature would be valuable even once the other stuff I'm doing to address #16 is also finished. I'd be happy to accept a PR. Also happy to try to implement it myself but it may not be a top priority for a little while.

Okay, I am convinced that this feature would be valuable even once the other stuff I'm doing to address #16 is also finished. I'd be happy to accept a PR. Also happy to try to implement it myself but it may not be a top priority for a little while.
Sign in to join this conversation.
No Label
No Milestone
No Assignees
4 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: solderpunk/molly-brown#32
No description provided.