Created
February 9, 2017 12:31
-
-
Save gagern/00c6168af466964022715737fd41ea61 to your computer and use it in GitHub Desktop.
Run apache as non-root on privileged port
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2013 Martin von Gagern | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
/* DOCUMENTATION | |
* | |
* Grant permissions to this file via | |
* setcap CAP_SETFCAP,CAP_FOWNER,CAP_NET_BIND_SERVICE+p <filename> | |
* | |
* This file can serve as a wrapper around apache to allow apache | |
* processes run as non-root users to bind to privileged ports. In | |
* the normal course of events, this process will make the required | |
* capability CAP_NET_BIND_SERVICE effective before executing the | |
* apache binary (passing along command line arguments). Apache will | |
* inherit this capability and therefore be able to bind to port 80. | |
* | |
* For the above to work, the apache binary itself must have the | |
* CAP_NET_BIND_SERVICE capability in its CAP_INHERITABLE and | |
* CAP_EFFECTIVE sets. Otherwise, the capability won't be inherited, | |
* or won't get activated automatically. Since a package upgrade might | |
* break any marks the system administrator manually placed on the | |
* apache binary, this wrapper here is designed to check the binary | |
* for the presence of the required flags, and to add them if they are | |
* missing. This is the reason why this wrapper should be marked with | |
* CAP_SETFCAP (to change file capabilities) and CAP_FOWNER (to | |
* circumvent ownership tests in doing so) as well, as stated above. | |
* | |
* Benefits over a simple call like | |
* setcap CAP_NET_BIND_SERVICE+pe /usr/sbin/apache2 | |
* are the fact that this approach here integrates better with | |
* automatic upgrades, since it can re-add the required flags | |
* automatically. Furthermore, the administrator can be more | |
* restrictive in granting execute permissions to this wrapper, | |
* without fear of breaking other uses of the apache2 binary, or | |
* loosing those changed permissions upon package update. | |
* | |
* Known security implications: | |
* | |
* - CAP_NET_BIND_SERVICE will be active throughout the Apache | |
* process. The normal mechanism of dropping privileges via a setuid | |
* call won't apply since the UID is non-zero to start with. Apache | |
* currently has no code to drop capabilities if run as non-root. | |
* Bits of code run in the apache process, like e.g. a PHP script, | |
* might explot this to run malicious services on privileged ports. | |
* To avoid this, a patched version of Apache is required, which | |
* drops capabilities instead of calling suid when run as non-root. | |
* | |
* - The wrapper will automatically add CAP_NET_BIND_SERVICE to the | |
* inheritable set of the apache binary. If some other process, by | |
* some other means, gained that capability, it too would be allowed | |
* to execute the apache binary with this capability in place. So | |
* any execute restrictions placed on this wrapper here might be | |
* circumvented that way. To fix this, the wrapper could be extended | |
* to automatically restrict execute permissions on the apache | |
* binary to those of the wrapper itself, but this would be going | |
* pretty far for an automatic operation, and might cause ugly | |
* surprises. In cases where users can launch arbitrary binaries | |
* with the CAP_NET_BIND_SERVICE capability in their inheritable | |
* set, the setcap call on the apache binary itself might be | |
* preferable, even though it means more work for the system | |
* administrator. | |
*/ | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <errno.h> | |
#include <sys/capability.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
/* Error checking, reporting and program termination. | |
* Error messages will contain source file and line number. | |
*/ | |
#define SRCLOC2(file, line) (file ":" #line) | |
#define SRCLOC1(file, line) SRCLOC2(file, line) | |
#define CHK(call, errval) \ | |
do { if ((call) == errval) { \ | |
perror(SRCLOC1(__FILE__, __LINE__)); \ | |
exit(EXIT_FAILURE); \ | |
} } while (0) | |
/* Location of the binary to wrap. */ | |
const char apache[] = "/usr/sbin/apache2"; | |
const cap_value_t net_bind_service[] = { CAP_NET_BIND_SERVICE }; | |
const cap_value_t change_fcaps[] = { CAP_SETFCAP, CAP_FOWNER }; | |
/* Check file capabilities of apache binary, fix them if required. */ | |
static void setcap() { | |
int fd; | |
cap_t fcaps; | |
cap_flag_value_t valI, valE; | |
/* We use a file descriptor, to avoid races, and to automatically | |
resolve symlinks. O_PATH would seem nicer than O_RDONLY, but | |
doesn't work in experiments. */ | |
CHK(fd = open(apache, O_RDONLY), -1); | |
fcaps = cap_get_fd(fd); | |
if (fcaps == NULL) { | |
if (errno == ENODATA) { /* This is the one error we expect and handle */ | |
CHK(fcaps = cap_init(), NULL); /* new empty set of capabilities */ | |
} | |
else { | |
CHK(NULL, NULL); /* Will report the error */ | |
} | |
} | |
CHK(cap_get_flag(fcaps, CAP_NET_BIND_SERVICE, CAP_INHERITABLE, &valI), -1); | |
CHK(cap_get_flag(fcaps, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &valE), -1); | |
if (valI != CAP_SET || valE != CAP_SET) { | |
/* We have to change file caps, and need some effective caps to do so */ | |
cap_t pcaps1, pcaps2; | |
CHK(cap_set_flag(fcaps, CAP_INHERITABLE, 1, net_bind_service, CAP_SET), -1); | |
CHK(cap_set_flag(fcaps, CAP_EFFECTIVE, 1, net_bind_service, CAP_SET), -1); | |
CHK(pcaps1 = cap_get_proc(), NULL); | |
CHK(pcaps2 = cap_dup(pcaps1), NULL); | |
CHK(cap_set_flag(pcaps2, CAP_EFFECTIVE, 2, change_fcaps, CAP_SET), -1); | |
CHK(cap_set_proc(pcaps2), -1); /* gain change_fcaps */ | |
cap_free(pcaps2); | |
CHK(cap_set_fd(fd, fcaps), -1); /* actually change the file caps */ | |
CHK(cap_set_proc(pcaps1), -1); /* drop change_fcaps again */ | |
cap_free(pcaps1); | |
} | |
cap_free(fcaps); | |
close(fd); | |
} | |
int main(int argc, char* argv[]) { | |
cap_t pcaps; | |
setcap(); | |
CHK(argv[0] = strdup(apache), NULL); | |
argv[argc] = NULL; | |
CHK(pcaps = cap_init(), NULL); /* don't keep unneccessary caps */ | |
CHK(cap_set_flag(pcaps, CAP_INHERITABLE, 1, net_bind_service, CAP_SET), -1); | |
CHK(cap_set_proc(pcaps), -1); /* inherit net_bind_service, drop rest */ | |
CHK(execv(apache, argv), -1); /* execute apache, should not return */ | |
return EXIT_FAILURE; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment