Introduction
A PHP Object Injection flaw in the Admin Columns WordPress plugin allows any authenticated user with Contributor level privileges to achieve full remote code execution on the underlying web server, scoring a CVSS 8.8. What makes this vulnerability particularly notable is not just the low authentication barrier, but the fact that the plugin bundles a Laravel library containing a ready made POP gadget chain that converts the deserialization weakness into immediate, practical code execution.
Admin Columns is a widely used WordPress plugin developed by CodePress that enhances the WordPress admin interface by allowing administrators to customize, sort, and manage columns on post, page, user, and taxonomy list screens. With over 100,000 active installations and a 4.9 out of 5 star rating from more than 1,650 reviews, it occupies a significant footprint in the WordPress ecosystem. The plugin also offers a commercial Pro version with integrations for WooCommerce, Advanced Custom Fields, and Yoast SEO, making it a common fixture on both small and enterprise WordPress deployments.
Technical Information
Root Cause: Unsafe Deserialization in IdsToCollection
The vulnerability resides in the IdsToCollection::get_ids_from_string() function, located at line 42 of classes/Formatter/IdsToCollection.php. This function calls PHP's native unserialize() on post meta values without specifying the allowed_classes option. The allowed_classes parameter was introduced in PHP 7.0 precisely to prevent Object Injection by controlling which classes can be instantiated during deserialization. Its absence here means any PHP class available in the runtime can be instantiated from a crafted serialized string.
The data flow begins in Meta.php (line 34 in classes/Formatter/), which reads custom field meta values from WordPress posts and passes them downstream to IdsToCollection for processing. This is the entry point where attacker controlled data enters the vulnerable deserialization path.
Attack Flow
The exploitation proceeds through a clear sequence:
-
Initial Access: An attacker authenticates to the WordPress site with Contributor level credentials. This is one of the lowest default WordPress roles, commonly granted to guest authors or external content contributors.
-
Payload Injection: The attacker creates or edits a post and sets a custom meta field value to a crafted serialized PHP object string. WordPress allows Contributors to set custom meta fields on their own posts.
-
Trigger Deserialization: When the Admin Columns plugin renders the column view for the post (for example, when an administrator views the post list in the WordPress admin panel), the
Meta.phpformatter reads the custom field value and passes it toIdsToCollection::get_ids_from_string(). -
Object Instantiation: The
get_ids_from_string()function callsunserialize()on the attacker controlled meta value. Because noallowed_classesrestriction is in place, PHP instantiates whatever class is specified in the serialized payload. -
POP Chain Execution: The crafted payload leverages classes from the Laravel
serializable-closurelibrary bundled in the plugin'svendor/directory to achieve arbitrary code execution.
The Bundled POP Gadget Chain
The escalation from Object Injection to Remote Code Execution depends entirely on the availability of exploitable classes in the application's scope. In many PHP Object Injection scenarios, attackers must discover suitable gadget chains from the application's own codebase or its dependencies. In this case, the Admin Columns plugin ships with the Laravel serializable-closure library, which provides a complete, self contained POP (Property Oriented Programming) chain.
Two classes in this library are central to the chain:
Native.php (located at vendor/laravel/serializable-closure/src/Serializers/Native.php, line 148): This serializer class implements magic methods (__wakeup() or __destruct()) that are automatically invoked during deserialization. These methods process serialized closure data, providing the initial entry point for the gadget chain.
ClosureStream.php (located at vendor/laravel/serializable-closure/src/Support/ClosureStream.php, line 47): This class implements a PHP stream wrapper that can execute arbitrary PHP code when its stream methods are invoked. The class is annotated with #[AllowDynamicProperties], which enables dynamic property injection, a capability critical to the POP chain's operation.
By constructing a serialized payload that chains the Native serializer's magic methods through to ClosureStream's code execution capability, an attacker executes arbitrary PHP code as the web server user. No file uploads, no additional vulnerabilities, and no external dependencies are required.
CVSS v3.1 Breakdown
The CVSS vector is CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, yielding a score of 8.8 (High).
| Metric | Value | Implication |
|---|---|---|
| Attack Vector | Network | Exploitable remotely over the internet |
| Attack Complexity | Low | No special conditions or race conditions required |
| Privileges Required | Low | Contributor level access is sufficient |
| User Interaction | None | No victim action needed; triggered when admin views post list |
| Scope | Unchanged | Impact confined to the vulnerable component |
| Confidentiality | High | Full system data accessible |
| Integrity | High | Full system modification possible |
| Availability | High | Full system disruption possible |
The combination of Low Attack Complexity and Low Privileges Required is what makes this vulnerability particularly accessible. The attacker does not need to be an Administrator; a Contributor account is enough. On sites that allow user registration with Contributor roles, the barrier to exploitation is essentially just creating an account.
CWE-502: Deserialization of Untrusted Data
This vulnerability is a textbook instance of CWE-502, which describes weaknesses where an application deserializes untrusted data without sufficiently ensuring the resulting data will be valid. In the PHP context, this manifests as PHP Object Injection: unserialize() processes attacker controlled serialized strings, creating objects whose magic methods (__wakeup(), __destruct(), __toString(), etc.) execute operations the developer never intended. The OWASP PHP Object Injection documentation specifically identifies the use of unserialize() on user controlled input as the root cause pattern.
Affected Systems and Versions
The vulnerability affects the Admin Columns plugin (slug: codepress-admin-columns) for WordPress in the following configuration:
- Affected versions: All versions up to and including 7.0.18
- Fixed version: 7.0.19 (released May 29, 2026)
- WordPress requirement: 5.9 or higher
- PHP requirement: 7.4 or higher
- Active installations: Over 100,000
The vulnerability is exploitable by any authenticated user with Contributor level access or above (Contributor, Author, Editor, or Administrator roles). Sites that allow open user registration with Contributor roles are at the highest risk.
Both the free version available on the WordPress plugin directory and the commercial Admin Columns Pro version should be evaluated, as the vulnerable code path exists in the core plugin's formatter classes and bundled vendor libraries.
Vendor Security History
The Wordfence Threat Intelligence database maintains a dedicated vulnerability listing page for the Admin Columns plugin, indicating that CVE-2026-7654 is not the first security issue discovered in this software. WPScan also maintains a separate vulnerability listing for the plugin.
A comparable authenticated PHP Object Injection vulnerability was discovered in the "Filter Custom Fields & Taxonomies Light" plugin (versions up to 1.05), which shares the same fundamental weakness pattern: insecure deserialization of custom field meta values in WordPress plugins. This suggests a recurring vulnerability class across the WordPress plugin ecosystem, particularly in plugins that process serialized post meta data.
CodePress's response to CVE-2026-7654 was timely. The patched version 7.0.19 was released on May 29, 2026, approximately one week before the CVE was formally published on the NVD on June 5, 2026. This timeline suggests responsible disclosure coordination between the vulnerability discoverer, Osvaldo Noe Gonzalez Del Rio, and the vendor, with the patch being made available before public disclosure.
References
- NVD Entry for CVE-2026-7654
- Wordfence Threat Intelligence Advisory: Admin Columns <= 7.0.18 PHP Object Injection to RCE
- Wordfence: Admin Columns Vulnerability Listing
- Admin Columns on WordPress Plugin Directory
- Vulnerable Code: IdsToCollection.php (line 42)
- Vulnerable Code: Meta.php (line 34)
- POP Chain: Native.php Serializer (line 148)
- POP Chain: ClosureStream.php (line 47)
- Plugin Repository Changeset 3553297 (Fix)
- CWE-502: Deserialization of Untrusted Data
- OWASP: PHP Object Injection
- WPScan: Admin Columns Vulnerabilities
- Patchstack: PHP Object Injection in WordPress



