From 25c36181389f07fbc7f0263827af3b4be8a79cdd Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Mon, 6 Jun 2016 21:33:30 +0000 Subject: [PATCH] REST API: Create the general `wp_check_jsonp_callback()` function for validating JSONP callback functions. Move the REST API JSONP callback validation check into a separate function named `wp_check_jsonp_callback()`. This allows plugins to use the built-in validation when handling JSONP callbacks. Extremely Important Note: If you send JSONP in your custom response, make sure you prefix the response with `/**/`. This will mitigate the Rosetta Flash exploit. You should also send the `X-Content-Type-Options:nosniff` header, or even better, use the REST API infrastructure. Props rmccue. Fixes #28523. git-svn-id: https://develop.svn.wordpress.org/trunk@37646 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 22 ++++++++++++++++ .../rest-api/class-wp-rest-server.php | 10 ++------ tests/phpunit/tests/rest-api.php | 25 +++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 4337b10914..cec338693e 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3104,6 +3104,28 @@ function wp_send_json_error( $data = null ) { wp_send_json( $response ); } +/** + * Check that a JSONP callback is a valid JavaScript callback. + * + * Only allows alphanumeric characters and the dot character in callback + * function names. This helps to mitigate XSS attacks caused by directly + * outputting user input. + * + * @since 4.6.0 + * + * @param string $callback Supplied JSONP callback function. + * @return bool True if valid callback, otherwise false. + */ +function wp_check_jsonp_callback( $callback ) { + if ( ! is_string( $callback ) ) { + return false; + } + + $jsonp_callback = preg_replace( '/[^\w\.]/', '', $callback, -1, $illegal_char_count ); + + return 0 === $illegal_char_count; +} + /** * Retrieve the WordPress home page URL. * diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index 43644b0158..6febebe34d 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -280,14 +280,8 @@ class WP_REST_Server { return false; } - // Check for invalid characters (only alphanumeric allowed). - if ( is_string( $_GET['_jsonp'] ) ) { - $jsonp_callback = preg_replace( '/[^\w\.]/', '', wp_unslash( $_GET['_jsonp'] ), -1, $illegal_char_count ); - if ( 0 !== $illegal_char_count ) { - $jsonp_callback = null; - } - } - if ( null === $jsonp_callback ) { + $jsonp_callback = $_GET['_jsonp']; + if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) { echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 ); return false; } diff --git a/tests/phpunit/tests/rest-api.php b/tests/phpunit/tests/rest-api.php index 0c6d22e41d..6710a830ae 100644 --- a/tests/phpunit/tests/rest-api.php +++ b/tests/phpunit/tests/rest-api.php @@ -322,4 +322,29 @@ class Tests_REST_API extends WP_UnitTestCase { } + public function jsonp_callback_provider() { + return array( + // Standard names + array( 'Springfield', true ), + array( 'shelby.ville', true ), + array( 'cypress_creek', true ), + array( 'KampKrusty1', true ), + + // Invalid names + array( 'ogden-ville', false ), + array( 'north haverbrook', false ), + array( "Terror['Lake']", false ), + array( 'Cape[Feare]', false ), + array( '"NewHorrorfield"', false ), + array( 'Scream\\ville', false ), + ); + } + + /** + * @dataProvider jsonp_callback_provider + */ + public function test_jsonp_callback_check( $callback, $valid ) { + $this->assertEquals( $valid, wp_check_jsonp_callback( $callback ) ); + } + }