Use Linux' capability system to run as a sort of "lesser root"; we drop nearly all root privilegies except the ability to setuid. An attacker capable of injecting code will still be able to run as any (normal) user on the system, but at least he/she cannot directly load kernel code etc. Index: httpd-2.4.2/server/mpm/config2.m4 =================================================================== --- httpd-2.4.2.orig/server/mpm/config2.m4 +++ httpd-2.4.2/server/mpm/config2.m4 @@ -66,6 +66,9 @@ for i in $ap_enabled_mpms; do else AC_MSG_ERROR([MPM $i is not supported on this platform.]) fi + if test "$i" = "itk" ; then + AC_CHECK_LIB(cap, cap_init) + fi done if test $mpm_build = "shared"; then Index: httpd-2.4.2/server/mpm/itk/itk.c =================================================================== --- httpd-2.4.2.orig/server/mpm/itk/itk.c +++ httpd-2.4.2/server/mpm/itk/itk.c @@ -67,6 +67,11 @@ #include /* for bindprocessor() */ #endif +#if HAVE_LIBCAP +#include +#include +#endif + #include #include @@ -519,6 +524,15 @@ static void child_main(int child_num_arg int last_poll_idx = 0; const char *lockfile; +#if HAVE_LIBCAP + cap_t caps; + cap_value_t suidcaps[] = { + CAP_SETUID, + CAP_SETGID, + CAP_DAC_READ_SEARCH, + }; +#endif + mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this * child initializes */ @@ -561,6 +575,29 @@ static void child_main(int child_num_arg clean_child_exit(APEXIT_CHILDFATAL); } +#if HAVE_LIBCAP + /* Drop down to a normal user. This means that even if an attacker + * manage to execute code before setuid(), he/she cannot write to + * files owned by uid 0, such as /etc/crontab. We'll keep our extra + * privileges, though, since we need them for the actual + * query processing later. + * + * Note that since we still have CAP_SETUID, an attacker can setuid(0) + * and get around this. + */ + if (prctl(PR_SET_KEEPCAPS, 1)) { + ap_log_error(APLOG_MARK, APLOG_EMERG, errno, NULL, "prctl(PR_SET_KEEPCAPS, 1) failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + if (prctl(PR_SET_KEEPCAPS, 0)) { + ap_log_error(APLOG_MARK, APLOG_EMERG, errno, NULL, "prctl(PR_SET_KEEPCAPS, 0) failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } +#endif + ap_run_child_init(pchild, ap_server_conf); ap_create_sb_handle(&sbh, pchild, my_child_num, 0); @@ -593,6 +630,22 @@ static void child_main(int child_num_arg lr->accept_func = ap_unixd_accept; } +#if HAVE_LIBCAP + /* Drop as many privileges as we can. We'll still + * access files with uid=0, and we can setuid() to anything, but + * at least there's tons of other evilness (like loading kernel + * modules) we can't do directly. (The setuid() capability will + * go away automatically when we setuid() or exec() -- the former + * is likely to come first.) + */ + caps = cap_init(); + cap_clear(caps); + cap_set_flag(caps, CAP_PERMITTED, sizeof(suidcaps)/sizeof(cap_value_t), suidcaps, CAP_SET); + cap_set_flag(caps, CAP_EFFECTIVE, sizeof(suidcaps)/sizeof(cap_value_t), suidcaps, CAP_SET); + cap_set_proc(caps); + cap_free(caps); +#endif + mpm_state = AP_MPMQ_RUNNING; bucket_alloc = apr_bucket_alloc_create(pchild); @@ -1523,6 +1576,17 @@ static int itk_post_perdir_config(reques _DBG("setuid(%d): %s", wanted_uid, strerror(errno)); err = 1; } else { +#if HAVE_LIBCAP + /* Drop our remaining privileges. Normally setuid() would do this + * for us, but since we were previously not uid 0 (just a normal + * user with CAP_SETUID), we need to do it ourselves. + */ + cap_t caps; + caps = cap_init(); + cap_clear(caps); + cap_set_proc(caps); + cap_free(caps); +#endif ap_has_irreversibly_setuid = 1; } }