Broken access control vulnerability fixed in WordPress 404 to 301 plugin.

The WordPress 404 to 301 plugin (100,000+ active installations), fixed a broken access control vulnerability affecting version 3.0.7 and below.

The plugin allows the administrator to manage 404 errors by creating individual redirections to other pages or websites. Due to a broken access control vulnerability, an authenticated user could view, create and edit redirections.

Broken Access Control

In the __construct method of the “404-to-301/includes/admin/class-jj4t3-admin.php” script, the plugin registers the jj4t3_redirect_thickbox and jj4t3_redirect_form AJAX actions for authenticated users:

add_action( 'wp_ajax_jj4t3_redirect_thickbox', array( 'JJ4T3_Log_Listing', 'open_redirect' ), 100 );
add_action( 'wp_ajax_jj4t3_redirect_form', array( 'JJ4T3_Log_Listing', 'save_redirect' ) );

The open_redirect function is used to view redirections and the save_redirect function is used to edit them. They are both located in the “404-to-301/includes/admin/class-jj4t3-log-listing.php” script:

895  public static function open_redirect() {
896
897     // Yes, security check is a must when you alter something.
898     check_ajax_referer( 'jj4t3_redirect_nonce', 'nonce' );
899  
900     // Verify if the 404 value is found.
901     if ( empty( $_POST['url_404'] ) ) {
902        wp_die();
903     }
904
905     $url_404 = $_POST['url_404'];
     ...
     ...
 
954  public static function save_redirect() {
955
956     // Yes, security check is a must when you alter something.
957     check_ajax_referer( 'jj4t3_redirect_nonce', 'jj4t3_redirect_nonce' );
958
959     // Custom options for the 404 path.
960     $options = maybe_serialize(
961        array(
962           'redirect' => jj4t3_from_request( 'jj4t3_custom_redirect_redirect' ),
963           'log' => jj4t3_from_request( 'jj4t3_custom_redirect_log' ),
964           'alert' => jj4t3_from_request( 'jj4t3_custom_redirect_alert' ),
965           'type' => jj4t3_from_request( 'jj4t3_custom_redirect_type' ),
966        )
967     );
     ...
     ...

Both functions lack a capability check and rely only on the same security nonce lines 898 and 957. Nonces are used to protect against CSRF attacks but aren’t suitable for authentication or capability check because they could be compromised, e.g., they could leak in the HTML code.

The function used to populate and echo the nonce is get_redirect_content, which too is loaded in the __construct method:

add_action( 'admin_footer', array( 'JJ4T3_Log_Listing', 'get_redirect_content' ), 100 );

Because it is loaded by the admin_footer hook, the security nonce (as well as the hidden form used to edit redirections) leaks in the backend section of WordPress, and thus can be viewed by a logged-in user in the source of every HTML page:

An authenticated user can retrieve the nonce and use it to interact with the two AJAX actions in order to view, edit or create redirections.

An attacker could exploit the vulnerability to redirect users to a malicious website, but also for spamming and, more interestingly, for phishing attacks:

Spamming:
Spammers could trigger 404 errors by visiting non-existent pages such as https://example.com/fakerolex, create the redirection to their website and send the intermediary link in junk emails in order to hide the final site.

Phishing:
Attackers could create malicious links with one or more homoglyphs. For instance, in the following link, https://example.com/wp-аdmin/, the а character is not the ASCII a (U+0061) but, instead, the Cyrillic а (U+0430 or %D0%B0). Accessing the link would therefore trigger a 404 error that will be logged by the plugin. The attacker could then create a redirection to another website and could try to trick an admin or any other user to click on the malicious link, in order to redirect them to another login page and attempt to steal their login credentials:

The redirection is performed via the Location header:

$ wget -S http://example.com/wp-аdmin/
--2021-06-18 12:43:47--  http://example.com/wp-%D0%B0dmin/
Resolving example.com (example.com)... 127.0.0.1
Connecting to example.com (example.com)|127.0.0.1|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 301 Moved Permanently
  Server: nginx/1.18.0
  Date: Fri, 18 Jun 2021 05:43:47 GMT
  X-Redirect-By: WordPress
  Location: http://evil.com/wp-login.php

Recommendations

Update immediately if you have version 3.0.7 or below installed. If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability.

Timeline

The vulnerability was reported to the authors on April 05, 2021, and a new version 3.0.8 was released on June 12, 2021.

Stay informed about the latest vulnerabilities