Compare commits

...

2 Commits

Author SHA1 Message Date
jf-cbd
baba2b414b Update branch with PR reviews 2026-10-06 17:35:19 +02:00
jf-cbd
a283804435 Allow protocols for post request 2026-10-06 15:38:36 +02:00
3 changed files with 73 additions and 0 deletions

View File

@@ -1886,6 +1886,9 @@ SQL;
$aHTTPHeaders[] = trim($sHeaderString);
}
// Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
$iCurlAllowedProtocols = self::ConvertProtocolsToCurlOption(
self::GetConfig()->Get('security.post_requests.allowed_protocols')
);
$aOptions = [
CURLOPT_RETURNTRANSFER => true, // return the content of the request
CURLOPT_HEADER => false, // don't return the headers in the output
@@ -1897,6 +1900,7 @@ SQL;
CURLOPT_TIMEOUT => 120, // timeout on response
CURLOPT_MAXREDIRS => 10, // stop after 10 redirects
CURLOPT_SSL_VERIFYPEER => false, // Disabled SSL Cert checks
CURLOPT_PROTOCOLS => $iCurlAllowedProtocols,
// SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why
// but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
// CURLOPT_SSLVERSION => 3,
@@ -1908,6 +1912,9 @@ SQL;
$aAllOptions = $aCurlOptions + $aOptions;
$ch = curl_init($sUrl);
curl_setopt_array($ch, $aAllOptions);
if (!curl_setopt($ch, CURLOPT_PROTOCOLS, $iCurlAllowedProtocols)) {
throw new Exception('Unable to enforce allowed protocols for cURL request');
}
$response = curl_exec($ch);
$iErr = curl_errno($ch);
$sErrMsg = curl_error($ch);
@@ -1929,6 +1936,40 @@ SQL;
return $response;
}
private static function ConvertProtocolsToCurlOption(string $sProtocols): int
{
$aProtocols = explode(',', $sProtocols);
$iCurlProtocols = 0;
foreach ($aProtocols as $sProtocol) {
$iCurlProtocols |= match (strtolower(trim($sProtocol))) {
'dict' => defined('CURLPROTO_DICT') ? CURLPROTO_DICT : 0,
'file' => defined('CURLPROTO_FILE') ? CURLPROTO_FILE : 0,
'ftp' => defined('CURLPROTO_FTP') ? CURLPROTO_FTP : 0,
'ftps' => defined('CURLPROTO_FTPS') ? CURLPROTO_FTPS : 0,
'gopher' => defined('CURLPROTO_GOPHER') ? CURLPROTO_GOPHER : 0,
'http' => defined('CURLPROTO_HTTP') ? CURLPROTO_HTTP : 0,
'https' => defined('CURLPROTO_HTTPS') ? CURLPROTO_HTTPS : 0,
'imap' => defined('CURLPROTO_IMAP') ? CURLPROTO_IMAP : 0,
'imaps' => defined('CURLPROTO_IMAPS') ? CURLPROTO_IMAPS : 0,
'ldap' => defined('CURLPROTO_LDAP') ? CURLPROTO_LDAP : 0,
'ldaps' => defined('CURLPROTO_LDAPS') ? CURLPROTO_LDAPS : 0,
'mqtt' => defined('CURLPROTO_MQTT') ? CURLPROTO_MQTT : 0,
'pop3' => defined('CURLPROTO_POP3') ? CURLPROTO_POP3 : 0,
'pop3s' => defined('CURLPROTO_POP3S') ? CURLPROTO_POP3S : 0,
'rtsp' => defined('CURLPROTO_RTSP') ? CURLPROTO_RTSP : 0,
'scp' => defined('CURLPROTO_SCP') ? CURLPROTO_SCP : 0,
'sftp' => defined('CURLPROTO_SFTP') ? CURLPROTO_SFTP : 0,
'smtp' => defined('CURLPROTO_SMTP') ? CURLPROTO_SMTP : 0,
'smtps' => defined('CURLPROTO_SMTPS') ? CURLPROTO_SMTPS : 0,
'telnet' => defined('CURLPROTO_TELNET') ? CURLPROTO_TELNET : 0,
'tftp' => defined('CURLPROTO_TFTP') ? CURLPROTO_TFTP : 0,
default => throw new Exception("Unsupported protocol: $sProtocol"),
};
}
return $iCurlProtocols;
}
/**
* Get a standard list of character sets
*

View File

@@ -1771,6 +1771,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.post_requests.allowed_protocols' => [
'type' => 'string',
'description' => 'List of allowed protocols that will be used for post requests. Allowed values: dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp',
'default' => 'http,https,ftp,ftps,sftp',
'value' => 'http,https,ftp,ftps,sftp',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',

View File

@@ -72,6 +72,30 @@ class utilsTest extends ItopTestCase
];
}
/**
* @dataProvider DoPostRequestAllowedProtocolsProvider
*/
public function testDoPostRequestRespectsAllowedProtocolsConfig(string $sAllowedProtocols, bool $bFileProtocolAllowed): void
{
// Config::Set changes the in-memory setting only; the test framework reloads config after this test class.
utils::GetConfig()->Set('security.post_requests.allowed_protocols', $sAllowedProtocols);
if (!$bFileProtocolAllowed) {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Problem opening URL');
} else {
$this->expectNotToPerformAssertions();
}
utils::DoPostRequest('file:///tmp', ['payload' => 'test']);
}
public function DoPostRequestAllowedProtocolsProvider(): array
{
return [
'file protocol is allowed' => ['file,http,https', true],
'file protocol is not allowed' => ['http,https', false],
];
}
/**
* @dataProvider realPathDataProvider
* @covers utils::RealPath()