From a641dfbfc8bb11c15ee1c591e69f57d0bb326e9c Mon Sep 17 00:00:00 2001 From: Johannes Feichtner Date: Wed, 16 Sep 2015 00:42:11 +0200 Subject: [PATCH] Security-critical fix: Nginx directory protection did not prevent access to PHP scripts Although the implemented direction protection posed a prompt when accessing the http://...com/protectedir/ it was still possible to call http://...com/protectedir/script.php This vulnerability emerges from the precedence order of "location" statements. The RegEx matching the PHP script is triggered before the directory protection is evaluated. As a result, the PHP script is interpreted and path parsing stops due to the circumflex (see http://nginx.org/en/docs/http/ngx_http_core_module.html#location). The fix involves adding a PHP parsing snippet to every protected block. In order to prevent PHP-related config params repeatedly, the required section is referenced using a prefix. --- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 14 +++++++++++++- .../jobs/cron_tasks.inc.http.35.nginx_phpfpm.php | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index 4bff60a1..bf55350e 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -271,6 +271,8 @@ class nginx extends HttpConfigBase { && !is_dir(Settings::Get('system.apacheconf_vhost'))) || is_dir(Settings::Get('system.apacheconf_vhost')) ) { + $domain['nonexistinguri'] = '/' . md5(uniqid(microtime(), 1)) . '.htm'; + // Create non-ssl host $this->nginx_data[$vhost_filename].= $this->getVhostContent($domain, false); if ($domain['ssl'] == '1' || $domain['ssl_redirect'] == '1') { @@ -681,6 +683,9 @@ class nginx extends HttpConfigBase { if ($single['path'] == '/') { $path_options .= "\t\t" . 'auth_basic "' . $single['authname'] . '";' . "\n"; $path_options .= "\t\t" . 'auth_basic_user_file ' . makeCorrectFile($single['usrf']) . ';'."\n"; + $path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n"; + $path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n"; + $path_options .= "\t\t" . '}' . "\n"; // remove already used entries so we do not have doubles unset($htpasswds[$idx]); } @@ -741,6 +746,9 @@ class nginx extends HttpConfigBase { $path_options .= "\t" . 'location ' . makeCorrectDir($single['path']) . ' {' . "\n"; $path_options .= "\t\t" . 'auth_basic "' . $single['authname'] . '";' . "\n"; $path_options .= "\t\t" . 'auth_basic_user_file ' . makeCorrectFile($single['usrf']) . ';'."\n"; + $path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n"; + $path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n"; + $path_options .= "\t\t" . '}' . "\n"; $path_options .= "\t".'}' . "\n"; } //} @@ -804,7 +812,11 @@ class nginx extends HttpConfigBase { protected function composePhpOptions($domain, $ssl_vhost = false) { $phpopts = ''; if ($domain['phpenabled'] == '1') { - $phpopts = "\tlocation ~ \.php {\n"; + $phpopts = "\tlocation ~ \.php {\n"; + $phpopts .= "\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n"; + $phpopts .= "\t" . '}' . "\n\n"; + + $phpopts .= "\tlocation @php {\n"; $phpopts .= "\t\tfastcgi_split_path_info ^(.+\.php)(/.+)\$;\n"; $phpopts .= "\t\tinclude ".Settings::Get('nginx.fastcgiparams').";\n"; $phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;\n"; diff --git a/scripts/jobs/cron_tasks.inc.http.35.nginx_phpfpm.php b/scripts/jobs/cron_tasks.inc.http.35.nginx_phpfpm.php index dbf29212..f47ccd82 100644 --- a/scripts/jobs/cron_tasks.inc.http.35.nginx_phpfpm.php +++ b/scripts/jobs/cron_tasks.inc.http.35.nginx_phpfpm.php @@ -25,6 +25,10 @@ class nginx_phpfpm extends nginx $phpconfig = $php->getPhpConfig((int)$domain['phpsettingid']); $php_options_text = "\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n"; + $php_options_text .= "\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n"; + $php_options_text .= "\t" . '}' . "\n\n"; + + $php_options_text .= "\t" . 'location @php {' . "\n"; $php_options_text .= "\t\t" . 'try_files $1 = 404;' . "\n\n"; $php_options_text .= "\t\t" . 'include ' . Settings::Get('nginx.fastcgiparams') . ";\n"; $php_options_text .= "\t\t" . 'fastcgi_split_path_info ^(.+\.php)(/.+)\$;' . "\n";