Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/class-wp-http-streams.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * HTTP API: WP_Http_Streams class
4
 *
5
 * @package WordPress
6
 * @subpackage HTTP
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used to integrate PHP Streams as an HTTP transport.
12
 *
13
 * @since 2.7.0
14
 * @since 3.7.0 Combined with the fsockopen transport and switched to `stream_socket_client()`.
15
 */
16
class WP_Http_Streams {
17
	/**
18
	 * Send a HTTP request to a URI using PHP Streams.
19
	 *
20
	 * @see WP_Http::request For default options descriptions.
21
	 *
22
	 * @since 2.7.0
23
	 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
24
	 *
25
	 * @access public
26
	 * @param string $url The request URL.
27
	 * @param string|array $args Optional. Override the defaults.
28
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
29
	 */
30
	public function request($url, $args = array()) {
31
		$defaults = array(
32
			'method' => 'GET', 'timeout' => 5,
33
			'redirection' => 5, 'httpversion' => '1.0',
34
			'blocking' => true,
35
			'headers' => array(), 'body' => null, 'cookies' => array()
36
		);
37
38
		$r = wp_parse_args( $args, $defaults );
39
40 View Code Duplication
		if ( isset( $r['headers']['User-Agent'] ) ) {
41
			$r['user-agent'] = $r['headers']['User-Agent'];
42
			unset( $r['headers']['User-Agent'] );
43
		} elseif ( isset( $r['headers']['user-agent'] ) ) {
44
			$r['user-agent'] = $r['headers']['user-agent'];
45
			unset( $r['headers']['user-agent'] );
46
		}
47
48
		// Construct Cookie: header if any cookies are set.
49
		WP_Http::buildCookieHeader( $r );
50
51
		$arrURL = parse_url($url);
52
53
		$connect_host = $arrURL['host'];
54
55
		$secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
56
		if ( ! isset( $arrURL['port'] ) ) {
57
			if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
58
				$arrURL['port'] = 443;
59
				$secure_transport = true;
60
			} else {
61
				$arrURL['port'] = 80;
62
			}
63
		}
64
65
		// Always pass a Path, defaulting to the root in cases such as http://example.com
66
		if ( ! isset( $arrURL['path'] ) ) {
67
			$arrURL['path'] = '/';
68
		}
69
70
		if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
71
			if ( isset( $r['headers']['Host'] ) )
72
				$arrURL['host'] = $r['headers']['Host'];
73
			else
74
				$arrURL['host'] = $r['headers']['host'];
75
			unset( $r['headers']['Host'], $r['headers']['host'] );
76
		}
77
78
		/*
79
		 * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
80
		 * to ::1, which fails when the server is not set up for it. For compatibility, always
81
		 * connect to the IPv4 address.
82
		 */
83
		if ( 'localhost' == strtolower( $connect_host ) )
84
			$connect_host = '127.0.0.1';
85
86
		$connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
87
88
		$is_local = isset( $r['local'] ) && $r['local'];
89
		$ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
90 View Code Duplication
		if ( $is_local ) {
91
			/**
92
			 * Filters whether SSL should be verified for local requests.
93
			 *
94
			 * @since 2.8.0
95
			 *
96
			 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
97
			 */
98
			$ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
99
		} elseif ( ! $is_local ) {
100
			/**
101
			 * Filters whether SSL should be verified for non-local requests.
102
			 *
103
			 * @since 2.8.0
104
			 *
105
			 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
106
			 */
107
			$ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
108
		}
109
110
		$proxy = new WP_HTTP_Proxy();
111
112
		$context = stream_context_create( array(
113
			'ssl' => array(
114
				'verify_peer' => $ssl_verify,
115
				//'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
116
				'capture_peer_cert' => $ssl_verify,
117
				'SNI_enabled' => true,
118
				'cafile' => $r['sslcertificates'],
119
				'allow_self_signed' => ! $ssl_verify,
120
			)
121
		) );
122
123
		$timeout = (int) floor( $r['timeout'] );
124
		$utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
125
		$connect_timeout = max( $timeout, 1 );
126
127
		// Store error number.
128
		$connection_error = null;
129
130
		// Store error string.
131
		$connection_error_str = null;
132
133
		if ( !WP_DEBUG ) {
134
			// In the event that the SSL connection fails, silence the many PHP Warnings.
135
			if ( $secure_transport )
136
				$error_reporting = error_reporting(0);
137
138 View Code Duplication
			if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
139
				$handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
140
			else
141
				$handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
142
143
			if ( $secure_transport )
144
				error_reporting( $error_reporting );
0 ignored issues
show
The variable $error_reporting does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
145
146 View Code Duplication
		} else {
147
			if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
148
				$handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
149
			else
150
				$handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
151
		}
152
153
		if ( false === $handle ) {
154
			// SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken.
155
			if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
156
				return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
157
158
			return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
159
		}
160
161
		// Verify that the SSL certificate is valid for this request.
162
		if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
163
			if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
164
				return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
165
		}
166
167
		stream_set_timeout( $handle, $timeout, $utimeout );
168
169
		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
170
			$requestPath = $url;
171
		else
172
			$requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
173
174
		$strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
175
176
		$include_port_in_host_header = (
177
			( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) ||
178
			( 'http'  == $arrURL['scheme'] && 80  != $arrURL['port'] ) ||
179
			( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] )
180
		);
181
182
		if ( $include_port_in_host_header ) {
183
			$strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
184
		} else {
185
			$strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
186
		}
187
188
		if ( isset($r['user-agent']) )
189
			$strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
190
191
		if ( is_array($r['headers']) ) {
192
			foreach ( (array) $r['headers'] as $header => $headerValue )
193
				$strHeaders .= $header . ': ' . $headerValue . "\r\n";
194
		} else {
195
			$strHeaders .= $r['headers'];
196
		}
197
198
		if ( $proxy->use_authentication() )
199
			$strHeaders .= $proxy->authentication_header() . "\r\n";
200
201
		$strHeaders .= "\r\n";
202
203
		if ( ! is_null($r['body']) )
204
			$strHeaders .= $r['body'];
205
206
		fwrite($handle, $strHeaders);
207
208
		if ( ! $r['blocking'] ) {
209
			stream_set_blocking( $handle, 0 );
210
			fclose( $handle );
211
			return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
212
		}
213
214
		$strResponse = '';
215
		$bodyStarted = false;
216
		$keep_reading = true;
217
		$block_size = 4096;
218
		if ( isset( $r['limit_response_size'] ) )
219
			$block_size = min( $block_size, $r['limit_response_size'] );
220
221
		// If streaming to a file setup the file handle.
222
		if ( $r['stream'] ) {
223
			if ( ! WP_DEBUG )
224
				$stream_handle = @fopen( $r['filename'], 'w+' );
225
			else
226
				$stream_handle = fopen( $r['filename'], 'w+' );
227
			if ( ! $stream_handle )
228
				return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
229
230
			$bytes_written = 0;
231
			while ( ! feof($handle) && $keep_reading ) {
232
				$block = fread( $handle, $block_size );
233
				if ( ! $bodyStarted ) {
234
					$strResponse .= $block;
235
					if ( strpos( $strResponse, "\r\n\r\n" ) ) {
236
						$process = WP_Http::processResponse( $strResponse );
237
						$bodyStarted = true;
238
						$block = $process['body'];
239
						unset( $strResponse );
240
						$process['body'] = '';
241
					}
242
				}
243
244
				$this_block_size = strlen( $block );
245
246
				if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) {
247
					$this_block_size = ( $r['limit_response_size'] - $bytes_written );
248
					$block = substr( $block, 0, $this_block_size );
249
				}
250
251
				$bytes_written_to_file = fwrite( $stream_handle, $block );
252
253
				if ( $bytes_written_to_file != $this_block_size ) {
254
					fclose( $handle );
255
					fclose( $stream_handle );
256
					return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
257
				}
258
259
				$bytes_written += $bytes_written_to_file;
260
261
				$keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
262
			}
263
264
			fclose( $stream_handle );
265
266
		} else {
267
			$header_length = 0;
268
			while ( ! feof( $handle ) && $keep_reading ) {
269
				$block = fread( $handle, $block_size );
270
				$strResponse .= $block;
271
				if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
272
					$header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
273
					$bodyStarted = true;
274
				}
275
				$keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
276
			}
277
278
			$process = WP_Http::processResponse( $strResponse );
279
			unset( $strResponse );
280
281
		}
282
283
		fclose( $handle );
284
285
		$arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
0 ignored issues
show
The variable $process does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
286
287
		$response = array(
288
			'headers' => $arrHeaders['headers'],
289
			// Not yet processed.
290
			'body' => null,
291
			'response' => $arrHeaders['response'],
292
			'cookies' => $arrHeaders['cookies'],
293
			'filename' => $r['filename']
294
		);
295
296
		// Handle redirects.
297
		if ( false !== ( $redirect_response = WP_Http::handle_redirects( $url, $r, $response ) ) )
298
			return $redirect_response;
299
300
		// If the body was chunk encoded, then decode it.
301
		if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
302
			$process['body'] = WP_Http::chunkTransferDecode($process['body']);
303
304 View Code Duplication
		if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
305
			$process['body'] = WP_Http_Encoding::decompress( $process['body'] );
306
307
		if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
308
			$process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
309
310
		$response['body'] = $process['body'];
311
312
		return $response;
313
	}
314
315
	/**
316
	 * Verifies the received SSL certificate against its Common Names and subjectAltName fields.
317
	 *
318
	 * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
319
	 * the certificate is valid for the hostname which was requested.
320
	 * This function verifies the requested hostname against certificate's subjectAltName field,
321
	 * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
322
	 *
323
	 * IP Address support is included if the request is being made to an IP address.
324
	 *
325
	 * @since 3.7.0
326
	 * @static
327
	 *
328
	 * @param stream $stream The PHP Stream which the SSL request is being made over
329
	 * @param string $host The hostname being requested
330
	 * @return bool If the cerficiate presented in $stream is valid for $host
331
	 */
332
	public static function verify_ssl_certificate( $stream, $host ) {
333
		$context_options = stream_context_get_options( $stream );
334
335
		if ( empty( $context_options['ssl']['peer_certificate'] ) )
336
			return false;
337
338
		$cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
339
		if ( ! $cert )
340
			return false;
341
342
		/*
343
		 * If the request is being made to an IP address, we'll validate against IP fields
344
		 * in the cert (if they exist)
345
		 */
346
		$host_type = ( WP_Http::is_ip_address( $host ) ? 'ip' : 'dns' );
347
348
		$certificate_hostnames = array();
349
		if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
350
			$match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
351
			foreach ( $match_against as $match ) {
352
				list( $match_type, $match_host ) = explode( ':', $match );
353
				if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
354
					$certificate_hostnames[] = strtolower( trim( $match_host ) );
355
			}
356
		} elseif ( !empty( $cert['subject']['CN'] ) ) {
357
			// Only use the CN when the certificate includes no subjectAltName extension.
358
			$certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
359
		}
360
361
		// Exact hostname/IP matches.
362
		if ( in_array( strtolower( $host ), $certificate_hostnames ) )
363
			return true;
364
365
		// IP's can't be wildcards, Stop processing.
366
		if ( 'ip' == $host_type )
367
			return false;
368
369
		// Test to see if the domain is at least 2 deep for wildcard support.
370
		if ( substr_count( $host, '.' ) < 2 )
371
			return false;
372
373
		// Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com.
374
		$wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
375
376
		return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
377
	}
378
379
	/**
380
	 * Determines whether this class can be used for retrieving a URL.
381
	 *
382
	 * @static
383
	 * @access public
384
	 * @since 2.7.0
385
	 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
386
	 *
387
	 * @param array $args Optional. Array of request arguments. Default empty array.
388
	 * @return bool False means this class can not be used, true means it can.
389
	 */
390
	public static function test( $args = array() ) {
391
		if ( ! function_exists( 'stream_socket_client' ) )
392
			return false;
393
394
		$is_ssl = isset( $args['ssl'] ) && $args['ssl'];
395
396
		if ( $is_ssl ) {
397
			if ( ! extension_loaded( 'openssl' ) )
398
				return false;
399
			if ( ! function_exists( 'openssl_x509_parse' ) )
400
				return false;
401
		}
402
403
		/**
404
		 * Filters whether streams can be used as a transport for retrieving a URL.
405
		 *
406
		 * @since 2.7.0
407
		 *
408
		 * @param bool  $use_class Whether the class can be used. Default true.
409
		 * @param array $args      Request arguments.
410
		 */
411
		return apply_filters( 'use_streams_transport', true, $args );
412
	}
413
}
414
415
/**
416
 * Deprecated HTTP Transport method which used fsockopen.
417
 *
418
 * This class is not used, and is included for backward compatibility only.
419
 * All code should make use of WP_Http directly through its API.
420
 *
421
 * @see WP_HTTP::request
422
 *
423
 * @since 2.7.0
424
 * @deprecated 3.7.0 Please use WP_HTTP::request() directly
425
 */
426
class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
427
	// For backward compatibility for users who are using the class directly.
428
}
429