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.
This commit is contained in:
Johannes Feichtner
2015-09-16 00:42:11 +02:00
parent 6e0f18b200
commit a641dfbfc8
2 changed files with 17 additions and 1 deletions

View File

@@ -271,6 +271,8 @@ class nginx extends HttpConfigBase {
&& !is_dir(Settings::Get('system.apacheconf_vhost'))) && !is_dir(Settings::Get('system.apacheconf_vhost')))
|| 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 // Create non-ssl host
$this->nginx_data[$vhost_filename].= $this->getVhostContent($domain, false); $this->nginx_data[$vhost_filename].= $this->getVhostContent($domain, false);
if ($domain['ssl'] == '1' || $domain['ssl_redirect'] == '1') { if ($domain['ssl'] == '1' || $domain['ssl_redirect'] == '1') {
@@ -681,6 +683,9 @@ class nginx extends HttpConfigBase {
if ($single['path'] == '/') { if ($single['path'] == '/') {
$path_options .= "\t\t" . 'auth_basic "' . $single['authname'] . '";' . "\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" . '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 // remove already used entries so we do not have doubles
unset($htpasswds[$idx]); unset($htpasswds[$idx]);
} }
@@ -741,6 +746,9 @@ class nginx extends HttpConfigBase {
$path_options .= "\t" . 'location ' . makeCorrectDir($single['path']) . ' {' . "\n"; $path_options .= "\t" . 'location ' . makeCorrectDir($single['path']) . ' {' . "\n";
$path_options .= "\t\t" . 'auth_basic "' . $single['authname'] . '";' . "\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" . '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"; $path_options .= "\t".'}' . "\n";
} }
//} //}
@@ -804,7 +812,11 @@ class nginx extends HttpConfigBase {
protected function composePhpOptions($domain, $ssl_vhost = false) { protected function composePhpOptions($domain, $ssl_vhost = false) {
$phpopts = ''; $phpopts = '';
if ($domain['phpenabled'] == '1') { 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\tfastcgi_split_path_info ^(.+\.php)(/.+)\$;\n";
$phpopts .= "\t\tinclude ".Settings::Get('nginx.fastcgiparams').";\n"; $phpopts .= "\t\tinclude ".Settings::Get('nginx.fastcgiparams').";\n";
$phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;\n"; $phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;\n";

View File

@@ -25,6 +25,10 @@ class nginx_phpfpm extends nginx
$phpconfig = $php->getPhpConfig((int)$domain['phpsettingid']); $phpconfig = $php->getPhpConfig((int)$domain['phpsettingid']);
$php_options_text = "\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n"; $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" . 'try_files $1 = 404;' . "\n\n";
$php_options_text .= "\t\t" . 'include ' . Settings::Get('nginx.fastcgiparams') . ";\n"; $php_options_text .= "\t\t" . 'include ' . Settings::Get('nginx.fastcgiparams') . ";\n";
$php_options_text .= "\t\t" . 'fastcgi_split_path_info ^(.+\.php)(/.+)\$;' . "\n"; $php_options_text .= "\t\t" . 'fastcgi_split_path_info ^(.+\.php)(/.+)\$;' . "\n";