
WP Ghost
Local File Inclusion to RCE
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
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.