Skip to content

Commit fb18b94

Browse files
committed
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
1 parent 910aeaa commit fb18b94

File tree

4 files changed

+202
-1
lines changed

4 files changed

+202
-1
lines changed

ext/openssl/tests/bug74796.phpt

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
--TEST--
2+
Bug #74796: TLS encryption fails behind HTTP proxy
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
12+
$serverCode = <<<'CODE'
13+
$serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
14+
$ctx = stream_context_create(['ssl' => [
15+
'SNI_server_certs' => [
16+
"cs.php.net" => __DIR__ . "/sni_server_cs.pem",
17+
"uk.php.net" => __DIR__ . "/sni_server_uk.pem",
18+
"us.php.net" => __DIR__ . "/sni_server_us.pem"
19+
]
20+
]]);
21+
22+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $serverFlags, $ctx);
23+
phpt_notify_server_start($server);
24+
25+
for ($i=0; $i < 3; $i++) {
26+
$conn = stream_socket_accept($server, 3);
27+
fwrite($conn, "HTTP/1.0 200 OK\r\n\r\nHello from server $i");
28+
usleep(200000);
29+
fclose($conn);
30+
}
31+
32+
phpt_wait();
33+
CODE;
34+
35+
$proxyCode = <<<'CODE'
36+
function parse_sni_from_client_hello($data) {
37+
$sni = null;
38+
39+
if (strlen($data) < 5 || ord($data[0]) != 0x16) return null;
40+
41+
$session_id_len = ord($data[43]);
42+
$ptr = 44 + $session_id_len;
43+
44+
// Cipher suites length
45+
$cipher_suites_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
46+
$ptr += 2 + $cipher_suites_len;
47+
48+
// Compression methods length
49+
$compression_methods_len = ord($data[$ptr]);
50+
$ptr += 1 + $compression_methods_len;
51+
52+
// Extensions length
53+
if ($ptr + 2 > strlen($data)) return null;
54+
$extensions_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
55+
$ptr += 2;
56+
57+
$extensions_end = $ptr + $extensions_len;
58+
59+
while ($ptr + 4 <= $extensions_end) {
60+
$ext_type = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
61+
$ext_len = (ord($data[$ptr+2]) << 8) | ord($data[$ptr+3]);
62+
$ptr += 4;
63+
64+
if ($ext_type === 0x00) { // SNI extension
65+
if ($ptr + 2 > strlen($data)) break;
66+
$name_list_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]);
67+
$ptr += 2;
68+
69+
if ($ptr + 3 > strlen($data)) break;
70+
$name_type = ord($data[$ptr]);
71+
$name_len = (ord($data[$ptr+1]) << 8) | ord($data[$ptr+2]);
72+
$ptr += 3;
73+
74+
if ($name_type === 0) { // host_name type
75+
$sni = substr($data, $ptr, $name_len);
76+
break;
77+
}
78+
}
79+
80+
$ptr += $ext_len;
81+
}
82+
83+
return $sni;
84+
}
85+
86+
$logFile = __DIR__ . "/bug74796_proxy_sni.log";
87+
file_put_contents($logFile, "");
88+
$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
89+
$server = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, $flags);
90+
phpt_notify_server_start($server);
91+
92+
for ($i=0; $i < 3; $i++) {
93+
$upstream = stream_socket_client("tcp://{{ ADDR }}", $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT);
94+
stream_set_blocking($upstream, false);
95+
96+
$conn = stream_socket_accept($server);
97+
stream_set_blocking($conn, true);
98+
99+
// reading CONNECT request headers
100+
while (($line = fgets($conn)) !== false) {
101+
if (rtrim($line) === '') break; // empty line means end of headers
102+
}
103+
104+
// successful CONNECT response
105+
fwrite($conn, "HTTP/1.0 200 Connection established\r\n\r\n");
106+
107+
// tunnel data
108+
stream_set_blocking($conn, false);
109+
$read = [$upstream, $conn];
110+
$applicationData = false;
111+
$firstRead = true;
112+
while (stream_select($read, $write, $except, 1)) {
113+
foreach ($read as $fp) {
114+
$data = stream_get_contents($fp);
115+
if ($fp === $conn) {
116+
if ($firstRead && $data !== '') {
117+
$sni = parse_sni_from_client_hello($data);
118+
if ($sni !== null) {
119+
file_put_contents($logFile, $sni . "\n", FILE_APPEND);
120+
}
121+
$firstRead = false;
122+
}
123+
fwrite($upstream, $data);
124+
} else {
125+
fwrite($conn, $data);
126+
}
127+
}
128+
if (feof($upstream)) {
129+
break;
130+
}
131+
$read = [$upstream, $conn];
132+
}
133+
phpt_wait();
134+
}
135+
CODE;
136+
137+
$clientCode = <<<'CODE'
138+
$clientCtx = stream_context_create([
139+
'ssl' => [
140+
'cafile' => __DIR__ . '/sni_server_ca.pem',
141+
'verify_peer' => true,
142+
'verify_peer_name' => true,
143+
],
144+
"http" => [
145+
"proxy" => "tcp://{{ ADDR }}"
146+
],
147+
]);
148+
// servers
149+
$hosts = ["cs.php.net", "uk.php.net", "us.php.net"];
150+
foreach ($hosts as $host) {
151+
var_dump(file_get_contents("https://$host/", false, $clientCtx));
152+
var_dump(stream_context_get_options($clientCtx)['ssl']['peer_name'] ?? null);
153+
phpt_notify('proxy');
154+
}
155+
156+
echo file_get_contents(__DIR__ . "/bug74796_proxy_sni.log");
157+
158+
phpt_notify('server');
159+
CODE;
160+
161+
include 'ServerClientTestCase.inc';
162+
ServerClientTestCase::getInstance()->run($clientCode, [
163+
'server' => $serverCode,
164+
'proxy' => $proxyCode,
165+
]);
166+
?>
167+
--CLEAN--
168+
<?php
169+
@unlink(__DIR__ . "/bug74796_proxy_sni.log");
170+
?>
171+
--EXPECT--
172+
string(19) "Hello from server 0"
173+
NULL
174+
string(19) "Hello from server 1"
175+
NULL
176+
string(19) "Hello from server 2"
177+
NULL
178+
cs.php.net
179+
uk.php.net
180+
us.php.net

ext/standard/http_fopen_wrapper.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,12 +470,14 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
470470

471471
if (stream && use_proxy && use_ssl) {
472472
smart_str header = {0};
473+
bool reset_ssl_peer_name = false;
473474

474475
/* Set peer_name or name verification will try to use the proxy server name */
475476
if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) {
476477
ZVAL_STR_COPY(&ssl_proxy_peer_name, resource->host);
477478
php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name);
478479
zval_ptr_dtor(&ssl_proxy_peer_name);
480+
reset_ssl_peer_name = true;
479481
}
480482

481483
smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1);
@@ -572,6 +574,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
572574
stream = NULL;
573575
}
574576
}
577+
578+
if (reset_ssl_peer_name) {
579+
php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
580+
}
575581
}
576582

577583
php_stream_http_response_header_info_init(&header_info);

main/streams/php_stream_context.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
5959
const char *wrappername, const char *optionname);
6060
PHPAPI void php_stream_context_set_option(php_stream_context *context,
6161
const char *wrappername, const char *optionname, zval *optionvalue);
62-
62+
void php_stream_context_unset_option(php_stream_context *context,
63+
const char *wrappername, const char *optionname);
6364
PHPAPI php_stream_notifier *php_stream_notification_alloc(void);
6465
PHPAPI void php_stream_notification_free(php_stream_notifier *notifier);
6566
END_EXTERN_C()

main/streams/streams.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,6 +2432,20 @@ PHPAPI void php_stream_context_set_option(php_stream_context *context,
24322432
SEPARATE_ARRAY(wrapperhash);
24332433
zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), optionvalue);
24342434
}
2435+
2436+
void php_stream_context_unset_option(php_stream_context *context,
2437+
const char *wrappername, const char *optionname)
2438+
{
2439+
zval *wrapperhash;
2440+
2441+
wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername));
2442+
if (NULL == wrapperhash) {
2443+
return;
2444+
}
2445+
SEPARATE_ARRAY(&context->options);
2446+
SEPARATE_ARRAY(wrapperhash);
2447+
zend_hash_str_del(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname));
2448+
}
24352449
/* }}} */
24362450

24372451
/* {{{ php_stream_dirent_alphasort */

0 commit comments

Comments
 (0)