WordPress.org

Make WordPress Core

Changeset 35366


Ignore:
Timestamp:
10/23/2015 04:45:10 AM (6 years ago)
Author:
dd32
Message:

XMLRPC: Prevent authentication from occuring after a failed authentication attmept in any single XML-RPC call.

This hardens WordPress against a common vector which uses multiple user identifiers in a single system.multicall call. In the event that authentication fails, all following authentication attempts in that call will also fail.

Props dd32, johnbillion.
Fixes #34336

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-xmlrpc-server.php

    r35170 r35366  
    4545     */
    4646    public $error;
     47
     48    /**
     49     * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
     50     *
     51     * @access protected
     52     * @var bool
     53     */
     54    protected $auth_failed = false;
    4755
    4856    /**
     
    252260        }
    253261
    254         $user = wp_authenticate($username, $password);
    255 
    256         if (is_wp_error($user)) {
     262        if ( $this->auth_failed ) {
     263            $user = new WP_Error( 'login_prevented' );
     264        } else {
     265            $user = wp_authenticate( $username, $password );
     266        }
     267
     268        if ( is_wp_error( $user ) ) {
    257269            $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
     270
     271            // Flag that authentication has failed once on this wp_xmlrpc_server instance
     272            $this->auth_failed = true;
    258273
    259274            /**
  • trunk/tests/phpunit/includes/testcase-xmlrpc.php

    r35242 r35366  
    1212        add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
    1313
    14         $this->myxmlrpcserver = new wp_xmlrpc_server();
     14        $this->myxmlrpcserver = new WP_XMLRPC_Server_UnitTestable();
    1515    }
    1616
    1717    function tearDown() {
    1818        remove_filter( 'pre_option_enable_xmlrpc', '__return_true' );
     19
     20         $this->myxmlrpcserver->reset_failed_auth();
     21
    1922        $this->remove_added_uploads();
    2023
     
    3033    }
    3134}
     35
     36class WP_XMLRPC_Server_UnitTestable extends wp_xmlrpc_server {
     37    public function reset_failed_auth() {
     38        $this->auth_failed = false;
     39    }
     40}
  • trunk/tests/phpunit/tests/xmlrpc/basic.php

    r25002 r35366  
    2020        $user_id = $this->make_user_by_role( 'subscriber' );
    2121
     22        $this->assertTrue( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) );
     23        $this->assertInstanceOf( 'WP_User', $this->myxmlrpcserver->login( 'subscriber', 'subscriber' ) );
     24    }
     25
     26    function test_login_pass_bad() {
     27        $user_id = $this->make_user_by_role( 'subscriber' );
     28
    2229        $this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'username', 'password' ) );
    2330        $this->assertFalse( $this->myxmlrpcserver->login( 'username', 'password' ) );
    2431
    25         $this->assertTrue( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) );
    26         $this->assertInstanceOf( 'WP_User', $this->myxmlrpcserver->login( 'subscriber', 'subscriber' ) );
     32        // The auth will still fail due to authentication blocking after the first failed attempt
     33        $this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) );
     34    }
     35
     36    /**
     37     * @ticket 34336
     38     */
     39    function test_multicall_invalidates_all_calls_after_invalid_call() {
     40        $editor_id = $this->make_user_by_role( 'editor' );
     41        $post_id = self::factory()->post->create( array(
     42            'post_author' => $editor_id,
     43        ) );
     44
     45        $method_calls = array(
     46            // Valid login
     47            array(
     48                'methodName' => 'wp.editPost',
     49                'params'     => array(
     50                    0,
     51                    'editor',
     52                    'editor',
     53                    $post_id,
     54                    array(
     55                        'title' => 'Title 1',
     56                    ),
     57                ),
     58            ),
     59            // *Invalid* login
     60            array(
     61                'methodName' => 'wp.editPost',
     62                'params'     => array(
     63                    0,
     64                    'editor',
     65                    'password',
     66                    $post_id,
     67                    array(
     68                        'title' => 'Title 2',
     69                    ),
     70                ),
     71            ),
     72            // Valid login
     73            array(
     74                'methodName' => 'wp.editPost',
     75                'params'     => array(
     76                    0,
     77                    'editor',
     78                    'editor',
     79                    $post_id,
     80                    array(
     81                        'title' => 'Title 3',
     82                    ),
     83                ),
     84            ),
     85        );
     86
     87        $this->myxmlrpcserver->callbacks = $this->myxmlrpcserver->methods;
     88
     89        $result = $this->myxmlrpcserver->multiCall( $method_calls );
     90
     91        $this->assertArrayNotHasKey( 'faultCode', $result[0] );
     92        $this->assertArrayHasKey( 'faultCode', $result[1] );
     93        $this->assertArrayHasKey( 'faultCode', $result[2] );
     94
    2795    }
    2896}
Note: See TracChangeset for help on using the changeset viewer.