Просмотр исходного кода

Fix bad header filtering bypass (#8964)

* Fix bad header filtering bypass

* Sanitize every save using `_attribute('curl_params')`

* Sanitize every read using `attributeArray('curl_params')`
Inverle 9 часов назад
Родитель
Сommit
a3badc2d2e

+ 1 - 1
app/Controllers/feedController.php

@@ -221,7 +221,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
 			}
 
 			$attributes = [
-				'curl_params' => empty($opts) ? null : $opts,
+				'curl_params' => empty($opts) ? null : FreshRSS_http_Util::sanitizeCurlParams($opts),
 			];
 			$attributes['ssl_verify'] = Minz_Request::paramTernary('ssl_verify');
 			$timeout = Minz_Request::paramInt('timeout');

+ 1 - 1
app/Controllers/subscriptionController.php

@@ -216,7 +216,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 				$opts[CURLOPT_HTTPHEADER] = array_unique($opts[CURLOPT_HTTPHEADER]);
 			}
 
-			$feed->_attribute('curl_params', empty($opts) ? null : $opts);
+			$feed->_attribute('curl_params', empty($opts) ? null : FreshRSS_http_Util::sanitizeCurlParams($opts));
 
 			$feed->_attribute('content_action', Minz_Request::paramString('content_action', true) ?: 'replace');
 

+ 2 - 6
app/Models/Feed.php

@@ -106,12 +106,8 @@ class FreshRSS_Feed extends Minz_Model {
 	}
 
 	public function proxyParam(): string {
-		$curl_params = $this->attributeArray('curl_params');
-		if (is_array($curl_params)) {
-			// Content provided through a proxy may be completely different
-			return is_string($curl_params[CURLOPT_PROXY] ?? null) ? $curl_params[CURLOPT_PROXY] : '';
-		}
-		return '';
+		$curl_params = FreshRSS_http_Util::sanitizeCurlParams($this->attributeArray('curl_params') ?? []);
+		return is_string($curl_params[CURLOPT_PROXY] ?? null) ? $curl_params[CURLOPT_PROXY] : '';
 	}
 
 	/**

+ 1 - 1
app/Services/ImportService.php

@@ -324,7 +324,7 @@ class FreshRSS_Import_Service {
 				$curl_params[CURLOPT_USERAGENT] = $feed_elt['frss:CURLOPT_USERAGENT'];
 			}
 			if (!empty($curl_params)) {
-				$feed->_attribute('curl_params', $curl_params);
+				$feed->_attribute('curl_params', FreshRSS_http_Util::sanitizeCurlParams($curl_params));
 			}
 
 			// Call the extension hook

+ 5 - 3
app/Utils/httpUtil.php

@@ -129,6 +129,11 @@ final class FreshRSS_http_Util {
 			if ($k === CURLOPT_COOKIEFILE) {
 				$curl_params[$k] = '';
 			}
+			// Remove HTTP authentication headers problematic for security
+			if ($k === CURLOPT_HTTPHEADER && is_array($curl_params[$k])) {
+				$curl_params[$k] = array_filter($curl_params[$k],
+					fn($header) => is_string($header) && !preg_match('/^(Remote[-_\s]*User|X[-_\s]*WebAuth[-_\s]*User)\\s*:/i', $header));
+			}
 		}
 		return $curl_params;
 	}
@@ -443,9 +448,6 @@ final class FreshRSS_http_Util {
 			$options = self::sanitizeCurlParams($attributes['curl_params']);
 			$proxy = is_string($options[CURLOPT_PROXY] ?? null) ? $options[CURLOPT_PROXY] : $proxy;
 			if (is_array($options[CURLOPT_HTTPHEADER] ?? null)) {
-				// Remove headers problematic for security
-				$options[CURLOPT_HTTPHEADER] = array_filter($options[CURLOPT_HTTPHEADER],
-					fn($header) => is_string($header) && !preg_match('/^(Remote-User|X-WebAuth-User)\\s*:/i', $header));
 				// Add Accept header if it is not set
 				if (preg_grep('/^Accept\\s*:/i', $options[CURLOPT_HTTPHEADER]) === false) {
 					$options[CURLOPT_HTTPHEADER][] = 'Accept: ' . $accept;

+ 1 - 1
app/views/helpers/export/opml.phtml

@@ -114,7 +114,7 @@ function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
 			$outline['frss:cssContentFilter'] = $feed->attributeString('path_entries_filter');
 		}
 
-		$curl_params = $feed->attributeArray('curl_params');
+		$curl_params = FreshRSS_http_Util::sanitizeCurlParams($feed->attributeArray('curl_params') ?? []);
 		if (!empty($curl_params)) {
 			$outline['frss:CURLOPT_COOKIE'] = $curl_params[CURLOPT_COOKIE] ?? null;
 			$outline['frss:CURLOPT_COOKIEFILE'] = $curl_params[CURLOPT_COOKIEFILE] ?? null;

+ 8 - 12
app/views/helpers/feed/update.phtml

@@ -786,13 +786,12 @@
 
 			<div class="form-group">
 				<?php
-					/** @var array<int,int|string> $curlParams */
-					$curlParams = $this->feed->attributeArray('curl_params') ?? [];
+					$curlParams = FreshRSS_http_Util::sanitizeCurlParams($this->feed->attributeArray('curl_params') ?? []);
 				?>
 				<label class="group-name" for="curl_params_cookie"><?= _t('sub.feed.css_cookie') ?></label>
 				<div class="group-controls">
 					<input type="text" name="curl_params_cookie" id="curl_params_cookie" class="w100" value="<?=
-						htmlspecialchars((string)($curlParams[CURLOPT_COOKIE] ?? ''), ENT_COMPAT, 'UTF-8')
+						htmlspecialchars(is_string($curlParams[CURLOPT_COOKIE] ?? null) ? $curlParams[CURLOPT_COOKIE] : '', ENT_COMPAT, 'UTF-8')
 					?>" placeholder="<?= _t('gen.short.blank_to_disable') ?>" />
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.css_cookie_help') ?></p>
 					<label for="curl_params_cookiefile">
@@ -809,7 +808,7 @@
 				<label class="group-name" for="curl_params_redirects"><?= _t('sub.feed.max_http_redir') ?></label>
 				<div class="group-controls">
 					<input type="number" name="curl_params_redirects" id="curl_params_redirects" class="w50" min="-1" value="<?=
-						!empty($curlParams[CURLOPT_MAXREDIRS]) ? $curlParams[CURLOPT_MAXREDIRS] : ''
+						is_int($curlParams[CURLOPT_MAXREDIRS] ?? null) ? $curlParams[CURLOPT_MAXREDIRS] : ''
 					?>" placeholder="<?= _t('gen.short.blank_to_disable') ?>" />
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.max_http_redir_help') ?></p>
 				</div>
@@ -830,7 +829,7 @@
 				<label class="group-name" for="curl_params_useragent"><?= _t('sub.feed.useragent') ?></label>
 				<div class="group-controls">
 					<input type="text" name="curl_params_useragent" id="curl_params_useragent" class="w100" value="<?=
-						htmlspecialchars((string)($curlParams[CURLOPT_USERAGENT] ?? ''), ENT_COMPAT, 'UTF-8')
+						htmlspecialchars(is_string($curlParams[CURLOPT_USERAGENT] ?? null) ? $curlParams[CURLOPT_USERAGENT] : '', ENT_COMPAT, 'UTF-8')
 					?>" placeholder="<?= _t('gen.short.by_default') ?>" />
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.useragent_help') ?></p>
 				</div>
@@ -853,7 +852,7 @@
 					?>
 					</select>
 					<input type="text" name="curl_params" id="curl_params" value="<?=
-						htmlspecialchars((string)($curlParams[CURLOPT_PROXY] ?? ''), ENT_COMPAT, 'UTF-8')
+						htmlspecialchars(is_string($curlParams[CURLOPT_PROXY] ?? null) ? $curlParams[CURLOPT_PROXY] : '', ENT_COMPAT, 'UTF-8')
 					?>" placeholder="<?= _t('gen.short.by_default') ?>" />
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.proxy_help') ?></p>
 				</div>
@@ -864,7 +863,7 @@
 				<div class="group-controls">
 					<select class="number" name="curl_method" id="curl_method"><?php
 					$curl_method = 'GET';
-					if ($this->feed->attributeArray('curl_params') !== null && !empty($this->feed->attributeArray('curl_params')[CURLOPT_POST])) {
+					if (!empty($curlParams[CURLOPT_POST])) {
 						$curl_method = 'POST';
 					}
 					foreach (['GET' => 'GET', 'POST' => 'POST'] as $k => $v) {
@@ -874,7 +873,7 @@
 					</select>
 					<div class="stick">
 						<?php
-							$postFields = $this->feed->attributeArray('curl_params')[CURLOPT_POSTFIELDS] ?? '';
+							$postFields = $curlParams[CURLOPT_POSTFIELDS] ?? '';
 							if (!is_string($postFields)) {
 								$postFields = '';
 							}
@@ -909,14 +908,11 @@
 				<label class="group-name" for="http_headers"><?= _t('sub.feed.http_headers') ?></label>
 				<div class="group-controls">
 					<?php
-						$httpHeaders = $this->feed->attributeArray('curl_params')[CURLOPT_HTTPHEADER] ?? [];
+						$httpHeaders = $curlParams[CURLOPT_HTTPHEADER] ?? [];
 						if (!is_array($httpHeaders)) {
 							$httpHeaders = [];
 						}
 						$httpHeaders = array_filter($httpHeaders, 'is_string');
-						// Remove headers problematic for security
-						$httpHeaders = array_filter($httpHeaders,
-							fn(string $header) => !preg_match('/^(Remote-User|X-WebAuth-User)\\s*:/i', $header));
 					?>
 					<textarea class="w100" id="http_headers" name="http_headers" rows="3" spellcheck="false"><?php
 						foreach ($httpHeaders as $header) {