nopriv is not a typo: unauthenticated post tampering in Easy Post Submission

Broken access control stays at the top of every application-security ranking for a boring reason: it is easy to introduce and invisible in a demo. The feature works, the happy path looks fine, and the missing check only matters when someone who was never supposed to reach the code path reaches it anyway. The Easy Post Submission plugin for WordPress, in all versions up to and including 2.3.0, is a clean, small example that is worth dissecting because the same mistake appears in plugin after plugin.

The defect in one sentence

The plugin registers its post-submission handler for unauthenticated users and then never checks whether the caller is allowed to do what the handler does.

The mechanics

WordPress exposes AJAX actions through two hooks. wp_ajax_{action} fires for logged-in users. wp_ajax_nopriv_{action} fires for everyone else, including completely unauthenticated visitors. Registering the second hook is a deliberate decision that says "this endpoint is meant to be reachable without logging in."

Easy Post Submission registers rbsm_submit_post through wp_ajax_nopriv_rbsm_submit_post, which is defensible for a front-end submission form. The problem is what the handler, create_post(), does when it receives a postId parameter. Instead of treating the request as the creation of a brand new pending submission, the presence of postId steers the code into modifying an existing post, and it does so without any capability check, nonce enforcement that binds to an authorized user, or ownership test.

So an unauthenticated attacker supplies a postId for any post on the site and the handler happily rewrites it. The reachable fields are not cosmetic: title, content, excerpt, categories, and tags, plus the post status. Setting the status to draft unpublishes the target, which means the endpoint is both a content-integrity problem and an availability problem in the same request.

What an attacker can do with it

Consider the range. An attacker can silently edit the content of a published article to insert misinformation, defacement, spam, or links that poison the site's reputation and search ranking. They can retitle posts, recategorize them, or bury them by flipping the status to draft so they vanish from the site without deleting anything, which delays detection because nothing looks broken, content simply disappears. On a busy publication this is a slow, deniable form of sabotage that does not trip the alarms that a full compromise would.

Because the modification is unauthenticated and driven entirely by an attacker-supplied postId, it is also trivially scriptable across every post ID in a range. There is no session to steal and no credential to phish. The authorization that should have stood between the request and the database was simply never written.

The pattern to recognize

This is the archetype of a missing capability check, and it has a recognizable signature in code review. An AJAX action is registered for nopriv. The handler reads an object identifier from the request. It then performs a privileged operation on that object without calling current_user_can(), without verifying a nonce that is bound to an authorized session, and without checking that the caller owns the object. Any two of those three absences is usually enough to break. All three, on an unauthenticated endpoint, is a remote takeover of the affected data.

The fix is equally standard: the handler must verify that the caller is permitted to edit the specific post before touching it, reject nopriv callers from any code path that modifies existing content, and treat the front-end submission path as strictly create-only, never update, when the request arrives without an authenticated, capability-bearing user.

How to tell if you are exposed

Check the plugin version against 2.3.0 and treat anything at or below it as vulnerable if the plugin is active. Then grep your own custom plugins and themes for the same shape: wp_ajax_nopriv_ registrations whose handlers call post, user, or option modification functions. That one grep finds a surprising amount of latent exposure in bespoke code. In your logs, look for POST requests to admin-ajax.php carrying the rbsm_submit_post action with a postId parameter from unauthenticated sources, and compare post revision histories for edits that no logged-in user made.

What to do today

Update Easy Post Submission past 2.3.0. If an update is not yet available in your change window, disable the plugin, because an unauthenticated write path into your content store is not something to leave running. After patching, review recent post revisions for unauthorized edits and status changes, restore any tampered content from a known-good revision, and re-index so search engines pick up the corrected pages.

Reference

CELVEX Group tracks this class in the scanner catalog and tests unauthenticated AJAX actions for missing authorization directly, since the version banner tells you nothing about the vendored and copy-pasted variants of this same mistake living in custom code. Refer to the vendor advisory for the patched release.