1

I have never done any Wordpress development and have no experience at all with the technical architecture of Wordpress. I am self hosting a Wordpress instance and am investigating a recent attack on my instance where the attacker was able to reset the password of my admin account.

Once thing that I noticed was that I have the Post SMTP plugin installed, and that plugin seems to keep a log of all outgoing e-mails. As an admin, I can view the outgoing password reset e-mails including the password reset link, so if an attacker somehow got access to the e-mail log, this would explain how they managed to change my password.

I had a look at how Post SMTP loads an e-mail in the admin UI. It makes a requests to admin_ajax.php like so (ajaxurl is /wp-admin/admin-ajax.php):

        var logsDTSecirity = jQuery( '#ps-email-log-nonce' ).val();
        jQuery.ajax( {
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'ps-view-log',
                security: logsDTSecirity,
                id: id,
                type: toDo
            },
            success: function( response ) {
                ...
            }
        } )

The nonce is created in the plugin using the following code:

<input type="hidden" id="ps-email-log-nonce" value="<?php echo wp_create_nonce( 'security' ) ?>" />

On the server side, the function view_log_ajax() of the Post SMTP plugin serves the mail. It is registered like this:

                $email_logs = new PostmanEmailLogs;
                add_action( 'wp_ajax_ps-view-log', array( $email_logs, 'view_log_ajax' ) )

I cannot find anything that looks like an access control in the view_log_ajax() function, apart from the verification of the nonce:

                if( !wp_verify_nonce( $_POST['security'], 'security' ) ) {

            return;

        }

It seems that the admin_ajax.php script can be called by all users. As a regular user without any rights, when I change the admin colour scheme for my user profile, it also calls that script.

According to wp_create_nonce, the nonce is a simple hash based on the session cookie, the user ID, the current time stamp and the “action” (which in this case is security), so it should be simply to create a valid nonce on the client side.

With all this, I have a suspicion that the e-mail log can be reached by everyone. However, when I try to replicate the call to admin_ajax.php, I get a 400 Bad Request, even when I comment out the nonce verification on the server. My question is: Does admin_ajax.php have any particular security measures in place? What is producing the 400 Bad Request? Do you have any tips how I could investigate the issue further?

1 Answer 1

1

If you look at the source of admin-ajax.php, the answer becomes clearer:

$action = $_REQUEST['action'];

if ( is_user_logged_in() ) {
    // If no action is registered, return a Bad Request response.
    if ( ! has_action( "wp_ajax_{$action}" ) ) {
        wp_die( '0', 400 );
    }

    /**
     * Fires authenticated Ajax actions for logged-in users.
     *
     * The dynamic portion of the hook name, `$action`, refers
     * to the name of the Ajax action callback being fired.
     *
     * @since 2.1.0
     */
    do_action( "wp_ajax_{$action}" );
} else {
    // If no action is registered, return a Bad Request response.
    if ( ! has_action( "wp_ajax_nopriv_{$action}" ) ) {
        wp_die( '0', 400 );
    }

    /**
     * Fires non-authenticated Ajax actions for logged-out users.
     *
     * The dynamic portion of the hook name, `$action`, refers
     * to the name of the Ajax action callback being fired.
     *
     * @since 2.8.0
     */
    do_action( "wp_ajax_nopriv_{$action}" );
}

Any code hooked to 'wp_ajax_ps-view-log' will only run if the user is logged in. For an AJAX action to be usable by anonymous users, it would also need to be hooked to 'wp_ajax_nopriv_ps-view-log', and according to the code you've shared it is not.

So any admin-ajax.php code that is not explicitly allowed for logged-out users is protected by the same WordPress authentication that protects /wp-admin.

If you have registered users who should not have access to this endpoint, then the nonce verifies that they at least have access to a page that generates the nonce.

3
  • Thank you, this is very helpful! This means that the user has to be logged in. At least on one of my instances, registration is open in order to be able to write comments.
    – cdauth
    Jan 13 at 17:31
  • You are writing that the nonce verifies that users have access to the page that generates the nonce. Could you explain how exactly this works? As far as I understand, the wp_create_nonce function simply creates a hash from the user ID, timestamp, session cookie and action, so I could just create a valid nonce locally on my computer, no?
    – cdauth
    Jan 13 at 17:33
  • It only verifies it in the sense that the only way you’d typically know what the nonce is would be to go to the page where it’s generated. If you somehow knew the user id, cookie and action then I guess theoretically you could create it, but at that point if you have the session token then you probably wouldn’t need to create the nonce artificially. Honestly, I think any vulnerability that you’ve fallen victim would be less obvious, or the result of something like phishing. I highly doubt this is it. Jan 14 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.