Skip to content

Fix unauthenticated SQL injection on the push REST endpoint (CVE-2026-15162) - #572

Merged
jonathanstegall merged 1 commit into
MinnPost:masterfrom
paulsheldrake:fix/cve-2026-15162-sqli-push-endpoint
Aug 17, 2026
Merged

Fix unauthenticated SQL injection on the push REST endpoint (CVE-2026-15162)#572
jonathanstegall merged 1 commit into
MinnPost:masterfrom
paulsheldrake:fix/cve-2026-15162-sqli-push-endpoint

Conversation

@paulsheldrake

Copy link
Copy Markdown
Contributor

Fixes #571.

The vulnerability (CVE-2026-15162)

The /wp-json/object-sync-for-salesforce/push REST route is exploitable by unauthenticated callers. Its permission callback, Object_Sync_Sf_Rest::can_process(), only checks the HTTP method for the push (and pull) classes — no capability or nonce check:

case 'push':
    if ( ! in_array( $http_method, array( 'POST', 'PUT' ), true ) ) {
        return new WP_Error( 'rest_forbidden', ... );
    }
    break;

The wordpress_object_type body parameter then flows, unsanitized, through:

process()Object_Sync_Sf_Salesforce_Push::manual_push()Object_Sync_Sf_WordPress::get_wordpress_object_data()get_wordpress_table_structure()

where, for post / attachment / custom-post-type objects, it is concatenated directly into the post_type WHERE clause:

'where' => 'AND ' . $this->wpdb->prefix . 'posts.post_type = "' . $object_type . '"',

That where string is later executed without $wpdb->prepare() in object_fields():

$select_meta = ' ... WHERE ' . $meta_table . '.meta_key != "" ' . $where . ' ';
$meta_fields = $this->wpdb->get_results( $select_meta );

Result: an unauthenticated request with a crafted wordpress_object_type and any valid wordpress_id yields blind SQL injection / data exfiltration.

The fix

  1. Root cause — bind the object type as a %s parameter via $wpdb->prepare() in all three post-type WHERE clauses in get_wordpress_table_structure(). The user-controlled value can no longer break out of the string literal.
  2. Defense in depth — reject any wordpress_object_type that is not a registered WordPress object type (get_object_types()) at the REST boundary in process(), returning 400, so untrusted input never reaches the database layer on this unauthenticated endpoint.

Legitimate callers are unaffected: real object types (user, post, registered CPTs, etc.) pass the allow-list, and $wpdb->prepare() produces the same query for valid post-type names.

Verification

  • php -l clean on both changed files.
  • Runtime check: $wpdb->prepare( 'AND ' . $wpdb->prefix . 'posts.post_type = %s', 'cpt" OR 1=1 -- -' ) produces AND wp_posts.post_type = 'cpt\" OR 1=1 -- -' — the payload is an inert quoted literal.
  • With the REST guard, POST /wp-json/object-sync-for-salesforce/push carrying an injection string as wordpress_object_type returns 400 Invalid WordPress object type instead of executing SQL.

Happy to add a changelog.md / readme entry or adjust the approach (e.g. tightening can_process() instead of / in addition to the allow-list) if you'd prefer.

…-15162)

The `/wp-json/object-sync-for-salesforce/push` route's permission callback
(`Object_Sync_Sf_Rest::can_process()`) only validates the HTTP method, so the
endpoint is reachable by unauthenticated callers. The `wordpress_object_type`
body parameter then flows unsanitized through `process()` -> `manual_push()` ->
`get_wordpress_object_data()` -> `get_wordpress_table_structure()`, where for
post / attachment / custom-post-type objects it is concatenated directly into
the `post_type` WHERE clause. That clause is later executed without
`$wpdb->prepare()` in `object_fields()`, allowing blind SQL injection and data
exfiltration with only a valid `wordpress_id`.

Fix:

- Bind the object type via `$wpdb->prepare( ... %s )` in all three post-type
  WHERE clauses in `get_wordpress_table_structure()` (root cause).
- Reject object types that are not registered WordPress objects at the REST
  boundary in `Object_Sync_Sf_Rest::process()` before they reach the database
  layer (defense in depth for the unauthenticated endpoint).

Refs: CVE-2026-15162, MinnPost#571
@jonathanstegall jonathanstegall added the bug fix Pull request that fixes a bug label Aug 17, 2026
@jonathanstegall jonathanstegall added this to the v2.2.14 milestone Aug 17, 2026
@jonathanstegall
jonathanstegall merged commit 610ba7c into MinnPost:master Aug 17, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug fix Pull request that fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CVE-2026-15162

2 participants