mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Fix bug #74796: Requests through http proxy set peer name
This issue happens because http wrapper sets peer_name but then does not remove so it stays in the context. The fix removes the peer name from the context after enabling crypto. In addition to bug #74796, this also fixes bug #76196. In addition it should be a final fix for those SOAP bugs: bug #69783 bug #52913 bug #61463
This commit is contained in:
parent
e13ba36abb
commit
42f6c15186
5 changed files with 199 additions and 1 deletions
2
NEWS
2
NEWS
|
@ -22,6 +22,8 @@ PHP NEWS
|
|||
- OpenSSL:
|
||||
. Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure.
|
||||
(nielsdos)
|
||||
. Fixed bug #74796 (Requests through http proxy set peer name).
|
||||
(Jakub Zelenka)
|
||||
|
||||
- Phar:
|
||||
. Add missing filter cleanups on phar failure. (nielsdos)
|
||||
|
|
175
ext/openssl/tests/bug74796.phpt
Normal file
175
ext/openssl/tests/bug74796.phpt
Normal file
|
@ -0,0 +1,175 @@
|
|||
--TEST--
|
||||
Bug #74796: TLS encryption fails behind HTTP proxy
|
||||
--EXTENSIONS--
|
||||
openssl
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!function_exists("proc_open")) die("skip no proc_open");
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$serverCode = <<<'CODE'
|
||||
$serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
|
||||
$ctx = stream_context_create(['ssl' => [
|
||||
'SNI_server_certs' => [
|
||||
"cs.php.net" => __DIR__ . "/sni_server_cs.pem",
|
||||
"uk.php.net" => __DIR__ . "/sni_server_uk.pem",
|
||||
"us.php.net" => __DIR__ . "/sni_server_us.pem"
|
||||
]
|
||||
]]);
|
||||
|
||||
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $serverFlags, $ctx);
|
||||
phpt_notify_server_start($server);
|
||||
|
||||
for ($i=0; $i < 3; $i++) {
|
||||
$conn = stream_socket_accept($server, 3);
|
||||
fwrite($conn, "HTTP/1.0 200 OK\r\n\r\nHello from server $i");
|
||||
fclose($conn);
|
||||
}
|
||||
|
||||
phpt_wait();
|
||||
CODE;
|
||||
|
||||
$proxyCode = <<<'CODE'
|
||||
function parse_sni_from_client_hello($data) {
|
||||
$sni = null;
|
||||
|
||||
if (strlen($data) < 5 || ord($data[0]) != 0x16) return null;
|
||||
|
||||
$session_id_len = ord($data[43]);
|
||||
$ptr = 44 + $session_id_len;
|
||||
|
||||
// Cipher suites length
|
||||
$cipher_suites_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
|
||||
$ptr += 2 + $cipher_suites_len;
|
||||
|
||||
// Compression methods length
|
||||
$compression_methods_len = ord($data[$ptr]);
|
||||
$ptr += 1 + $compression_methods_len;
|
||||
|
||||
// Extensions length
|
||||
if ($ptr + 2 > strlen($data)) return null;
|
||||
$extensions_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
|
||||
$ptr += 2;
|
||||
|
||||
$extensions_end = $ptr + $extensions_len;
|
||||
|
||||
while ($ptr + 4 <= $extensions_end) {
|
||||
$ext_type = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
|
||||
$ext_len = (ord($data[$ptr+2]) << 8) | ord($data[$ptr+3]);
|
||||
$ptr += 4;
|
||||
|
||||
if ($ext_type === 0x00) { // SNI extension
|
||||
if ($ptr + 2 > strlen($data)) break;
|
||||
$name_list_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
|
||||
$ptr += 2;
|
||||
|
||||
if ($ptr + 3 > strlen($data)) break;
|
||||
$name_type = ord($data[$ptr]);
|
||||
$name_len = (ord($data[$ptr+1]) << 8) | ord($data[$ptr+2]);
|
||||
$ptr += 3;
|
||||
|
||||
if ($name_type === 0) { // host_name type
|
||||
$sni = substr($data, $ptr, $name_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$ptr += $ext_len;
|
||||
}
|
||||
|
||||
return $sni;
|
||||
}
|
||||
|
||||
$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
|
||||
$server = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, $flags);
|
||||
phpt_notify_server_start($server);
|
||||
|
||||
for ($i=0; $i < 3; $i++) {
|
||||
$upstream = stream_socket_client("tcp://{{ ADDR }}", $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT);
|
||||
stream_set_blocking($upstream, false);
|
||||
|
||||
$conn = stream_socket_accept($server);
|
||||
stream_set_blocking($conn, true);
|
||||
|
||||
// reading CONNECT request headers
|
||||
while (($line = fgets($conn)) !== false) {
|
||||
if (rtrim($line) === '') break; // empty line means end of headers
|
||||
}
|
||||
|
||||
// successful CONNECT response
|
||||
fwrite($conn, "HTTP/1.0 200 Connection established\r\n\r\n");
|
||||
|
||||
// tunnel data
|
||||
stream_set_blocking($conn, false);
|
||||
$firstRead = true;
|
||||
while (!feof($conn) && !feof($upstream)) {
|
||||
$clientData = fread($conn, 8192);
|
||||
if ($clientData !== false && $clientData !== '') {
|
||||
if ($firstRead) {
|
||||
$sni = parse_sni_from_client_hello($clientData);
|
||||
if ($sni !== null) {
|
||||
file_put_contents(__DIR__ . "/bug74796_proxy_sni.log", $sni . "\n", FILE_APPEND);
|
||||
}
|
||||
$firstRead = false;
|
||||
}
|
||||
fwrite($upstream, $clientData);
|
||||
}
|
||||
|
||||
$serverData = fread($upstream, 8192);
|
||||
if ($serverData !== false && $serverData !== '') {
|
||||
fwrite($conn, $serverData);
|
||||
}
|
||||
}
|
||||
fclose($conn);
|
||||
fclose($upstream);
|
||||
phpt_wait();
|
||||
}
|
||||
CODE;
|
||||
|
||||
$clientCode = <<<'CODE'
|
||||
$clientCtx = stream_context_create([
|
||||
'ssl' => [
|
||||
'cafile' => __DIR__ . '/sni_server_ca.pem',
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
"http" => [
|
||||
"proxy" => "tcp://{{ ADDR }}"
|
||||
],
|
||||
]);
|
||||
|
||||
// servers
|
||||
$hosts = ["cs.php.net", "uk.php.net", "us.php.net"];
|
||||
foreach ($hosts as $host) {
|
||||
var_dump(file_get_contents("https://$host/", false, $clientCtx));
|
||||
var_dump(stream_context_get_options($clientCtx)['ssl']['peer_name'] ?? null);
|
||||
phpt_notify('proxy');
|
||||
}
|
||||
|
||||
echo file_get_contents(__DIR__ . "/bug74796_proxy_sni.log");
|
||||
|
||||
phpt_notify('server');
|
||||
CODE;
|
||||
|
||||
include 'ServerClientTestCase.inc';
|
||||
ServerClientTestCase::getInstance()->run($clientCode, [
|
||||
'server' => $serverCode,
|
||||
'proxy' => $proxyCode,
|
||||
]);
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
@unlink(__DIR__ . "/bug74796_proxy_sni.log");
|
||||
?>
|
||||
--EXPECT--
|
||||
string(19) "Hello from server 0"
|
||||
NULL
|
||||
string(19) "Hello from server 1"
|
||||
NULL
|
||||
string(19) "Hello from server 2"
|
||||
NULL
|
||||
cs.php.net
|
||||
uk.php.net
|
||||
us.php.net
|
|
@ -470,12 +470,14 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
|
|||
|
||||
if (stream && use_proxy && use_ssl) {
|
||||
smart_str header = {0};
|
||||
bool reset_ssl_peer_name = false;
|
||||
|
||||
/* Set peer_name or name verification will try to use the proxy server name */
|
||||
if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) {
|
||||
ZVAL_STR_COPY(&ssl_proxy_peer_name, resource->host);
|
||||
php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name);
|
||||
zval_ptr_dtor(&ssl_proxy_peer_name);
|
||||
reset_ssl_peer_name = true;
|
||||
}
|
||||
|
||||
smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1);
|
||||
|
@ -572,6 +574,10 @@ finish:
|
|||
stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (reset_ssl_peer_name) {
|
||||
php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
|
||||
}
|
||||
}
|
||||
|
||||
php_stream_http_response_header_info_init(&header_info);
|
||||
|
|
|
@ -59,7 +59,8 @@ PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
|
|||
const char *wrappername, const char *optionname);
|
||||
PHPAPI void php_stream_context_set_option(php_stream_context *context,
|
||||
const char *wrappername, const char *optionname, zval *optionvalue);
|
||||
|
||||
void php_stream_context_unset_option(php_stream_context *context,
|
||||
const char *wrappername, const char *optionname);
|
||||
PHPAPI php_stream_notifier *php_stream_notification_alloc(void);
|
||||
PHPAPI void php_stream_notification_free(php_stream_notifier *notifier);
|
||||
END_EXTERN_C()
|
||||
|
|
|
@ -2432,6 +2432,20 @@ PHPAPI void php_stream_context_set_option(php_stream_context *context,
|
|||
SEPARATE_ARRAY(wrapperhash);
|
||||
zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), optionvalue);
|
||||
}
|
||||
|
||||
void php_stream_context_unset_option(php_stream_context *context,
|
||||
const char *wrappername, const char *optionname)
|
||||
{
|
||||
zval *wrapperhash;
|
||||
|
||||
wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername));
|
||||
if (NULL == wrapperhash) {
|
||||
return;
|
||||
}
|
||||
SEPARATE_ARRAY(&context->options);
|
||||
SEPARATE_ARRAY(wrapperhash);
|
||||
zend_hash_str_del(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ php_stream_dirent_alphasort */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue