From 2f0a379c03aa152c3aba0193efd94eed2dbbe6b9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Loyet?= Date: Tue, 12 Jul 2011 23:00:42 +0000 Subject: [PATCH] - Implemented FR #55181 (Enhance security by limiting access to user defined extensions) --- NEWS | 2 ++ sapi/fpm/fpm/fpm_conf.c | 53 ++++++++++++++++++++++++++++++++++ sapi/fpm/fpm/fpm_conf.h | 1 + sapi/fpm/fpm/fpm_main.c | 6 ++++ sapi/fpm/fpm/fpm_php.c | 34 ++++++++++++++++++++++ sapi/fpm/fpm/fpm_php.h | 1 + sapi/fpm/fpm/fpm_worker_pool.h | 1 + sapi/fpm/php-fpm.conf.in | 8 +++++ 8 files changed, 106 insertions(+) diff --git a/NEWS b/NEWS index 4cdc725603..2fd5ee1634 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,8 @@ - Improved PHP-FPM SAPI: . Added partial syslog support (on error_log only). FR #52052. (fat) . Lowered default value for Process Manager. FR #54098. (fat) + . Enhance security by limiting access to user defined extensions. + FR #55181. (fat) - Improved core functions: . Changed http_response_code() to be able to set a response code. (Kalle) diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index a7ad4d2551..2f28fb9465 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -121,6 +121,7 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = { { "ping.response", &fpm_conf_set_string, WPO(ping_response) }, { "access.log", &fpm_conf_set_string, WPO(access_log) }, { "access.format", &fpm_conf_set_string, WPO(access_format) }, + { "security.limit_extensions", &fpm_conf_set_string, WPO(security_limit_extensions) }, { 0, 0, 0 } }; @@ -599,6 +600,7 @@ int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc) /* {{{ */ free(wpc->prefix); free(wpc->access_log); free(wpc->access_format); + free(wpc->security_limit_extensions); return 0; } @@ -845,6 +847,56 @@ static int fpm_conf_process_all_pools() /* {{{ */ } } + if (!wp->config->security_limit_extensions) { + wp->config->security_limit_extensions = strdup(".php"); + } + + if (*wp->config->security_limit_extensions) { + int nb_ext; + char *ext; + char *security_limit_extensions; + char *limit_extensions; + + + /* strdup because strtok(3) alters the string it parses */ + security_limit_extensions = strdup(wp->config->security_limit_extensions); + limit_extensions = security_limit_extensions; + nb_ext = 0; + + /* find the number of extensions */ + while ((ext = strtok(limit_extensions, " \t"))) { + limit_extensions = NULL; + nb_ext++; + } + free(security_limit_extensions); + + /* if something found */ + if (nb_ext > 0) { + + /* malloc the extension array */ + wp->limit_extensions = malloc(sizeof(char *) * (nb_ext + 1)); + if (!wp->limit_extensions) { + zlog(ZLOG_ERROR, "[pool %s] unable to malloc extensions array", wp->config->name); + return -1; + } + + /* strdup because strtok(3) alters the string it parses */ + security_limit_extensions = strdup(wp->config->security_limit_extensions); + limit_extensions = security_limit_extensions; + nb_ext = 0; + + /* parse the string and save the extension in the array */ + while ((ext = strtok(security_limit_extensions, " \t"))) { + security_limit_extensions = NULL; + wp->limit_extensions[nb_ext++] = strdup(ext); + } + + /* end the array with NULL in order to parse it */ + wp->limit_extensions[nb_ext] = NULL; + free(security_limit_extensions); + } + } + if (wp->config->chroot && *wp->config->chroot) { fpm_evaluate_full_path(&wp->config->chroot, wp, NULL, 1); @@ -1380,6 +1432,7 @@ static void fpm_conf_dump() /* {{{ */ zlog(ZLOG_NOTICE, "\tslowlog = %s", STR2STR(wp->config->slowlog)); zlog(ZLOG_NOTICE, "\trlimit_files = %d", wp->config->rlimit_files); zlog(ZLOG_NOTICE, "\trlimit_core = %d", wp->config->rlimit_core); + zlog(ZLOG_NOTICE, "\tsecurity.limit_extensions = %s", wp->config->security_limit_extensions); for (kv = wp->config->env; kv; kv = kv->next) { zlog(ZLOG_NOTICE, "\tenv[%s] = %s", kv->key, kv->value); diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h index 87a5cd332b..d19c2b61f5 100644 --- a/sapi/fpm/fpm/fpm_conf.h +++ b/sapi/fpm/fpm/fpm_conf.h @@ -66,6 +66,7 @@ struct fpm_worker_pool_config_s { char *listen_group; char *listen_mode; char *listen_allowed_clients; + char *security_limit_extensions; struct key_value_s *env; struct key_value_s *php_admin_values; struct key_value_s *php_values; diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index 7dbd6c00c9..06370d855c 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -1879,6 +1879,12 @@ consult the installation file that came with this distribution, or visit \n\ goto fastcgi_request_done; } + if (fpm_php_limit_extensions(SG(request_info).path_translated)) { + SG(sapi_headers).http_response_code = 403; + PUTS("Access denied.\n"); + goto fastcgi_request_done; + } + /* path_translated exists, we can continue ! */ if (php_fopen_primary_script(&file_handle TSRMLS_CC) == FAILURE) { zend_try { diff --git a/sapi/fpm/fpm/fpm_php.c b/sapi/fpm/fpm/fpm_php.c index 33b9e410e1..f320f5148e 100644 --- a/sapi/fpm/fpm/fpm_php.c +++ b/sapi/fpm/fpm/fpm_php.c @@ -19,6 +19,9 @@ #include "fpm_php.h" #include "fpm_cleanup.h" #include "fpm_worker_pool.h" +#include "zlog.h" + +static char **limit_extensions = NULL; static int fpm_php_zend_ini_alter_master(char *name, int name_length, char *new_value, int new_value_length, int mode, int stage TSRMLS_DC) /* {{{ */ { @@ -219,7 +222,38 @@ int fpm_php_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ 0 > fpm_php_set_allowed_clients(wp)) { return -1; } + + if (wp->limit_extensions) { + limit_extensions = wp->limit_extensions; + } return 0; } /* }}} */ +int fpm_php_limit_extensions(char *path) /* {{{ */ +{ + char **p; + size_t path_len; + + if (!path || !limit_extensions) { + return 0; /* allowed by default */ + } + + p = limit_extensions; + path_len = strlen(path); + while (p && *p) { + size_t ext_len = strlen(*p); + if (path_len > ext_len) { + char *path_ext = path + path_len - ext_len; + if (strcmp(*p, path_ext) == 0) { + return 0; /* allow as the extension has been found */ + } + } + p++; + } + + + zlog(ZLOG_NOTICE, "Access to the file '%s' has been denied (see security.limit_extensions)", path); + return 1; /* extension not found: not allowed */ +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_php.h b/sapi/fpm/fpm/fpm_php.h index 62a47e7fd3..a2c7ed318a 100644 --- a/sapi/fpm/fpm/fpm_php.h +++ b/sapi/fpm/fpm/fpm_php.h @@ -43,6 +43,7 @@ size_t fpm_php_content_length(TSRMLS_D); void fpm_php_soft_quit(); int fpm_php_init_main(); int fpm_php_apply_defines_ex(struct key_value_s *kv, int mode); +int fpm_php_limit_extensions(char *path); #endif diff --git a/sapi/fpm/fpm/fpm_worker_pool.h b/sapi/fpm/fpm/fpm_worker_pool.h index 098def5b7e..100c3689cb 100644 --- a/sapi/fpm/fpm/fpm_worker_pool.h +++ b/sapi/fpm/fpm/fpm_worker_pool.h @@ -37,6 +37,7 @@ struct fpm_worker_pool_s { #endif struct fpm_scoreboard_s *scoreboard; int log_fd; + char **limit_extensions; }; struct fpm_worker_pool_s *fpm_worker_pool_alloc(); diff --git a/sapi/fpm/php-fpm.conf.in b/sapi/fpm/php-fpm.conf.in index b3151c0a99..8c9d4bb406 100644 --- a/sapi/fpm/php-fpm.conf.in +++ b/sapi/fpm/php-fpm.conf.in @@ -421,6 +421,14 @@ pm.max_spare_servers = 3 ; process time (several ms). ; Default Value: no ;catch_workers_output = yes + +; Limits the extensions of the main script FPM will allow to parse. This can +; prevent configuration mistakes on the web server side. You should only limit +; FPM to .php extensions to prevent malicious users to use other extensions to +; exectute php code. +; Note: set an empty value to allow all extensions. +; Default Value: .php +;security.limit_extensions = .php .php3 .php4 .php5 ; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from ; the current environment. -- 2.40.0