PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-ip/src/class-utils.php +123 -1 16.3-a.3 → 16.3-beta View file →
@@ -11,9 +11,9 @@
11 11 * Class that provides static methods for working with IP addresses.
12 12 */
13 13 class Utils {
14 14
15 - const PACKAGE_VERSION = '0.6.0';
15 + const PACKAGE_VERSION = '0.7.0';
16 16
17 17 /**
18 18 * Get the current user's IP address.
19 19 *
@@ -215,8 +215,130 @@
215 215 return true;
216 216 }
217 217
218 218 return false;
219 + }
220 +
221 + /**
222 + * Checks whether a URL is a safe destination for a server-side request.
223 + *
224 + * The URL must pass wp_http_validate_url(), which rejects IPv6-literal hosts, and every
225 + * address its host resolves to must pass ip_is_public(). A host that resolves to none fails.
226 + * Not covered: redirect hops (check each one), DNS rebinding, or AAAA records without ext-dns.
227 + *
228 + * @since 0.7.0
229 + *
230 + * @param string $url URL to check.
231 + * @return bool True when the URL is safe to request, false otherwise.
232 + */
233 + public static function url_is_public( $url ) {
234 + if ( ! is_string( $url ) || '' === $url ) {
235 + return false;
236 + }
237 +
238 + $validated_url = wp_http_validate_url( $url );
239 + if ( ! $validated_url ) {
240 + return false;
241 + }
242 +
243 + $host = wp_parse_url( $validated_url, PHP_URL_HOST );
244 + if ( ! is_string( $host ) || '' === $host ) {
245 + return false;
246 + }
247 +
248 + $ips = self::resolve_host_ips( $host );
249 +
250 + // Fail closed: an unresolvable host is not assumed safe.
251 + if ( empty( $ips ) ) {
252 + return false;
253 + }
254 +
255 + foreach ( $ips as $ip ) {
256 + if ( ! self::ip_is_public( $ip ) ) {
257 + return false;
258 + }
259 + }
260 +
261 + return true;
262 + }
263 +
264 + /**
265 + * Resolves a host to the distinct IPv4 and IPv6 addresses a request to it could reach.
266 + *
267 + * IP literals are returned as-is. An empty list means the host is malformed or resolved
268 + * to nothing, and callers must treat it as unsafe.
269 + *
270 + * @since 0.7.0
271 + *
272 + * @param string $host Host name or IP literal (IPv6 literals may be bracketed).
273 + * @return string[] List of IP addresses.
274 + */
275 + public static function resolve_host_ips( $host ) {
276 + if ( ! is_string( $host ) || '' === $host ) {
277 + return array();
278 + }
279 +
280 + // Unwrap one bracket pair only, so "]8.8.8.8[" can't become a clean address.
281 + if ( preg_match( '/^\[(.*)\]$/', $host, $matches ) ) {
282 + $host = $matches[1];
283 + }
284 +
285 + // Decode so "169%2e254%2e169%2e254" can't slip past the checks below.
286 + $host = rawurldecode( $host );
287 +
288 + // Strip an IPv6 zone id ("fe80::1%eth0"); a '%' on any other host is malformed.
289 + if ( false !== strpos( $host, '%' ) ) {
290 + if ( false === strpos( $host, ':' ) ) {
291 + return array();
292 + }
293 + $host = preg_replace( '/%.*$/', '', $host );
294 + }
295 +
296 + /*
297 + * Reject control bytes (gethostbynamel() throws on a NUL), stray brackets, and
298 + * raw non-ASCII, which the request layer would punycode into a different host.
299 + */
300 + if ( '' === $host || preg_match( '/[\x00-\x20\x7f-\xff\[\]]/', $host ) ) {
301 + return array();
302 + }
303 +
304 + if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
305 + return array( $host );
306 + }
307 +
308 + $ips = array();
309 +
310 + if ( function_exists( 'gethostbynamel' ) ) {
311 + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- gethostbynamel() warns on an unresolvable host.
312 + $ipv4 = @gethostbynamel( $host );
313 + if ( is_array( $ipv4 ) ) {
314 + $ips = $ipv4;
315 + }
316 + }
317 +
318 + // gethostbynamel() only resolves IPv4; check AAAA records too. dns_get_record()
319 + // can be disabled on some hosts and may warn on lookup failure.
320 + if ( function_exists( 'dns_get_record' ) ) {
321 + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- dns_get_record may fail on some systems.
322 + $aaaa = @dns_get_record( $host, DNS_AAAA );
323 + if ( is_array( $aaaa ) ) {
324 + foreach ( $aaaa as $record ) {
325 + if ( ! empty( $record['ipv6'] ) ) {
326 + $ips[] = $record['ipv6'];
327 + }
328 + }
329 + }
330 + }
331 +
332 + // Drop anything a resolver returned that is not an IP address.
333 + $ips = array_filter(
334 + $ips,
335 + function ( $ip ) {
336 + return is_string( $ip ) && false !== filter_var( $ip, FILTER_VALIDATE_IP );
337 + }
338 + );
339 +
340 + return array_values( array_unique( $ips ) );
219 341 }
220 342
221 343 /**
222 344 * Validate an IP address.