SSO Enforcement Bypass – WP SAML Auth 2.1.3

| in


Affected pluginWP SAML Auth
Active installs5,000+
Vulnerable version<= 2.1.3
Audited version2.1.3
Fully patched version2.1.4
Recommended remediationUpgrade the plugin to 2.1.4

Description


The WP SAML Auth plugin allows enforcing that all users must log in via the configured SAML IDP rather than the standard WordPress login.

This can be bypassed by adding a loggedout query parameter to any URL that allows logins.

Any SSO-provisioned user can reset their WordPress password in the WordPress admin area.

Thus, users who are removed from a company’s IDP will still be able to log into WordPress using their username and password.

Proof of concept


The plugin contains the following code, which hooks into the WordPress authenticate filter.

public function filter_authenticate( $user, $username, $password ) {
    
    $permit_wp_login = self::get_option( 'permit_wp_login' );
    if ( is_a( $user, 'WP_User' ) && $permit_wp_login ) {
        return $user;
    }
    
    if ( ! empty( $_POST['SAMLResponse'] ) ) {
        $user = $this->do_saml_authentication();
    } elseif ( ( ! $permit_wp_login && empty( $_GET['loggedout'] ) ) || ( ! empty( $_GET['action'] ) && 'wp-saml-auth' === $_GET['action'] ) ) {
        $user = $this->do_saml_authentication();
    }
    return $user;
}

The highlighted line shows that SAML authentication will not be performed if the loggedout query parameter is present in the request.

This was presumably added to prevent the SAML authentication from running once a user logs out of WordPress (which would redirect them to /wp-login.php?loggedout=1).

However, it’s also possible to send query parameters via POST requests and thus bypass the SSO requirements.

curl 
  -X POST https://wp.test/wp-login.php?loggedout=1 \
  -d "log=admin" \
  -d "pwd=admin" -v

Sending the above request will return valid WordPress authentication cookies.

Proposed patch


The logic of the authenticate callback should be changed to something like the following:

function filter_authenticate( $user, $username, $password ) {
    
    $allow_wp_login = self::get_option('permit_wp_login');
    
    // Some other WordPress filter already authenticated the user.
    if(is_a($user, 'WP_User')) {
        
        // Unless explicitly allowed, only SAML users can be authenticated.
        if(! $allow_wp_login){
           $user = $this->do_saml_authentication();
        }
        
        return $user;
    }
    
    if(! $allow_wp_login){
        // If wp-login is not allowed, always redirect to IDP
        // unless the user just logged out.
        $should_saml = !isset($_GET['loggedout']);
    }else{
        // If wp-login is allowed, redirect to IDP selectively.
        $should_saml = isset($_POST['SAMLResponse']) || (isset($_GET['action']) && 'wp-saml-auth' === $_GET['action']);
    }
    
    if($should_saml){
        return $this->do_saml_authentication();
    }
    
    return $user;
}

Here, we strictly separate the SSO enforcement (security) from the SSO redirect functionality when the user hits wp-login.php, and normal WordPress logins are not allowed (UX).

Timeline


Vendor contactedOktober 23, 2023
First ResponseOktober 23, 2023
Fully patched atNovember 27, 2023
Publicly disclosedDecember 30, 2023

Miscellaneous


Pantheon was very cooperative and fixed the issue in a timely manner using our recommended patch.



Leave a Reply

Your email address will not be published. Required fields are marked *