Critical LFI to RCE Vulnerability in WP Ghost Plugin Affecting 200k+ Sites

Published 20 March 2025
Updated 22 March 2025
Table of Contents

WP Ghost

Local File Inclusion to RCE

200k
CVSS 9.6

This blog post is about the WP Ghost plugin vulnerability. If you’re a WP Ghost user, please update the plugin to at least version 5.4.02.

If you are a Patchstack customer, you are protected from this vulnerability already, and no further action is required from you.

For plugin developers, we have security audit services and Enterprise API for hosting companies.

About the WP Ghost plugin

The plugin WP Ghost, which has over 200k active installations, is one of the most popular free security and firewall plugins in the WordPress repository. The plugin is developed by John Darrel.

This plugin provides some security solutions for WordPress websites. It adds multiple layers of security to block bots and prevent unauthorized access.

The security vulnerability

The WP Ghost plugin suffered from an unauthenticated Local File Inclusion vulnerability. The vulnerability occurred due to insufficient user input value via the URL path that will be included as a file. Due to the behavior of the LFI case, this vulnerability could lead to Remote Code Execution on almost all of the environment setup. The vulnerability is fixed in version 5.4.02 and has been tracked with CVE-2025-26909.

The underlying vulnerability exists in the showFile function:

public function showFile( $url ) {

	// Initialize WordPress Filesystem
	$wp_filesystem = HMWP_Classes_ObjController::initFilesystem();

	// Remove the redirect hook
	remove_filter( 'wp_redirect', array( HMWP_Classes_ObjController::getClass( 'HMWP_Models_Rewrite' ), 'sanitize_redirect' ), PHP_INT_MAX );
	remove_filter( 'template_directory_uri', array( HMWP_Classes_ObjController::getClass( 'HMWP_Models_Rewrite' ), 'find_replace_url' ), PHP_INT_MAX );

	// In case of SAFE MODE URL or File mapping
	if ( HMW_DYNAMIC_FILES ) {
		$url = str_replace( $this->_safe_files, $this->_files, $url );
	}

	// Build the rewrite rules
	$this->buildRedirect();

	//Get the original URL and path based on rewrite rules
	$url_no_query = ( ( strpos( $url, '?' ) !== false ) ? substr( $url, 0, strpos( $url, '?' ) ) : $url );
	$new_url          = $this->getOriginalUrl( $url );
	$new_url_no_query = ( ( strpos( $new_url, '?' ) !== false ) ? substr( $new_url, 0, strpos( $new_url, '?' ) ) : $new_url );
	$new_path         = $this->getOriginalPath( $new_url );
	$ctype            = false;

------------------ CUT HERE ------------------
	} elseif (  stripos( trailingslashit( $url_no_query ), '/' . HMWP_Classes_Tools::getOption( 'hmwp_activate_url' ) . '/' ) !== false ) {

		header( "HTTP/1.1 200 OK" );

		ob_start();
		include $new_path;
		$content = ob_get_clean();

		//Echo the html file content
		echo $content;
		die();

	}
------------------ CUT HERE ------------------

The function above can be called from maybeShowNotFound:

public function maybeShowNotFound() {
	//If the file doesn't exist
	//show the file content
	if ( is_404() ) {
		$this->showFile( $this->getCurrentURL() );
	} else {
		$this->maybeShowLogin( $this->getCurrentURL() );
	}

}

The value passed from the getCurrentURL function:

public function getCurrentURL() {
	$url = '';

	if ( isset( $_SERVER['HTTP_HOST'] ) ) {
		// build the URL in the address bar
		$url = is_ssl() ? 'https://' : 'http://';
		$url .= $_SERVER['HTTP_HOST'];
		$url .= rawurldecode( $_SERVER['REQUEST_URI'] );
	}

	return $url;
}

First, the maybeShowNotFound function will be hooked to the template_redirect, which can be triggered by unauthenticated users. Then, if the path or file accessed by unauthenticated users is not found, it will call the maybeShowNotFound function and finally will call the showFile function.

On the showFile function, the $new_path variable is coming from the getCurrentURL -> getOriginalUrl -> getOriginalPath functions chain. The value then will be directly passed to the require_once function. Since there is no proper check and sanitization of the URL path, users are able to do a complete path traversal and include arbitrary files on the server to execute arbitrary code. As we know, there are a couple of techniques that could leverage a full arbitrary LFI to RCE, such as php:// filter chains and PHP_SESSION_UPLOAD_PROGRESS trick.

This vulnerability can only be exploited if the Change Paths feature on the WP Ghost feature is set to Lite or Ghost mode, which is not enabled by default.

The patch

The vendor patched the issue by adding additional validation on the supplied URL or path from the users. The patch can be seen below:

Conclusion

When working with user-provided data for a local file inclusion process, always implement a strict check on the supplied value and only allow users to access specific or whitelisted paths or files.

Want to learn more about finding and fixing vulnerabilities?

Explore our Academy to master the art of finding and patching vulnerabilities within the WordPress ecosystem. Dive deep into detailed guides on various vulnerability types, from discovery tactics for researchers to robust fixes for developers. Join us and contribute to our growing knowledge base.

Timeline

25-28 February, 2025Patchstack Alliance researcher Dimas Maulana reported the vulnerability, and we discussed the full impact with the researcher.
3 March, 2025We reached out to the vendor regarding the vulnerability.
4 March, 2025WP Ghost version 5.4.02 was released to patch the issue.
19 March, 2025The vulnerability was added to the Patchstack database.
20 March, 2025Security advisory article published.

Help us make the Internet 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