Skip to content

fix: enforce submission gate in AJAX submit_post (unauthenticated post via subscription-gated form) - #1928

Open
arifulhoque7 wants to merge 2 commits into
weDevsOfficial:developfrom
arifulhoque7:fix/guest-post-subscription-bypass
Open

fix: enforce submission gate in AJAX submit_post (unauthenticated post via subscription-gated form)#1928
arifulhoque7 wants to merge 2 commits into
weDevsOfficial:developfrom
arifulhoque7:fix/guest-post-subscription-bypass

Conversation

@arifulhoque7

@arifulhoque7 arifulhoque7 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Vulnerability

[WPScan] Unauthenticated post creation through a subscription-gated form (CVSS 5.3).

Frontend_Form_Ajax::submit_post() never called Form::is_submission_open(), so the mandatory-subscription / pack-ownership / post-count gate that the renderer enforces (includes/Frontend/Frontend_Form.php) was skipped on the write path. It verified only the wpuf_form_add nonce, which for logged-out visitors is a site-wide constant — an attacker could scrape it from any guest form and replay it against a subscription-gated form's ID:

  • Post created (published where the gated form publishes), with no order record and no pending-payment status.
  • Not just a payment bypass: where the open form moderates and the gated form publishes, this grants immediate unreviewed publication.

Fix (minimal, mirrors the render path)

Re-check the gate server-side for new posts before building the post array, using the same call the renderer uses:

[ $user_can_post, $submission_info ] = $form->is_submission_open( $form, $this->form_settings );
$user_can_post = apply_filters( 'wpuf_can_post', $user_can_post, $form_id, $this->form_settings );
if ( 'yes' !== $user_can_post ) { wpuf()->ajax->send_error( ... ); }

Non-breaking

  • Guest forms without charging, pay-per-post forms, and logged-in users with a valid pack all resolve to yes (unchanged).
  • Only new submissions are gated (post_id unset); edits are untouched.
  • The wpuf_can_post filter is preserved, so Pro/extension grants still apply.

Verified — before / after (local WP, real guest HTTP requests, no cookies)

Gated form = post_permission=guest_post + payment_options=on + choose_payment_option=force_pack_purchase + post_status=publish. Guest wpuf_form_add nonce scraped/minted for user 0.

Submit to the gated form:

Result
Before (develop) {"success":true,…"message":"Post saved"} — post published, no order
After (patch) {"success":false,…"error":"You need to purchase a subscription package to post in this form"}no post created

Legit non-gated guest form, on the patched code:
{"success":true,…"message":"Post saved"} — post created. Legitimate guest posting is unaffected.

Note on nonce binding

The report also suggests binding the nonce to the form ID. That changes the shared guest nonce contract (JS + guest-page caching) and risks breaking legitimate guest submissions, so it's intentionally out of scope here — the server-side gate closes the reported bypass on its own. Can follow up separately if desired.

Reported by external security researcher Charles Vosburgh — please credit in the changelog/advisory.

Closes weDevsOfficial/wpuf-pro#1653.

…orm)

Frontend_Form_Ajax::submit_post() never called Form::is_submission_open(),
so the mandatory-subscription / pack-ownership / post-limit gate enforced by
the renderer was skipped. A logged-out visitor could scrape the site-wide
guest wpuf_form_add nonce from any guest form and replay it against a
subscription-gated form's ID, creating a post (published where the form
publishes) with no order and no pending-payment status.

Re-check the gate server-side for new posts before building the post array,
mirroring the render path (same is_submission_open + wpuf_can_post filter).
Edits (post_id present) are unaffected; guest and pay-per-post forms that
legitimately allow submission still return 'yes'.

Reported by Charles Vosburgh. Closes weDevsOfficial/wpuf-pro#1653.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@arifulhoque7, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc9f2793-cd7d-4e89-a8aa-81e80bb1baba

📥 Commits

Reviewing files that changed from the base of the PR and between d730dc5 and c46688d.

📒 Files selected for processing (1)
  • includes/Ajax/Frontend_Form_Ajax.php

Walkthrough

The AJAX handler now revalidates submission access for new posts. It checks Form::is_submission_open(), applies the wpuf_can_post filter, and returns an error before post creation when submission is disallowed.

Changes

Submission gate enforcement

Layer / File(s) Summary
Validate submission access before post creation
includes/Ajax/Frontend_Form_Ajax.php
New submissions are checked with Form::is_submission_open() and the wpuf_can_post filter. Disallowed submissions return the form-specific message or a default error.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: needs: dev review

Poem

A rabbit checked the posting gate,
Before new posts could leave the slate.
Filters spoke, and errors showed,
The form now guards its submission roads.
Hop, hop—validation’s in the code!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue [#1653] by rechecking submission eligibility and preserving wpuf_can_post before creating new posts.
Out of Scope Changes check ✅ Passed The changes are limited to the AJAX submission handler and directly support the linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the AJAX submission-gate fix for unauthenticated posts, which matches the primary change.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@includes/Ajax/Frontend_Form_Ajax.php`:
- Around line 185-196: Compute a single $is_new_submission predicate near the
start of the submission flow using both post_id absence and wpuf_form_status ===
'new'. Use this predicate for the is_submission_open/wpuf_can_post gate, all
other new-submission-only branches, and the $is_update assignment, removing the
duplicated later status extraction so draft-backed new submissions cannot bypass
validation.
- Around line 186-194: Update the AJAX rejection branch in the submission
handler around is_submission_open so the selected submission restriction message
is passed through wpuf_addpost_notice before choosing or returning the fallback
text, matching the notice-filter contract in Frontend_Form.php and keeping
customized messages consistent between renderer and AJAX responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e392bb30-87fa-4674-9aab-7dee26c4dd3b

📥 Commits

Reviewing files that changed from the base of the PR and between c326034 and d730dc5.

📒 Files selected for processing (1)
  • includes/Ajax/Frontend_Form_Ajax.php

Comment thread includes/Ajax/Frontend_Form_Ajax.php Outdated
Comment thread includes/Ajax/Frontend_Form_Ajax.php
Address review: a submission with a post_id but wpuf_form_status 'new' is
treated as a new post by the update logic, so it must go through the same
subscription gate — key the check on both signals. Also run the rejection
message through wpuf_addpost_notice so AJAX and the renderer surface the
same customized notice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant